diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 000000000..0ef019adc --- /dev/null +++ b/.dockerignore @@ -0,0 +1,8 @@ +*Dockerfile +.dockerignore +.git +.vagrant +contrib +docs +tests +!contrib/dokku-installer.py diff --git a/.editorconfig b/.editorconfig index 514928225..0643f4310 100644 --- a/.editorconfig +++ b/.editorconfig @@ -14,3 +14,8 @@ indent_size = 4 insert_final_newline = true indent_style = tab indent_size = 4 + +[*.go] +insert_final_newline = true +indent_style = space +indent_size = 4 diff --git a/.gitignore b/.gitignore index 033d317f2..2794757ee 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ *.deb +*.rpm .DS_Store .ruby-version .vagrant diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 1599d2c3d..4e8a25fde 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -55,7 +55,7 @@ doesn't already include that problem or suggestion before submitting an issue. If you find a match, add a quick "+1" or "I have this problem too." Doing this helps prioritize the most common problems and requests. -When reporting issues, please include your host OS (Ubuntu 14.04, Fedora 19, +When reporting issues, please include your host OS (Ubuntu 14.04, CentOS 7, etc). Please include: * The output of `uname -a`. diff --git a/HISTORY.md b/HISTORY.md index 601a8ac7f..45894ba88 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -1,5 +1,201 @@ # History +## 0.9.4 + +### Documentation + +- #2710: @josegonzalez Quiet output for git-related commands + +## 0.9.3 + +### Bug Fixes + +- #2706: @josegonzalez fix: ensure nginx conf.d directory exists when running nginx install hook +- #2701: @scjody Set SSH_USER for root commands + +### New Features + +- #2708: @josegonzalez Document that we will not do buildpack support in the issue tracker + +### Documentation + +- #2709: @michaelshobbs increase CURL_TIMEOUT and CURL_CONNECT_TIMEOUT defaults +- #2699: @mbreit Add support for git >= 2.11 + +## 0.9.2 + +### New Features + +- #2698: @josegonzalez docs: Document that we only care about specific sections +- #2697: @callahad Restore installer note regarding AUFS on Linode +- #2694: @scjody Add documentation note re: git-pre-pull vs. auth + +### Documentation + +- #2695: @michaelshobbs add tests for pre/post deploy tasks + +## 0.9.1 + +### Bug Fixes + +- #2693: @josegonzalez fix: explicitly chown data and data/storage directories + +## 0.9.0 + +### Bug Fixes + +- #2691: @josegonzalez Fix package building when golang binaries are available +- #2671: @znz Fix variable name +- #2672: @callahad Fix logrotate on Debian +- #2666: @josegonzalez Use correct flag for build arguments when installing herokuish +- #2664: @pvalentim Fix remote name when using --remote option with apps:create + +### New Features + +- #2689: @mbreit Add dokku-monit to community plugin list +- #2683: @josegonzalez Ensure we have an example for adding keys as another user +- #2682: @josegonzalez Clarify supported stanzas in app.json +- #2679: @callahad Remove unnecessary Linode-specific instructions +- #2670: @znz Remove duplicated `(i.e. ` + +### Documentation + +- #2685: @josegonzalez Pass shellcheck on os x +- #2677: @callahad Prefer HTTP2 to SPDY in nginx-vhosts +- #2673: @michaelshobbs Update to herokuish 0.3.27 +- #2674: @michaelshobbs Update sshcommand to 0.7.0 +- #2654: @ebeigarts Enable nginx and docker on system startup when using bootstrap.sh on CentOS +- #2546: @michaelshobbs Convert repo plugin to golang + +## 0.8.2 + +### New Features + +- #2660: @josegonzalez allow installation of plugins via tarball +- #2661: @josegonzalez Do not run builds in quiet mode + +## 0.8.1 + +### Bug Fixes + +- #2519: @tkalus Further guard against duplicate ssl server names +- #2554: @josegonzalez Add ssl ports when generating a self-signed certificate +- #2555: @josegonzalez Skip failing applications when running ps:restore on boot +- #2576: @znz Remove unused variable +- #2592: @josegonzalez always set a default ssl port for apps with ssl enabled +- #2612: @josegonzalez Ensure VHOST files exist before executing commands against them +- #2647: @josegonzalez Properly escape post-install variables +- #2650: @josegonzalez Fix help output for nginx and ssh-keys +- #2656: @josegonzalez ensure we can call the report subcommand without an app while specifying flags +- d79a79: @josegonzalez bail early when checking ps output for an undeployed app + +### New Features + +- #2500: @josegonzalez Suppress output unless the `git submodule update` call fails +- #2504: @mbtamuli Implement apps:report and storage:report +- #2508: @OmarShehata Add default functions for all commands +- #2557: @josegonzalez Dokku cli improvements +- #2573: @ebeigarts Recommend parallel package for faster ps:restore +- #2578: @josegonzalez Require specific versions for dokku-maintained packages +- #2583: @josegonzalez Implement apps:clone subcommand +- #2586: @bevand10 Add http-proxy support for deb-herokuish installs +- #2587: @josegonzalez Allow specifying the deploy branch via DOKKU_DEPLOY_BRANCH +- #2594: @michaelshobbs Upgrade to herokuish v0.3.25 +- #2615: @josegonzalez Implement certs:report +- #2616: @josegonzalez Replace apps:default subcommand with apps:list +- #2617: @josegonzalez Implement checks:report +- #2618: @josegonzalez Implement docker-options:report and storage:report +- #2619: @josegonzalez Call ssh-keys:help from ssh-keys:default +- #2620: @josegonzalez Implement domains:report and proxy:report +- #2622: @raphaklaus Allow file names with multiple dots in certs:add command +- #2634: @michaelshobbs Update herokuish to 0.3.26 +- #2657: @josegonzalez Add post-extract plugin trigger + +### Documentation + +- #2475: @pranavgoel25 Minor readme and sponsor changes +- #2556: @josegonzalez Use slightly better font style for docs +- #2560: @ebeigarts Improve install documentation on CentOS +- #2564: @ka7 Fix spelling mistakes +- #2565: @josegonzalez Update persistent storage to reference the sample app as normal +- #2566: @josegonzalez Move "new as of" note in storage docs to correct section +- #2569: @OmgImAlexis Reload nginx after adding default vhost file +- #2570: @OmgImAlexis Update ISSUE_TEMPLATE.md to reference `dokku report` command +- #2580: @znz Fix font URL +- #2588: @josegonzalez Clarify when the `~/.ssh/config` settings need to match `vagrant ssh-config output` +- #2605: @andyjeffries Documented build failures when using SSL_CERT_FILE environment variable +- #2613: @emveeoh Update Linode installation instructions for new GRUB 2 Linode boot option +- #2626: @znz Update apps:help example command +- #2629: @znz Update certs:help output for certs:default subcommand +- #2633: @drpoggi Reference the x-forwarded headers in correct order +- #2637: @josegonzalez Add documentation surrounding flags that ps:report accepts +- #2638: @fwolfst Improve README links +- #2639: @joshco Add documentation for how to grant other Unix accounts Dokku access +- #2648: @josegonzalez Note that the remote username is important +- #2659: @jfw Minor clarifications to application deployment tutorial + +## 0.8.0 + +The big kahuna. Lots of documentation changes, and a few bug fixes to make Dokku development a bit easier. + +CentOS 7 users will be happy to see that we now have experimental support for your operating system. Huge thanks to @ebeigarts for working on that feature :) + +Thanks to all the contributors who helped with this release! + +### Bug Fixes + +- #2407: @josegonzalez Move core post-deploy triggers to core-post-deploy +- #2442: @znz Fix `is_tag_force_available` bug when docker major version up +- #2452: @mbtamuli Solves SSH Key problem when admin user already exists +- #2454: @onbjerg Return exit 1 in config:get if no ENV file exists +- #2464: @sseemayer Remove duplicate SSL hostnames +- #2465: @polettix Fix issue when importing ssh-keys +- #2477: @ebeigarts Fix dokku-redeploy systemd script to start only after docker +- #2485: @znz Fix bug when VHOST file is missing newline +- #2492: @josegonzalez Fix iteration on all apps for `dokku proxy` command +- #2495: @josegonzalez Create the user's `authorized_keys` file if it does not exist +- #2496: @josegonzalez Detect nginx versions that support HTTP/2 well +- #2518: @josegonzalez Use same check for dockerfile apps during a tar build +- #2526: @michaelshobbs Actually fail deploy when app.json script fails +- #2539: @ebeigarts Fix dokku-installer.service removal + +### New Features + +- #2378: @knjcode Skip container finish processing when zero downtime is disabled +- #2406: @josegonzalez Use apps_create method when renaming an application +- #2419: @ebeigarts Support for CentOS 7 +- #2489: @joshmanders Add plugin uninstall trigger +- #2510: @IlyaSemenov Add domains:set and domains:set-global commands +- #2544: @michaelshobbs Update herokuish to 0.3.24 +- #2552: @josegonzalez Allow package building on OSX +- #2553: @josegonzalez Add release-related Dockerfiles + +### Documentation + +- #2432: @josegonzalez Clarify DOKKU_SCALE docs +- #2433: @alexgleason Add robots.txt plugin to community docs +- #2437: @vishnubhagwan Fix warning in installation docs +- #2451: @mbtamuli Document the logs plugin +- #2470: @fteychene Add build-hook to plugins doc +- #2472: @mainto Add dokku-access to plugins doc +- #2484: @nahtnam Document build issues when a `Killed` message is displayed +- #2486: @OmarShehata Fixing typo & broken link in docs +- #2488: @joshmanders Bump font size in documentation +- #2493: @josegonzalez Clarify checks documentation +- #2497: @joshmanders Make features heading more clear +- #2503: @mlebkowski Update dokku-acl plugin link +- #2505: @kjschulz Add Dokku Wordpress plugin to docs +- #2506: @simonkotwicz Fix typos in deployment docs +- #2507: @josegonzalez Update upgrade docs to point out sshcommand and plugn upgrading as well +- #2509: @kjschulz Map dokku-community user in plugins +- #2511: @IlyaSemenov Clarify instructions for arranging default nginx site +- #2515: @sgloutnikov Update DreamHost Cloud install instructions +- #2517: @josegonzalez Update docs regarding if the ssh-keys plugin should be in use +- #2522: @joshmanders Ensure install documentation can be run via copy-paste +- #2525: @OmarShehata Plugin-triggers typo: dokkku -> dokku +- #2531: @slava-vishnyakov Document rebuilding app after mounting storage +- #2533: @facundomedica Document potential firewall problem on Ubuntu 16.04 + ## 0.7.2 This minor release contains mostly documentation changes, and should be fully backwards compatible with previous 0.7.x releases. @@ -962,7 +1158,7 @@ Thanks to the *many* contributors for making this release our best release so fa - #1473: @josegonzalez Handle crashing containers by using restart=on-failure policy - #1476: @michaelshobbs Support static nginx port when deploying without an application VHOST - #1476: nginx proxy without VHOST -- #1477: @arthurschreiber Support removing config variables that contain `\n`. +- #1477: @arthurschreiber Support removing config variables containing newlines. ### Documentation @@ -1108,7 +1304,7 @@ This release pegs Dokku to Docker 1.6.2. Docker 1.7.0 introduced changes in `doc ### New Features -- #1245: @arthurschreiber Support config variables containing `\n` +- #1245: @arthurschreiber Support config variables containing newlines - #1257: @josegonzalez Split nginx ssl logs by $APP ### Bug Fixes diff --git a/ISSUE_TEMPLATE.md b/ISSUE_TEMPLATE.md index 0917e8bcc..69cb82f1c 100644 --- a/ISSUE_TEMPLATE.md +++ b/ISSUE_TEMPLATE.md @@ -1,8 +1,12 @@ +> If you need help figuring out how to use a specific buildpack, or are having issues using multiple buildpacks, please see our [irc or slack channels](http://dokku.viewdocs.io/dokku/getting-started/where-to-get-help/#the-irc-and-slack-channels). Issues pertaining to buildpacks may be closed and locked. + Description of problem: Output of the following commands + + - `uname -a`: - `free -m`: - `docker version`: diff --git a/Makefile b/Makefile index fb4beee80..9c77fdbd9 100644 --- a/Makefile +++ b/Makefile @@ -1,13 +1,14 @@ -DOKKU_VERSION = master +DOKKU_VERSION ?= master SSHCOMMAND_URL ?= https://raw.githubusercontent.com/dokku/sshcommand/v0.6.0/sshcommand -PLUGN_URL ?= https://github.com/dokku/plugn/releases/download/v0.2.2/plugn_0.2.2_linux_x86_64.tgz +PLUGN_URL ?= https://github.com/dokku/plugn/releases/download/v0.3.0/plugn_0.3.0_linux_x86_64.tgz SIGIL_URL ?= https://github.com/gliderlabs/sigil/releases/download/v0.4.0/sigil_0.4.0_Linux_x86_64.tgz STACK_URL ?= https://github.com/gliderlabs/herokuish.git PREBUILT_STACK_URL ?= gliderlabs/herokuish:latest DOKKU_LIB_ROOT ?= /var/lib/dokku PLUGINS_PATH ?= ${DOKKU_LIB_ROOT}/plugins CORE_PLUGINS_PATH ?= ${DOKKU_LIB_ROOT}/core-plugins +PLUGIN_MAKE_TARGET ?= build-in-docker # If the first argument is "vagrant-dokku"... ifeq (vagrant-dokku,$(firstword $(MAKECMDGOALS))) @@ -23,10 +24,13 @@ else BUILD_STACK_TARGETS = build-in-docker endif -.PHONY: all apt-update install version copyfiles man-db plugins dependencies sshcommand plugn docker aufs stack count dokku-installer vagrant-acl-add vagrant-dokku +include common.mk + +.PHONY: all apt-update install version copyfiles man-db plugins dependencies sshcommand plugn docker aufs stack count dokku-installer vagrant-acl-add vagrant-dokku go-build include tests.mk include deb.mk +include rpm.mk include arch.mk all: @@ -34,18 +38,39 @@ all: install: dependencies version copyfiles plugin-dependencies plugins -release: deb-all package_cloud packer +release: deb-all rpm-all package_cloud packer package_cloud: package_cloud push dokku/dokku/ubuntu/trusty herokuish*.deb package_cloud push dokku/dokku/ubuntu/trusty sshcommand*.deb package_cloud push dokku/dokku/ubuntu/trusty plugn*.deb package_cloud push dokku/dokku/ubuntu/trusty dokku*.deb + package_cloud push dokku/dokku/el/7 herokuish*.rpm + package_cloud push dokku/dokku/el/7 sshcommand*.rpm + package_cloud push dokku/dokku/el/7 plugn*.rpm + package_cloud push dokku/dokku/el/7 dokku*.rpm packer: packer build contrib/packer.json +go-build: + basedir=$(PWD); \ + for dir in plugins/*; do \ + if [ -e $$dir/Makefile ]; then \ + $(MAKE) -e -C $$dir $(PLUGIN_MAKE_TARGET) || exit $$? ;\ + fi ;\ + done + +go-clean: + basedir=$(PWD); \ + for dir in plugins/*; do \ + if [ -e $$dir/Makefile ]; then \ + $(MAKE) -e -C $$dir clean ;\ + fi ;\ + done + copyfiles: + $(MAKE) go-build || exit 1 cp dokku /usr/local/bin/dokku mkdir -p ${CORE_PLUGINS_PATH} ${PLUGINS_PATH} rm -rf ${CORE_PLUGINS_PATH}/* @@ -57,21 +82,29 @@ copyfiles: rm -rf ${CORE_PLUGINS_PATH}/$$plugin && \ rm -rf ${PLUGINS_PATH}/$$plugin && \ cp -R plugins/$$plugin ${CORE_PLUGINS_PATH}/available && \ + rm -rf ${CORE_PLUGINS_PATH}/available/$$plugin/src && \ ln -s ${CORE_PLUGINS_PATH}/available/$$plugin ${PLUGINS_PATH}/available; \ find /var/lib/dokku/ -xtype l -delete;\ PLUGIN_PATH=${CORE_PLUGINS_PATH} plugn enable $$plugin ;\ PLUGIN_PATH=${PLUGINS_PATH} plugn enable $$plugin ;\ - done + done +ifndef SKIP_GO_CLEAN + $(MAKE) go-clean +endif chown dokku:dokku -R ${PLUGINS_PATH} ${CORE_PLUGINS_PATH} || true $(MAKE) addman -addman: help2man +addman: help2man man-db mkdir -p /usr/local/share/man/man1 help2man -Nh help -v version -n "configure and get information from your dokku installation" -o /usr/local/share/man/man1/dokku.1 dokku mandb version: +ifeq ($(DOKKU_VERSION),master) git describe --tags > ~dokku/VERSION 2> /dev/null || echo '~${DOKKU_VERSION} ($(shell date -uIminutes))' > ~dokku/VERSION +else + echo $(DOKKU_VERSION) > ~dokku/VERSION +endif plugin-dependencies: plugn sudo -E dokku plugin:install-dependencies --core diff --git a/README.md b/README.md index f52a76cb1..282e51b38 100644 --- a/README.md +++ b/README.md @@ -1,43 +1,16 @@ -# Dokku [![Build Status](https://img.shields.io/circleci/project/dokku/dokku/master.svg?style=flat-square "Build Status")](https://circleci.com/gh/dokku/dokku/tree/master) [![Ubuntu Package](https://img.shields.io/badge/package-ubuntu-brightgreen.svg?style=flat-square "Ubuntu Package")](https://packagecloud.io/dokku/dokku) [![Arch Package](https://img.shields.io/badge/package-arch-brightgreen.svg?style=flat-square "Arch Package")](https://aur.archlinux.org/packages/dokku/) [![IRC Network](https://img.shields.io/badge/irc-freenode-blue.svg?style=flat-square "IRC Freenode")](https://webchat.freenode.net/?channels=dokku) [![Slack Group](https://img.shields.io/badge/irc-slack-blue.svg?style=flat-square "Slack Group")](https://glider-slackin.herokuapp.com/) [![Documentation](https://img.shields.io/badge/docs-viewdocs-blue.svg?style=flat-square "Viewdocs")](http://dokku.viewdocs.io/dokku/) +# Dokku +[![Build Status](https://img.shields.io/circleci/project/dokku/dokku/master.svg?style=flat-square "Build Status")](https://circleci.com/gh/dokku/dokku/tree/master) +[![Ubuntu Package](https://img.shields.io/badge/package-ubuntu-brightgreen.svg?style=flat-square "Ubuntu Package")](https://packagecloud.io/dokku/dokku) +[![Arch Package](https://img.shields.io/badge/package-arch-brightgreen.svg?style=flat-square "Arch Package")](https://aur.archlinux.org/packages/dokku/) +[![IRC Network](https://img.shields.io/badge/irc-freenode-blue.svg?style=flat-square "IRC Freenode")](https://webchat.freenode.net/?channels=dokku) +[![Slack Group](https://img.shields.io/badge/irc-slack-blue.svg?style=flat-square "Slack Group")](https://glider-slackin.herokuapp.com/) +[![Documentation](https://img.shields.io/badge/docs-viewdocs-blue.svg?style=flat-square "Viewdocs")](http://dokku.viewdocs.io/dokku/) [![OpenCollective](https://opencollective.com/dokku/sponsors/badge.svg?style=flat-square)](#sponsors) [![OpenCollective](https://opencollective.com/dokku/backers/badge.svg?style=flat-square)](#backers) [![Gratipay](https://img.shields.io/gratipay/dokku.svg?style=flat-square)](https://gratipay.com/dokku/) Docker powered mini-Heroku. The smallest PaaS implementation you've ever seen. -## Requirements - -- A fresh VM running `Debian 8.2 x64`, `Ubuntu 14.04 x64`, or `Ubuntu 16.04 x64` - -## Installing - -To install the latest stable release, you can run the following commands as a user that has access to `sudo`: - - wget https://raw.githubusercontent.com/dokku/dokku/v0.7.2/bootstrap.sh - sudo DOKKU_TAG=v0.7.2 bash bootstrap.sh - -You can then proceed to the ip address or domain name associated with your server to complete the web-based installation. - -If you wish for a more unattended installation method, see [these](http://dokku.viewdocs.io/dokku/getting-started/install/debian/#unattended-installation) docs. - -### Upgrading - -[View the docs for upgrading](http://dokku.viewdocs.io/dokku/getting-started/upgrading/) from an older version of Dokku. - -## Documentation - -Full documentation - including advanced installation docs - are available online at [docs](http://dokku.viewdocs.io/dokku/) - -## Support - -You can use [Github Issues](https://github.com/dokku/dokku/issues), check [Troubleshooting](http://dokku.viewdocs.io/dokku/getting-started/troubleshooting/) in the documentation, or join us on [freenode in #dokku](https://webchat.freenode.net/?channels=%23dokku) - -## Contribution - -After checking [Github Issues](https://github.com/dokku/dokku/issues), the [Troubleshooting Guide](http://dokku.viewdocs.io/dokku/getting-started/troubleshooting/) or having a chat with us on [freenode in #dokku](https://webchat.freenode.net/?channels=%23dokku), feel free to fork and create a Pull Request. - -While we may not merge your PR as is, they serve to start conversations and improve the general Dokku experience for all users. - ## Sponsors See our list of past sponsors in the [SPONSORS](https://github.com/dokku/dokku/blob/master/SPONSORS.md) file. @@ -111,6 +84,47 @@ Support us with a monthly donation and help us continue our activities. [[Become +## Requirements + +A fresh VM running any of the following operating systems: + +- Ubuntu 16.04 x64 +- Ubuntu 14.04 x64 +- Debian 8.2 x64 +- CentOS 7 x64 *(experimental)* +- Arch Linux x64 *(experimental)* + +## Installation + +To install the latest stable release, run the following commands as a user who has access to `sudo`: + +```shell +wget https://raw.githubusercontent.com/dokku/dokku/v0.9.4/bootstrap.sh +sudo DOKKU_TAG=v0.9.4 bash bootstrap.sh +``` + +You can then proceed to the ip address or domain name associated with your server to complete the web-based installation. + +If you wish for a more unattended installation method, see [these](http://dokku.viewdocs.io/dokku/getting-started/install/debian/#unattended-installation) docs. + +### Upgrade + +[View the docs](http://dokku.viewdocs.io/dokku/getting-started/upgrading/) for upgrading from an older version of Dokku. + +## Documentation + +Full documentation - including advanced installation docs - are available online at [http://dokku.viewdocs.io/dokku/](http://dokku.viewdocs.io/dokku/). + +## Support + +You can use [Github Issues](https://github.com/dokku/dokku/issues), check [Troubleshooting](http://dokku.viewdocs.io/dokku/getting-started/troubleshooting/) in the documentation, or join us on [freenode in #dokku](https://webchat.freenode.net/?channels=%23dokku). + +## Contribution + +After checking [Github Issues](https://github.com/dokku/dokku/issues), the [Troubleshooting Guide](http://dokku.viewdocs.io/dokku/getting-started/troubleshooting/) or having a chat with us on [freenode in #dokku](https://webchat.freenode.net/?channels=%23dokku), feel free to fork and create a Pull Request. + +While we may not merge your PR as is, they serve to start conversations and improve the general Dokku experience for all users. + ## License [MIT License](https://github.com/dokku/dokku/blob/master/LICENSE) © Jeff Lindsay diff --git a/SPONSORS.md b/SPONSORS.md index 4c7c0b9bc..eb1cd5b87 100644 --- a/SPONSORS.md +++ b/SPONSORS.md @@ -1,3 +1,5 @@ +#Sponsors + [Deis](http://deis.io) [DigitalOcean](https://www.digitalocean.com/) diff --git a/Vagrantfile b/Vagrantfile index 4f3fc6c60..53f578c8d 100644 --- a/Vagrantfile +++ b/Vagrantfile @@ -76,13 +76,22 @@ Vagrant::configure("2") do |config| vm.vm.provision :shell, :inline => "cd /root/dokku && make install-from-deb" end + config.vm.define "dokku-rpm", autostart: false do |vm| + vm.vm.box = "centos/7" + vm.vm.synced_folder File.dirname(__FILE__), "/root/dokku" + vm.vm.network :forwarded_port, guest: 80, host: FORWARDED_PORT + vm.vm.hostname = "#{DOKKU_DOMAIN}" + vm.vm.network :private_network, ip: DOKKU_IP + vm.vm.provision :shell, :inline => "cd /root/dokku && bash bootstrap.sh" + end + config.vm.define "build", autostart: false do |vm| vm.vm.synced_folder File.dirname(__FILE__), "/root/dokku" vm.vm.network :forwarded_port, guest: 80, host: FORWARDED_PORT vm.vm.hostname = "#{DOKKU_DOMAIN}" vm.vm.network :private_network, ip: DOKKU_IP vm.vm.provision :shell, :inline => "export DEBIAN_FRONTEND=noninteractive && apt-get update > /dev/null && apt-get -qq -y install git > /dev/null && cd /root/dokku && #{make_cmd}" - vm.vm.provision :shell, :inline => "export IS_RELEASE=true && cd /root/dokku && make deb-all" + vm.vm.provision :shell, :inline => "export IS_RELEASE=true && cd /root/dokku && make deb-all rpm-all" end config.vm.define "build-arch", autostart: false do |vm| diff --git a/arch.mk b/arch.mk index c9b9159a5..c10f17186 100644 --- a/arch.mk +++ b/arch.mk @@ -22,7 +22,11 @@ arch-setup: arch-dokku: arch-setup echo "-> Update package sums, create metadata file and test the build of the package" +ifeq ($(DOKKU_VERSION),master) git describe --tags > /tmp/VERSION +else + echo $(DOKKU_VERSION) > /tmp/VERSION +endif cat /tmp/VERSION | cut -d '-' -f 1 | cut -d 'v' -f 2 > /tmp/STABLE_VERSION sed -i -e "s/pkgver=.*/pkgver=`cat /tmp/STABLE_VERSION`/" /dokku-arch/PKGBUILD cd /dokku-arch; updpkgsums; mksrcinfo; makepkg -fd diff --git a/bootstrap.sh b/bootstrap.sh index b5aacb6ff..8c915b0ba 100755 --- a/bootstrap.sh +++ b/bootstrap.sh @@ -14,10 +14,6 @@ set -eo pipefail; [[ $TRACE ]] && set -x ensure-environment() { local FREE_MEMORY echo "Preparing to install $DOKKU_TAG from $DOKKU_REPO..." - if ! command -v apt-get &>/dev/null; then - echo "This installation script requires apt-get. For manual installation instructions, consult http://dokku.viewdocs.io/dokku/advanced-installation/" - exit 1 - fi hostname -f > /dev/null 2>&1 || { echo "This installation script requires that you have a hostname set for the instance. Please set a hostname for 127.0.0.1 in your /etc/hosts" @@ -33,10 +29,15 @@ ensure-environment() { install-requirements() { echo "--> Ensuring we have the proper dependencies" - apt-get update -qq > /dev/null - if [[ $(lsb_release -sr) == "12.04" ]]; then - apt-get -qq -y install python-software-properties - fi + + case "$DOKKU_DISTRO" in + debian|ubuntu) + apt-get update -qq > /dev/null + if [[ "$DOKKU_DISTRO_VERSION" == "12.04" ]]; then + apt-get -qq -y install python-software-properties + fi + ;; + esac } install-dokku() { @@ -68,9 +69,14 @@ install-dokku() { fi } - install-dokku-from-source() { local DOKKU_CHECKOUT="$1" + + if ! command -v apt-get &>/dev/null; then + echo "This installation script requires apt-get. For manual installation instructions, consult http://dokku.viewdocs.io/dokku/advanced-installation/" + exit 1 + fi + apt-get -qq -y install git make software-properties-common cd /root if [[ ! -d /root/dokku ]]; then @@ -84,6 +90,21 @@ install-dokku-from-source() { } install-dokku-from-package() { + case "$DOKKU_DISTRO" in + debian|ubuntu) + install-dokku-from-deb-package "$@" + ;; + centos) + install-dokku-from-rpm-package "$@" + ;; + *) + echo "Unsupported Linux distribution. For manual installation instructions, consult http://dokku.viewdocs.io/dokku/advanced-installation/" + exit 1 + ;; + esac +} + +install-dokku-from-deb-package() { local DOKKU_CHECKOUT="$1" local NO_INSTALL_RECOMMENDS=${DOKKU_NO_INSTALL_RECOMMENDS:=""} @@ -97,8 +118,8 @@ install-dokku-from-package() { echo "--> Installing docker" if uname -r | grep -q linode; then - echo "--> NOTE: Using Linode? Docker might complain about missing AUFS support." - echo " See http://dokku.viewdocs.io/dokku/getting-started/install/linode/" + echo "--> NOTE: Using Linode? Docker may complain about missing AUFS support." + echo " You can safely ignore this warning." echo " Installation will continue in 10 seconds." sleep 10 fi @@ -124,7 +145,43 @@ install-dokku-from-package() { fi } +install-dokku-from-rpm-package() { + local DOKKU_CHECKOUT="$1" + + if [[ "$DOKKU_DISTRO_VERSION" != "7" ]]; then + echo "Only CentOS version 7 is supported." + exit 1 + fi + + echo "--> Installing docker" + curl -fsSL https://get.docker.com/ | sh + + echo "--> Installing epel for nginx packages to be available" + yum install -y epel-release + + echo "--> Installing herokuish and dokku" + curl -s https://packagecloud.io/install/repositories/dokku/dokku/script.rpm.sh | bash + if [[ -n $DOKKU_CHECKOUT ]]; then + yum -y install herokuish "dokku-$DOKKU_CHECKOUT" + else + yum -y install herokuish dokku + fi + + echo "--> Enabling docker and nginx on system startup" + systemctl enable docker + systemctl enable nginx + + echo "--> Starting nginx" + systemctl start nginx +} + main() { + export DOKKU_DISTRO DOKKU_DISTRO_VERSION + # shellcheck disable=SC1091 + DOKKU_DISTRO=$(. /etc/os-release && echo "$ID") + # shellcheck disable=SC1091 + DOKKU_DISTRO_VERSION=$(. /etc/os-release && echo "$VERSION_ID") + export DEBIAN_FRONTEND=noninteractive export DOKKU_REPO=${DOKKU_REPO:-"https://github.com/dokku/dokku.git"} diff --git a/common.mk b/common.mk new file mode 100644 index 000000000..8b8f02501 --- /dev/null +++ b/common.mk @@ -0,0 +1,4 @@ +GO_REPO_ROOT := /go/src/github.com/dokku/dokku +BUILD_IMAGE := golang:1.7.5 + +.PHONY: build-in-docker build clean src-clean diff --git a/contrib/build.Dockerfile b/contrib/build.Dockerfile new file mode 100644 index 000000000..17ec3137d --- /dev/null +++ b/contrib/build.Dockerfile @@ -0,0 +1,44 @@ +FROM ubuntu:14.04 + +ENV DEBIAN_FRONTEND=noninteractive + +RUN apt-get update +RUN apt-get -y install gcc git build-essential wget ruby-dev ruby1.9.1 lintian rpm help2man man-db +RUN command -v fpm > /dev/null || sudo gem install fpm --no-ri --no-rdoc + +ARG GOLANG_VERSION + +RUN wget -qO /tmp/go${GOLANG_VERSION}.linux-amd64.tar.gz https://storage.googleapis.com/golang/go${GOLANG_VERSION}.linux-amd64.tar.gz \ + && tar -C /usr/local -xzf /tmp/go${GOLANG_VERSION}.linux-amd64.tar.gz + +ARG WORKDIR=/go/src/github.com/dokku/dokku + +WORKDIR ${WORKDIR} + +COPY Makefile ${WORKDIR}/ +COPY *.mk ${WORKDIR}/ + +RUN make deb-setup rpm-setup sshcommand plugn + +COPY . ${WORKDIR} + +ARG PLUGIN_MAKE_TARGET +ARG DOKKU_VERSION=master +ARG DOKKU_GIT_REV +ARG IS_RELEASE=false + +RUN PATH=$PATH:/usr/local/go/bin GOPATH=/go \ + PLUGIN_MAKE_TARGET=${PLUGIN_MAKE_TARGET} \ + DOKKU_VERSION=${DOKKU_VERSION} \ + DOKKU_GIT_REV=${DOKKU_GIT_REV} \ + IS_RELEASE=${IS_RELEASE} \ + SKIP_GO_CLEAN=true \ + make version copyfiles \ + && rm -rf plugins/common/*.go plugins/common/glide* plugins/common/vendor/ \ + && make deb-herokuish deb-dokku deb-plugn deb-sshcommand deb-sigil \ + rpm-herokuish rpm-dokku rpm-plugn rpm-sshcommand rpm-sigil + +RUN mkdir -p /data \ + && cp /tmp/*.deb /data \ + && cp /tmp/*.rpm /data \ + && ls -lha /data/ diff --git a/contrib/dokku-installer.py b/contrib/dokku-installer.py index b57cf3b63..1a153ea68 100755 --- a/contrib/dokku-installer.py +++ b/contrib/dokku-installer.py @@ -3,13 +3,14 @@ import cgi import json import os +import re import SimpleHTTPServer import SocketServer import subprocess import sys import threading -VERSION = 'v0.7.2' +VERSION = 'v0.9.4' hostname = '' try: @@ -100,8 +101,20 @@ class GetHandler(SimpleHTTPServer.SimpleHTTPRequestHandler): with open('{0}/HOSTNAME'.format(dokku_root), 'w') as f: f.write(params['hostname'].value) - command = ['sshcommand', 'acl-add', 'dokku', 'admin'] - for key in params['keys'].value.split("\n"): + for (index, key) in enumerate(params['keys'].value.splitlines(), 1): + user = 'admin' + if self.admin_user_exists() is not None: + user = 'web-admin' + if self.web_admin_user_exists() is not None: + index = int(self.web_admin_user_exists()) + 1 + elif self.web_admin_user_exists() is None: + index = 1 + elif self.admin_user_exists() is None: + pass + else: + index = int(self.admin_user_exists()) + 1 + user = user + str(index) + command = ['sshcommand', 'acl-add', 'dokku', user] proc = subprocess.Popen(command, stdin=subprocess.PIPE) proc.stdin.write(key) proc.stdin.close() @@ -119,6 +132,29 @@ class GetHandler(SimpleHTTPServer.SimpleHTTPRequestHandler): self.end_headers() self.wfile.write(json.dumps({'status': 'ok'})) + def web_admin_user_exists(self): + return self.user_exists('web-admin(\d+)') + + def admin_user_exists(self): + return self.user_exists('admin(\d+)') + + def user_exists(self, name): + command = 'dokku ssh-keys:list' + pattern = re.compile(r'NAME="' + name + '"') + proc = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE) + max_num = 0 + exists = False + for line in proc.stdout: + m = pattern.search(line) + if m: + # User of the form `user` or `user#` exists + exists = True + max_num = max(max_num, m.group(1)) + if exists: + return max_num + else: + return None + def set_debconf_selection(debconf_type, key, value): found = False diff --git a/contrib/dokku_client.sh b/contrib/dokku_client.sh index 6ed95a804..85c1c1480 100755 --- a/contrib/dokku_client.sh +++ b/contrib/dokku_client.sh @@ -1,10 +1,9 @@ #!/usr/bin/env bash set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x +export DOKKU_PORT=${DOKKU_PORT:=22} +export DOKKU_HOST=${DOKKU_HOST:=} -MOVES=(ABLE ABNORMA AGAIN AIREXPL ANG ANGER ASAIL ATTACK AURORA AWL BAN BAND BARE BEAT BEATED BELLY BIND BITE BLOC BLOOD BODY BOOK BREATH BUMP CAST CHAM CLAMP CLAP CLAW CLEAR CLI CLIP CLOUD CONTRO CONVY COOLHIT CRASH CRY CUT DESCRI D-FIGHT DIG DITCH DIV DOZ DRE DUL DU-PIN DYE EARTH EDU EG-BOMB EGG ELEGY ELE-HIT EMBODY EMPLI ENGL ERUPT EVENS EXPLOR EYES FALL FAST F-CAR F-DANCE FEARS F-FIGHT FIGHT FIR FIRE FIREHIT FLAME FLAP FLASH FLEW FORCE FRA FREEZE FROG G-BIRD GENKISS GIFT G-KISS G-MOUSE GRADE GROW HAMMER HARD HAT HATE H-BOMB HELL-R HEMP HINT HIT HU HUNT HYPNOSI INHA IRO IRONBAR IR-WING J-GUN KEE KICK KNIF KNIFE KNOCK LEVEL LIGH LIGHHIT LIGHT LIVE L-WALL MAD MAJUS MEL MELO MESS MILK MIMI MISS MIXING MOVE MUD NI-BED NOISY NOONLI NULL N-WAVE PAT PEACE PIN PLAN PLANE POIS POL POWDE POWE POWER PRIZE PROTECT PROUD RAGE RECOR REFLAC REFREC REGR RELIV RENEW R-FIGHT RING RKICK ROCK ROUND RUS RUSH SAND SAW SCISSOR SCRA SCRIPT SEEN SERVER SHADOW SHELL SHINE SHO SIGHT SIN SMALL SMELT SMOK SNAKE SNO SNOW SOU SO-WAVE SPAR SPEC SPID S-PIN SPRA STAM STARE STEA STONE STORM STRU STRUG STUDEN SUBS SUCID SUN-LIG SUNRIS SUPLY S-WAVE TAILS TANGL TASTE TELLI THANK TONKICK TOOTH TORL TRAIN TRIKICK TUNGE VOLT WA-GUN WATCH WAVE W-BOMB WFALL WFING WHIP WHIRL WIND WOLF WOOD WOR YUJA) -NAMES=(SEED GRASS FLOWE SHAD CABR SNAKE GOLD COW GUIKI PEDAL DELAN B-FLY BIDE KEYU FORK LAP PIGE PIJIA CAML LAT BIRD BABOO VIV ABOKE PIKAQ RYE SAN BREAD LIDEL LIDE PIP PIKEX ROK JUGEN PUD BUDE ZHIB GELU GRAS FLOW LAFUL ATH BALA CORN MOLUF DESP DAKED MIMI BOLUX KODA GELUD MONK SUMOY GEDI WENDI NILEM NILE NILEC KEZI YONGL HUDE WANLI GELI GUAIL MADAQ WUCI WUCI MUJEF JELLY SICIB GELU NELUO BOLI JIALE YED YEDE CLO SCARE AOCO DEDE DEDEI BAWU JIUG BADEB BADEB HOLE BALUX GES FANT QUAR YIHE SWAB SLIPP CLU DEPOS BILIY YUANO SOME NO YELA EMPT ZECUN XIAHE BOLEL DEJI MACID XIHON XITO LUCK MENJI GELU DECI XIDE DASAJ DONGN RICUL MINXI BALIY ZENDA LUZEL HELE5 0FENB KAIL JIAND CARP JINDE LAPU MUDE YIFU LINLI SANDI HUSI JINC OUMU OUMUX CAP KUIZA PUD TIAO FRMAN CLAU SPARK DRAGO BOLIU GUAIL MIYOU MIY QIAOK BEIL MUKEI RIDED MADAM BAGEP CROC ALIGE OUDAL OUD DADA HEHE YEDEA NUXI NUXIN ROUY ALIAD STICK QIANG LAAND PIQI PI PUPI DEKE DEKEJ NADI NADIO MALI PEA ELECT FLOWE MAL MALI HUSHU NILEE YUZI POPOZ DUZI HEBA XIAN SHAN YEYEA WUY LUO KEFE HULA CROW YADEH MOW ANNAN SUONI KYLI HULU HUDEL YEHE GULAE YEHE BLU GELAN BOAT NIP POIT HELAK XINL BEAR LINB MAGEH MAGEJ WULI YIDE RIVE FISH AOGU DELIE MANTE KONMU DELU HELU HUAN HUMA DONGF JINCA HEDE DEFU LIBY JIAPA MEJI HELE BUHU MILK HABI THUN GARD DON YANGQ SANAQ BANQ LUJ PHIX SIEI EGG) - -random_number() { +fn-random-number() { [[ -n "$1" ]] && RANGE="$1" if [[ -n "$RANGE" ]]; then number=$RANDOM @@ -15,94 +14,141 @@ random_number() { echo $number } -random_name() { - NUM1=$(random_number ${#MOVES[@]}) - NUM2=$(random_number ${#MOVES[@]}) - NUM3=$(random_number ${#NAMES[@]}) +fn-random-name() { + local NUM1 NUM2 NUM3 UPPER_APPNAME lower_appname + local MOVES=(ABLE ABNORMA AGAIN AIREXPL ANG ANGER ASAIL ATTACK AURORA AWL BAN BAND BARE BEAT BEATED BELLY BIND BITE BLOC BLOOD BODY BOOK BREATH BUMP CAST CHAM CLAMP CLAP CLAW CLEAR CLI CLIP CLOUD CONTRO CONVY COOLHIT CRASH CRY CUT DESCRI D-FIGHT DIG DITCH DIV DOZ DRE DUL DU-PIN DYE EARTH EDU EG-BOMB EGG ELEGY ELE-HIT EMBODY EMPLI ENGL ERUPT EVENS EXPLOR EYES FALL FAST F-CAR F-DANCE FEARS F-FIGHT FIGHT FIR FIRE FIREHIT FLAME FLAP FLASH FLEW FORCE FRA FREEZE FROG G-BIRD GENKISS GIFT G-KISS G-MOUSE GRADE GROW HAMMER HARD HAT HATE H-BOMB HELL-R HEMP HINT HIT HU HUNT HYPNOSI INHA IRO IRONBAR IR-WING J-GUN KEE KICK KNIF KNIFE KNOCK LEVEL LIGH LIGHHIT LIGHT LIVE L-WALL MAD MAJUS MEL MELO MESS MILK MIMI MISS MIXING MOVE MUD NI-BED NOISY NOONLI NULL N-WAVE PAT PEACE PIN PLAN PLANE POIS POL POWDE POWE POWER PRIZE PROTECT PROUD RAGE RECOR REFLAC REFREC REGR RELIV RENEW R-FIGHT RING RKICK ROCK ROUND RUS RUSH SAND SAW SCISSOR SCRA SCRIPT SEEN SERVER SHADOW SHELL SHINE SHO SIGHT SIN SMALL SMELT SMOK SNAKE SNO SNOW SOU SO-WAVE SPAR SPEC SPID S-PIN SPRA STAM STARE STEA STONE STORM STRU STRUG STUDEN SUBS SUCID SUN-LIG SUNRIS SUPLY S-WAVE TAILS TANGL TASTE TELLI THANK TONKICK TOOTH TORL TRAIN TRIKICK TUNGE VOLT WA-GUN WATCH WAVE W-BOMB WFALL WFING WHIP WHIRL WIND WOLF WOOD WOR YUJA) + local NAMES=(SEED GRASS FLOWE SHAD CABR SNAKE GOLD COW GUIKI PEDAL DELAN B-FLY BIDE KEYU FORK LAP PIGE PIJIA CAML LAT BIRD BABOO VIV ABOKE PIKAQ RYE SAN BREAD LIDEL LIDE PIP PIKEX ROK JUGEN PUD BUDE ZHIB GELU GRAS FLOW LAFUL ATH BALA CORN MOLUF DESP DAKED MIMI BOLUX KODA GELUD MONK SUMOY GEDI WENDI NILEM NILE NILEC KEZI YONGL HUDE WANLI GELI GUAIL MADAQ WUCI WUCI MUJEF JELLY SICIB GELU NELUO BOLI JIALE YED YEDE CLO SCARE AOCO DEDE DEDEI BAWU JIUG BADEB BADEB HOLE BALUX GES FANT QUAR YIHE SWAB SLIPP CLU DEPOS BILIY YUANO SOME NO YELA EMPT ZECUN XIAHE BOLEL DEJI MACID XIHON XITO LUCK MENJI GELU DECI XIDE DASAJ DONGN RICUL MINXI BALIY ZENDA LUZEL HELE5 0FENB KAIL JIAND CARP JINDE LAPU MUDE YIFU LINLI SANDI HUSI JINC OUMU OUMUX CAP KUIZA PUD TIAO FRMAN CLAU SPARK DRAGO BOLIU GUAIL MIYOU MIY QIAOK BEIL MUKEI RIDED MADAM BAGEP CROC ALIGE OUDAL OUD DADA HEHE YEDEA NUXI NUXIN ROUY ALIAD STICK QIANG LAAND PIQI PI PUPI DEKE DEKEJ NADI NADIO MALI PEA ELECT FLOWE MAL MALI HUSHU NILEE YUZI POPOZ DUZI HEBA XIAN SHAN YEYEA WUY LUO KEFE HULA CROW YADEH MOW ANNAN SUONI KYLI HULU HUDEL YEHE GULAE YEHE BLU GELAN BOAT NIP POIT HELAK XINL BEAR LINB MAGEH MAGEJ WULI YIDE RIVE FISH AOGU DELIE MANTE KONMU DELU HELU HUAN HUMA DONGF JINCA HEDE DEFU LIBY JIAPA MEJI HELE BUHU MILK HABI THUN GARD DON YANGQ SANAQ BANQ LUJ PHIX SIEI EGG) - UPPER_APPNAME="${MOVES[${NUM1}]}-${MOVES[${NUM2}]}-${NAMES[${NUM3}]}" + NUM1=$(fn-random-number ${#MOVES[@]}) + NUM2=$(fn-random-number ${#MOVES[@]}) + NUM3=$(fn-random-number ${#NAMES[@]}) - [[ "$BASH_VERSION" =~ 4.* ]] && lower_appname=${UPPER_APPNAME,,} - [[ -z "$lower_appname" ]] && lower_appname=$(echo "$UPPER_APPNAME" | tr '[:upper:]' '[:lower:]') - echo "$lower_appname" + UPPER_APPNAME="${MOVES[${NUM1}]}-${MOVES[${NUM2}]}-${NAMES[${NUM3}]}" + + [[ "$BASH_VERSION" =~ 4.* ]] && lower_appname=${UPPER_APPNAME,,} + [[ -z "$lower_appname" ]] && lower_appname=$(echo "$UPPER_APPNAME" | tr '[:upper:]' '[:lower:]') + echo "$lower_appname" } -client_help_msg() { - echo "==> Configure the DOKKU_HOST environment variable or run $0 from a repository with a git remote named dokku" - echo "--> i.e. git remote add dokku dokku@:" +fn-client-help-msg() { + echo "==> Configure the DOKKU_HOST environment variable or run $0 from a repository with a git remote named dokku" 1>&2 + echo "--> i.e. git remote add dokku dokku@:" 1>&2 exit 20 # exit with specific status. only used in units tests for now } -is_git_repo() { - git rev-parse &>/dev/null +fn-is-git-repo() { + git rev-parse &>/dev/null } -has_dokku_remote() { - git remote show | grep dokku +fn-has-dokku-remote() { + git remote show | grep -E "^${DOKKU_GIT_REMOTE}\s" } -if [[ -z $DOKKU_HOST ]]; then - if [[ -d .git ]] || git rev-parse --git-dir > /dev/null 2>&1; then - DOKKU_HOST=$(git remote -v 2>/dev/null | grep -Ei "^dokku" | head -n 1 | cut -f1 -d' ' | cut -f2 -d '@' | cut -f1 -d':' 2>/dev/null || true) - else - client_help_msg +fn-dokku-host() { + declare DOKKU_GIT_REMOTE="$1" DOKKU_HOST="$2" + + if [[ -z "$DOKKU_HOST" ]]; then + if [[ -d .git ]] || git rev-parse --git-dir > /dev/null 2>&1; then + DOKKU_HOST=$(git remote -v 2>/dev/null | grep -Ei "^${DOKKU_GIT_REMOTE}\s" | head -n 1 | cut -f1 -d' ' | cut -f2 -d '@' | cut -f1 -d':' 2>/dev/null || true) + fi fi -fi -export DOKKU_PORT=${DOKKU_PORT:=22} + if [[ -z "$DOKKU_HOST" ]]; then + fn-client-help-msg + fi -if [[ ! -z $DOKKU_HOST ]]; then - _dokku() { - appname="" + echo "$DOKKU_HOST" +} + +main() { + declare CMD="$1" APP_ARG="$2" + local APP="" DOKKU_GIT_REMOTE="dokku" DOKKU_REMOTE_HOST="" + local cmd_set=false next_index=1 skip=false args=("$@") + + for arg in "$@"; do + if [[ "$skip" == "true" ]]; then + next_index=$(( next_index + 1 )) + skip=false + continue + fi + + if [[ "$arg" == "--app" ]]; then + APP=${args[$next_index]} + skip=true + shift 2 + elif [[ "$arg" == "--remote" ]]; then + DOKKU_GIT_REMOTE=${args[$next_index]} + skip=true + shift 2 + elif [[ "$arg" =~ ^--.* ]]; then + [[ "$cmd_set" == "true" ]] && APP_ARG="$arg" && break + [[ "$arg" == "--trace" ]] && export DOKKU_TRACE=1 && set -x + else + if [[ "$cmd_set" == "true" ]]; then + APP_ARG="$arg" + break + else + CMD="$arg" + cmd_set=true + fi + fi + next_index=$(( next_index + 1 )) + done + + DOKKU_REMOTE_HOST="$(fn-dokku-host "$DOKKU_GIT_REMOTE" "$DOKKU_HOST")" + + if [[ -z "$APP" ]]; then if [[ -d .git ]] || git rev-parse --git-dir > /dev/null 2>&1; then set +e - appname=$(git remote -v 2>/dev/null | grep -Ei "dokku@$DOKKU_HOST" | head -n 1 | cut -f2 -d'@' | cut -f1 -d' ' | cut -f2 -d':' 2>/dev/null) + APP=$(git remote -v 2>/dev/null | grep -Ei "dokku@$DOKKU_REMOTE_HOST" | head -n 1 | cut -f2 -d'@' | cut -f1 -d' ' | cut -f2 -d':' 2>/dev/null) set -e else echo "This is not a git repository" fi - - case "$1" in - apps:create) - if [[ -z "$2" ]]; then - appname=$(random_name) - counter=0 - while ssh -p "$DOKKU_PORT" "dokku@$DOKKU_HOST" apps 2>/dev/null| grep -q "$appname"; do - if [[ $counter -ge 100 ]]; then - echo "Error: could not reasonably generate a new app name. try cleaning up some apps..." - ssh -p "$DOKKU_PORT" "dokku@$DOKKU_HOST" apps - exit 1 - else - appname=$(random_name) - counter=$((counter+1)) - fi - done - else - appname="$2" - fi - if git remote add dokku "dokku@$DOKKU_HOST:$appname"; then - echo "-----> Dokku remote added at $DOKKU_HOST" - echo "-----> Application name is $appname" - else - echo "! Dokku remote not added! Do you already have a dokku remote?" - return - fi - ;; - apps:destroy) - is_git_repo && has_dokku_remote && git remote remove dokku - ;; - esac - - [[ -n "$@" ]] && [[ -n "$appname" ]] && app_arg="--app $appname" - # echo "ssh -o LogLevel=QUIET -p $DOKKU_PORT -t dokku@$DOKKU_HOST -- $app_arg $@" - # shellcheck disable=SC2068,SC2086 - ssh -o LogLevel=QUIET -p $DOKKU_PORT -t dokku@$DOKKU_HOST -- $app_arg $@ - } - - if [[ "$0" == "dokku" ]] || [[ "$0" == *dokku_client.sh ]] || [[ "$0" == $(which dokku) ]]; then - _dokku "$@" - exit $? fi -else - client_help_msg + + case "$CMD" in + apps:create) + if [[ -z "$APP_ARG" ]]; then + APP=$(fn-random-name) + counter=0 + while ssh -p "$DOKKU_PORT" "dokku@$DOKKU_REMOTE_HOST" apps 2>/dev/null| grep -q "$APP"; do + if [[ $counter -ge 100 ]]; then + echo "Error: could not reasonably generate a new app name. try cleaning up some apps..." + ssh -p "$DOKKU_PORT" "dokku@$DOKKU_REMOTE_HOST" apps + exit 1 + else + APP=$(random_name) + counter=$((counter+1)) + fi + done + else + APP="$APP_ARG" + fi + if git remote add "$DOKKU_GIT_REMOTE" "dokku@$DOKKU_REMOTE_HOST:$APP"; then + echo "-----> Dokku remote added at ${DOKKU_REMOTE_HOST} called ${DOKKU_GIT_REMOTE}" + echo "-----> Application name is ${APP}" + else + echo "! Dokku remote not added! Do you already have a dokku remote?" + return + fi + ;; + apps:destroy) + fn-is-git-repo && fn-has-dokku-remote && git remote remove "$DOKKU_GIT_REMOTE" + ;; + esac + + [[ " apps certs help ls nginx shell storage trace version " == *" $CMD "* ]] && unset APP + [[ " certs:chain domains:add-global domains:remove-global domains:set-global ps:rebuildall ps:restartall ps:restore " == *" $CMD "* ]] && unset APP + [[ "$CMD" =~ events*|plugin*|ssh-keys* ]] && unset APP + [[ -n "$APP_ARG" ]] && [[ "$APP_ARG" == "--global" ]] && unset APP + [[ -n "$@" ]] && [[ -n "$APP" ]] && app_arg="--app $APP" + # echo "ssh -o LogLevel=QUIET -p $DOKKU_PORT -t dokku@$DOKKU_REMOTE_HOST -- $app_arg $@" + # shellcheck disable=SC2068,SC2086 + ssh -o LogLevel=QUIET -p $DOKKU_PORT -t dokku@$DOKKU_REMOTE_HOST -- $app_arg $@ +} + +if [[ "$0" == "dokku" ]] || [[ "$0" == *dokku_client.sh ]] || [[ "$0" == $(which dokku) ]]; then + main "$@" + exit $? fi diff --git a/contrib/release b/contrib/release new file mode 100755 index 000000000..4c02376e7 --- /dev/null +++ b/contrib/release @@ -0,0 +1,313 @@ +#!/usr/bin/env bash +set -eo pipefail +[[ $TRACE ]] && set -x + +readonly ROOT_DIR="$(cd "$(dirname "$(dirname "${BASH_SOURCE[0]}")")" && pwd)" +readonly TMP_WORK_DIR="$(mktemp -d "/tmp/dokku-release.XXXX")" +readonly DOKKU_GIT_REV="$(git rev-parse HEAD)" + +trap 'rm -rf "$TMP_WORK_DIR" > /dev/null' RETURN INT TERM EXIT + +log-info() { + # shellcheck disable=SC2034 + declare desc="Log info formatter" + echo "$*" +} + +log-error() { + # shellcheck disable=SC2034 + declare desc="Log error formatter" + echo "! $*" 1>&2 +} + +log-fail() { + # shellcheck disable=SC2034 + declare desc="Log fail formatter" + log-error "$*" + exit -1 +} + +fn-build-dokku() { + declare desc="Builds dokku packages within a docker container" + declare IS_RELEASE="$1" DOKKU_VERSION="$2" + local NAME GOLANG_VERSION + + if [[ -z "$DOKKU_VERSION" ]]; then + log-error "Invalid deb file specified" + return 1 + fi + + NAME="dokku:build" + [[ "$IS_RELEASE" == "true" ]] && NAME="dokku:build-release" + [[ -n "$DOKKU_VERSION" ]] || DOKKU_VERSION="master" + + pushd "$ROOT_DIR" >/dev/null + GOLANG_VERSION="$(grep BUILD_IMAGE common.mk | cut -d' ' -f3 | cut -d':' -f2)" + docker build \ + --build-arg DOKKU_VERSION="$DOKKU_VERSION" \ + --build-arg DOKKU_GIT_REV="$DOKKU_GIT_REV" \ + --build-arg IS_RELEASE="$IS_RELEASE" \ + --build-arg PLUGIN_MAKE_TARGET="build" \ + --build-arg GOLANG_VERSION="$GOLANG_VERSION" \ + -f "contrib/build.Dockerfile" \ + -t "$NAME" . +} + +fn-extract-package() { + declare desc="Extract packages from a docker container to the root directory" + declare IS_RELEASE="$1" PACKAGE_NAME="$2" + local NAME + + if [[ -z "$PACKAGE_NAME" ]]; then + log-error "Invalid deb file specified" + return 1 + fi + + NAME="dokku:build" + [[ "$IS_RELEASE" == "true" ]] && NAME="dokku:build-release" + + log-info "(extract-package) writing ${PACKAGE_NAME} to correct path" + docker run --rm --entrypoint cat "$NAME" "/data/${PACKAGE_NAME}" > "${ROOT_DIR}/${PACKAGE_NAME}" +} + +fn-in-array() { + declare desc="return true if value ($1) is in list (all other arguments)" + + local e + for e in "${@:2}"; do + [[ "$e" == "$1" ]] && return 0 + done + return 1 +} + +fn-is-release() { + declare desc="Checks if a given run is a release run" + declare RELEASE="$1" + local IS_RELEASE=false + if [[ "$RELEASE" == "major" ]] || [[ "$RELEASE" == "minor" ]] || [[ "$RELEASE" == "patch" ]]; then + IS_RELEASE=true + fi + + echo "$IS_RELEASE" +} + +fn-version-current() { + declare desc="Retrieves the current version of dokku" + pushd "$ROOT_DIR" >/dev/null + grep Version debian/control | cut -d' ' -f2 +} + +fn-version-next() { + declare desc="Calculates the next version name of dokku" + declare CURRENT_VERSION="$1" RELEASE="$2" + local major minor patch short_object + + major=$(echo "$CURRENT_VERSION" | awk '{split($0,a,"."); print a[1]}') + minor=$(echo "$CURRENT_VERSION" | awk '{split($0,a,"."); print a[2]}') + patch=$(echo "$CURRENT_VERSION" | awk '{split($0,a,"."); print a[3]}') + + if [[ "$RELEASE" == "major" ]]; then + major=$((major + 1)) + minor="0" + patch="0" + elif [[ "$RELEASE" == "minor" ]]; then + minor=$((minor + 1)) + patch="0" + elif [[ "$RELEASE" == "patch" ]]; then + patch=$((patch + 1)) + else + short_object=$(git log --oneline | tail -n 1 | cut -d' ' -f1) + echo "${CURRENT_VERSION}build+$(echo "$DOKKU_GIT_REV" | cut -c1-${#short_object})" + return 0 + fi + + echo "${major}.${minor}.${patch}" +} + +fn-publish-package() { + declare desc="Publishes a package to packagecloud" + declare IS_RELEASE="$1" RELEASE_TYPE="$2" PACKAGE_NAME="$3" + local REPOSITORY=dokku/dokku-betafish DIST=ubuntu/trusty + + [[ "$IS_RELEASE" == "true" ]] && REPOSITORY=dokku/dokku + [[ "$RELEASE_TYPE" == "rpm" ]] && DIST=el/7 + log-info "(release-dokku) pushing ${RELEASE_TYPE} to packagecloud.com/${REPOSITORY}/${DIST}" + package_cloud push "${REPOSITORY}/${DIST}" "$PACKAGE_NAME" +} + +fn-replace-version() { + declare desc="Replaces the version within a file" + local CURRENT_VERSION="$1" + local NEXT_VERSION="$2" + local FILENAME="$3" + sed -i.bak "s/${CURRENT_VERSION//./\.}/$NEXT_VERSION/g" "$FILENAME" && rm "$FILENAME.bak" + git add "$FILENAME" +} + +fn-repo-merged-requests() { + declare desc="Retrieves all pull request ids since a given release of dokku" + declare TAG="$1" + git log "v${TAG}..HEAD" --oneline | grep "Merge pull request" | cut -d' ' -f 5 | cut -d '#' -f2 +} + +fn-repo-push-tags() { + declare desc="Pushes tags and the repository to github" + declare IS_RELEASE="$1" + + if [[ "$IS_RELEASE" == "false" ]]; then + return + fi + + pushd "$ROOT_DIR" >/dev/null + git push --tags origin master +} + +fn-repo-update() { + declare desc="Updates files within the repository for a given release" + declare IS_RELEASE="$1" CURRENT_VERSION="$2" NEXT_VERSION="$3" + + log-info "(release-dokku) Replacing ${CURRENT_VERSION} with ${NEXT_VERSION}" + for f in plugins/*/plugin.toml; do + fn-replace-version "$CURRENT_VERSION" "$NEXT_VERSION" "$f" + done + + if [[ "$IS_RELEASE" == "false" ]]; then + return + fi + + fn-replace-version "$CURRENT_VERSION" "$NEXT_VERSION" contrib/dokku-installer.py + fn-replace-version "$CURRENT_VERSION" "$NEXT_VERSION" debian/control + fn-replace-version "$CURRENT_VERSION" "$NEXT_VERSION" docs/advanced-usage/plugin-management.md + fn-replace-version "$CURRENT_VERSION" "$NEXT_VERSION" docs/assets/favicons/browserconfig.xml + fn-replace-version "$CURRENT_VERSION" "$NEXT_VERSION" docs/assets/favicons/manifest.json + fn-replace-version "$CURRENT_VERSION" "$NEXT_VERSION" docs/assets/style.css + fn-replace-version "$CURRENT_VERSION" "$NEXT_VERSION" docs/development/release-process.md + fn-replace-version "$CURRENT_VERSION" "$NEXT_VERSION" docs/home.html + fn-replace-version "$CURRENT_VERSION" "$NEXT_VERSION" docs/getting-started/installation.md + fn-replace-version "$CURRENT_VERSION" "$NEXT_VERSION" docs/template.html + fn-replace-version "$CURRENT_VERSION" "$NEXT_VERSION" README.md + + if [[ "$RELEASE" == 'patch' ]]; then + fn-replace-version "$CURRENT_VERSION" "$NEXT_VERSION" docs/assets/versions.json + else + versions=$(python -c 'import json,sys;d=json.load(sys.stdin);d["max-versions"].append("'"${NEXT_VERSION}"'"); print json.dumps(d, indent=2, sort_keys=True)' docs/assets/versions.json + git add docs/assets/versions.json + fi + + if [[ "$IS_RELEASE" == "false" ]]; then + return + fi + + log-info "(release-dokku) Adding HISTORY.md, committing and tagging" + fn-repo-update-history-and-commit "$CURRENT_VERSION" "$NEXT_VERSION" + git tag "v${NEXT_VERSION}" +} + +fn-repo-update-history-and-commit() { + declare desc="Updates the history file and commits the changes" + declare CURRENT_VERSION="$1" NEXT_VERSION="$2" + local PULL_REQUEST_ID TITLE AUTHOR TYPE CHANGELOG_TEXT + local COMMIT_MESSAGE HISTORY HISTORY_CONTENTS HISTORY_DOCUMENTATION HISTORY_ENHANCEMENT HISTORY_BUG ISSUE_FILE + + pushd "$ROOT_DIR" >/dev/null + + mkdir -p /tmp/github-data + + for PULL_REQUEST_ID in $(fn-repo-merged-requests "$CURRENT_VERSION"); do + ISSUE_FILE="/tmp/github-data/${PULL_REQUEST_ID}.json" + if [[ ! -f "$ISSUE_FILE" ]]; then + curl -sS "https://api.github.com/repos/dokku/dokku/issues/${PULL_REQUEST_ID}" >"$ISSUE_FILE" + fi + + TITLE="$(jq -r '.title' "$ISSUE_FILE")" + AUTHOR="$(jq -r '.user.login' "$ISSUE_FILE")" + TYPE="$(jq -r '.labels[] | select( .name | contains("type") ) | .name' "$ISSUE_FILE" | cut -d' ' -f2)" + CHANGELOG_TEXT="- #${PULL_REQUEST_ID}: @${AUTHOR} ${TITLE}" + + if [[ "$TYPE" == "bug" ]]; then + HISTORY_BUG="${HISTORY_BUG}"$'\n'"$CHANGELOG_TEXT" + elif [[ "$TYPE" == "documentation" ]]; then + HISTORY_DOCUMENTATION="${HISTORY_DOCUMENTATION}"$'\n'"$CHANGELOG_TEXT" + elif [[ "$TYPE" == "enhancement" ]]; then + HISTORY_ENHANCEMENT="${HISTORY_ENHANCEMENT}"$'\n'"$CHANGELOG_TEXT" + fi + done + + HISTORY="# History" + HISTORY="${HISTORY}"$'\n\n'"## ${NEXT_VERSION}" + + if [[ "$HISTORY_BUG" ]]; then + HISTORY="${HISTORY}"$'\n\n'"### Bug Fixes" + HISTORY="${HISTORY}"$'\n'"$HISTORY_BUG" + fi + + if [[ "$HISTORY_DOCUMENTATION" ]]; then + HISTORY="${HISTORY}"$'\n\n'"### New Features" + HISTORY="${HISTORY}"$'\n'"$HISTORY_DOCUMENTATION" + fi + + if [[ "$HISTORY_ENHANCEMENT" ]]; then + HISTORY="${HISTORY}"$'\n\n'"### Documentation" + HISTORY="${HISTORY}"$'\n'"$HISTORY_ENHANCEMENT" + fi + + sed -i.bak '1d' HISTORY.md && rm HISTORY.md.bak + HISTORY_CONTENTS="$(cat HISTORY.md)" + echo -e "$HISTORY\n$HISTORY_CONTENTS" >HISTORY.md + git add HISTORY.md + + COMMIT_MESSAGE="Release ${NEXT_VERSION}"$'\n\n'"${HISTORY}" + git commit -m "${COMMIT_MESSAGE}" +} + +fn-require-bin() { + declare desc="Checks that a binary exists" + declare BINARY="$1" + if ! command -v "$BINARY" &>/dev/null; then + log-fail "Missing ${BINARY}, please install it" + fi +} + +main() { + declare RELEASE="$1" + local CURRENT_VERSION NEXT_VERSION IS_RELEASE major minor patch versions + local VALID_RELEASE_LEVELS=("major" "minor" "patch" "betafish") + + if [[ "$RELEASE" == '--trace' ]]; then + shift 1 + RELEASE="$1" + TRACE=1 && set -x + fi + + if [[ -z "$RELEASE" ]]; then + log-fail "Argument 1 must be one of [major, minor, patch, betafish], none given" + fi + + if ! fn-in-array "$RELEASE" "${VALID_RELEASE_LEVELS[@]}"; then + log-fail "Argument 1 must be one of [major, minor, patch, betafish], '${RELEASE}' given" + fi + + fn-require-bin "docker" + fn-require-bin "git" + fn-require-bin "package_cloud" + [[ -n "$PACKAGECLOUD_API_TOKEN" ]] || log-fail "Missing PACKAGECLOUD_API_TOKEN environment variable" + + CURRENT_VERSION="$(fn-version-current)" + NEXT_VERSION="$(fn-version-next "$CURRENT_VERSION" "$RELEASE")" + IS_RELEASE="$(fn-is-release "$RELEASE")" + + fn-repo-update "$IS_RELEASE" "$CURRENT_VERSION" "$NEXT_VERSION" + + fn-build-dokku "$IS_RELEASE" "$NEXT_VERSION" + + fn-extract-package "$IS_RELEASE" "dokku_${NEXT_VERSION}_amd64.deb" + fn-extract-package "$IS_RELEASE" "dokku-${NEXT_VERSION}-1.x86_64.rpm" + + fn-publish-package "$IS_RELEASE" "deb" "dokku_${NEXT_VERSION}_amd64.deb" + fn-publish-package "$IS_RELEASE" "rpm" "dokku-${NEXT_VERSION}-1.x86_64.rpm" + + fn-repo-push-tags "$IS_RELEASE" +} + +main "$@" diff --git a/deb.mk b/deb.mk index f9f9c52ac..ba8d61ea8 100644 --- a/deb.mk +++ b/deb.mk @@ -1,6 +1,6 @@ HEROKUISH_DESCRIPTION = 'Herokuish uses Docker and Buildpacks to build applications like Heroku' HEROKUISH_REPO_NAME ?= gliderlabs/herokuish -HEROKUISH_VERSION ?= 0.3.19 +HEROKUISH_VERSION ?= 0.3.27 HEROKUISH_ARCHITECTURE = amd64 HEROKUISH_PACKAGE_NAME = herokuish_$(HEROKUISH_VERSION)_$(HEROKUISH_ARCHITECTURE).deb @@ -15,7 +15,7 @@ You can use any language you want, so long as the script is executable and has the proper language requirements installed endef PLUGN_REPO_NAME ?= dokku/plugn -PLUGN_VERSION ?= 0.2.2 +PLUGN_VERSION ?= 0.3.0 PLUGN_ARCHITECTURE = amd64 PLUGN_PACKAGE_NAME = plugn_$(PLUGN_VERSION)_$(PLUGN_ARCHITECTURE).deb PLUGN_URL = https://github.com/dokku/plugn/releases/download/v$(PLUGN_VERSION)/plugn_$(PLUGN_VERSION)_linux_x86_64.tgz @@ -26,7 +26,7 @@ Simplifies running a single command over SSH, and manages authorized keys (ACL) and users in order to do so. endef SSHCOMMAND_REPO_NAME ?= dokku/sshcommand -SSHCOMMAND_VERSION ?= 0.6.0 +SSHCOMMAND_VERSION ?= 0.7.0 SSHCOMMAND_ARCHITECTURE = amd64 SSHCOMMAND_PACKAGE_NAME = sshcommand_$(SSHCOMMAND_VERSION)_$(SSHCOMMAND_ARCHITECTURE).deb SSHCOMMAND_URL ?= https://raw.githubusercontent.com/dokku/sshcommand/v$(SSHCOMMAND_VERSION)/sshcommand @@ -92,7 +92,10 @@ deb-herokuish: @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 'if [[ ! -z $${http_proxy+x} ]]; then BUILDARGS="--build-arg http_proxy=$$http_proxy"; fi' >> /tmp/tmp/post-install + @echo 'if [[ ! -z $${https_proxy+x} ]]; then BUILDARGS="$$BUILDARGS --build-arg https_proxy=$$https_proxy"; fi' >> /tmp/tmp/post-install + @echo 'if [[ ! -z $${BUILDARGS+x} ]]; then echo Adding proxy settings to docker build: $$BUILDARGS; fi' >> /tmp/tmp/post-install + @echo 'sudo docker build $$BUILDARGS -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" --branch "v$(HEROKUISH_VERSION)" /tmp/tmp/herokuish > /dev/null @@ -121,24 +124,36 @@ deb-dokku: cp dokku /tmp/build/usr/bin cp LICENSE /tmp/build/usr/share/doc/dokku/copyright find . -name ".DS_Store" -depth -exec rm {} \; + $(MAKE) go-build + cp common.mk /tmp/build/var/lib/dokku/core-plugins/common.mk cp -r plugins/* /tmp/build/var/lib/dokku/core-plugins/available + find plugins/ -mindepth 1 -maxdepth 1 -type d -printf '%f\n' | while read plugin; do cd /tmp/build/var/lib/dokku/core-plugins/available/$$plugin && if [ -e Makefile ]; then $(MAKE) src-clean; fi; done find plugins/ -mindepth 1 -maxdepth 1 -type d -printf '%f\n' | while read plugin; do touch /tmp/build/var/lib/dokku/core-plugins/available/$$plugin/.core; done + rm /tmp/build/var/lib/dokku/core-plugins/common.mk $(MAKE) help2man $(MAKE) addman cp /usr/local/share/man/man1/dokku.1 /tmp/build/usr/share/man/man1/dokku.1 gzip -9 /tmp/build/usr/share/man/man1/dokku.1 cp contrib/dokku-installer.py /tmp/build/usr/share/dokku/contrib +ifeq ($(DOKKU_VERSION),master) git describe --tags > /tmp/build/var/lib/dokku/VERSION +else + echo $(DOKKU_VERSION) > /tmp/build/var/lib/dokku/VERSION +endif cat /tmp/build/var/lib/dokku/VERSION | cut -d '-' -f 1 | cut -d 'v' -f 2 > /tmp/build/var/lib/dokku/STABLE_VERSION ifneq (,$(findstring false,$(IS_RELEASE))) sed -i.bak -e "s/^/`date +%s`:/" /tmp/build/var/lib/dokku/STABLE_VERSION && rm /tmp/build/var/lib/dokku/STABLE_VERSION.bak endif - rm /tmp/build/DEBIAN/lintian-overrides - mv debian/lintian-overrides /tmp/build/usr/share/lintian/overrides/dokku + rm -f /tmp/build/DEBIAN/lintian-overrides + cp debian/lintian-overrides /tmp/build/usr/share/lintian/overrides/dokku +ifdef DOKKU_GIT_REV + echo "$(DOKKU_GIT_REV)" > /tmp/build/var/lib/dokku/GIT_REV +else git rev-parse HEAD > /tmp/build/var/lib/dokku/GIT_REV - sed -i "s/^Version: .*/Version: `cat /tmp/build/var/lib/dokku/STABLE_VERSION`/g" /tmp/build/DEBIAN/control - dpkg-deb --build /tmp/build "/tmp/dokku_`cat /tmp/build/var/lib/dokku/STABLE_VERSION`_$(DOKKU_ARCHITECTURE).deb" - lintian "/tmp/dokku_`cat /tmp/build/var/lib/dokku/STABLE_VERSION`_$(DOKKU_ARCHITECTURE).deb" +endif + sed -i.bak "s/^Version: .*/Version: `cat /tmp/build/var/lib/dokku/STABLE_VERSION`/g" /tmp/build/DEBIAN/control && rm /tmp/build/DEBIAN/control.bak + dpkg-deb --build /tmp/build "/tmp/dokku_`cat /tmp/build/var/lib/dokku/VERSION`_$(DOKKU_ARCHITECTURE).deb" + lintian "/tmp/dokku_`cat /tmp/build/var/lib/dokku/VERSION`_$(DOKKU_ARCHITECTURE).deb" deb-plugn: rm -rf /tmp/tmp /tmp/build $(PLUGN_PACKAGE_NAME) diff --git a/debian/control b/debian/control index f8c628bb2..d8881e06d 100644 --- a/debian/control +++ b/debian/control @@ -1,11 +1,11 @@ Package: dokku -Version: 0.7.2 +Version: 0.9.4 Section: web Priority: optional Architecture: amd64 -Depends: locales, git, make, curl, gcc, man-db, sshcommand, gliderlabs-sigil, docker-engine-cs (>= 1.9.1) | docker-engine (>= 1.9.1), software-properties-common, python-software-properties -Recommends: herokuish -Pre-Depends: nginx, dnsutils, cgroupfs-mount | cgroup-lite, plugn, sudo, python2.7, debconf +Depends: locales, git, make, curl, gcc, man-db, sshcommand (>= 0.6.0), gliderlabs-sigil, docker-engine-cs (>= 1.9.1) | docker-engine (>= 1.9.1) | docker-ce | docker-ee, software-properties-common, python-software-properties +Recommends: herokuish (>= 0.3.4), parallel +Pre-Depends: nginx, dnsutils, cgroupfs-mount | cgroup-lite, plugn (>= 0.3.0), sudo, python2.7, debconf Maintainer: Jose Diaz-Gonzalez Description: Docker-powered PaaS that helps build and manage the lifecycle of applications Dokku is an extensible, open source Platform as a Service diff --git a/debian/lintian-overrides b/debian/lintian-overrides index 5fb3547e9..b567152e9 100644 --- a/debian/lintian-overrides +++ b/debian/lintian-overrides @@ -1,3 +1,5 @@ dokku: command-with-path-in-maintainer-script postinst:16 /usr/bin/sshcommand dokku: maintainer-script-calls-systemctl postinst:87 dokku: changelog-file-missing-in-native-package +dokku: statically-linked-binary +dokku: unstripped-binary-or-object diff --git a/debian/postinst b/debian/postinst index 424c82322..e6a1f2412 100755 --- a/debian/postinst +++ b/debian/postinst @@ -35,7 +35,7 @@ case "$1" in echo "Setting up storage directories" mkdir -p ${DOKKU_LIB_ROOT}/data ${DOKKU_LIB_ROOT}/data/storage - chown dokku:dokku -R ${DOKKU_LIB_ROOT}/data + chown dokku:dokku ${DOKKU_LIB_ROOT}/data ${DOKKU_LIB_ROOT}/data/storage echo "Setting up plugin directories" # should be replaced by `plugn init` diff --git a/debian/postrm b/debian/postrm index 859fec170..2ddc84de9 100755 --- a/debian/postrm +++ b/debian/postrm @@ -11,13 +11,13 @@ readonly DOKKU_LIB_ROOT="${DOKKU_LIB_PATH:-/var/lib/dokku}" readonly DOKKU_LOGS_DIR="${DOKKU_LOGS_DIR:="/var/log/dokku"}" main() { - if [[ -f /etc/systemd/system/dokku-installer.conf ]] || [[ -f /etc/init/dokku-installer.conf ]]; then + if [[ -f /etc/systemd/system/dokku-installer.service ]] || [[ -f /etc/init/dokku-installer.conf ]]; then service dokku-installer stop || true fi rm -f /etc/init/dokku-installer.conf rm -f /etc/init/dokku-redeploy.conf - rm -f /etc/systemd/system/dokku-installer.conf + rm -f /etc/systemd/system/dokku-installer.service rm -f /etc/systemd/system/dokku-redeploy.service rm -f /etc/update-motd.d/99-dokku diff --git a/docs/advanced-usage/backup-recovery.md b/docs/advanced-usage/backup-recovery.md index 26e6fef24..0be832612 100644 --- a/docs/advanced-usage/backup-recovery.md +++ b/docs/advanced-usage/backup-recovery.md @@ -26,7 +26,7 @@ dokku postgres:import [db_name] < [db_name].dump Dokku doesn't enforce a [300mb](https://devcenter.heroku.com/articles/slug-compiler#slug-size) limit on apps, but it's best practice to keep binary assets outside of git. Since containers are considered volatile in Dokku, external stores like s3 or storage mounts should be used for non-volatile items like user uploads. The Dokku storage core plugin can be used to mount local directories / volumes inside the docker container. -See the [persistent storage documentation](/dokku/advanced-usage/persistent-storage/) for more details. +See the [persistent storage documentation](/docs/advanced-usage/persistent-storage.md) for more details. ## Disaster Recovery diff --git a/docs/advanced-usage/deployment-tasks.md b/docs/advanced-usage/deployment-tasks.md index 0a42f4edf..93e9a4d8f 100644 --- a/docs/advanced-usage/deployment-tasks.md +++ b/docs/advanced-usage/deployment-tasks.md @@ -15,44 +15,20 @@ Dokku accomplishes this by using an `app.json` file. The format in use is simila However, Dokku currently only supports the nodes `scripts.dokku.predeploy` and `scripts.dokku.postdeploy`. For buildpack apps, simply place an `app.json` file in the root of your repository. For dockerfile apps, place `app.json` in the configured `WORKDIR` directory; otherwise Dokku defaults to the buildpack app behavior of looking in `/app`. ->NOTE: postdeploy changes are *NOT* committed to the app image. + +> NOTE: postdeploy changes are *NOT* committed to the app image. ### Example app.json +> NOTE: Only the `scripts.dokku.predeploy` and `scripts.dokku.postdeploy` tasks are supported by Dokku at this time. All other fields will be ignored and can be omitted. + ```json { - "name": "barebones nodejs", - "description": "A barebones Node.js app using Express 4.", - "keywords": [ - "nodejs", - "express" - ], - "repository": "https://github.com/michaelshobbs/node-js-sample", "scripts": { "dokku": { "predeploy": "touch /app/predeploy.test", "postdeploy": "curl https://some.external.api.service.com/deployment?state=success" } - }, - "env": { - "SECRET_TOKEN": { - "description": "A secret key for verifying the integrity of signed cookies.", - "value": "secret" - }, - "WEB_CONCURRENCY": { - "description": "The number of processes to run.", - "generator": "echo 5" - } - }, - "image": "gliderlabs/herokuish", - "addons": [ - "dokku-postgres", - "dokku-redis" - ], - "buildpacks": [ - { - "url": "https://github.com/heroku/heroku-buildpack-nodejs" - } - ] + } } ``` diff --git a/docs/advanced-usage/docker-options.md b/docs/advanced-usage/docker-options.md index cd6261ae8..dced57b8a 100644 --- a/docs/advanced-usage/docker-options.md +++ b/docs/advanced-usage/docker-options.md @@ -5,15 +5,15 @@ Pass [options](https://docs.docker.com/engine/reference/run/) to Docker during Dokku's `build`, `deploy` and `run` phases ``` -docker-options [phase(s)] # Display app's Docker options for all phases (or comma separated phase list) docker-options:add OPTION # Add Docker option to app for phase (comma-separated phase list) docker-options:remove OPTION # Remove Docker option from app for phase (comma-separated phase list) +docker-options:report [] [] # Displays a docker options report for one or more apps ``` > When specifying multiple phases, they **must** be comma-separated _without_ spaces in between each phase, like so: > > ```shell -> dokku docker-options:add myapp deploy,run "-v /home/dokku/logs/myapp:/app/logs" +> dokku docker-options:add node-js-app deploy,run "-v /var/log/node-js-app:/app/logs" > ``` ## About Dokku phases @@ -22,41 +22,73 @@ Dokku deploys your application in multiple "phases" and the `docker-options` plu - `build`: the container that executes the appropriate buildpack - `deploy`: the container that executes your running/deployed application -- `run`: the container that executes any arbitrary command via `dokku run myapp` +- `run`: the container that executes any arbitrary command via `dokku run` ## Examples ### Add Docker options -Add some options for the deployed/running app and when executing [`dokku run`](/dokku/deployment/one-off-processes/): +Add some options for the deployed/running app and when executing [`dokku run`](/docs/deployment/one-off-processes.md): ```shell # Mount a host volume in a Docker container: "-v /host/path:/container/path" -dokku docker-options:add myapp deploy "-v /home/dokku/logs/myapp:/app/logs" -dokku docker-options:add myapp run "-v /home/dokku/logs/myapp:/app/logs" +dokku docker-options:add node-js-app deploy "-v /var/log/node-js-app:/app/logs" +dokku docker-options:add node-js-app run "-v /var/log/node-js-app:/app/logs" ``` > Note: When [mounting a host directory](https://docs.docker.com/engine/reference/run/#volume-shared-filesystems) in a Dokku app you should first create that directory as user `dokku` and then mount the directory under `/app` in the container using `docker-options` as above. Otherwise the app will lack write permission in the directory. -### Output Docker options - -```shell -dokku docker-options myapp -``` - -``` -Deploy options: - -v /home/dokku/logs/myapp:/app/logs -Run options: - -v /home/dokku/logs/myapp:/app/logs -``` - ### Remove a Docker option ```shell -dokku docker-options:remove myapp run "-v /home/dokku/logs/myapp:/app/logs" +dokku docker-options:remove node-js-app run "-v /var/log/node-js-app:/app/logs" ``` +### Displaying docker-options reports about an app + +> New as of 0.8.1 + +You can get a report about the app's docker-options status using the `docker-options:report` command: + +```shell +dokku docker-options:report +``` + +``` +=====> node-js-app docker options information + Docker options build: + Docker options deploy: -v /var/log/node-js-app:/app/logs + Docker options run: -v /var/log/node-js-app:/app/logs +=====> python-sample docker options information + Docker options build: + Docker options deploy: + Docker options run: +=====> ruby-sample docker options information + Docker options build: + Docker options deploy: + Docker options run: +``` + +You can run the command for a specific app also. + +```shell +dokku docker-options:report node-js-app +``` + +``` +=====> node-js-app docker options information + Storage build mounts: + Storage deploy mounts: -v /var/log/node-js-app:/app/logs + Storage run mounts: -v /var/log/node-js-app:/app/logs +``` + +You can pass flags which will output only the value of the specific information you want. For example: + +```shell +dokku docker-options:report node-js-app --docker-options-build +``` + + ## Advanced usage In your applications folder `/home/dokku/app_name` create a file called `DOCKER_OPTIONS_RUN` (or `DOCKER_OPTIONS_BUILD` or `DOCKER_OPTIONS_DEPLOY`). @@ -77,4 +109,4 @@ The above example will result in the following options being passed to Docker du You may also include comments (lines beginning with a #) and blank lines in the DOCKER_OPTIONS file. -Move information on Docker options can be found here: http://docs.docker.io/en/latest/reference/run/ . +More information on Docker options can be found here: https://docs.docker.com/engine/reference/commandline/run/. diff --git a/docs/advanced-usage/persistent-storage.md b/docs/advanced-usage/persistent-storage.md index 88348290f..849656440 100644 --- a/docs/advanced-usage/persistent-storage.md +++ b/docs/advanced-usage/persistent-storage.md @@ -8,41 +8,96 @@ The preferred method to mount external containers to a Dokku managed container, ``` storage:list # List bind mounts for app's container(s) (host:container) storage:mount # Create a new bind mount +storage:report [] [] # Displays a checks report for one or more apps storage:unmount # Remove an existing bind mount ``` +> The storage plugin is compatible with storage mounts created with the docker-options. The storage plugin will only list mounts from the deploy/run phase. + ## Ideology and Background The storage plugin requires explicit paths on the host side. This is intentional to ensure that new users avoid running into unexpected results with implicit paths that may not exist (a feature deprecate in [Docker 1.9.0](https://github.com/docker/docker/releases/tag/v1.9.0])). The container directory is created for the mount point in the container. Any existing directory contents are not accessible after a mount is added to the container. Dokku creates a new directory `/var/lib/dokku/data/storage` during installation, it's the general consensus that new users should use this directory. Mounts are only available at run and deploy times, you must redeploy (restart) an app to mount or unmount to an existing app's container. ## Usage -This example demonstrates how to mount the recommended directory to `/storage` inside the container: +This example demonstrates how to mount the recommended directory to `/storage` inside an application called `node-js-app`: ```shell -dokku storage:mount app-name /var/lib/dokku/data/storage:/storage +# we use a subdirectory inside of the host directory to scope it to just the app +dokku storage:mount node-js-app /var/lib/dokku/data/storage/node-js-app:/storage ``` Dokku will then mount the shared contents of `/var/lib/dokku/data/storage` to `/storage` inside the container. +Once you have mounted persistent storage, you will also need to restart the application. See the +[process scaling documentation](/docs/deployment/process-management.md) for more information. + +```shell +dokku ps:rebuild app-name +``` + A more complete workflow may require making a custom directory for your application and mounting it within your `/app/storage` directory instead. The mount point is *not* relative to your application's working directory, and is instead relative to the root of the container. ```shell -# creating storage for the app 'ruby-rails-sample' -mkdir -p /var/lib/dokku/data/storage/ruby-rails-sample +# creating storage for the app 'node-js-app' +mkdir -p /var/lib/dokku/data/storage/node-js-app # ensure the proper user has access to this directory -chown -R dokku:dokku /var/lib/dokku/data/storage/ruby-rails-sample +chown -R dokku:dokku /var/lib/dokku/data/storage/node-js-app # as of 0.7.x, you should chown using the `32767` user and group id -chown -R 32767:32767 /var/lib/dokku/data/storage/ruby-rails-sample +chown -R 32767:32767 /var/lib/dokku/data/storage/node-js-app # mount the directory into your container's /app/storage directory, relative to root -dokku storage:mount app-name /var/lib/dokku/data/storage/app-name:/app/storage +dokku storage:mount app-name /var/lib/dokku/data/storage/node-js-app:/app/storage ``` You can mount one or more directories as desired by following the above pattern. +### Displaying storage reports about an app + +> New as of 0.8.1 + +You can get a report about the app's storage status using the `storage:report` command: + +```shell +dokku storage:report +``` + +``` +=====> node-js-app storage information + Storage build mounts: + Storage deploy mounts: -v /var/lib/dokku/data/storage/node-js-app:/app/storage + Storage run mounts: -v /var/lib/dokku/data/storage/node-js-app:/app/storage +=====> python-sample storage information + Storage build mounts: + Storage deploy mounts: + Storage run mounts: +=====> ruby-sample storage information + Storage build mounts: + Storage deploy mounts: + Storage run mounts: +``` + +You can run the command for a specific app also. + +```shell +dokku storage:report node-js-app +``` + +``` +=====> node-js-app storage information + Storage build mounts: + Storage deploy mounts: -v /var/lib/dokku/data/storage/node-js-app:/app/storage + Storage run mounts: -v /var/lib/dokku/data/storage/node-js-app:/app/storage +``` + +You can pass flags which will output only the value of the specific information you want. For example: + +```shell +dokku storage:report node-js-app --storage-deploy-mounts +``` + ## Use Cases ### Persistent storage @@ -73,15 +128,10 @@ You cannot use mounted volumes during the build phase of a Dockerfile deploy. Th > Note: **This can cause data loss** if you bind a mount under `/app` in buildpack apps as herokuish will attempt to remove the original app path during the build phase. -## Docker-Options Note - -The storage plugin is compatible with storage mounts created with the docker-options. The storage plugin will only list mounts from the deploy phase. - +## Application User and Persistent Storage file ownership (buildpack apps only) > New as of 0.7.1 -## Application User and Persistent Storage file ownership (buildpack apps only) - By default, Dokku will execute your buildpack application processes as the `herokuishuser` user. You may override this by setting the `DOKKU_APP_USER` config variable. > NOTE: this user must exist in your herokuish image. diff --git a/docs/advanced-usage/plugin-management.md b/docs/advanced-usage/plugin-management.md index 985b386ae..ef28f810d 100644 --- a/docs/advanced-usage/plugin-management.md +++ b/docs/advanced-usage/plugin-management.md @@ -3,11 +3,11 @@ > New as of 0.4.0 ``` -plugin # Print active plugins plugin:disable # Disable an installed plugin (third-party only) plugin:enable # Enable a previously disabled plugin plugin:install [--core|git-url [--committish tag|branch|commit|--name custom-plugin-name]] # Optionally download git-url (with custom tag/committish) & run install trigger for active plugins (or only core ones) plugin:install-dependencies [--core] # Run install-dependencies trigger for active plugins (or only core ones) +plugin:list # Print active plugins plugin:uninstall # Uninstall a plugin (third-party only) plugin:update [name [committish]] # Optionally update named plugin from git (with custom tag/committish) & run update trigger for active plugins ``` @@ -31,30 +31,30 @@ dokku plugin ``` plugn: dev - 00_dokku-standard 0.7.2 enabled dokku core standard plugin - 20_events 0.7.2 enabled dokku core events logging plugin - apps 0.7.2 enabled dokku core apps plugin - build-env 0.7.2 enabled dokku core build-env plugin - certs 0.7.2 enabled dokku core certificate management plugin - checks 0.7.2 enabled dokku core checks plugin - common 0.7.2 enabled dokku core common plugin - config 0.7.2 enabled dokku core config plugin - docker-options 0.7.2 enabled dokku core docker-options plugin - domains 0.7.2 enabled dokku core domains plugin - enter 0.7.2 enabled dokku core enter plugin - git 0.7.2 enabled dokku core git plugin - logs 0.7.2 enabled dokku core logs plugin - named-containers 0.7.2 enabled dokku core named containers plugin - nginx-vhosts 0.7.2 enabled dokku core nginx-vhosts plugin - plugin 0.7.2 enabled dokku core plugin plugin - proxy 0.7.2 enabled dokku core proxy plugin - ps 0.7.2 enabled dokku core ps plugin - repo 0.7.2 enabled dokku core repo plugin - shell 0.7.2 enabled dokku core shell plugin - ssh-keys 0.7.2 enabled dokku core ssh-keys plugin - storage 0.7.2 enabled dokku core storage plugin - tags 0.7.2 enabled dokku core tags plugin - tar 0.7.2 enabled dokku core tar plugin + 00_dokku-standard 0.9.4 enabled dokku core standard plugin + 20_events 0.9.4 enabled dokku core events logging plugin + apps 0.9.4 enabled dokku core apps plugin + build-env 0.9.4 enabled dokku core build-env plugin + certs 0.9.4 enabled dokku core certificate management plugin + checks 0.9.4 enabled dokku core checks plugin + common 0.9.4 enabled dokku core common plugin + config 0.9.4 enabled dokku core config plugin + docker-options 0.9.4 enabled dokku core docker-options plugin + domains 0.9.4 enabled dokku core domains plugin + enter 0.9.4 enabled dokku core enter plugin + git 0.9.4 enabled dokku core git plugin + logs 0.9.4 enabled dokku core logs plugin + named-containers 0.9.4 enabled dokku core named containers plugin + nginx-vhosts 0.9.4 enabled dokku core nginx-vhosts plugin + plugin 0.9.4 enabled dokku core plugin plugin + proxy 0.9.4 enabled dokku core proxy plugin + ps 0.9.4 enabled dokku core ps plugin + repo 0.9.4 enabled dokku core repo plugin + shell 0.9.4 enabled dokku core shell plugin + ssh-keys 0.9.4 enabled dokku core ssh-keys plugin + storage 0.9.4 enabled dokku core storage plugin + tags 0.9.4 enabled dokku core tags plugin + tar 0.9.4 enabled dokku core tar plugin ``` Installing a plugin is easy as well using the `plugin:install` command. This command will also trigger the `install` pluginhook on all existing plugins. @@ -72,17 +72,6 @@ Receiving objects: 100% (646/646), 134.24 KiB | 0 bytes/s, done. Resolving deltas: 100% (406/406), done. Checking connectivity... done. -----> Plugin postgres enabled ------> Migrating zero downtime env variables to 0.5.x. The following variables have been deprecated -=====> DOKKU_SKIP_ALL_CHECKS DOKKU_SKIP_DEFAULT_CHECKS -=====> Please use dokku checks:[disable|enable] to control zero downtime functionality -=====> Migration complete -=====> ------> Migrating zero downtime env variables to 0.6.x. The following variables will be migrated -=====> DOKKU_CHECKS_ENABLED -> DOKKU_CHECKS_SKIPPED -=====> Migration complete -=====> -Adding user dokku to group adm -Starting nginx (via systemctl): nginx.service. ``` You can also uninstall a third-party plugin using the `plugin:uninstall` command: diff --git a/docs/advanced-usage/proxy-management.md b/docs/advanced-usage/proxy-management.md index 31d695530..09f88c0c4 100644 --- a/docs/advanced-usage/proxy-management.md +++ b/docs/advanced-usage/proxy-management.md @@ -3,19 +3,21 @@ > New as of 0.5.0, Enhanced in 0.6.0 ``` -proxy # Show proxy settings for app proxy:disable # Disable proxy for app proxy:enable # Enable proxy for app proxy:ports # List proxy port mappings for app proxy:ports-add :: [::...] # Set proxy port mappings for app proxy:ports-clear # Clear all proxy port mappings for app proxy:ports-remove [|::...] # Unset proxy port mappings for app +proxy:report [] [] # Displays a proxy report for one or more apps proxy:set # Set proxy type for app ``` In Dokku 0.5.0, port proxying was decoupled from the `nginx-vhosts` plugin into the proxy plugin. Dokku 0.6.0 introduced the ability to map host ports to specific container ports. In the future this will allow other proxy software - such as HAProxy or Caddy - to be used in place of nginx. -## Container network interface binding +## Usage + +### Container network interface binding > New as of 0.5.0 @@ -58,7 +60,51 @@ CONTAINER ID IMAGE COMMAND CREATED 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 ``` -## Proxy port mapping +### Displaying proxy reports about an app + +> New as of 0.8.1 + +You can get a report about the app's proxy status using the `proxy:report` command: + +```shell +dokku proxy:report +``` + +``` +=====> node-js-app proxy information + Proxy enabled: true + Proxy type: nginx + Proxy port map: http:80:5000 https:443:5000 +=====> python-sample proxy information + Proxy enabled: true + Proxy type: nginx + Proxy port map: http:80:5000 +=====> ruby-sample proxy information + Proxy enabled: true + Proxy type: nginx + Proxy port map: http:80:5000 +``` + +You can run the command for a specific app also. + +```shell +dokku proxy:report node-js-app +``` + +``` +=====> node-js-app proxy information + Proxy enabled: true + Proxy type: nginx + Proxy port map: http:80:5000 https:443:5000 +``` + +You can pass flags which will output only the value of the specific information you want. For example: + +```shell +dokku proxy:report node-js-app --proxy-type +``` + +### Proxy port mapping > New as of 0.6.0 @@ -141,7 +187,7 @@ By default, buildpack apps and dockerfile apps **without** explicitly exposed po > Note: This default behavior **will not** be automatically changed on subsequent pushes and must be manipulated with the `proxy:ports-*` syntax detailed above. -## Proxy Port Scheme +#### Proxy Port Scheme The proxy port scheme is as follows: diff --git a/docs/advanced-usage/repository-management.md b/docs/advanced-usage/repository-management.md index 9cee535aa..e8235f863 100644 --- a/docs/advanced-usage/repository-management.md +++ b/docs/advanced-usage/repository-management.md @@ -3,7 +3,7 @@ > New as of 0.6.0 ``` -repo:gc # Runs 'git gc --agressive' against the application's repo +repo:gc # Runs 'git gc --aggressive' against the application's repo repo:purge-cache # Deletes the contents of the build cache stored in the repository ``` @@ -13,7 +13,7 @@ The repository plugin is meant to allow users to perform management commands aga ### Git Garbage Collection -This will run a git gc --agressive against the applications repo. This is performed on the Dokku host, and not within an application container. +This will run a git gc --aggressive against the applications repo. This is performed on the Dokku host, and not within an application container. ```shell dokku repo:gc node-js-app diff --git a/docs/appendices/0.5.0-migration-guide.md b/docs/appendices/0.5.0-migration-guide.md index 7c3dd4e7e..da074399f 100644 --- a/docs/appendices/0.5.0-migration-guide.md +++ b/docs/appendices/0.5.0-migration-guide.md @@ -5,11 +5,11 @@ - 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/configuration/nginx/#available-template-variables) + - A detailed list of template variables can be found [here](/docs/configuration/nginx.md#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/configuration/nginx/) + - Check out an example template [here](/docs/configuration/nginx.md) - 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 @@ -20,6 +20,6 @@ ## Dockerfile apps with exposed ports -- Dockerfiles with `EXPOSE` clauses will get [all **tcp** ports proxied by default](/dokku/deployment/methods/dockerfiles/#exposed-ports) +- Dockerfiles with `EXPOSE` clauses will get [all **tcp** ports proxied by default](/docs/deployment/methods/dockerfiles.md#exposed-ports) - Note that nginx will proxy the same port numbers to listen publicly - - UDP ports can be exposed by disabling the [nginx proxy](/dokku/advanced-usage/proxy-management/) with `dokku proxy:disable myapp` + - UDP ports can be exposed by disabling the [nginx proxy](/docs/advanced-usage/proxy-management.md) with `dokku proxy:disable myapp` diff --git a/docs/appendices/0.6.0-migration-guide.md b/docs/appendices/0.6.0-migration-guide.md index 76d4cc86f..e5afebe9d 100644 --- a/docs/appendices/0.6.0-migration-guide.md +++ b/docs/appendices/0.6.0-migration-guide.md @@ -7,16 +7,16 @@ - `DOKKU_CHECKS_ENABLED` has been migrated to `DOKKU_CHECKS_SKIPPED` - `DOKKU_CHECKS_DISABLED` is now a thing - The values for the above can be a comma-separated list of process-types or the literal string `_all_` - - See the [updated `checks`](/dokku/deployment/zero-downtime-deploys/) docs for more info + - See the [updated `checks`](/docs/deployment/zero-downtime-deploys.md) docs for more info ## Proxy port mapping - You can now configure host -> container proxy port mappings - - The UI is handled by the [proxy](/dokku/advanced-usage/proxy-management/) interface plugin by setting the `DOKKU_PROXY_PORT_MAP` config variable in the format of `scheme:host-port:container-port`. The default `nginx-vhosts` proxy plugin supports both the `http` and `https` schemes. + - The UI is handled by the [proxy](/docs/advanced-usage/proxy-management.md) interface plugin by setting the `DOKKU_PROXY_PORT_MAP` config variable in the format of `scheme:host-port:container-port`. The default `nginx-vhosts` proxy plugin supports both the `http` and `https` schemes. - Default port mappings - buildpack apps will be set to `http:80:5000` and will also include `https:443:5000` if SSL is enabled. - dockerfile apps with explicitly exposed ports (i.e. using the `EXPOSE` directive) will be configured with a listener on each exposed port that proxies to the same port of the deployed application container. - - You may override this behavior with the [`proxy:ports-*` commands](/dokku/advanced-usage/proxy-management/) or by directly setting `DOKKU_PROXY_PORT_MAP` with the `config:set` command + - You may override this behavior with the [`proxy:ports-*` commands](/docs/advanced-usage/proxy-management.md) or by directly setting `DOKKU_PROXY_PORT_MAP` with the `config:set` command - dockerfile apps without explicitly exposed ports will behave the same as a buildpack app - NOTE: These defaults **are not** automatically changed on subsequent pushes and must be manipulated with the aforementioned commands diff --git a/docs/appendices/0.7.0-migration-guide.md b/docs/appendices/0.7.0-migration-guide.md index 34ff26239..927fc0235 100644 --- a/docs/appendices/0.7.0-migration-guide.md +++ b/docs/appendices/0.7.0-migration-guide.md @@ -2,12 +2,12 @@ ## Persistent Storage -We should now properly handle file ownership for mounted directories. This was enhanced in 0.7.1. See the [persistent storage documentation](/dokku/advanced-usage/persistent-storage/) for more details. +We should now properly handle file ownership for mounted directories. This was enhanced in 0.7.1. See the [persistent storage documentation](/docs/advanced-usage/persistent-storage.md) for more details. ## Restart Policies -If you previously managed container restart policies via docker-options, these can now be managed natively via the built-in `ps` plugin. There is no migration necessary. See the [restart policy documentation](/dokku/deployment/process-management/#restart-policies) for more details. +If you previously managed container restart policies via docker-options, these can now be managed natively via the built-in `ps` plugin. There is no migration necessary. See the [restart policy documentation](/docs/deployment/process-management.md#restart-policies) for more details. ## SSH Keys -Dokku now has an `ssh-keys` plugin that can be used to manage ssh keys for the `dokku` user on the host operating system. See the [user management documentation](/dokku/deployment/user-management/) for more details. +Dokku now has an `ssh-keys` plugin that can be used to manage ssh keys for the `dokku` user on the host operating system. See the [user management documentation](/docs/deployment/user-management.md) for more details. diff --git a/docs/assets/favicons/browserconfig.xml b/docs/assets/favicons/browserconfig.xml index 1881ad8c8..76a478dfa 100644 --- a/docs/assets/favicons/browserconfig.xml +++ b/docs/assets/favicons/browserconfig.xml @@ -2,10 +2,10 @@ - - - - + + + + #da532c diff --git a/docs/assets/favicons/manifest.json b/docs/assets/favicons/manifest.json index a3d6a7458..c3c4b0d8d 100644 --- a/docs/assets/favicons/manifest.json +++ b/docs/assets/favicons/manifest.json @@ -2,37 +2,37 @@ "name": "Dokku", "icons": [ { - "src": "https:\/\/cdn.rawgit.com\/progrium\/dokku\/v0.7.2\/docs\/assets\/favicons\/android-chrome-36x36.png", + "src": "https:\/\/cdn.rawgit.com\/progrium\/dokku\/v0.9.4\/docs\/assets\/favicons\/android-chrome-36x36.png", "sizes": "36x36", "type": "image\/png", "density": "0.75" }, { - "src": "https:\/\/cdn.rawgit.com\/progrium\/dokku\/v0.7.2\/docs\/assets\/favicons\/android-chrome-48x48.png", + "src": "https:\/\/cdn.rawgit.com\/progrium\/dokku\/v0.9.4\/docs\/assets\/favicons\/android-chrome-48x48.png", "sizes": "48x48", "type": "image\/png", "density": "1.0" }, { - "src": "https:\/\/cdn.rawgit.com\/progrium\/dokku\/v0.7.2\/docs\/assets\/favicons\/android-chrome-72x72.png", + "src": "https:\/\/cdn.rawgit.com\/progrium\/dokku\/v0.9.4\/docs\/assets\/favicons\/android-chrome-72x72.png", "sizes": "72x72", "type": "image\/png", "density": "1.5" }, { - "src": "https:\/\/cdn.rawgit.com\/progrium\/dokku\/v0.7.2\/docs\/assets\/favicons\/android-chrome-96x96.png", + "src": "https:\/\/cdn.rawgit.com\/progrium\/dokku\/v0.9.4\/docs\/assets\/favicons\/android-chrome-96x96.png", "sizes": "96x96", "type": "image\/png", "density": "2.0" }, { - "src": "https:\/\/cdn.rawgit.com\/progrium\/dokku\/v0.7.2\/docs\/assets\/favicons\/android-chrome-144x144.png", + "src": "https:\/\/cdn.rawgit.com\/progrium\/dokku\/v0.9.4\/docs\/assets\/favicons\/android-chrome-144x144.png", "sizes": "144x144", "type": "image\/png", "density": "3.0" }, { - "src": "https:\/\/cdn.rawgit.com\/progrium\/dokku\/v0.7.2\/docs\/assets\/favicons\/android-chrome-192x192.png", + "src": "https:\/\/cdn.rawgit.com\/progrium\/dokku\/v0.9.4\/docs\/assets\/favicons\/android-chrome-192x192.png", "sizes": "192x192", "type": "image\/png", "density": "4.0" diff --git a/docs/assets/style.css b/docs/assets/style.css index 34d6acc43..513df6211 100644 --- a/docs/assets/style.css +++ b/docs/assets/style.css @@ -1,7 +1,6 @@ body { - font-family: 'Open Sans', sans-serif; + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; font-weight: 400; - font-size: 12px; padding-bottom: 40px; color: #5a5a5a; -webkit-font-smoothing: antialiased; @@ -21,7 +20,7 @@ h1 { .header .heading { color: #fff; font-size: 23px; - margin: 0; + margin: 7px 0 0 0; padding: 0 0 0 1em; } .header a { @@ -36,13 +35,13 @@ h1 { background-repeat: no-repeat; } .header .navbar-brand a { - background-image: url(https://cdn.rawgit.com/dokku/dokku/v0.7.2/docs/assets/dokku.png); + background-image: url(https://cdn.rawgit.com/dokku/dokku/v0.9.4/docs/assets/dokku.png); text-indent: 40px; } .blurb { color: #424242; background-color: #ededed; - background-image: url(https://cdn.rawgit.com/dokku/dokku/v0.7.2/docs/assets/gplaypattern.png); + background-image: url(https://cdn.rawgit.com/dokku/dokku/v0.9.4/docs/assets/gplaypattern.png); padding: 45px 0; text-align: center; } @@ -67,7 +66,6 @@ h1 { color: #444; display: block; font-weight: 400; - font-size: 14px; margin: 0 20px; padding: 3px 0; text-align: center; @@ -211,6 +209,7 @@ h1 { .list-group-item { border: none; + font-size: 14px; font-weight: normal; padding: .2rem .5rem; color: #157577; @@ -246,10 +245,9 @@ a.list-group-item { -webkit-font-smoothing: antialiased; box-sizing: border-box; color: rgb(71, 74, 84); - font-size: 14px; font-weight: normal; height: 20px; - line-height: 20px; + line-height: 1.5; text-align: left; } .markdown-body h1 { diff --git a/docs/assets/versions.json b/docs/assets/versions.json index 32140569b..4dfd3da4c 100644 --- a/docs/assets/versions.json +++ b/docs/assets/versions.json @@ -1 +1,11 @@ -{ "max-versions": [ "0.3.26", "0.4.14", "0.5.8", "0.6.5", "0.7.2" ] } +{ + "max-versions": [ + "0.3.26", + "0.4.14", + "0.5.8", + "0.6.5", + "0.7.2", + "0.8.2", + "0.9.4" + ] +} diff --git a/docs/community/plugins.md b/docs/community/plugins.md index a961ed5f0..4943f221c 100644 --- a/docs/community/plugins.md +++ b/docs/community/plugins.md @@ -12,11 +12,11 @@ Let's take a quick look at the current Dokku nginx plugin that's shipped with Do ## Installing a plugin -[See the plugin management documentation](/dokku/advanced-usage/plugin-management/). +[See the plugin management documentation](/docs/advanced-usage/plugin-management.md). ## Creating your own plugin -[See the full documentation](/dokku/development/plugin-creation/). +[See the full documentation](/docs/development/plugin-creation.md). ## Official Plugins (Beta) @@ -69,6 +69,7 @@ Note: The following plugins have been supplied by our community and may not have [cu12]: https://github.com/cu12 [darkpixel]: https://github.com/darkpixel [dokku]: https://github.com/dokku +[dokku-community]: https://github.com/dokku-community [dyson]: https://github.com/dyson [F4-Group]: https://github.com/F4-Group [fermuch]: https://github.com/fermuch @@ -90,6 +91,7 @@ Note: The following plugins have been supplied by our community and may not have [luxifer]: https://github.com/luxifer [Maciej Łebkowski]: https://github.com/mlebkowski [matto1990]: https://github.com/matto1990 +[mbreit]: https://github.com/mbreit [mbriskar]: https://github.com/mbriskar [michaelshobbs]: https://github.com/michaelshobbs [mikecsh]: https://github.com/mikecsh @@ -119,6 +121,7 @@ Note: The following plugins have been supplied by our community and may not have [thrashr888]: https://github.com/thrashr888 [wmluke]: https://github.com/wmluke [Zenedith]: https://github.com/Zenedith +[fteychene]: https://github.com/fteychene ### Datastores @@ -162,7 +165,7 @@ Note: The following plugins have been supplied by our community and may not have [c77cbf1]: https://github.com/dokku/dokku/commit/c77cbf1d3ae07f0eafb85082ed7edcae9e836147 [28de3ec]: https://github.com/dokku/dokku/commit/28de3ecaa3231a223f83fd8d03f373308673bc40 -### Dokku Features +### Plugins Implementing New Dokku Functionality | Plugin | Author | Compatibility | | ------------------------------------------------------------------------------------------------- | --------------------- | --------------------- | @@ -178,12 +181,14 @@ Note: The following plugins have been supplied by our community and may not have | [Haproxy tcp load balancer](https://github.com/256dpi/dokku-haproxy) | [256dpi][] | 0.4.0+ | | [Hostname](https://github.com/michaelshobbs/dokku-hostname) | [michaelshobbs][] | 0.4.0+ | | [HTTP Auth Secure Apps](https://github.com/matto1990/dokku-secure-apps) | [matto1990][] | 0.4.0+ | +| [Monit (Health Checks)](https://github.com/mbreit/dokku-monit) | [mbreit][] | 0.8.0+ | | [Nuke Containers](https://github.com/josegonzalez/dokku-nuke) | [josegonzalez][] | 0.4.0+ | | [Open App Ports](https://github.com/josegonzalez/dokku-ports) | [josegonzalez][] | 0.3.x | | [Proctype Filter](https://github.com/michaelshobbs/dokku-proctype-filter) | [michaelshobbs][] | 0.4.0+ | | [robots.txt](https://notabug.org/candlewaster/dokku-robots.txt) | [candlewaster][] | 0.4.x | | [SSH Deployment Keys](https://github.com/cedricziel/dokku-deployment-keys)3 | [cedricziel][] | 0.4.0+ | | [SSH Hostkeys](https://github.com/cedricziel/dokku-hostkeys-plugin)4 | [cedricziel][] | 0.3.x | +| [Application build hook](https://github.com/fteychene/dokku-build-hook) | [fteychene][] | 0.4.0+ | [217d00a]: https://github.com/dokku/dokku/commit/217d00a1bc47a7e24d8847617bb08a1633025fc7 @@ -218,10 +223,11 @@ Note: The following plugins have been supplied by our community and may not have | [Rollbar](https://github.com/iloveitaly/dokku-rollbar) | [iloveitaly][] | 0.5.0+ | | [Slack Notifications](https://github.com/ribot/dokku-slack) | [ribot][] | 0.4.0+ | | [Tor](https://github.com/michaelshobbs/dokku-tor) | [michaelshobbs][] | 0.4.0+ | -| [User ACL](https://github.com/mlebkowski/dokku-acl) | [Maciej Łebkowski][] | 0.4.0+ | +| [User ACL](https://github.com/dokku-community/dokku-acl) | [Maciej Łebkowski][] | 0.4.0+ | | [Webhooks](https://github.com/nickstenning/dokku-webhooks) | [nickstenning][] | 0.3.x | | [Wkhtmltopdf](https://github.com/mbriskar/dokku-wkhtmltopdf) | [mbriskar][] | 0.4.0+ | -| [Wordpress Setup](https://github.com/dudagroup/dokku-wordpress-template) | [abossard][] | | +| [Dokku Wordpress](https://github.com/dokku-community/dokku-wordpress) | [dokku-community][] | 0.4.0+ | +| [Access](https://github.com/mainto/dokku-access) | [mainto](https://github.com/mainto) | 0.4.0+ | ### Deprecated Plugins diff --git a/docs/configuration/dns.md b/docs/configuration/dns.md index c75c55b8a..1bb79249b 100644 --- a/docs/configuration/dns.md +++ b/docs/configuration/dns.md @@ -70,7 +70,7 @@ If everything is working correctly, you should also be able to query for any oth If they all return your IP address, you have set DNS up properly for dokku. You should also be able to `ssh root@myserver.example.tld` and access your server. -Proceed with the setup instructions in the [installation documentation](/dokku/getting-started/installation/) +Proceed with the setup instructions in the [installation documentation](/docs/getting-started/installation.md) #### Using the root of your domain (myapp.example.tld) diff --git a/docs/configuration/domains.md b/docs/configuration/domains.md index fdda6a71f..cb3782e54 100644 --- a/docs/configuration/domains.md +++ b/docs/configuration/domains.md @@ -5,15 +5,17 @@ ``` domains:add [ ...] # Add domains to app domains:add-global [ ...] # Add global domain names -domains [] # List domains domains:clear # Clear all domains for app domains:disable # Disable VHOST support domains:enable # Enable VHOST support domains:remove [ ...] # Remove domains from app domains:remove-global [ ...] # Remove global domain names +domains:report [] [] # Displays a domains report for one or more apps +domains:set [ ...] # Set domains for app +domains:set-global [ ...] # Set global domain names ``` -> Adding a domain before deploying an application will result in port mappings being set. This may cause issues for applications that use non-standard ports, as those will not be automatically detected. Please refer to the [proxy documentation](/dokku/advanced-usage/proxy-management/) for information as to how to reconfigure the mappings. +> Adding a domain before deploying an application will result in port mappings being set. This may cause issues for applications that use non-standard ports, as those will not be automatically detected. Please refer to the [proxy documentation](/docs/advanced-usage/proxy-management.md) for information as to how to reconfigure the mappings. ## Customizing hostnames @@ -39,7 +41,7 @@ NEW_SUBDOMAIN=`echo $SUBDOMAIN | rev` echo "$NEW_SUBDOMAIN.$VHOST" ``` -If the `nginx-hostname` has no output, the normal hostname algorithm will be executed. +If the `nginx-hostname` plugin has no output, the normal hostname algorithm will be executed. ## Disabling VHOSTS @@ -68,11 +70,64 @@ dokku domains:clear myapp # remove a custom domain from app dokku domains:remove myapp example.com + +# set all custom domains for app +dokku domains:set myapp example.com example.org +``` + +## Displaying domains reports about an app + +> New as of 0.8.1 + +You can get a report about the app's domains status using the `domains:report` command: + +```shell +dokku domains:report +``` + +``` +=====> node-js-app domains information + Domains app enabled: true + Domains app vhosts: ruby-sample.example.org + Domains global enabled: true + Domains global vhosts: example.org +=====> python-sample domains information + Domains app enabled: true + Domains app vhosts: ruby-sample.example.org + Domains global enabled: true + Domains global vhosts: example.org +=====> ruby-sample domains information + Domains app enabled: true + Domains app vhosts: ruby-sample.example.org + Domains global enabled: true + Domains global vhosts: example.org +``` + +You can run the command for a specific app also. + +```shell +dokku domains:report node-js-app +``` + +``` +=====> node-js-app domains information + Domains app enabled: true + Domains app vhosts: node-js-app.example.org + Domains global enabled: true + Domains global vhosts: example.org +``` + +You can pass flags which will output only the value of the specific information you want. For example: + +```shell +dokku domains:report node-js-app --domains-app-enabled ``` ## Default site -By default, Dokku will route any received request with an unknown HOST header value to the lexicographically first site in the nginx config stack. If this is not the desired behavior, you may want to add the following configuration to the global nginx configuration. This will catch all unknown HOST header values and return a `410 Gone` response. You can replace the `return 410;` with `return 444;` which will cause nginx to not respond to requests that do not match known domains (connection refused). +By default, Dokku will route any received request with an unknown HOST header value to the lexicographically first site in the nginx config stack. If this is not the desired behavior, you may want to add the following configuration to the global nginx configuration. + +Create the file at `/etc/nginx/conf.d/00-default-vhost.conf`: ```nginx server { @@ -85,17 +140,10 @@ server { } ``` -You may also wish to use a separate vhost in your `/etc/nginx/sites-enabled` directory. To do so, create the vhost in that directory as `/etc/nginx/sites-enabled/00-default.conf`. You will also need to change two lines in the main `nginx.conf`: +Make sure to reload nginx after creating this file by running `service nginx reload`. -```nginx -# Swap both conf.d include line and the sites-enabled include line. From: -include /etc/nginx/conf.d/*.conf; -include /etc/nginx/sites-enabled/*; +This will catch all unknown HOST header values and return a `410 Gone` response. You can replace the `return 410;` with `return 444;` which will cause nginx to not respond to requests that do not match known domains (connection refused). -# to the following - -include /etc/nginx/sites-enabled/*; -include /etc/nginx/conf.d/*.conf; -``` +The configuration file must be loaded before `/etc/nginx/conf.d/dokku.conf`, so it can not be arranged as a vhost in `/etc/nginx/sites-enabled` that is only processed afterwards. Alternatively, you may push an app to your Dokku host with a name like "00-default". As long as it lists first in `ls /home/dokku/*/nginx.conf | head`, it will be used as the default nginx vhost. diff --git a/docs/configuration/nginx.md b/docs/configuration/nginx.md index 31f95a234..accaa2c8d 100644 --- a/docs/configuration/nginx.md +++ b/docs/configuration/nginx.md @@ -15,7 +15,7 @@ nginx:error-logs [-t] # Show the nginx error logs for an appl Dokku uses a templating library by the name of [sigil](https://github.com/gliderlabs/sigil) to generate nginx configuration for each app. You may also provide a custom template for your application as follows: - Copy the following example template to a file named `nginx.conf.sigil` and either: - - check it into the root of your app repo for buildpack applications + - If using a buildpack application, you __must__ check it into the root of your app repo. - `ADD` it to your dockerfile `WORKDIR` - if your dockerfile has no `WORKDIR`, `ADD` it to the `/app` folder @@ -202,36 +202,36 @@ The example above uses additional configuration files directly on the Dokku host ## Domains plugin -See the [domain configuration documentation](/dokku/configuration/domains/). +See the [domain configuration documentation](/docs/configuration/domains.md). ## Customizing hostnames -See the [customizing hostnames documentation](/dokku/configuration/domains/#customizing-hostnames). +See the [customizing hostnames documentation](/docs/configuration/domains.md#customizing-hostnames). ## Disabling VHOSTS -See the [disabling vhosts documentation](/dokku/configuration/domains/#disabling-vhosts). +See the [disabling vhosts documentation](/docs/configuration/domains.md#disabling-vhosts). ## Default site -See the [default site documentation](/dokku/configuration/domains/#default-site). +See the [default site documentation](/docs/configuration/domains.md#default-site). ## Running behind a load balancer -See the [load balancer documentation](/dokku/configuration/ssl/#running-behind-a-load-balancer). +See the [load balancer documentation](/docs/configuration/ssl.md#running-behind-a-load-balancer). ## HSTS Header -See the [HSTS documentation](/dokku/configuration/ssl/#hsts-header). +See the [HSTS documentation](/docs/configuration/ssl.md#hsts-header). ## SSL Configuration -See the [ssl documentation](/dokku/configuration/ssl/). +See the [ssl documentation](/docs/configuration/ssl.md). ## Disabling Nginx -See the [proxy documentation](/dokku/advanced-usage/proxy-management/). +See the [proxy documentation](/docs/advanced-usage/proxy-management.md). ## Managing Proxy Port mappings -See the [proxy documentation](/dokku/advanced-usage/proxy-management/#proxy-port-mapping). +See the [proxy documentation](/docs/advanced-usage/proxy-management.md#proxy-port-mapping). diff --git a/docs/configuration/ssl.md b/docs/configuration/ssl.md index 7a1a397d8..de04c3348 100644 --- a/docs/configuration/ssl.md +++ b/docs/configuration/ssl.md @@ -7,8 +7,8 @@ Dokku supports SSL/TLS certificate inspection and CSR/Self-signed certificate ge ``` certs:add CRT KEY # Add an ssl endpoint to an app. Can also import from a tarball on stdin. certs:generate DOMAIN # Generate a key and certificate signing request (and self-signed certificate) -certs:info # Show certificate information for an ssl endpoint. certs:remove # Remove an SSL Endpoint from an app. +certs:report [] [] # Displays an ssl report for one or more apps certs:update CRT KEY # Update an SSL Endpoint on an app. Can also import from a tarball on stdin ``` @@ -17,7 +17,7 @@ certs:update CRT KEY # Update an SSL Endpoint on an app. Can dokku nginx:import-ssl < certs.tar ``` -> Adding an ssl certificate before deploying an application will result in port mappings being updated. This may cause issues for applications that use non-standard ports, as those may not be automatically detected. Please refer to the [proxy documentation](/dokku/advanced-usage/proxy-management/) for information as to how to reconfigure the mappings. +> Adding an ssl certificate before deploying an application will result in port mappings being updated. This may cause issues for applications that use non-standard ports, as those may not be automatically detected. Please refer to the [proxy documentation](/docs/advanced-usage/proxy-management.md) for information as to how to reconfigure the mappings. ## Per-application certificate management @@ -29,8 +29,7 @@ The `certs:add` command can be used to push a `tar` containing a certificate `.c ```shell tar cvf cert-key.tar server.crt server.key -# replace APP with the name of your application -dokku certs:add < cert-key.tar +dokku certs:add node-js-app < cert-key.tar ``` > Note: If your `.crt` file came alongside a `.ca-bundle`, you'll want to concatenate those into a single `.crt` file before adding it to the `.tar`. @@ -41,9 +40,9 @@ cat yourdomain_com.crt yourdomain_com.ca-bundle > server.crt #### SSL and Multiple Domains -When an SSL certificate is associated to an application, the certificate will be associated with *all* domains currently associated with said application. Your certificate _should_ be associated with all of those domains, otherwise accessing the application will result in SSL errors. If you wish to remove one of the domains from the application, refer to the [domain configuration documentation](/dokku/configuration/domains/). +When an SSL certificate is associated to an application, the certificate will be associated with *all* domains currently associated with said application. Your certificate _should_ be associated with all of those domains, otherwise accessing the application will result in SSL errors. If you wish to remove one of the domains from the application, refer to the [domain configuration documentation](/docs/configuration/domains.md). -Note that with the default nginx template, requests will be redirected to the `https` version of the domain. If this is not the desired state of request resolution, you may customize the nginx template in use. For more details, see the [nginx documentation](/dokku/configuration/nginx/). +Note that with the default nginx template, requests will be redirected to the `https` version of the domain. If this is not the desired state of request resolution, you may customize the nginx template in use. For more details, see the [nginx documentation](/docs/configuration/nginx.md). ### Certificate generation @@ -51,32 +50,66 @@ Note that with the default nginx template, requests will be redirected to the `h The `certs:generate` command will walk you through the correct `openssl` commands to create a key, csr and a self-signed cert for a given app/domain. We automatically put the self-signed cert in place as well as add the specified domain to the application configuration. -If you decide to obtain a CA signed certficate, you can import that certificate using the aformentioned `dokku certs:add` command. - -### Certificate information - -The `certs:info` command will simply inspect the install SSL cert and print out details. NOTE: The server-wide certificate will be inspect if installed and no app-specific certificate exists. - -```shell -dokku certs:info node-js-app -``` - -``` ------> Fetching SSL Endpoint info for node-js-app... ------> Certificate details: -=====> Common Name(s): -=====> test.dokku.me -=====> Expires At: Aug 24 23:32:59 2016 GMT -=====> Issuer: C=US, ST=California, L=San Francisco, O=dokku.me, CN=test.dokku.me -=====> Starts At: Aug 25 23:32:59 2015 GMT -=====> Subject: C=US; ST=California; L=San Francisco; O=dokku.me; CN=test.dokku.me -=====> SSL certificate is self signed. -``` +If you decide to obtain a CA signed certificate, you can import that certificate using the aforementioned `dokku certs:add` command. ### Certificate removal The `certs:remove` command only works on app-specific certificates. It will `rm` the app-specific tls directory, rebuild the nginx configuration, and reload nginx. +### Displaying certificate reports about an app + +> New as of 0.8.1 + +You can get a report about the apps ssl status using the `certs:report` command: + +```shell +dokku certs:report +``` + +``` +=====> node-js-sample + Ssl dir: /home/dokku/node-js-sample/tls + Ssl enabled: true + Ssl hostnames: *.node-js-sample.org node-js-sample.org + Ssl expires at: Oct 5 23:59:59 2019 GMT + Ssl issuer: C=GB, ST=Greater Manchester, L=Salford, O=COMODO CA Limited, CN=COMODO RSA Domain Validation Secure Server CA + Ssl starts at: Oct 5 00:00:00 2016 GMT + Ssl subject: OU=Domain Control Validated; OU=PositiveSSL Wildcard; CN=*.node-js-sample.org + Ssl verified: self signed. +=====> python-sample + Ssl dir: /home/dokku/python-sample/tls + Ssl enabled: false + Ssl hostnames: + Ssl expires at: + Ssl issuer: + Ssl starts at: + Ssl subject: + Ssl verified: +``` + +You can run the command for a specific app also. + +```shell +dokku certs:report node-js-sample +``` + +``` +=====> node-js-sample ssl information + Ssl dir: /home/dokku/node-js-sample/tls + Ssl enabled: true + Ssl hostnames: *.example.org example.org + Ssl expires at: Oct 5 23:59:59 2019 GMT + Ssl issuer: C=GB, ST=Greater Manchester, L=Salford, O=COMODO CA Limited, CN=COMODO RSA Domain Validation Secure Server CA + Ssl starts at: Oct 5 00:00:00 2016 GMT + Ssl subject: OU=Domain Control Validated; OU=PositiveSSL Wildcard; CN=*.example.org + Ssl verified: self signed. +``` + +You can pass flags which will output only the value of the specific information you want. For example: + +```shell +dokku certs:report node-js-sample --ssl-enabled +``` ## HSTS Header @@ -86,9 +119,9 @@ Beware that if you enable the header and a subsequent deploy of your application ## Running behind a load balancer -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. +Your application has access to the HTTP headers `X-Forwarded-Proto`, `X-Forwarded-Port` and `X-Forwarded-For`. 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 using the following [nginx custom template](/dokku/configuration/nginx/#customizing-the-nginx-configuration) +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](/docs/configuration/nginx.md#customizing-the-nginx-configuration) ```go server { diff --git a/docs/deployment/application-deployment.md b/docs/deployment/application-deployment.md index e96df35ba..c3686c750 100644 --- a/docs/deployment/application-deployment.md +++ b/docs/deployment/application-deployment.md @@ -6,6 +6,7 @@ Once Dokku has been configured with at least one user, applications can be deplo ```shell # from your local machine +# SSH access to github must be enabled on this host git clone git@github.com:heroku/ruby-rails-sample.git ``` @@ -20,7 +21,7 @@ dokku apps:create ruby-rails-sample ### Create the backing services -When you create a new app, Dokku by default *does not* provide any datastores such as MySQL or PostgreSQL. You will need to install plugins to handle that, but fortunately [Dokku has official plugins](/dokku/community/plugins/#official-plugins-beta) for common datastores. Our sample app requires a PostgreSQL service: +When you create a new app, Dokku by default *does not* provide any datastores such as MySQL or PostgreSQL. You will need to install plugins to handle that, but fortunately [Dokku has official plugins](/docs/community/plugins.md#official-plugins-beta) for common datastores. Our sample app requires a PostgreSQL service: ```shell # on your Dokku host @@ -52,6 +53,8 @@ Now you can deploy the `ruby-rails-sample` app to your Dokku server. All you hav ```shell # from your local machine +# the remote username *must* be dokku or pushes will fail +cd ruby-rails-sample git remote add dokku dokku@dokku.me:ruby-rails-sample git push dokku master ``` @@ -90,7 +93,19 @@ Dokku supports deploying applications via [Heroku buildpacks](https://devcenter. Dokku only supports deploying from its master branch, so if you'd like to deploy a different local branch use: ```git push dokku :master``` -You can also support pushing multiple branches using the [receive-branch](/dokku/development/plugin-triggers/#receive-branch) plugin trigger in a custom plugin. +An alternative is to use the `DOKKU_DEPLOY_BRANCH` application config value to specify a branch that should be deployed. The implicit default is master, and this can be modified both at the app and global level: + +```shell +# on your Dokku host + +# set it globally +dokku config:set --global DOKKU_DEPLOY_BRANCH=some-branch + +# override for a specific app +dokku config:set ruby-rails-sample DOKKU_DEPLOY_BRANCH=some-branch +``` + +You can also support pushing multiple branches using the [receive-branch](/docs/development/plugin-triggers.md#receive-branch) plugin trigger in a custom plugin. ### Skipping deployment @@ -101,6 +116,17 @@ If you only want to rebuild and tag a container, you can skip the deployment pha dokku config:set ruby-rails-sample DOKKU_SKIP_DEPLOY=true ``` +### Re-Deploying / restarting + +If you need to re-deploy (or restart) your app: + +```shell +# on your Dokku host +dokku ps:rebuild ruby-rails-sample +``` + +See the [process scaling documentation](/docs/deployment/process-management.md) for more information. + ### Deploying with private git submodules Dokku uses git locally (i.e. not a docker image) to build its own copy of your app repo, including submodules. This is done as the `dokku` user. Therefore, in order to deploy private git submodules, you'll need to drop your deploy key in `/home/dokku/.ssh/` and potentially add github.com (or your VCS host key) into `/home/dokku/.ssh/known_hosts`. The following test should help confirm you've done it correctly. @@ -119,6 +145,8 @@ Note that if the buildpack or dockerfile build process require ssh key access fo The name of remote repository is used as the name of application to be deployed, as for example above: ```shell +# from your local machine +# the remote username *must* be dokku or pushes will fail git remote add dokku dokku@dokku.me:ruby-rails-sample git push dokku master ``` @@ -131,6 +159,8 @@ remote: http://ruby-rails-sample.dokku.me You can also specify fully qualified names, say `app.dokku.me`, as ```shell +# from your local machine +# the remote username *must* be dokku or pushes will fail git remote add dokku dokku@dokku.me:app.dokku.me git push dokku master ``` @@ -143,6 +173,8 @@ remote: http://app.dokku.me This is in particular useful, then you want to deploy to root domain, as ```shell +# from your local machine +# the remote username *must* be dokku or pushes will fail git remote add dokku dokku@dokku.me:dokku.me git push dokku master ``` @@ -160,32 +192,32 @@ As of 0.5.x, this function removes all containers with the label `dokku` where t ## Adding deploy users -See the [user management documentation](/dokku/deployment/user-management). +See the [user management documentation](/docs/deployment/user-management.md). ## Default vhost -See the [nginx documentation](/dokku/configuration/nginx/#default-site). +See the [nginx documentation](/docs/configuration/nginx.md#default-site). ## Dockerfile deployment -See the [dockerfile documentation](/dokku/deployment/methods/dockerfiles/). +See the [dockerfile documentation](/docs/deployment/methods/dockerfiles.md). ## Specifying a custom buildpack -See the [buildpack documentation](/dokku/deployment/methods/buildpacks/). +See the [buildpack documentation](/docs/deployment/methods/buildpacks.md). ## Image tagging -See the [image tagging documentation](/dokku/deployment/methods/images/). +See the [image tagging documentation](/docs/deployment/methods/images.md). ## Removing a deployed app -See the [application management documentation](/dokku/deployment/application-management/#removing-a-deployed-app). +See the [application management documentation](/docs/deployment/application-management.md#removing-a-deployed-app). ### Renaming a deployed app -See the [application management documentation](/dokku/deployment/application-management/#renaming-a-deployed-app). +See the [application management documentation](/docs/deployment/application-management.md#renaming-a-deployed-app). ## Zero downtime deploy -See the [zero-downtime deploy documentation](/dokku/deployment/zero-downtime-deploys/). +See the [zero-downtime deploy documentation](/docs/deployment/zero-downtime-deploys.md). diff --git a/docs/deployment/application-management.md b/docs/deployment/application-management.md index 9b44c3ebc..866791f06 100644 --- a/docs/deployment/application-management.md +++ b/docs/deployment/application-management.md @@ -3,20 +3,24 @@ > New as of 0.3.1 ``` -apps # List your apps +apps:clone # Clones an app apps:create # Create a new app apps:destroy # Permanently destroy an app +apps:list # List your apps apps:rename # Rename an app +apps:report [] [] # Display report about an app ``` ## Usage ### Listing Applications -You can easily list all available applications using the `apps` command: +> New as of 0.8.1. Use the `apps` command for older versions. + +You can easily list all available applications using the `apps:list` command: ```shell -dokku apps +dokku apps:list ``` ``` @@ -28,7 +32,7 @@ python-app Note that you can easily hide extra output from Dokku commands by using the `--quiet` flag, which makes it easier to parse on the command-line. ```shell -dokku --quiet apps +dokku --quiet apps:list ``` ``` @@ -112,3 +116,72 @@ Renaming node-js-app to io-js-app... done ``` This will copy all of your app's contents into a new app directory with the name of your choice, delete your old app, then rebuild the new version of the app and deploy it. All of your config variables, including database urls, will be preserved. + +### Cloning an existing app + +> New as of 0.8.1 + +You can clone an existing app using the `apps:clone` command. Note that the application *must* have been deployed at least once, or cloning will not complete successfully: + +```shell +dokku apps:clone node-js-app io-js-app +``` + +``` +Cloning node-js-app to io-js-app... done +``` + +This will copy all of your app's contents into a new app directory with the name of your choice and then rebuild the new version of the app and deploy it. All of your config variables, including database urls, will be preserved. + +> Warning: If you have exposed specific ports via docker-options, added generic domains, or performed anything that cannot be done against multiple applications, `apps:clone` may result in errors. + +By default, Dokku will deploy this new application, though you can skip the deploy by using the `--skip-deploy` flag: + +```shell +dokku apps:clone --skip-deploy node-js-app io-js-app +``` + +### Displaying reports about an app + +> New as of 0.8.1 + +You can get a report about the deployed apps using the `apps:report` command: + +```shell +dokku apps:report +``` + +``` +=====> node-js-app + App dir: /home/dokku/node-js-app + Git sha: dbddc3f + App cid: 7b18489c98be + Status: running +=====> python-sample +not deployed +=====> ruby-sample + App dir: /home/dokku/ruby-sample + Git sha: a2d477c + App cid: 78a44d71012a + Status: running +``` + +You can run the command for a specific app also. + +```shell +dokku apps:report node-js-app +``` + +``` +=====> node-js-app + App dir: /home/dokku/node-js-app + Git sha: dbddc3f + App cid: 7b18489c98be + Status: running +``` + +You can pass flags which will output only the value of the specific information you want. For example: + +```shell +dokku apps:report node-js-app --git-sha +``` diff --git a/docs/deployment/logs.md b/docs/deployment/logs.md index 9be0352f8..eefb5983c 100644 --- a/docs/deployment/logs.md +++ b/docs/deployment/logs.md @@ -24,13 +24,8 @@ Dokku also supports certain command-line arguments that augment the `log` comman -q, --quiet # display raw logs without colors, time and names ``` -You can use these modifiers as +You can use these modifiers as follows: -```shell -dokku logs -``` - -For example, ```shell dokku logs node-js-app -t -p web diff --git a/docs/deployment/methods/buildpacks.md b/docs/deployment/methods/buildpacks.md index 897d9ef41..4c8a72b25 100644 --- a/docs/deployment/methods/buildpacks.md +++ b/docs/deployment/methods/buildpacks.md @@ -4,7 +4,7 @@ Dokku normally defaults to using [heroku buildpacks](https://devcenter.heroku.co - Use `dokku config:set` to set the `BUILDPACK_URL` environment variable. - Add `BUILDPACK_URL` to a committed `.env` file in the root of your repository. - - See the [environment variable documentation](/dokku/configuration/environment-variables/) for more details. + - See the [environment variable documentation](/docs/configuration/environment-variables.md) for more details. - Create a `.buildpacks` file in the root of your repository. ## Specifying a custom buildpack @@ -14,12 +14,11 @@ In certain cases you may want to specify a custom buildpack. While Dokku uses he To use a specific buildpack, you can run the following Dokku command: ```shell -# replace APP with the name of your application # replace REPOSITORY_URL with your buildpack's url -dokku config:set APP BUILDPACK_URL=REPOSITORY_URL +dokku config:set node-js-app BUILDPACK_URL=REPOSITORY_URL # example: using a specific ruby buildpack version -dokku config:set APP BUILDPACK_URL=https://github.com/heroku/heroku-buildpack-ruby.git#v142 +dokku config:set node-js-app BUILDPACK_URL=https://github.com/heroku/heroku-buildpack-ruby.git#v142 ``` Please check the documentation for your particular buildpack as you may need to include configuration files (such as a Procfile) in your project root. @@ -45,21 +44,19 @@ As Dokku pins all buildpacks via herokuish releases, there may be occasions wher ```shell # using the latest nodejs buildpack -# replace APP with the name of your application -dokku config:set APP BUILDPACK_URL=https://github.com/heroku/heroku-buildpack-nodejs +dokku config:set node-js-app BUILDPACK_URL=https://github.com/heroku/heroku-buildpack-nodejs ``` You may also wish to use a **specific** version of a buildpack, which is also simple ```shell # using v87 of the nodejs buildpack -# replace APP with the name of your application -dokku config:set APP BUILDPACK_URL=https://github.com/heroku/heroku-buildpack-nodejs#v87 +dokku config:set node-js-app BUILDPACK_URL=https://github.com/heroku/heroku-buildpack-nodejs#v87 ``` ## Specifying commands via Procfile -While many buildpacks have a default command that is run when a detected repository is pushed, it is possible to override this command via a Procfile. A Procfile can also be used to specify multiple commands, each of which is subject to process scaling. See the [process scaling documentation](/dokku/deployment/process-management/) for more details around scaling individual processes. +While many buildpacks have a default command that is run when a detected repository is pushed, it is possible to override this command via a Procfile. A Procfile can also be used to specify multiple commands, each of which is subject to process scaling. See the [process scaling documentation](/docs/deployment/process-management.md) for more details around scaling individual processes. A Procfile is a file named `Procfile`. It should be named `Procfile` exactly, and not anything else. For example, `Procfile.txt` is not valid. The file should be a simple text file. @@ -80,7 +77,7 @@ worker: env QUEUE=* bundle exec rake resque:work importantworker: env QUEUE=important bundle exec rake resque:work ``` -The `web` process type holds some significance in that it is the only process type that is automatically scaled to `1` on the initial application deploy. See the [process scaling documentation](/dokku/deployment/process-management/) for more details around scaling individual processes. +The `web` process type holds some significance in that it is the only process type that is automatically scaled to `1` on the initial application deploy. See the [process scaling documentation](/docs/deployment/process-management.md) for more details around scaling individual processes. ## Curl Build Timeouts @@ -95,4 +92,4 @@ dokku config:set --global CURL_CONNECT_TIMEOUT=30 ## Clearing buildpack cache -See the [repository management documentation](/dokku/advanced-usage/repository-management/#clearing-app-cache). +See the [repository management documentation](/docs/advanced-usage/repository-management.md#clearing-app-cache). diff --git a/docs/deployment/methods/dockerfiles.md b/docs/deployment/methods/dockerfiles.md index a51b427da..3ccf9714e 100644 --- a/docs/deployment/methods/dockerfiles.md +++ b/docs/deployment/methods/dockerfiles.md @@ -21,7 +21,7 @@ Dokku will extract all tcp ports exposed using the `EXPOSE` directive (one port If you do not explicitly `EXPOSE` a port in your `Dockerfile`, Dokku will configure the nginx proxy to listen on port 80 (and 443 for TLS) and forward traffic to your app listening on port 5000 inside the container. Just like buildpack apps, you can also use the `$PORT` environment variable in your app to maintain portability. -When ports are exposed through the default nginx proxy, they are proxied externally as HTTP ports. At this time, in no case do we proxy plain TCP or UDP ports. If you would like to investigate alternative proxy methods, please refer to our [proxy management documentation](/dokku/advanced-usage/proxy-management/). +When ports are exposed through the default nginx proxy, they are proxied externally as HTTP ports. At this time, in no case do we proxy plain TCP or UDP ports. If you would like to investigate alternative proxy methods, please refer to our [proxy management documentation](/docs/advanced-usage/proxy-management.md). ## Customizing the run command @@ -34,7 +34,7 @@ ENTRYPOINT ["node"] You can do: ```shell -dokku config:set APP DOKKU_DOCKERFILE_START_CMD="--harmony server.js" +dokku config:set node-js-app DOKKU_DOCKERFILE_START_CMD="--harmony server.js" ``` To tell docker what to run. diff --git a/docs/deployment/one-off-processes.md b/docs/deployment/one-off-processes.md index b40dc7bf4..3210a6f7c 100644 --- a/docs/deployment/one-off-processes.md +++ b/docs/deployment/one-off-processes.md @@ -29,7 +29,7 @@ dokku run my-app console If you want to remove the container after a command has started, you can run the following command: ```shell -# keep `run` containers around +# don't keep `run` containers around dokku config:set --global DOKKU_RM_CONTAINER=1 # revert the above setting and keep containers around diff --git a/docs/deployment/process-management.md b/docs/deployment/process-management.md index bd924e4e4..0e39542a6 100644 --- a/docs/deployment/process-management.md +++ b/docs/deployment/process-management.md @@ -4,18 +4,19 @@ ``` ps # List processes running in app container(s) -ps:rebuildall # Rebuild all apps from source ps:rebuild # Rebuild an app from source -ps:restartall # Restart all deployed app containers +ps:rebuildall # Rebuild all apps from source +ps:report [] [] # Displays a process report for one or more apps ps:restart # Restart app container(s) -ps:scale = [=] # Set how many processes of a given process to run +ps:restart-policy # Shows the restart-policy for an app +ps:restartall # Restart all deployed app containers +ps:scale = [=] # Get/Set how many instances of a given process to run +ps:set-restart-policy # Sets app restart-policy ps:start # Start app container(s) ps:stop # Stop app container(s) -ps:restart-policy # Shows the restart-policy for an app -ps:set-restart-policy # Sets app restart-policy ``` -By default, Dokku will only start a single `web` process - if defined - though process scaling can be managed by the `ps` plugin or [via a custom `DOKKU_SCALE` file](/dokku/deployment/process-management/#manually-managing-process-scaling). +By default, Dokku will only start a single `web` process - if defined - though process scaling can be managed by the `ps` plugin or [via a custom `DOKKU_SCALE` file](/docs/deployment/process-management.md#manually-managing-process-scaling). > The `web` proctype is the only proctype that will invoke custom checks as defined by a CHECKS file. It is also the only process type that will be launched in a container that is either proxied via nginx or bound to an external port. @@ -31,7 +32,7 @@ dokku ps node-js-app ### Rebuilding applications -There are some Dokku commands which will not automatically rebuild an application's environment, or which can be told to skip a rebuild. For instance, you may wish to run mulitple `config:set` commands without a restart so as to speed up configuration. In these cases, you can ultimately trigger an application rebuild using `ps:rebuild` +There are some Dokku commands which will not automatically rebuild an application's environment, or which can be told to skip a rebuild. For instance, you may wish to run multiple `config:set` commands without a restart so as to speed up configuration. In these cases, you can ultimately trigger an application rebuild using `ps:rebuild` ```shell dokku ps:rebuild node-js-app @@ -45,7 +46,7 @@ dokku ps:rebuildall > The `ps:rebuild` and `ps:rebuildall` commands only work for applications for which there is a source, and thus > will only always work deterministically for git-deployed application. Please see -> the [images documentation](/dokku/deployment/methods/images/) and [tar documentation](/dokku/deployment/methods/tar/) +> the [images documentation](/docs/deployment/methods/images.md) and [tar documentation](/docs/deployment/methods/tar.md) > in for more information concerning rebuilding those applications. ### Restarting applications @@ -92,7 +93,7 @@ dokku ps:scale node-js-app ### Stopping applications -Deployed applications can be stopped using the `ps:stop` command. This turns all running containers for an application, and will result in a `502 Bad Gateway` reponse for the default nginx proxy implementation. +Deployed applications can be stopped using the `ps:stop` command. This turns all running containers for an application, and will result in a `502 Bad Gateway` response for the default nginx proxy implementation. ```shell dokku ps:stop node-js-app diff --git a/docs/deployment/remote-commands.md b/docs/deployment/remote-commands.md index 9bc3b2b15..d87a65bb7 100644 --- a/docs/deployment/remote-commands.md +++ b/docs/deployment/remote-commands.md @@ -25,4 +25,4 @@ in order to avoid ssh interpretting Dokku arguments for itself. You may optionally use a client to connect to your Dokku server. Most clients use the configured `git remote` to locate the Dokku server, though some allow for overriding this via an environment variable or flag. -Please refer to the [clients](/dokku/community/clients/) list for more details. +Please refer to the [clients](/docs/community/clients.md) list for more details. diff --git a/docs/deployment/user-management.md b/docs/deployment/user-management.md index b8a4c4b70..729147010 100644 --- a/docs/deployment/user-management.md +++ b/docs/deployment/user-management.md @@ -12,7 +12,7 @@ When pushing to Dokku, ssh key based authorization is the preferred authenticati Users in Dokku are managed via the `~/dokku/.ssh/authorized_keys` file. It is **highly** recommended that you follow the steps below to manage users on a Dokku server. -> Users of older versions of Dokku should use the `sshcommand` binary to manage keys. Please refer to the Dokku documentation for your version for more details. +> Users of older versions of Dokku may use the `sshcommand` binary to manage keys instead of the `ssh-keys` plugin. Please refer to the Dokku documentation for your version for more details. ## Usage @@ -56,6 +56,13 @@ Admin users and root can also add keys remotely: cat ~/.ssh/id_rsa.pub | ssh dokku@dokku.me ssh-keys:add KEY_NAME ``` +If you are using an ssh user other than `dokku`, then you'll also need to specify the `dokku` bin: + +```shell +cat ~/.ssh/id_rsa.pub | ssh root@dokku.me dokku ssh-keys:add KEY_NAME +``` + + Finally, if you are using the vagrant installation, you can also use the `make vagrant-acl-add` target to add your public key to Dokku (it will use your host username as the `USER`): ```shell @@ -72,4 +79,17 @@ dokku ssh-keys:remove KEY_NAME ## Scoping commands to specific users -Keys are given unique names, which can be used in conjunction with the [user-auth](/dokku/development/plugin-triggers/#user-auth) plugin trigger to handle command authorization. Please see the documentation on that trigger for more information. +Keys are given unique names, which can be used in conjunction with the [user-auth](/docs/development/plugin-triggers.md#user-auth) plugin trigger to handle command authorization. Please see the documentation on that trigger for more information. + +## Granting other Unix user accounts Dokku access + +Any Unix user account which belongs to the 'sudo' Unix group can run Dokku. However, you may want to give them Dokku access but not full sudo privileges. + +To allow other Unix user accounts to be able to run Dokku commands, without giving them full sudo access, modify your sudoers configuration. + +Use `visudo /etc/sudoers.d/dokku-users`, or `visudo /etc/sudoers` to add the following line: + +``` +%dokku ALL=(ALL:ALL) NOPASSWD:SETENV: /usr/bin/dokku +``` + diff --git a/docs/deployment/zero-downtime-deploys.md b/docs/deployment/zero-downtime-deploys.md index 2ef6bd89e..69c05730b 100644 --- a/docs/deployment/zero-downtime-deploys.md +++ b/docs/deployment/zero-downtime-deploys.md @@ -6,22 +6,37 @@ checks Show zero-downtime status checks:disable [process-type(s)] Disable zero-downtime deployment for all processes (or comma-separated process-type list) ***WARNING: this will cause downtime during deployments*** checks:enable [process-type(s)] Enable zero-downtime deployment for all processes (or comma-separated process-type list) +checks:report [] [] Displays a checks report for one or more apps checks:run [process-type(s)] Runs zero-downtime checks for all processes (or comma-separated process-type list) checks:skip [process-type(s)] Skip zero-downtime checks for all processes (or comma-separated process-type list) ``` -Following a deploy, Dokku will wait `10` seconds before routing traffic to the new container to give your application time to boot up. If the application is not running after this time, then the deploy is failed and your old container will continue serving traffic. You can modify this value globally or on a per-application basis: - -```shell -dokku config:set --global DOKKU_DEFAULT_CHECKS_WAIT=30 -dokku config:set node-js-app DOKKU_DEFAULT_CHECKS_WAIT=30 -``` - -You can also choose to skip checks or disable zero-downtime completely on a per-application/per-process basis - -> Note that `checks:disable` will now (as of 0.6.0) cause downtime for that process-type during deployments. +By default, Dokku will wait `10` seconds after starting each container before assuming it is up and proceeding with the deploy. Once this has occurred for all containers started by for an application, traffic will be switched to point to your new containers. Dokku will also wait a further `60` seconds _after_ the deploy is complete before terminating old containers in order to give time for long running connections to terminate. In either case, you may have more than one container running for a given application. + +You may both create user-defined checks for web processes using a `CHECKS` file, as well as customize any and all parts of this experience using the checks plugin. + +## Configuring Check Settings using the `config` plugin + +There are certain settings that can be configured via environment variables: + +- `DOKKU_DEFAULT_CHECKS_WAIT`: (default: `10`) If no user-defined checks are specified - or if the process being checked is not a `web` process - this is the period of time Dokku will wait before checking that a container is still running. +- `DOKKU_DOCKER_STOP_TIMEOUT`: (default: `10`) Configurable grace period given to the `docker stop` command. If a container has not stopped by this time, a `kill -9` signal or equivalent is sent in order to force-terminate the container. Both the `ps:stop` and `apps:destroy` commands _also_ respect this value. If not specified, the docker defaults for the [docker stop command](https://docs.docker.com/engine/reference/commandline/stop/) will be used. +- `DOKKU_WAIT_TO_RETIRE`: (default: `60`) After a successful deploy, the grace period given to old containers before they are stopped/terminated. This is useful for ensuring completion of long-running http connections. + +The following settings may also be specified in the `CHECKS` file, though are available as environment variables in order to ease application reuse. + +- `DOKKU_CHECKS_WAIT`: (default: `5`) Wait this many seconds for the container to start before running checks. +- `DOKKU_CHECKS_TIMEOUT`: (default: `30`) Wait this many seconds for each response before marking it as a failure. +- `DOKKU_CHECKS_ATTEMPTS`: (default: `5`) Number of retries for to run for a specific check before marking it as a failure + +## Skipping and Disabling Checks + +> Note that `checks:disable` will now (as of 0.6.0) cause downtime for that process-type during deployments. Previously, it acted as `checks:skip` currently does. + +You can choose to skip checks completely on a per-application/per-process basis. Skipping checks will avoid the default 10 second waiting period entirely, as well as any other user-defined checks. ```shell +# process type specification is optional dokku checks:skip node-js-app worker,web ``` @@ -33,8 +48,7 @@ dokku checks:skip node-js-app worker,web DOKKU_CHECKS_SKIPPED: worker,web ``` -Additionally, any given process can only be configured to disable zero-downtime. example: - +Zero-downtime checks can also be disabled completely. This will stop old containers **before** new ones start, which may result in broken connections and downtime if your application fails to boot properly. ```shell dokku checks:disable node-js-app worker @@ -48,14 +62,107 @@ dokku checks:disable node-js-app worker DOKKU_CHECKS_SKIPPED: web ``` -Dokku will wait `60` seconds before stopping the old container so that existing connections are given a chance to complete. You can modify this value globally or on a per-application basis: +### Displaying checks reports about an app + +> New as of 0.8.1 + +You can get a report about the app's checks status using the `checks:report` command: ```shell -dokku config:set --global DOKKU_WAIT_TO_RETIRE=120 -dokku config:set DOKKU_WAIT_TO_RETIRE=120 +dokku checks:report ``` -> Note that during this time, multiple containers may be running on your server, which can be an issue for memory-hungry applications on memory-constrained servers. +``` +=====> search checks information + Checks disabled list: none + Checks skipped list: none +=====> python-sample checks information + Checks disabled list: none + Checks skipped list: none +=====> ruby-sample checks information + Checks disabled list: _all_ + Checks skipped list: none +``` + +You can run the command for a specific app also. + +```shell +dokku checks:report node-js-sample +``` + +``` +=====> node-js-sample checks information + Checks disabled list: none + Checks skipped list: none +``` + +You can pass flags which will output only the value of the specific information you want. For example: + +```shell +dokku checks:report node-js-sample --checks-disabled-list +``` + +## Customizing Checks + +If your application needs a longer period to boot up - perhaps to load data into memory, or because of slow boot time - you may also use dokku's `checks` functionality to more precisely check whether an application can serve traffic or not. + +Checks are run against the detected `web` process from your application's `Procfile`. For non-web processes, Dokku will fallback to the aforementioned process uptime check. + +To specify checks, add a `CHECKS` file to the root of your project directory. The `CHECKS` file should be plain text and may contain: + +- Check instructions +- Settings (NAME=VALUE) +- Comments (lines starting with #) +- Empty lines + +> For dockerfile-based deploys, the file *must* be in `/app/CHECKS` within the container. `/app` is used by default as the root container directory for buildpack-based deploys. + +### Check Instructions + +The format of a check instruction is a path or relative URL, optionally followed by the expected content: + +``` +/about Our Amazing Team +``` + +The `CHECKS` file can contain multiple checks: + +``` +/ My Amazing App +/stylesheets/index.css .body +/scripts/index.js $(function() +/images/logo.png +``` + +To check an application that supports multiple hostnames, use relative URLs that include the hostname: + +``` +//admin.example.com Admin Dashboard +//static.example.com/logo.png +``` + +You can also specify the protocol to explicitly check HTTPS requests: + +``` +https://admin.example.com Admin Dashboard +https://static.example.com/logo.png +``` + +While a full url may be used in order to invoke checks, if you are using relative urls, the port *must* be omitted. + +### Check Settings + +The default behavior is to wait for `5` seconds before running the checks, to timeout the checks after `30` seconds, and to attempt the checks `5` times. If the checks fail `5` times, the deployment is considered failed and the old container will continue serving traffic. + +You can change the default behavior by setting `WAIT`, `TIMEOUT`, and `ATTEMPTS` to different values in the `CHECKS` file: + +``` +WAIT=30 # Wait 1/2 minute +TIMEOUT=60 # Timeout after a minute +ATTEMPTS=10 # Attempt checks 10 times + +/ My Amazing App +``` ## Manually Invoking Checks @@ -92,7 +199,7 @@ dokku checks:run APP Checks can be scoped to a particular process type: ```shell -dokku checks:run APP worker +dokku checks:run node-js-app worker ``` ``` @@ -108,7 +215,7 @@ dokku checks:run APP worker An app process id may also be specified: ```shell -dokku checks:run APP web.2 +dokku checks:run node-js-app web.2 ``` ``` @@ -124,7 +231,7 @@ dokku checks:run APP web.2 Non-existent process types will result in an error: ```shell -dokku checks:run APP non-existent +dokku checks:run node-js-app non-existent ``` ``` @@ -135,7 +242,7 @@ Invalid process type specified (APP.non-existent) Non-existent process ids will *also* result in an error ```shell -dokku checks:run APP web.3 +dokku checks:run node-js-app web.3 ``` ``` @@ -143,88 +250,6 @@ dokku checks:run APP web.3 Invalid container id specified (APP.web.3) ``` -## Customizing Checks - -If your application needs a longer period to boot up - perhaps to load data into memory, or because of slow boot time - you may also use dokku's `checks` functionality to more precisely check whether an application can serve traffic or not. - -Checks are run against the detected `web` process from your application's `Procfile`. For non-web processes, Dokku will fallback to the aforementioned process uptime check. - -To specify checks, add a `CHECKS` file to the root of your project directory. The `CHECKS` file should be plain text and may contain: - -* Check instructions -* Settings (NAME=VALUE) -* Comments (lines starting with #) -* Empty lines - -> For dockerfile-based deploys, the file *must* be in `/app/CHECKS` within the container. `/app` is used by default as the root container directory for buildpack-based deploys. - -### Check Instructions - -The format of a check instruction is a path or relative URL, optionally followed by the expected content: - -``` -/about Our Amazing Team -``` - -The `CHECKS` file can contain multiple checks: - -``` -/ My Amazing App -/stylesheets/index.css .body -/scripts/index.js $(function() -/images/logo.png -``` - -To check an application that supports multiple hostnames, use relative URLs that include the hostname: - -``` -//admin.example.com Admin Dashboard -//static.example.com/logo.png -``` - -You can also specify the protocol to explicitly check HTTPS requests: - -``` -https://admin.example.com Admin Dashboard -https://static.example.com/logo.png -``` - -### Check Settings - -The default behavior is to wait for `5` seconds before running the checks, to timeout the checks after `30` seconds, and to attempt the checks `5` times. If the checks fail `5` times, the deployment is considered failed and the old container will continue serving traffic. - -You can change the default behavior by setting `WAIT`, `TIMEOUT`, and `ATTEMPTS` to different values in the `CHECKS` file: - -``` -WAIT=30 # Wait 1/2 minute -TIMEOUT=60 # Timeout after a minute -ATTEMPTS=10 # Attempt checks 10 times - -/ My Amazing App -``` - -You can also override the default `WAIT`, `TIMEOUT`, and `ATTEMPTS` variables for the global Dokku installation: - -```shell -dokku config:set --global DOKKU_CHECKS_WAIT=30 -dokku config:set --global DOKKU_CHECKS_TIMEOUT=60 -dokku config:set --global DOKKU_CHECKS_ATTEMPTS=10 -``` - -If your application runs multiple processes (a background worker configured in your `Procfile`, for example) and you have checks to ensure that your web application has booted up, you may want to disable the default check wait time for that application to avoid the `10` second wait per non-web process: - -```shell -dokku config:set DOKKU_DEFAULT_CHECKS_WAIT=0 -``` - -### Configuring docker stop timeout - -[By default](https://docs.docker.com/engine/reference/commandline/stop/), docker will wait 10 seconds from the time the `stop` command is passed to a container before it attempts to kill said container. This timeout can be configured on a per-app basis in Dokku by setting the `DOKKU_DOCKER_STOP_TIMEOUT` configuration variable. This timeout applies to normal zero-downtime deployments as well as the `ps:stop` and `apps:destroy` commands. - -```shell -dokku config:set $APP DOKKU_DOCKER_STOP_TIMEOUT=20 -``` - ## Example: Successful Rails Deployment In this example, a Rails application is successfully deployed to dokku. The initial round of checks fails while the server is starting, but once it starts they succeed and the deployment is successful. `WAIT` is set to `10` because our application takes a while to boot up. `ATTEMPTS` is set to `6`, but the third attempt succeeds. diff --git a/docs/development/plugin-creation.md b/docs/development/plugin-creation.md index 3c7bff94a..ae27e8053 100644 --- a/docs/development/plugin-creation.md +++ b/docs/development/plugin-creation.md @@ -1,14 +1,33 @@ # Plugin Creation +Plugins can simply be implementations of [triggers](/docs/development/plugin-triggers.md) or they may implement a command structure of their own. Dokku has no restrictions on the language in which a plugin is implemented, it only cares that the plugin implements the appropriate [commands](/docs/development/plugin-creation.md#command-api) or [triggers](/docs/development/plugin-triggers.md) API. NOTE: all files that implement the triggers or commands API must be executable. + If you create your own plugin: 1. Take a look at the plugins shipped with Dokku and hack away! -2. Check out the [list of triggers](/dokku/development/plugin-triggers) your plugin can implement. +2. Check out the [list of triggers](/docs/development/plugin-triggers.md) your plugin can implement. 3. Upload your plugin to github with a repository name in form of `dokku-` (e.g. `dokku-mariadb`) -4. Edit [this page](/dokku/community/plugins/) and add a link to it. +4. Edit [this page](/docs/community/plugins.md) and add a link to it. 5. Subscribe to the [dokku development blog](http://progrium.com) to be notified about API changes and releases +### Compilable Plugins (i.e. golang, java(?), c, etc.) +The plugin developer is required to implement the `install` trigger such that it will output your built executable(s) in the correct directory structure to implement the plugin's desired command and/or trigger API. See [smoke-test-plugin](https://github.com/dokku/smoke-test-plugin) for an example. + + +### Command API +There are 3 main implementation points: `commands`, `subcommands/default`, and `subcommands/` + +#### commands +Primarily used to supply the plugin's usage/help output. (i.e. [plugin help](https://github.com/dokku/dokku/tree/master/plugins/plugin/commands)) + +#### subcommands/default +Implements the plugin's default command behavior. (i.e. [`dokku plugin`](https://github.com/dokku/dokku/tree/master/plugins/plugin/subcommands/default)) + +#### subcommands/ +Implements the additional command interface and will translate to `dokku plugin:cmd` on the command line. (i.e. [`dokku plugin:install`](https://github.com/dokku/dokku/tree/master/plugins/plugin/subcommands/install)) + + ### Sample plugin The below plugin is a dummy `dokku hello` plugin. @@ -30,7 +49,7 @@ hello_main_cmd() { set -- $cmd $@ ## - [[ -z $2 ]] && echo "Please specify an app to run the command on" && exit 1 + [[ -z $2 ]] && dokku_log_fail "Please specify an app to run the command on" verify_app_name "$2" local APP="$2"; @@ -58,7 +77,7 @@ hello_world_cmd() { set -- $cmd $@ ## - [[ -z $2 ]] && echo "Please specify an app to run the command on" && exit 1 + [[ -z $2 ]] && dokku_log_fail "Please specify an app to run the command on" verify_app_name "$2" local APP="$2"; @@ -150,10 +169,10 @@ A few notes: - As some plugins require access to set app config settings and do not want/require the default Heroku-style behavior of a restart, we have the following "internal" commands that provide this functionality : ```shell - dokku config:set --no-restart APP KEY1=VALUE1 [KEY2=VALUE2 ...] - dokku config:unset --no-restart APP KEY1 [KEY2 ...] + dokku config:set --no-restart node-js-app KEY1=VALUE1 [KEY2=VALUE2 ...] + dokku config:unset --no-restart node-js-app KEY1 [KEY2 ...] ``` - From time to time you may want to allow other plugins access to (some of) your plugin's functionality. You can expose this by including a `functions` file in your plugin for others to source. Consider all functions in that file to be publicly accessible by other plugins. Any functions not wished to be made "public" should reside within your plugin trigger or commands files. -- As of 0.4.0, we allow image tagging and deployment of said tagged images. Therefore, hard-coding of `$IMAGE` as `dokku/$APP` is no longer sufficient. Instead, for non `pre/post-build-*` plugins, use `get_running_image_tag()` & `get_app_image_name()` as sourced from common/functions. See the [plugin triggers](/dokku/development/plugin-triggers) doc for examples. +- As of 0.4.0, we allow image tagging and deployment of said tagged images. Therefore, hard-coding of `$IMAGE` as `dokku/$APP` is no longer sufficient. Instead, for non `pre/post-build-*` plugins, use `get_running_image_tag()` & `get_app_image_name()` as sourced from common/functions. See the [plugin triggers](/docs/development/plugin-triggers.md) doc for examples. - As of 0.5.0, we use container labels to help cleanup intermediate containers with `dokku cleanup`. If manually calling `docker run`, include `$DOKKU_GLOBAL_RUN_ARGS`. This will ensure you intermediate containers labeled correctly. - As of 0.6.0, we advise you to *not* call the `dokku` binary directly from within plugins. Clients using the `--app` argument are potentially broken, amongst other issues, when doing so. Instead, please source the `functions` file for a given plugin when attempting to call Dokku internal functions diff --git a/docs/development/plugin-triggers.md b/docs/development/plugin-triggers.md index 7ca54dc06..534fd8217 100644 --- a/docs/development/plugin-triggers.md +++ b/docs/development/plugin-triggers.md @@ -27,7 +27,7 @@ There are a number of plugin-related triggers. These can be optionally implement The following plugin triggers describe those available to a Dokku installation. As well, there is an example for each trigger that you can use as templates for your own plugin development. -> The example plugin trigger code is not guaranteed to be implemented as in within dokkku, and are merely simplified examples. Please look at the Dokku source for larger, more in-depth examples. +> The example plugin trigger code is not guaranteed to be implemented as in within dokku, and are merely simplified examples. Please look at the Dokku source for larger, more in-depth examples. ### `post-config-update` @@ -134,6 +134,24 @@ help_content esac ``` +### `core-post-deploy` + +> To avoid issues with community plugins, this plugin trigger should be used *only* for core plugins. Please avoid using this trigger in your own plugins. + +- Description: Allows running of commands after an application's processes have been scaled up, but before old containers are torn down. Dokku core currently uses this to switch traffic on nginx. +- Invoked by: `dokku deploy` +- Arguments: `$APP $INTERNAL_PORT $INTERNAL_IP_ADDRESS $IMAGE_TAG` +- Example: + +```shell +#!/usr/bin/env bash +# Notify an external service that a successful deploy has occurred. + +set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x + +curl "http://httpstat.us/200" +``` + ### `dependencies` - Description: Used to install system-level dependencies. Invoked by `plugin:install-dependencies`. @@ -303,6 +321,10 @@ set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x # TODO ``` +> WARNING: The `git-pre-pull` trigger should _not_ be used for authentication +since it does not get called for commands that use `git-upload-archive` such +as `git archive`. Instead, use the [`user-auth`](#user-auth) trigger. + ### `install` - Description: Used to setup any files/configuration for a plugin. @@ -430,7 +452,9 @@ dokku postgres:destroy $APP ### `post-deploy` -- Description: Allows running of commands after an application's processes have been scaled up, but before old containers are torn down. Dokku core currently uses this to switch traffic on nginx. +> Please see [core-post-deploy](#core-post-deploy) if contributing a core plugin with the `post-deploy` hook. + +- Description: Allows running of commands after an application's processes have been scaled up, but before old containers are torn down. Dokku calls this _after_ `core-post-deploy`. Deployment Tasks are also invoked by this plugin trigger. - Invoked by: `dokku deploy` - Arguments: `$APP $INTERNAL_PORT $INTERNAL_IP_ADDRESS $IMAGE_TAG` - Example: @@ -447,7 +471,7 @@ curl "http://httpstat.us/200" ### `post-domains-update` - Description: Allows you to run commands once the domain for an application has been updated. It also sends in the command that has been used. This can be "add", "clear" or "remove". The third argument will be the optional list of domains -- Invoked by: `dokku domains:add`, `dokku domains:clear`, `dokku domains:remove` +- Invoked by: `dokku domains:add`, `dokku domains:clear`, `dokku domains:remove`, `dokku domains:set` - Arguments: `$APP` `action name` `domains` - Example: @@ -461,6 +485,28 @@ set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x sudo service haproxy reload ``` +### `post-extract` + +- Description: Allows you to modify the contents of an application *after* it has been extracted from git/tarball but *before* the image source type is detected. +- Invoked by: `dokku tar:in`, `dokku tar:from` and the `receive-app` plugin trigger +- Arguments: `$APP` `$TMP_WORK_DIR` `$REV` +- Example: + +```shell +#!/usr/bin/env bash +# Adds a clock process to an app's Procfile + +set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x +source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" +APP="$1"; verify_app_name "$APP" +TMP_WORK_DIR="$2" +REV="$3" # optional, may not be sent for tar-based builds + +pushd "$TMP_WORK_DIR" > /dev/null +touch Procfile +echo "clock: some-command" >> Procfile +``` + ### `post-proxy-ports-update` - Description: Allows you to run commands once the proxy port mappings for an application have been updated. It also sends the invoking command. This can be "add", "clear" or "remove". @@ -846,6 +892,26 @@ APP="$1"; IMAGE_TAG="$2" some code to remove a docker hub tag because it's not implemented in the CLI.... ``` +### `uninstall` + + - Description: Used to cleanup after itself. + - Invoked by: `dokku plugin:uninstall` + - Arguments: `$PLUGIN` + - Example: + + ```shell + #!/usr/bin/env bash + # Cleanup up extra containers created + + set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x + + PLUGIN="$1" + + [[ "$PLUGIN" = "my-plugin" ]] && docker rmi -f "${PLUGIN_IMAGE_DEPENDENCY}" + ``` + + > To avoid uninstalling other plugins make sure to check the plugin name like shown in the example. + ### `update` - Description: Can be used to run plugin updates on a regular interval. You can schedule the invoker in a cron-task to ensure your system gets regular updates. diff --git a/docs/development/release-process.md b/docs/development/release-process.md index f69948dd0..105ed8e80 100644 --- a/docs/development/release-process.md +++ b/docs/development/release-process.md @@ -20,7 +20,7 @@ To propose a release, the following tasks need to be performed: - The versioned links should be updated or added to the `docs/assets/versions.json` file. - A list of changes must be made in the `HISTORY.md`. - A tag must be created locally with your release version -- Debian packages *must* be created via `vagrant up build` +- Debian and RPM packages *must* be created via `vagrant up build` - The packages should be uploaded to packagecloud.io - All changes are pushed to master and the tag should be turned into a release which will contain the changelog. - ArchLinux package description *must* be updated via `vagrant up build-arch` (needs to be done after the tag is pushed to GitHub, because it is based on that) @@ -37,9 +37,9 @@ git tag v0.9.9 At the moment, tags need not be signed, though that may change in the future. -## Debian Packages +## Debian and RPM packages -The `build` target in the Dokku `Vagrantfile` creates debian packages for Dokku at a point in time. The version will be based upon the latest local tag - you may create your own, internal tags/releases if that is so desired. +The `build` target in the Dokku `Vagrantfile` creates debian and rpm packages for Dokku at a point in time. The version will be based upon the latest local tag - you may create your own, internal tags/releases if that is so desired. Debian package information is held in the `debian` directory of the Dokku project. @@ -51,6 +51,7 @@ git tag v0.9.9 vagrant up build export PACKAGECLOUD_TOKEN=SOME_TOKEN package_cloud push dokku/dokku/ubuntu/trusty dokku_0.9.9_amd64.deb +package_cloud push dokku/dokku/el/7 dokku-0.9.9-1.x86_64.rpm ``` If new versions of other packages were created, these should also be pushed at this time. @@ -66,7 +67,7 @@ The workflow looks like this: ```shell # having dokku-arch in ../dokku-arch vagrant up build-arch -# wait for "==> build-arch: ==> Finished making: dokku 0.7.2-2 (Mon Feb 22 23:20:37 CET 2016)" +# wait for "==> build-arch: ==> Finished making: dokku 0.9.4-2 (Mon Feb 22 23:20:37 CET 2016)" cd ../dokku-arch git add PKGBUILD .SRCINFO git commit -m 'Update to dokku 0.9.9' diff --git a/docs/development/testing.md b/docs/development/testing.md index 4cc4cb7cb..2b9db0a37 100644 --- a/docs/development/testing.md +++ b/docs/development/testing.md @@ -17,7 +17,7 @@ While we do provide official packages for a variety of platforms, as our test su ## Local Test Execution -- Setup Dokku in a [vagrant vm](/dokku/getting-started/install/vagrant) +- Setup Dokku in a [vagrant vm](/docs/getting-started/install/vagrant.md) - Run the following to setup tests and execute them: ```shell diff --git a/docs/getting-started/advanced-installation.md b/docs/getting-started/advanced-installation.md index 975ea7129..85a51d40e 100644 --- a/docs/getting-started/advanced-installation.md +++ b/docs/getting-started/advanced-installation.md @@ -4,7 +4,7 @@ You can always install Dokku straight from the latest - potentially unstable - m ```shell # using a branch results in installing from source -wget https://raw.githubusercontent.com/dokku/dokku/master/bootstrap.sh +wget https://raw.githubusercontent.com/dokku/dokku/master/bootstrap.sh; sudo DOKKU_BRANCH=master bash bootstrap.sh ``` @@ -29,7 +29,7 @@ sudo SSHCOMMAND_URL=https://raw.githubusercontent.com/yourusername/sshcommand/ma The bootstrap script allows the Dokku repository URL to be overridden to bootstrap a host from your own clone of Dokku using the `DOKKU_REPO` environment variable. Example: ```shell -wget https://raw.githubusercontent.com/dokku/dokku/master/bootstrap.sh +wget https://raw.githubusercontent.com/dokku/dokku/master/bootstrap.sh; chmod +x bootstrap.sh sudo DOKKU_REPO=https://github.com/yourusername/dokku.git DOKKU_BRANCH=master ./bootstrap.sh ``` @@ -52,7 +52,7 @@ Once Dokku is installed, if you are not using the web-installer, you'll want to Set up a domain and a wildcard domain pointing to that host. Make sure `/home/dokku/VHOST` is set to this domain. By default it's set to whatever hostname the host has. This file is only created if the hostname can be resolved by dig (`dig +short $(hostname -f)`). Otherwise you have to create the file manually and set it to your preferred domain. If this file still is not present when you push your app, Dokku will publish the app with a port number (i.e. `http://example.com:49154` - note the missing subdomain). -Follow the [user management documentation](/dokku/deployment/user-management/) in order to add users to dokku. +Follow the [user management documentation](/docs/deployment/user-management.md) in order to add users to Dokku, or to give other Unix accounts access to Dokku. That's it! diff --git a/docs/getting-started/install/digitalocean.md b/docs/getting-started/install/digitalocean.md index 186ef3418..4454e6053 100644 --- a/docs/getting-started/install/digitalocean.md +++ b/docs/getting-started/install/digitalocean.md @@ -2,7 +2,7 @@ [Digital Ocean](https://www.digitalocean.com/products/compute/) offers a pre-installed Dokku image. You can run this image on any sized droplet, although larger droplets will allow you to run larger applications. -> When choosing your Droplet configuration please disable IPv6. There are known issues with IPv6 on Digital Ocean and Docker, and many have been reported to the Dokku issue tracker. If you would like to run Dokku on an IPv6 Digital Ocean Droplet, please consult [this guide](https://jeffloughridge.wordpress.com/2015/01/17/native-ipv6-functionality-in-docker/). +> **Please disable IPv6**. There are known issues with IPv6 on Digital Ocean and Docker. If you would like to run Dokku on an IPv6 Digital Ocean Droplet, please consult [this guide](https://jeffloughridge.wordpress.com/2015/01/17/native-ipv6-functionality-in-docker/). 1. Login to your [Digital Ocean](https://m.do.co/c/fe06b043a083) account 2. Click **Create a Droplet**. diff --git a/docs/getting-started/install/dreamhost.md b/docs/getting-started/install/dreamhost.md index 296f6cc87..8fa721a05 100644 --- a/docs/getting-started/install/dreamhost.md +++ b/docs/getting-started/install/dreamhost.md @@ -4,7 +4,7 @@ Cloud-init script can be used to automate installation of Dokku on Dreamhost (or any other OpenStack-compatible cloud with minimal changes). -A new server can be created on DreamHost Cloud from the command line +A new server instance can be created on DreamHost Cloud from the command line using openstack client or from the web UI and with the same command use a cloud-init script to install Dokku. Install the [openstack cli](https://help.dreamhost.com/hc/en-us/articles/216185658-How-to-Install-the-OpenStack-command-line-clients), @@ -18,18 +18,18 @@ source openrc.sh # Set the environment variables for DreamHost Cloud ``` This allows openstack client to connect to DreamHost API endpoints. -The command below creates a new server named `my-dokku-instance` based -on Ubuntu 14.04, with 2GB RAM and 1CPU (the flavor called -`semisonic`), opening network port access to http and ssh (the +The command below creates a new server instance named `my-dokku-instance` +based on Ubuntu 14.04, with 2GB RAM and 1CPU (the flavor called +`supersonic`), opening network port access to http and ssh (the `default` security group), and the name of the chosen SSH key. This key will be automatically added to the new server in the -`authorized_keys` for the default SSH user (`dhc-user`), and it will +`authorized_keys` for the default SSH user (`ubuntu`), and it will be reused by Dokku. ```sh openstack server create \ --image Ubuntu-14.04 \ - --flavor gp1.semisonic \ + --flavor gp1.supersonic \ --security-group default \ --key-name $YOUR_SSH_KEYNAME \ --user-data dokku-cloudinit.sh \ @@ -178,8 +178,20 @@ debconf_selections: | # set the domain name of the new Dokku server dokku dokku/hostname string $YOUR_FULL_QUALIFIED_DOMAIN # this copies over the public SSH key assigned to the server - dokku dokku/key_file string /home/dhc-user/.ssh/authorized_keys + dokku dokku/key_file string /home/ubuntu/.ssh/authorized_keys packages: - dokku ``` + +Shortly after running the create command you will get a confirmation that the +instance has been created, and after about a minute it should be ready to login. +Check the IP of the instance through the web UI or by running: + +```sh +nova list +``` + +SSH with the `ubuntu` username and the public key previously added. +Keep in mind that if you logged in quick enough dokku might still be installing +in the background, and not be ready. The installation takes a few minutes. diff --git a/docs/getting-started/install/linode.md b/docs/getting-started/install/linode.md deleted file mode 100644 index 85d05c103..000000000 --- a/docs/getting-started/install/linode.md +++ /dev/null @@ -1,41 +0,0 @@ -# Installing on Linode - -When installing Dokku on a Linode server, you'll likely see an error during Docker installation: - -> Warning: current kernel is not supported by the linux-image-extra-virtual package. We have no AUFS support. Consider installing the packages linux-image-virtual kernel and linux-image-extra-virtual for AUFS support. - -After 10 seconds, the installation will continue as normal. - -This warning is the result of Linode using its own kernel, which does not support AUFS, instead of using the kernel supplied by Ubuntu. If you ignore the warning, Docker will fall back to using the DeviceMapper storage backend and Dokku will work normally. However, AUFS is better tested and will perform better. - -If you would like to use AUFS, follow the steps below to install Ubuntu's kernel and configure your server to boot it instead of Linode's. - -## Preparing your Linode for AUFS - -__Warning__: These steps will delete *everything* on your Linode. - -1. Open your server's dashboard in the [Linode Manager](https://manager.linode.com/). - -2. Make sure your Linode is [using KVM](https://www.linode.com/docs/platform/kvm#how-to-enable-kvm), not Xen, for virtualization. - -3. In the "Rebuild" tab, select "Ubuntu 14.04 LTS", set a root password, and rebuild. - -4. Once your Linode has been created, click "Boot" and wait for it to complete. - -5. SSH into your Linode as root and run the following commands: - - ```shell - apt-get update - apt-get -qq upgrade - apt-get install linux-image-virtual linux-image-extra-virtual - ``` - -6. When prompted, install Grub onto the first hard drive. - -7. Back in your server's dashboard, click "Edit" on its Configuration Profile - -8. Change the "Kernel" option to "GRUB 2" and save your changes. - -9. Lastly, reboot the Linode. - -Once your server comes back online, you'll be running Ubuntu's default kernel. You can now follow Dokku's [normal installation instructions](/dokku/getting-started/installation/) and `bootstrap.sh` will take care of everything else. diff --git a/docs/getting-started/install/rpm.md b/docs/getting-started/install/rpm.md new file mode 100644 index 000000000..ee58dfe48 --- /dev/null +++ b/docs/getting-started/install/rpm.md @@ -0,0 +1,27 @@ +# RPM Package Installation Notes + +> New as of 0.8.0 *(experimental)* + +>**Warning:** Web installer is not available on CentOS. You will need to configure [SSH keys](/docs/deployment/user-management.md#adding-ssh-keys) and [virtual hosts](/docs/configuration/domains.md#customizing-hostnames) using dokku command line interface. + +Dokku defaults to being installed via RPM package on CentOS 7. While certain hosts may require extra work to get running, you may optionally wish to automate the installation of Dokku without the use of our `bootstrap.sh` bash script. The following are the steps run by said script: + +```shell +# Install docker +curl -fsSL https://get.docker.com/ | sh + +# Install epel for nginx packages to be available +sudo yum install -y epel-release + +# Install dokku +curl -s https://packagecloud.io/install/repositories/dokku/dokku/script.rpm.sh | sudo bash +sudo yum install -y herokuish dokku +sudo dokku plugin:install-dependencies --core +systemctl start nginx + +# Enable docker and nginx on system startup +systemctl enable docker +systemctl enable nginx +``` + +The [devicemapper](https://docs.docker.com/engine/userguide/storagedriver/device-mapper-driver/) is the default Docker storage driver on CentOS 7 and defaults to a configuration mode known as `loop-lvm`. This mode is designed to work out-of-the-box with no additional configuration. However, production deployments should not run under `loop-lvm` mode. The preferred configuration for production deployments is [`direct-lvm`](https://docs.docker.com/engine/userguide/storagedriver/device-mapper-driver/#/configure-direct-lvm-mode-for-production). diff --git a/docs/getting-started/install/vagrant.md b/docs/getting-started/install/vagrant.md index d6116be84..b6f79c7b0 100644 --- a/docs/getting-started/install/vagrant.md +++ b/docs/getting-started/install/vagrant.md @@ -21,13 +21,15 @@ cd path/to/dokku vagrant up ``` -- Setup SSH Config in `~/.ssh/config`. The port listed here is usually correct, though you may want to verify that it is the same as the one listed in the output of `vagrant ssh-config dokku` +- Setup SSH Config in `~/.ssh/config`. ```ini Host dokku.me Port 22 ``` + > For users that have customized the IP address of their VM - either in a custom `Vagrantfile` or via the `DOKKU_IP` environment variable - and are not using `10.0.0.2` for the Vagrant IP, you'll need to instead use the output of `vagrant ssh-config dokku` for your ~/.ssh/config entry. + - Copy your SSH key via `cat ~/.ssh/id_rsa.pub | pbcopy` and paste it into the dokku-installer at http://dokku.me . Change the `Hostname` field on the Dokku Setup screen to your domain and then check the box that says `Use virtualhost naming`. Then click *Finish Setup* to install your key. You'll be directed to application deployment instructions from here. Please note, the `dokku.me` domain is setup to point to `10.0.0.2` along with all subdomains (ie yourapp.dokku.me). If you change the `DOKKU_IP` in your vagrant setup you'll need to update your `/etc/hosts` file to point your reconfigured ip address. diff --git a/docs/getting-started/installation.md b/docs/getting-started/installation.md index ba8b6ee44..23eb62619 100644 --- a/docs/getting-started/installation.md +++ b/docs/getting-started/installation.md @@ -6,12 +6,12 @@ Dokku is an extensible, open source Platform as a Service that runs on a single To start using Dokku, you'll need a system that meets the following minimum requirements: -- A fresh installation of [Ubuntu 14.04 x64](http://www.ubuntu.com/download/) with the FQDN set [1] +- A fresh installation of [Ubuntu 16.04 x64](https://www.ubuntu.com/download), [Ubuntu 14.04 x64](https://www.ubuntu.com/download), [Debian 8.2 x64](https://www.debian.org/distrib/) or [CentOS 7 x64](https://www.centos.org/download/) *(experimental)* with the FQDN set [1] - At least `1GB` of system memory [2] You can *optionally* have a domain name pointed at the host's IP, though this is not necessary. -Dokku is designed for usage on a fresh installation of Ubuntu, and should install all necessary dependencies if installing via the bootstrap method. +Dokku is designed for usage on a fresh VM installation, and should install all necessary dependencies if installing via the bootstrap method. ### Installing the latest stable version @@ -21,8 +21,8 @@ To install the latest stable version of dokku, you can run the following shell c ```shell # for debian systems, installs Dokku via apt-get -wget https://raw.githubusercontent.com/dokku/dokku/v0.7.2/bootstrap.sh -sudo DOKKU_TAG=v0.7.2 bash bootstrap.sh +wget https://raw.githubusercontent.com/dokku/dokku/v0.9.4/bootstrap.sh; +sudo DOKKU_TAG=v0.9.4 bash bootstrap.sh ``` The installation process takes about 5-10 minutes, depending upon internet connection speed. @@ -33,6 +33,8 @@ Once the installation is complete, you can open a browser to setup your SSH key >**Warning:** If you don't complete setup via the web installer (even if you set up SSH keys and virtual hosts otherwise) your Dokku installation will remain vulnerable to anyone finding the setup page and inserting their key. +>**Warning:** Web installer is not available on CentOS and Arch Linux. You will need to configure [SSH keys](/docs/deployment/user-management.md#adding-ssh-keys) and [virtual hosts](/docs/configuration/domains.md#customizing-hostnames) using dokku command line interface. + #### 3. Deploy your first application Once you save your settings, the web admin will self-terminate and you should be able to run or deploy to the Dokku installation. @@ -40,18 +42,18 @@ Once you save your settings, the web admin will self-terminate and you should be For various reasons, certain hosting providers may have other steps that should be preferred to the above. If hosted on any of the following popular hosts, please follow the linked to instructions: -- [Digital Ocean Installation Notes](/dokku/getting-started/install/digitalocean) -- [DreamHost Cloud Installation Notes](/dokku/getting-started/install/dreamhost/) -- [Linode Installation Notes](/dokku/getting-started/install/linode/) -- [Microsoft Azure Installation Notes](/dokku/getting-started/install/azure/) +- [Digital Ocean Installation Notes](/docs/getting-started/install/digitalocean.md) +- [DreamHost Cloud Installation Notes](/docs/getting-started/install/dreamhost.md) +- [Microsoft Azure Installation Notes](/docs/getting-started/install/azure.md) As well, you may wish to customize your installation in some other fashion. or experiment with vagrant. The guides below should get you started: -- [Debian Package Installation Notes](/dokku/getting-started/install/debian/) -- [Vagrant Installation Notes](/dokku/getting-started/install/vagrant/) -- [Advanced Install Customization](/dokku/getting-started/advanced-installation/) +- [Debian Package Installation Notes](/docs/getting-started/install/debian.md) +- [RPM Package Installation Notes](/docs/getting-started/install/rpm.md) +- [Vagrant Installation Notes](/docs/getting-started/install/vagrant.md) +- [Advanced Install Customization](/docs/getting-started/advanced-installation.md) --- - [1]: To check whether your system has an fqdn set, run `sudo hostname -f` -- [2]: If your system has less than 1GB of memory, you can use [this workaround](/dokku/getting-started/advanced-installation/#vms-with-less-than-1gb-of-memory). +- [2]: If your system has less than 1GB of memory, you can use [this workaround](/docs/getting-started/advanced-installation.md#vms-with-less-than-1gb-of-memory). diff --git a/docs/getting-started/troubleshooting.md b/docs/getting-started/troubleshooting.md index ef6b35ead..7300b457a 100644 --- a/docs/getting-started/troubleshooting.md +++ b/docs/getting-started/troubleshooting.md @@ -154,3 +154,44 @@ dokku config:set --global CURL_CONNECT_TIMEOUT=30 ``` Please see https://github.com/dokku/dokku/issues/509 + +Another reason for this error (although it may respond immediately ruling out a timeout issue) may be because you've set the config setting `SSL_CERT_FILE`. Using a config setting with this key interferes with the buildpack's ability to download it's dependencies, so you must rename the config setting to something else, e.g. `MY_APP_SSL_CERT_FILE`. + +*** + +__Symptom:__ Build fails with `Killed` message. + +__Solution:__ + +This generally occurs when the server runs out of memory. You can either add more ram to your server or setup swap space. The follow script will create 2gb of swap space. + +```shell +sudo install -o root -g root -m 0600 /dev/null /swapfile +dd if=/dev/zero of=/swapfile bs=1k count=2048k +mkswap /swapfile +swapon /swapfile +echo "/swapfile swap swap auto 0 0" | sudo tee -a /etc/fstab +sudo sysctl -w vm.swappiness=10 +echo vm.swappiness = 10 | sudo tee -a /etc/sysctl.conf +``` + +*** + +__Symptom:__ I successfully deployed my application with no deployment errors but I'm receiving Connection Timeout when attempting to access the application. + +__Solution:__ + +This can occur if Dokku is running on a system with a firewall like ufw enabled (some OS versions like Ubuntu 16.04 have this enabled by default). You can check if this is your case by running the following script: + +```shell +sudo ufw status +``` + +If the previous script returned `Status: active` and a list of ports, ufw is enabled and is probably the cause of the symptom described above. To disable it, run: + +```shell +sudo ufw disable +``` + +*** + diff --git a/docs/getting-started/upgrading.md b/docs/getting-started/upgrading.md index 42cfa36a6..5caebb44e 100644 --- a/docs/getting-started/upgrading.md +++ b/docs/getting-started/upgrading.md @@ -1,6 +1,6 @@ # Upgrading -If your version of Dokku is pre 0.3.0 (check with `dokku version`), we recommend [a fresh install](/dokku/getting-started/installation/) on a new server. +If your version of Dokku is pre 0.3.0 (check with `dokku version`), we recommend [a fresh install](/docs/getting-started/installation.md) on a new server. ## Migration Guides @@ -8,15 +8,15 @@ Before upgrading, check the migration guides to get comfortable with new feature ### 0.5 Migration Guide -- [0.5 Migration Guide](/dokku/appendices/0.5.0-migration-guide/) +- [0.5 Migration Guide](/docs/appendices/0.5.0-migration-guide.md) ### 0.6 Migration Guide -- [0.6 Migration Guide](/dokku/appendices/0.6.0-migration-guide/) +- [0.6 Migration Guide](/docs/appendices/0.6.0-migration-guide.md) ### 0.7 Migration Guide -- [0.7 Migration Guide](/dokku/appendices/0.7.0-migration-guide/) +- [0.7 Migration Guide](/docs/appendices/0.7.0-migration-guide.md) ## Upgrade Instructions @@ -25,8 +25,8 @@ If Dokku was installed via `apt-get install dokku` or `bootstrap.sh` (most commo ```shell sudo apt-get update dokku apps -dokku ps:stop # repeat to shut down each running app -sudo apt-get install -qq -y dokku herokuish +dokku ps:stop APP # repeat to shut down each running app +sudo apt-get install -qq -y dokku herokuish sshcommand plugn dokku ps:rebuildall # rebuilds all applications ``` @@ -37,7 +37,7 @@ dokku ps:rebuildall # rebuilds all applications > dokku ps:rebuild APP > ``` > -> Please see the [images documentation](/dokku/deployment/methods/images/) and [tar documentation](/dokku/deployment/methods/tar/) +> Please see the [images documentation](/docs/deployment/methods/images.md) and [tar documentation](/docs/deployment/methods/tar.md) > for instructions on rebuilding applications deployed by those plugins. ### Upgrade From Source @@ -46,7 +46,7 @@ If you installed Dokku from source (less common), upgrade with: ```shell dokku apps -dokku ps:stop # repeat to shut down each running app +dokku ps:stop APP # repeat to shut down each running app cd ~/dokku git pull --tags origin master diff --git a/docs/getting-started/where-to-get-help.md b/docs/getting-started/where-to-get-help.md index b0f6fb591..f96dac057 100644 --- a/docs/getting-started/where-to-get-help.md +++ b/docs/getting-started/where-to-get-help.md @@ -4,7 +4,7 @@ If you’re stuck, there are a number of places you can get help: ## The Official Dokku Website -- [http://dokku.viewdocs.io/dokku/](/dokku/) +- [http://dokku.viewdocs.io/dokku/](/docs/) The Official Dokku website is always a great place to visit. It features links to oft-used developer tools, community plugins, and guides on using dokku. diff --git a/docs/home.html b/docs/home.html index 402a9e9b8..95396896d 100644 --- a/docs/home.html +++ b/docs/home.html @@ -7,33 +7,33 @@ - + Dokku - The smallest PaaS implementation you've ever seen - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + - - + + - + @@ -98,12 +98,12 @@

$ - wget https://raw.githubusercontent.com/dokku/dokku/v0.7.2/bootstrap.sh + wget https://raw.githubusercontent.com/dokku/dokku/v0.9.4/bootstrap.sh

$ - sudo DOKKU_TAG=v0.7.2 bash bootstrap.sh + sudo DOKKU_TAG=v0.9.4 bash bootstrap.sh

 # go to your server's IP and follow the web installer @@ -173,7 +173,7 @@

-

Hate curl | bash? See our official Azure, DigitalOcean, DreamHost Cloud, and Linode instructions.

+

Hate curl | bash? See our official Azure, DigitalOcean, and DreamHost Cloud instructions.

Still no love? Contributions welcome!

diff --git a/docs/template.html b/docs/template.html index 7c69c23c8..84b26c22f 100644 --- a/docs/template.html +++ b/docs/template.html @@ -7,29 +7,28 @@ - Dokku - The smallest PaaS implementation you've ever seen - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + - - + + - + diff --git a/dokku b/dokku index 15a65e353..a8f4a3606 100755 --- a/dokku +++ b/dokku @@ -7,17 +7,9 @@ export DOKKU_ROOT=${DOKKU_ROOT:=~dokku} [[ -d $DOKKU_ROOT/.dokkurc ]] && for f in $DOKKU_ROOT/.dokkurc/*; do source "$f"; done [[ $DOKKU_TRACE ]] && set -x -case "$(lsb_release -si)" in - Arch) - export DOKKU_DISTRO=${DOKKU_DISTRO:="arch"} - ;; - Debian) - export DOKKU_DISTRO=${DOKKU_DISTRO:="debian"} - ;; - *) - export DOKKU_DISTRO=${DOKKU_DISTRO:="ubuntu"} - ;; -esac +export DOKKU_DISTRO +# shellcheck disable=SC1091 +DOKKU_DISTRO=$(. /etc/os-release > /dev/null 2>&1 || true ; echo "$ID") export DOKKU_IMAGE=${DOKKU_IMAGE:="gliderlabs/herokuish"} export DOKKU_LIB_ROOT=${DOKKU_LIB_PATH:="/var/lib/dokku"} @@ -43,10 +35,12 @@ source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" parse_args "$@" args=("$@") +skip_arg=false if [[ "${args[0]}" =~ ^--.* ]]; then for arg in "$@"; do + $skip_arg && skip_arg=false && continue if [[ "$arg" == "--app" ]]; then - shift 2 + shift 2 && skip_arg=true elif [[ "$arg" =~ ^--.* ]]; then shift 1 else @@ -62,8 +56,12 @@ if [[ $(id -un) != "dokku" ]] && [[ ! $1 =~ plugin:* ]] && [[ ! $1 == "ssh-keys: exit $? fi -if [[ $(id -un) != "root" && $1 =~ ^plugin:.* ]] || [[ $(id -un) != "root" && $1 == "ssh-keys:add" ]]; then - dokku_log_fail "This command must be run as root" +if [[ $1 =~ ^plugin:.* && $1 != "plugin:help" && $1 != "plugin:list" ]] || [[ $1 == "ssh-keys:add" ]]; then + if [[ $(id -un) != "root" ]]; then + dokku_log_fail "This command must be run as root" + else + export SSH_USER=root + fi fi if [[ -n "$SSH_ORIGINAL_COMMAND" ]]; then diff --git a/plugins/00_dokku-standard/core-post-deploy b/plugins/00_dokku-standard/core-post-deploy new file mode 100755 index 000000000..7b21978cb --- /dev/null +++ b/plugins/00_dokku-standard/core-post-deploy @@ -0,0 +1,23 @@ +#!/usr/bin/env bash +set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x +source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" + +cleanup_container_state() { + declare desc="core post-deploy state cleanup" + local trigger="core-post-deploy cleanup_container_state" + local APP="$1"; local PROCTYPES="$(egrep -v "^#" "$DOKKU_ROOT/$APP/DOKKU_SCALE" | awk -F '=' '{ print $1 }' | xargs)" + local CONTAINER_FILES="$(find "$DOKKU_ROOT/$APP" -maxdepth 1 -name "CONTAINER.*" -printf "%f\n" 2>/dev/null | sort -t . -k 3 -n | xargs)" + + local CONTAINER_FILE + for CONTAINER_FILE in $CONTAINER_FILES; do + local CONTAINER_TYPE="$(awk -F '.' '{ print $2 }' <<< "$CONTAINER_FILE")" + if [[ "$(is_val_in_list "$CONTAINER_TYPE" "$PROCTYPES" " ")" == "false" ]]; then + dokku_log_info1 "Container type ($CONTAINER_TYPE) is no longer defined. Removing state" + local IP_FILE="${CONTAINER_FILE//CONTAINER/IP}" + local PORT_FILE="${CONTAINER_FILE//CONTAINER/PORT}" + rm -f "$DOKKU_ROOT/$APP/$CONTAINER_FILE" "$DOKKU_ROOT/$APP/$IP_FILE" "$DOKKU_ROOT/$APP/$PORT_FILE" + fi + done +} + +cleanup_container_state "$@" diff --git a/plugins/00_dokku-standard/exec-app-json-scripts b/plugins/00_dokku-standard/exec-app-json-scripts index b6a6870de..7bb3e5809 100755 --- a/plugins/00_dokku-standard/exec-app-json-scripts +++ b/plugins/00_dokku-standard/exec-app-json-scripts @@ -42,7 +42,7 @@ execute_script() { COMMAND+=" rm -rf /tmp/cache ; " COMMAND+=" ln -sf /cache /tmp/cache ; " COMMAND+=" fi ; " - COMMAND+=" $SCRIPT_CMD ;" + COMMAND+=" $SCRIPT_CMD || exit 1;" COMMAND+=" if [[ -d '/cache' ]]; then " COMMAND+=" echo removing installation cache... ; " COMMAND+=" rm -f /tmp/cache ; " diff --git a/plugins/00_dokku-standard/install b/plugins/00_dokku-standard/install index 9ef25cf45..b129367b7 100755 --- a/plugins/00_dokku-standard/install +++ b/plugins/00_dokku-standard/install @@ -30,7 +30,8 @@ if [[ $(systemctl 2> /dev/null) =~ -\.mount ]]; then cat< /etc/systemd/system/dokku-redeploy.service [Unit] Description=Dokku app redeploy service -After=docker.target +Requires=docker.service +After=docker.service [Service] Type=simple diff --git a/plugins/00_dokku-standard/plugin.toml b/plugins/00_dokku-standard/plugin.toml index b0a1b8939..2f379d7d8 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.7.2" +version = "0.9.4" [plugin.config] diff --git a/plugins/00_dokku-standard/post-deploy b/plugins/00_dokku-standard/post-deploy index 7c765c3b3..50603555e 100755 --- a/plugins/00_dokku-standard/post-deploy +++ b/plugins/00_dokku-standard/post-deploy @@ -5,30 +5,11 @@ source "$PLUGIN_CORE_AVAILABLE_PATH/00_dokku-standard/exec-app-json-scripts" exec_app_json_scripts() { declare desc="core app.json scripts execution" - local trigger="pre-deploy app_json_scripts" + local trigger="post-deploy app_json_scripts" local APP="$1"; local IMAGE_TAG="$4"; local PHASE_SCRIPT_KEY="postdeploy" dokku_log_info1 "Attempting to run scripts.dokku.$PHASE_SCRIPT_KEY from app.json (if defined)" execute_script "$APP" "$IMAGE_TAG" "$PHASE_SCRIPT_KEY" } -cleanup_container_state() { - declare desc="core post-deploy state cleanup" - local trigger="post-deploy cleanup_container_state" - local APP="$1"; local PROCTYPES="$(egrep -v "^#" "$DOKKU_ROOT/$APP/DOKKU_SCALE" | awk -F '=' '{ print $1 }' | xargs)" - local CONTAINER_FILES="$(find "$DOKKU_ROOT/$APP" -maxdepth 1 -name "CONTAINER.*" -printf "%f\n" 2>/dev/null | sort -t . -k 3 -n | xargs)" - - local CONTAINER_FILE - for CONTAINER_FILE in $CONTAINER_FILES; do - local CONTAINER_TYPE="$(awk -F '.' '{ print $2 }' <<< "$CONTAINER_FILE")" - if [[ "$(is_val_in_list "$CONTAINER_TYPE" "$PROCTYPES" " ")" == "false" ]]; then - dokku_log_info1 "Container type ($CONTAINER_TYPE) is no longer defined. Removing state" - local IP_FILE="${CONTAINER_FILE//CONTAINER/IP}" - local PORT_FILE="${CONTAINER_FILE//CONTAINER/PORT}" - rm -f "$DOKKU_ROOT/$APP/$CONTAINER_FILE" "$DOKKU_ROOT/$APP/$IP_FILE" "$DOKKU_ROOT/$APP/$PORT_FILE" - fi - done -} - exec_app_json_scripts "$@" -cleanup_container_state "$@" diff --git a/plugins/00_dokku-standard/pre-deploy b/plugins/00_dokku-standard/pre-deploy index 928637b41..c78257140 100755 --- a/plugins/00_dokku-standard/pre-deploy +++ b/plugins/00_dokku-standard/pre-deploy @@ -1,8 +1,8 @@ #!/usr/bin/env bash set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" -source "$PLUGIN_CORE_AVAILABLE_PATH/config/functions" source "$PLUGIN_CORE_AVAILABLE_PATH/00_dokku-standard/exec-app-json-scripts" +source "$PLUGIN_AVAILABLE_PATH/config/functions" exec_app_json_scripts() { declare desc="core app.json scripts execution" diff --git a/plugins/00_dokku-standard/subcommands/report b/plugins/00_dokku-standard/subcommands/report index 1a155e744..036d61079 100755 --- a/plugins/00_dokku-standard/subcommands/report +++ b/plugins/00_dokku-standard/subcommands/report @@ -18,7 +18,7 @@ dokku_report_cmd() { docker run --rm -ti gliderlabs/herokuish:latest herokuish version dokku_log_info1 "dokku version: $(dokku version)" dokku_log_info1 "dokku plugins: " - dokku plugin + dokku plugin:list } dokku_report_cmd "$@" diff --git a/plugins/00_dokku-standard/subcommands/run b/plugins/00_dokku-standard/subcommands/run index 4a0d04d66..1c413de7c 100755 --- a/plugins/00_dokku-standard/subcommands/run +++ b/plugins/00_dokku-standard/subcommands/run @@ -1,7 +1,7 @@ #!/usr/bin/env bash set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" -source "$PLUGIN_CORE_AVAILABLE_PATH/ps/functions" +source "$PLUGIN_AVAILABLE_PATH/ps/functions" source "$PLUGIN_AVAILABLE_PATH/config/functions" dokku_run_cmd() { diff --git a/plugins/20_events/core-post-deploy b/plugins/20_events/core-post-deploy new file mode 120000 index 000000000..5178a749f --- /dev/null +++ b/plugins/20_events/core-post-deploy @@ -0,0 +1 @@ +hook \ No newline at end of file diff --git a/plugins/20_events/install b/plugins/20_events/install index 99be9dc24..cdc5cb497 100755 --- a/plugins/20_events/install +++ b/plugins/20_events/install @@ -10,9 +10,10 @@ flag_rsyslog_needs_restart=n # This can be done unconditionally as mkdir -p # exits gracefully if the path already exists +# shellcheck disable=SC2174 mkdir -m 775 -p "$DOKKU_LOGS_DIR" case "$DOKKU_DISTRO" in - arch|debian) + arch|debian|centos) chgrp dokku "$DOKKU_LOGS_DIR" ;; *) @@ -23,7 +24,7 @@ esac if [[ ! -f "$DOKKU_EVENTS_LOGFILE" ]]; then touch "$DOKKU_EVENTS_LOGFILE" case "$DOKKU_DISTRO" in - arch|debian) + arch|debian|centos) chgrp dokku "$DOKKU_EVENTS_LOGFILE" ;; *) @@ -59,7 +60,7 @@ $DOKKU_LOGS_DIR/*.log { EOF if [[ "$DOKKU_DISTRO" = "debian" ]]; then - sed -i '/\s*su syslog dokku/d; s/\(create [0-7][0-7][0-7]\) syslog dokku/\1 root dokku/g' $DOKKU_LOGROTATE_FILE + sed -i 's/ syslog dokku$/root dokku/g' $DOKKU_LOGROTATE_FILE fi flag_rsyslog_needs_restart=y diff --git a/plugins/20_events/plugin.toml b/plugins/20_events/plugin.toml index 8221c20a6..d88622762 100644 --- a/plugins/20_events/plugin.toml +++ b/plugins/20_events/plugin.toml @@ -1,4 +1,4 @@ [plugin] description = "dokku core events logging plugin" -version = "0.7.2" +version = "0.9.4" [plugin.config] diff --git a/plugins/apps/commands b/plugins/apps/commands index c46d255b1..84cd4b172 100755 --- a/plugins/apps/commands +++ b/plugins/apps/commands @@ -1,36 +1,11 @@ #!/usr/bin/env bash [[ " help apps:help " == *" $1 "* ]] || exit "$DOKKU_NOT_IMPLEMENTED_EXIT" +source "$PLUGIN_AVAILABLE_PATH/apps/internal-functions" set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x case "$1" in help | apps:help) - help_content_func () { - declare desc="return apps plugin help content" - cat<, Create a new app - apps:destroy , Permanently destroy an app - apps:rename , Rename an app -help_content - } - - if [[ $1 = "apps:help" ]] ; then - echo -e 'Usage: dokku apps[:COMMAND]' - echo '' - echo 'List your apps.' - echo '' - echo 'Example:' - echo '' - echo '$ dokku apps' - echo '=====> My Apps' - echo 'example' - echo 'example2' - echo '' - echo 'Additional commands:' - help_content_func | sort | column -c2 -t -s, - else - help_content_func - fi + apps_help_cmd "$@" ;; *) diff --git a/plugins/apps/internal-functions b/plugins/apps/internal-functions new file mode 100755 index 000000000..4de6de77e --- /dev/null +++ b/plugins/apps/internal-functions @@ -0,0 +1,51 @@ +#!/usr/bin/env bash +set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x +source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" + +apps_help_content_func() { + declare desc="return apps plugin help content" + cat< , Clones an app + apps:create , Create a new app + apps:destroy , Permanently destroy an app + apps:list, List your apps + apps:rename , Rename an app + apps:report [] [], Display report about an app +help_content +} + +apps_help_cmd() { + if [[ $1 = "apps:help" ]] ; then + echo -e 'Usage: dokku apps[:COMMAND]' + echo '' + echo 'Manage Dokku apps' + echo '' + echo 'Example:' + echo '' + echo '$ dokku apps:list' + echo '=====> My Apps' + echo 'example' + echo 'example2' + echo '' + echo 'Additional commands:' + apps_help_content_func | sort | column -c2 -t -s, + elif [[ $(ps -o command= $PPID) == *"--all"* ]]; then + apps_help_content_func + else + cat< /dev/null + find ./* -not \( -name .cache \) | cpio -pvdmu "$DOKKU_ROOT/$NEW_APP" + popd > /dev/null + + if [[ -d "$NEW_CACHE_DIR" ]] && ! rmdir "$NEW_CACHE_DIR"; then + docker run "$DOKKU_GLOBAL_RUN_ARGS" --rm -v "$NEW_CACHE_DIR:/cache" "dokku/$OLD_APP" chmod 777 -R /cache + fi + rm -rf "$NEW_CACHE_DIR" + + [[ -f "$DOKKU_ROOT/$NEW_APP/URLS" ]] && sed -i -e "s/$OLD_APP/$NEW_APP/g" "$DOKKU_ROOT/$NEW_APP/URLS" + [[ -f "$DOKKU_ROOT/$NEW_APP/VHOST" ]] && sed -i -e "s/$OLD_APP/$NEW_APP/g" "$DOKKU_ROOT/$NEW_APP/VHOST" + [[ -f "$DOKKU_ROOT/$NEW_APP/hooks/pre-receive" ]] && sed -i -e "s/git-hook $OLD_APP/git-hook $NEW_APP/g" "$DOKKU_ROOT/$NEW_APP/hooks/pre-receive" + [[ "$SKIP_REBUILD" == "true" ]] || ps_rebuild "$NEW_APP" + plugn trigger post-app-clone "$OLD_APP" "$NEW_APP" + echo "Cloning $OLD_APP to $NEW_APP... done" +} + +apps_clone_cmd "$@" diff --git a/plugins/apps/subcommands/default b/plugins/apps/subcommands/default index 47f5973a6..ac2c04ed3 100755 --- a/plugins/apps/subcommands/default +++ b/plugins/apps/subcommands/default @@ -1,16 +1,6 @@ #!/usr/bin/env bash set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x -source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" +source "$PLUGIN_AVAILABLE_PATH/apps/internal-functions" -apps_main_cmd() { - declare desc="lists all apps" - local cmd="apps" - local app - - dokku_log_info2_quiet "My Apps" - for app in $(dokku_apps); do - echo "$app" - done -} - -apps_main_cmd "$@" +dokku_log_warn "Deprecated: Please use apps:list" +apps_list_cmd "$@" diff --git a/plugins/apps/subcommands/list b/plugins/apps/subcommands/list new file mode 100755 index 000000000..b9573fac2 --- /dev/null +++ b/plugins/apps/subcommands/list @@ -0,0 +1,5 @@ +#!/usr/bin/env bash +set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x +source "$PLUGIN_AVAILABLE_PATH/apps/internal-functions" + +apps_list_cmd "$@" diff --git a/plugins/apps/subcommands/rename b/plugins/apps/subcommands/rename index 7ad59b08d..34da3df31 100755 --- a/plugins/apps/subcommands/rename +++ b/plugins/apps/subcommands/rename @@ -13,11 +13,11 @@ apps_rename_cmd() { local NEW_APP="$3" local OLD_CACHE_DIR="$DOKKU_ROOT/$OLD_APP/cache" - mkdir -p "$DOKKU_ROOT/$NEW_APP" if [[ -d "$OLD_CACHE_DIR" ]] && ! rmdir "$OLD_CACHE_DIR"; then docker run "$DOKKU_GLOBAL_RUN_ARGS" --rm -v "$OLD_CACHE_DIR:/cache" "dokku/$OLD_APP" chmod 777 -R /cache fi rm -rf "$OLD_CACHE_DIR" + apps_create "$NEW_APP" cp -a "$DOKKU_ROOT/$OLD_APP/." "$DOKKU_ROOT/$NEW_APP" DOKKU_APPS_FORCE_DELETE=1 apps_destroy "$OLD_APP" [[ -f "$DOKKU_ROOT/$NEW_APP/URLS" ]] && sed -i -e "s/$OLD_APP/$NEW_APP/g" "$DOKKU_ROOT/$NEW_APP/URLS" diff --git a/plugins/apps/subcommands/report b/plugins/apps/subcommands/report new file mode 100755 index 000000000..10e48e54c --- /dev/null +++ b/plugins/apps/subcommands/report @@ -0,0 +1,92 @@ +#!/usr/bin/env bash +set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x +source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" +source "$PLUGIN_AVAILABLE_PATH/apps/functions" + +report_single_app() { + declare APP="$1" INFO_FLAG="$2" + local APP_DIR="$DOKKU_ROOT/$APP" + local use_echo; + if [[ "$INFO_FLAG" == "true" ]]; then + use_echo=true + INFO_FLAG="" + fi + verify_app_name "$APP" + local DOKKU_APP_CIDS=$(get_app_container_ids "$APP") + if [[ -n $DOKKU_APP_CIDS ]]; then + local cid + for CID in $DOKKU_APP_CIDS; do + local APP_CONTAINER_STATUS + local DOKKU_APP_CID + DOKKU_APP_CID=$CID + APP_CONTAINER_STATUS=$(docker inspect -f '{{.State.Status}}' "$CID") + done + fi + local flag_map=( + "--app-dir: $APP_DIR" + "--git-sha: $(GIT_DIR="$APP_DIR" git rev-parse --short HEAD 2> /dev/null || false)" + "--app-cid: ${DOKKU_APP_CID:0:12}" + "--status: $APP_CONTAINER_STATUS" + ) + + if [[ -z "$INFO_FLAG" ]]; then + dokku_log_info2 "$APP" + if (is_deployed "$APP"); then + for flag in "${flag_map[@]}"; do + key="$(echo "${flag#--}" | cut -f1 -d' ' | tr - ' ')" + dokku_log_verbose "$(printf "%-20s %-25s" "${key^}" "${flag#*: }")" + done + else + if [[ "$use_echo" ]]; then + echo "not deployed" + else + dokku_log_fail "not deployed" + fi + fi + else + local match=false; local value_exists=false + for flag in "${flag_map[@]}"; do + valid_flags="${valid_flags} $(echo "$flag" | cut -d':' -f1)" + if [[ "$flag" == "${INFO_FLAG}:"* ]]; then + value=${flag#*: } + size="${#value}" + if [[ "$size" -ne 0 ]]; then + echo "$value" && match=true && value_exists=true + else + match=true + fi + fi + done + if [[ "$match" == "true" ]]; then + [[ "$value_exists" == "true" ]] || dokku_log_fail "not deployed" + else + dokku_log_fail "Invalid flag passed, valid flags:${valid_flags}" + fi + fi +} + +apps_report_cmd() { + declare desc="displays the app report for one or more apps" + local cmd="apps:report" + local INSTALLED_APPS=$(dokku_apps) + local APP="$2" INFO_FLAG="$3" + + if [[ -n "$APP" ]] && [[ "$APP" == --* ]]; then + INFO_FLAG="$APP" + APP="" + fi + + if [[ -z "$APP" ]] && [[ -z "$INFO_FLAG" ]]; then + INFO_FLAG="true" + fi + + if [[ -z "$APP" ]]; then + for app in $INSTALLED_APPS; do + report_single_app "$app" "$INFO_FLAG" | tee || true + done + else + report_single_app "$APP" "$INFO_FLAG" + fi +} + +apps_report_cmd "$@" diff --git a/plugins/build-env/plugin.toml b/plugins/build-env/plugin.toml index ade77a4a7..c71e37113 100644 --- a/plugins/build-env/plugin.toml +++ b/plugins/build-env/plugin.toml @@ -1,4 +1,4 @@ [plugin] description = "dokku core build-env plugin" -version = "0.7.2" +version = "0.9.4" [plugin.config] diff --git a/plugins/build-env/pre-build-buildpack b/plugins/build-env/pre-build-buildpack index 537cececf..57d32917d 100755 --- a/plugins/build-env/pre-build-buildpack +++ b/plugins/build-env/pre-build-buildpack @@ -9,8 +9,8 @@ build_env_pre_build_buildpack() { local APP="$1"; local IMAGE=$(get_app_image_name "$APP"); local BUILD_ENV="" verify_app_name "$APP" - [[ -z $(config_get --global CURL_CONNECT_TIMEOUT) ]] && config_set --global CURL_CONNECT_TIMEOUT=5 - [[ -z $(config_get --global CURL_TIMEOUT) ]] && config_set --global CURL_TIMEOUT=30 + [[ -z $(config_get --global CURL_CONNECT_TIMEOUT) ]] && config_set --global CURL_CONNECT_TIMEOUT=90 + [[ -z $(config_get --global CURL_TIMEOUT) ]] && config_set --global CURL_TIMEOUT=60 if [[ -n $(config_export global) ]]; then local BUILD_ENV+=$'\n' diff --git a/plugins/certs/commands b/plugins/certs/commands index c34b902c8..9e13305ab 100755 --- a/plugins/certs/commands +++ b/plugins/certs/commands @@ -1,34 +1,11 @@ #!/usr/bin/env bash [[ " help certs:help " == *" $1 "* ]] || exit "$DOKKU_NOT_IMPLEMENTED_EXIT" +source "$PLUGIN_AVAILABLE_PATH/certs/internal-functions" set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x case "$1" in help | certs:help) - help_content_func () { - declare desc="return certs plugin help content" - cat< CRT KEY, Add an ssl endpoint to an app. Can also import from a tarball on stdin - certs:chain CRT [CRT ...], [NOT IMPLEMENTED] Print the ordered and complete chain for the given certificate - certs:generate DOMAIN, Generate a key and certificate signing request (and self-signed certificate) - certs:info , Show certificate information for an ssl endpoint - certs:key CRT KEY [KEY ...], [NOT IMPLEMENTED] Print the correct key for the given certificate - certs:remove , Remove an SSL Endpoint from an app - certs:rollback , [NOT IMPLEMENTED] Rollback an SSL Endpoint for an app - certs:update CRT KEY, Update an SSL Endpoint on an app. Can also import from a tarball on stdin -help_content - } - - if [[ $1 = "certs:help" ]] ; then - echo -e 'Usage: dokku certs:COMMAND' - echo '' - echo 'Manage Dokku apps SSL (TLS) certs.' - echo '' - echo 'Additional commands:' - help_content_func | sort | column -c2 -t -s, - else - help_content_func - fi + certs_help_cmd "$@" ;; *) diff --git a/plugins/certs/functions b/plugins/certs/functions index b64a14a56..a130c993e 100755 --- a/plugins/certs/functions +++ b/plugins/certs/functions @@ -26,6 +26,6 @@ get_ssl_hostnames() { else local SSL_HOSTNAMES=$SSL_HOSTNAME fi - echo -e "$SSL_HOSTNAMES" + echo -e "$SSL_HOSTNAMES" | sort -u return 0 } diff --git a/plugins/certs/internal-functions b/plugins/certs/internal-functions new file mode 100755 index 000000000..77f4f0c79 --- /dev/null +++ b/plugins/certs/internal-functions @@ -0,0 +1,71 @@ +#!/usr/bin/env bash +set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x +source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" +source "$PLUGIN_AVAILABLE_PATH/certs/functions" + +certs_help_content_func() { + declare desc="return certs plugin help content" + cat<, [DEPRECATED] Alternative for certs:report + certs:add CRT KEY, Add an ssl endpoint to an app. Can also import from a tarball on stdin + certs:chain CRT [CRT ...], [NOT IMPLEMENTED] Print the ordered and complete chain for the given certificate + certs:generate DOMAIN, Generate a key and certificate signing request (and self-signed certificate) + certs:info , [DEPRECATED] Alternative for certs:report + certs:key CRT KEY [KEY ...], [NOT IMPLEMENTED] Print the correct key for the given certificate + certs:remove , Remove an SSL Endpoint from an app + certs:report [] [], Displays an ssl report for one or more apps + certs:rollback , [NOT IMPLEMENTED] Rollback an SSL Endpoint for an app + certs:update CRT KEY, Update an SSL Endpoint on an app. Can also import from a tarball on stdin +help_content +} + +certs_help_cmd() { + if [[ $1 = "certs:help" ]] ; then + echo -e 'Usage: dokku certs[:COMMAND]' + echo '' + echo 'Manage Dokku apps SSL (TLS) certs.' + echo '' + echo 'Additional commands:' + certs_help_content_func | sort | column -c2 -t -s, + elif [[ $(ps -o command= $PPID) == *"--all"* ]]; then + certs_help_content_func + else + cat< /dev/null || true; rm -rf $CERTS_SET_TMP_WORK_DIR > /dev/null' RETURN tar xvf - <&0 - local CRT_FILE_SEARCH=$(find . -not -path '*/\.*' -type f -name "*.crt") + local CRT_FILE_SEARCH=$(find . -not -path '*/\.*' -type f | grep ".crt$") local CRT_FILE_COUNT=$(printf "%s" "$CRT_FILE_SEARCH" | grep -c '^') if [[ $CRT_FILE_COUNT -lt 1 ]]; then dokku_log_fail "Tar archive is missing .crt file" @@ -54,7 +54,7 @@ certs_set() { local CRT_FILE=$CRT_FILE_SEARCH fi - local KEY_FILE_SEARCH=$(find . -not -path '*/\.*' -type f -name "*.key") + local KEY_FILE_SEARCH=$(find . -not -path '*/\.*' -type f | grep ".key$") local KEY_FILE_COUNT=$(printf "%s" "$KEY_FILE_SEARCH" | grep -c '^') if [[ $KEY_FILE_COUNT -lt 1 ]]; then dokku_log_fail "Tar archive is missing .key file" @@ -72,7 +72,6 @@ certs_set() { cp "$KEY_FILE" "$APP_SSL_PATH/server.key" chmod 750 "$APP_SSL_PATH" chmod 640 "$APP_SSL_PATH/server.crt" "$APP_SSL_PATH/server.key" - cd "$DOKKU_ROOT" plugn trigger post-certs-update "$APP" plugn trigger post-domains-update "$APP" } diff --git a/plugins/certs/subcommands/default b/plugins/certs/subcommands/default new file mode 100755 index 000000000..9591bedae --- /dev/null +++ b/plugins/certs/subcommands/default @@ -0,0 +1,6 @@ +#!/usr/bin/env bash +set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x +source "$PLUGIN_AVAILABLE_PATH/certs/internal-functions" + +dokku_log_warn "Deprecated: Please use certs:report" +certs_info_cmd "$@" diff --git a/plugins/certs/subcommands/generate b/plugins/certs/subcommands/generate index daa2caed8..10349e9f9 100755 --- a/plugins/certs/subcommands/generate +++ b/plugins/certs/subcommands/generate @@ -22,14 +22,15 @@ certs_generate_cmd() { mkdir -p "$APP_SSL_PATH" dokku_log_info1 "Installing certificate and key..." - mv -f "$CERTS_GENERATE_TMP_WORK_DIR/server.key" "$CERTS_GENERATE_TMP_WORK_DIR/server.crt" "$APP_SSL_PATH" + mv -f "$CERTS_GENERATE_TMP_WORK_DIR/server.crt" "$CERTS_GENERATE_TMP_WORK_DIR/server.csr" "$CERTS_GENERATE_TMP_WORK_DIR/server.key" "$APP_SSL_PATH" chmod 750 "$APP_SSL_PATH" - chmod 640 "$APP_SSL_PATH/server.key" "$APP_SSL_PATH/server.crt" + chmod 640 "$APP_SSL_PATH/server.crt" "$APP_SSL_PATH/server.csr" "$APP_SSL_PATH/server.key" + plugn trigger post-certs-update "$APP" [[ -n "$DOMAIN" ]] && (domains_add "$APP" "$DOMAIN" || plugn trigger post-domains-update "$APP") dokku_log_info1 "The following is a certificate signing request that can be used" dokku_log_info1 "to generate an 'officially' signed SSL certificate for $APP at $DOMAIN" dokku_log_info1 "by a CA of your choosing." - cat server.csr + cat "$APP_SSL_PATH/server.csr" else dokku_log_info1 "$APP has an SSL endpoint already defined" fi diff --git a/plugins/certs/subcommands/info b/plugins/certs/subcommands/info index cfda760d8..9591bedae 100755 --- a/plugins/certs/subcommands/info +++ b/plugins/certs/subcommands/info @@ -1,38 +1,6 @@ #!/usr/bin/env bash set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x -source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" -source "$PLUGIN_AVAILABLE_PATH/certs/functions" - -certs_info_cmd() { - declare desc="prints SSL certificate info for app" - local cmd="certs:info" - [[ -z $2 ]] && dokku_log_fail "Please specify an app to run the command on" - verify_app_name "$2" - local APP="$2"; local APP_SSL_PATH="$DOKKU_ROOT/$APP/tls" - - if [[ -n "$APP_SSL_PATH" ]]; then - dokku_log_info1 "Fetching SSL Endpoint info for $APP..." - dokku_log_info1 "Certificate details:" - dokku_log_info2 "Common Name(s): " - - for domain in $(get_ssl_hostnames "$APP" | xargs); do - dokku_log_info2 " $domain" - done - - dokku_log_info2 "Expires At: $(openssl x509 -in "$APP_SSL_PATH/server.crt" -noout -text | grep "Not After :" | awk -F " : " '{ print $2 }')" - dokku_log_info2 "Issuer: $(openssl x509 -in "$APP_SSL_PATH/server.crt" -noout -text | grep "Issuer:" | xargs | sed -e "s/Issuer: //g")" - dokku_log_info2 "Starts At: $(openssl x509 -in "$APP_SSL_PATH/server.crt" -noout -text | grep "Not Before:" | awk -F ": " '{ print $2 }')" - dokku_log_info2 "Subject: $(openssl x509 -in "$APP_SSL_PATH/server.crt" -noout -subject | sed -e "s:subject= ::g"| sed -e "s:^/::g" | sed -e "s:/:; :g")" - local SSL_VERIFY_OUTPUT="$(openssl verify -verbose -purpose sslserver "$APP_SSL_PATH/server.crt" | awk -F ':' '{ print $2 }' | tail -1 | xargs || true)" - if [[ "$SSL_VERIFY_OUTPUT" == "OK" ]]; then - local SSL_SELF_SIGNED="verified by a certificate authority." - else - local SSL_SELF_SIGNED="self signed." - fi - dokku_log_info2 "SSL certificate is $SSL_SELF_SIGNED" - else - dokku_log_info1 "$APP does not have an SSL endpoint" - fi -} +source "$PLUGIN_AVAILABLE_PATH/certs/internal-functions" +dokku_log_warn "Deprecated: Please use certs:report" certs_info_cmd "$@" diff --git a/plugins/certs/subcommands/report b/plugins/certs/subcommands/report new file mode 100755 index 000000000..9d8c7d976 --- /dev/null +++ b/plugins/certs/subcommands/report @@ -0,0 +1,147 @@ +#!/usr/bin/env bash +set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x +source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" +source "$PLUGIN_AVAILABLE_PATH/certs/functions" + +fn-ssl-enabled() { + declare APP="$1" + local SSL_ENABLED=false + + if is_ssl_enabled "$APP"; then + SSL_ENABLED=true + fi + echo "$SSL_ENABLED" +} + +fn-ssl-expires-at() { + declare APP="$1" + local APP_SSL_PATH="$DOKKU_ROOT/$APP/tls" + + if is_ssl_enabled "$APP"; then + openssl x509 -in "$APP_SSL_PATH/server.crt" -noout -text | grep "Not After :" | awk -F " : " '{ print $2 }' + fi +} + +fn-ssl-hostnames() { + declare APP="$1" + + if is_ssl_enabled "$APP"; then + get_ssl_hostnames "$APP" | xargs + fi +} + +fn-ssl-issuer() { + declare APP="$1" + local APP_SSL_PATH="$DOKKU_ROOT/$APP/tls" + + if is_ssl_enabled "$APP"; then + openssl x509 -in "$APP_SSL_PATH/server.crt" -noout -text | grep "Issuer:" | xargs | sed -e "s/Issuer: //g" + fi +} + +fn-ssl-starts-at() { + declare APP="$1" + local APP_SSL_PATH="$DOKKU_ROOT/$APP/tls" + + if is_ssl_enabled "$APP"; then + openssl x509 -in "$APP_SSL_PATH/server.crt" -noout -text | grep "Not Before:" | awk -F ": " '{ print $2 }' + fi +} + +fn-ssl-subject() { + declare APP="$1" + local APP_SSL_PATH="$DOKKU_ROOT/$APP/tls" + + if is_ssl_enabled "$APP"; then + openssl x509 -in "$APP_SSL_PATH/server.crt" -noout -subject | sed -e "s:subject= ::g"| sed -e "s:^/::g" | sed -e "s:/:; :g" + fi +} + +fn-ssl-verified() { + declare APP="$1" + local APP_SSL_PATH="$DOKKU_ROOT/$APP/tls" + local SSL_VERIFY_OUTPUT=false SSL_SELF_SIGNED="self signed" + + if ! is_ssl_enabled "$APP"; then + return + fi + + SSL_VERIFY_OUTPUT="$(openssl verify -verbose -purpose sslserver "$APP_SSL_PATH/server.crt" | awk -F ':' '{ print $2 }' | tail -1 | xargs || true)" + if [[ "$SSL_VERIFY_OUTPUT" == "OK" ]]; then + SSL_SELF_SIGNED="verified by a certificate authority" + fi + + echo "$SSL_SELF_SIGNED" +} + +report_single_app() { + declare APP="$1" INFO_FLAG="$2" + local APP_DIR="$DOKKU_ROOT/$APP" + if [[ "$INFO_FLAG" == "true" ]]; then + INFO_FLAG="" + fi + verify_app_name "$APP" + local flag_map=( + "--ssl-dir: $APP_DIR/tls" + "--ssl-enabled: $(fn-ssl-enabled "$APP")" + "--ssl-hostnames: $(fn-ssl-hostnames "$APP")" + "--ssl-expires-at: $(fn-ssl-expires-at "$APP")" + "--ssl-issuer: $(fn-ssl-issuer "$APP")" + "--ssl-starts-at: $(fn-ssl-starts-at "$APP")" + "--ssl-subject: $(fn-ssl-subject "$APP")" + "--ssl-verified: $(fn-ssl-verified "$APP")" + ) + + if [[ -z "$INFO_FLAG" ]]; then + dokku_log_info2 "$APP ssl information" + for flag in "${flag_map[@]}"; do + key="$(echo "${flag#--}" | cut -f1 -d' ' | tr - ' ')" + dokku_log_verbose "$(printf "%-20s %-25s" "${key^}" "${flag#*: }")" + done + else + local match=false; local value_exists=false + for flag in "${flag_map[@]}"; do + valid_flags="${valid_flags} $(echo "$flag" | cut -d':' -f1)" + if [[ "$flag" == "${INFO_FLAG}:"* ]]; then + value=${flag#*: } + size="${#value}" + if [[ "$size" -ne 0 ]]; then + echo "$value" && match=true && value_exists=true + else + match=true + fi + fi + done + if [[ "$match" == "true" ]]; then + [[ "$value_exists" == "true" ]] || dokku_log_fail "not deployed" + else + dokku_log_fail "Invalid flag passed, valid flags:${valid_flags}" + fi + fi +} + +certs_report_cmd() { + declare desc="displays an ssl report for one or more apps" + local cmd="certs:report" + local INSTALLED_APPS=$(dokku_apps) + local APP="$2" INFO_FLAG="$3" + + if [[ -n "$APP" ]] && [[ "$APP" == --* ]]; then + INFO_FLAG="$APP" + APP="" + fi + + if [[ -z "$APP" ]] && [[ -z "$INFO_FLAG" ]]; then + INFO_FLAG="true" + fi + + if [[ -z "$APP" ]]; then + for app in $INSTALLED_APPS; do + report_single_app "$app" "$INFO_FLAG" | tee || true + done + else + report_single_app "$APP" "$INFO_FLAG" + fi +} + +certs_report_cmd "$@" diff --git a/plugins/checks/check-deploy b/plugins/checks/check-deploy index 7652331e4..7afe7a56b 100755 --- a/plugins/checks/check-deploy +++ b/plugins/checks/check-deploy @@ -74,7 +74,7 @@ checks_check_deploy() { # after successfully copying the file. Thus, we suppress stderr. # ref: https://github.com/dotcloud/docker/issues/3986 local CHECK_DEPLOY_TMP_WORK_DIR=$(mktemp -d /tmp/dokku_CHECKS.XXXXX) - docker cp $DOKKU_APP_CONTAINER_ID:/app/CHECKS "$CHECK_DEPLOY_TMP_WORK_DIR" 2> /dev/null || true + docker cp "$DOKKU_APP_CONTAINER_ID:/app/CHECKS" "$CHECK_DEPLOY_TMP_WORK_DIR" 2> /dev/null || true local FILENAME=${CHECK_DEPLOY_TMP_WORK_DIR}/CHECKS @@ -107,8 +107,8 @@ checks_check_deploy() { dokku_log_info1 "Waiting for $DOKKU_DEFAULT_CHECKS_WAIT seconds ..." sleep "$DOKKU_DEFAULT_CHECKS_WAIT" - ! (is_container_running $DOKKU_APP_CONTAINER_ID) && dokku_log_fail "App container failed to start!!" - local container_restarts=$(docker inspect -f "{{ .RestartCount }}" $DOKKU_APP_CONTAINER_ID) + ! (is_container_running "$DOKKU_APP_CONTAINER_ID") && dokku_log_fail "App container failed to start!!" + local container_restarts="$(docker inspect -f "{{ .RestartCount }}" "$DOKKU_APP_CONTAINER_ID")" if [[ $container_restarts -ne 0 ]]; then docker stop "$DOKKU_APP_CONTAINER_ID" || true dokku_log_fail "App container failed to start!!" diff --git a/plugins/checks/commands b/plugins/checks/commands index 2400b5ee0..0aed38a1b 100755 --- a/plugins/checks/commands +++ b/plugins/checks/commands @@ -1,30 +1,11 @@ #!/usr/bin/env bash [[ " help checks:help " == *" $1 "* ]] || exit "$DOKKU_NOT_IMPLEMENTED_EXIT" +source "$PLUGIN_AVAILABLE_PATH/checks/internal-functions" set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x case "$1" in help | checks:help) - help_content_func () { - declare desc="return checks plugin help content" - cat<, Show zero-downtime status - checks:disable [process-type(s)], Disable zero-downtime deployment for all processes (or comma-separated process-type list) ***WARNING: this will cause downtime during deployments*** - checks:enable [process-type(s)], Enable zero-downtime deployment for all processes (or comma-separated process-type list) - checks:run [process-type(s)], Runs zero-downtime checks for all processes (or comma-separated process-type list) - checks:skip [process-type(s)], Skip zero-downtime checks for all processes (or comma-separated process-type list) -help_content - } - - if [[ $1 = "checks:help" ]] ; then - echo -e 'Usage: dokku checks[:COMAND]' - echo '' - echo 'Manage zero-downtime settings.' - echo '' - echo 'Additional commands:' - help_content_func | sort | column -c2 -t -s, - else - help_content_func - fi + checks_help_cmd "$@" ;; *) diff --git a/plugins/checks/internal-functions b/plugins/checks/internal-functions new file mode 100755 index 000000000..d0ebad465 --- /dev/null +++ b/plugins/checks/internal-functions @@ -0,0 +1,32 @@ +#!/usr/bin/env bash +set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x +source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" + +checks_help_content_func() { + declare desc="return checks plugin help content" + cat<, [DEPRECATED] Alternative for checks:report + checks:disable [process-type(s)], Disable zero-downtime deployment for all processes (or comma-separated process-type list) ***WARNING: this will cause downtime during deployments*** + checks:enable [process-type(s)], Enable zero-downtime deployment for all processes (or comma-separated process-type list) + checks:report [] [], Displays a checks report for one or more apps + checks:run [process-type(s)], Runs zero-downtime checks for all processes (or comma-separated process-type list) + checks:skip [process-type(s)], Skip zero-downtime checks for all processes (or comma-separated process-type list) +help_content +} + +checks_help_cmd() { + if [[ $1 = "checks:help" ]] ; then + echo -e 'Usage: dokku checks[:COMMAND]' + echo '' + echo 'Manage zero-downtime settings.' + echo '' + echo 'Additional commands:' + checks_help_content_func | sort | column -c2 -t -s, + elif [[ $(ps -o command= $PPID) == *"--all"* ]]; then + checks_help_content_func + else + cat<&2 } dokku_log_fail() { @@ -142,7 +142,7 @@ verify_app_name() { local APP="$1" [[ ! -n "$APP" ]] && dokku_log_fail "(verify_app_name) APP must not be null" if [[ ! "$APP" =~ ^[a-z].* && ! "$APP" =~ ^[0-9].* ]]; then - [[ -d "$DOKKU_ROOT/$APP" ]] && rm -rf "$DOKKU_ROOT/$APP" + [[ -d "$DOKKU_ROOT/$APP" ]] && rm -rf "${DOKKU_ROOT:?}/$APP" dokku_log_fail "App name must begin with lowercase alphanumeric character" fi [[ ! -d "$DOKKU_ROOT/$APP" ]] && dokku_log_fail "App $APP does not exist" @@ -211,7 +211,7 @@ get_running_image_tag() { verify_app_name "$APP" local CIDS=( $(get_app_container_ids "$APP") ) - local RUNNING_IMAGE_TAG=$(docker inspect -f '{{ .Config.Image }}' ${CIDS[0]} 2>/dev/null | awk -F: '{ print $2 }' || echo '') + local RUNNING_IMAGE_TAG=$(docker inspect -f '{{ .Config.Image }}' "${CIDS[0]}" 2>/dev/null | awk -F: '{ print $2 }' || echo '') echo "$RUNNING_IMAGE_TAG" } @@ -251,7 +251,12 @@ parse_args() { declare desc="top-level cli arg parser" local next_index=1; local skip=false; local args=("$@") for arg in "$@"; do - $skip && skip=false && continue + if [[ "$skip" == "true" ]]; then + next_index=$(( next_index + 1 )) + skip=false + continue + fi + case "$arg" in --quiet) export DOKKU_QUIET_OUTPUT=1 @@ -272,7 +277,7 @@ parse_args() { export DOKKU_APP_NAME=${args[$next_index]}; skip=true ;; esac - local next_index=$(( next_index + 1 )) + next_index=$(( next_index + 1 )) done return 0 } @@ -326,10 +331,11 @@ get_app_container_ids() { get_app_running_container_ids() { declare desc="return list of running docker container ids for given app and optional container_type" local APP="$1" CONTAINER_TYPE="$2" + local CIDS verify_app_name "$APP" ! (is_deployed "$APP") && dokku_log_fail "App $APP has not been deployed" - local CIDS=$(get_app_container_ids "$APP" "$CONTAINER_TYPE") + CIDS=$(get_app_container_ids "$APP" "$CONTAINER_TYPE") for CID in $CIDS; do local APP_CONTAINER_STATUS=$(docker inspect -f '{{.State.Running}}' "$CID" 2>/dev/null || true) @@ -362,7 +368,7 @@ get_cmd_from_procfile() { if [[ -f $DOKKU_PROCFILE ]]; then local line; local name; local command - while read line || [[ -n "$line" ]]; do + while read -r line || [[ -n "$line" ]]; do if [[ -z "$line" ]] || [[ "$line" == "#"* ]]; then continue fi @@ -525,7 +531,7 @@ dokku_deploy_cmd() { source "$PLUGIN_AVAILABLE_PATH/config/functions" source "$PLUGIN_AVAILABLE_PATH/proxy/functions" - [[ -z $1 ]] && dokku_log_fail "Please specify an app to deploy" + [[ -z $1 ]] && dokku_log_fail "Please specify an app to run the command on" local APP="$1"; local IMAGE_TAG="$2"; local IMAGE=$(get_deploying_app_image_name "$APP" "$IMAGE_TAG") verify_app_name "$APP" plugn trigger pre-deploy "$APP" "$IMAGE_TAG" @@ -554,6 +560,8 @@ dokku_deploy_cmd() { dokku_log_info2 "stopping $APP.$PROC_TYPE ($cid)" # shellcheck disable=SC2086 docker stop $DOCKER_STOP_TIME_ARG "$cid" &> /dev/null + # remove cid from oldids to skip the old container finish processing + oldids="$(remove_val_from_list "$cid" "$oldids" " ")" done fi @@ -651,6 +659,7 @@ dokku_deploy_cmd() { done < "$DOKKU_SCALE_FILE" dokku_log_info1 "Running post-deploy" + plugn trigger core-post-deploy "$APP" "$port" "$ipaddr" "$IMAGE_TAG" plugn trigger post-deploy "$APP" "$port" "$ipaddr" "$IMAGE_TAG" # kill the old container @@ -694,6 +703,8 @@ release_and_deploy() { source "$PLUGIN_AVAILABLE_PATH/config/functions" local APP="$1"; local IMAGE_TAG="$2"; local IMAGE=$(get_app_image_name "$APP" "$IMAGE_TAG") + local DOKKU_DOCKERFILE_PORTS + verify_app_name "$APP" if verify_image "$IMAGE"; then @@ -701,7 +712,7 @@ release_and_deploy() { local IMAGE_SOURCE_TYPE="herokuish" else local IMAGE_SOURCE_TYPE="dockerfile" - local DOKKU_DOCKERFILE_PORTS=$(config_get "$APP" DOKKU_DOCKERFILE_PORTS || true) + DOKKU_DOCKERFILE_PORTS=$(config_get "$APP" DOKKU_DOCKERFILE_PORTS || true) if [[ -z "$DOKKU_DOCKERFILE_PORTS" ]]; then local DOCKER_IMAGE_PORTS=$(get_exposed_ports_from_image "$IMAGE") [[ -n "$DOCKER_IMAGE_PORTS" ]] && config_set --no-restart "$APP" DOKKU_DOCKERFILE_PORTS="$DOCKER_IMAGE_PORTS" diff --git a/plugins/common/glide.lock b/plugins/common/glide.lock new file mode 100644 index 000000000..4f5cbc783 --- /dev/null +++ b/plugins/common/glide.lock @@ -0,0 +1,8 @@ +hash: e438ca8cf1bdca3e9c984bb1ee70c0d58be9da52cbe13a079a7c6cbc14210a37 +updated: 2017-01-03T17:17:27.973373328-08:00 +imports: +- name: github.com/codegangsta/inject + version: 33e0aa1cb7c019ccc3fbe049a8262a6403d30504 +- name: github.com/codeskyblue/go-sh + version: ceb46ec4630a726eeed51d0a70066c9e1102c48f +testImports: [] diff --git a/plugins/common/glide.yaml b/plugins/common/glide.yaml new file mode 100644 index 000000000..2d8bf9366 --- /dev/null +++ b/plugins/common/glide.yaml @@ -0,0 +1,3 @@ +package: . +import: +- package: github.com/codeskyblue/go-sh diff --git a/plugins/common/plugin.toml b/plugins/common/plugin.toml index 9a1675ca7..98192de39 100644 --- a/plugins/common/plugin.toml +++ b/plugins/common/plugin.toml @@ -1,4 +1,4 @@ [plugin] description = "dokku core common plugin" -version = "0.7.2" +version = "0.9.4" [plugin.config] diff --git a/plugins/common/vendor/github.com/codegangsta/inject/.gitignore b/plugins/common/vendor/github.com/codegangsta/inject/.gitignore new file mode 100644 index 000000000..df3df8a90 --- /dev/null +++ b/plugins/common/vendor/github.com/codegangsta/inject/.gitignore @@ -0,0 +1,2 @@ +inject +inject.test diff --git a/plugins/common/vendor/github.com/codegangsta/inject/LICENSE b/plugins/common/vendor/github.com/codegangsta/inject/LICENSE new file mode 100644 index 000000000..eb68a0e05 --- /dev/null +++ b/plugins/common/vendor/github.com/codegangsta/inject/LICENSE @@ -0,0 +1,20 @@ +The MIT License (MIT) + +Copyright (c) 2013 Jeremy Saenz + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/plugins/common/vendor/github.com/codegangsta/inject/README.md b/plugins/common/vendor/github.com/codegangsta/inject/README.md new file mode 100644 index 000000000..679abe01a --- /dev/null +++ b/plugins/common/vendor/github.com/codegangsta/inject/README.md @@ -0,0 +1,92 @@ +# inject +-- + import "github.com/codegangsta/inject" + +Package inject provides utilities for mapping and injecting dependencies in +various ways. + +Language Translations: +* [简体中文](translations/README_zh_cn.md) + +## Usage + +#### func InterfaceOf + +```go +func InterfaceOf(value interface{}) reflect.Type +``` +InterfaceOf dereferences a pointer to an Interface type. It panics if value is +not an pointer to an interface. + +#### type Applicator + +```go +type Applicator interface { + // Maps dependencies in the Type map to each field in the struct + // that is tagged with 'inject'. Returns an error if the injection + // fails. + Apply(interface{}) error +} +``` + +Applicator represents an interface for mapping dependencies to a struct. + +#### type Injector + +```go +type Injector interface { + Applicator + Invoker + TypeMapper + // SetParent sets the parent of the injector. If the injector cannot find a + // dependency in its Type map it will check its parent before returning an + // error. + SetParent(Injector) +} +``` + +Injector represents an interface for mapping and injecting dependencies into +structs and function arguments. + +#### func New + +```go +func New() Injector +``` +New returns a new Injector. + +#### type Invoker + +```go +type Invoker interface { + // Invoke attempts to call the interface{} provided as a function, + // providing dependencies for function arguments based on Type. Returns + // a slice of reflect.Value representing the returned values of the function. + // Returns an error if the injection fails. + Invoke(interface{}) ([]reflect.Value, error) +} +``` + +Invoker represents an interface for calling functions via reflection. + +#### type TypeMapper + +```go +type TypeMapper interface { + // Maps the interface{} value based on its immediate type from reflect.TypeOf. + Map(interface{}) TypeMapper + // Maps the interface{} value based on the pointer of an Interface provided. + // This is really only useful for mapping a value as an interface, as interfaces + // cannot at this time be referenced directly without a pointer. + MapTo(interface{}, interface{}) TypeMapper + // Provides a possibility to directly insert a mapping based on type and value. + // This makes it possible to directly map type arguments not possible to instantiate + // with reflect like unidirectional channels. + Set(reflect.Type, reflect.Value) TypeMapper + // Returns the Value that is mapped to the current type. Returns a zeroed Value if + // the Type has not been mapped. + Get(reflect.Type) reflect.Value +} +``` + +TypeMapper represents an interface for mapping interface{} values based on type. diff --git a/plugins/common/vendor/github.com/codegangsta/inject/inject.go b/plugins/common/vendor/github.com/codegangsta/inject/inject.go new file mode 100644 index 000000000..3ff713c8a --- /dev/null +++ b/plugins/common/vendor/github.com/codegangsta/inject/inject.go @@ -0,0 +1,187 @@ +// Package inject provides utilities for mapping and injecting dependencies in various ways. +package inject + +import ( + "fmt" + "reflect" +) + +// Injector represents an interface for mapping and injecting dependencies into structs +// and function arguments. +type Injector interface { + Applicator + Invoker + TypeMapper + // SetParent sets the parent of the injector. If the injector cannot find a + // dependency in its Type map it will check its parent before returning an + // error. + SetParent(Injector) +} + +// Applicator represents an interface for mapping dependencies to a struct. +type Applicator interface { + // Maps dependencies in the Type map to each field in the struct + // that is tagged with 'inject'. Returns an error if the injection + // fails. + Apply(interface{}) error +} + +// Invoker represents an interface for calling functions via reflection. +type Invoker interface { + // Invoke attempts to call the interface{} provided as a function, + // providing dependencies for function arguments based on Type. Returns + // a slice of reflect.Value representing the returned values of the function. + // Returns an error if the injection fails. + Invoke(interface{}) ([]reflect.Value, error) +} + +// TypeMapper represents an interface for mapping interface{} values based on type. +type TypeMapper interface { + // Maps the interface{} value based on its immediate type from reflect.TypeOf. + Map(interface{}) TypeMapper + // Maps the interface{} value based on the pointer of an Interface provided. + // This is really only useful for mapping a value as an interface, as interfaces + // cannot at this time be referenced directly without a pointer. + MapTo(interface{}, interface{}) TypeMapper + // Provides a possibility to directly insert a mapping based on type and value. + // This makes it possible to directly map type arguments not possible to instantiate + // with reflect like unidirectional channels. + Set(reflect.Type, reflect.Value) TypeMapper + // Returns the Value that is mapped to the current type. Returns a zeroed Value if + // the Type has not been mapped. + Get(reflect.Type) reflect.Value +} + +type injector struct { + values map[reflect.Type]reflect.Value + parent Injector +} + +// InterfaceOf dereferences a pointer to an Interface type. +// It panics if value is not an pointer to an interface. +func InterfaceOf(value interface{}) reflect.Type { + t := reflect.TypeOf(value) + + for t.Kind() == reflect.Ptr { + t = t.Elem() + } + + if t.Kind() != reflect.Interface { + panic("Called inject.InterfaceOf with a value that is not a pointer to an interface. (*MyInterface)(nil)") + } + + return t +} + +// New returns a new Injector. +func New() Injector { + return &injector{ + values: make(map[reflect.Type]reflect.Value), + } +} + +// Invoke attempts to call the interface{} provided as a function, +// providing dependencies for function arguments based on Type. +// Returns a slice of reflect.Value representing the returned values of the function. +// Returns an error if the injection fails. +// It panics if f is not a function +func (inj *injector) Invoke(f interface{}) ([]reflect.Value, error) { + t := reflect.TypeOf(f) + + var in = make([]reflect.Value, t.NumIn()) //Panic if t is not kind of Func + for i := 0; i < t.NumIn(); i++ { + argType := t.In(i) + val := inj.Get(argType) + if !val.IsValid() { + return nil, fmt.Errorf("Value not found for type %v", argType) + } + + in[i] = val + } + + return reflect.ValueOf(f).Call(in), nil +} + +// Maps dependencies in the Type map to each field in the struct +// that is tagged with 'inject'. +// Returns an error if the injection fails. +func (inj *injector) Apply(val interface{}) error { + v := reflect.ValueOf(val) + + for v.Kind() == reflect.Ptr { + v = v.Elem() + } + + if v.Kind() != reflect.Struct { + return nil // Should not panic here ? + } + + t := v.Type() + + for i := 0; i < v.NumField(); i++ { + f := v.Field(i) + structField := t.Field(i) + if f.CanSet() && (structField.Tag == "inject" || structField.Tag.Get("inject") != "") { + ft := f.Type() + v := inj.Get(ft) + if !v.IsValid() { + return fmt.Errorf("Value not found for type %v", ft) + } + + f.Set(v) + } + + } + + return nil +} + +// Maps the concrete value of val to its dynamic type using reflect.TypeOf, +// It returns the TypeMapper registered in. +func (i *injector) Map(val interface{}) TypeMapper { + i.values[reflect.TypeOf(val)] = reflect.ValueOf(val) + return i +} + +func (i *injector) MapTo(val interface{}, ifacePtr interface{}) TypeMapper { + i.values[InterfaceOf(ifacePtr)] = reflect.ValueOf(val) + return i +} + +// Maps the given reflect.Type to the given reflect.Value and returns +// the Typemapper the mapping has been registered in. +func (i *injector) Set(typ reflect.Type, val reflect.Value) TypeMapper { + i.values[typ] = val + return i +} + +func (i *injector) Get(t reflect.Type) reflect.Value { + val := i.values[t] + + if val.IsValid() { + return val + } + + // no concrete types found, try to find implementors + // if t is an interface + if t.Kind() == reflect.Interface { + for k, v := range i.values { + if k.Implements(t) { + val = v + break + } + } + } + + // Still no type found, try to look it up on the parent + if !val.IsValid() && i.parent != nil { + val = i.parent.Get(t) + } + + return val + +} + +func (i *injector) SetParent(parent Injector) { + i.parent = parent +} diff --git a/plugins/common/vendor/github.com/codegangsta/inject/inject_test.go b/plugins/common/vendor/github.com/codegangsta/inject/inject_test.go new file mode 100644 index 000000000..eb94471d3 --- /dev/null +++ b/plugins/common/vendor/github.com/codegangsta/inject/inject_test.go @@ -0,0 +1,159 @@ +package inject_test + +import ( + "fmt" + "github.com/codegangsta/inject" + "reflect" + "testing" +) + +type SpecialString interface { +} + +type TestStruct struct { + Dep1 string `inject:"t" json:"-"` + Dep2 SpecialString `inject` + Dep3 string +} + +type Greeter struct { + Name string +} + +func (g *Greeter) String() string { + return "Hello, My name is" + g.Name +} + +/* Test Helpers */ +func expect(t *testing.T, a interface{}, b interface{}) { + if a != b { + t.Errorf("Expected %v (type %v) - Got %v (type %v)", b, reflect.TypeOf(b), a, reflect.TypeOf(a)) + } +} + +func refute(t *testing.T, a interface{}, b interface{}) { + if a == b { + t.Errorf("Did not expect %v (type %v) - Got %v (type %v)", b, reflect.TypeOf(b), a, reflect.TypeOf(a)) + } +} + +func Test_InjectorInvoke(t *testing.T) { + injector := inject.New() + expect(t, injector == nil, false) + + dep := "some dependency" + injector.Map(dep) + dep2 := "another dep" + injector.MapTo(dep2, (*SpecialString)(nil)) + dep3 := make(chan *SpecialString) + dep4 := make(chan *SpecialString) + typRecv := reflect.ChanOf(reflect.RecvDir, reflect.TypeOf(dep3).Elem()) + typSend := reflect.ChanOf(reflect.SendDir, reflect.TypeOf(dep4).Elem()) + injector.Set(typRecv, reflect.ValueOf(dep3)) + injector.Set(typSend, reflect.ValueOf(dep4)) + + _, err := injector.Invoke(func(d1 string, d2 SpecialString, d3 <-chan *SpecialString, d4 chan<- *SpecialString) { + expect(t, d1, dep) + expect(t, d2, dep2) + expect(t, reflect.TypeOf(d3).Elem(), reflect.TypeOf(dep3).Elem()) + expect(t, reflect.TypeOf(d4).Elem(), reflect.TypeOf(dep4).Elem()) + expect(t, reflect.TypeOf(d3).ChanDir(), reflect.RecvDir) + expect(t, reflect.TypeOf(d4).ChanDir(), reflect.SendDir) + }) + + expect(t, err, nil) +} + +func Test_InjectorInvokeReturnValues(t *testing.T) { + injector := inject.New() + expect(t, injector == nil, false) + + dep := "some dependency" + injector.Map(dep) + dep2 := "another dep" + injector.MapTo(dep2, (*SpecialString)(nil)) + + result, err := injector.Invoke(func(d1 string, d2 SpecialString) string { + expect(t, d1, dep) + expect(t, d2, dep2) + return "Hello world" + }) + + expect(t, result[0].String(), "Hello world") + expect(t, err, nil) +} + +func Test_InjectorApply(t *testing.T) { + injector := inject.New() + + injector.Map("a dep").MapTo("another dep", (*SpecialString)(nil)) + + s := TestStruct{} + err := injector.Apply(&s) + expect(t, err, nil) + + expect(t, s.Dep1, "a dep") + expect(t, s.Dep2, "another dep") + expect(t, s.Dep3, "") +} + +func Test_InterfaceOf(t *testing.T) { + iType := inject.InterfaceOf((*SpecialString)(nil)) + expect(t, iType.Kind(), reflect.Interface) + + iType = inject.InterfaceOf((**SpecialString)(nil)) + expect(t, iType.Kind(), reflect.Interface) + + // Expecting nil + defer func() { + rec := recover() + refute(t, rec, nil) + }() + iType = inject.InterfaceOf((*testing.T)(nil)) +} + +func Test_InjectorSet(t *testing.T) { + injector := inject.New() + typ := reflect.TypeOf("string") + typSend := reflect.ChanOf(reflect.SendDir, typ) + typRecv := reflect.ChanOf(reflect.RecvDir, typ) + + // instantiating unidirectional channels is not possible using reflect + // http://golang.org/src/pkg/reflect/value.go?s=60463:60504#L2064 + chanRecv := reflect.MakeChan(reflect.ChanOf(reflect.BothDir, typ), 0) + chanSend := reflect.MakeChan(reflect.ChanOf(reflect.BothDir, typ), 0) + + injector.Set(typSend, chanSend) + injector.Set(typRecv, chanRecv) + + expect(t, injector.Get(typSend).IsValid(), true) + expect(t, injector.Get(typRecv).IsValid(), true) + expect(t, injector.Get(chanSend.Type()).IsValid(), false) +} + +func Test_InjectorGet(t *testing.T) { + injector := inject.New() + + injector.Map("some dependency") + + expect(t, injector.Get(reflect.TypeOf("string")).IsValid(), true) + expect(t, injector.Get(reflect.TypeOf(11)).IsValid(), false) +} + +func Test_InjectorSetParent(t *testing.T) { + injector := inject.New() + injector.MapTo("another dep", (*SpecialString)(nil)) + + injector2 := inject.New() + injector2.SetParent(injector) + + expect(t, injector2.Get(inject.InterfaceOf((*SpecialString)(nil))).IsValid(), true) +} + +func TestInjectImplementors(t *testing.T) { + injector := inject.New() + g := &Greeter{"Jeremy"} + injector.Map(g) + + expect(t, injector.Get(inject.InterfaceOf((*fmt.Stringer)(nil))).IsValid(), true) +} diff --git a/plugins/common/vendor/github.com/codegangsta/inject/translations/README_zh_cn.md b/plugins/common/vendor/github.com/codegangsta/inject/translations/README_zh_cn.md new file mode 100644 index 000000000..0ac3d3f55 --- /dev/null +++ b/plugins/common/vendor/github.com/codegangsta/inject/translations/README_zh_cn.md @@ -0,0 +1,85 @@ +# inject +-- + import "github.com/codegangsta/inject" + +inject包提供了多种对实体的映射和依赖注入方式。 + +## 用法 + +#### func InterfaceOf + +```go +func InterfaceOf(value interface{}) reflect.Type +``` +函数InterfaceOf返回指向接口类型的指针。如果传入的value值不是指向接口的指针,将抛出一个panic异常。 + +#### type Applicator + +```go +type Applicator interface { + // 在Type map中维持对结构体中每个域的引用并用'inject'来标记 + // 如果注入失败将会返回一个error. + Apply(interface{}) error +} +``` + +Applicator接口表示到结构体的依赖映射关系。 + +#### type Injector + +```go +type Injector interface { + Applicator + Invoker + TypeMapper + // SetParent用来设置父injector. 如果在当前injector的Type map中找不到依赖, + // 将会继续从它的父injector中找,直到返回error. + SetParent(Injector) +} +``` + +Injector接口表示对结构体、函数参数的映射和依赖注入。 + +#### func New + +```go +func New() Injector +``` +New创建并返回一个Injector. + +#### type Invoker + +```go +type Invoker interface { + // Invoke尝试将interface{}作为一个函数来调用,并基于Type为函数提供参数。 + // 它将返回reflect.Value的切片,其中存放原函数的返回值。 + // 如果注入失败则返回error. + Invoke(interface{}) ([]reflect.Value, error) +} +``` + +Invoker接口表示通过反射进行函数调用。 + +#### type TypeMapper + +```go +type TypeMapper interface { + // 基于调用reflect.TypeOf得到的类型映射interface{}的值。 + Map(interface{}) TypeMapper + // 基于提供的接口的指针映射interface{}的值。 + // 该函数仅用来将一个值映射为接口,因为接口无法不通过指针而直接引用到。 + MapTo(interface{}, interface{}) TypeMapper + // 为直接插入基于类型和值的map提供一种可能性。 + // 它使得这一类直接映射成为可能:无法通过反射直接实例化的类型参数,如单向管道。 + Set(reflect.Type, reflect.Value) TypeMapper + // 返回映射到当前类型的Value. 如果Type没被映射,将返回对应的零值。 + Get(reflect.Type) reflect.Value +} +``` + +TypeMapper接口用来表示基于类型到接口值的映射。 + + +## 译者 + +张强 (qqbunny@yeah.net) \ No newline at end of file diff --git a/plugins/common/vendor/github.com/codegangsta/inject/update_readme.sh b/plugins/common/vendor/github.com/codegangsta/inject/update_readme.sh new file mode 100644 index 000000000..497f9a577 --- /dev/null +++ b/plugins/common/vendor/github.com/codegangsta/inject/update_readme.sh @@ -0,0 +1,3 @@ +#!/bin/bash +go get github.com/robertkrimen/godocdown/godocdown +godocdown > README.md diff --git a/plugins/common/vendor/github.com/codeskyblue/go-sh/LICENSE b/plugins/common/vendor/github.com/codeskyblue/go-sh/LICENSE new file mode 100644 index 000000000..e06d20818 --- /dev/null +++ b/plugins/common/vendor/github.com/codeskyblue/go-sh/LICENSE @@ -0,0 +1,202 @@ +Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright {yyyy} {name of copyright owner} + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + diff --git a/plugins/common/vendor/github.com/codeskyblue/go-sh/OLD_README.md b/plugins/common/vendor/github.com/codeskyblue/go-sh/OLD_README.md new file mode 100644 index 000000000..7e899402d --- /dev/null +++ b/plugins/common/vendor/github.com/codeskyblue/go-sh/OLD_README.md @@ -0,0 +1,69 @@ +## OLD README +First give you a full example, I will explain every command below. + + session := sh.NewSession() + session.Env["PATH"] = "/usr/bin:/bin" + session.Stdout = os.Stdout + session.Stderr = os.Stderr + session.Alias("ll", "ls", "-l") + session.ShowCMD = true // enable for debug + var err error + err = session.Call("ll", "/") + if err != nil { + log.Fatal(err) + } + ret, err := session.Capture("pwd", sh.Dir("/home")) # wraper of session.Call + if err != nil { + log.Fatal(err) + } + # ret is "/home\n" + fmt.Println(ret) + +create a new Session + + session := sh.NewSession() + +use alias like this + + session.Alias("ll", "ls", "-l") # like alias ll='ls -l' + +set current env like this + + session.Env["BUILD_ID"] = "123" # like export BUILD_ID=123 + +set current directory + + session.Set(sh.Dir("/")) # like cd / + +pipe is also supported + + session.Command("echo", "hello\tworld").Command("cut", "-f2") + // output should be "world" + session.Run() + +test, the build in command support + + session.Test("d", "dir") // test dir + session.Test("f", "file) // test regular file + +with `Alias Env Set Call Capture Command` a shell scripts can be easily converted into golang program. below is a shell script. + + #!/bin/bash - + # + export PATH=/usr/bin:/bin + alias ll='ls -l' + cd /usr + if test -d "local" + then + ll local | awk '{print $1, $NF}' + fi + +convert to golang, will be + + s := sh.NewSession() + s.Env["PATH"] = "/usr/bin:/bin" + s.Set(sh.Dir("/usr")) + s.Alias("ll", "ls", "-l") + if s.Test("d", "local") { + s.Command("ll", "local").Command("awk", "{print $1, $NF}").Run() + } diff --git a/plugins/common/vendor/github.com/codeskyblue/go-sh/README.md b/plugins/common/vendor/github.com/codeskyblue/go-sh/README.md new file mode 100644 index 000000000..28614708d --- /dev/null +++ b/plugins/common/vendor/github.com/codeskyblue/go-sh/README.md @@ -0,0 +1,85 @@ +## go-sh +[![wercker status](https://app.wercker.com/status/009acbd4f00ccc6de7e2554e12a50d84/s "wercker status")](https://app.wercker.com/project/bykey/009acbd4f00ccc6de7e2554e12a50d84) +[![Go Walker](http://gowalker.org/api/v1/badge)](http://gowalker.org/github.com/codeskyblue/go-sh) + +*If you depend on the old api, see tag: v.0.1* + +install: `go get github.com/codeskyblue/go-sh` + +Pipe Example: + + package main + + import "github.com/codeskyblue/go-sh" + + func main() { + sh.Command("echo", "hello\tworld").Command("cut", "-f2").Run() + } + +Because I like os/exec, `go-sh` is very much modelled after it. However, `go-sh` provides a better experience. + +These are some of its features: + +* keep the variable environment (e.g. export) +* alias support (e.g. alias in shell) +* remember current dir +* pipe command +* shell build-in commands echo & test +* timeout support + +Examples are important: + + sh: echo hello + go: sh.Command("echo", "hello").Run() + + sh: export BUILD_ID=123 + go: s = sh.NewSession().SetEnv("BUILD_ID", "123") + + sh: alias ll='ls -l' + go: s = sh.NewSession().Alias('ll', 'ls', '-l') + + sh: (cd /; pwd) + go: sh.Command("pwd", sh.Dir("/")).Run() + + sh: test -d data || mkdir data + go: if ! sh.Test("dir", "data") { sh.Command("mkdir", "data").Run() } + + sh: cat first second | awk '{print $1}' + go: sh.Command("cat", "first", "second").Command("awk", "{print $1}").Run() + + sh: count=$(echo "one two three" | wc -w) + go: count, err := sh.Echo("one two three").Command("wc", "-w").Output() + + sh(in ubuntu): timeout 1s sleep 3 + go: c := sh.Command("sleep", "3"); c.Start(); c.WaitTimeout(time.Second) # default SIGKILL + go: out, err := sh.Command("sleep", "3").SetTimeout(time.Second).Output() # set session timeout and get output) + + sh: echo hello | cat + go: out, err := sh.Command("cat").SetInput("hello").Output() + + sh: cat # read from stdin + go: out, err := sh.Command("cat").SetStdin(os.Stdin).Output() + +If you need to keep env and dir, it is better to create a session + + session := sh.NewSession() + session.SetEnv("BUILD_ID", "123") + session.SetDir("/") + # then call cmd + session.Command("echo", "hello").Run() + # set ShowCMD to true for easily debug + session.ShowCMD = true + +for more information, it better to see docs. +[![Go Walker](http://gowalker.org/api/v1/badge)](http://gowalker.org/github.com/codeskyblue/go-sh) + +### contribute +If you love this project, starring it will encourage the coder. Pull requests are welcome. + +support the author: [alipay](https://me.alipay.com/goskyblue) + +### thanks +this project is based on . thanks for the author. + +# the reason to use Go shell +Sometimes we need to write shell scripts, but shell scripts are not good at working cross platform, Go, on the other hand, is good at that. Is there a good way to use Go to write shell like scripts? Using go-sh we can do this now. diff --git a/plugins/common/vendor/github.com/codeskyblue/go-sh/example/example1.go b/plugins/common/vendor/github.com/codeskyblue/go-sh/example/example1.go new file mode 100644 index 000000000..cdc7f6c39 --- /dev/null +++ b/plugins/common/vendor/github.com/codeskyblue/go-sh/example/example1.go @@ -0,0 +1,41 @@ +package main + +import ( + "fmt" + "log" + + "github.com/codeskyblue/go-sh" +) + +func main() { + sh.Command("echo", "hello").Run() + out, err := sh.Command("echo", "hello").Output() + if err != nil { + log.Fatal(err) + } + fmt.Println("output is", string(out)) + + var a int + sh.Command("echo", "2").UnmarshalJSON(&a) + fmt.Println("a =", a) + + s := sh.NewSession() + s.Alias("hi", "echo", "hi") + s.Command("hi", "boy").Run() + + fmt.Print("pwd = ") + s.Command("pwd", sh.Dir("/")).Run() + + if !sh.Test("dir", "data") { + sh.Command("echo", "mkdir", "data").Run() + } + + sh.Command("echo", "hello", "world"). + Command("awk", `{print "second arg is "$2}`).Run() + s.ShowCMD = true + s.Command("echo", "hello", "world"). + Command("awk", `{print "second arg is "$2}`).Run() + + s.SetEnv("BUILD_ID", "123").Command("bash", "-c", "echo $BUILD_ID").Run() + s.Command("bash", "-c", "echo current shell is $SHELL").Run() +} diff --git a/plugins/common/vendor/github.com/codeskyblue/go-sh/example/less/less.go b/plugins/common/vendor/github.com/codeskyblue/go-sh/example/less/less.go new file mode 100644 index 000000000..ffced2e81 --- /dev/null +++ b/plugins/common/vendor/github.com/codeskyblue/go-sh/example/less/less.go @@ -0,0 +1,7 @@ +package main + +import "github.com/codeskyblue/go-sh" + +func main() { + sh.Command("less", "less.go").Run() +} diff --git a/plugins/common/vendor/github.com/codeskyblue/go-sh/example/tail/tailf.go b/plugins/common/vendor/github.com/codeskyblue/go-sh/example/tail/tailf.go new file mode 100644 index 000000000..b7ecd3bed --- /dev/null +++ b/plugins/common/vendor/github.com/codeskyblue/go-sh/example/tail/tailf.go @@ -0,0 +1,17 @@ +package main + +import ( + "flag" + "fmt" + + "github.com/codeskyblue/go-sh" +) + +func main() { + flag.Parse() + if flag.NArg() != 1 { + fmt.Println("Usage: PROGRAM ") + return + } + sh.Command("tail", "-f", flag.Arg(0)).Run() +} diff --git a/plugins/common/vendor/github.com/codeskyblue/go-sh/example/timeout/timeout.go b/plugins/common/vendor/github.com/codeskyblue/go-sh/example/timeout/timeout.go new file mode 100644 index 000000000..852908a47 --- /dev/null +++ b/plugins/common/vendor/github.com/codeskyblue/go-sh/example/timeout/timeout.go @@ -0,0 +1,23 @@ +package main + +import ( + "fmt" + "time" + + sh "github.com/codeskyblue/go-sh" +) + +func main() { + c := sh.Command("sleep", "3") + c.Start() + err := c.WaitTimeout(time.Second * 1) + if err != nil { + fmt.Printf("timeout should happend: %v\n", err) + } + // timeout should be a session + out, err := sh.Command("sleep", "2").SetTimeout(time.Second).Output() + fmt.Printf("output:(%s), err(%v)\n", string(out), err) + + out, err = sh.Command("echo", "hello").SetTimeout(time.Second).Output() + fmt.Printf("output:(%s), err(%v)\n", string(out), err) +} diff --git a/plugins/common/vendor/github.com/codeskyblue/go-sh/example_test.go b/plugins/common/vendor/github.com/codeskyblue/go-sh/example_test.go new file mode 100644 index 000000000..5abfed64d --- /dev/null +++ b/plugins/common/vendor/github.com/codeskyblue/go-sh/example_test.go @@ -0,0 +1,28 @@ +package sh_test + +import ( + "fmt" + + "github.com/codeskyblue/go-sh" +) + +func ExampleCommand() { + out, err := sh.Command("echo", "hello").Output() + fmt.Println(string(out), err) +} + +func ExampleCommandPipe() { + out, err := sh.Command("echo", "-n", "hi").Command("wc", "-c").Output() + fmt.Println(string(out), err) +} + +func ExampleCommandSetDir() { + out, err := sh.Command("pwd", sh.Dir("/")).Output() + fmt.Println(string(out), err) +} + +func ExampleTest() { + if sh.Test("dir", "mydir") { + fmt.Println("mydir exists") + } +} diff --git a/plugins/common/vendor/github.com/codeskyblue/go-sh/pipe.go b/plugins/common/vendor/github.com/codeskyblue/go-sh/pipe.go new file mode 100644 index 000000000..c35f4e2a0 --- /dev/null +++ b/plugins/common/vendor/github.com/codeskyblue/go-sh/pipe.go @@ -0,0 +1,148 @@ +package sh + +import ( + "bytes" + "encoding/json" + "encoding/xml" + "errors" + "io" + "os" + "strings" + "syscall" + "time" +) + +var ErrExecTimeout = errors.New("execute timeout") + +// unmarshal shell output to decode json +func (s *Session) UnmarshalJSON(data interface{}) (err error) { + bufrw := bytes.NewBuffer(nil) + s.Stdout = bufrw + if err = s.Run(); err != nil { + return + } + return json.NewDecoder(bufrw).Decode(data) +} + +// unmarshal command output into xml +func (s *Session) UnmarshalXML(data interface{}) (err error) { + bufrw := bytes.NewBuffer(nil) + s.Stdout = bufrw + if err = s.Run(); err != nil { + return + } + return xml.NewDecoder(bufrw).Decode(data) +} + +// start command +func (s *Session) Start() (err error) { + s.started = true + var rd *io.PipeReader + var wr *io.PipeWriter + var length = len(s.cmds) + if s.ShowCMD { + var cmds = make([]string, 0, 4) + for _, cmd := range s.cmds { + cmds = append(cmds, strings.Join(cmd.Args, " ")) + } + s.writePrompt(strings.Join(cmds, " | ")) + } + for index, cmd := range s.cmds { + if index == 0 { + cmd.Stdin = s.Stdin + } else { + cmd.Stdin = rd + } + if index != length { + rd, wr = io.Pipe() // create pipe + cmd.Stdout = wr + cmd.Stderr = os.Stderr + } + if index == length-1 { + cmd.Stdout = s.Stdout + cmd.Stderr = s.Stderr + } + err = cmd.Start() + if err != nil { + return + } + } + return +} + +// Should be call after Start() +// only catch the last command error +func (s *Session) Wait() (err error) { + for _, cmd := range s.cmds { + err = cmd.Wait() + wr, ok := cmd.Stdout.(*io.PipeWriter) + if ok { + wr.Close() + } + } + return err +} + +func (s *Session) Kill(sig os.Signal) { + for _, cmd := range s.cmds { + if cmd.Process != nil { + cmd.Process.Signal(sig) + } + } +} + +func (s *Session) WaitTimeout(timeout time.Duration) (err error) { + select { + case <-time.After(timeout): + s.Kill(syscall.SIGKILL) + return ErrExecTimeout + case err = <-Go(s.Wait): + return err + } +} + +func Go(f func() error) chan error { + ch := make(chan error) + go func() { + ch <- f() + }() + return ch +} + +func (s *Session) Run() (err error) { + if err = s.Start(); err != nil { + return + } + if s.timeout != time.Duration(0) { + return s.WaitTimeout(s.timeout) + } + return s.Wait() +} + +func (s *Session) Output() (out []byte, err error) { + oldout := s.Stdout + defer func() { + s.Stdout = oldout + }() + stdout := bytes.NewBuffer(nil) + s.Stdout = stdout + err = s.Run() + out = stdout.Bytes() + return +} + +func (s *Session) CombinedOutput() (out []byte, err error) { + oldout := s.Stdout + olderr := s.Stderr + defer func() { + s.Stdout = oldout + s.Stderr = olderr + }() + stdout := bytes.NewBuffer(nil) + s.Stdout = stdout + s.Stderr = stdout + + err = s.Run() + out = stdout.Bytes() + return +} diff --git a/plugins/common/vendor/github.com/codeskyblue/go-sh/pipe_test.go b/plugins/common/vendor/github.com/codeskyblue/go-sh/pipe_test.go new file mode 100644 index 000000000..8d0e22c0c --- /dev/null +++ b/plugins/common/vendor/github.com/codeskyblue/go-sh/pipe_test.go @@ -0,0 +1,126 @@ +package sh + +import ( + "encoding/xml" + "io" + "os" + "os/exec" + "strings" + "testing" + "time" +) + +func TestUnmarshalJSON(t *testing.T) { + var a int + s := NewSession() + s.ShowCMD = true + err := s.Command("echo", []string{"1"}).UnmarshalJSON(&a) + if err != nil { + t.Error(err) + } + if a != 1 { + t.Errorf("expect a tobe 1, but got %d", a) + } +} + +func TestUnmarshalXML(t *testing.T) { + s := NewSession() + xmlSample := ` +` + type server struct { + XMLName xml.Name `xml:"server"` + Version string `xml:"version,attr"` + } + data := &server{} + s.Command("echo", xmlSample).UnmarshalXML(data) + if data.Version != "1" { + t.Error(data) + } +} + +func TestPipe(t *testing.T) { + s := NewSession() + s.ShowCMD = true + s.Call("echo", "hello") + err := s.Command("echo", "hi").Command("cat", "-n").Start() + if err != nil { + t.Error(err) + } + err = s.Wait() + if err != nil { + t.Error(err) + } + out, err := s.Command("echo", []string{"hello"}).Output() + if err != nil { + t.Error(err) + } + if string(out) != "hello\n" { + t.Error("capture wrong output:", out) + } + s.Command("echo", []string{"hello\tworld"}).Command("cut", []string{"-f2"}).Run() +} + +func TestPipeCommand(t *testing.T) { + c1 := exec.Command("echo", "good") + rd, wr := io.Pipe() + c1.Stdout = wr + c2 := exec.Command("cat", "-n") + c2.Stdout = os.Stdout + c2.Stdin = rd + c1.Start() + c2.Start() + + c1.Wait() + wc, ok := c1.Stdout.(io.WriteCloser) + if ok { + wc.Close() + } + c2.Wait() +} + +func TestPipeInput(t *testing.T) { + s := NewSession() + s.ShowCMD = true + s.SetInput("first line\nsecond line\n") + out, err := s.Command("grep", "second").Output() + if err != nil { + t.Error(err) + } + if string(out) != "second line\n" { + t.Error("capture wrong output:", out) + } +} + +func TestTimeout(t *testing.T) { + s := NewSession() + err := s.Command("sleep", "2").Start() + if err != nil { + t.Fatal(err) + } + err = s.WaitTimeout(time.Second) + if err != ErrExecTimeout { + t.Fatal(err) + } +} + +func TestSetTimeout(t *testing.T) { + s := NewSession() + s.SetTimeout(time.Second) + defer s.SetTimeout(0) + err := s.Command("sleep", "2").Run() + if err != ErrExecTimeout { + t.Fatal(err) + } +} + +func TestCombinedOutput(t *testing.T) { + s := NewSession() + bytes, err := s.Command("sh", "-c", "echo stderr >&2 ; echo stdout").CombinedOutput() + if err != nil { + t.Error(err) + } + stringOutput := string(bytes) + if !(strings.Contains(stringOutput, "stdout") && strings.Contains(stringOutput, "stderr")) { + t.Errorf("expect output from both output streams, got '%s'", strings.TrimSpace(stringOutput)) + } +} diff --git a/plugins/common/vendor/github.com/codeskyblue/go-sh/sh.go b/plugins/common/vendor/github.com/codeskyblue/go-sh/sh.go new file mode 100644 index 000000000..15f1b85d5 --- /dev/null +++ b/plugins/common/vendor/github.com/codeskyblue/go-sh/sh.go @@ -0,0 +1,200 @@ +/* +Package go-sh is intented to make shell call with golang more easily. +Some usage is more similar to os/exec, eg: Run(), Output(), Command(name, args...) + +But with these similar function, pipe is added in and this package also got shell-session support. + +Why I love golang so much, because the usage of golang is simple, but the power is unlimited. I want to make this pakcage got the sample style like golang. + + // just like os/exec + sh.Command("echo", "hello").Run() + + // support pipe + sh.Command("echo", "hello").Command("wc", "-c").Run() + + // create a session to store dir and env + sh.NewSession().SetDir("/").Command("pwd") + + // shell buildin command - "test" + sh.Test("dir", "mydir") + + // like shell call: (cd /; pwd) + sh.Command("pwd", sh.Dir("/")) same with sh.Command(sh.Dir("/"), "pwd") + + // output to json and xml easily + v := map[string] int {} + err = sh.Command("echo", `{"number": 1}`).UnmarshalJSON(&v) +*/ +package sh + +import ( + "fmt" + "io" + "os" + "os/exec" + "reflect" + "strings" + "time" + + "github.com/codegangsta/inject" +) + +type Dir string + +type Session struct { + inj inject.Injector + alias map[string][]string + cmds []*exec.Cmd + dir Dir + started bool + Env map[string]string + Stdin io.Reader + Stdout io.Writer + Stderr io.Writer + ShowCMD bool // enable for debug + timeout time.Duration +} + +func (s *Session) writePrompt(args ...interface{}) { + var ps1 = fmt.Sprintf("[golang-sh]$") + args = append([]interface{}{ps1}, args...) + fmt.Fprintln(s.Stderr, args...) +} + +func NewSession() *Session { + env := make(map[string]string) + for _, key := range []string{"PATH"} { + env[key] = os.Getenv(key) + } + s := &Session{ + inj: inject.New(), + alias: make(map[string][]string), + dir: Dir(""), + Stdin: strings.NewReader(""), + Stdout: os.Stdout, + Stderr: os.Stderr, + Env: env, + } + return s +} + +func InteractiveSession() *Session { + s := NewSession() + s.SetStdin(os.Stdin) + return s +} + +func Command(name string, a ...interface{}) *Session { + s := NewSession() + return s.Command(name, a...) +} + +func Echo(in string) *Session { + s := NewSession() + return s.SetInput(in) +} + +func (s *Session) Alias(alias, cmd string, args ...string) { + v := []string{cmd} + v = append(v, args...) + s.alias[alias] = v +} + +func (s *Session) Command(name string, a ...interface{}) *Session { + var args = make([]string, 0) + var sType = reflect.TypeOf("") + + // init cmd, args, dir, envs + // if not init, program may panic + s.inj.Map(name).Map(args).Map(s.dir).Map(map[string]string{}) + for _, v := range a { + switch reflect.TypeOf(v) { + case sType: + args = append(args, v.(string)) + default: + s.inj.Map(v) + } + } + if len(args) != 0 { + s.inj.Map(args) + } + s.inj.Invoke(s.appendCmd) + return s +} + +// combine Command and Run +func (s *Session) Call(name string, a ...interface{}) error { + return s.Command(name, a...).Run() +} + +/* +func (s *Session) Exec(cmd string, args ...string) error { + return s.Call(cmd, args) +} +*/ + +func (s *Session) SetEnv(key, value string) *Session { + s.Env[key] = value + return s +} + +func (s *Session) SetDir(dir string) *Session { + s.dir = Dir(dir) + return s +} + +func (s *Session) SetInput(in string) *Session { + s.Stdin = strings.NewReader(in) + return s +} + +func (s *Session) SetStdin(r io.Reader) *Session { + s.Stdin = r + return s +} + +func (s *Session) SetTimeout(d time.Duration) *Session { + s.timeout = d + return s +} + +func newEnviron(env map[string]string, inherit bool) []string { //map[string]string { + environ := make([]string, 0, len(env)) + if inherit { + for _, line := range os.Environ() { + for k, _ := range env { + if strings.HasPrefix(line, k+"=") { + goto CONTINUE + } + } + environ = append(environ, line) + CONTINUE: + } + } + for k, v := range env { + environ = append(environ, k+"="+v) + } + return environ +} + +func (s *Session) appendCmd(cmd string, args []string, cwd Dir, env map[string]string) { + if s.started { + s.started = false + s.cmds = make([]*exec.Cmd, 0) + } + for k, v := range s.Env { + if _, ok := env[k]; !ok { + env[k] = v + } + } + environ := newEnviron(s.Env, true) // true: inherit sys-env + v, ok := s.alias[cmd] + if ok { + cmd = v[0] + args = append(v[1:], args...) + } + c := exec.Command(cmd, args...) + c.Env = environ + c.Dir = string(cwd) + s.cmds = append(s.cmds, c) +} diff --git a/plugins/common/vendor/github.com/codeskyblue/go-sh/sh_test.go b/plugins/common/vendor/github.com/codeskyblue/go-sh/sh_test.go new file mode 100644 index 000000000..be5c27998 --- /dev/null +++ b/plugins/common/vendor/github.com/codeskyblue/go-sh/sh_test.go @@ -0,0 +1,106 @@ +package sh + +import ( + "fmt" + "log" + "runtime" + "strings" + "testing" +) + +func TestAlias(t *testing.T) { + s := NewSession() + s.Alias("gr", "echo", "hi") + out, err := s.Command("gr", "sky").Output() + if err != nil { + t.Error(err) + } + if string(out) != "hi sky\n" { + t.Errorf("expect 'hi sky' but got:%s", string(out)) + } +} + +func ExampleSession_Command() { + s := NewSession() + out, err := s.Command("echo", "hello").Output() + if err != nil { + log.Fatal(err) + } + fmt.Println(string(out)) + // Output: hello +} + +func ExampleSession_Command_pipe() { + s := NewSession() + out, err := s.Command("echo", "hello", "world").Command("awk", "{print $2}").Output() + if err != nil { + log.Fatal(err) + } + fmt.Println(string(out)) + // Output: world +} + +func ExampleSession_Alias() { + s := NewSession() + s.Alias("alias_echo_hello", "echo", "hello") + out, err := s.Command("alias_echo_hello", "world").Output() + if err != nil { + log.Fatal(err) + } + fmt.Println(string(out)) + // Output: hello world +} + +func TestEcho(t *testing.T) { + out, err := Echo("one two three").Command("wc", "-w").Output() + if err != nil { + t.Error(err) + } + if strings.TrimSpace(string(out)) != "3" { + t.Errorf("expect '3' but got:%s", string(out)) + } +} + +func TestSession(t *testing.T) { + if runtime.GOOS == "windows" { + t.Log("ignore test on windows") + return + } + session := NewSession() + session.ShowCMD = true + err := session.Call("pwd") + if err != nil { + t.Error(err) + } + out, err := session.SetDir("/").Command("pwd").Output() + if err != nil { + t.Error(err) + } + if string(out) != "/\n" { + t.Errorf("expect /, but got %s", string(out)) + } +} + +/* + #!/bin/bash - + # + export PATH=/usr/bin:/bin + alias ll='ls -l' + cd /usr + if test -d "local" + then + ll local | awk '{print $1, $NF}' | grep bin + fi +*/ +func Example(t *testing.T) { + s := NewSession() + //s.ShowCMD = true + s.Env["PATH"] = "/usr/bin:/bin" + s.SetDir("/bin") + s.Alias("ll", "ls", "-l") + + if s.Test("d", "local") { + //s.Command("ll", []string{"local"}).Command("awk", []string{"{print $1, $NF}"}).Command("grep", []string{"bin"}).Run() + s.Command("ll", "local").Command("awk", "{print $1, $NF}").Command("grep", "bin").Run() + } +} diff --git a/plugins/common/vendor/github.com/codeskyblue/go-sh/test.go b/plugins/common/vendor/github.com/codeskyblue/go-sh/test.go new file mode 100644 index 000000000..b9d310527 --- /dev/null +++ b/plugins/common/vendor/github.com/codeskyblue/go-sh/test.go @@ -0,0 +1,64 @@ +package sh + +import ( + "os" + "path/filepath" +) + +func filetest(name string, modemask os.FileMode) (match bool, err error) { + fi, err := os.Stat(name) + if err != nil { + return + } + match = (fi.Mode() & modemask) == modemask + return +} + +func (s *Session) pwd() string { + dir := string(s.dir) + if dir == "" { + dir, _ = os.Getwd() + } + return dir +} + +func (s *Session) abspath(name string) string { + if filepath.IsAbs(name) { + return name + } + return filepath.Join(s.pwd(), name) +} + +func init() { + //log.SetFlags(log.Lshortfile | log.LstdFlags) +} + +// expression can be dir, file, link +func (s *Session) Test(expression string, argument string) bool { + var err error + var fi os.FileInfo + fi, err = os.Lstat(s.abspath(argument)) + switch expression { + case "d", "dir": + return err == nil && fi.IsDir() + case "f", "file": + return err == nil && fi.Mode().IsRegular() + case "x", "executable": + /* + fmt.Println(expression, argument) + if err == nil { + fmt.Println(fi.Mode()) + } + */ + return err == nil && fi.Mode()&os.FileMode(0100) != 0 + case "L", "link": + return err == nil && fi.Mode()&os.ModeSymlink != 0 + } + return false +} + +// expression can be d,dir, f,file, link +func Test(exp string, arg string) bool { + s := NewSession() + return s.Test(exp, arg) +} diff --git a/plugins/common/vendor/github.com/codeskyblue/go-sh/test_test.go b/plugins/common/vendor/github.com/codeskyblue/go-sh/test_test.go new file mode 100644 index 000000000..15225aa9a --- /dev/null +++ b/plugins/common/vendor/github.com/codeskyblue/go-sh/test_test.go @@ -0,0 +1,58 @@ +package sh_test + +import ( + "testing" + + "github.com/codeskyblue/go-sh" +) + +var s = sh.NewSession() + +type T struct{ *testing.T } + +func NewT(t *testing.T) *T { + return &T{t} +} + +func (t *T) checkTest(exp string, arg string, result bool) { + r := s.Test(exp, arg) + if r != result { + t.Errorf("test -%s %s, %v != %v", exp, arg, r, result) + } +} + +func TestTest(i *testing.T) { + t := NewT(i) + t.checkTest("d", "../go-sh", true) + t.checkTest("d", "./yymm", false) + + // file test + t.checkTest("f", "testdata/hello.txt", true) + t.checkTest("f", "testdata/xxxxx", false) + t.checkTest("f", "testdata/yymm", false) + + // link test + t.checkTest("link", "testdata/linkfile", true) + t.checkTest("link", "testdata/xxxxxlinkfile", false) + t.checkTest("link", "testdata/hello.txt", false) + + // executable test + t.checkTest("x", "testdata/executable", true) + t.checkTest("x", "testdata/xxxxx", false) + t.checkTest("x", "testdata/hello.txt", false) +} + +func ExampleShellTest(t *testing.T) { + // test -L + sh.Test("link", "testdata/linkfile") + sh.Test("L", "testdata/linkfile") + // test -f + sh.Test("file", "testdata/file") + sh.Test("f", "testdata/file") + // test -x + sh.Test("executable", "testdata/binfile") + sh.Test("x", "testdata/binfile") + // test -d + sh.Test("dir", "testdata/dir") + sh.Test("d", "testdata/dir") +} diff --git a/plugins/common/vendor/github.com/codeskyblue/go-sh/testdata/executable b/plugins/common/vendor/github.com/codeskyblue/go-sh/testdata/executable new file mode 100755 index 000000000..e69de29bb diff --git a/plugins/common/vendor/github.com/codeskyblue/go-sh/testdata/hello.txt b/plugins/common/vendor/github.com/codeskyblue/go-sh/testdata/hello.txt new file mode 100644 index 000000000..e69de29bb diff --git a/plugins/common/vendor/github.com/codeskyblue/go-sh/testdata/linkfile b/plugins/common/vendor/github.com/codeskyblue/go-sh/testdata/linkfile new file mode 120000 index 000000000..a5162f80d --- /dev/null +++ b/plugins/common/vendor/github.com/codeskyblue/go-sh/testdata/linkfile @@ -0,0 +1 @@ +hello.txt \ No newline at end of file diff --git a/plugins/common/vendor/github.com/codeskyblue/go-sh/wercker.yml b/plugins/common/vendor/github.com/codeskyblue/go-sh/wercker.yml new file mode 100644 index 000000000..72c47020f --- /dev/null +++ b/plugins/common/vendor/github.com/codeskyblue/go-sh/wercker.yml @@ -0,0 +1,28 @@ +box: wercker/golang +# Build definition +build: + # The steps that will be executed on build + steps: + # Sets the go workspace and places you package + # at the right place in the workspace tree + - setup-go-workspace + + # Gets the dependencies + - script: + name: go get + code: | + cd $WERCKER_SOURCE_DIR + go version + go get -t . + + # Build the project + - script: + name: go build + code: | + go build . + + # Test the project + - script: + name: go test + code: | + go test -v ./... diff --git a/plugins/config/functions b/plugins/config/functions index c97ff7c57..908f6a7af 100755 --- a/plugins/config/functions +++ b/plugins/config/functions @@ -150,7 +150,7 @@ config_get() { config_create "$ENV_FILE" if [[ ! -s $ENV_FILE ]]; then - return 0 + return 1 fi local KEY="${*: -1}" diff --git a/plugins/config/plugin.toml b/plugins/config/plugin.toml index 4f87dd4ea..46139556c 100644 --- a/plugins/config/plugin.toml +++ b/plugins/config/plugin.toml @@ -1,4 +1,4 @@ [plugin] description = "dokku core config plugin" -version = "0.7.2" +version = "0.9.4" [plugin.config] diff --git a/plugins/docker-options/commands b/plugins/docker-options/commands index f3fd00ccc..da85da9c8 100755 --- a/plugins/docker-options/commands +++ b/plugins/docker-options/commands @@ -7,9 +7,10 @@ case "$1" in help_content_func () { declare desc="return docker-options plugin help content" cat< [phase(s)], Display app's Docker options for all phases (or comma separated phase list) + docker-options [phase(s)], [DEPRECATED] Alternative for docker-options:report docker-options:add OPTION, Add Docker option to app for phase (comma separated phase list) docker-options:remove OPTION, Remove Docker option from app for phase (comma separated phase list) + docker-options:report [] [], Displays a docker options report for one or more apps help_content } diff --git a/plugins/docker-options/functions b/plugins/docker-options/functions index d49ba305d..fe95dbf0c 100755 --- a/plugins/docker-options/functions +++ b/plugins/docker-options/functions @@ -4,12 +4,19 @@ source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" AVAILABLE_PHASES=(build deploy run) +fn-get-phase-file-path() { + declare desc="return docker-options config file path for specified phase" + local APP="$1" PHASE="$2" + local PHASE_FILE="${DOKKU_ROOT}/${APP}/DOCKER_OPTIONS_${PHASE^^}" + echo "$PHASE_FILE" +} + get_phase_file_path() { declare desc="return docker-options config file path for specified phase" local phase_file_prefix="DOCKER_OPTIONS_" local phase=$1 - [[ "$DOKKU_ROOT" && "$APP" && "$phase_file_prefix" && "$phase" ]] || dokku_log_fail "Error: phase_file_path is incomplete." - echo "${DOKKU_ROOT}/${APP}/${phase_file_prefix}${phase^^}" + [[ "$APP" && "$phase" ]] || dokku_log_fail "Error: phase_file_path is incomplete." + fn-get-phase-file-path "$APP" "$phase" } get_phases() { diff --git a/plugins/docker-options/plugin.toml b/plugins/docker-options/plugin.toml index a802b0fc4..2373b0f26 100644 --- a/plugins/docker-options/plugin.toml +++ b/plugins/docker-options/plugin.toml @@ -1,4 +1,4 @@ [plugin] description = "dokku core docker-options plugin" -version = "0.7.2" +version = "0.9.4" [plugin.config] diff --git a/plugins/docker-options/subcommands/add b/plugins/docker-options/subcommands/add index b66576539..8a5242243 100755 --- a/plugins/docker-options/subcommands/add +++ b/plugins/docker-options/subcommands/add @@ -1,7 +1,7 @@ #!/usr/bin/env bash set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" -source "$PLUGIN_CORE_AVAILABLE_PATH/docker-options/functions" +source "$PLUGIN_AVAILABLE_PATH/docker-options/functions" docker_options_add_cmd() { declare desc="Add a docker option to application" diff --git a/plugins/docker-options/subcommands/default b/plugins/docker-options/subcommands/default index 25d4580d5..4cfaa6114 100755 --- a/plugins/docker-options/subcommands/default +++ b/plugins/docker-options/subcommands/default @@ -1,11 +1,12 @@ #!/usr/bin/env bash set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" -source "$PLUGIN_CORE_AVAILABLE_PATH/docker-options/functions" +source "$PLUGIN_AVAILABLE_PATH/docker-options/functions" docker_options_main_cmd() { declare desc="Display applications docker options" local cmd="docker-options" + dokku_log_warn "Deprecated: Please use docker-options:report" verify_app_name "$2" && local APP="$2" read -ra passed_phases <<< "$(get_phases "$3")" diff --git a/plugins/docker-options/subcommands/remove b/plugins/docker-options/subcommands/remove index f232083ef..8677039ed 100755 --- a/plugins/docker-options/subcommands/remove +++ b/plugins/docker-options/subcommands/remove @@ -1,7 +1,7 @@ #!/usr/bin/env bash set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" -source "$PLUGIN_CORE_AVAILABLE_PATH/docker-options/functions" +source "$PLUGIN_AVAILABLE_PATH/docker-options/functions" docker_options_remove_cmd() { declare desc="Remove a docker option from application" @@ -10,6 +10,7 @@ docker_options_remove_cmd() { verify_app_name "$2" && local APP="$2" read -ra passed_phases <<< "$(get_phases "$3")" shift 3 # everything else passed is the docker option + # shellcheck disable=SC2154 [[ -z ${passed_docker_option="$@"} ]] && dokku_log_fail "Please specify docker options to add to the phase" remove_passed_docker_option passed_phases[@] "${passed_docker_option[@]}" } diff --git a/plugins/docker-options/subcommands/report b/plugins/docker-options/subcommands/report new file mode 100755 index 000000000..e13618364 --- /dev/null +++ b/plugins/docker-options/subcommands/report @@ -0,0 +1,79 @@ +#!/usr/bin/env bash +set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x +source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" +source "$PLUGIN_AVAILABLE_PATH/docker-options/functions" + +fn-docker-options() { + declare APP="$1" PHASE="$2" + local PHASE_FILE="$(fn-get-phase-file-path "$APP" "$PHASE")" + if [[ -r "$PHASE_FILE" ]]; then + tr '\n' ' ' < "$PHASE_FILE" + fi +} + +report_single_app() { + declare APP="$1" INFO_FLAG="$2" + local APP_DIR="$DOKKU_ROOT/$APP" + if [[ "$INFO_FLAG" == "true" ]]; then + INFO_FLAG="" + fi + verify_app_name "$APP" + local flag_map=( + "--docker-options-build: $(fn-docker-options "$APP" build)" + "--docker-options-deploy: $(fn-docker-options "$APP" deploy)" + "--docker-options-run: $(fn-docker-options "$APP" run)" + ) + + if [[ -z "$INFO_FLAG" ]]; then + dokku_log_info2 "$APP docker options information" + for flag in "${flag_map[@]}"; do + key="$(echo "${flag#--}" | cut -f1 -d' ' | tr - ' ')" + dokku_log_verbose "$(printf "%-20s %-25s" "${key^}" "${flag#*: }")" + done + else + local match=false; local value_exists=false + for flag in "${flag_map[@]}"; do + valid_flags="${valid_flags} $(echo "$flag" | cut -d':' -f1)" + if [[ "$flag" == "${INFO_FLAG}:"* ]]; then + value=${flag#*: } + size="${#value}" + if [[ "$size" -ne 0 ]]; then + echo "$value" && match=true && value_exists=true + else + match=true + fi + fi + done + if [[ "$match" == "true" ]]; then + [[ "$value_exists" == "true" ]] || dokku_log_fail "not deployed" + else + dokku_log_fail "Invalid flag passed, valid flags:${valid_flags}" + fi + fi +} + +docker_options_report_cmd() { + declare desc="displays a docker options report for one or more apps" + local cmd="docker-options:report" + local INSTALLED_APPS=$(dokku_apps) + local APP="$2" INFO_FLAG="$3" + + if [[ -n "$APP" ]] && [[ "$APP" == --* ]]; then + INFO_FLAG="$APP" + APP="" + fi + + if [[ -z "$APP" ]] && [[ -z "$INFO_FLAG" ]]; then + INFO_FLAG="true" + fi + + if [[ -z "$APP" ]]; then + for app in $INSTALLED_APPS; do + report_single_app "$app" "$INFO_FLAG" | tee || true + done + else + report_single_app "$APP" "$INFO_FLAG" + fi +} + +docker_options_report_cmd "$@" diff --git a/plugins/domains/commands b/plugins/domains/commands index 7504678f7..1ed2fde7f 100755 --- a/plugins/domains/commands +++ b/plugins/domains/commands @@ -1,33 +1,11 @@ #!/usr/bin/env bash [[ " help domains:help " == *" $1 "* ]] || exit "$DOKKU_NOT_IMPLEMENTED_EXIT" +source "$PLUGIN_AVAILABLE_PATH/domains/internal-functions" set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x case "$1" in help | domains:help) - help_content_func () { - declare desc="return domains plugin help content" - cat<], List domains - domains:add [ ...], Add domains to app - domains:add-global [ ...], Add global domain names - domains:clear , Clear all domains for app - domains:disable , Disable VHOST support - domains:enable , Enable VHOST support - domains:remove [ ...], Remove domains from app - domains:remove-global [ ...], Remove global domain names -help_content - } - - if [[ $1 = "domains:help" ]] ; then - echo -e 'Usage: dokku domains[:COMMAND]' - echo '' - echo 'Manage vhost domains used by the Dokku proxy.' - echo '' - echo 'Additional commands:' - help_content_func | sort | column -c2 -t -s, - else - help_content_func - fi + domains_help_cmd "$@" ;; *) diff --git a/plugins/domains/functions b/plugins/domains/functions index 2cc3a78a4..a4171dbd0 100755 --- a/plugins/domains/functions +++ b/plugins/domains/functions @@ -85,7 +85,7 @@ domains_add() { fi done - if [[ "$(is_app_vhost_enabled "$APP")" == "false" ]];then + if [[ "$(is_app_vhost_enabled "$APP")" == "false" ]]; then domains_enable "$APP" --no-restart fi @@ -98,6 +98,7 @@ domains_remove() { local APP="$1"; local APP_VHOST_PATH="$DOKKU_ROOT/$APP/VHOST" shift 1 + touch "$APP_VHOST_PATH" for DOMAIN in "$@"; do sed -i "\|^$DOMAIN\$|d" "$APP_VHOST_PATH" dokku_log_info1 "Removed $DOMAIN from $APP" @@ -105,12 +106,36 @@ domains_remove() { plugn trigger post-domains-update "$APP" "remove" "$@" } +domains_set() { + declare desc="set list of domains for app" + verify_app_name "$1" + local APP="$1"; local APP_VHOST_PATH="$DOKKU_ROOT/$APP/VHOST" + + shift 1 + touch "$APP_VHOST_PATH" + for DOMAIN in "$@"; do + if ! (is_valid_hostname "$DOMAIN"); then + dokku_log_fail "$DOMAIN is invalid. exiting..." + fi + done + + echo "$*" | tr " " "\n" > "$APP_VHOST_PATH" + dokku_log_info1 "Set $* for $APP" + + if [[ "$(is_app_vhost_enabled "$APP")" == "false" ]]; then + domains_enable "$APP" --no-restart + fi + + plugn trigger post-domains-update "$APP" "set" "$@" +} + domains_disable() { declare 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 + touch "$APP_VHOST_PATH" + 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)" @@ -123,7 +148,8 @@ domains_enable() { local APP="$1"; local APP_VHOST_PATH="$DOKKU_ROOT/$APP/VHOST" local DEFAULT_VHOSTS="$(get_default_vhosts "$APP")" - if [[ "$(is_app_vhost_enabled "$APP")" == "false" ]];then + touch "$APP_VHOST_PATH" + if [[ "$(is_app_vhost_enabled "$APP")" == "false" ]]; then if [[ -n "$DEFAULT_VHOSTS" ]]; then echo "$DEFAULT_VHOSTS" > "$APP_VHOST_PATH" fi @@ -138,6 +164,7 @@ domains_add_global() { declare desc="add list of global domains" local GLOBAL_VHOST_PATH="$DOKKU_ROOT/VHOST" + touch "$GLOBAL_VHOST_PATH" for DOMAIN in "$@"; do if ! (is_valid_hostname "$DOMAIN"); then dokku_log_fail "$DOMAIN is invalid. exiting..." @@ -148,7 +175,7 @@ domains_add_global() { if [[ $(egrep -w "^$DOMAIN$" "$GLOBAL_VHOST_PATH" > /dev/null 2>&1; echo $?) -eq 0 ]]; then dokku_log_info1 "Skipping: $DOMAIN already added" else - echo "$DOMAIN" >> "$GLOBAL_VHOST_PATH" + sed -i -e "\$a\\$DOMAIN" "$GLOBAL_VHOST_PATH" dokku_log_info1 "Added $DOMAIN" fi done @@ -158,8 +185,24 @@ domains_remove_global() { declare desc="remove list of domains" local GLOBAL_VHOST_PATH="$DOKKU_ROOT/VHOST" + touch "$GLOBAL_VHOST_PATH" for DOMAIN in "$@"; do sed -i "\|^$DOMAIN\$|d" "$GLOBAL_VHOST_PATH" dokku_log_info1 "Removed $DOMAIN" done } + +domains_set_global() { + declare desc="set list of global domains" + local GLOBAL_VHOST_PATH="$DOKKU_ROOT/VHOST" + + touch "$GLOBAL_VHOST_PATH" + for DOMAIN in "$@"; do + if ! (is_valid_hostname "$DOMAIN"); then + dokku_log_fail "$DOMAIN is invalid. exiting..." + fi + done + + echo "$*" | tr " " "\n" > "$GLOBAL_VHOST_PATH" + dokku_log_info1 "Set $*" +} diff --git a/plugins/domains/internal-functions b/plugins/domains/internal-functions new file mode 100755 index 000000000..fb4ab8fce --- /dev/null +++ b/plugins/domains/internal-functions @@ -0,0 +1,36 @@ +#!/usr/bin/env bash +set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x + +domains_help_content_func () { + declare desc="return domains plugin help content" + cat<], [DEPRECATED] Alternative for domains:report + domains:add [ ...], Add domains to app + domains:add-global [ ...], Add global domain names + domains:clear , Clear all domains for app + domains:disable , Disable VHOST support + domains:enable , Enable VHOST support + domains:remove [ ...], Remove domains from app + domains:remove-global [ ...], Remove global domain names + domains:report [] [], Displays a domains report for one or more apps + domains:set [ ...], Set domains for app + domains:set-global [ ...], Set global domain names +help_content +} + +domains_help_cmd() { + if [[ $1 = "domains:help" ]] ; then + echo -e 'Usage: dokku domains[:COMMAND]' + echo '' + echo 'Manage vhost domains used by the Dokku proxy.' + echo '' + echo 'Additional commands:' + domains_help_content_func | sort | column -c2 -t -s, + elif [[ $(ps -o command= $PPID) == *"--all"* ]]; then + domains_help_content_func + else + cat< [ ...]" + + shift 1 + domains_set "$@" +} + +domains_set_cmd "$@" diff --git a/plugins/domains/subcommands/set-global b/plugins/domains/subcommands/set-global new file mode 100755 index 000000000..665990104 --- /dev/null +++ b/plugins/domains/subcommands/set-global @@ -0,0 +1,15 @@ +#!/usr/bin/env bash +set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x +source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" +source "$PLUGIN_AVAILABLE_PATH/domains/functions" + +domains_set_global_cmd() { + declare desc="set global domain names via command line" + local cmd="domains:set-global" + [[ -z $2 ]] && dokku_log_fail "Please specify a domain name. Usage: dokku $1 [ ...]" + + shift 1 + domains_set_global "$@" +} + +domains_set_global_cmd "$@" diff --git a/plugins/enter/plugin.toml b/plugins/enter/plugin.toml index 71417684a..fe9bd0aa5 100644 --- a/plugins/enter/plugin.toml +++ b/plugins/enter/plugin.toml @@ -1,4 +1,4 @@ [plugin] description = "dokku core enter plugin" -version = "0.7.2" +version = "0.9.4" [plugin.config] diff --git a/plugins/enter/subcommands/default b/plugins/enter/subcommands/default index 9d2a23da1..b53ea0a71 100755 --- a/plugins/enter/subcommands/default +++ b/plugins/enter/subcommands/default @@ -12,7 +12,7 @@ enter_default_cmd() { if [[ -z "$3" ]]; then if [[ ${#AVAILABLE_CONTAINER_TYPES[@]} -gt 1 ]]; then dokku_log_warn "No container type specified." - dokku_log_fail "Available types for app ($APP): ${AVAILABLE_CONTAINER_TYPES[@]}" + dokku_log_fail "Available types for app ($APP): ${AVAILABLE_CONTAINER_TYPES[*]}" else CONTAINER_TYPE="${AVAILABLE_CONTAINER_TYPES[0]}" fi @@ -22,11 +22,11 @@ enter_default_cmd() { local DOKKU_APP_CIDS=( $(get_app_container_ids "$APP") ) if [[ ! -n "$4" ]]; then dokku_log_warn "No container id specified." - dokku_log_fail "Available ids for app ($APP): ${DOKKU_APP_CIDS[@]}" + dokku_log_fail "Available ids for app ($APP): ${DOKKU_APP_CIDS[*]}" fi if ! (printf -- '%s\n' "${DOKKU_APP_CIDS[@]}" | grep -q -e "^$4"); then dokku_log_warn "Invalid container id for app" - dokku_log_fail "Available ids for app ($APP): ${DOKKU_APP_CIDS[@]}" + dokku_log_fail "Available ids for app ($APP): ${DOKKU_APP_CIDS[*]}" fi local ID=$(printf -- '%s\n' "${DOKKU_APP_CIDS[@]}" | grep -e "^$4") shift 4 @@ -35,7 +35,7 @@ enter_default_cmd() { local ID=${DOKKU_APP_CIDS[0]} if [[ ! -n $ID ]]; then dokku_log_warn "No containers found for type '$CONTAINER_TYPE'" - dokku_log_fail "Available types for app ($APP): ${AVAILABLE_CONTAINER_TYPES[@]}" + dokku_log_fail "Available types for app ($APP): ${AVAILABLE_CONTAINER_TYPES[*]}" fi if [[ $3 ]]; then shift 3 diff --git a/plugins/git/functions b/plugins/git/functions index 33158d646..40d5cac2d 100755 --- a/plugins/git/functions +++ b/plugins/git/functions @@ -4,6 +4,23 @@ source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" source "$PLUGIN_AVAILABLE_PATH/apps/functions" source "$PLUGIN_AVAILABLE_PATH/config/functions" +use_git_worktree() { + declare desc="detects whether to use git worktree" + local GIT_VERSION MAJOR_VERSION MINOR_VERSION + + GIT_VERSION=$(git --version | awk '{split($0,a," "); print a[3]}') + MAJOR_VERSION=$(echo "$GIT_VERSION" | awk '{split($0,a,"."); print a[1]}') + MINOR_VERSION=$(echo "$GIT_VERSION" | awk '{split($0,a,"."); print a[2]}') + + if [[ "$MAJOR_VERSION" -ge "3" ]]; then + return 0 + elif [[ "$MAJOR_VERSION" -eq "2" ]] && [[ "$MINOR_VERSION" -ge "11" ]]; then + return 0 + else + return 1 + fi +} + git_build_app_repo() { declare desc="builds local git app repo for app" verify_app_name "$1" @@ -13,24 +30,37 @@ git_build_app_repo() { local GIT_BUILD_APP_REPO_TMP_WORK_DIR=$(mktemp -d "/tmp/dokku_git.XXXX") trap 'rm -rf "$GIT_BUILD_APP_REPO_TMP_WORK_DIR" > /dev/null' RETURN INT TERM EXIT - # git clone - this method creates a new git repository and adds the primary - # repo as a remote, then does a fetch depth=1 to avoid cloning - # the entire repo local TMP_TAG="dokku/$REV" chmod 755 "$GIT_BUILD_APP_REPO_TMP_WORK_DIR" unset GIT_DIR GIT_WORK_TREE - pushd "$GIT_BUILD_APP_REPO_TMP_WORK_DIR" > /dev/null [[ ! -d "$DOKKU_ROOT/$APP" ]] && apps_create "$APP" - GIT_DIR="$DOKKU_ROOT/$APP" git tag -d "$TMP_TAG" &> /dev/null || true - GIT_DIR="$DOKKU_ROOT/$APP" git tag "$TMP_TAG" "$REV" &> /dev/null - git init &> /dev/null - git config advice.detachedHead false - git remote add origin "$DOKKU_ROOT/$APP" &> /dev/null - git fetch --depth=1 origin "refs/tags/$TMP_TAG" &> /dev/null - git reset --hard FETCH_HEAD &> /dev/null - git submodule update --init --recursive &> /dev/null - GIT_DIR="$DOKKU_ROOT/$APP" git tag -d "$TMP_TAG" &> /dev/null || true - find -name .git -prune -exec rm -rf {} \; > /dev/null + + if use_git_worktree; then + # git worktree - this method uses git worktree which was introduced in git 2.5 + git worktree add "$GIT_BUILD_APP_REPO_TMP_WORK_DIR" "$REV" > /dev/null + pushd "$GIT_BUILD_APP_REPO_TMP_WORK_DIR" > /dev/null + else + # git clone - this method creates a new git repository and adds the primary + # repo as a remote, then does a fetch depth=1 to avoid cloning + # the entire repo. + # Not working for git >= 2.11.0 due to changes introduced + # in this merge: + # https://github.com/git/git/commit/25ab004c53cdcfea485e5bf437aeaa74df47196d + pushd "$GIT_BUILD_APP_REPO_TMP_WORK_DIR" > /dev/null + GIT_DIR="$DOKKU_ROOT/$APP" git tag -d "$TMP_TAG" &> /dev/null || true + GIT_DIR="$DOKKU_ROOT/$APP" git tag "$TMP_TAG" "$REV" &> /dev/null + git init &> /dev/null + git config advice.detachedHead false + git remote add origin "$DOKKU_ROOT/$APP" &> /dev/null + git fetch --depth=1 origin "refs/tags/$TMP_TAG" &> /dev/null + git reset --hard FETCH_HEAD &> /dev/null + GIT_DIR="$DOKKU_ROOT/$APP" git tag -d "$TMP_TAG" &> /dev/null || true + fi + + suppress_output git submodule update --init --recursive + find . -name .git -prune -exec rm -rf {} \; > /dev/null + + plugn trigger post-extract "$APP" "$GIT_BUILD_APP_REPO_TMP_WORK_DIR" "$REV" | sed -u "s/^/"$'\e[1G'"/" if [[ -f Dockerfile ]] && [[ "$([[ -f .env ]] && grep -q BUILDPACK_URL .env; echo $?)" != "0" ]] && [[ ! -f ".buildpacks" ]] && [[ -z $(config_get "$APP" BUILDPACK_URL || true) ]]; then plugn trigger pre-receive-app "$APP" "dockerfile" "$GIT_BUILD_APP_REPO_TMP_WORK_DIR" "$REV" @@ -41,16 +71,53 @@ git_build_app_repo() { fi } +suppress_output() { + declare desc="suppress all output from a given command unless there is an error" + local TMP_COMMAND_OUTPUT + TMP_COMMAND_OUTPUT=$(mktemp "/tmp/${FUNCNAME[0]}.XXXX") + trap 'rm -rf "$TMP_COMMAND_OUTPUT" > /dev/null' RETURN INT TERM EXIT + + "$@" > "$TMP_COMMAND_OUTPUT" 2>&1 || { + local exit_code="$?" + cat "$TMP_COMMAND_OUTPUT" + return "$exit_code" + } + return 0 +} + +git_deploy_branch() { + declare desc="retrieve the deploy branch for a given application" + local cmd="git-hook" + local APP="$1" + + local DOKKU_DEPLOY_BRANCH="$(config_get "$APP" DOKKU_DEPLOY_BRANCH || true)" + local DOKKU_GLOBAL_DEPLOY_BRANCH="$(config_get --global DOKKU_DEPLOY_BRANCH || true)" + if [[ -n "$DOKKU_DEPLOY_BRANCH" ]]; then + echo "$DOKKU_DEPLOY_BRANCH" + elif [[ -n "$DOKKU_GLOBAL_DEPLOY_BRANCH" ]]; then + echo "$DOKKU_GLOBAL_DEPLOY_BRANCH" + else + echo "master" + fi +} + git_hook_cmd() { declare desc="kick off receive-app trigger from git prereceive hook" local cmd="git-hook" - local APP="$2" + local APP="$2" + local DOKKU_DEPLOY_BRANCH="$(git_deploy_branch "$APP")" + + if ! git check-ref-format --branch "$DOKKU_DEPLOY_BRANCH" > /dev/null 2>&1; then + echo $'\e[1G\e[K'"-----> WARNING: Invalid branch name '$DOKKU_DEPLOY_BRANCH' specified via DOKKU_DEPLOY_BRANCH." + echo $'\e[1G\e[K'"-----> For more details, please see the man page for 'git-check-ref-format.'" + return + fi local oldrev newrev refname while read -r oldrev newrev refname; do # Only run this script for the master branch. You can remove this # if block if you wish to run it for others as well. - if [[ $refname = "refs/heads/master" ]]; then + if [[ $refname = "refs/heads/${DOKKU_DEPLOY_BRANCH}" ]]; then # broken out into plugin so we might support other methods to receive an app # shellcheck disable=SC2086 plugn trigger receive-app $APP $newrev diff --git a/plugins/git/plugin.toml b/plugins/git/plugin.toml index e045d49d2..c3a8c857d 100644 --- a/plugins/git/plugin.toml +++ b/plugins/git/plugin.toml @@ -1,4 +1,4 @@ [plugin] description = "dokku core git plugin" -version = "0.7.2" +version = "0.9.4" [plugin.config] diff --git a/plugins/logs/plugin.toml b/plugins/logs/plugin.toml index 8913ebada..a780f0ef7 100644 --- a/plugins/logs/plugin.toml +++ b/plugins/logs/plugin.toml @@ -1,4 +1,4 @@ [plugin] description = "dokku core logs plugin" -version = "0.7.2" +version = "0.9.4" [plugin.config] diff --git a/plugins/logs/subcommands/default b/plugins/logs/subcommands/default index e97480c6a..14532f31d 100755 --- a/plugins/logs/subcommands/default +++ b/plugins/logs/subcommands/default @@ -17,7 +17,8 @@ logs_default_cmd() { shift 2; local TEMP=$(getopt -o htqn:p: --long help,tail,quiet,num:,ps: -n 'dokku logs' -- "$@") - if [[ $? != 0 ]]; then usage >&2 ; exit 1 ; fi + local EXIT_CODE="$?" + if [[ "$EXIT_CODE" != 0 ]]; then usage >&2 ; exit 1 ; fi eval set -- "$TEMP" local DOKKU_LOGS_LINE_NUMBERS="100" diff --git a/plugins/named-containers/post-deploy b/plugins/named-containers/core-post-deploy similarity index 92% rename from plugins/named-containers/post-deploy rename to plugins/named-containers/core-post-deploy index 2489b71ce..2d84b28c3 100755 --- a/plugins/named-containers/post-deploy +++ b/plugins/named-containers/core-post-deploy @@ -2,9 +2,9 @@ set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" -named_containers_post_deploy() { +named_containers_core_post_deploy() { declare desc="names deployed app container is consistent manner" - local trigger="named_containers_post_deploy" + local trigger="named_containers_core_post_deploy" local APP="$1"; local APP_ROOT="$DOKKU_ROOT/$APP" local container @@ -38,4 +38,4 @@ named_containers_post_deploy() { shopt -u nullglob } -named_containers_post_deploy "$@" +named_containers_core_post_deploy "$@" diff --git a/plugins/named-containers/plugin.toml b/plugins/named-containers/plugin.toml index 6488244b6..e18894aa9 100644 --- a/plugins/named-containers/plugin.toml +++ b/plugins/named-containers/plugin.toml @@ -1,4 +1,4 @@ [plugin] description = "dokku core named containers plugin" -version = "0.7.2" +version = "0.9.4" [plugin.config] diff --git a/plugins/nginx-vhosts/commands b/plugins/nginx-vhosts/commands index f7911fc25..3dbfc98d8 100755 --- a/plugins/nginx-vhosts/commands +++ b/plugins/nginx-vhosts/commands @@ -1,33 +1,15 @@ #!/usr/bin/env bash [[ " help nginx:help " == *" $1 "* ]] || exit "$DOKKU_NOT_IMPLEMENTED_EXIT" +source "$PLUGIN_AVAILABLE_PATH/nginx-vhosts/internal-functions" set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x case "$1" in help | nginx:help) - help_content_func () { - declare desc="return nginx plugin help content" - cat<, (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) -help_content - } - - if [[ $1 = "nginx:help" ]] ; then - echo -e 'Usage: dokku nginx[:COMMAND]' - echo '' - echo 'Interact with Dokku'"'"'s Nginx proxy.' - echo '' - echo 'Additional commands:' - help_content_func | sort | column -c2 -t -s, - else - help_content_func - fi - ;; + nginx_vhosts_help_cmd "$@" + ;; *) exit "$DOKKU_NOT_IMPLEMENTED_EXIT" - ;; + ;; esac diff --git a/plugins/nginx-vhosts/post-deploy b/plugins/nginx-vhosts/core-post-deploy similarity index 81% rename from plugins/nginx-vhosts/post-deploy rename to plugins/nginx-vhosts/core-post-deploy index f716b0715..7bb7db877 100755 --- a/plugins/nginx-vhosts/post-deploy +++ b/plugins/nginx-vhosts/core-post-deploy @@ -5,9 +5,9 @@ source "$PLUGIN_AVAILABLE_PATH/domains/functions" source "$PLUGIN_AVAILABLE_PATH/nginx-vhosts/functions" source "$PLUGIN_AVAILABLE_PATH/proxy/functions" -nginx_post_deploy() { - declare desc="nginx-vhosts post-deploy plugin trigger" - local trigger="nginx_post_deploy" +nginx_core_post_deploy() { + declare desc="nginx-vhosts core-post-deploy plugin trigger" + local trigger="nginx_core_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 @@ -21,5 +21,5 @@ nginx_post_deploy() { } if [[ "$(get_app_proxy_type "$1")" == "nginx" ]]; then - nginx_post_deploy "$@" + nginx_core_post_deploy "$@" fi diff --git a/plugins/nginx-vhosts/functions b/plugins/nginx-vhosts/functions index 044c2d0dd..4778d266d 100755 --- a/plugins/nginx-vhosts/functions +++ b/plugins/nginx-vhosts/functions @@ -39,9 +39,9 @@ restart_nginx() { sudo /sbin/service nginx reload > /dev/null ;; - arch) + arch|centos) sudo /usr/bin/systemctl reload nginx - ;; + ;; esac } @@ -81,14 +81,13 @@ configure_nginx_ports() { config_set --no-restart "$APP" DOKKU_NGINX_PORT="$NGINX_PORT" fi if [[ -z "$DOKKU_NGINX_SSL_PORT" ]]; then - if (is_ssl_enabled "$APP") && [[ -z "$RAW_TCP_PORTS" ]]; then - if [[ "$IS_APP_VHOST_ENABLED" == "false" ]]; then + if (is_ssl_enabled "$APP"); then + local NGINX_SSL_PORT=443 + if [[ -z "$RAW_TCP_PORTS" ]] && [[ "$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 + NGINX_SSL_PORT=$(get_available_port) fi - config_set --no-restart "$APP" DOKKU_NGINX_SSL_PORT="$NGINX_SSL_PORT" + config_set --no-restart "$APP" DOKKU_NGINX_SSL_PORT="$NGINX_SSL_PORT" fi fi if [[ -z "$DOKKU_PROXY_PORT_MAP" ]]; then @@ -148,7 +147,7 @@ get_custom_nginx_template() { } is_spdy_enabled() { - declare desc="detects whether the installed nginx version has spdy or http2 support" + declare desc="detects whether the installed nginx version has spdy support" local NGINX_VERSION="$1" local MAJOR_VERSION MINOR_VERSION PATCH_VERSION local HAS_SUPPORT=true @@ -174,6 +173,33 @@ is_spdy_enabled() { echo $HAS_SUPPORT } +is_http2_enabled() { + declare desc="detects whether the installed nginx version has http2 support" + local NGINX_VERSION="$1" + local MAJOR_VERSION MINOR_VERSION PATCH_VERSION + local HAS_SUPPORT=false + + if ! which nginx > /dev/null 2>&1; then + echo $HAS_SUPPORT + return + fi + + MAJOR_VERSION=$(echo "$NGINX_VERSION" | awk '{split($0,a,"."); print a[1]}') + MINOR_VERSION=$(echo "$NGINX_VERSION" | awk '{split($0,a,"."); print a[2]}') + PATCH_VERSION=$(echo "$NGINX_VERSION" | awk '{split($0,a,"."); print a[3]}') + if [[ "$MAJOR_VERSION" -ge "2" ]]; then + HAS_SUPPORT=true + elif [[ "$MAJOR_VERSION" -eq "1" ]]; then + if [[ "$MINOR_VERSION" -eq "11" ]] && [[ "$PATCH_VERSION" -ge "5" ]]; then + HAS_SUPPORT=true + elif [[ "$MINOR_VERSION" -ge "12" ]]; then + HAS_SUPPORT=true + fi + fi + + echo $HAS_SUPPORT +} + nginx_build_config() { declare desc="build nginx config to proxy app containers using sigil" local APP="$1"; verify_app_name "$APP" @@ -245,11 +271,19 @@ nginx_build_config() { else local SSL_VHOSTS=$(< "$DOKKU_ROOT/HOSTNAME") fi - local SSL_SERVER_NAME=$(echo "$SSL_VHOSTS" | xargs) + local SSL_SERVER_NAME + local host + for host in $SSL_VHOSTS; do + # SSL_SERVER_NAME should only contain items not in NOSSL_SERVER_NAME + if [[ ! $NOSSL_SERVER_NAME =~ (^|[[:space:]])$host($|[[:space:]]) ]]; then + SSL_SERVER_NAME="${host}${SSL_SERVER_NAME:+ $SSL_SERVER_NAME}" + fi + done fi local NGINX_VERSION="$(nginx -v 2>&1 | cut -d'/' -f 2)" local SPDY_SUPPORTED="$(is_spdy_enabled "$NGINX_VERSION")" + local HTTP2_SUPPORTED="$(is_http2_enabled "$NGINX_VERSION")" eval "$(config_export app "$APP")" local SIGIL_PARAMS=(-f $NGINX_TEMPLATE APP="$APP" DOKKU_ROOT="$DOKKU_ROOT" @@ -257,6 +291,7 @@ nginx_build_config() { DOKKU_APP_LISTENERS="$DOKKU_APP_LISTENERS" PASSED_LISTEN_IP_PORT="$PASSED_LISTEN_IP_PORT" SPDY_SUPPORTED="$SPDY_SUPPORTED" + HTTP2_SUPPORTED="$HTTP2_SUPPORTED" 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" diff --git a/plugins/nginx-vhosts/install b/plugins/nginx-vhosts/install index e08840d2d..6057a8720 100755 --- a/plugins/nginx-vhosts/install +++ b/plugins/nginx-vhosts/install @@ -17,12 +17,19 @@ case "$DOKKU_DISTRO" in arch) echo "%dokku ALL=(ALL) NOPASSWD:/usr/bin/systemctl reload nginx, /usr/sbin/nginx -t" > /etc/sudoers.d/dokku-nginx ;; + + centos) + echo "%dokku ALL=(ALL) NOPASSWD:/usr/bin/systemctl reload nginx, /usr/sbin/nginx -t" > /etc/sudoers.d/dokku-nginx + echo "Defaults:dokku !requiretty" >> /etc/sudoers.d/dokku-nginx + ;; esac chmod 0440 /etc/sudoers.d/dokku-nginx # if dokku.conf has not been created, create it if [[ ! -f /etc/nginx/conf.d/dokku.conf ]]; then + mkdir -p /etc/nginx/conf.d + chown root:root /etc/nginx/conf.d cat< /etc/nginx/conf.d/dokku.conf include $DOKKU_ROOT/*/nginx.conf; @@ -66,10 +73,8 @@ case "$DOKKU_DISTRO" in "$NGINX_INIT" nginx start || "$NGINX_INIT" nginx reload ;; - arch) + arch|centos) NGINX_INIT="/usr/bin/systemctl" "$NGINX_INIT" start nginx || "$NGINX_INIT" reload nginx ;; esac - - diff --git a/plugins/nginx-vhosts/internal-functions b/plugins/nginx-vhosts/internal-functions new file mode 100755 index 000000000..93d929bb6 --- /dev/null +++ b/plugins/nginx-vhosts/internal-functions @@ -0,0 +1,28 @@ +#!/usr/bin/env bash +set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x + +nginx_vhosts_help_content_func() { + declare desc="return nginx plugin help content" + cat<, (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) +help_content +} + +nginx_vhosts_help_cmd() { + if [[ $1 = "nginx:help" ]] ; then + echo -e 'Usage: dokku nginx[:COMMAND]' + echo '' + echo 'Interact with Dokku'"'"'s Nginx proxy.' + echo '' + echo 'Additional commands:' + nginx_vhosts_help_content_func | sort | column -c2 -t -s, + elif [[ $(ps -o command= $PPID) == *"--all"* ]]; then + nginx_vhosts_help_content_func + else + cat<, Show proxy settings for app - proxy:enable , Enable proxy for app - proxy:disable , Disable proxy for app - proxy:ports , List proxy port mappings for app - proxy:ports-clear , Clear all proxy port mappings for app - proxy:ports-add :: [::...], Set proxy port mappings for app - proxy:ports-remove [|::...], Unset proxy port mappings for app - proxy:set , Set proxy type for app -help_content - } - - if [[ $1 = "proxy:help" ]] ; then - echo -e 'Usage: dokku proxy[:COMMAND]' - echo '' - echo 'Control the proxy used by dokku, per app.' - echo '' - echo 'Additional commands:' - help_content_func | sort | column -c2 -t -s, - else - help_content_func - fi + proxy_help_cmd "$@" ;; *) diff --git a/plugins/proxy/functions b/plugins/proxy/functions index d7d3b1a2f..5202a21ef 100755 --- a/plugins/proxy/functions +++ b/plugins/proxy/functions @@ -24,10 +24,16 @@ get_app_proxy_type() { echo "$APP_PROXY_TYPE" } +get_app_proxy_port_map() { + declare desc="return a list of configured port mappings" + local APP="$1"; local PROXY_PORT_MAP="$(config_get "$APP" DOKKU_PROXY_PORT_MAP || true)" + echo "$PROXY_PORT_MAP" +} + list_app_proxy_ports() { declare desc="list proxy port mappings for an app" local APP="$1"; verify_app_name "$APP" - local PROXY_PORT_MAP="$(config_get "$APP" DOKKU_PROXY_PORT_MAP || true)" + local PROXY_PORT_MAP="$(get_app_proxy_port_map "$APP")" if [[ -n "$PROXY_PORT_MAP" ]]; then dokku_log_info1_quiet "Port mappings for $APP" @@ -45,7 +51,7 @@ filter_app_proxy_ports() { declare desc="list proxy port mappings for an app that start with a particular scheme and a host port" local APP="$1"; verify_app_name "$APP" local SCHEME="$2" HOST_PORT="$3" - local PROXY_PORT_MAP="$(config_get "$APP" DOKKU_PROXY_PORT_MAP || true)" + local PROXY_PORT_MAP="$(get_app_proxy_port_map "$APP")" if [[ -n "$PROXY_PORT_MAP" ]]; then local port_map @@ -62,7 +68,7 @@ filter_app_proxy_ports() { add_proxy_ports() { declare desc="add proxy port mappings from an app" local APP="$1" - local PROXY_PORT_MAP="$(config_get "$APP" DOKKU_PROXY_PORT_MAP || true)" + local PROXY_PORT_MAP="$(get_app_proxy_port_map "$APP")" shift 1 local INPUT_PORTS="$*" @@ -78,7 +84,7 @@ add_proxy_ports() { remove_proxy_ports() { declare desc="remove specific proxy port mappings from an app" local APP="$1" - local PROXY_PORT_MAP="$(config_get "$APP" DOKKU_PROXY_PORT_MAP || true)" + local PROXY_PORT_MAP="$(get_app_proxy_port_map "$APP")" local RE_PORT='^[0-9]+$' shift 1 diff --git a/plugins/proxy/internal-functions b/plugins/proxy/internal-functions new file mode 100755 index 000000000..3ff81c845 --- /dev/null +++ b/plugins/proxy/internal-functions @@ -0,0 +1,34 @@ +#!/usr/bin/env bash +set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x + +proxy_help_content_func() { + declare desc="return proxy plugin help content" + cat<, [DEPRECATED] Show proxy settings for app + proxy:enable , Enable proxy for app + proxy:disable , Disable proxy for app + proxy:ports , List proxy port mappings for app + proxy:ports-clear , Clear all proxy port mappings for app + proxy:ports-add :: [::...], Set proxy port mappings for app + proxy:ports-remove [|::...], Unset proxy port mappings for app + proxy:report [] [], Displays a proxy report for one or more apps + proxy:set , Set proxy type for app +help_content +} + +proxy_help_cmd() { + if [[ $1 = "proxy:help" ]] ; then + echo -e 'Usage: dokku proxy[:COMMAND]' + echo '' + echo 'Manage the proxy used by dokku on a per app.' + echo '' + echo 'Additional commands:' + proxy_help_content_func | sort | column -c2 -t -s, + elif [[ $(ps -o command= $PPID) == *"--all"* ]]; then + proxy_help_content_func + else + cat<, Stop app container(s) ps:rebuild , Rebuild an app from source ps:rebuildall, Rebuild all apps from source + ps:report [] [], Displays a process report for one or more apps ps:restart , Restart app container(s) ps:restartall, Restart all deployed app containers ps:restore, Start previously running apps e.g. after reboot diff --git a/plugins/ps/post-deploy b/plugins/ps/core-post-deploy similarity index 69% rename from plugins/ps/post-deploy rename to plugins/ps/core-post-deploy index f66170f6f..1fc2e2b6b 100755 --- a/plugins/ps/post-deploy +++ b/plugins/ps/core-post-deploy @@ -4,13 +4,13 @@ source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" source "$PLUGIN_AVAILABLE_PATH/config/functions" source "$PLUGIN_AVAILABLE_PATH/ps/functions" -ps_post_deploy() { - declare desc="ps post-deploy plugin trigger" - local trigger="ps_post_deploy" +ps_core_post_deploy() { + declare desc="ps core-post-deploy plugin trigger" + local trigger="ps_core_post_deploy" local APP="$1" remove_procfile "$APP" config_set --no-restart "$APP" DOKKU_APP_RESTORE=1 } -ps_post_deploy "$@" +ps_core_post_deploy "$@" diff --git a/plugins/ps/functions b/plugins/ps/functions index 3191f88ef..f9f094df8 100755 --- a/plugins/ps/functions +++ b/plugins/ps/functions @@ -77,9 +77,9 @@ set_scale() { for procscale in "${SCALE_SETTINGS[@]}"; do local PROC_NAME=${procscale%%=*} local PROC_COUNT=${procscale#*=} - is_number $PROC_COUNT || dokku_log_fail "ps:scale $PROC_COUNT is not a number" + is_number "$PROC_COUNT" || dokku_log_fail "ps:scale $PROC_COUNT is not a number" dokku_log_info1_quiet "Scaling $APP:$PROC_NAME to $PROC_COUNT" - if (egrep -q ^${PROC_NAME}= "$DOKKU_SCALE_FILE" > /dev/null 2>&1); then + if (egrep -q "^${PROC_NAME}=" "$DOKKU_SCALE_FILE" > /dev/null 2>&1); then sed --in-place "s:^${PROC_NAME}=.*:$PROC_NAME=$PROC_COUNT:g" "$DOKKU_SCALE_FILE" else echo "$PROC_NAME=$PROC_COUNT" >> "$DOKKU_SCALE_FILE" diff --git a/plugins/ps/plugin.toml b/plugins/ps/plugin.toml index f25ef3c61..600a45d3e 100644 --- a/plugins/ps/plugin.toml +++ b/plugins/ps/plugin.toml @@ -1,4 +1,4 @@ [plugin] description = "dokku core ps plugin" -version = "0.7.2" +version = "0.9.4" [plugin.config] diff --git a/plugins/ps/subcommands/default b/plugins/ps/subcommands/default index 46dec800a..07f044d57 100755 --- a/plugins/ps/subcommands/default +++ b/plugins/ps/subcommands/default @@ -5,13 +5,15 @@ source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" ps_main_cmd() { declare desc="shows running processes in all running app containers" local cmd="ps" + dokku_log_warn "Deprecated: Please use ps:report" + [[ -z $2 ]] && dokku_log_fail "Please specify an app to run the command on" local APP="$2"; 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 + local DOKKU_APP_RUNNING_CONTAINER_IDS=$(get_app_running_container_ids "$APP") + local CID for CID in $DOKKU_APP_RUNNING_CONTAINER_IDS; do has_tty && local DOKKU_RUN_OPTS="-i -t" diff --git a/plugins/ps/subcommands/report b/plugins/ps/subcommands/report new file mode 100755 index 000000000..77f952e6e --- /dev/null +++ b/plugins/ps/subcommands/report @@ -0,0 +1,115 @@ +#!/usr/bin/env bash +set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x +source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" +source "$PLUGIN_AVAILABLE_PATH/docker-options/functions" +source "$PLUGIN_AVAILABLE_PATH/ps/functions" + +report_single_app() { + declare APP="$1" INFO_FLAG="$2" + local APP_DIR="$DOKKU_ROOT/$APP" + local passed_phases="deploy" + local APP_CIDS=$(get_app_container_ids "$APP"); local PROCS=0; local RUNNING="" + + for CID in $APP_CIDS; do + local APP_CONTAINER_STATUS=$(docker inspect -f '{{.State.Running}}' "$CID" 2>/dev/null || true) + if [[ "$APP_CONTAINER_STATUS" == "true" ]]; then RUNNING+="0"; else RUNNING+="1"; fi + PROCS=$((PROCS + 1)) + done + + if [[ "${#RUNNING}" -eq 0 ]] || [[ "${#RUNNING}" -ne 0 ]] && [[ "$RUNNING" != *"0"* ]]; then + RUNNING="false" + elif [[ "$RUNNING" != *"1"* ]] && [[ "${#RUNNING}" -ne 0 ]]; then + RUNNING="true"; + else + RUNNING="mixed" + fi + + if (is_deployed "$APP"); then DEPLOYED="true"; else DEPLOYED="false"; fi + RESTARTPOLICY=$(get_restart_policies "$(get_phase_file_path "$passed_phases")") + + local CONTAINER_FILES="$(find "$DOKKU_ROOT/$APP" -maxdepth 1 -name "CONTAINER.*" -printf "%f\n" 2>/dev/null | sort -t . -k 2 -n | xargs)" + local CONTAINER_FILE + local DOKKU_APP_RESTORE=$(config_get "$APP" DOKKU_APP_RESTORE || true) + local STATUSES=() + if [[ "$DOKKU_APP_RESTORE" != 0 ]]; then RESTORE="true"; else RESTORE="false"; fi + for CONTAINER_FILE in $CONTAINER_FILES; do + CID=$(< "$DOKKU_ROOT/$APP/$CONTAINER_FILE") + local APP_CONTAINER_STATUS=$(docker inspect -f '{{.State.Running}}' "$CID") + STATUSES+=("${CONTAINER_FILE#*.}:$APP_CONTAINER_STATUS#${CID:0:12}") + done + local flag_map=( + "--processes: $PROCS" + "--deployed: $DEPLOYED" + "--running: $RUNNING" + "--restore: $RESTORE" + "--restart-policy: $RESTARTPOLICY" + ) + + if [[ -z "$INFO_FLAG" ]]; then + dokku_log_info2 "$APP" + for flag in "${flag_map[@]}"; do + key="$(echo "${flag#--}" | cut -f1 -d' ' | tr - ' ')" + dokku_log_verbose "$(printf "%-20s %-25s" "${key^}" "${flag#*: }")" + done + for STATUS in "${STATUSES[@]}"; do + name=${STATUS%:*} + value=${STATUS#*:} + status=${value%#*} + cid=${value#*#} + dokku_log_verbose "$(printf "%-20s %-10s (CID: %s)" "Status $name" "$status" "$cid")" + done + else + local match=false; local value_exists=false + for STATUS in "${STATUSES[@]}"; do + name=${STATUS%:*} + value=${STATUS#*:} + status=${value%#*} + cid=${value#*#} + status_flag="--status-$name" + flag_map+=("$status_flag: $status") + done + for flag in "${flag_map[@]}"; do + valid_flags="${valid_flags} $(echo "$flag" | cut -d':' -f1)" + if [[ "$flag" == "${INFO_FLAG}:"* ]]; then + value=${flag#*: } + size="${#value}" + if [[ "$size" -ne 0 ]]; then + echo "$value" && match=true && value_exists=true + else + match=true + fi + fi + done + if [[ "$match" == "true" ]]; then + [[ "$value_exists" == "true" ]] || dokku_log_fail "not deployed" + else + dokku_log_fail "Invalid flag passed, valid flags:${valid_flags}" + fi + fi +} + +ps_report_cmd() { + declare desc="shows reports for an app" + local cmd="ps:report" + local INSTALLED_APPS=$(dokku_apps) + local APP="$2" INFO_FLAG="$3" + + if [[ -n "$APP" ]] && [[ "$APP" == --* ]]; then + INFO_FLAG="$APP" + APP="" + fi + + if [[ -z "$APP" ]] && [[ -z "$INFO_FLAG" ]]; then + INFO_FLAG="true" + fi + + if [[ -z "$APP" ]]; then + for app in $INSTALLED_APPS; do + report_single_app "$app" "$INFO_FLAG" | tee || true + done + else + report_single_app "$APP" "$INFO_FLAG" + fi +} + +ps_report_cmd "$@" diff --git a/plugins/ps/subcommands/restore b/plugins/ps/subcommands/restore index 0b7b83729..818c3d980 100755 --- a/plugins/ps/subcommands/restore +++ b/plugins/ps/subcommands/restore @@ -7,11 +7,20 @@ source "$PLUGIN_AVAILABLE_PATH/ps/functions" ps_restore_cmd() { declare desc="starts all apps with DOKKU_APP_RESTORE not set to 0 via command line" local cmd="ps:restore" + + if which parallel > /dev/null 2>&1; then + dokku_apps | parallel dokku ps:start + return + fi + for app in $(dokku_apps); do local DOKKU_APP_RESTORE=$(config_get "$app" DOKKU_APP_RESTORE || true) if [[ $DOKKU_APP_RESTORE != 0 ]]; then echo "Restoring app $app ..." - ps_start "$app" + if ps_start "$app"; then + continue + fi + dokku_log_warn "dokku ps:restore ${app} failed" fi done } diff --git a/plugins/repo/.gitignore b/plugins/repo/.gitignore new file mode 100644 index 000000000..0f6770b1e --- /dev/null +++ b/plugins/repo/.gitignore @@ -0,0 +1,2 @@ +/commands +/subcommands/* diff --git a/plugins/repo/Makefile b/plugins/repo/Makefile new file mode 100644 index 000000000..6c127ac4b --- /dev/null +++ b/plugins/repo/Makefile @@ -0,0 +1,26 @@ +include ../../common.mk + +build-in-docker: clean + docker run --rm \ + -v $$PWD/../..:$(GO_REPO_ROOT) \ + -w $(GO_REPO_ROOT)/plugins/repo \ + $(BUILD_IMAGE) \ + bash -c "make build" || exit $$? + +build: commands subcommands +subcommands: subcommands/gc subcommands/purge-cache + +commands: **/**/commands.go + go build -a -o commands src/commands/commands.go + +subcommands/gc: **/**/**/gc.go + go build -a -o subcommands/gc src/subcommands/gc/gc.go + +subcommands/purge-cache: **/**/**/purge-cache.go + go build -a -o subcommands/purge-cache src/subcommands/purge-cache/purge-cache.go + +clean: + rm -rf commands subcommands + +src-clean: + rm -rf .gitignore src vendor Makefile diff --git a/plugins/repo/commands b/plugins/repo/commands deleted file mode 100755 index 5025f89f5..000000000 --- a/plugins/repo/commands +++ /dev/null @@ -1,31 +0,0 @@ -#!/usr/bin/env bash -[[ " help repo:help " == *" $1 "* ]] || exit "$DOKKU_NOT_IMPLEMENTED_EXIT" -set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x - -case "$1" in - help | repo:help) - help_content_func () { - declare desc="return repo plugin help content" - cat<, Runs 'git gc --agressive' against the application's repo - repo:purge-cache , Deletes the contents of the build cache stored in the repository -help_content - } - - if [[ $1 = "repo:help" ]] ; then - echo -e 'Usage: dokku repo[:COMMAND]' - echo '' - echo "Runs commands that interact with the app's repo" - echo '' - echo 'Additional commands:' - help_content_func | sort | column -c2 -t -s, - else - help_content_func - fi - ;; - - *) - exit "$DOKKU_NOT_IMPLEMENTED_EXIT" - ;; - -esac diff --git a/plugins/repo/plugin.toml b/plugins/repo/plugin.toml index f354ceff0..00f6ae36e 100644 --- a/plugins/repo/plugin.toml +++ b/plugins/repo/plugin.toml @@ -1,4 +1,4 @@ [plugin] description = "dokku core repo plugin" -version = "0.7.2" +version = "0.9.4" [plugin.config] diff --git a/plugins/repo/src/commands/commands.go b/plugins/repo/src/commands/commands.go new file mode 100644 index 000000000..a01d546d5 --- /dev/null +++ b/plugins/repo/src/commands/commands.go @@ -0,0 +1,54 @@ +package main + +import ( + "flag" + "fmt" + "os" + "strconv" + "strings" + + columnize "github.com/ryanuber/columnize" +) + +const ( + helpHeader = `Usage: dokku repo[:COMMAND] + +Runs commands that interact with the app's repo + +Additional commands:` + + helpContent = ` + repo:gc , Runs 'git gc --aggressive' against the application's repo + repo:purge-cache , Deletes the contents of the build cache stored in the repository +` +) + +func main() { + flag.Usage = usage + flag.Parse() + + cmd := flag.Arg(0) + switch cmd { + case "repo:help": + usage() + case "help": + fmt.Print(helpContent) + default: + dokkuNotImplementExitCode, err := strconv.Atoi(os.Getenv("DOKKU_NOT_IMPLEMENTED_EXIT")) + if err != nil { + fmt.Println("failed to retrieve DOKKU_NOT_IMPLEMENTED_EXIT environment variable") + dokkuNotImplementExitCode = 10 + } + os.Exit(dokkuNotImplementExitCode) + } +} + +func usage() { + config := columnize.DefaultConfig() + config.Delim = "," + config.Prefix = "\t" + config.Empty = "" + content := strings.Split(helpContent, "\n")[1:] + fmt.Println(helpHeader) + fmt.Println(columnize.Format(content, config)) +} diff --git a/plugins/repo/src/glide.lock b/plugins/repo/src/glide.lock new file mode 100644 index 000000000..fe4a98f07 --- /dev/null +++ b/plugins/repo/src/glide.lock @@ -0,0 +1,6 @@ +hash: 1ddab5de41d1514c2722bd7e24758ad4b60bf6956eb5b9b925fa071a1427f149 +updated: 2017-01-03T17:16:50.97156327-08:00 +imports: +- name: github.com/ryanuber/columnize + version: 0fbbb3f0e3fbdc5bae7c6cd5f6c1887ebfb76360 +testImports: [] diff --git a/plugins/repo/src/glide.yaml b/plugins/repo/src/glide.yaml new file mode 100644 index 000000000..3f7a96657 --- /dev/null +++ b/plugins/repo/src/glide.yaml @@ -0,0 +1,3 @@ +package: . +import: +- package: github.com/ryanuber/columnize diff --git a/plugins/repo/src/subcommands/gc/gc.go b/plugins/repo/src/subcommands/gc/gc.go new file mode 100644 index 000000000..7787385b8 --- /dev/null +++ b/plugins/repo/src/subcommands/gc/gc.go @@ -0,0 +1,29 @@ +package main + +import ( + "flag" + "strings" + + common "github.com/dokku/dokku/plugins/common" +) + +// runs 'git gc --aggressive' against the application's repo +func main() { + flag.Parse() + appName := flag.Arg(1) + if appName == "" { + common.LogFail("Please specify an app to run the command on") + } + err := common.VerifyAppName(appName) + if err != nil { + common.LogFail(err.Error()) + } + + appRoot := strings.Join([]string{common.MustGetEnv("DOKKU_ROOT"), appName}, "/") + cmdEnv := map[string]string{ + "GIT_DIR": appRoot, + } + gitGcCmd := common.NewShellCmd("git gc --aggressive") + gitGcCmd.Env = cmdEnv + gitGcCmd.Execute() +} diff --git a/plugins/repo/src/subcommands/purge-cache/purge-cache.go b/plugins/repo/src/subcommands/purge-cache/purge-cache.go new file mode 100644 index 000000000..863d4d46c --- /dev/null +++ b/plugins/repo/src/subcommands/purge-cache/purge-cache.go @@ -0,0 +1,36 @@ +package main + +import ( + "flag" + "os" + "strings" + + common "github.com/dokku/dokku/plugins/common" +) + +// deletes the contents of the build cache stored in the repository +func main() { + flag.Parse() + appName := flag.Arg(1) + if appName == "" { + common.LogFail("Please specify an app to run the command on") + } + err := common.VerifyAppName(appName) + if err != nil { + common.LogFail(err.Error()) + } + + cacheDir := strings.Join([]string{common.MustGetEnv("DOKKU_ROOT"), appName, "cache"}, "/") + dokkuGlobalRunArgs := common.MustGetEnv("DOKKU_GLOBAL_RUN_ARGS") + image := common.GetDeployingAppImageName(appName, "", "") + if info, _ := os.Stat(cacheDir); info != nil && info.IsDir() { + purgeCacheCmd := common.NewShellCmd(strings.Join([]string{"docker run --rm", dokkuGlobalRunArgs, + "-v", strings.Join([]string{cacheDir, ":/cache"}, ""), image, + `find /cache -depth -mindepth 1 -maxdepth 1 -exec rm -Rf {} ;`}, " ")) + purgeCacheCmd.Execute() + err := os.MkdirAll(cacheDir, 0644) + if err != nil { + common.LogFail(err.Error()) + } + } +} diff --git a/plugins/repo/src/vendor/github.com/ryanuber/columnize/.travis.yml b/plugins/repo/src/vendor/github.com/ryanuber/columnize/.travis.yml new file mode 100644 index 000000000..1a0bbea6c --- /dev/null +++ b/plugins/repo/src/vendor/github.com/ryanuber/columnize/.travis.yml @@ -0,0 +1,3 @@ +language: go +go: + - tip diff --git a/plugins/repo/src/vendor/github.com/ryanuber/columnize/LICENSE b/plugins/repo/src/vendor/github.com/ryanuber/columnize/LICENSE new file mode 100644 index 000000000..b9c0e2b68 --- /dev/null +++ b/plugins/repo/src/vendor/github.com/ryanuber/columnize/LICENSE @@ -0,0 +1,20 @@ +Copyright (c) 2016 Ryan Uber + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/plugins/repo/src/vendor/github.com/ryanuber/columnize/README.md b/plugins/repo/src/vendor/github.com/ryanuber/columnize/README.md new file mode 100644 index 000000000..e47634fc6 --- /dev/null +++ b/plugins/repo/src/vendor/github.com/ryanuber/columnize/README.md @@ -0,0 +1,69 @@ +Columnize +========= + +Easy column-formatted output for golang + +[![Build Status](https://travis-ci.org/ryanuber/columnize.svg)](https://travis-ci.org/ryanuber/columnize) +[![GoDoc](https://godoc.org/github.com/ryanuber/columnize?status.svg)](https://godoc.org/github.com/ryanuber/columnize) + +Columnize is a really small Go package that makes building CLI's a little bit +easier. In some CLI designs, you want to output a number similar items in a +human-readable way with nicely aligned columns. However, figuring out how wide +to make each column is a boring problem to solve and eats your valuable time. + +Here is an example: + +```go +package main + +import ( + "fmt" + "github.com/ryanuber/columnize" +) + +func main() { + output := []string{ + "Name | Gender | Age", + "Bob | Male | 38", + "Sally | Female | 26", + } + result := columnize.SimpleFormat(output) + fmt.Println(result) +} +``` + +As you can see, you just pass in a list of strings. And the result: + +``` +Name Gender Age +Bob Male 38 +Sally Female 26 +``` + +Columnize is tolerant of missing or empty fields, or even empty lines, so +passing in extra lines for spacing should show up as you would expect. + +Configuration +============= + +Columnize is configured using a `Config`, which can be obtained by calling the +`DefaultConfig()` method. You can then tweak the settings in the resulting +`Config`: + +``` +config := columnize.DefaultConfig() +config.Delim = "|" +config.Glue = " " +config.Prefix = "" +config.Empty = "" +``` + +* `Delim` is the string by which columns of **input** are delimited +* `Glue` is the string by which columns of **output** are delimited +* `Prefix` is a string by which each line of **output** is prefixed +* `Empty` is a string used to replace blank values found in output + +You can then pass the `Config` in using the `Format` method (signature below) to +have text formatted to your liking. + +See the [godoc](https://godoc.org/github.com/ryanuber/columnize) page for usage. diff --git a/plugins/repo/src/vendor/github.com/ryanuber/columnize/columnize.go b/plugins/repo/src/vendor/github.com/ryanuber/columnize/columnize.go new file mode 100644 index 000000000..915716a10 --- /dev/null +++ b/plugins/repo/src/vendor/github.com/ryanuber/columnize/columnize.go @@ -0,0 +1,178 @@ +package columnize + +import ( + "bytes" + "fmt" + "strings" +) + +// Config can be used to tune certain parameters which affect the way +// in which Columnize will format output text. +type Config struct { + // The string by which the lines of input will be split. + Delim string + + // The string by which columns of output will be separated. + Glue string + + // The string by which columns of output will be prefixed. + Prefix string + + // A replacement string to replace empty fields + Empty string +} + +// DefaultConfig returns a *Config with default values. +func DefaultConfig() *Config { + return &Config{ + Delim: "|", + Glue: " ", + Prefix: "", + Empty: "", + } +} + +// MergeConfig merges two config objects together and returns the resulting +// configuration. Values from the right take precedence over the left side. +func MergeConfig(a, b *Config) *Config { + var result Config = *a + + // Return quickly if either side was nil + if a == nil || b == nil { + return &result + } + + if b.Delim != "" { + result.Delim = b.Delim + } + if b.Glue != "" { + result.Glue = b.Glue + } + if b.Prefix != "" { + result.Prefix = b.Prefix + } + if b.Empty != "" { + result.Empty = b.Empty + } + + return &result +} + +// stringFormat, given a set of column widths and the number of columns in +// the current line, returns a sprintf-style format string which can be used +// to print output aligned properly with other lines using the same widths set. +func stringFormat(c *Config, widths []int, columns int) string { + // Create the buffer with an estimate of the length + buf := bytes.NewBuffer(make([]byte, 0, (6+len(c.Glue))*columns)) + + // Start with the prefix, if any was given. The buffer will not return an + // error so it does not need to be handled + buf.WriteString(c.Prefix) + + // Create the format string from the discovered widths + for i := 0; i < columns && i < len(widths); i++ { + if i == columns-1 { + buf.WriteString("%s\n") + } else { + fmt.Fprintf(buf, "%%-%ds%s", widths[i], c.Glue) + } + } + return buf.String() +} + +// elementsFromLine returns a list of elements, each representing a single +// item which will belong to a column of output. +func elementsFromLine(config *Config, line string) []interface{} { + seperated := strings.Split(line, config.Delim) + elements := make([]interface{}, len(seperated)) + for i, field := range seperated { + value := strings.TrimSpace(field) + + // Apply the empty value, if configured. + if value == "" && config.Empty != "" { + value = config.Empty + } + elements[i] = value + } + return elements +} + +// runeLen calculates the number of visible "characters" in a string +func runeLen(s string) int { + l := 0 + for _ = range s { + l++ + } + return l +} + +// widthsFromLines examines a list of strings and determines how wide each +// column should be considering all of the elements that need to be printed +// within it. +func widthsFromLines(config *Config, lines []string) []int { + widths := make([]int, 0, 8) + + for _, line := range lines { + elems := elementsFromLine(config, line) + for i := 0; i < len(elems); i++ { + l := runeLen(elems[i].(string)) + if len(widths) <= i { + widths = append(widths, l) + } else if widths[i] < l { + widths[i] = l + } + } + } + return widths +} + +// Format is the public-facing interface that takes a list of strings and +// returns nicely aligned column-formatted text. +func Format(lines []string, config *Config) string { + conf := MergeConfig(DefaultConfig(), config) + widths := widthsFromLines(conf, lines) + + // Estimate the buffer size + glueSize := len(conf.Glue) + var size int + for _, w := range widths { + size += w + glueSize + } + size *= len(lines) + + // Create the buffer + buf := bytes.NewBuffer(make([]byte, 0, size)) + + // Create a cache for the string formats + fmtCache := make(map[int]string, 16) + + // Create the formatted output using the format string + for _, line := range lines { + elems := elementsFromLine(conf, line) + + // Get the string format using cache + numElems := len(elems) + stringfmt, ok := fmtCache[numElems] + if !ok { + stringfmt = stringFormat(conf, widths, numElems) + fmtCache[numElems] = stringfmt + } + + fmt.Fprintf(buf, stringfmt, elems...) + } + + // Get the string result + result := buf.String() + + // Remove trailing newline without removing leading/trailing space + if n := len(result); n > 0 && result[n-1] == '\n' { + result = result[:n-1] + } + + return result +} + +// SimpleFormat is a convenience function to format text with the defaults. +func SimpleFormat(lines []string) string { + return Format(lines, nil) +} diff --git a/plugins/repo/src/vendor/github.com/ryanuber/columnize/columnize_test.go b/plugins/repo/src/vendor/github.com/ryanuber/columnize/columnize_test.go new file mode 100644 index 000000000..89dabaa38 --- /dev/null +++ b/plugins/repo/src/vendor/github.com/ryanuber/columnize/columnize_test.go @@ -0,0 +1,306 @@ +package columnize + +import ( + "fmt" + "testing" + + crand "crypto/rand" +) + +func TestListOfStringsInput(t *testing.T) { + input := []string{ + "Column A | Column B | Column C", + "x | y | z", + } + + config := DefaultConfig() + output := Format(input, config) + + expected := "Column A Column B Column C\n" + expected += "x y z" + + if output != expected { + t.Fatalf("\nexpected:\n%s\n\ngot:\n%s", expected, output) + } +} + +func TestEmptyLinesOutput(t *testing.T) { + input := []string{ + "Column A | Column B | Column C", + "", + "x | y | z", + } + + config := DefaultConfig() + output := Format(input, config) + + expected := "Column A Column B Column C\n" + expected += "\n" + expected += "x y z" + + if output != expected { + t.Fatalf("\nexpected:\n%s\n\ngot:\n%s", expected, output) + } +} + +func TestLeadingSpacePreserved(t *testing.T) { + input := []string{ + "| Column B | Column C", + "x | y | z", + } + + config := DefaultConfig() + output := Format(input, config) + + expected := " Column B Column C\n" + expected += "x y z" + + if output != expected { + t.Fatalf("\nexpected:\n%s\n\ngot:\n%s", expected, output) + } +} + +func TestColumnWidthCalculator(t *testing.T) { + input := []string{ + "Column A | Column B | Column C", + "Longer than A | Longer than B | Longer than C", + "short | short | short", + } + + config := DefaultConfig() + output := Format(input, config) + + expected := "Column A Column B Column C\n" + expected += "Longer than A Longer than B Longer than C\n" + expected += "short short short" + + if output != expected { + printableProof := fmt.Sprintf("\nGot: %+q", output) + printableProof += fmt.Sprintf("\nExpected: %+q", expected) + t.Fatalf("\n%s", printableProof) + } +} + +func TestColumnWidthCalculatorNonASCII(t *testing.T) { + input := []string{ + "Column A | Column B | Column C", + "⌘⌘⌘⌘⌘⌘⌘⌘ | Longer than B | Longer than C", + "short | short | short", + } + + config := DefaultConfig() + output := Format(input, config) + + expected := "Column A Column B Column C\n" + expected += "⌘⌘⌘⌘⌘⌘⌘⌘ Longer than B Longer than C\n" + expected += "short short short" + + if output != expected { + printableProof := fmt.Sprintf("\nGot: %+q", output) + printableProof += fmt.Sprintf("\nExpected: %+q", expected) + t.Fatalf("\n%s", printableProof) + } +} + +func BenchmarkColumnWidthCalculator(b *testing.B) { + // Generate the input + input := []string{ + "UUID A | UUID B | UUID C | Column D | Column E", + } + + format := "%s|%s|%s|%s" + short := "short" + + uuid := func() string { + buf := make([]byte, 16) + if _, err := crand.Read(buf); err != nil { + panic(fmt.Errorf("failed to read random bytes: %v", err)) + } + + return fmt.Sprintf("%08x-%04x-%04x-%04x-%12x", + buf[0:4], + buf[4:6], + buf[6:8], + buf[8:10], + buf[10:16]) + } + + for i := 0; i < 1000; i++ { + l := fmt.Sprintf(format, uuid()[:8], uuid()[:12], uuid(), short, short) + input = append(input, l) + } + + config := DefaultConfig() + b.ResetTimer() + + for i := 0; i < b.N; i++ { + Format(input, config) + } +} + +func TestVariedInputSpacing(t *testing.T) { + input := []string{ + "Column A |Column B| Column C", + "x|y| z", + } + + config := DefaultConfig() + output := Format(input, config) + + expected := "Column A Column B Column C\n" + expected += "x y z" + + if output != expected { + t.Fatalf("\nexpected:\n%s\n\ngot:\n%s", expected, output) + } +} + +func TestUnmatchedColumnCounts(t *testing.T) { + input := []string{ + "Column A | Column B | Column C", + "Value A | Value B", + "Value A | Value B | Value C | Value D", + } + + config := DefaultConfig() + output := Format(input, config) + + expected := "Column A Column B Column C\n" + expected += "Value A Value B\n" + expected += "Value A Value B Value C Value D" + + if output != expected { + t.Fatalf("\nexpected:\n%s\n\ngot:\n%s", expected, output) + } +} + +func TestAlternateDelimiter(t *testing.T) { + input := []string{ + "Column | A % Column | B % Column | C", + "Value A % Value B % Value C", + } + + config := DefaultConfig() + config.Delim = "%" + output := Format(input, config) + + expected := "Column | A Column | B Column | C\n" + expected += "Value A Value B Value C" + + if output != expected { + t.Fatalf("\nexpected:\n%s\n\ngot:\n%s", expected, output) + } +} + +func TestAlternateSpacingString(t *testing.T) { + input := []string{ + "Column A | Column B | Column C", + "x | y | z", + } + + config := DefaultConfig() + config.Glue = " " + output := Format(input, config) + + expected := "Column A Column B Column C\n" + expected += "x y z" + + if output != expected { + t.Fatalf("\nexpected:\n%s\n\ngot:\n%s", expected, output) + } +} + +func TestSimpleFormat(t *testing.T) { + input := []string{ + "Column A | Column B | Column C", + "x | y | z", + } + + output := SimpleFormat(input) + + expected := "Column A Column B Column C\n" + expected += "x y z" + + if output != expected { + t.Fatalf("\nexpected:\n%s\n\ngot:\n%s", expected, output) + } +} + +func TestAlternatePrefixString(t *testing.T) { + input := []string{ + "Column A | Column B | Column C", + "x | y | z", + } + + config := DefaultConfig() + config.Prefix = " " + output := Format(input, config) + + expected := " Column A Column B Column C\n" + expected += " x y z" + + if output != expected { + t.Fatalf("\nexpected:\n%s\n\ngot:\n%s", expected, output) + } +} + +func TestEmptyFieldReplacement(t *testing.T) { + input := []string{ + "Column A | Column B | Column C", + "x | | z", + } + + config := DefaultConfig() + config.Empty = "" + output := Format(input, config) + + expected := "Column A Column B Column C\n" + expected += "x z" + + if output != expected { + t.Fatalf("\nexpected:\n%s\n\ngot:\n%s", expected, output) + } +} + +func TestEmptyConfigValues(t *testing.T) { + input := []string{ + "Column A | Column B | Column C", + "x | y | z", + } + + config := Config{} + output := Format(input, &config) + + expected := "Column A Column B Column C\n" + expected += "x y z" + + if output != expected { + t.Fatalf("\nexpected:\n%s\n\ngot:\n%s", expected, output) + } +} + +func TestMergeConfig(t *testing.T) { + conf1 := &Config{Delim: "a", Glue: "a", Prefix: "a", Empty: "a"} + conf2 := &Config{Delim: "b", Glue: "b", Prefix: "b", Empty: "b"} + conf3 := &Config{Delim: "c", Prefix: "c"} + + m := MergeConfig(conf1, conf2) + if m.Delim != "b" || m.Glue != "b" || m.Prefix != "b" || m.Empty != "b" { + t.Fatalf("bad: %#v", m) + } + + m = MergeConfig(conf1, conf3) + if m.Delim != "c" || m.Glue != "a" || m.Prefix != "c" || m.Empty != "a" { + t.Fatalf("bad: %#v", m) + } + + m = MergeConfig(conf1, nil) + if m.Delim != "a" || m.Glue != "a" || m.Prefix != "a" || m.Empty != "a" { + t.Fatalf("bad: %#v", m) + } + + m = MergeConfig(conf1, &Config{}) + if m.Delim != "a" || m.Glue != "a" || m.Prefix != "a" || m.Empty != "a" { + t.Fatalf("bad: %#v", m) + } +} diff --git a/plugins/repo/subcommands/gc b/plugins/repo/subcommands/gc deleted file mode 100755 index 0fd3f281e..000000000 --- a/plugins/repo/subcommands/gc +++ /dev/null @@ -1,15 +0,0 @@ -#!/usr/bin/env bash -set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x -source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" - -repo_gc() { - declare desc="runs 'git gc --agressive' against the application's repo" - local cmd="repo:gc" - [[ -z $2 ]] && dokku_log_fail "Please specify an app to run the command on" - local APP="$2"; local APP_DIR="$DOKKU_ROOT/$APP" - verify_app_name "$APP" - - GIT_DIR="$APP_DIR" git gc --aggressive -} - -repo_gc "$@" diff --git a/plugins/repo/subcommands/purge-cache b/plugins/repo/subcommands/purge-cache deleted file mode 100755 index 4bebd43a0..000000000 --- a/plugins/repo/subcommands/purge-cache +++ /dev/null @@ -1,20 +0,0 @@ -#!/usr/bin/env bash -set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x -source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" - -repo_purge_cache() { - declare desc="deletes the contents of the build cache stored in the repository" - local cmd="repo:purge-cache" - [[ -z $2 ]] && dokku_log_fail "Please specify an app to run the command on" - local APP="$2"; - verify_app_name "$APP" - - local IMAGE=$(get_deploying_app_image_name "$APP"); local CACHE_DIR="$DOKKU_ROOT/$APP/cache" - - if [[ -d $CACHE_DIR ]]; then - docker run "$DOKKU_GLOBAL_RUN_ARGS" --rm -v "$CACHE_DIR:/cache" "$IMAGE" find /cache -depth -mindepth 1 -maxdepth 1 -exec rm -Rf {} \; || true - mkdir -p "$CACHE_DIR" || true - fi -} - -repo_purge_cache "$@" diff --git a/plugins/shell/plugin.toml b/plugins/shell/plugin.toml index 3e96e7c2e..29d146b14 100644 --- a/plugins/shell/plugin.toml +++ b/plugins/shell/plugin.toml @@ -1,4 +1,4 @@ [plugin] description = "dokku core shell plugin" -version = "0.7.2" +version = "0.9.4" [plugin.config] diff --git a/plugins/ssh-keys/commands b/plugins/ssh-keys/commands index bfd36a0ce..1aabfd445 100755 --- a/plugins/ssh-keys/commands +++ b/plugins/ssh-keys/commands @@ -1,29 +1,11 @@ #!/usr/bin/env bash [[ " help ssh-keys:help " == *" $1 "* ]] || exit "$DOKKU_NOT_IMPLEMENTED_EXIT" +source "$PLUGIN_AVAILABLE_PATH/ssh-keys/internal-functions" set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x case "$1" in help | ssh-keys:help) - help_content_func () { - declare desc="return ssh-keys plugin help content" - cat< [/path/to/key], Add a new public key by pipe or path - ssh-keys:remove , Remove SSH public key by name -help_content - } - - if [[ $1 = "ssh-keys:help" ]] ; then - echo -e 'Usage: dokku ssh-keys[:COMMAND]' - echo '' - echo 'Manage public ssh keys that are allowed to connect to Dokku' - echo '' - echo 'Additional commands:' - help_content_func | sort | column -c2 -t -s, - else - help_content_func - fi + ssh_keys_help_cmd "$@" ;; *) diff --git a/plugins/ssh-keys/functions b/plugins/ssh-keys/functions index 32e7e971e..2d5d1ebf0 100755 --- a/plugins/ssh-keys/functions +++ b/plugins/ssh-keys/functions @@ -2,13 +2,29 @@ set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" +list_ssh_keys() { + declare desc="List ssh key hashes" + local cmd="ssh-keys:list" + verify_ssh_key_file + sshcommand list dokku +} + verify_ssh_key_file() { declare desc="Test that public key is valid" [[ -s ${DOKKU_ROOT}/.ssh/authorized_keys ]] || dokku_log_fail "No public keys found." - ssh-keygen -l -f "${DOKKU_ROOT}/.ssh/authorized_keys" &> /dev/null || dokku_log_fail "${DOKKU_ROOT}/.ssh/authorized_keys failed ssh-keygen check." + local key line=0 + while read -r key; do + line=$((line + 1)) + ssh-keygen -l -f /dev/stdin <<< "$key" &> /dev/null || dokku_log_fail "${DOKKU_ROOT}/.ssh/authorized_keys line $line failed ssh-keygen check." + done <"${DOKKU_ROOT}/.ssh/authorized_keys" } verify_ssh_key_exists() { declare desc="Test that public key exists" [[ -e ${DOKKU_ROOT}/.ssh/authorized_keys ]] || dokku_log_fail "No public keys found." } + +create_ssh_key_file() { + declare desc="Ensure the public key file exists" + touch "${DOKKU_ROOT}/.ssh/authorized_keys" +} diff --git a/plugins/ssh-keys/internal-functions b/plugins/ssh-keys/internal-functions new file mode 100755 index 000000000..e4bff023f --- /dev/null +++ b/plugins/ssh-keys/internal-functions @@ -0,0 +1,28 @@ +#!/usr/bin/env bash +set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x + +ssh_keys_help_content_func() { + declare desc="return ssh-keys plugin help content" + cat< [/path/to/key], Add a new public key by pipe or path + ssh-keys:remove , Remove SSH public key by name +help_content +} + +ssh_keys_help_cmd() { + if [[ $1 = "ssh-keys:help" ]] ; then + echo -e 'Usage: dokku ssh-keys[:COMMAND]' + echo '' + echo 'Manage public ssh keys that are allowed to connect to Dokku' + echo '' + echo 'Additional commands:' + ssh_keys_help_content_func | sort | column -c2 -t -s, + elif [[ $(ps -o command= $PPID) == *"--all"* ]]; then + ssh_keys_help_content_func + else + cat< /dev/null || dokku_log_fail "Key from file is not a valid ssh public key" fi [[ -n "$name" && -n "$key_contents" ]] || dokku_log_fail "Two arguments are required if not piping, ie: dokku ssh-keys:add " + local count="$(wc -l <<< "$key_contents")" + [ "$count" -eq 1 ] || dokku_log_fail 'Too many keys provided, set one per invocation of dokku ssh-keys:add ' + create_ssh_key_file verify_ssh_key_exists echo "$key_contents" | sshcommand acl-add dokku "$name" || dokku_log_fail "sshcommand returned an error: $?" + verify_ssh_key_file } add_keys "$@" diff --git a/plugins/ssh-keys/subcommands/default b/plugins/ssh-keys/subcommands/default new file mode 100755 index 000000000..2260f6e62 --- /dev/null +++ b/plugins/ssh-keys/subcommands/default @@ -0,0 +1,5 @@ +#!/usr/bin/env bash +set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x +source "$PLUGIN_AVAILABLE_PATH/ssh-keys/internal-functions" + +ssh_keys_help_cmd "ssh-keys:help" diff --git a/plugins/ssh-keys/subcommands/list b/plugins/ssh-keys/subcommands/list index ce485bbd6..fb78c5e2a 100755 --- a/plugins/ssh-keys/subcommands/list +++ b/plugins/ssh-keys/subcommands/list @@ -3,11 +3,4 @@ set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" source "$PLUGIN_AVAILABLE_PATH/ssh-keys/functions" -list_ssh_keys() { - declare desc="List ssh key hashes" - local cmd="ssh-keys:list" - verify_ssh_key_file - sshcommand list dokku -} - list_ssh_keys "$@" diff --git a/plugins/storage/commands b/plugins/storage/commands index 337f07473..d4335a84c 100755 --- a/plugins/storage/commands +++ b/plugins/storage/commands @@ -1,29 +1,11 @@ #!/usr/bin/env bash [[ " storage:help help " == *" $1 "* ]] || exit "$DOKKU_NOT_IMPLEMENTED_EXIT" +source "$PLUGIN_AVAILABLE_PATH/storage/internal-functions" set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x case "$1" in help | storage:help) - help_content_func () { - declare desc="return storage plugin help content" - cat<, List bind mounts for app's container(s) (host:container) - storage:mount , Create a new bind mount - storage:unmount , Remove an existing bind mount -help_content - } - - if [[ $1 = "shell:help" ]] ; then - echo -e 'Usage: dokku storage:COMMAND' - echo '' - echo 'Mount local volume / directories inside containers.' - echo '' - echo 'Additional commands:' - help_content_func | sort | column -c2 -t -s, - else - help_content_func - fi + storage_help_cmd "$@" ;; *) diff --git a/plugins/storage/functions b/plugins/storage/functions index 767059f13..643fa906c 100755 --- a/plugins/storage/functions +++ b/plugins/storage/functions @@ -1,6 +1,7 @@ #!/usr/bin/env bash set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" +source "$PLUGIN_AVAILABLE_PATH/docker-options/functions" verify_paths() { declare desc="verifies storage paths" diff --git a/plugins/storage/internal-functions b/plugins/storage/internal-functions new file mode 100755 index 000000000..6224e4966 --- /dev/null +++ b/plugins/storage/internal-functions @@ -0,0 +1,42 @@ +#!/usr/bin/env bash +set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x +source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" +source "$PLUGIN_AVAILABLE_PATH/docker-options/functions" +source "$PLUGIN_AVAILABLE_PATH/storage/functions" + +storage_help_content_func() { + declare desc="return storage plugin help content" + cat<, List bind mounts for app's container(s) (host:container) + storage:mount , Create a new bind mount + storage:report [] [], Displays a checks report for one or more apps + storage:unmount , Remove an existing bind mount +help_content +} + +storage_help_cmd() { + if [[ $1 = "storage:help" ]] ; then + echo -e 'Usage: dokku storage[:COMMAND]' + echo '' + echo 'Mount local volume / directories inside containers.' + echo '' + echo 'Additional commands:' + storage_help_content_func | sort | column -c2 -t -s, + elif [[ $(ps -o command= $PPID) == *"--all"* ]]; then + storage_help_content_func + else + cat<, Reads an tarball containing the app from stdin - tar:from , Loads an app tarball from url -help_content - } - - if [[ $1 = "tar:help" ]] ; then - echo -e 'Usage: dokku tar[:COMMAND]' - echo '' - echo 'An alternative to using git, apps are loaded via tarballs instead.' - echo '' - echo 'Additional commands:' - help_content_func | sort | column -c2 -t -s, - else - help_content_func - fi + tar_help_cmd "$1" ;; *) diff --git a/plugins/tar/functions b/plugins/tar/functions index 93b024645..f90d879f5 100755 --- a/plugins/tar/functions +++ b/plugins/tar/functions @@ -24,7 +24,9 @@ tar_build() { tar -x -C "$TAR_BUILD_TMP_WORK_DIR" -f "$DOKKU_ROOT/$APP/src.tar" --strip-components="$BOGUS_PARTS" chmod -R u+r "$TAR_BUILD_TMP_WORK_DIR" - if [[ -f Dockerfile ]] && [[ "$([[ -f .env ]] && grep -q BUILDPACK_URL .env; echo $?)" != "0" ]] && [[ ! -f ".buildpacks" ]]; then + plugn trigger post-extract "$APP" "$TAR_BUILD_TMP_WORK_DIR" + + if [[ -f Dockerfile ]] && [[ "$([[ -f .env ]] && grep -q BUILDPACK_URL .env; echo $?)" != "0" ]] && [[ ! -f ".buildpacks" ]] && [[ -z $(config_get "$APP" BUILDPACK_URL || true) ]]; then plugn trigger pre-receive-app "$APP" "dockerfile" "$TAR_BUILD_TMP_WORK_DIR" dokku_receive "$APP" "dockerfile" "$TAR_BUILD_TMP_WORK_DIR" | sed -u "s/^/"$'\e[1G'"/" else diff --git a/plugins/tar/internal-functions b/plugins/tar/internal-functions new file mode 100755 index 000000000..1d81df165 --- /dev/null +++ b/plugins/tar/internal-functions @@ -0,0 +1,27 @@ +#!/usr/bin/env bash +set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x + +tar_help_content_func() { + declare desc="return tar plugin help content" + cat<, Reads an tarball containing the app from stdin + tar:from , Loads an app tarball from url +help_content +} + +tar_help_cmd() { + if [[ $1 = "tar:help" ]] ; then + echo -e 'Usage: dokku tar[:COMMAND]' + echo '' + echo 'An alternative to using git, apps are loaded via tarballs instead.' + echo '' + echo 'Additional commands:' + tar_help_content_func | sort | column -c2 -t -s, + elif [[ $(ps -o command= $PPID) == *"--all"* ]]; then + tar_help_content_func + else + cat< Installing rpm build requirements" + @sudo apt-get update -qq > /dev/null + @sudo DEBIAN_FRONTEND=noninteractive DEBCONF_NONINTERACTIVE_SEEN=true apt-get install -qq -y gcc git build-essential wget ruby-dev ruby1.9.1 rpm > /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 + +rpm-herokuish: + rm -rf /tmp/tmp /tmp/build $(HEROKUISH_RPM_PACKAGE_NAME) + mkdir -p /tmp/tmp /tmp/build + + @echo "-> Cloning repository" + git clone -q "https://github.com/$(HEROKUISH_REPO_NAME).git" \ + --branch "v$(HEROKUISH_VERSION)" /tmp/tmp/herokuish > /dev/null + rm -rf /tmp/tmp/herokuish/.git /tmp/tmp/herokuish/.gitignore + + @echo "-> Copying files into place" + mkdir -p "/tmp/build/var/lib" + cp -rf /tmp/tmp/herokuish /tmp/build/var/lib/herokuish + + @echo "-> Creating $(HEROKUISH_RPM_PACKAGE_NAME)" + sudo fpm -t rpm -s dir -C /tmp/build -n herokuish \ + -v $(HEROKUISH_VERSION) \ + -a $(RPM_ARCHITECTURE) \ + -p $(HEROKUISH_RPM_PACKAGE_NAME) \ + --depends 'docker-engine >= 1.9.1' \ + --depends 'sudo' \ + --after-install rpm/herokuish.postinst \ + --url "https://github.com/$(HEROKUISH_REPO_NAME)" \ + --description $(HEROKUISH_DESCRIPTION) \ + --license 'MIT License' \ + . + mv *.rpm /tmp + +rpm-dokku: + rm -rf /tmp/tmp /tmp/build dokku_*_$(RPM_ARCHITECTURE).rpm + mkdir -p /tmp/tmp /tmp/build + + mkdir -p /tmp/build/usr/bin + mkdir -p /tmp/build/usr/share/doc/dokku + mkdir -p /tmp/build/usr/share/dokku/contrib + mkdir -p /tmp/build/usr/share/lintian/overrides + mkdir -p /tmp/build/usr/share/man/man1 + mkdir -p /tmp/build/var/lib/dokku/core-plugins/available + + cp dokku /tmp/build/usr/bin + cp LICENSE /tmp/build/usr/share/doc/dokku/copyright + find . -name ".DS_Store" -depth -exec rm {} \; + $(MAKE) go-build + cp common.mk /tmp/build/var/lib/dokku/core-plugins/common.mk + cp -r plugins/* /tmp/build/var/lib/dokku/core-plugins/available + find plugins/ -mindepth 1 -maxdepth 1 -type d -printf '%f\n' | while read plugin; do cd /tmp/build/var/lib/dokku/core-plugins/available/$$plugin && if [ -e Makefile ]; then $(MAKE) src-clean; fi; done + find plugins/ -mindepth 1 -maxdepth 1 -type d -printf '%f\n' | while read plugin; do touch /tmp/build/var/lib/dokku/core-plugins/available/$$plugin/.core; done + rm /tmp/build/var/lib/dokku/core-plugins/common.mk + $(MAKE) help2man + $(MAKE) addman + cp /usr/local/share/man/man1/dokku.1 /tmp/build/usr/share/man/man1/dokku.1 + gzip -9 /tmp/build/usr/share/man/man1/dokku.1 + cp contrib/dokku-installer.py /tmp/build/usr/share/dokku/contrib +ifeq ($(DOKKU_VERSION),master) + git describe --tags > /tmp/build/var/lib/dokku/VERSION +else + echo $(DOKKU_VERSION) > /tmp/build/var/lib/dokku/VERSION +endif + cat /tmp/build/var/lib/dokku/VERSION | cut -d '-' -f 1 | cut -d 'v' -f 2 > /tmp/build/var/lib/dokku/STABLE_VERSION +ifneq (,$(findstring false,$(IS_RELEASE))) + sed -i.bak -e "s/^/`date +%s`-/" /tmp/build/var/lib/dokku/STABLE_VERSION && rm /tmp/build/var/lib/dokku/STABLE_VERSION.bak +endif +ifdef DOKKU_GIT_REV + echo "$(DOKKU_GIT_REV)" > /tmp/build/var/lib/dokku/GIT_REV +else + git rev-parse HEAD > /tmp/build/var/lib/dokku/GIT_REV +endif + + @echo "-> Creating rpm package" + VERSION=$$(cat /tmp/build/var/lib/dokku/STABLE_VERSION); \ + sudo fpm -t rpm -s dir -C /tmp/build -n dokku \ + -v "$$VERSION" \ + -a $(RPM_ARCHITECTURE) \ + -p "dokku-$$VERSION-1.x86_64.rpm" \ + --depends 'git' \ + --depends 'make' \ + --depends 'curl' \ + --depends 'gcc' \ + --depends 'man-db' \ + --depends 'sshcommand' \ + --depends 'gliderlabs-sigil' \ + --depends 'docker-engine >= 1.9.1' \ + --depends 'bind-utils' \ + --depends 'nginx' \ + --depends 'plugn' \ + --depends 'sudo' \ + --depends 'python' \ + --depends 'nc' \ + --after-install rpm/dokku.postinst \ + --url "https://github.com/$(DOKKU_REPO_NAME)" \ + --description $(DOKKU_DESCRIPTION) \ + --license 'MIT License' \ + . + mv *.rpm "/tmp/dokku-`cat /tmp/build/var/lib/dokku/VERSION`-1.$(RPM_ARCHITECTURE).rpm" + +rpm-plugn: + rm -rf /tmp/tmp /tmp/build $(PLUGN_RPM_PACKAGE_NAME) + mkdir -p /tmp/tmp /tmp/build /tmp/build/usr/bin + + @echo "-> Downloading package" + wget -q -O /tmp/tmp/plugn-$(PLUGN_VERSION).tgz $(PLUGN_URL) + cd /tmp/tmp/ && tar zxf /tmp/tmp/plugn-$(PLUGN_VERSION).tgz + + @echo "-> Copying files into place" + cp /tmp/tmp/plugn /tmp/build/usr/bin/plugn && chmod +x /tmp/build/usr/bin/plugn + + @echo "-> Creating $(PLUGN_RPM_PACKAGE_NAME)" + sudo fpm -t rpm -s dir -C /tmp/build -n plugn \ + --version $(PLUGN_VERSION) \ + -a $(RPM_ARCHITECTURE) \ + --package $(PLUGN_RPM_PACKAGE_NAME) \ + --url "https://github.com/$(PLUGN_REPO_NAME)" \ + --category utils \ + --description "$$PLUGN_DESCRIPTION" \ + --license 'MIT License' \ + . + mv *.rpm /tmp + +rpm-sshcommand: + rm -rf /tmp/tmp /tmp/build $(SSHCOMMAND_RPM_PACKAGE_NAME) + mkdir -p /tmp/tmp /tmp/build /tmp/build/usr/bin + + @echo "-> Downloading package" + wget -q -O /tmp/tmp/sshcommand-$(SSHCOMMAND_VERSION) $(SSHCOMMAND_URL) + + @echo "-> Copying files into place" + mkdir -p "/tmp/build/usr/bin" + cp /tmp/tmp/sshcommand-$(SSHCOMMAND_VERSION) /tmp/build/usr/bin/sshcommand + chmod +x /tmp/build/usr/bin/sshcommand + + @echo "-> Creating $(SSHCOMMAND_RPM_PACKAGE_NAME)" + sudo fpm -t rpm -s dir -C /tmp/build -n sshcommand \ + --version $(SSHCOMMAND_VERSION) \ + -a $(RPM_ARCHITECTURE) \ + --package $(SSHCOMMAND_RPM_PACKAGE_NAME) \ + --url "https://github.com/$(SSHCOMMAND_REPO_NAME)" \ + --category admin \ + --description "$$SSHCOMMAND_DESCRIPTION" \ + --license 'MIT License' \ + . + mv *.rpm /tmp + +rpm-sigil: + rm -rf /tmp/tmp /tmp/build $(SIGIL_PACKAGE_NAME) + mkdir -p /tmp/tmp /tmp/build /tmp/build/usr/bin + + @echo "-> Downloading package" + wget -q -O /tmp/tmp/sigil-$(SIGIL_VERSION).tgz $(SIGIL_URL) + cd /tmp/tmp/ && tar zxf /tmp/tmp/sigil-$(SIGIL_VERSION).tgz + + @echo "-> Copying files into place" + cp /tmp/tmp/sigil /tmp/build/usr/bin/sigil && chmod +x /tmp/build/usr/bin/sigil + + @echo "-> Creating $(SIGIL_RPM_PACKAGE_NAME)" + sudo fpm -t rpm -s dir -C /tmp/build -n gliderlabs-sigil \ + --version $(SIGIL_VERSION) \ + -a $(RPM_ARCHITECTURE) \ + --package $(SIGIL_RPM_PACKAGE_NAME) \ + --url "https://github.com/$(SIGIL_REPO_NAME)" \ + --category utils \ + --description "$$SIGIL_DESCRIPTION" \ + --license 'MIT License' \ + . + mv *.rpm /tmp diff --git a/rpm/dokku.postinst b/rpm/dokku.postinst new file mode 100644 index 000000000..8638a9065 --- /dev/null +++ b/rpm/dokku.postinst @@ -0,0 +1,63 @@ +#!/usr/bin/env bash +set +o posix # rpm uses /bin/sh that forces bash in posix mode +set -eo pipefail; [[ $TRACE ]] && set -x + +setup-user() { + echo "Setting up dokku user" + getent passwd dokku >/dev/null || useradd -m -s /bin/bash dokku + mkdir -p "$DOKKU_ROOT/.ssh" + touch "$DOKKU_ROOT/.ssh/authorized_keys" + chown -R dokku:dokku "$DOKKU_ROOT/.ssh" + usermod -aG docker dokku +} + +setup-storage() { + echo "Setting up storage directories" + mkdir -p "${DOKKU_LIB_ROOT}/data" "${DOKKU_LIB_ROOT}/data/storage" + chown dokku:dokku -R "${DOKKU_LIB_ROOT}/data" +} + +setup-plugins() { + echo "Setting up plugin directories" + # should be replaced by `plugn init` + mkdir -p "${DOKKU_LIB_ROOT}/core-plugins/available" "${DOKKU_LIB_ROOT}/plugins/available" + mkdir -p "${DOKKU_LIB_ROOT}/core-plugins/enabled" "${DOKKU_LIB_ROOT}/plugins/enabled" + touch "${DOKKU_LIB_ROOT}/core-plugins/config.toml" "${DOKKU_LIB_ROOT}/plugins/config.toml" + + echo "Enabling all core plugins" + find "${DOKKU_LIB_ROOT}/core-plugins/available" -mindepth 1 -maxdepth 1 -type d -printf '%f\n' | while read -r plugin; do + if [ ! -d "${DOKKU_LIB_ROOT}/plugins/available/$plugin" ]; then + ln -s "${DOKKU_LIB_ROOT}/core-plugins/available/$plugin" "${DOKKU_LIB_ROOT}/plugins/available/$plugin" + PLUGIN_PATH="${DOKKU_LIB_ROOT}/core-plugins" plugn enable "$plugin" + PLUGIN_PATH="${DOKKU_LIB_ROOT}/plugins" plugn enable "$plugin" + fi + done + find -L "${DOKKU_LIB_ROOT}" -type l -delete + chown dokku:dokku -R "${DOKKU_LIB_ROOT}/plugins" "${DOKKU_LIB_ROOT}/core-plugins" + + echo "Install all core plugins" + dokku plugin:install --core +} + +setup-sshcommand() { + echo "Ensure proper sshcommand path" + echo '/usr/bin/dokku' > "${DOKKU_ROOT}/.sshcommand" +} + +setup-version() { + rm -f "${DOKKU_ROOT}/VERSION" + cp "${DOKKU_LIB_ROOT}/STABLE_VERSION" "${DOKKU_ROOT}/VERSION" +} + +main() { + readonly DOKKU_ROOT="${DOKKU_ROOT:-/home/dokku}" + readonly DOKKU_LIB_ROOT="${DOKKU_LIB_PATH:-/var/lib/dokku}" + + setup-user + setup-storage + setup-plugins + setup-sshcommand + setup-version +} + +main diff --git a/rpm/herokuish.postinst b/rpm/herokuish.postinst new file mode 100644 index 000000000..e9c5db778 --- /dev/null +++ b/rpm/herokuish.postinst @@ -0,0 +1,20 @@ +#!/usr/bin/env bash +set +o posix # rpm uses /bin/sh that forces bash in posix mode + +main() { + echo 'Starting docker' + systemctl start docker + + sleep 5 + + count=$(sudo docker images | grep -c gliderlabs/herokuish) + if [ "$count" -ne 0 ]; then + echo 'Removing old herokuish image' + sudo docker rmi gliderlabs/herokuish + fi + + echo 'Importing herokuish into docker (around 5 minutes)' + sudo docker build -t gliderlabs/herokuish /var/lib/herokuish 1> /dev/null +} + +main diff --git a/tests.mk b/tests.mk index 8549d8259..106c17d36 100644 --- a/tests.mk +++ b/tests.mk @@ -73,14 +73,32 @@ lint: @echo linting... @$(QUIET) find . -not -path '*/\.*' -not -path './debian/*' -type f | xargs file | grep text | awk -F ':' '{ print $$1 }' | xargs head -n1 | egrep -B1 "bash" | grep "==>" | awk '{ print $$2 }' | xargs shellcheck -e SC2034 -unit-tests: - @echo running unit tests... +go-tests: + @echo running go unit tests... + docker run --rm -ti \ + -e DOKKU_ROOT=/home/dokku \ + -v $$PWD:$(GO_REPO_ROOT) \ + -w $(GO_REPO_ROOT) \ + $(BUILD_IMAGE) \ + bash -c "go get github.com/onsi/gomega && \ + go list ./... | grep -v /vendor/ | grep -v /tests/apps/ | xargs go test -v -p 1 -race" || exit $$? + +unit-tests: go-tests + @echo running bats unit tests... ifndef UNIT_TEST_BATCH @$(QUIET) bats tests/unit else @$(QUIET) ./tests/ci/unit_test_runner.sh $$UNIT_TEST_BATCH endif +deploy-test-go-fail-predeploy: + @echo deploying go-fail-predeploy app... + cd tests && ./test_deploy ./apps/go-fail-predeploy dokku.me '' true + +deploy-test-go-fail-postdeploy: + @echo deploying go-fail-postdeploy app... + cd tests && ./test_deploy ./apps/go-fail-postdeploy dokku.me '' true + deploy-test-checks-root: @echo deploying checks-root app... cd tests && ./test_deploy ./apps/checks-root dokku.me '' true @@ -156,6 +174,8 @@ deploy-test-static: deploy-tests: @echo running deploy tests... @$(QUIET) $(MAKE) deploy-test-checks-root + @$(QUIET) $(MAKE) deploy-test-go-fail-predeploy + @$(QUIET) $(MAKE) deploy-test-go-fail-postdeploy @$(QUIET) $(MAKE) deploy-test-config @$(QUIET) $(MAKE) deploy-test-clojure @$(QUIET) $(MAKE) deploy-test-dockerfile diff --git a/tests/apps/go-fail-postdeploy/.godir b/tests/apps/go-fail-postdeploy/.godir new file mode 100644 index 000000000..c0772185a --- /dev/null +++ b/tests/apps/go-fail-postdeploy/.godir @@ -0,0 +1 @@ +web diff --git a/tests/apps/go-fail-postdeploy/CHECKS b/tests/apps/go-fail-postdeploy/CHECKS new file mode 100644 index 000000000..52e1189c7 --- /dev/null +++ b/tests/apps/go-fail-postdeploy/CHECKS @@ -0,0 +1 @@ +/ go diff --git a/tests/apps/go-fail-postdeploy/Dockerfile b/tests/apps/go-fail-postdeploy/Dockerfile new file mode 100644 index 000000000..9e748affb --- /dev/null +++ b/tests/apps/go-fail-postdeploy/Dockerfile @@ -0,0 +1,8 @@ +FROM golang:1.7.5 + +EXPOSE 3000/tcp + +ADD . /go/src/github.com/dokku/apps/web +WORKDIR /go/src/github.com/dokku/apps/web + +RUN go install -v ./... diff --git a/tests/apps/go-fail-postdeploy/Procfile b/tests/apps/go-fail-postdeploy/Procfile new file mode 100644 index 000000000..eabc37787 --- /dev/null +++ b/tests/apps/go-fail-postdeploy/Procfile @@ -0,0 +1 @@ +web: /go/bin/web diff --git a/tests/apps/go-fail-postdeploy/app.json b/tests/apps/go-fail-postdeploy/app.json new file mode 100644 index 000000000..eeb2e3e1d --- /dev/null +++ b/tests/apps/go-fail-postdeploy/app.json @@ -0,0 +1,17 @@ +{ + "name": "Sample go app", + "description": "Used in dokku's test suite", + "website": "http://dokku.viewdocs.io/dokku/", + "repository": "https://github.com/dokku/dokku", + "logo": "https://raw.githubusercontent.com/dokku/dokku/master/docs/assets/dokku.png", + "scripts": { + "dokku": { + "postdeploy": "touch /app/postdeploy.test && ls -la /app" + } + }, + "buildpacks": [ + { + "url": "https://github.com/heroku/heroku-buildpack-go" + } + ] +} diff --git a/tests/apps/go-fail-postdeploy/check_deploy b/tests/apps/go-fail-postdeploy/check_deploy new file mode 100755 index 000000000..980dc933a --- /dev/null +++ b/tests/apps/go-fail-postdeploy/check_deploy @@ -0,0 +1,2 @@ +#!/usr/bin/env bash +set -e; test "$(curl -s -S "$1")" == "go" diff --git a/tests/apps/go-fail-postdeploy/web.go b/tests/apps/go-fail-postdeploy/web.go new file mode 100644 index 000000000..3bb0cc5c9 --- /dev/null +++ b/tests/apps/go-fail-postdeploy/web.go @@ -0,0 +1,20 @@ +package main + +import ( + "fmt" + "net/http" + "os" +) + +func main() { + http.HandleFunc("/", hello) + fmt.Println("listening...") + err := http.ListenAndServe(":"+os.Getenv("PORT"), nil) + if err != nil { + panic(err) + } +} + +func hello(res http.ResponseWriter, req *http.Request) { + fmt.Fprintln(res, "go") +} diff --git a/tests/apps/go-fail-predeploy/.godir b/tests/apps/go-fail-predeploy/.godir new file mode 100644 index 000000000..c0772185a --- /dev/null +++ b/tests/apps/go-fail-predeploy/.godir @@ -0,0 +1 @@ +web diff --git a/tests/apps/go-fail-predeploy/CHECKS b/tests/apps/go-fail-predeploy/CHECKS new file mode 100644 index 000000000..52e1189c7 --- /dev/null +++ b/tests/apps/go-fail-predeploy/CHECKS @@ -0,0 +1 @@ +/ go diff --git a/tests/apps/go-fail-predeploy/Dockerfile b/tests/apps/go-fail-predeploy/Dockerfile new file mode 100644 index 000000000..9e748affb --- /dev/null +++ b/tests/apps/go-fail-predeploy/Dockerfile @@ -0,0 +1,8 @@ +FROM golang:1.7.5 + +EXPOSE 3000/tcp + +ADD . /go/src/github.com/dokku/apps/web +WORKDIR /go/src/github.com/dokku/apps/web + +RUN go install -v ./... diff --git a/tests/apps/go-fail-predeploy/Procfile b/tests/apps/go-fail-predeploy/Procfile new file mode 100644 index 000000000..eabc37787 --- /dev/null +++ b/tests/apps/go-fail-predeploy/Procfile @@ -0,0 +1 @@ +web: /go/bin/web diff --git a/tests/apps/go-fail-predeploy/app.json b/tests/apps/go-fail-predeploy/app.json new file mode 100644 index 000000000..76574050f --- /dev/null +++ b/tests/apps/go-fail-predeploy/app.json @@ -0,0 +1,17 @@ +{ + "name": "Sample go app", + "description": "Used in dokku's test suite", + "website": "http://dokku.viewdocs.io/dokku/", + "repository": "https://github.com/dokku/dokku", + "logo": "https://raw.githubusercontent.com/dokku/dokku/master/docs/assets/dokku.png", + "scripts": { + "dokku": { + "predeploy": "touch /app/predeploy.test && ls -la /app" + } + }, + "buildpacks": [ + { + "url": "https://github.com/heroku/heroku-buildpack-go" + } + ] +} diff --git a/tests/apps/go-fail-predeploy/check_deploy b/tests/apps/go-fail-predeploy/check_deploy new file mode 100755 index 000000000..980dc933a --- /dev/null +++ b/tests/apps/go-fail-predeploy/check_deploy @@ -0,0 +1,2 @@ +#!/usr/bin/env bash +set -e; test "$(curl -s -S "$1")" == "go" diff --git a/tests/apps/go-fail-predeploy/web.go b/tests/apps/go-fail-predeploy/web.go new file mode 100644 index 000000000..3bb0cc5c9 --- /dev/null +++ b/tests/apps/go-fail-predeploy/web.go @@ -0,0 +1,20 @@ +package main + +import ( + "fmt" + "net/http" + "os" +) + +func main() { + http.HandleFunc("/", hello) + fmt.Println("listening...") + err := http.ListenAndServe(":"+os.Getenv("PORT"), nil) + if err != nil { + panic(err) + } +} + +func hello(res http.ResponseWriter, req *http.Request) { + fmt.Fprintln(res, "go") +} diff --git a/tests/apps/multi/static/styles/main.scss b/tests/apps/multi/static/styles/main.scss index 93efb53fa..20b7ae981 100644 --- a/tests/apps/multi/static/styles/main.scss +++ b/tests/apps/multi/static/styles/main.scss @@ -1,4 +1,4 @@ -// override scss variables within bootsrap by delaring them +// override scss variables within bootsrap by declaring them // before importing bootstrap $brand-primary: #428bca !default; diff --git a/tests/ci/parallel_runner.sh b/tests/ci/parallel_runner.sh index 3d11995ef..0cca6806e 100755 --- a/tests/ci/parallel_runner.sh +++ b/tests/ci/parallel_runner.sh @@ -79,7 +79,7 @@ case "$CIRCLE_NODE_INDEX" in exit $RC fi echo "=====> make deploy tests" - sudo -E make -e deploy-test-checks-root deploy-test-config deploy-test-multi + sudo -E make -e deploy-test-checks-root deploy-test-config deploy-test-multi deploy-test-go-fail-predeploy deploy-test-go-fail-postdeploy RC=$? if [[ $RC -ne 0 ]]; then echo "exit status: $RC" diff --git a/tests/unit/10_apps.bats b/tests/unit/10_apps.bats index 9558b3e9b..797304716 100644 --- a/tests/unit/10_apps.bats +++ b/tests/unit/10_apps.bats @@ -15,7 +15,7 @@ teardown () { echo "output: "$output echo "status: "$status assert_success - run bash -c "dokku apps | grep $TEST_APP" + run bash -c "dokku apps:list | grep $TEST_APP" echo "output: "$output echo "status: "$status assert_output $TEST_APP @@ -36,7 +36,7 @@ teardown () { echo "output: "$output echo "status: "$status assert_success - run bash -c "dokku apps | grep $TEST_APP" + run bash -c "dokku apps:list | grep $TEST_APP" echo "output: "$output echo "status: "$status assert_output $TEST_APP @@ -64,7 +64,7 @@ teardown () { echo "output: "$output echo "status: "$status assert_success - run bash -c "dokku apps | grep $TEST_APP" + run bash -c "dokku apps:list | grep $TEST_APP" echo "output: "$output echo "status: "$status assert_output "" @@ -90,3 +90,36 @@ teardown () { echo "status: "$status assert_success } + +@test "(apps) apps:clone" { + deploy_app + run bash -c "dokku apps:clone $TEST_APP great-test-name" + echo "output: "$output + echo "status: "$status + assert_success + run bash -c "dokku apps:list | grep $TEST_APP" + echo "output: "$output + echo "status: "$status + assert_success + run bash -c "curl --silent --write-out '%{http_code}\n' `dokku url great-test-name` | grep 404" + echo "output: "$output + echo "status: "$status + assert_output "" + run bash -c "dokku --force apps:destroy great-test-name" + echo "output: "$output + echo "status: "$status + assert_success + + run bash -c "dokku apps:clone --skip-deploy $TEST_APP great-test-name" + echo "output: "$output + echo "status: "$status + assert_success + run bash -c "curl --silent --write-out '%{http_code}\n' `dokku url great-test-name` | grep 404" + echo "output: "$output + echo "status: "$status + assert_failure + run bash -c "dokku --force apps:destroy great-test-name" + echo "output: "$output + echo "status: "$status + assert_success +} diff --git a/tests/unit/10_certs.bats b/tests/unit/10_certs.bats index e7978ab13..9b2003b88 100644 --- a/tests/unit/10_certs.bats +++ b/tests/unit/10_certs.bats @@ -6,6 +6,7 @@ setup_local_tls() { TLS=$BATS_TMPDIR/tls mkdir -p $TLS tar xf $BATS_TEST_DIRNAME/server_ssl.tar -C $TLS + tar xf $BATS_TEST_DIRNAME/domain_ssl.tar -C $TLS sudo chown -R dokku:dokku $TLS } @@ -33,6 +34,13 @@ teardown() { assert_success } +@test "(certs) certs:add with multiple dots in the filename" { + run bash -c "dokku certs:add $TEST_APP $BATS_TMPDIR/tls/domain.com.crt $BATS_TMPDIR/tls/domain.com.key" + echo "output: "$output + echo "status: "$status + assert_success +} + @test "(certs) certs:add < tar" { run bash -c "dokku certs:add $TEST_APP < $BATS_TEST_DIRNAME/server_ssl.tar" echo "output: "$output diff --git a/tests/unit/10_checks.bats b/tests/unit/10_checks.bats index c0a8fafc5..085c190d0 100644 --- a/tests/unit/10_checks.bats +++ b/tests/unit/10_checks.bats @@ -17,7 +17,7 @@ teardown() { } @test "(checks) checks" { - run bash -c "dokku checks $TEST_APP| grep $TEST_APP | xargs" + run bash -c "dokku checks $TEST_APP 2> /dev/null | grep $TEST_APP | xargs" echo "output: "$output echo "status: "$status assert_output "$TEST_APP none none" diff --git a/tests/unit/20_build-env.bats b/tests/unit/20_build-env.bats index c28413d1a..dc7affbe8 100644 --- a/tests/unit/20_build-env.bats +++ b/tests/unit/20_build-env.bats @@ -35,12 +35,12 @@ teardown() { assert_success deploy_app - run /bin/bash -c "dokku config:get --global CURL_CONNECT_TIMEOUT | grep 5" + run /bin/bash -c "dokku config:get --global CURL_CONNECT_TIMEOUT | grep 90" echo "output: "$output echo "status: "$status assert_success - run /bin/bash -c "dokku config:get --global CURL_TIMEOUT | grep 30" + run /bin/bash -c "dokku config:get --global CURL_TIMEOUT | grep 60" echo "output: "$output echo "status: "$status assert_success diff --git a/tests/unit/20_config.bats b/tests/unit/20_config.bats index a5894372c..87dafa332 100644 --- a/tests/unit/20_config.bats +++ b/tests/unit/20_config.bats @@ -145,3 +145,17 @@ teardown() { echo "status: "$status assert_success } + +@test "(config) deploy specific DOKKU_DEPLOY_BRANCH" { + run ssh dokku@dokku.me config:set --global DOKKU_DEPLOY_BRANCH=global-branch + GIT_REMOTE_BRANCH=global-branch deploy_app + echo "output: "$output + echo "status: "$status + assert_success + + run ssh dokku@dokku.me config:set $TEST_APP DOKKU_DEPLOY_BRANCH=app-branch + GIT_REMOTE_BRANCH=app-branch deploy_app + echo "output: "$output + echo "status: "$status + assert_success +} diff --git a/tests/unit/20_docker-options.bats b/tests/unit/20_docker-options.bats index 7c9432f68..fe0db6685 100644 --- a/tests/unit/20_docker-options.bats +++ b/tests/unit/20_docker-options.bats @@ -69,7 +69,7 @@ teardown() { echo "output: "$output echo "status: "$status assert_success - run /bin/bash -c "dokku docker-options $TEST_APP | xargs" + run /bin/bash -c "dokku docker-options $TEST_APP 2> /dev/null | xargs" echo "output: "$output echo "status: "$status assert_output "Deploy options: --restart=on-failure:10" @@ -88,7 +88,7 @@ teardown() { echo "output: "$output echo "status: "$status assert_success - run /bin/bash -c "dokku docker-options $TEST_APP build" + run /bin/bash -c "dokku docker-options $TEST_APP build 2> /dev/null" echo "output: "$output echo "status: "$status assert_output "Build options: none" @@ -107,7 +107,7 @@ teardown() { echo "output: "$output echo "status: "$status assert_success - run /bin/bash -c "dokku docker-options $TEST_APP deploy | xargs" + run /bin/bash -c "dokku docker-options $TEST_APP deploy 2> /dev/null | xargs" echo "output: "$output echo "status: "$status assert_output "Deploy options: --restart=on-failure:10" @@ -126,7 +126,7 @@ teardown() { echo "output: "$output echo "status: "$status assert_success - run /bin/bash -c "dokku docker-options $TEST_APP run" + run /bin/bash -c "dokku docker-options $TEST_APP run 2> /dev/null" echo "output: "$output echo "status: "$status assert_output "Run options: none" diff --git a/tests/unit/20_domains.bats b/tests/unit/20_domains.bats index 3aba4aa8f..9ec3f345a 100644 --- a/tests/unit/20_domains.bats +++ b/tests/unit/20_domains.bats @@ -18,7 +18,7 @@ teardown() { @test "(domains) domains" { dokku domains:setup $TEST_APP - run bash -c "dokku domains $TEST_APP | grep ${TEST_APP}.dokku.me" + run bash -c "dokku domains $TEST_APP 2> /dev/null | grep ${TEST_APP}.dokku.me" echo "output: "$output echo "status: "$status assert_output "${TEST_APP}.dokku.me" @@ -45,7 +45,7 @@ teardown() { echo "status: "$status assert_success - run dokku domains $TEST_APP + run dokku domains $TEST_APP 2> /dev/null echo "output: "$output echo "status: "$status assert_success @@ -61,7 +61,7 @@ teardown() { echo "status: "$status assert_success - run dokku domains $TEST_APP + run dokku domains $TEST_APP 2> /dev/null echo "output: "$output echo "status: "$status assert_success @@ -101,7 +101,7 @@ teardown() { echo "status: "$status assert_success - run dokku domains $TEST_APP + run dokku domains $TEST_APP 2> /dev/null echo "output: "$output echo "status: "$status assert_success @@ -119,7 +119,7 @@ teardown() { echo "status: "$status assert_success - run dokku domains $TEST_APP + run dokku domains $TEST_APP 2> /dev/null echo "output: "$output echo "status: "$status assert_success @@ -139,13 +139,34 @@ teardown() { echo "status: "$status assert_success - run dokku domains $TEST_APP + run dokku domains $TEST_APP 2> /dev/null echo "output: "$output echo "status: "$status assert_success refute_line *.dokku.me } +@test "(domains) domains:set" { + run dokku domains:add $TEST_APP www.test.app.dokku.me test.app.dokku.me + echo "output: "$output + echo "status: "$status + assert_success + + run dokku domains:set $TEST_APP 2.app.dokku.me a--domain.with--hyphens + echo "output: "$output + echo "status: "$status + assert_success + + run dokku domains $TEST_APP 2> /dev/null + echo "output: "$output + echo "status: "$status + assert_success + refute_line www.test.app.dokku.me + refute_line test.app.dokku.me + assert_line 2.app.dokku.me + assert_line a--domain.with--hyphens +} + @test "(domains) domains:clear" { run dokku domains:add $TEST_APP test.app.dokku.me echo "output: "$output @@ -157,7 +178,7 @@ teardown() { echo "status: "$status assert_success - run dokku domains $TEST_APP + run dokku domains $TEST_APP 2> /dev/null echo "output: "$output echo "status: "$status assert_success @@ -170,7 +191,7 @@ teardown() { echo "status: "$status assert_success - run bash -c "dokku domains | grep -q global.dokku.me" + run bash -c "dokku domains 2> /dev/null | egrep -qw '^global.dokku.me\$'" echo "output: "$output echo "status: "$status assert_success @@ -182,17 +203,17 @@ teardown() { echo "status: "$status assert_success - run bash -c "dokku domains | grep -q global1.dokku.me" + run bash -c "dokku domains 2> /dev/null | grep -q global1.dokku.me" echo "output: "$output echo "status: "$status assert_success - run bash -c "dokku domains | grep -q global2.dokku.me" + run bash -c "dokku domains 2> /dev/null | grep -q global2.dokku.me" echo "output: "$output echo "status: "$status assert_success - run bash -c "dokku domains | grep -q global3.dokku.me" + run bash -c "dokku domains 2> /dev/null | grep -q global3.dokku.me" echo "output: "$output echo "status: "$status assert_success @@ -217,13 +238,34 @@ teardown() { assert_success dokku domains:setup $TEST_APP - run bash -c "dokku domains $TEST_APP | grep -q ${TEST_APP}.global1.dokku.me" + + run dokku domains $TEST_APP 2> /dev/null + echo "output: "$output + echo "status: "$status + assert_success + assert_line ${TEST_APP}.global1.dokku.me + assert_line ${TEST_APP}.global2.dokku.me +} + +@test "(domains) domains:set-global" { + run dokku domains:add-global global1.dokku.me global2.dokku.me echo "output: "$output echo "status: "$status assert_success - run bash -c "dokku domains $TEST_APP | grep -q ${TEST_APP}.global2.dokku.me" + run dokku domains:set-global global3.dokku.me global4.dokku.me echo "output: "$output echo "status: "$status assert_success + + dokku domains:setup $TEST_APP + + run dokku domains $TEST_APP 2> /dev/null + echo "output: "$output + echo "status: "$status + assert_success + refute_line ${TEST_APP}.global1.dokku.me + refute_line ${TEST_APP}.global2.dokku.me + assert_line ${TEST_APP}.global3.dokku.me + assert_line ${TEST_APP}.global4.dokku.me } diff --git a/tests/unit/30_ssh_keys.bats b/tests/unit/30_ssh_keys.bats index d3f157c04..6f4b3e287 100644 --- a/tests/unit/30_ssh_keys.bats +++ b/tests/unit/30_ssh_keys.bats @@ -4,10 +4,31 @@ load test_helper setup() { global_setup + + # create a temporary key and save in a variable create_key + local KEY="$(cat /tmp/testkey.pub)" + destroy_key + + # now create key that will be really used + create_key + + # the temporary key is useful for adding in the file with two keys + # useful for a negative test + { cat /tmp/testkey.pub ; echo "$KEY" ; } > /tmp/testkey-double.pub + + # another negative test input + echo 'invalid!' > /tmp/testkey-invalid.pub + + # save current authorized_keys to remove all changes afterwards + cp "${DOKKU_ROOT:-/home/dokku}/.ssh/authorized_keys" /tmp/testkey-authorized_keys } teardown() { + # restore authorized_keys to its contents before the tests + cp /tmp/testkey-authorized_keys "${DOKKU_ROOT:-/home/dokku}/.ssh/authorized_keys" + + # normal cleanup after here destroy_key global_teardown } @@ -37,4 +58,26 @@ teardown() { echo "output: "$output echo "status: "$status assert_success + run /bin/bash -c "dokku ssh-keys:add name3 /tmp/testkey-double.pub" + echo "output: "$output + echo "status: "$status + assert_failure + run /bin/bash -c "dokku ssh-keys:add name4 /tmp/testkey-invalid.pub" + echo "output: "$output + echo "status: "$status + assert_failure + run /bin/bash -c "dokku ssh-keys:add name4 /tmp/testkey.pub" + echo "output: "$output + echo "status: "$status + assert_success + + # leave this as the last test in the sequence! It introduces an error in authorized_keys + run /bin/bash -c 'echo invalid >> "${DOKKU_ROOT:-/home/dokku}/.ssh/authorized_keys"' + echo "output: "$output + echo "status: "$status + assert_success + run /bin/bash -c "dokku ssh-keys:add name5 /tmp/testkey.pub" + echo "output: "$output + echo "status: "$status + assert_failure } diff --git a/tests/unit/40_core_2.bats b/tests/unit/40_core_2.bats index 03bbee8ec..561901491 100644 --- a/tests/unit/40_core_2.bats +++ b/tests/unit/40_core_2.bats @@ -41,7 +41,7 @@ build_nginx_config() { echo "output: "$output echo "status: "$status assert_failure - run /bin/bash -c "dokku fakecommand | grep -q 'is not a dokku command'" + run /bin/bash -c "dokku fakecommand 2>&1 | grep -q 'is not a dokku command'" echo "output: "$output echo "status: "$status assert_success diff --git a/tests/unit/40_proxy.bats b/tests/unit/40_proxy.bats index 329da8f33..60ea5037f 100644 --- a/tests/unit/40_proxy.bats +++ b/tests/unit/40_proxy.bats @@ -24,7 +24,7 @@ assert_nonssl_domain() { assert_app_domain() { local domain=$1 - run /bin/bash -c "dokku domains $TEST_APP | grep -xF ${domain}" + run /bin/bash -c "dokku domains $TEST_APP 2> /dev/null | grep -xF ${domain}" echo "output: "$output echo "status: "$status assert_output "${domain}" diff --git a/tests/unit/domain_ssl.tar b/tests/unit/domain_ssl.tar new file mode 100644 index 000000000..b10121314 Binary files /dev/null and b/tests/unit/domain_ssl.tar differ diff --git a/tests/unit/test_helper.bash b/tests/unit/test_helper.bash index e6d16da86..2614c284d 100644 --- a/tests/unit/test_helper.bash +++ b/tests/unit/test_helper.bash @@ -180,7 +180,7 @@ assert_nonssl_domain() { assert_app_domain() { local domain=$1 - run /bin/bash -c "dokku domains $TEST_APP | grep -xF ${domain}" + run /bin/bash -c "dokku domains $TEST_APP 2> /dev/null | grep -xF ${domain}" echo "output: $output" echo "status: $status" assert_output "${domain}" @@ -196,10 +196,11 @@ assert_http_redirect() { } deploy_app() { - 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 "/tmp/dokku.me.XXXXX") - local CUSTOM_PATH="$4" + declare APP_TYPE="$1" GIT_REMOTE="$2" CUSTOM_TEMPLATE="$3" CUSTOM_PATH="$4" + local APP_TYPE=${APP_TYPE:="nodejs-express"} + local GIT_REMOTE=${GIT_REMOTE:="dokku@dokku.me:$TEST_APP"} + local GIT_REMOTE_BRANCH=${GIT_REMOTE_BRANCH:="master"} + local TMP=$(mktemp -d "/tmp/dokku.me.XXXXX") rmdir "$TMP" && cp -r "./tests/apps/$APP_TYPE" "$TMP" @@ -218,7 +219,7 @@ deploy_app() { [[ -f gitignore ]] && mv gitignore .gitignore git add . git commit -m 'initial commit' - git push target master || destroy_app $? + git push target "master:${GIT_REMOTE_BRANCH}" || destroy_app $? } setup_client_repo() {