diff --git a/.dockerignore b/.dockerignore index 4ae7a89ec..84200e4bd 100644 --- a/.dockerignore +++ b/.dockerignore @@ -9,3 +9,5 @@ tests !contrib/dokku-installer.py !contrib/dokku-update !contrib/bash-completion +!contrib/docker +vendor diff --git a/HISTORY.md b/HISTORY.md index 0dcc7c5d1..636061590 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -1,5 +1,401 @@ # History +## 0.17.9 + +Install/update via the bootstrap script: + +```shell +wget https://raw.githubusercontent.com/dokku/dokku/v0.17.9/bootstrap.sh +sudo DOKKU_TAG=v0.17.9 bash bootstrap.sh +``` + +### Bug Fixes + +- #3593: @JakeAngell Fix nginx template for https in "Connection" header + +### Documentation + +- #3595: @josegonzalez Drop extra help output for trace + +## 0.17.8 + +Install/update via the bootstrap script: + +```shell +wget https://raw.githubusercontent.com/dokku/dokku/v0.17.8/bootstrap.sh +sudo DOKKU_TAG=v0.17.8 bash bootstrap.sh +``` + +### Bug Fixes + +- #3591: @palfrey Allow SSH keys with no ending newline + +### New Features + +- #3587: @josegonzalez feat: drop make and gcc as dependencies + +## 0.17.7 + +Install/update via the bootstrap script: + +```shell +wget https://raw.githubusercontent.com/dokku/dokku/v0.17.7/bootstrap.sh +sudo DOKKU_TAG=v0.17.7 bash bootstrap.sh +``` + +### New Features + +- #3586: @josegonzalez ps plugin parallel usage cleanup + +### Documentation + +- #3584: @josegonzalez Fix no-install-recommends documentation + +### Other + +- #3579: @znz Update plugin list + +## 0.17.6 + +Install/update via the bootstrap script: + +```shell +wget https://raw.githubusercontent.com/dokku/dokku/v0.17.6/bootstrap.sh +sudo DOKKU_TAG=v0.17.6 bash bootstrap.sh +``` + +### New Features + +- #3578: @josegonzalez Allow omitting resource args by setting DOKKU_OMIT_RESOURCE_ARGS + +## 0.17.5 + +Install/update via the bootstrap script: + +```shell +wget https://raw.githubusercontent.com/dokku/dokku/v0.17.5/bootstrap.sh +sudo DOKKU_TAG=v0.17.5 bash bootstrap.sh +``` + +### New Features + +- #3576: @josegonzalez Allow setting DOCKER_BIN path for docker execution + +## 0.17.4 + +Install/update via the bootstrap script: + +```shell +wget https://raw.githubusercontent.com/dokku/dokku/v0.17.4/bootstrap.sh +sudo DOKKU_TAG=v0.17.4 bash bootstrap.sh +``` + +### New Features + +- #3575: @josegonzalez Add a way to cleanup apps for specific schedulers + +## 0.17.3 + +Install/update via the bootstrap script: + +```shell +wget https://raw.githubusercontent.com/dokku/dokku/v0.17.3/bootstrap.sh +sudo DOKKU_TAG=v0.17.3 bash bootstrap.sh +``` + +### Bug Fixes + +- #3570: @znz Fix typos in trace help + +### New Features + +- #3574: @josegonzalez Add support for pulling app status from scheduler plugins +- #3571: @znz Simplify hostname_regex + +## 0.17.2 + +Install/update via the bootstrap script: + +```shell +wget https://raw.githubusercontent.com/dokku/dokku/v0.17.2/bootstrap.sh +sudo DOKKU_TAG=v0.17.2 bash bootstrap.sh +``` + +### Bug Fixes + +- #3568: @josegonzalez Correct issue with clearing global domains + +## 0.17.1 + +Install/update via the bootstrap script: + +```shell +wget https://raw.githubusercontent.com/dokku/dokku/v0.17.1/bootstrap.sh +sudo DOKKU_TAG=v0.17.1 bash bootstrap.sh +``` + +### Bug Fixes + +- #3567: @josegonzalez Ignore SC2064 + +## 0.17.0 + +Install/update via the bootstrap script: + +```shell +wget https://raw.githubusercontent.com/dokku/dokku/v0.17.0/bootstrap.sh +sudo DOKKU_TAG=v0.17.0 bash bootstrap.sh +``` + +### Bug Fixes + +- #3565: @josegonzalez Properly cleanup temp files +- #3563: @josegonzalez Properly split strings in sanitized ps:inspect +- #3560: @josegonzalez Resource setting fixes +- #3556: @josegonzalez Add missing domains:clear-global command +- #3554: @znz Switch from rawgit to jsdelivr in manifest.json too + +### New Features + +- #3566: @josegonzalez Allow users to specify wildcard domains +- #3564: @josegonzalez Add config:clear command +- #3477: @alexquick Allow specifying environment variables for dokku run +- #3540: @josegonzalez Do not append the global domain for matching subdomains +- #3559: @josegonzalez Add trace:on and trace:off +- #3561: @josegonzalez feat: disable scaling if app contains DOKKU_SCALE file +- #3558: @josegonzalez Add nginx:show-conf command +- #3549: @josegonzalez Add message indicating that the user is looking at default limits/reservations + +### Documentation + +- #3557: @josegonzalez Standardize on node-js-app in examples +- #3548: @josegonzalez Remove outdated global resource setting + +### Other + +- #3562: @josegonzalez Proxy ports manipulation updates + +## 0.16.4 + +Install/update via the bootstrap script: + +```shell +wget https://raw.githubusercontent.com/dokku/dokku/v0.16.4/bootstrap.sh +sudo DOKKU_TAG=v0.16.4 bash bootstrap.sh +``` + +### Bug Fixes + +- #3547: @josegonzalez Correct retrieval of resource values for alternative schedulers + +### New Features + +- #3546: @josegonzalez Add ability to trigger an arbitrary plugin hook + +## 0.16.3 + +Install/update via the bootstrap script: + +```shell +wget https://raw.githubusercontent.com/dokku/dokku/v0.16.3/bootstrap.sh +sudo DOKKU_TAG=v0.16.3 bash bootstrap.sh +``` + +### Bug Fixes + +- #3541: @josegonzalez Handle case where image is null on first deploy + +### New Features + +- #3543: @josegonzalez Add ability to clear global domains +- #3517: @josegonzalez SSH key updates +- #3538: @josegonzalez Silence dokku run 'errors' + +### Documentation + +- #3523: @MarcDiethelm Add an example how to specify a Dockerfile for deployment +- #3539: @josegonzalez Warn users when ufw is enabled + +## 0.16.2 + +Install/update via the bootstrap script: + +```shell +wget https://raw.githubusercontent.com/dokku/dokku/v0.16.2/bootstrap.sh +sudo DOKKU_TAG=v0.16.2 bash bootstrap.sh +``` + +### Documentation + +- #3536: @josegonzalez docs: Add documentation for nginx-pre-reload limitation +- #3535: @josegonzalez Add help output for apps:exists + +## 0.16.1 + +Install/update via the bootstrap script: + +```shell +wget https://raw.githubusercontent.com/dokku/dokku/v0.16.1/bootstrap.sh +sudo DOKKU_TAG=v0.16.1 bash bootstrap.sh +``` + +### New Features + +- #3532: @josegonzalez refactor: allow the scheduler to decide if an app is deployed + +### Documentation + +- #3530: @jhstatewide Updated info about supported Ubuntu versions + +## 0.16.0 + +Install/update via the bootstrap script: + +```shell +wget https://raw.githubusercontent.com/dokku/dokku/v0.16.0/bootstrap.sh +sudo DOKKU_TAG=v0.16.0 bash bootstrap.sh +``` + +### Bug Fixes + +- #3527: @josegonzalez Use DOKKU_IMAGE for report command + +### New Features + +- #3528: @josegonzalez feat: add support for quiet ps:scale output +- #3516: @ape-box fix nginx template with Connection header to http_connection +- #3513: @Mayeu Support installing plugins via file:// scheme + +### Documentation + +- #3525: @artofrawr add multi dockerfile plugin to plugins.md +- #3518: @renestalder Add GitLab GIT_STRATEGY for stop_preview_app +- #3506: @vanastassiou Clarify application deployment documentation +- #3512: @Mayeu Update example app in deploy tutorial + +## 0.15.5 + +Install/update via the bootstrap script: + +```shell +wget https://raw.githubusercontent.com/dokku/dokku/v0.15.5/bootstrap.sh +sudo DOKKU_TAG=v0.15.5 bash bootstrap.sh +``` + +### New Features + +- #3511: @josegonzalez Add json output to config:export + +### Documentation + +- #3510: @josegonzalez Remove SPONSORS doc in favor of OpenCollective +- #3509: @Neamar Missing space in markdown formatting +- #3507: @renestalder Fix GitLab CI examples + +## 0.15.4 + +Install/update via the bootstrap script: + +```shell +wget https://raw.githubusercontent.com/dokku/dokku/v0.15.4/bootstrap.sh +sudo DOKKU_TAG=v0.15.4 bash bootstrap.sh +``` + +### Bug Fixes + +- #3499: @josegonzalez Ensure the .buildpacks file exists before writing to it +- #3500: @josegonzalez Always allow ps:scale proc=0 +- #3497: @josegonzalez Allow reporting domains when there are none specified + +### New Features + +- #3502: @josegonzalez Add a trigger to fetch the git revision +- #3501: @josegonzalez Move log retrieval to docker-local scheduler +- #3498: @josegonzalez Add ability to report domains globally +- #3496: @josegonzalez Cleanup glide plugins when running src-clean + +### Documentation + +- #3504: @vanastassiou Edits for readability and conciseness +- #3490: @josegonzalez Add missing link to resource management docs + +## 0.15.3 + +Install/update via the bootstrap script: + +```shell +wget https://raw.githubusercontent.com/dokku/dokku/v0.15.3/bootstrap.sh +sudo DOKKU_TAG=v0.15.3 bash bootstrap.sh +``` + +### New Features + +- #3489: @josegonzalez feat: upgrade herokuish to 0.5.0 (Ubuntu 18.04) + +## 0.15.2 + +Install/update via the bootstrap script: + +```shell +wget https://raw.githubusercontent.com/dokku/dokku/v0.15.2/bootstrap.sh +sudo DOKKU_TAG=v0.15.2 bash bootstrap.sh +``` + +### Bug Fixes + +- #3488: @josegonzalez fix: call ps:retire from system service + +## 0.15.1 + +Install/update via the bootstrap script: + +```shell +wget https://raw.githubusercontent.com/dokku/dokku/v0.15.1/bootstrap.sh +sudo DOKKU_TAG=v0.15.1 bash bootstrap.sh +``` + +### Bug Fixes + +- #3485: @josegonzalez fix: ensure 'dokku report' always succeeds for any app + +## 0.15.0 + +Install/update via the bootstrap script: + +```shell +wget https://raw.githubusercontent.com/dokku/dokku/v0.15.0/bootstrap.sh +sudo DOKKU_TAG=v0.15.0 bash bootstrap.sh +``` + +### Bug Fixes + +- #3479: @josegonzalez Turn off logging for nginx validate configuration +- #3470: @josegonzalez fix: correct the argument for get_release_cmd + +### New Features + +- #3469: @josegonzalez Resource management +- #3466: @josegonzalez Quieter builds +- #3467: @josegonzalez feat: update golang in use for binary building +- #3465: @josegonzalez Vagrant VM Enhancements +- #3464: @josegonzalez Upgrade to go-procfile-util version 0.6.0 +- #3463: @josegonzalez Implement version flags +- #3462: @josegonzalez Upgrade procfile-util +- #3449: @josegonzalez Only override the `WORKDIR` in copy_from_image if the image is `gliderlabs/herokuish` based +- #3461: @josegonzalez Allow skipping aws releases for plugins +- #3459: @josegonzalez Upgrade to herokuish:0.4.9 +- #3413: @josegonzalez Implement buildpacks plugin + +### Documentation + +- #3482: @multikatt rails-database -> railsdatabase +- #3476: @lazyatom Add chrome plugin to documentation +- #3468: @josegonzalez Doc cleanup + +### Other + +- #3471: @josegonzalez chore: drop plugn package building + ## 0.14.6 Install/update via the bootstrap script: diff --git a/ISSUE_TEMPLATE.md b/ISSUE_TEMPLATE.md index a167f728c..a1b729960 100644 --- a/ISSUE_TEMPLATE.md +++ b/ISSUE_TEMPLATE.md @@ -29,9 +29,7 @@ ### Additional information -- Container Inspect Output (if applicable): - - 0.13.0+: `dokku inspect APP_NAME` - - <0.13.0: `docker inspect CONTAINER_ID`: WARNING, `docker inspect` will print environment variables for some commands, be sure you're not exposing any sensitive information when posting issues. You may replace these values with XXXXXXX. +- Container Inspect Output (if applicable) via `dokku inspect APP_NAME` - `cat /home/dokku//nginx.conf` (if applicable): - Link to the exact repository being deployed (if possible/applicable): - If a deploy is failing or behaving unexpectedly: @@ -40,5 +38,5 @@ - If using buildpacks, which custom buildpacks are in use - If using a `Dockerfile`, the contents of that file - If it exists, the contents of your `Procfile`. -- Output of failing Dokku commands after running `dokku trace on` - (BEWARE: `trace on` will print environment variables for some commands, be sure you're not exposing any sensitive information when posting issues. You may replace these values with XXXXXXX): +- Output of failing Dokku commands after running `dokku trace:on` + (BEWARE: `trace:on` will print environment variables for some commands, be sure you're not exposing any sensitive information when posting issues. You may replace these values with XXXXXXX): diff --git a/Makefile b/Makefile index 40715fcdc..61718d955 100644 --- a/Makefile +++ b/Makefile @@ -32,6 +32,7 @@ include common.mk .PHONY: all apt-update install version copyfiles copyplugin man-db plugins dependencies sshcommand procfile-util plugn docker aufs stack count dokku-installer vagrant-acl-add vagrant-dokku go-build include tests.mk +include package.mk include deb.mk include rpm.mk include arch.mk diff --git a/README.md b/README.md index 02e3f97b0..6bb64accd 100644 --- a/README.md +++ b/README.md @@ -98,8 +98,8 @@ A fresh VM running any of the following operating systems: 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.14.6/bootstrap.sh -sudo DOKKU_TAG=v0.14.6 bash bootstrap.sh +wget https://raw.githubusercontent.com/dokku/dokku/v0.17.9/bootstrap.sh +sudo DOKKU_TAG=v0.17.9 bash bootstrap.sh ``` You can then proceed to the ip address or domain name associated with your server to complete the web-based installation. diff --git a/SPONSORS.md b/SPONSORS.md deleted file mode 100644 index eb1cd5b87..000000000 --- a/SPONSORS.md +++ /dev/null @@ -1,7 +0,0 @@ -#Sponsors - -[Deis](http://deis.io) - -[DigitalOcean](https://www.digitalocean.com/) - -[Melvin Lammerts](http://dokku.net) diff --git a/contrib/dokku-installer.py b/contrib/dokku-installer.py index afd791b67..07065d895 100755 --- a/contrib/dokku-installer.py +++ b/contrib/dokku-installer.py @@ -10,7 +10,7 @@ import subprocess import sys import threading -VERSION = 'v0.14.6' +VERSION = 'v0.17.9' hostname = '' try: @@ -37,6 +37,15 @@ if os.path.isfile(key_file): except subprocess.CalledProcessError: pass +ufw_display = 'block' +try: + command = "sudo ufw status" + ufw_output = subprocess.check_output(command, shell=True).strip() + if "inactive" in ufw_output: + ufw_display = 'none' +except subprocess.CalledProcessError: + ufw_display = 'none' + def check_boot(): if 'onboot' not in sys.argv: @@ -77,6 +86,7 @@ def check_boot(): class GetHandler(SimpleHTTPServer.SimpleHTTPRequestHandler): def do_GET(self): content = PAGE.replace('{VERSION}', VERSION) + content = content.replace('{UFW_DISPLAY}', ufw_display) content = content.replace('{HOSTNAME}', hostname) content = content.replace('{AUTHORIZED_KEYS_LOCATION}', key_file) content = content.replace('{ADMIN_KEYS}', "\n".join(admin_keys)) @@ -294,6 +304,9 @@ PAGE = """ When enabled, Nginx will be run on port 80 and proxy requests to apps based on hostname. When disabled, a specific port will be setup for each application on first deploy, and requests to that port will be proxied to the relevant app. +
What will app URLs look like?
http://hostname:port
diff --git a/contrib/release-dokku b/contrib/release-dokku index fcb6be588..f88f01a92 100755 --- a/contrib/release-dokku +++ b/contrib/release-dokku @@ -219,7 +219,7 @@ fn-repo-update() { 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 diff --git a/deb.mk b/deb.mk index 6cc71853a..fa8ce972f 100644 --- a/deb.mk +++ b/deb.mk @@ -2,7 +2,7 @@ BUILD_DIRECTORY ?= /tmp HEROKUISH_DESCRIPTION = 'Herokuish uses Docker and Buildpacks to build applications like Heroku' HEROKUISH_REPO_NAME ?= gliderlabs/herokuish -HEROKUISH_VERSION ?= 0.4.9 +HEROKUISH_VERSION ?= 0.5.0 HEROKUISH_ARCHITECTURE = amd64 HEROKUISH_PACKAGE_NAME = herokuish_$(HEROKUISH_VERSION)_$(HEROKUISH_ARCHITECTURE).deb @@ -100,53 +100,20 @@ deb-herokuish: --license 'MIT License' \ . -deb-dokku: - rm -rf /tmp/tmp /tmp/build dokku_*_$(DOKKU_ARCHITECTURE).deb - mkdir -p /tmp/tmp /tmp/build +deb-dokku: /tmp/build-dokku/var/lib/dokku/GIT_REV + rm -f $(BUILD_DIRECTORY)/dokku_*_$(DOKKU_ARCHITECTURE).deb - cp -r debian /tmp/build/DEBIAN - mkdir -p /tmp/build/usr/share/bash-completion/completions - 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 - cp contrib/bash-completion /tmp/build/usr/share/bash-completion/completions/dokku - 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 + cat /tmp/build-dokku/var/lib/dokku/VERSION | cut -d '-' -f 1 | cut -d 'v' -f 2 > /tmp/build-dokku/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 + sed -i.bak -e "s/^/`date +%s`:/" /tmp/build-dokku/var/lib/dokku/STABLE_VERSION && rm /tmp/build-dokku/var/lib/dokku/STABLE_VERSION.bak endif - 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 -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 "$(BUILD_DIRECTORY)/dokku_`cat /tmp/build/var/lib/dokku/VERSION`_$(DOKKU_ARCHITECTURE).deb" - lintian "$(BUILD_DIRECTORY)/dokku_`cat /tmp/build/var/lib/dokku/VERSION`_$(DOKKU_ARCHITECTURE).deb" + + cp -r debian /tmp/build-dokku/DEBIAN + rm -f /tmp/build-dokku/DEBIAN/lintian-overrides + cp debian/lintian-overrides /tmp/build-dokku/usr/share/lintian/overrides/dokku + sed -i.bak "s/^Version: .*/Version: `cat /tmp/build-dokku/var/lib/dokku/STABLE_VERSION`/g" /tmp/build-dokku/DEBIAN/control && rm /tmp/build-dokku/DEBIAN/control.bak + dpkg-deb --build /tmp/build-dokku "$(BUILD_DIRECTORY)/dokku_`cat /tmp/build-dokku/var/lib/dokku/VERSION`_$(DOKKU_ARCHITECTURE).deb" + lintian "$(BUILD_DIRECTORY)/dokku_`cat /tmp/build-dokku/var/lib/dokku/VERSION`_$(DOKKU_ARCHITECTURE).deb" deb-dokku-update: rm -rf /tmp/dokku-update*.deb dokku-update*.deb diff --git a/debian/control b/debian/control index 31f00b7d9..0dde0157d 100644 --- a/debian/control +++ b/debian/control @@ -1,9 +1,9 @@ Package: dokku -Version: 0.14.6 +Version: 0.17.9 Section: web Priority: optional Architecture: amd64 -Depends: locales, git, make, curl, gcc, man-db, netcat, sshcommand (>= 0.6.0), gliderlabs-sigil, docker-engine-cs (>= 1.7.1) | docker-engine (>= 1.7.1) | docker-io (>= 1.7.1) | docker.io (>= 1.7.1) | docker-ce | docker-ee, net-tools, software-properties-common, procfile-util, python-software-properties | python3-software-properties, rsyslog +Depends: locales, git, curl, man-db, netcat, sshcommand (>= 0.6.0), gliderlabs-sigil, docker-engine-cs (>= 1.7.1) | docker-engine (>= 1.7.1) | docker-io (>= 1.7.1) | docker.io (>= 1.7.1) | docker-ce | docker-ee, net-tools, software-properties-common, procfile-util, python-software-properties | python3-software-properties, rsyslog Recommends: herokuish (>= 0.3.4), parallel, dokku-update Pre-Depends: nginx (>= 1.8.0) | openresty, dnsutils, cgroupfs-mount | cgroup-lite, plugn (>= 0.3.0), sudo, python2.7, debconf Maintainer: Jose Diaz-Gonzalez diff --git a/docs/advanced-usage/plugin-management.md b/docs/advanced-usage/plugin-management.md index 636b26c07..e49f20393 100644 --- a/docs/advanced-usage/plugin-management.md +++ b/docs/advanced-usage/plugin-management.md @@ -8,6 +8,7 @@ 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:trigger . # Trigger an arbitrary plugin hook 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 ``` @@ -23,39 +24,43 @@ dokku plugins-install ## Usage -You can list all installed plugins using the `plugin` command: +You can list all installed plugins using the `plugin:list` command: ```shell -dokku plugin +dokku plugin:list ``` ``` plugn: dev - 00_dokku-standard 0.14.6 enabled dokku core standard plugin - 20_events 0.14.6 enabled dokku core events logging plugin - apps 0.14.6 enabled dokku core apps plugin - build-env 0.14.6 enabled dokku core build-env plugin - certs 0.14.6 enabled dokku core certificate management plugin - checks 0.14.6 enabled dokku core checks plugin - common 0.14.6 enabled dokku core common plugin - config 0.14.6 enabled dokku core config plugin - docker-options 0.14.6 enabled dokku core docker-options plugin - domains 0.14.6 enabled dokku core domains plugin - enter 0.14.6 enabled dokku core enter plugin - git 0.14.6 enabled dokku core git plugin - logs 0.14.6 enabled dokku core logs plugin - named-containers 0.14.6 enabled dokku core named containers plugin - network 0.14.6 enabled dokku core network plugin - nginx-vhosts 0.14.6 enabled dokku core nginx-vhosts plugin - plugin 0.14.6 enabled dokku core plugin plugin - proxy 0.14.6 enabled dokku core proxy plugin - ps 0.14.6 enabled dokku core ps plugin - repo 0.14.6 enabled dokku core repo plugin - shell 0.14.6 enabled dokku core shell plugin - ssh-keys 0.14.6 enabled dokku core ssh-keys plugin - storage 0.14.6 enabled dokku core storage plugin - tags 0.14.6 enabled dokku core tags plugin - tar 0.14.6 enabled dokku core tar plugin + 00_dokku-standard 0.17.9 enabled dokku core standard plugin + 20_events 0.17.9 enabled dokku core events logging plugin + app-json 0.17.9 enabled dokku core app-json plugin + apps 0.17.9 enabled dokku core apps plugin + build-env 0.17.9 enabled dokku core build-env plugin + buildpacks 0.17.9 enabled dokku core buildpacks plugin + certs 0.17.9 enabled dokku core certificate management plugin + checks 0.17.9 enabled dokku core checks plugin + common 0.17.9 enabled dokku core common plugin + config 0.17.9 enabled dokku core config plugin + docker-options 0.17.9 enabled dokku core docker-options plugin + domains 0.17.9 enabled dokku core domains plugin + enter 0.17.9 enabled dokku core enter plugin + git 0.17.9 enabled dokku core git plugin + logs 0.17.9 enabled dokku core logs plugin + network 0.17.9 enabled dokku core network plugin + nginx-vhosts 0.17.9 enabled dokku core nginx-vhosts plugin + plugin 0.17.9 enabled dokku core plugin plugin + proxy 0.17.9 enabled dokku core proxy plugin + ps 0.17.9 enabled dokku core ps plugin + repo 0.17.9 enabled dokku core repo plugin + resource 0.17.9 enabled dokku core resource plugin + scheduler-docker-local 0.17.9 enabled dokku core scheduler-docker-local plugin + shell 0.17.9 enabled dokku core shell plugin + ssh-keys 0.17.9 enabled dokku core ssh-keys plugin + storage 0.17.9 enabled dokku core storage plugin + tags 0.17.9 enabled dokku core tags plugin + tar 0.17.9 enabled dokku core tar plugin + trace 0.17.9 enabled dokku core trace 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. diff --git a/docs/advanced-usage/resource-management.md b/docs/advanced-usage/resource-management.md new file mode 100644 index 000000000..6e489d0b8 --- /dev/null +++ b/docs/advanced-usage/resource-management.md @@ -0,0 +1,304 @@ +# Resource Management + +> New as of 0.15.0 + +``` +resource:limit [--process-type ] [RESOURCE_OPTS...] # Limit resources for a given app/process-type combination +resource:limit-clear [--process-type ] # Limit resources for a given app/process-type combination +resource:report [] [] # Displays a resource report for one or more apps +resource:reserve [--process-type ] [RESOURCE_OPTS...] # Reserve resources for a given app/process-type combination +resource:reserve-clear [--process-type ] # Reserve resources for a given app/process-type combination +``` + +The resource plugin is meant to allow users to limit or reserve resources for a given app/process-type combination. + +## Usage + +By default, Dokku allows unlimited resource access to apps deployed on a server. In some cases, it may be desirable to limit this on a per-app or per-process-type basis. The `resource` plugin allows management of both resource "limits" and resource "reservations", where each resource request type has specific meaning to the scheduler in use for a given app. + +> The meaning of a values and it's units are specific to the scheduler in use for a given app. If a scheduler does not support a given resource type combination, it will be ignored. All resource commands require an app rebuild or deploy in order to take effect. + +Valid resource options include: + +- `--cpu` +- `--memory` +- `--memory-swap` +- `--network` +- `--network-ingress` +- `--network-egress` + +Resource limits and reservations are applied only during the `run` and `deploy` phases of an application, and will not impact the `build` phase of an application. + +### Resource Limits + +When specified and supported, a resource limit will ensure that your app does not go _over_ the specified value. If this occurs, the underlying scheduler may either cap resource utilization, or it may decide to terminate and reschedule your process. + +Resource limits may be set via the `resource:limit` command: + +```shell +dokku resource:limit --memory 100 node-js-app +``` + +``` +=====> Setting resource limits for node-js-app + memory: 100 +``` + +Multiple resources can be limited in a single call: + +```shell +dokku resource:limit --cpu 100 --memory 100 node-js-app +``` + +``` +=====> Setting resource limits for node-js-app + cpu: 100 + memory: 100 +``` + +Resources can also be limited on a per-process type basis. If specified, this will override any generic limits set for the app. + +```shell +dokku resource:limit --cpu 100 --memory 100 --process-type worker node-js-app +``` + +``` +=====> Setting resource limits for node-js-app (worker) + cpu: 100 + memory: 100 +``` + +#### Displaying Resource Limits + +Running the `resource:limit` command without any flags will display the currently configured default app limits. + +```shell +dokku resource:limit node-js-app +``` + +``` +=====> resource limits node-js-app information [defaults] + cpu: + memory: + memory-swap: 100 + network: 100 + network-ingress: + network-egress: +``` + +This may also be combined with the `--process-type` flag to see app limits on a process-type level. Note that the displayed values are not merged with the defaults. + + +```shell +dokku resource:limit --process-type web node-js-app +``` + +``` +=====> resource limits node-js-app information (web) + cpu: 100 + memory: 100 + memory-swap: + network: + network-ingress: + network-egress: +``` + +#### Clearing Resource Limits + +In cases where the values are incorrect - or there is no desire to limit resources - resource limits may be cleared using the `resource:limit-clear` command. + +```shell +dokku resource:limit-clear node-js-app +``` + +``` +-----> Clearing resource limit for node-js-app +``` + +Defaults can also be cleared by leaving the app unspecified. + +```shell +dokku resource:limit-clear +``` + +``` +-----> Clearing default resource limits +``` + +### Resource Reservations + +When specified and supported, a resource reservation will ensure that your server has _at least_ the specified resources before placing a given app's process. If there a resource exhaustion, future rebuilds and deploys may fail. + +Resource reservations may be set via the `resource:reserve` command: + +```shell +dokku resource:reserve --memory 100 node-js-app +``` + +``` +=====> Setting resource reservation for node-js-app + memory: 100 +``` + +Multiple resources can be limited in a single call: + +```shell +dokku resource:reserve --cpu 100 --memory 100 node-js-app +``` + +``` +=====> Setting resource reservation for node-js-app + cpu: 100 + memory: 100 +``` + +Resources can also be limited on a per-process type basis. If specified, this will override any generic limits set for the app. + +```shell +dokku resource:reserve --cpu 100 --memory 100 --process-type worker node-js-app +``` + +``` +=====> Setting resource reservation for node-js-app (worker) + cpu: 100 + memory: 100 +``` + +#### Displaying Resource Reservations + +Running the `resource:reserve` command without any flags will display the currently configured default app reservations. + +```shell +dokku resource:reserve node-js-app +``` + +``` +=====> resource reservation node-js-app information [defaults] + cpu: 100 + memory: 100 + memory-swap: + network: + network-ingress: + network-egress: +``` + +This may also be combined with the `--process-type` flag to see app reservations on a process-type level. Note that the displayed values are not merged with the defaults. + +```shell +dokku resource:reserve --process-type web node-js-app +``` + +``` +=====> resource reservation node-js-app information (web) + cpu: 100 + memory: 100 + memory-swap: + network: + network-ingress: + network-egress: +``` + +#### Clearing Resource Reservations + +In cases where the values are incorrect - or there is no desire to reserve resources - resource reservations may be cleared using the `resource:reserve-clear` command. + +```shell +dokku resource:reserve-clear node-js-app +``` + +``` +-----> Clearing resource reservation for node-js-app +``` + +Defaults can also be cleared by leaving the app unspecified. + +```shell +dokku resource:reserve-clear +``` + +``` +-----> Clearing default resource reservation +``` + +### Displaying resource reports for an app + +You can get a report about the app's resource status using the `resource:report` command: + +```shell +dokku resource:report +``` + +``` +=====> node-js-app resource information + web limit cpu: + web limit memory: 1024 + web limit memory swap: 0 + web limit network: 10 + web limit network ingress: + web limit network egress: + web reservation cpu: + web reservation memory: 512 + web reservation memory swap: + web reservation network: 8 + web reservation network ingress: + web reservation network egress: +=====> python-sample resource information + web limit cpu: + web limit memory: + web limit memory swap: + web limit network: + web limit network ingress: + web limit network egress: + web reservation cpu: + web reservation memory: + web reservation memory swap: + web reservation network: + web reservation network ingress: + web reservation network egress: +=====> ruby-sample resource information + web limit cpu: + web limit memory: + web limit memory swap: + web limit network: + web limit network ingress: + web limit network egress: + web reservation cpu: + web reservation memory: + web reservation memory swap: + web reservation network: + web reservation network ingress: + web reservation network egress: +``` + +You can run the command for a specific app also. + +```shell +dokku resource:report node-js-app +``` + +``` +=====> node-js-app resource information + web limit cpu: + web limit memory: 1024 + web limit memory swap: 0 + web limit network: 10 + web limit network ingress: + web limit network egress: + web reservation cpu: + web reservation memory: 512 + web reservation memory swap: + web reservation network: 8 + web reservation network ingress: + web reservation network egress: +``` + +You can pass flags which will output only the value of the specific information you want. For example: + +```shell +# Note the periods in the flag name +dokku resource:report node-js-app --resource-web.limit.memory +``` + +``` +1024 +``` diff --git a/docs/advanced-usage/schedulers/docker-local.md b/docs/advanced-usage/schedulers/docker-local.md index 574486e36..e1da7f384 100644 --- a/docs/advanced-usage/schedulers/docker-local.md +++ b/docs/advanced-usage/schedulers/docker-local.md @@ -43,13 +43,32 @@ This plugin implements various functionality through `plugn` triggers to integra - `check-deploy` - `core-post-deploy` +- `post-app-clone-setup` +- `post-create` - `post-delete` - `pre-deploy` - `pre-restore` - `scheduler-deploy` - `scheduler-docker-cleanup` +- `scheduler-inspect` - `scheduler-logs-failed` +- `scheduler-logs` +- `scheduler-retire` - `scheduler-run` - `scheduler-stop` - `scheduler-tags-create` - `scheduler-tags-destroy` + +## Supported Resource Management Properties + +The `docker-local` scheduler supports a minimal list of resource _limits_ and _reservations_. The following properties are supported: + +### Resource Limits + +- cpu: (docker option: `--cpus`), and is specified in number of CPUs a process can access. +- memory: (docker option: `--memory`) and should be specified with a suffix of `b` (bytes), `k` (kilobytes), `m` (megabytes), `g` (gigabytes) +- memory-swap: (docker option: `--memory-swap`) and should be specified with a suffix of `b` (bytes), `k` (kilobytes), `m` (megabytes), `g` (gigabytes) + +### Resource Reservations + +- memory: (docker option: `--memory-reservation`) and should be specified with a suffix of `b` (bytes), `k` (kilobytes), `m` (megabytes), `g` (gigabytes) diff --git a/docs/assets/favicons/browserconfig.xml b/docs/assets/favicons/browserconfig.xml index b1641bf1c..4ab4af5ba 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 aa374f086..36361aa2d 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.14.6\/docs\/assets\/favicons\/android-chrome-36x36.png", + "src": "https:\/\/cdn.jsdelivr.net\/dokku\/dokku@v0.17.9\/docs\/assets\/favicons\/android-chrome-36x36.png", "sizes": "36x36", "type": "image\/png", "density": "0.75" }, { - "src": "https:\/\/cdn.rawgit.com\/progrium\/dokku\/v0.14.6\/docs\/assets\/favicons\/android-chrome-48x48.png", + "src": "https:\/\/cdn.jsdelivr.net\/dokku\/dokku@v0.17.9\/docs\/assets\/favicons\/android-chrome-48x48.png", "sizes": "48x48", "type": "image\/png", "density": "1.0" }, { - "src": "https:\/\/cdn.rawgit.com\/progrium\/dokku\/v0.14.6\/docs\/assets\/favicons\/android-chrome-72x72.png", + "src": "https:\/\/cdn.jsdelivr.net\/dokku\/dokku@v0.17.9\/docs\/assets\/favicons\/android-chrome-72x72.png", "sizes": "72x72", "type": "image\/png", "density": "1.5" }, { - "src": "https:\/\/cdn.rawgit.com\/progrium\/dokku\/v0.14.6\/docs\/assets\/favicons\/android-chrome-96x96.png", + "src": "https:\/\/cdn.jsdelivr.net\/dokku\/dokku@v0.17.9\/docs\/assets\/favicons\/android-chrome-96x96.png", "sizes": "96x96", "type": "image\/png", "density": "2.0" }, { - "src": "https:\/\/cdn.rawgit.com\/progrium\/dokku\/v0.14.6\/docs\/assets\/favicons\/android-chrome-144x144.png", + "src": "https:\/\/cdn.jsdelivr.net\/dokku\/dokku@v0.17.9\/docs\/assets\/favicons\/android-chrome-144x144.png", "sizes": "144x144", "type": "image\/png", "density": "3.0" }, { - "src": "https:\/\/cdn.rawgit.com\/progrium\/dokku\/v0.14.6\/docs\/assets\/favicons\/android-chrome-192x192.png", + "src": "https:\/\/cdn.jsdelivr.net\/dokku\/dokku@v0.17.9\/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 b85b033af..530ccb3b3 100644 --- a/docs/assets/style.css +++ b/docs/assets/style.css @@ -39,13 +39,13 @@ h1 { font-size: 1.6em; } .header .navbar-brand a { - background-image: url(https://cdn.jsdelivr.net/gh/dokku/dokku@v0.14.6/docs/assets/dokku.png); + background-image: url(https://cdn.jsdelivr.net/gh/dokku/dokku@v0.17.9/docs/assets/dokku.png); text-indent: 40px; } .blurb { color: #424242; background-color: #ededed; - background-image: url(https://cdn.jsdelivr.net/gh/dokku/dokku@v0.14.6/docs/assets/gplaypattern.png); + background-image: url(https://cdn.jsdelivr.net/gh/dokku/dokku@v0.17.9/docs/assets/gplaypattern.png); padding: 45px 0; text-align: center; } diff --git a/docs/assets/versions.json b/docs/assets/versions.json index 9cdd57b90..8a2610baa 100644 --- a/docs/assets/versions.json +++ b/docs/assets/versions.json @@ -1,16 +1,19 @@ { "max-versions": [ - "0.3.26", - "0.4.14", - "0.5.8", - "0.6.5", - "0.7.2", - "0.8.2", - "0.9.4", - "0.10.5", - "0.11.6", - "0.12.13", - "0.13.4", - "0.14.6" + "0.3.26", + "0.4.14", + "0.5.8", + "0.6.5", + "0.7.2", + "0.8.2", + "0.9.4", + "0.10.5", + "0.11.6", + "0.12.13", + "0.13.4", + "0.14.6", + "0.15.5", + "0.16.4", + "0.17.9" ] } diff --git a/docs/community/plugins.md b/docs/community/plugins.md index 0c63f5896..a5f317f9e 100644 --- a/docs/community/plugins.md +++ b/docs/community/plugins.md @@ -57,6 +57,7 @@ The following plugins are available and provided by Dokku maintainers. Where not [Aluxian]: https://github.com/Aluxian [Aomitayo]: https://github.com/Aomitayo [apmorton]: https://github.com/apmorton +[artofrawr]: https://github.com/artofrawr [basgys]: https://github.com/basgys [Benjamin-Dobell]: https://github.com/Benjamin-Dobell [blag]: https://github.com/blag @@ -229,6 +230,7 @@ The following plugins are available and provided by Dokku maintainers. Where not | [Long Timeout](https://github.com/investtools/dokku-long-timeout-plugin) | [investtools][] | 0.4.0+ | | [Monit](https://github.com/cjblomqvist/dokku-monit) | [cjblomqvist][] | 0.3.x | | [Monorepo](https://github.com/iamale/dokku-monorepo) | [iamale][] | 0.4.0+ | +| [Multi Dockerfile](https://github.com/artofrawr/dokku-multi-dockerfile) | [artofrawr][] | 0.4.0+ | | [Node](https://github.com/ademuk/dokku-nodejs) | [ademuk][] | 0.3.x | | [Node](https://github.com/pnegahdar/dokku-node) | [pnegahdar][] | 0.3.x | | [Rollbar](https://github.com/iloveitaly/dokku-rollbar) | [iloveitaly][] | 0.5.0+ | diff --git a/docs/community/tutorials/deploying-with-gitlab-ci.md b/docs/community/tutorials/deploying-with-gitlab-ci.md index 9e3752bc0..e35119752 100644 --- a/docs/community/tutorials/deploying-with-gitlab-ci.md +++ b/docs/community/tutorials/deploying-with-gitlab-ci.md @@ -65,7 +65,7 @@ review_app: image: ilyasemenov/gitlab-ci-git-push stage: deploy environment: - name: review/$CI_ENVIRONMENT_SLUG + name: review/$CI_COMMIT_REF_NAME url: https://$CI_ENVIRONMENT_SLUG.dokku.me/ on_stop: stop_review_app only: @@ -87,8 +87,10 @@ The above only runs for non-master branches, and will _also_ trigger an `on_stop stop_review_app: image: ilyasemenov/gitlab-ci-git-push stage: deploy + variables: + GIT_STRATEGY: none environment: - name: review/$CI_ENVIRONMENT_SLUG + name: review/$CI_COMMIT_REF_NAME action: stop when: manual script: diff --git a/docs/configuration/domains.md b/docs/configuration/domains.md index f039a51f9..859c0013d 100644 --- a/docs/configuration/domains.md +++ b/docs/configuration/domains.md @@ -6,11 +6,12 @@ domains:add [ ...] # Add domains to app domains:add-global [ ...] # Add global domain names domains:clear # Clear all domains for app +domains:clear-global # Clear global domain names 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:report [|--global] [] # Displays a domains report for one or more apps domains:set [ ...] # Set domains for app domains:set-global [ ...] # Set global domain names ``` @@ -27,21 +28,10 @@ scheme://subdomain.domain.tld The `subdomain` is inferred from the pushed application name, while the `domain.tld` is set during initial dokku configuration. It can then be modified with `dokku domains:add-global` and `dokku domains:remove-global`. This value is used as a default TLD for all applications on a host. -If a FQDN such as `other.tld` is used as the application name, the global virtualhost will be ignored and the resulting vhost URL for that application will be `other.tld`. The exception to this rule being that if the FQDN has the same ending as the default vhost (such as `subdomain.domain.tld`), then the entire FQDN will be treated as a subdomain. The application will therefore be deployed at `subdomain.domain.tld.domain.tld`. +If an FQDN such as `dokku.org` is used as the application name, the global virtualhost will be ignored and the resulting vhost URL for that application will be `dokku.org`. -You can optionally override this in a plugin by implementing the `nginx-hostname` plugin trigger. For example, you can reverse the subdomain with the following sample `nginx-hostname` plugin trigger: +You can optionally override this in a plugin by implementing the `nginx-hostname` plugin trigger. If the `nginx-hostname` plugin has no output, the normal hostname algorithm will be executed. See the [plugin trigger documentation](/docs/development/plugin-triggers.md#nginx-hostname) for more information. -```shell -#!/usr/bin/env bash -set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x - -APP="$1"; SUBDOMAIN="$2"; VHOST="$3" - -NEW_SUBDOMAIN=`echo $SUBDOMAIN | rev` -echo "$NEW_SUBDOMAIN.$VHOST" -``` - -If the `nginx-hostname` plugin has no output, the normal hostname algorithm will be executed. ## Disabling VHOSTS @@ -88,17 +78,17 @@ dokku domains:report ``` =====> node-js-app domains information Domains app enabled: true - Domains app vhosts: node-js-sample.dokku.org + Domains app vhosts: node-js-app.dokku.org Domains global enabled: true Domains global vhosts: dokku.org -=====> python-sample domains information +=====> python-app domains information Domains app enabled: true - Domains app vhosts: python-sample.dokku.org + Domains app vhosts: python-app.dokku.org Domains global enabled: true Domains global vhosts: dokku.org -=====> ruby-sample domains information +=====> ruby-app domains information Domains app enabled: true - Domains app vhosts: ruby-sample.dokku.org + Domains app vhosts: ruby-app.dokku.org Domains global enabled: true Domains global vhosts: dokku.org ``` diff --git a/docs/configuration/environment-variables.md b/docs/configuration/environment-variables.md index 435372e09..6d8b266a5 100644 --- a/docs/configuration/environment-variables.md +++ b/docs/configuration/environment-variables.md @@ -6,12 +6,13 @@ The `config` plugin provides the following commands to manage your variables: ``` config (|--global) Pretty-print an app or global environment +config:bundle (|--global) [--merged] Bundle environment into tarfile +config:clear (|--global) Clears environment variables +config:export (|--global) [--envfile] Export a global or app environment config:get (|--global) KEY Display a global or app-specific config value +config:keys (|--global) [--merged] Show keys set in environment config:set [--encoded] [--no-restart] (|--global) KEY1=VALUE1 [KEY2=VALUE2 ...] Set one or more config vars config:unset [--no-restart] (|--global) KEY1 [KEY2 ...] Unset one or more config vars -config:export (|--global) [--envfile] Export a global or app environment -config:keys (|--global) [--merged] Show keys set in environment -config:bundle (|--global) [--merged] Bundle environment into tarfile ``` > For security reasons - and as per [docker recommendations](https://github.com/docker/docker/issues/13490) - Dockerfile-based deploys have variables available *only* during runtime, as noted in [this issue](https://github.com/dokku/dokku/issues/1860). @@ -92,7 +93,7 @@ The following list config variables have special meaning and can be set in a var | `DOKKU_DETACH_CONTAINER` | none | `--detach` flag | Whether to detach a container started via `dokku run`. | | `DOKKU_QUIET_OUTPUT` | none | `--quiet` flag | Silences certain header output for `dokku` commands. | | `DOKKU_RM_CONTAINER` | none | `dokku config:set`
`--rm-container` flag
`--rm` flag | Whether to keep `dokku run` containers around or not. | -| `DOKKU_TRACE` | none | `dokku trace on`
`dokku trace false`
`--trace` flag | Turn on very verbose debugging. | +| `DOKKU_TRACE` | none | `dokku trace:on`
`dokku trace:off`
`--trace` flag | Turn on very verbose debugging. | | `DOKKU_APP_PROXY_TYPE` | `nginx` | `dokku proxy:set` | | | `DOKKU_APP_RESTORE` | `1` | `dokku config:set`
`dokku ps:stop` | | | `DOKKU_APP_SHELL` | `/bin/bash` | `dokku config:set` | Allows users to change the default shell used by Dokku for `dokku enter` and execution of deployment tasks. | @@ -113,6 +114,7 @@ The following list config variables have special meaning and can be set in a var | `DOKKU_DOCKERFILE_ENTRYPOINT` | dockerfile entrypoint | `dokku config:set` | | | `DOKKU_DOCKERFILE_PORTS` | dockerfile ports | `dokku config:set` | | | `DOKKU_DOCKERFILE_START_CMD` | none | `dokku config:set` | | +| `DOKKU_PARALLEL_ARGUMENTS`. | none | `dokku config:set` | Allows passing custom arguments to parallel for `ps:*all` commands | | `DOKKU_PROXY_PORT` | automatically assigned | `dokku config:set` | | | `DOKKU_PROXY_SSL_PORT` | automatically assigned | `dokku config:set` | | | `DOKKU_PROXY_PORT_MAP` | automatically assigned | `dokku proxy:ports-add`
`dokku proxy:ports-remove`, `dokku proxy:ports-clear` | | diff --git a/docs/configuration/ssl.md b/docs/configuration/ssl.md index 769ada806..fd82264ba 100644 --- a/docs/configuration/ssl.md +++ b/docs/configuration/ssl.md @@ -67,17 +67,17 @@ dokku certs:report ``` ``` -=====> node-js-sample - Ssl dir: /home/dokku/node-js-sample/tls +=====> node-js-app + Ssl dir: /home/dokku/node-js-app/tls Ssl enabled: true - Ssl hostnames: *.node-js-sample.org node-js-sample.org + Ssl hostnames: *.node-js-app.org node-js-app.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 subject: OU=Domain Control Validated; OU=PositiveSSL Wildcard; CN=*.node-js-app.org Ssl verified: self signed. -=====> python-sample - Ssl dir: /home/dokku/python-sample/tls +=====> python-app + Ssl dir: /home/dokku/python-app/tls Ssl enabled: false Ssl hostnames: Ssl expires at: @@ -90,12 +90,12 @@ dokku certs:report You can run the command for a specific app also. ```shell -dokku certs:report node-js-sample +dokku certs:report node-js-app ``` ``` -=====> node-js-sample ssl information - Ssl dir: /home/dokku/node-js-sample/tls +=====> node-js-app ssl information + Ssl dir: /home/dokku/node-js-app/tls Ssl enabled: true Ssl hostnames: *.dokku.org dokku.org Ssl expires at: Oct 5 23:59:59 2019 GMT @@ -108,7 +108,7 @@ dokku certs:report node-js-sample 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 +dokku certs:report node-js-app --ssl-enabled ``` ## HSTS Header diff --git a/docs/deployment/application-deployment.md b/docs/deployment/application-deployment.md index ade89e194..0ccc37cef 100644 --- a/docs/deployment/application-deployment.md +++ b/docs/deployment/application-deployment.md @@ -1,30 +1,31 @@ # Deploying to Dokku -> Note: This document uses the hostname `dokku.me` in commands. For your server, please -> substitute your server's hostname instead. +> Note: This walkthrough uses the hostname `dokku.me` in commands. When deploying to your own server, you should set the `DOKKU_DOMAIN` value in the `Vagrantfile` before you initialize the Vagrant VM. ## Deploy tutorial -Once Dokku has been configured with at least one user, applications can be deployed via a `git push` command. To quickly see Dokku deployment in action, you can use the Heroku Ruby on Rails example app. +Once you have configured Dokku with at least one user, you can deploy applications using `git push`. To quickly see Dokku deployment in action, try using [the Heroku Ruby on Rails "Getting Started" app](https://github.com/heroku/ruby-getting-started). ```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 +git clone git@github.com:heroku/ruby-getting-started.git ``` ### Create the app -Create the application on the Dokku host. You will need to SSH onto the host to run this command. +SSH into the Dokku host and create the application as follows: ```shell # on the Dokku host -dokku apps:create ruby-rails-sample +dokku apps:create ruby-getting-started ``` ### 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](/docs/community/plugins.md#official-plugins-beta) for common datastores. Our sample app requires a PostgreSQL service: +Dokku by default **does not** provide datastores (e.g. MySQL, PostgreSQL) on a newly created app. You can add datastore support by installing plugins, and the Dokku project [provides official plugins](/docs/community/plugins.md#official-plugins-beta) for common datastores. + +The Getting Started app requires a PostgreSQL service, so install the plugin and create the related service as follows: ```shell # on the Dokku host @@ -32,46 +33,45 @@ When you create a new app, Dokku by default *does not* provide any datastores su # plugin installation requires root, hence the user change sudo dokku plugin:install https://github.com/dokku/dokku-postgres.git -# create a postgres service with the name rails-database -dokku postgres:create rails-database +# create a postgres service with the name railsdatabase +dokku postgres:create railsdatabase ``` -> Each service may take a few moments to create. +Each service may take a few moments to create. ### Linking backing services to applications -Once the service creation is complete, set the `DATABASE_URL` environment variable by linking the service. +Once the services have been created, you then set the `DATABASE_URL` environment variable by linking the service, as follows: ```shell # on the Dokku host # each official datastore offers a `link` method to link a service to any application -dokku postgres:link rails-database ruby-rails-sample +dokku postgres:link railsdatabase ruby-getting-started ``` -> You can link a single service to multiple applications or use one service per application. +Dokku supports linking a single service to multiple applications as well as linking only one service per application. ### Deploy the app -> Warning: Your application *should* respect the `PORT` environment variable or it may not respond to web requests. -> Please see the [port management documentation](/docs/networking/port-management.md) for details. +> Warning: Your app should respect the `PORT` environment variable, otherwise it may not respond to web requests. You can find more information in the [port management documentation](/docs/networking/port-management.md).** -Now you can deploy the `ruby-rails-sample` app to your Dokku server. All you have to do is add a remote to name the app. Applications are created on-the-fly on the Dokku server. +Now you can deploy the `ruby-getting-started` app to your Dokku server. All you have to do is add a remote to name the app. Applications are created on-the-fly on the Dokku server. ```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 +cd ruby-getting-started +git remote add dokku dokku@dokku.me:ruby-getting-started git push dokku master ``` > Note: Some tools may not support the short-upstream syntax referenced above, and you may need to prefix -> the upstream with the scheme `ssh://` like so: `ssh://dokku@dokku.me:ruby-rails-sample` +> the upstream with the scheme `ssh://` like so: `ssh://dokku@dokku.me:ruby-getting-started` > Please see the [Git](https://git-scm.com/docs/git-clone#_git_urls_a_id_urls_a) documentation for more details. -> Note: Your private key should be registered with ssh-agent in local development. If you get a -> permission denied error when pushing you can register your private key by running -> `ssh-add -k ~/`. +> Note: Your private key should be registered with `ssh-agent` in your local development environment. If you get a `permission denied` error when pushing, you can register your private key as follows: `ssh-add -k ~/`. + +After running `git push dokku master`, you should have output similar to this in your terminal: ``` Counting objects: 231, done. @@ -80,7 +80,7 @@ Compressing objects: 100% (162/162), done. Writing objects: 100% (231/231), 36.96 KiB | 0 bytes/s, done. Total 231 (delta 93), reused 147 (delta 53) -----> Cleaning up... ------> Building ruby-rails-sample from herokuish... +-----> Building ruby-getting-started from herokuish... -----> Adding BUILD_ENV to build environment... -----> Ruby app detected -----> Compiling Ruby/Rails @@ -95,12 +95,12 @@ Total 231 (delta 93), reused 147 (delta 53) ... =====> Application deployed: - http://ruby-rails-sample.dokku.me + http://ruby-getting-started.dokku.me ``` -When the deploy finishes, the application's URL will be shown as seen above. +Once the deploy is complete, the application's web URL will be generated as above. -Dokku supports deploying applications via [Heroku buildpacks](https://devcenter.heroku.com/articles/buildpacks) with [Herokuish](https://github.com/gliderlabs/herokuish#buildpacks) or using a project's [Dockerfile](https://docs.docker.com/reference/builder/). +Dokku supports deploying applications via [Heroku buildpacks](https://devcenter.heroku.com/articles/buildpacks) with [Herokuish](https://github.com/gliderlabs/herokuish#buildpacks), as well as by using a project's [Dockerfile](https://docs.docker.com/reference/builder/). ### Skipping deployment @@ -109,23 +109,23 @@ If you only want to rebuild and tag a container, you can skip the deployment pha ``` shell # on the Dokku host -dokku config:set ruby-rails-sample DOKKU_SKIP_DEPLOY=true +dokku config:set ruby-getting-started DOKKU_SKIP_DEPLOY=true ``` ### Redeploying or restarting -If you need to redeploy (or restart) your app: +If you need to redeploy or restart your app: ```shell # on the Dokku host -dokku ps:rebuild ruby-rails-sample +dokku ps:rebuild ruby-getting-started ``` See the [process scaling documentation](/docs/deployment/process-management.md) for more information. -### Deploying with private git submodules +### 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. +Dokku uses Git locally (i.e. not a Docker image) to build its own copy of your app repo, including submodules, as the `dokku` user. This means that in order to deploy private Git submodules, you need to put your deploy key in `/home/dokku/.ssh/` and potentially add `github.com` (or your VCS host key) into `/home/dokku/.ssh/known_hosts`. You can use the following test to confirm your setup is correct: ```shell # on the Dokku host @@ -134,25 +134,25 @@ ssh-keyscan -t rsa github.com >> ~/.ssh/known_hosts ssh -T git@github.com ``` -Note that if the buildpack or Dockerfile build process require SSH key access for other reasons, the above may not always apply. +> Warning: if the buildpack or Dockerfile build process require SSH key access for other reasons, the above may not always apply. ## Deploying to subdomains -The name of remote repository is used as the name of application to be deployed, as for example above: +If you do not enter a fully qualified domain name when pushing your app, Dokku deploys the app to `.yourdomain.tld` as follows: ```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 remote add dokku dokku@dokku.me:ruby-getting-started git push dokku master ``` ``` remote: -----> Application deployed: -remote: http://ruby-rails-sample.dokku.me +remote: http://ruby-getting-started.dokku.me ``` -You can also specify fully qualified names, say `app.dokku.me`, as +You can also specify the fully qualified name as follows: ```shell # from your local machine @@ -166,7 +166,7 @@ remote: -----> Application deployed: remote: http://app.dokku.me ``` -This is in particular useful, then you want to deploy to root domain, as +This is useful when you want to deploy to the root domain: ```shell # from your local machine @@ -182,9 +182,9 @@ git push dokku master ## Dokku/Docker container management compatibility -Dokku is, at its core, a Docker container manager. Thus, it does not necessarily play well with other out-of-band processes interacting with the Docker daemon. One thing to note as in [issue #1220](https://github.com/dokku/dokku/issues/1220), Dokku executes a cleanup function prior to every deployment. +Dokku is, at its core, a Docker container manager. Thus, it does not necessarily play well with other out-of-band processes interacting with the Docker daemon. -As of 0.5.x, this function removes all containers with the label `dokku` where the status is either `dead` or `exited`, as well as all `dangling` images. Previous versions would remove `dead` or `exited` containers, regardless of their label. +Prior to every deployment, Dokku will execute a cleanup function. As of 0.5.x, the cleanup removes all containers with the `dokku` label where the status is either `dead` or `exited` (previous versions would remove _all_ `dead` or `exited` containers). The cleanup function also removes all images with `dangling` status. ## Adding deploy users diff --git a/docs/deployment/methods/dockerfiles.md b/docs/deployment/methods/dockerfiles.md index 4a8458fa9..3139474dd 100644 --- a/docs/deployment/methods/dockerfiles.md +++ b/docs/deployment/methods/dockerfiles.md @@ -29,6 +29,12 @@ For users that require customization in the `build` phase, you may use build arg dokku docker-options:add node-js-app build '--build-arg NODE_ENV=production' ``` +The location of the `Dockerfile` may also be specified. If the location is changed, the repository **must also** have a `Dockerfile` in the root directory in order to trigger a dockerfile-based deploy. + +```shell +dokku docker-options:add node-js-app build '--file Dockerfile.dokku' +``` + Once set, the Dockerfile usage would be as follows: ```Dockerfile diff --git a/docs/deployment/one-off-processes.md b/docs/deployment/one-off-processes.md index 719adbb46..79dfb5a16 100644 --- a/docs/deployment/one-off-processes.md +++ b/docs/deployment/one-off-processes.md @@ -5,7 +5,7 @@ Sometimes you need to either inspect running containers or run a one-off command ## Run a command in an app environment ``` -run # Run a command in a new container using the current application image +run [ --env KEY=VALUE | -e KEY=VALUE ] # Run a command in a new container using the current application image ``` The `run` command can be used to run a one-off process for a specific command. This will start a new container and run the desired command within that container. Note that this container will be stay around even after command completes. The container will be the same container as was used to start the currently deployed application. @@ -13,6 +13,9 @@ The `run` command can be used to run a one-off process for a specific command. T ```shell # runs `ls -lah` in the `/app` directory of the application `node-js-app` dokku run node-js-app ls -lah + +# optionally, run can be passed custom environment variables +dokku run --env "NODE_ENV=development" --env "PATH=/custom/path" node-js-app npm run mytask ``` The `run` command can also be used to run a command defined in your Procfile: diff --git a/docs/deployment/zero-downtime-deploys.md b/docs/deployment/zero-downtime-deploys.md index ebcccc9df..7ebee5124 100644 --- a/docs/deployment/zero-downtime-deploys.md +++ b/docs/deployment/zero-downtime-deploys.md @@ -81,13 +81,13 @@ dokku checks:report ``` ``` -=====> search checks information +=====> node-js-app checks information Checks disabled list: none Checks skipped list: none -=====> python-sample checks information +=====> python-app checks information Checks disabled list: none Checks skipped list: none -=====> ruby-sample checks information +=====> ruby-app checks information Checks disabled list: _all_ Checks skipped list: none ``` @@ -95,11 +95,11 @@ dokku checks:report You can run the command for a specific app also. ```shell -dokku checks:report node-js-sample +dokku checks:report node-js-app ``` ``` -=====> node-js-sample checks information +=====> node-js-app checks information Checks disabled list: none Checks skipped list: none ``` @@ -107,7 +107,7 @@ dokku checks:report node-js-sample 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 +dokku checks:report node-js-app --checks-disabled-list ``` ## Customizing checks diff --git a/docs/development/plugin-creation.md b/docs/development/plugin-creation.md index 66817f2cc..05468f134 100644 --- a/docs/development/plugin-creation.md +++ b/docs/development/plugin-creation.md @@ -1,37 +1,37 @@ -# Plugin Creation +# 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. +A plugin can be a simple implementation of [triggers](/docs/development/plugin-triggers.md) or can implement a command structure of its 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) for the API. **NOTE:** any file that implements triggers or uses the command 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](/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](/docs/community/plugins.md) and add a link to it. +1. Take a look at [the plugins shipped with Dokku](/docs/community/plugins.md) and hack away! +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 following the `dokku-` convention (e.g. `dokku-mariadb`) +4. Edit [this page](/docs/community/plugins.md) and add a link to your plugin 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. +## Compilable plugins (Golang, Java(?), C, etc.) +When developing a plugin, you must implement the `install` trigger such that it outputs the built executable(s) using a directory structure that implements the plugin's desired command and/or triggers the API. See the [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/` +## Command API +There are 3 main integration 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)) +### `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/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)) +### `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 +# Sample plugin The below plugin is a dummy `dokku hello` plugin. -hello/subcommands/default +`hello/subcommands/default` ```shell #!/usr/bin/env bash @@ -59,7 +59,7 @@ hello_main_cmd() { hello_main_cmd "$@" ``` -hello/subcommands/world +`hello/subcommands/world` ```shell #!/usr/bin/env bash @@ -87,7 +87,7 @@ hello_world_cmd() { hello_world_cmd "$@" ``` -hello/commands +`hello/commands` ```shell #!/usr/bin/env bash @@ -136,15 +136,15 @@ version = "0.1.0" [plugin.config] ``` -A few notes: +# A few notes: -- Don't forget to `chmod +x` your executable files. -- You should always support `DOKKU_TRACE` as specified on the 2nd line of the plugin. -- If your command requires that an application exists, ensure you check for it's existence in the manner prescribed above. -- A `help` command is required, though it is allowed to be empty. Also, the command syntax will need to separated by `, ` in order to maintain columnar output alignment. -- Commands *should* be namespaced. -- As of 0.3.3, a catch-all should be implemented which exits with a `DOKKU_NOT_IMPLEMENTED_EXIT` code. This allows Dokku to output a `command not found` message. -- Be sure you want the "set -eo pipefail" option. Look at the following example : +- Remember to `chmod +x` your executable files +- Always support `DOKKU_TRACE` as per the 2nd line of the above example +- If your command depends on an application, include a check for whether that application exists (see the above example) +- You must implement a `help` command, though you may leave it empty. Also, you must use commas (`,`) in the command syntax to support output in columns +- Commands **should** be namespaced +- As of 0.3.3, a catch-all should be implemented that exits with a `DOKKU_NOT_IMPLEMENTED_EXIT` code. This allows Dokku to output a `command not found` message. +- Consider whether you want to include the `set -eo pipefail` option. Look at the following example : ```shell IMAGE=$(docker images | grep "user/repo" | awk '{print $3}') @@ -153,10 +153,9 @@ A few notes: fi ``` - In the case where the "user/repo" is not installed, Dokku exits just before the awk command, - you will never see the message printed with echo. You just want "set -e" in this case. + If `user/repo` doesn't exist, Dokku exits just before the `awk` command and the `dokku_log_fail` message will never go to `STDOUT`. printed with echo. You would want to use `set -e` in this case. - Here is the documentation of the 'set -eo pipefail' option: + Here is the `help` entry for `set`: ``` help set Options: @@ -166,13 +165,19 @@ A few notes: the last command to exit with a non-zero status, or zero if no command exited with a non-zero status ``` -- 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 : +- In the case that your plugin needs to set application configuration settings and you want to avoid having to restart (default Heroku-style behavior) these "internal" commands provide this functionality: ```shell 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](/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 +- If you 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 + - You should consider all functions in that file to be publicly accessible by other plugins + - Any functions you want to keep private should reside in your plugin's `trigger/` or `commands/` directories +- As of 0.4.0, Dokku allows image tagging and deployment of tagged images + - This means hard-coding the `$IMAGE` as `dokku/$APP` is no longer sufficient + - You should now use `get_running_image_tag()` and `get_app_image_name()` as sourced from `common/functions` (see the [plugin triggers](/docs/development/plugin-triggers.md) doc for examples). **Note:** This is only for plugins that are not `pre/post-build-*` plugins +- As of 0.5.0, we use container labels to help cleanup intermediate containers with `dokku cleanup + - This means that if you manually call `docker run`, you should include `$DOKKU_GLOBAL_RUN_ARGS` to ensure your intermediate containers are labeled correctly +- As of 0.6.0, you should not **not** call the `dokku` binary directly from within plugins because clients using the `--app` argument are potentially broken when doing so (as well as other issues) + - You should instead 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 4f4abbf5e..eaf905aa8 100644 --- a/docs/development/plugin-triggers.md +++ b/docs/development/plugin-triggers.md @@ -29,6 +29,21 @@ The following plugin triggers describe those available to a Dokku installation. > 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. +### `app-restart` + +- Description: Triggers an app restart +- Invoked by: `dokku config:clear`, `dokku config:set`, `dokku config:unset` +- Arguments: `$APP` +- Example: + +```shell +#!/usr/bin/env bash + +set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x + +# TODO +``` + ### `app-urls` - Description: Allows you to change the urls Dokku reports for an application. Will override any auto-detected urls. @@ -248,6 +263,8 @@ echo 'derp.dkr.ecr.us-east-1.amazonaws.com' ### `docker-args-build` +> Warning: Deprecated, please use `docker-args-process-build` instead + - Description: - Invoked by: `internal function dokku_build() (build phase)` - Arguments: `$APP $IMAGE_SOURCE_TYPE` @@ -271,6 +288,8 @@ echo -n "$STDIN$output" ### `docker-args-deploy` +> Warning: Deprecated, please use `docker-args-process-deploy` instead + - Description: - Invoked by: `dokku deploy` - Arguments: `$APP $IMAGE_TAG [$PROC_TYPE $CONTAINER_INDEX]` @@ -287,8 +306,64 @@ verify_app_name "$APP" # TODO ``` +### `docker-args-process-build` + +- Description: `$PROC_TYPE` may be set to magic `_all_` process type to signify global docker deploy options. +- Invoked by: `dokku ps:rebuild` +- Arguments: `$APP $IMAGE_TAG $IMAGE_SOURCE_TYPE` +- Example: + +```shell +#!/usr/bin/env bash + +set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x +source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" +APP="$1"; IMAGE_TAG="$2"; IMAGE=$(get_app_image_name $APP $IMAGE_TAG) +verify_app_name "$APP" + +# TODO +``` + +### `docker-args-process-deploy` + +- Description: `$PROC_TYPE` may be set to magic `_all_` process type to signify global docker deploy options. +- Invoked by: `dokku deploy` +- Arguments: `$APP $IMAGE_TAG $IMAGE_SOURCE_TYPE [$PROC_TYPE $CONTAINER_INDEX]` +- Example: + +```shell +#!/usr/bin/env bash + +set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x +source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" +APP="$1"; IMAGE_TAG="$2"; IMAGE=$(get_app_image_name $APP $IMAGE_TAG) +verify_app_name "$APP" + +# TODO +``` + +### `docker-args-process-run` + +- Description: `$PROC_TYPE` may be set to magic `_all_` process type to signify global docker run options. +- Invoked by: `dokku run` +- Arguments: `$APP $IMAGE_TAG $IMAGE_SOURCE_TYPE` +- Example: + +```shell +#!/usr/bin/env bash + +set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x +source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" +APP="$1"; IMAGE_TAG="$2"; IMAGE=$(get_app_image_name $APP $IMAGE_TAG) +verify_app_name "$APP" + +# TODO +``` + ### `docker-args-run` +> Warning: Deprecated, please use `docker-args-process-run` instead + - Description: - Invoked by: `dokku run` - Arguments: `$APP $IMAGE_TAG` @@ -339,6 +414,21 @@ set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x 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. +### `git-revision` + +- Description: Allows you to fetch the current git revision for a given application +- Invoked by: +- Arguments: `$APP` +- Example: + +```shell +#!/usr/bin/env bash + +set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x + +# TODO +``` + ### `install` - Description: Used to setup any files/configuration for a plugin. @@ -529,6 +619,8 @@ echo "$NEW_SUBDOMAIN.$VHOST" ### `nginx-pre-reload` +> Warning: The arguments INTERNAL_PORT and INTERNAL_IP_ADDRESS are no longer sufficient to retrieve all app listeners. Please run `plugn trigger network-get-listeners APP` within any implementation of `nginx-pre-reload` in order to retrieve all application listeners. + - Description: Run before nginx reloads hosts - Invoked by: `dokku nginx:build-config` - Arguments: `$APP $INTERNAL_PORT $INTERNAL_IP_ADDRESS` @@ -1085,9 +1177,9 @@ set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x # TODO ``` -### `proxy-enable` +### `proxy-disable` -- Description: Enables the configured proxy implementation for an app +- Description: Disables the configured proxy implementation for an app - Invoked by: `internally triggered by ps:restore` - Arguments: `$APP` - Example: @@ -1100,9 +1192,9 @@ set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x # TODO ``` -### `proxy-disable` +### `proxy-enable` -- Description: Disables the configured proxy implementation for an app +- Description: Enables the configured proxy implementation for an app - Invoked by: `internally triggered by ps:restore` - Arguments: `$APP` - Example: @@ -1157,6 +1249,38 @@ fi plugn trigger receive-app $APP $newrev ``` +### `report` + +- Description: Allows you to report on any custom configuration in use by your application +- Invoked by: `dokku report` +- Arguments: `$APP` +- Example: + +```shell +#!/usr/bin/env bash + +set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x +APP="$1"; + +# TODO +``` + +### `resource-get-property` + +- Description: Fetches a given resource property value +- Invoked by: +- Arguments: `$APP` `$PROC_TYPE` `$RESOURCE_TYPE` `$PROPERTY` +- Example: + +```shell +#!/usr/bin/env bash + +set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x +APP="$1"; PROC_TYPE="$2" RESOURCE_TYPE="$3" PROPERTY="$4" + +# TODO +``` + ### `retire-container-failed` - Description: Allows you to run commands if/when retiring old containers has failed @@ -1174,18 +1298,21 @@ APP="$1"; HOSTNAME=$(hostname -s) mail -s "$APP containers on $HOSTNAME failed to retire" ops@dokku.me ``` -### `report` +### `scheduler-app-status` -- Description: Allows you to report on any custom configuration in use by your application -- Invoked by: `dokku report` -- Arguments: `$APP` +> Warning: The scheduler plugin trigger apis are under development and may change +> between minor releases until the 1.0 release. + +- Description: Fetch the status of an app +- Invoked by: `dokku ps:report` +- Arguments: `$DOKKU_SCHEDULER $APP` - Example: ```shell #!/usr/bin/env bash set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x -APP="$1"; +DOKKU_SCHEDULER="$1"; APP="$2"; # TODO ``` @@ -1247,6 +1374,43 @@ DOKKU_SCHEDULER="$1"; APP="$2"; # TODO ``` +### `scheduler-is-deployed` + +> Warning: The scheduler plugin trigger apis are under development and may change +> between minor releases until the 1.0 release. + +- Description: Allows you to check if an app has been deployed +- Invoked by: `dokku ps:rebuild` +- Arguments: `$DOKKU_SCHEDULER $APP` +- Example: + +```shell +#!/usr/bin/env bash + +set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x +DOKKU_SCHEDULER="$1"; APP="$2"; + +# TODO +``` +### `scheduler-logs` + +> Warning: The scheduler plugin trigger apis are under development and may change +> between minor releases until the 1.0 release. + +- Description: Allows you to run scheduler commands when retrieving container logs +- Invoked by: `dokku logs:failed` +- Arguments: `$DOKKU_SCHEDULER $APP $PROCESS_TYPE $TAIL $PRETTY_PRINT $NUM` +- Example: + +```shell +#!/usr/bin/env bash + +set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x +DOKKU_SCHEDULER="$1"; APP="$2"; PROCESS_TYPE="$3"; TAIL="$4"; PRETTY_PRINT="$5"; NUM="$6" + +# TODO +``` + ### `scheduler-logs-failed` > Warning: The scheduler plugin trigger apis are under development and may change @@ -1261,7 +1425,45 @@ DOKKU_SCHEDULER="$1"; APP="$2"; #!/usr/bin/env bash set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x -DOKKU_SCHEDULER="$1"; APP="$2"; ARGS="${@:3}"; +DOKKU_SCHEDULER="$1"; APP="$2"; + +# TODO +``` + +### `scheduler-post-delete` + +> Warning: The scheduler plugin trigger apis are under development and may change +> between minor releases until the 1.0 release. + +- Description: Allows you to run scheduler commands when an app is deleted +- Invoked by: `dokku apps:destroy` +- Arguments: `$DOKKU_SCHEDULER $APP $IMAGE_TAG` +- Example: + +```shell +#!/usr/bin/env bash + +set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x +DOKKU_SCHEDULER="$1"; APP="$2"; IMAGE_TAG="$3"; + +# TODO +``` + +### `scheduler-retire` + +> Warning: The scheduler plugin trigger apis are under development and may change +> between minor releases until the 1.0 release. + +- Description: Allows you to run scheduler commands when containers should be force retired from the system +- Invoked by: `dokku run` +- Arguments: `$DOKKU_SCHEDULER` +- Example: + +```shell +#!/usr/bin/env bash + +set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x +DOKKU_SCHEDULER="$1"; # TODO ``` diff --git a/docs/development/release-process.md b/docs/development/release-process.md index 443ee1ba9..b23bbe570 100644 --- a/docs/development/release-process.md +++ b/docs/development/release-process.md @@ -29,7 +29,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.14.6-2 (Mon Feb 22 23:20:37 CET 2016)" +# wait for "==> build-arch: ==> Finished making: dokku 0.17.9-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 5b4d23c67..90ea77a1a 100644 --- a/docs/development/testing.md +++ b/docs/development/testing.md @@ -32,6 +32,9 @@ While we do provide official packages for a variety of platforms, as our test su # run linter & update vagrant Dokku install from local git clone make lint copyfiles + # build a specific plugin + make go-build-plugin copyplugin PLUGIN_NAME=apps + # execute all bats tests make unit-tests diff --git a/docs/getting-started/advanced-installation.md b/docs/getting-started/advanced-installation.md index c72f7c332..ef030cd56 100644 --- a/docs/getting-started/advanced-installation.md +++ b/docs/getting-started/advanced-installation.md @@ -46,7 +46,7 @@ sudo BUILD_STACK=true STACK_URL=https://github.com/gliderlabs/herokuish.git make ## Skipping Herokuish installation -The Herokuish package is recommended but not required if not using Heroku buildpacks for deployment. Debian-based OS users can run the bootstrap installer via `sudo DOKKU_NO_INSTALL_RECOMMENDS=true bash bootstrap.sh` to skip the dependency. Please note that this will _also_ skip installation of other recommended dependencies. +The Herokuish package is recommended but not required if not using Heroku buildpacks for deployment. Debian-based OS users can run the bootstrap installer via `sudo DOKKU_NO_INSTALL_RECOMMENDS=" --no-install-recommends " bash bootstrap.sh` to skip the dependency. Please note that this will _also_ skip installation of other recommended dependencies. ## Configuring an unattended installation diff --git a/docs/getting-started/installation.md b/docs/getting-started/installation.md index 4b73d51fe..e93fd2928 100644 --- a/docs/getting-started/installation.md +++ b/docs/getting-started/installation.md @@ -6,7 +6,7 @@ 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 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] +- A fresh installation of [Ubuntu x64 - Any currently supported release](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 1 GB of system memory [2] You can *optionally* have a domain name pointed at the host's IP, though this is not necessary. @@ -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.14.6/bootstrap.sh; -sudo DOKKU_TAG=v0.14.6 bash bootstrap.sh +wget https://raw.githubusercontent.com/dokku/dokku/v0.17.9/bootstrap.sh; +sudo DOKKU_TAG=v0.17.9 bash bootstrap.sh ``` The installation process takes about 5-10 minutes, depending upon internet connection speed. diff --git a/docs/getting-started/troubleshooting.md b/docs/getting-started/troubleshooting.md index 627664275..ed8181aad 100644 --- a/docs/getting-started/troubleshooting.md +++ b/docs/getting-started/troubleshooting.md @@ -1,5 +1,38 @@ # Troubleshooting +> New as of 0.17.0 + +``` +trace:on # Enables trace mode +trace:off # Disables trace mode +``` + +## Trace Mode + +By default, Dokku will constrain the amount of output displayed for any given command run. The verbosity of output can be increased by enabling trace mode. Trace mode will turn on the `set -x` flag for bash plugins, while other plugins are free to respect the environment variable `DOKKU_TRACE` and log differently as approprate. Trace mode can be useful to see _where_ plugins are running commands that would otherwise be unexpected. + +To enable trace mode, run `trace:on` + +```shell +dokku trace:on +``` + +``` +-----> Enabling trace mode +``` + +Trace mode can be disabled with `trace:off` + +```shell +dokku trace:off +``` + +``` +-----> Disabling trace mode +``` + +## Common Problems + __Symptom:__ I deployed my app but I am getting the default nginx page. __Solution:__ @@ -41,22 +74,7 @@ __Symptom:__ I want to deploy my app, but while pushing I get the following erro __Solution:__ -The `remote rejected` error does not give enough information. Anything could have failed. - -To enable Dokku tracing, simply run the following command: - -```shell -# since 0.3.9 -dokku trace on -``` - -In versions older than 0.3.9, you can create a `/home/dokku/dokkurc` file containing the following: - -```shell -export DOKKU_TRACE=1 -``` - -This will trace all of Dokku's activity. If this does not help you, create a [gist](https://gist.github.com) containing the full log, and create an issue. +The `remote rejected` error does not give enough information. Anything could have failed. Enable trace mode and begin debugging. If this does not help you, create a [gist](https://gist.github.com) containing the full log, and create an issue. *** diff --git a/docs/home.html b/docs/home.html index 5325f1e7b..7bf681bd0 100644 --- a/docs/home.html +++ b/docs/home.html @@ -10,30 +10,30 @@ Dokku - The smallest PaaS implementation you've ever seen - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + - - + + - + + + + + + +
+

We're sorry, but something went wrong.

+

If you are the application owner check the logs for more information.

+
+ + diff --git a/plugins/nginx-vhosts/templates/nginx.conf.sigil b/plugins/nginx-vhosts/templates/nginx.conf.sigil index 360ba4a6a..7ad4595a5 100644 --- a/plugins/nginx-vhosts/templates/nginx.conf.sigil +++ b/plugins/nginx-vhosts/templates/nginx.conf.sigil @@ -26,7 +26,7 @@ server { proxy_pass http://{{ $.APP }}-{{ $upstream_port }}; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; - proxy_set_header Connection "upgrade"; + proxy_set_header Connection $http_connection; proxy_set_header Host $http_host; proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header X-Forwarded-For $remote_addr; @@ -84,7 +84,7 @@ server { {{ if eq $.HTTP2_PUSH_SUPPORTED "true" }}http2_push_preload on; {{ end }} proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; - proxy_set_header Connection "upgrade"; + proxy_set_header Connection $http_connection; proxy_set_header Host $http_host; proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header X-Forwarded-For $remote_addr; @@ -105,11 +105,17 @@ server { internal; } - error_page 500 501 502 503 504 505 506 507 508 509 510 511 /500-error.html; + error_page 500 501 503 504 505 506 507 508 509 510 511 /500-error.html; location /500-error.html { root {{ $.DOKKU_LIB_ROOT }}/data/nginx-vhosts/dokku-errors; internal; } + + error_page 502 /502-error.html; + location /502-error.html { + root {{ $.DOKKU_LIB_ROOT }}/data/nginx-vhosts/dokku-errors; + internal; + } } {{ end }}{{ end }} diff --git a/plugins/nginx-vhosts/templates/validate.conf.sigil b/plugins/nginx-vhosts/templates/validate.conf.sigil index 232fd6d51..cb6a556c5 100644 --- a/plugins/nginx-vhosts/templates/validate.conf.sigil +++ b/plugins/nginx-vhosts/templates/validate.conf.sigil @@ -1,2 +1,6 @@ events { worker_connections 768; } -http { include {{ $.NGINX_CONF }}; } +http { + access_log off; + error_log /dev/null; + include {{ $.NGINX_CONF }}; +} diff --git a/plugins/plugin/commands b/plugins/plugin/commands index 0722d5002..7b436b331 100755 --- a/plugins/plugin/commands +++ b/plugins/plugin/commands @@ -8,13 +8,14 @@ case "$1" in help_content_func() { declare desc="return plugin plugin help content" cat <, 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:update [name [committish]], Optionally update named plugin from git (with custom tag/committish) & run update trigger for active plugins - plugin:enable , Enable a previously disabled plugin - plugin:disable , Disable an installed plugin (third-party only) + plugin:list, Print active plugins + plugin:trigger , Trigger an arbitrary plugin hook 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 help_content } diff --git a/plugins/plugin/plugin.toml b/plugins/plugin/plugin.toml index 4ea7de804..4da252316 100644 --- a/plugins/plugin/plugin.toml +++ b/plugins/plugin/plugin.toml @@ -1,4 +1,4 @@ [plugin] description = "dokku core plugin plugin" -version = "0.14.6" +version = "0.17.9" [plugin.config] diff --git a/plugins/plugin/subcommands/install b/plugins/plugin/subcommands/install index c9cf82926..6516a05f2 100755 --- a/plugins/plugin/subcommands/install +++ b/plugins/plugin/subcommands/install @@ -13,7 +13,7 @@ plugin_install_cmd() { [[ "$#" -gt 2 ]] && dokku_log_info1_quiet "Cannot install additional core plugins, running core plugin install trigger" PLUGIN_PATH="$PLUGIN_CORE_PATH" plugn trigger install ;; - https:* | git* | ssh:* | *.tar.gz | *.tgz) + https:* | git* | ssh:* | file:* | *.tar.gz | *.tgz) shift download_and_enable_plugin "$@" plugn trigger install diff --git a/plugins/plugin/subcommands/trigger b/plugins/plugin/subcommands/trigger new file mode 100755 index 000000000..94a58d78f --- /dev/null +++ b/plugins/plugin/subcommands/trigger @@ -0,0 +1,13 @@ +#!/usr/bin/env bash +set -eo pipefail +[[ $DOKKU_TRACE ]] && set -x + +plugin_trigger_cmd() { + declare desc="trigger an arbitrary plugin hook" + local cmd="plugin:trigger" + [[ "$1" == "$cmd" ]] && shift 1 + + plugn trigger "$@" +} + +plugin_trigger_cmd "$@" diff --git a/plugins/proxy/internal-functions b/plugins/proxy/internal-functions index 073c9aece..70e8dbbd5 100755 --- a/plugins/proxy/internal-functions +++ b/plugins/proxy/internal-functions @@ -80,12 +80,13 @@ 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:enable , Enable 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:ports-add [::...], Add proxy port mappings to an app + proxy:ports-clear , Clear all proxy port mappings for an app + proxy:ports-remove [|::...], Remove specific proxy port mappings from an app + proxy:ports-set [::...], Set proxy port mappings for an app proxy:report [] [], Displays a proxy report for one or more apps proxy:set , Set proxy type for app help_content diff --git a/plugins/proxy/plugin.toml b/plugins/proxy/plugin.toml index e8b207121..9a29e44fb 100644 --- a/plugins/proxy/plugin.toml +++ b/plugins/proxy/plugin.toml @@ -1,4 +1,4 @@ [plugin] description = "dokku core proxy plugin" -version = "0.14.6" +version = "0.17.9" [plugin.config] diff --git a/plugins/proxy/subcommands/ports-add b/plugins/proxy/subcommands/ports-add index 9ed40cbef..973c7b209 100755 --- a/plugins/proxy/subcommands/ports-add +++ b/plugins/proxy/subcommands/ports-add @@ -5,7 +5,7 @@ source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" source "$PLUGIN_AVAILABLE_PATH/proxy/functions" proxy_ports_add_cmd() { - declare desc="add proxy port mappings from an app" + declare desc="add proxy port mappings to an app" local cmd="proxy:add" local APP="$2" verify_app_name "$APP" diff --git a/plugins/proxy/subcommands/ports-set b/plugins/proxy/subcommands/ports-set index 23289c19c..d550a4fc7 100755 --- a/plugins/proxy/subcommands/ports-set +++ b/plugins/proxy/subcommands/ports-set @@ -5,14 +5,14 @@ source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" source "$PLUGIN_AVAILABLE_PATH/proxy/functions" proxy_ports_set_cmd() { - declare desc="add proxy port mappings from an app" - local cmd="proxy:add" + declare desc="set proxy port mappings for an app" + local cmd="proxy:set" local APP="$2" verify_app_name "$APP" shift 2 set_proxy_ports "$APP" "$@" - plugn trigger post-proxy-ports-update "$APP" "add" + plugn trigger post-proxy-ports-update "$APP" "set" } proxy_ports_set_cmd "$@" diff --git a/plugins/ps/functions b/plugins/ps/functions index f217e6f0d..fe0cb0ca4 100755 --- a/plugins/ps/functions +++ b/plugins/ps/functions @@ -62,12 +62,17 @@ generate_scale_file() { local DOKKU_PROCFILE="$DOKKU_ROOT/$APP/DOKKU_PROCFILE" verify_app_name "$APP" - copy_from_image "$IMAGE" "DOKKU_SCALE" "$DOKKU_ROOT/$APP" 2>/dev/null || true + if copy_from_image "$IMAGE" "DOKKU_SCALE" "$DOKKU_ROOT/$APP" >/dev/null 2>&1; then + cp "$DOKKU_SCALE_FILE" "${DOKKU_SCALE_FILE}.extracted" + else + rm -f "${DOKKU_SCALE_FILE}.extracted" + fi + if [[ ! -f $DOKKU_SCALE_FILE ]]; then dokku_log_info1_quiet "DOKKU_SCALE file not found in app image. Generating one based on Procfile..." - local GENERATE_SCALE_FILE_TMP_WORK_DIR=$(mktemp -d "/tmp/dokku_scale.XXXX") - trap 'rm -rf "$GENERATE_SCALE_FILE_TMP_WORK_DIR" >/dev/null' RETURN + local GENERATE_SCALE_FILE_TMP_WORK_DIR=$(mktemp -d "/tmp/dokku-${FUNCNAME[0]}.XXXX") + trap "rm -rf '$GENERATE_SCALE_FILE_TMP_WORK_DIR' >/dev/null" RETURN if [[ -f $DOKKU_PROCFILE ]]; then while read -r line || [[ -n "$line" ]]; do @@ -94,13 +99,13 @@ set_scale() { shift 1 extract_procfile "$APP" "$IMAGE_TAG" >/dev/null - trap 'remove_procfile $APP' RETURN INT TERM EXIT + trap "remove_procfile $APP" RETURN INT TERM EXIT local SCALE_SETTINGS=("$@") for procscale in "${SCALE_SETTINGS[@]}"; do local PROC_NAME=${procscale%%=*} local PROC_COUNT=${procscale#*=} - if [[ -f "$DOKKU_PROCFILE" ]] && ! procfile-util exists --procfile "$DOKKU_PROCFILE" --process-type "$PROC_NAME" >/dev/null 2>&1; then + if [[ -f "$DOKKU_PROCFILE" ]] && [[ "$PROC_COUNT" != "0" ]] && ! procfile-util exists --procfile "$DOKKU_PROCFILE" --process-type "$PROC_NAME" >/dev/null 2>&1; then dokku_log_fail "${PROC_NAME} is not a valid process name" fi is_number "$PROC_COUNT" || dokku_log_fail "ps:scale $PROC_COUNT is not a number" @@ -113,6 +118,13 @@ set_scale() { done } +ps_restore() { + declare desc="restores app" + local APP="$1" + + ps_start "$APP" +} + ps_start() { declare desc="starts app" local APP="$1" @@ -182,20 +194,24 @@ ps_scale() { local DOKKU_PROCFILE="$DOKKU_ROOT/$APP/DOKKU_PROCFILE" shift 1 - [[ ! -e $DOKKU_SCALE_FILE ]] && generate_scale_file "$APP" "$IMAGE_TAG" + [[ ! -e $DOKKU_SCALE_FILE ]] && generate_scale_file "$APP" "$IMAGE_TAG" >/dev/null 2>&1 if [[ -z "$@" ]]; then dokku_log_info1_quiet "Scaling for $APP" - dokku_col_log_info1_quiet "proctype" "qty" - dokku_col_log_info1_quiet "--------" "---" + dokku_col_log_msg_quiet "proctype" "qty" + dokku_col_log_msg_quiet "--------" "---" while read -r line || [[ -n "$line" ]]; do [[ -z "$line" ]] && continue local PROC_NAME=${line%%=*} local PROC_COUNT=${line#*=} - dokku_col_log_info1 "$PROC_NAME" "$PROC_COUNT" + dokku_col_log_msg "${PROC_NAME}:" "$PROC_COUNT" done <"$DOKKU_SCALE_FILE" else - set_scale "$APP" "$@" - release_and_deploy "$APP" "$IMAGE_TAG" + if [[ "$(fn-ps-can-scale "$APP")" == "true" ]]; then + set_scale "$APP" "$@" + release_and_deploy "$APP" "$IMAGE_TAG" + else + dokku_log_fail "App contains DOKKU_SCALE file and cannot be manually scaled" + fi fi } @@ -213,27 +229,9 @@ get_restart_policies() { fn-ps-app-status() { declare APP="$1" - local PROCS=0 RUNNING="" - local APP_CIDS=$(get_app_container_ids "$APP") - for CID in $APP_CIDS; do - if (is_container_status "$CID" "Running"); 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 - - echo "$PROCS $RUNNING" + local DOKKU_SCHEDULER=$(get_app_scheduler "$APP") + plugn trigger scheduler-app-status "$DOKKU_SCHEDULER" "$APP" } fn-ps-is-app-running() { @@ -241,3 +239,17 @@ fn-ps-is-app-running() { APP_STATUS=$(fn-ps-app-status "$APP") echo "$APP_STATUS" | cut -d ' ' -f 2 } + +fn-ps-can-scale() { + declare APP="$1" + local IMAGE_TAG=$(get_running_image_tag "$APP") + local DOKKU_SCALE_FILE="$DOKKU_ROOT/$APP/DOKKU_SCALE" + local CAN_SCALE=false + + [[ ! -e $DOKKU_SCALE_FILE ]] && generate_scale_file "$APP" "$IMAGE_TAG" >/dev/null 2>&1 + if [[ ! -f "${DOKKU_SCALE_FILE}.extracted" ]]; then + CAN_SCALE=true + fi + + echo "$CAN_SCALE" +} diff --git a/plugins/ps/internal-functions b/plugins/ps/internal-functions index cb145acb3..dc816f641 100755 --- a/plugins/ps/internal-functions +++ b/plugins/ps/internal-functions @@ -1,6 +1,7 @@ #!/usr/bin/env bash source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" source "$PLUGIN_AVAILABLE_PATH/docker-options/functions" +source "$PLUGIN_AVAILABLE_PATH/config/functions" source "$PLUGIN_AVAILABLE_PATH/ps/functions" set -eo pipefail [[ $DOKKU_TRACE ]] && set -x @@ -50,7 +51,8 @@ cmd-ps-report-single() { if (is_deployed "$APP"); then DEPLOYED="true"; else DEPLOYED="false"; fi local passed_phases="deploy" - RESTARTPOLICY=$(get_restart_policies "$(get_phase_file_path "$passed_phases")" || true) + local RESTARTPOLICY=$(get_restart_policies "$(get_phase_file_path "$passed_phases")" || true) + local CAN_SCALE="$(fn-ps-can-scale "$APP")" 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 @@ -60,7 +62,7 @@ cmd-ps-report-single() { local APP_CONTAINER_STATUS for CONTAINER_FILE in $CONTAINER_FILES; do CID=$(<"$DOKKU_ROOT/$APP/$CONTAINER_FILE") - APP_CONTAINER_STATUS=$(docker inspect -f '{{.State.Status}}' "$CID" 2>/dev/null || true) + APP_CONTAINER_STATUS=$("$DOCKER_BIN" inspect -f '{{.State.Status}}' "$CID" 2>/dev/null || true) [[ -z "$APP_CONTAINER_STATUS" ]] && APP_CONTAINER_STATUS="missing" STATUSES+=("${CONTAINER_FILE#*.}:$APP_CONTAINER_STATUS#${CID:0:12}") done @@ -70,6 +72,7 @@ cmd-ps-report-single() { "--running: $RUNNING" "--restore: $RESTORE" "--restart-policy: $RESTARTPOLICY" + "--ps-can-scale: $CAN_SCALE" ) if [[ -z "$INFO_FLAG" ]]; then @@ -115,3 +118,53 @@ cmd-ps-report-single() { fi fi } + +fn-ps-parallel-cmd() { + declare desc="Run a ps command against all apps" + declare CMD="$1" + local GNU_PARALLEL has_apps=false + + for APP in $(dokku_apps); do + has_apps=true + break + done + + if [[ "$has_apps" == "false" ]]; then + dokku_log_info1 "No apps detected" + return + fi + + if which parallel >/dev/null 2>&1; then + dokku_log_info1 "Running 'dokku ps:${CMD}' in parallel" + GNU_PARALLEL=$(parallel -V 2>&1 | grep GNU || true) + if [[ -z "$GNU_PARALLEL" ]]; then + # shellcheck disable=SC2046 + parallel $DOKKU_PARALLEL_ARGUMENTS -i bash -c "dokku ps:${CMD} {}" -- $(dokku_apps) + else + # shellcheck disable=SC2046 + dokku_apps | parallel $DOKKU_PARALLEL_ARGUMENTS "dokku ps:${CMD} {}" + fi + return + fi + + dokku_log_info1 "Running 'dokku ps:${CMD}' in serially" + for APP in $(dokku_apps); do + if ! (is_deployed "$APP"); then + dokku_log_warn "App $APP has not been deployed" + continue + fi + + if [[ "$CMD" == "restore" ]]; then + local DOKKU_APP_RESTORE=$(config_get "$APP" DOKKU_APP_RESTORE || true) + if [[ "$DOKKU_APP_RESTORE" == 0 ]]; then + continue + fi + fi + + dokku_log_verbose "Running 'dokku ps:${CMD} $APP' ..." + if ps_${CMD} "$APP"; then + continue + fi + dokku_log_warn "dokku ps:${CMD} $APP failed" + done +} diff --git a/plugins/ps/plugin.toml b/plugins/ps/plugin.toml index 9bc6063fd..00461abaa 100644 --- a/plugins/ps/plugin.toml +++ b/plugins/ps/plugin.toml @@ -1,4 +1,4 @@ [plugin] description = "dokku core ps plugin" -version = "0.14.6" +version = "0.17.9" [plugin.config] diff --git a/plugins/ps/subcommands/default b/plugins/ps/subcommands/default index ed5ca1182..8fc9f35c2 100755 --- a/plugins/ps/subcommands/default +++ b/plugins/ps/subcommands/default @@ -21,7 +21,7 @@ ps_main_cmd() { has_tty && local DOKKU_RUN_OPTS="-i -t" dokku_log_info1_quiet "running processes in container: $CID" # shellcheck disable=SC2086 - docker exec $DOKKU_RUN_OPTS $CID /bin/sh -c "ps auxwww" + "$DOCKER_BIN" exec $DOKKU_RUN_OPTS $CID /bin/sh -c "ps auxwww" done } diff --git a/plugins/ps/subcommands/rebuildall b/plugins/ps/subcommands/rebuildall index 3f10e32a8..9390f1e0d 100755 --- a/plugins/ps/subcommands/rebuildall +++ b/plugins/ps/subcommands/rebuildall @@ -2,38 +2,13 @@ set -eo pipefail [[ $DOKKU_TRACE ]] && set -x source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" -source "$PLUGIN_AVAILABLE_PATH/config/functions" -source "$PLUGIN_AVAILABLE_PATH/ps/functions" +source "$PLUGIN_AVAILABLE_PATH/ps/internal-functions" ps_rebuildall_cmd() { declare desc="rebuilds all apps from source" local cmd="ps:rebuildall" - local GNU_PARALLEL - if which parallel >/dev/null 2>&1; then - dokku_log_info1 "Rebuilding in parallel" - GNU_PARALLEL=$(parallel -V 2>&1 | grep GNU || true) - if [[ -z "$GNU_PARALLEL" ]]; then - # shellcheck disable=SC2046 - parallel -i bash -c "dokku ps:rebuild {}" -- $(dokku_apps) - else - dokku_apps | parallel 'dokku ps:rebuild {}' - fi - return - fi - - for app in $(dokku_apps); do - if ! (is_deployed "$app"); then - dokku_log_warn "App $app has not been deployed" - continue - fi - - dokku_log_verbose "Rebuilding app $app ..." - if ps_rebuild "$app"; then - continue - fi - dokku_log_warn "dokku ps:rebuild ${app} failed" - done + fn-ps-parallel-cmd "rebuild" } ps_rebuildall_cmd "$@" diff --git a/plugins/ps/subcommands/restartall b/plugins/ps/subcommands/restartall index 19a4be7b6..07a8e2a53 100755 --- a/plugins/ps/subcommands/restartall +++ b/plugins/ps/subcommands/restartall @@ -2,38 +2,13 @@ set -eo pipefail [[ $DOKKU_TRACE ]] && set -x source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" -source "$PLUGIN_AVAILABLE_PATH/config/functions" -source "$PLUGIN_AVAILABLE_PATH/ps/functions" +source "$PLUGIN_AVAILABLE_PATH/ps/internal-functions" ps_restartall_cmd() { declare desc="restarts all apps via command line" local cmd="ps:restartall" - local GNU_PARALLEL - if which parallel >/dev/null 2>&1; then - dokku_log_info1 "Restarting in parallel" - GNU_PARALLEL=$(parallel -V 2>&1 | grep GNU || true) - if [[ -z "$GNU_PARALLEL" ]]; then - # shellcheck disable=SC2046 - parallel -i bash -c "dokku ps:restart {}" -- $(dokku_apps) - else - dokku_apps | parallel 'dokku ps:restart {}' - fi - return - fi - - for app in $(dokku_apps); do - if ! (is_deployed "$app"); then - dokku_log_warn "App $app has not been deployed" - continue - fi - - dokku_log_verbose "Restarting app $app ..." - if ps_restart "$app"; then - continue - fi - dokku_log_warn "dokku ps:restart ${app} failed" - done + fn-ps-parallel-cmd "restart" } ps_restartall_cmd "$@" diff --git a/plugins/ps/subcommands/restore b/plugins/ps/subcommands/restore index 94f84206a..d8b43b514 100755 --- a/plugins/ps/subcommands/restore +++ b/plugins/ps/subcommands/restore @@ -4,6 +4,7 @@ set -eo pipefail source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" source "$PLUGIN_AVAILABLE_PATH/config/functions" source "$PLUGIN_AVAILABLE_PATH/ps/functions" +source "$PLUGIN_AVAILABLE_PATH/ps/internal-functions" ps_restore_cmd() { declare desc="starts all apps with DOKKU_APP_RESTORE not set to 0 via command line" @@ -12,7 +13,7 @@ ps_restore_cmd() { local DOKKU_SCHEDULER DOKKU_SCHEDULER=$(get_app_scheduler "$APP") - plugn trigger pre-restore "$DOKKU_SCHEDULER" + plugn trigger pre-restore "$DOKKU_SCHEDULER" "$APP" if [[ -n "$APP" ]]; then if ! (is_deployed "$APP"); then @@ -22,43 +23,11 @@ ps_restore_cmd() { DOKKU_APP_RESTORE="$(config_get "$APP" DOKKU_APP_RESTORE || true)" if [[ $DOKKU_APP_RESTORE != 0 ]]; then - ps_start "$APP" || dokku_log_warn "dokku ps:restore ${APP} failed" + ps_restore "$APP" || dokku_log_warn "dokku ps:restore ${APP} failed" fi else - ps_restore_all + fn-ps-parallel-cmd "restore" fi } -ps_restore_all() { - local GNU_PARALLEL - - if which parallel >/dev/null 2>&1; then - dokku_log_info1 "Restarting in parallel" - GNU_PARALLEL=$(parallel -V 2>&1 | grep GNU || true) - if [[ -z "$GNU_PARALLEL" ]]; then - # shellcheck disable=SC2046 - parallel -i bash -c "dokku ps:restore {}" -- $(dokku_apps) - else - dokku_apps | parallel 'dokku ps:restore {}' - fi - return - fi - - for app in $(dokku_apps); do - if ! (is_deployed "$app"); then - dokku_log_warn "App $app has not been deployed" - continue - fi - - local DOKKU_APP_RESTORE=$(config_get "$app" DOKKU_APP_RESTORE || true) - if [[ $DOKKU_APP_RESTORE != 0 ]]; then - dokku_log_verbose "Restoring app $app ..." - if ps_start "$app"; then - continue - fi - dokku_log_warn "dokku ps:restore ${app} failed" - fi - done -} - ps_restore_cmd "$@" diff --git a/plugins/ps/subcommands/startall b/plugins/ps/subcommands/startall index 13841486e..0ccb7234c 100755 --- a/plugins/ps/subcommands/startall +++ b/plugins/ps/subcommands/startall @@ -2,38 +2,13 @@ set -eo pipefail [[ $DOKKU_TRACE ]] && set -x source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" -source "$PLUGIN_AVAILABLE_PATH/config/functions" -source "$PLUGIN_AVAILABLE_PATH/ps/functions" +source "$PLUGIN_AVAILABLE_PATH/ps/internal-functions" ps_startall_cmd() { declare desc="starts all apps via command line" local cmd="ps:startall" - local GNU_PARALLEL - if which parallel >/dev/null 2>&1; then - dokku_log_info1 "Starting in parallel" - GNU_PARALLEL=$(parallel -V 2>&1 | grep GNU || true) - if [[ -z "$GNU_PARALLEL" ]]; then - # shellcheck disable=SC2046 - parallel -i bash -c "dokku ps:start {}" -- $(dokku_apps) - else - dokku_apps | parallel 'dokku ps:start {}' - fi - return - fi - - for app in $(dokku_apps); do - if ! (is_deployed "$app"); then - dokku_log_warn "App $app has not been deployed" - continue - fi - - dokku_log_verbose "Starting app $app ..." - if ps_start "$app"; then - continue - fi - dokku_log_warn "dokku ps:start ${app} failed" - done + fn-ps-parallel-cmd "start" } ps_startall_cmd "$@" diff --git a/plugins/ps/subcommands/stopall b/plugins/ps/subcommands/stopall index 3014ef189..decef1c98 100755 --- a/plugins/ps/subcommands/stopall +++ b/plugins/ps/subcommands/stopall @@ -2,38 +2,13 @@ set -eo pipefail [[ $DOKKU_TRACE ]] && set -x source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" -source "$PLUGIN_AVAILABLE_PATH/config/functions" -source "$PLUGIN_AVAILABLE_PATH/ps/functions" +source "$PLUGIN_AVAILABLE_PATH/ps/internal-functions" ps_stopall_cmd() { declare desc="stops all apps via command line" local cmd="ps:stopall" - local GNU_PARALLEL - if which parallel >/dev/null 2>&1; then - dokku_log_info1 "Stopping in parallel" - GNU_PARALLEL=$(parallel -V 2>&1 | grep GNU || true) - if [[ -z "$GNU_PARALLEL" ]]; then - # shellcheck disable=SC2046 - parallel -i bash -c "dokku ps:stop {}" -- $(dokku_apps) - else - dokku_apps | parallel 'dokku ps:stop {}' - fi - return - fi - - for app in $(dokku_apps); do - if ! (is_deployed "$app"); then - dokku_log_warn "App $app has not been deployed" - continue - fi - - dokku_log_verbose "Stopping app $app ..." - if ps_stop "$app"; then - continue - fi - dokku_log_warn "dokku ps:stop ${app} failed" - done + fn-ps-parallel-cmd "stop" } ps_stopall_cmd "$@" diff --git a/plugins/repo/Makefile b/plugins/repo/Makefile index 4c9c8bcae..706a28900 100644 --- a/plugins/repo/Makefile +++ b/plugins/repo/Makefile @@ -3,7 +3,7 @@ include ../../common.mk GO_ARGS ?= -a SUBCOMMANDS = subcommands/gc subcommands/purge-cache - +TRIGGERS = triggers/pre-delete build-in-docker: clean docker run --rm \ -v $$PWD/../..:$(GO_REPO_ROOT) \ @@ -11,7 +11,8 @@ build-in-docker: clean $(BUILD_IMAGE) \ bash -c "GO_ARGS='$(GO_ARGS)' make -j4 build" || exit $$? -build: commands subcommands +build: commands subcommands triggers + $(MAKE) triggers-copy commands: **/**/commands.go go build $(GO_ARGS) -o commands src/commands/commands.go @@ -22,7 +23,15 @@ subcommands/%: src/subcommands/*/%.go go build $(GO_ARGS) -o $@ $< clean: - rm -rf commands subcommands + rm -rf commands subcommands triggers pre-delete src-clean: - rm -rf .gitignore src triggers vendor Makefile *.go + rm -rf .gitignore src triggers vendor Makefile *.go glide.* + +triggers: $(TRIGGERS) + +triggers/%: src/triggers/*/%.go + go build $(GO_ARGS) -o $@ $< + +triggers-copy: + cp triggers/* . diff --git a/plugins/repo/plugin.toml b/plugins/repo/plugin.toml index 66e3c750d..39dd0fd82 100644 --- a/plugins/repo/plugin.toml +++ b/plugins/repo/plugin.toml @@ -1,4 +1,4 @@ [plugin] description = "dokku core repo plugin" -version = "0.14.6" +version = "0.17.9" [plugin.config] diff --git a/plugins/repo/repo.go b/plugins/repo/repo.go new file mode 100644 index 000000000..c317e9380 --- /dev/null +++ b/plugins/repo/repo.go @@ -0,0 +1,36 @@ +package repo + +import ( + "os" + "strings" + + "github.com/dokku/dokku/plugins/common" + "github.com/dokku/dokku/plugins/config" +) + +// PurgeCache deletes the contents of the build cache stored in the repository +func PurgeCache(appName string) error { + err := common.VerifyAppName(appName) + if err != nil { + return err + } + + cacheDir := strings.Join([]string{common.MustGetEnv("DOKKU_ROOT"), appName, "cache"}, "/") + cacheHostDir := strings.Join([]string{common.MustGetEnv("DOKKU_HOST_ROOT"), appName, "cache"}, "/") + dokkuGlobalRunArgs := common.MustGetEnv("DOKKU_GLOBAL_RUN_ARGS") + image := config.GetWithDefault(appName, "DOKKU_IMAGE", os.Getenv("DOKKU_IMAGE")) + if info, _ := os.Stat(cacheDir); info != nil && info.IsDir() { + purgeCacheCmd := common.NewShellCmd(strings.Join([]string{ + common.DockerBin(), + "run --rm", dokkuGlobalRunArgs, + "-v", strings.Join([]string{cacheHostDir, ":/cache"}, ""), image, + `find /cache -depth -mindepth 1 -maxdepth 1 -exec rm -Rf {} ;`}, " ")) + purgeCacheCmd.Execute() + err := os.MkdirAll(cacheDir, 0644) + if err != nil { + return err + } + } + + return nil +} diff --git a/plugins/repo/src/subcommands/purge-cache/purge-cache.go b/plugins/repo/src/subcommands/purge-cache/purge-cache.go index 6a39cde85..63665ca18 100644 --- a/plugins/repo/src/subcommands/purge-cache/purge-cache.go +++ b/plugins/repo/src/subcommands/purge-cache/purge-cache.go @@ -2,10 +2,9 @@ package main import ( "flag" - "os" - "strings" "github.com/dokku/dokku/plugins/common" + "github.com/dokku/dokku/plugins/repo" ) // deletes the contents of the build cache stored in the repository @@ -15,23 +14,9 @@ func main() { 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"}, "/") - cacheHostDir := strings.Join([]string{common.MustGetEnv("DOKKU_HOST_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{cacheHostDir, ":/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()) - } + err := repo.PurgeCache(appName) + if err != nil { + common.LogWarn(err.Error()) } } diff --git a/plugins/repo/src/triggers/pre-delete/pre-delete.go b/plugins/repo/src/triggers/pre-delete/pre-delete.go new file mode 100644 index 000000000..c43f5fba0 --- /dev/null +++ b/plugins/repo/src/triggers/pre-delete/pre-delete.go @@ -0,0 +1,19 @@ +package main + +import ( + "flag" + + "github.com/dokku/dokku/plugins/common" + "github.com/dokku/dokku/plugins/repo" +) + +// destroys the buildpacks property for a given app container +func main() { + flag.Parse() + appName := flag.Arg(0) + + err := repo.PurgeCache(appName) + if err != nil { + common.LogFail(err.Error()) + } +} diff --git a/plugins/resource/.gitignore b/plugins/resource/.gitignore new file mode 100644 index 000000000..bdb6a1395 --- /dev/null +++ b/plugins/resource/.gitignore @@ -0,0 +1,9 @@ +/commands +/subcommands/* +/triggers/* +/install +/post-delete +/report +/docker-args-process-deploy +/docker-args-process-run +/resource-get-property diff --git a/plugins/resource/Makefile b/plugins/resource/Makefile new file mode 100644 index 000000000..3a2bf7315 --- /dev/null +++ b/plugins/resource/Makefile @@ -0,0 +1,38 @@ +include ../../common.mk + +GO_ARGS ?= -a + +SUBCOMMANDS = subcommands/limit subcommands/limit-clear subcommands/report subcommands/reserve subcommands/reserve-clear +TRIGGERS = triggers/docker-args-process-deploy triggers/install triggers/post-delete triggers/report triggers/resource-get-property +build-in-docker: clean + docker run --rm \ + -v $$PWD/../..:$(GO_REPO_ROOT) \ + -w $(GO_REPO_ROOT)/plugins/resource \ + $(BUILD_IMAGE) \ + bash -c "GO_ARGS='$(GO_ARGS)' make -j4 build" || exit $$? + +build: commands subcommands triggers + $(MAKE) triggers-copy + +commands: **/**/commands.go + go build $(GO_ARGS) -o commands src/commands/commands.go + +subcommands: $(SUBCOMMANDS) + +subcommands/%: src/subcommands/*/%.go + go build $(GO_ARGS) -o $@ $< + +clean: + rm -rf commands subcommands triggers docker-args-process-deploy docker-args-process-run install post-delete report resource-get-property + +src-clean: + rm -rf .gitignore src triggers vendor Makefile *.go glide.* + +triggers: $(TRIGGERS) + +triggers/%: src/triggers/*/%.go + go build $(GO_ARGS) -o $@ $< + +triggers-copy: + cp triggers/* . + ln -sf docker-args-process-deploy docker-args-process-run diff --git a/plugins/resource/glide.yaml b/plugins/resource/glide.yaml new file mode 100644 index 000000000..3f7a96657 --- /dev/null +++ b/plugins/resource/glide.yaml @@ -0,0 +1,3 @@ +package: . +import: +- package: github.com/ryanuber/columnize diff --git a/plugins/resource/plugin.toml b/plugins/resource/plugin.toml new file mode 100644 index 000000000..e25647e26 --- /dev/null +++ b/plugins/resource/plugin.toml @@ -0,0 +1,4 @@ +[plugin] +description = "dokku core resource plugin" +version = "0.17.9" +[plugin.config] diff --git a/plugins/resource/resource.go b/plugins/resource/resource.go new file mode 100644 index 000000000..eeeebb0a2 --- /dev/null +++ b/plugins/resource/resource.go @@ -0,0 +1,117 @@ +package resource + +import ( + "errors" + "fmt" + "os" + "reflect" + "sort" + "strings" + + "github.com/dokku/dokku/plugins/common" +) + +// Resource is a collection of resource constraints for apps +type Resource struct { + CPU string `json:"cpu"` + Memory string `json:"memory"` + MemorySwap string `json:"memory-swap"` + Network string `json:"network"` + NetworkIngress string `json:"network-ingress"` + NetworkEgress string `json:"network-egress"` +} + +// ReportSingleApp is an internal function that displays the app report for one or more apps +func ReportSingleApp(appName, infoFlag string) { + if err := common.VerifyAppName(appName); err != nil { + common.LogFail(err.Error()) + } + + resources, err := common.PropertyGetAll("resource", appName) + if err != nil { + return + } + + flags := []string{} + infoFlags := map[string]string{} + for key, value := range resources { + flag := fmt.Sprintf("--resource-%v", key) + flags = append(flags, flag) + infoFlags[flag] = value + } + sort.Strings(flags) + + if len(infoFlag) == 0 { + common.LogInfo2Quiet(fmt.Sprintf("%s resource information", appName)) + for _, k := range flags { + v := infoFlags[k] + key := strings.Replace(strings.Replace(strings.TrimPrefix(k, "--resource-"), "-", " ", -1), ".", " ", -1) + common.LogVerbose(fmt.Sprintf("%s%s", Right(fmt.Sprintf("%s:", key), 31, " "), v)) + } + return + } + + for _, k := range flags { + v := infoFlags[k] + if infoFlag == k { + fmt.Fprintln(os.Stdout, v) + return + } + } + + keys := reflect.ValueOf(infoFlags).MapKeys() + strkeys := make([]string, len(keys)) + for i := 0; i < len(keys); i++ { + strkeys[i] = keys[i].String() + } + common.LogFail(fmt.Sprintf("Invalid flag passed, valid flags: %s", strings.Join(strkeys, ", "))) +} + +// GetResourceValue fetches a single value for a given app/process/request/key combination +func GetResourceValue(appName string, processType string, resourceType string, key string) (string, error) { + resources, err := common.PropertyGetAll("resource", appName) + if err != nil { + return "", err + } + + defaultValue := "" + for k, value := range resources { + if k == propertyKey("_default_", resourceType, key) { + defaultValue = value + } + if k == propertyKey(processType, resourceType, key) { + return value, nil + } + } + + return defaultValue, nil +} + +func times(str string, n int) (out string) { + for i := 0; i < n; i++ { + out += str + } + return +} + +// Right right-pads the string with pad up to len runes +func Right(str string, length int, pad string) string { + return str + times(pad, length-len(str)) +} + +func propertyKey(processType string, resourceType string, key string) string { + return fmt.Sprintf("%v.%v.%v", processType, resourceType, key) +} + +func getAppName(args []string) (string, error) { + if len(args) < 1 { + return "", errors.New("Please specify an app to run the command on") + } + + appName := args[0] + if err := common.VerifyAppName(appName); err != nil { + return "", err + } + + return appName, nil +} diff --git a/plugins/resource/src/commands/commands.go b/plugins/resource/src/commands/commands.go new file mode 100644 index 000000000..a998d631d --- /dev/null +++ b/plugins/resource/src/commands/commands.go @@ -0,0 +1,66 @@ +package main + +import ( + "flag" + "fmt" + "os" + "strconv" + "strings" + + "github.com/dokku/dokku/plugins/common" + columnize "github.com/ryanuber/columnize" +) + +const ( + helpHeader = `Usage: dokku resource[:COMMAND] + +Manages resource settings for an app + +Additional commands:` + + helpContent = ` + resource:limit [--process-type ] [RESOURCE_OPTS...] , Limit resources for a given app/process-type combination + resource:limit-clear [--process-type ] , Limit resources for a given app/process-type combination + resource:report [] [], Displays a resource report for one or more apps + resource:reserve [--process-type ] [RESOURCE_OPTS...] , Reserve resources for a given app/process-type combination + resource:reserve-clear [--process-type ] , Reserve resources for a given app/process-type combination +` +) + +func main() { + flag.Usage = usage + flag.Parse() + + cmd := flag.Arg(0) + switch cmd { + case "resource", "resource:help": + usage() + case "help": + command := common.NewShellCmd(fmt.Sprintf("ps -o command= %d", os.Getppid())) + command.ShowOutput = false + output, err := command.Output() + + if err == nil && strings.Contains(string(output), "--all") { + fmt.Println(helpContent) + } else { + fmt.Print("\n resource, Manages resource settings for an app\n") + } + 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 = " " + config.Empty = "" + content := strings.Split(helpContent, "\n")[1:] + fmt.Println(helpHeader) + fmt.Println(columnize.Format(content, config)) +} diff --git a/plugins/resource/src/glide.lock b/plugins/resource/src/glide.lock new file mode 100644 index 000000000..fe4a98f07 --- /dev/null +++ b/plugins/resource/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/resource/src/glide.yaml b/plugins/resource/src/glide.yaml new file mode 100644 index 000000000..3f7a96657 --- /dev/null +++ b/plugins/resource/src/glide.yaml @@ -0,0 +1,3 @@ +package: . +import: +- package: github.com/ryanuber/columnize diff --git a/plugins/resource/src/subcommands/limit-clear/limit-clear.go b/plugins/resource/src/subcommands/limit-clear/limit-clear.go new file mode 100644 index 000000000..b794dfc30 --- /dev/null +++ b/plugins/resource/src/subcommands/limit-clear/limit-clear.go @@ -0,0 +1,20 @@ +package main + +import ( + "flag" + "os" + + "github.com/dokku/dokku/plugins/common" + "github.com/dokku/dokku/plugins/resource" +) + +func main() { + args := flag.NewFlagSet("resource:limit-clear", flag.ExitOnError) + processType := args.String("process-type", "", "process-type: A process type to clear") + args.Parse(os.Args[2:]) + + err := resource.CommandLimitClear(args.Args(), *processType) + if err != nil { + common.LogFail(err.Error()) + } +} diff --git a/plugins/resource/src/subcommands/limit/limit.go b/plugins/resource/src/subcommands/limit/limit.go new file mode 100644 index 000000000..d45438e3a --- /dev/null +++ b/plugins/resource/src/subcommands/limit/limit.go @@ -0,0 +1,35 @@ +package main + +import ( + "flag" + "os" + + "github.com/dokku/dokku/plugins/common" + "github.com/dokku/dokku/plugins/resource" +) + +func main() { + args := flag.NewFlagSet("resource:limit", flag.ExitOnError) + processType := args.String("process-type", "", "process-type: A process type to manage") + cpu := args.String("cpu", "", "cpu: The amount of cpu to limit processes by") + memory := args.String("memory", "", "memory: The amount of memory to limit processes by") + memorySwap := args.String("memory-swap", "", "memory-swap: The amount of swap memory to limit processes by") + network := args.String("network", "", "network: The amount of network bandwidth to limit processes by") + networkIngress := args.String("network-ingress", "", "network-ingress: The amount of ingress network bandwidth to limit processes by") + networkEgress := args.String("network-egress", "", "network-egress: The amount of egress network bandwidth to limit processes by") + args.Parse(os.Args[2:]) + + resources := resource.Resource{ + CPU: *cpu, + Memory: *memory, + MemorySwap: *memorySwap, + Network: *network, + NetworkIngress: *networkIngress, + NetworkEgress: *networkEgress, + } + + err := resource.CommandLimit(args.Args(), *processType, resources) + if err != nil { + common.LogFail(err.Error()) + } +} diff --git a/plugins/resource/src/subcommands/report/report.go b/plugins/resource/src/subcommands/report/report.go new file mode 100644 index 000000000..f8385c3bd --- /dev/null +++ b/plugins/resource/src/subcommands/report/report.go @@ -0,0 +1,33 @@ +package main + +import ( + "flag" + "strings" + + "github.com/dokku/dokku/plugins/common" + "github.com/dokku/dokku/plugins/resource" +) + +// displays a resource report for one or more apps +func main() { + flag.Parse() + appName := flag.Arg(1) + infoFlag := flag.Arg(2) + + if strings.HasPrefix(appName, "--") { + common.LogFail("The resource:report command does not support flags without an app name") + } + + if len(appName) == 0 { + apps, err := common.DokkuApps() + if err != nil { + return + } + for _, appName := range apps { + resource.ReportSingleApp(appName, infoFlag) + } + return + } + + resource.ReportSingleApp(appName, infoFlag) +} diff --git a/plugins/resource/src/subcommands/reserve-clear/reserve-clear.go b/plugins/resource/src/subcommands/reserve-clear/reserve-clear.go new file mode 100644 index 000000000..4f882388d --- /dev/null +++ b/plugins/resource/src/subcommands/reserve-clear/reserve-clear.go @@ -0,0 +1,20 @@ +package main + +import ( + "flag" + "os" + + "github.com/dokku/dokku/plugins/common" + "github.com/dokku/dokku/plugins/resource" +) + +func main() { + args := flag.NewFlagSet("resource:reserve-clear", flag.ExitOnError) + processType := args.String("process-type", "", "process-type: A process type to clear") + args.Parse(os.Args[2:]) + + err := resource.CommandReserveClear(args.Args(), *processType) + if err != nil { + common.LogFail(err.Error()) + } +} diff --git a/plugins/resource/src/subcommands/reserve/reserve.go b/plugins/resource/src/subcommands/reserve/reserve.go new file mode 100644 index 000000000..afeccb551 --- /dev/null +++ b/plugins/resource/src/subcommands/reserve/reserve.go @@ -0,0 +1,35 @@ +package main + +import ( + "flag" + "os" + + "github.com/dokku/dokku/plugins/common" + "github.com/dokku/dokku/plugins/resource" +) + +func main() { + args := flag.NewFlagSet("resource:reserve", flag.ExitOnError) + processType := args.String("process-type", "", "process-type: A process type to manage") + cpu := args.String("cpu", "", "cpu: The amount of cpu to reserve for processes") + memory := args.String("memory", "", "memory: The amount of memory to reserve for processes") + memorySwap := args.String("memory-swap", "", "memory-swap: The amount of swap memory to reserve for processes") + network := args.String("network", "", "network: The amount of network bandwidth to reserve for processes") + networkIngress := args.String("network-ingress", "", "network-ingress: The amount of ingress network bandwidth to reserve for processes") + networkEgress := args.String("network-egress", "", "network-egress: The amount of egress network bandwidth to reserve for processes") + args.Parse(os.Args[2:]) + + resources := resource.Resource{ + CPU: *cpu, + Memory: *memory, + MemorySwap: *memorySwap, + Network: *network, + NetworkIngress: *networkIngress, + NetworkEgress: *networkEgress, + } + + err := resource.CommandReserve(args.Args(), *processType, resources) + if err != nil { + common.LogFail(err.Error()) + } +} diff --git a/plugins/resource/src/triggers/docker-args-process-deploy/docker-args-process-deploy.go b/plugins/resource/src/triggers/docker-args-process-deploy/docker-args-process-deploy.go new file mode 100644 index 000000000..c8f8da8f9 --- /dev/null +++ b/plugins/resource/src/triggers/docker-args-process-deploy/docker-args-process-deploy.go @@ -0,0 +1,89 @@ +package main + +import ( + "flag" + "fmt" + "io/ioutil" + "os" + "strings" + + "github.com/dokku/dokku/plugins/common" +) + +// outputs the process-specific docker options +func main() { + stdin, err := ioutil.ReadAll(os.Stdin) + if err != nil { + common.LogFail(err.Error()) + } + + flag.Parse() + appName := flag.Arg(0) + processType := flag.Arg(3) + + if os.Getenv("DOKKU_OMIT_RESOURCE_ARGS") == "1" { + fmt.Print(string(stdin)) + return + } + + resources, err := common.PropertyGetAll("resource", appName) + if err != nil { + fmt.Print(string(stdin)) + return + } + + limits := make(map[string]string) + reservations := make(map[string]string) + + validLimits := map[string]bool{ + "cpu": true, + "memory": true, + "memory-swap": true, + } + validReservations := map[string]bool{ + "memory": true, + } + validPrefixes := []string{"_default_.", fmt.Sprintf("%s.", processType)} + for _, validPrefix := range validPrefixes { + for key, value := range resources { + if !strings.HasPrefix(key, validPrefix) { + continue + } + parts := strings.SplitN(strings.TrimPrefix(key, validPrefix), ".", 2) + if parts[0] == "limit" { + if !validLimits[parts[1]] { + continue + } + + if parts[1] == "cpu" { + parts[1] = "cpus" + } + + limits[parts[1]] = value + } + if parts[0] == "reserve" { + if !validReservations[parts[1]] { + continue + } + + reservations[parts[1]] = value + } + } + } + + for key, value := range limits { + if value == "" { + continue + } + fmt.Printf(" --%s=%s ", key, value) + } + + for key, value := range reservations { + if value == "" { + continue + } + fmt.Printf(" --%s-reservation=%s ", key, value) + } + + fmt.Print(string(stdin)) +} diff --git a/plugins/resource/src/triggers/install/install.go b/plugins/resource/src/triggers/install/install.go new file mode 100644 index 000000000..77f7d14ed --- /dev/null +++ b/plugins/resource/src/triggers/install/install.go @@ -0,0 +1,14 @@ +package main + +import ( + "fmt" + + "github.com/dokku/dokku/plugins/common" +) + +// runs the install step for the resource plugin +func main() { + if err := common.PropertySetup("resource"); err != nil { + common.LogFail(fmt.Sprintf("Unable to install the resource plugin: %s", err.Error())) + } +} diff --git a/plugins/resource/src/triggers/post-delete/post-delete.go b/plugins/resource/src/triggers/post-delete/post-delete.go new file mode 100644 index 000000000..d22ceccea --- /dev/null +++ b/plugins/resource/src/triggers/post-delete/post-delete.go @@ -0,0 +1,18 @@ +package main + +import ( + "flag" + + "github.com/dokku/dokku/plugins/common" +) + +// destroys the resource property for a given app container +func main() { + flag.Parse() + appName := flag.Arg(0) + + err := common.PropertyDestroy("resource", appName) + if err != nil { + common.LogFail(err.Error()) + } +} diff --git a/plugins/resource/src/triggers/report/report.go b/plugins/resource/src/triggers/report/report.go new file mode 100644 index 000000000..b34ad49e0 --- /dev/null +++ b/plugins/resource/src/triggers/report/report.go @@ -0,0 +1,15 @@ +package main + +import ( + "flag" + + "github.com/dokku/dokku/plugins/resource" +) + +// displays a resource report for one or more apps +func main() { + flag.Parse() + appName := flag.Arg(0) + + resource.ReportSingleApp(appName, "") +} diff --git a/plugins/resource/src/triggers/resource-get-property/resource-get-property.go b/plugins/resource/src/triggers/resource-get-property/resource-get-property.go new file mode 100644 index 000000000..cacd856d9 --- /dev/null +++ b/plugins/resource/src/triggers/resource-get-property/resource-get-property.go @@ -0,0 +1,26 @@ +package main + +import ( + "flag" + "fmt" + "os" + + "github.com/dokku/dokku/plugins/common" + "github.com/dokku/dokku/plugins/resource" +) + +// writes the resource key to stdout for a given app container +func main() { + flag.Parse() + appName := flag.Arg(0) + processType := flag.Arg(1) + resourceType := flag.Arg(2) + key := flag.Arg(3) + + value, err := resource.GetResourceValue(appName, processType, resourceType, key) + if err != nil { + common.LogFail(err.Error()) + } + + fmt.Fprintln(os.Stdout, value) +} diff --git a/plugins/resource/src/vendor/github.com/ryanuber/columnize/.travis.yml b/plugins/resource/src/vendor/github.com/ryanuber/columnize/.travis.yml new file mode 100644 index 000000000..1a0bbea6c --- /dev/null +++ b/plugins/resource/src/vendor/github.com/ryanuber/columnize/.travis.yml @@ -0,0 +1,3 @@ +language: go +go: + - tip diff --git a/plugins/resource/src/vendor/github.com/ryanuber/columnize/LICENSE b/plugins/resource/src/vendor/github.com/ryanuber/columnize/LICENSE new file mode 100644 index 000000000..b9c0e2b68 --- /dev/null +++ b/plugins/resource/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/resource/src/vendor/github.com/ryanuber/columnize/README.md b/plugins/resource/src/vendor/github.com/ryanuber/columnize/README.md new file mode 100644 index 000000000..e47634fc6 --- /dev/null +++ b/plugins/resource/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/resource/src/vendor/github.com/ryanuber/columnize/columnize.go b/plugins/resource/src/vendor/github.com/ryanuber/columnize/columnize.go new file mode 100644 index 000000000..915716a10 --- /dev/null +++ b/plugins/resource/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/resource/src/vendor/github.com/ryanuber/columnize/columnize_test.go b/plugins/resource/src/vendor/github.com/ryanuber/columnize/columnize_test.go new file mode 100644 index 000000000..89dabaa38 --- /dev/null +++ b/plugins/resource/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/resource/subcommands.go b/plugins/resource/subcommands.go new file mode 100644 index 000000000..b607a4609 --- /dev/null +++ b/plugins/resource/subcommands.go @@ -0,0 +1,168 @@ +package resource + +import ( + "fmt" + "github.com/dokku/dokku/plugins/common" +) + +// CommandLimit implements resource:limit +func CommandLimit(args []string, processType string, r Resource) error { + appName, err := getAppName(args) + if err != nil { + return err + } + + return setResourceType(appName, processType, r, "limit") +} + +// CommandLimitClear implements resource:limit-clear +func CommandLimitClear(args []string, processType string) error { + appName, err := getAppName(args) + if err != nil { + return err + } + + clearByResourceType(appName, processType, "limit") + return nil +} + +// CommandReserve implements resource:reserve +func CommandReserve(args []string, processType string, r Resource) error { + appName, err := getAppName(args) + if err != nil { + return err + } + + return setResourceType(appName, processType, r, "reserve") +} + +// CommandReserveClear implements resource:reserve-clear +func CommandReserveClear(args []string, processType string) error { + appName, err := getAppName(args) + if err != nil { + return err + } + + clearByResourceType(appName, processType, "reserve") + return nil +} + +func clearByResourceType(appName string, processType string, resourceType string) { + noun := "limits" + if resourceType == "reserve" { + noun = "reservation" + } + + message := fmt.Sprintf("clearing %v %v", appName, noun) + if processType != "_default_" && processType != "" { + message = fmt.Sprintf("%v (%v)", message, processType) + } + common.LogInfo2Quiet(message) + + if processType == "" { + resources, err := common.PropertyGetAll("resource", appName) + if err != nil { + return + } + for key := range resources { + common.PropertyDelete("resource", appName, key) + } + } else { + resources := []string{ + "cpu", + "memory", + "memory-swap", + "network", + "network-ingress", + "network-egress", + } + + for _, key := range resources { + property := propertyKey(processType, resourceType, key) + common.PropertyDelete("resource", appName, property) + } + } +} + +func setResourceType(appName string, processType string, r Resource, resourceType string) error { + if len(processType) == 0 { + processType = "_default_" + } + + resources := map[string]string{ + "cpu": r.CPU, + "memory": r.Memory, + "memory-swap": r.MemorySwap, + "network": r.Network, + "network-ingress": r.NetworkIngress, + "network-egress": r.NetworkEgress, + } + + hasValues := false + for _, value := range resources { + if value != "" { + hasValues = true + } + } + + if !hasValues { + reportResourceType(appName, processType, resourceType) + return nil + } + + noun := "limits" + if resourceType == "reserve" { + noun = "reservation" + } + message := fmt.Sprintf("Setting resource %v for %v", noun, appName) + if processType != "_default_" { + message = fmt.Sprintf("%v (%v)", message, processType) + } + common.LogInfo2Quiet(message) + + for key, value := range resources { + if value == "" { + continue + } + + common.LogVerbose(fmt.Sprintf("%v: %v", key, value)) + property := propertyKey(processType, resourceType, key) + err := common.PropertyWrite("resource", appName, property, value) + if err != nil { + return err + } + } + + return nil +} + +func reportResourceType(appName string, processType string, resourceType string) { + noun := "limits" + if resourceType == "reserve" { + noun = "reservation" + } + + message := fmt.Sprintf("resource %v %v information", noun, appName) + if processType == "_default_" { + message = fmt.Sprintf("%v [defaults]", message) + } else { + message = fmt.Sprintf("%v (%v)", message, processType) + } + common.LogInfo2Quiet(message) + + resources := []string{ + "cpu", + "memory", + "memory-swap", + "network", + "network-ingress", + "network-egress", + } + + for _, key := range resources { + property := propertyKey(processType, resourceType, key) + value := common.PropertyGet("resource", appName, property) + common.LogVerbose(fmt.Sprintf("%v: %v", key, value)) + } + return +} diff --git a/plugins/resource/vendor/github.com/ryanuber/columnize/.travis.yml b/plugins/resource/vendor/github.com/ryanuber/columnize/.travis.yml new file mode 100644 index 000000000..1a0bbea6c --- /dev/null +++ b/plugins/resource/vendor/github.com/ryanuber/columnize/.travis.yml @@ -0,0 +1,3 @@ +language: go +go: + - tip diff --git a/plugins/resource/vendor/github.com/ryanuber/columnize/LICENSE b/plugins/resource/vendor/github.com/ryanuber/columnize/LICENSE new file mode 100644 index 000000000..b9c0e2b68 --- /dev/null +++ b/plugins/resource/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/resource/vendor/github.com/ryanuber/columnize/README.md b/plugins/resource/vendor/github.com/ryanuber/columnize/README.md new file mode 100644 index 000000000..e47634fc6 --- /dev/null +++ b/plugins/resource/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/resource/vendor/github.com/ryanuber/columnize/columnize.go b/plugins/resource/vendor/github.com/ryanuber/columnize/columnize.go new file mode 100644 index 000000000..915716a10 --- /dev/null +++ b/plugins/resource/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/resource/vendor/github.com/ryanuber/columnize/columnize_test.go b/plugins/resource/vendor/github.com/ryanuber/columnize/columnize_test.go new file mode 100644 index 000000000..89dabaa38 --- /dev/null +++ b/plugins/resource/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/scheduler-docker-local/check-deploy b/plugins/scheduler-docker-local/check-deploy index ba136186e..148c427f7 100755 --- a/plugins/scheduler-docker-local/check-deploy +++ b/plugins/scheduler-docker-local/check-deploy @@ -84,8 +84,8 @@ scheduler-docker-local-check-deploy() { # docker cp exits with status 1 when run as non-root user when it tries to chown the file # 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 + local CHECK_DEPLOY_TMP_WORK_DIR=$(mktemp -d "/tmp/dokku-${FUNCNAME[0]}.XXXX") + "$DOCKER_BIN" cp "$DOKKU_APP_CONTAINER_ID:/app/CHECKS" "$CHECK_DEPLOY_TMP_WORK_DIR" 2>/dev/null || true local FILENAME=${CHECK_DEPLOY_TMP_WORK_DIR}/CHECKS @@ -99,7 +99,7 @@ scheduler-docker-local-check-deploy() { dokku_log_info2_quiet "end $APP $DOKKU_APP_CONTAINER_TYPE container output" fi } - trap 'checks_check_deploy_cleanup $DOKKU_APP_CONTAINER_ID' RETURN INT TERM EXIT + trap "checks_check_deploy_cleanup $DOKKU_APP_CONTAINER_ID" RETURN INT TERM EXIT if [[ ! -s "${CHECK_DEPLOY_TMP_WORK_DIR}/CHECKS" ]] || [[ "$DOKKU_APP_CONTAINER_TYPE" != "web" ]]; then # We allow custom check for web instances only @@ -119,10 +119,10 @@ scheduler-docker-local-check-deploy() { sleep "$DOKKU_DEFAULT_CHECKS_WAIT" ! (is_container_status "$DOKKU_APP_CONTAINER_ID" "Running") && dokku_log_fail "App container failed to start!!" - local container_restarts="$(docker inspect -f "{{ .RestartCount }}" "$DOKKU_APP_CONTAINER_ID")" + local container_restarts="$("$DOCKER_BIN" inspect -f "{{ .RestartCount }}" "$DOKKU_APP_CONTAINER_ID")" if [[ $container_restarts -ne 0 ]]; then - docker container update --restart=no "$DOKKU_APP_CONTAINER_ID" &>/dev/null || true - docker stop "$DOKKU_APP_CONTAINER_ID" || true + "$DOCKER_BIN" container update --restart=no "$DOKKU_APP_CONTAINER_ID" &>/dev/null || true + "$DOCKER_BIN" stop "$DOKKU_APP_CONTAINER_ID" || true dokku_log_fail "App container failed to start!!" fi diff --git a/plugins/scheduler-docker-local/core-post-deploy b/plugins/scheduler-docker-local/core-post-deploy index 5a24b04ef..f78f34c49 100755 --- a/plugins/scheduler-docker-local/core-post-deploy +++ b/plugins/scheduler-docker-local/core-post-deploy @@ -36,26 +36,26 @@ scheduler-docker-local-core-post-deploy() { local NAME="$APP.$DYNO" local CURRENT_CONTAINER_ID="$(<"$container")" # TODO: Ensure these are from the current service - local PREVIOUS_CIDS=$(docker ps -a -q -f name="^.?$NAME\$" | xargs) || true + local PREVIOUS_CIDS=$("$DOCKER_BIN" ps -a -q -f name="^.?$NAME\$" | xargs) || true if [[ -n $PREVIOUS_CIDS ]]; then dokku_log_info1_quiet "Found previous container(s) ($PREVIOUS_CIDS) named $NAME" # in case $PREVIOUS_CIDS has more than one entry local cid for cid in $PREVIOUS_CIDS; do - local PREVIOUS_CONTAINER_STATUS=$(docker inspect -f '{{.State.Status}}' "$cid" || echo "dead") + local PREVIOUS_CONTAINER_STATUS=$("$DOCKER_BIN" inspect -f '{{.State.Status}}' "$cid" || echo "dead") # dead containers cannot be renamed if [[ "$PREVIOUS_CONTAINER_STATUS" != "dead" ]]; then local CONTAINER_DATE_NAME="$NAME.$(date +%s)" dokku_log_info2_quiet "Renaming container ($cid) ${NAME} to $CONTAINER_DATE_NAME" - docker rename "$NAME" "$CONTAINER_DATE_NAME" >/dev/null 2>&1 || dokku_log_warn "Unable to rename container" + "$DOCKER_BIN" rename "$NAME" "$CONTAINER_DATE_NAME" >/dev/null 2>&1 || dokku_log_warn "Unable to rename container" fi done fi local ID=$(cat "$container") - local CURRENT_NAME=$(docker inspect -f '{{.Name}}' "$ID" | tr -d /) + local CURRENT_NAME=$("$DOCKER_BIN" inspect -f '{{.Name}}' "$ID" | tr -d /) if [[ -n "$CURRENT_NAME" ]]; then dokku_log_info2_quiet "Renaming container (${ID:0:12}) $CURRENT_NAME to $NAME" - docker rename "$CURRENT_NAME" "$NAME" >/dev/null + "$DOCKER_BIN" rename "$CURRENT_NAME" "$NAME" >/dev/null fi done shopt -u nullglob diff --git a/plugins/scheduler-docker-local/install b/plugins/scheduler-docker-local/install index 2dfdda4a6..174853aa8 100755 --- a/plugins/scheduler-docker-local/install +++ b/plugins/scheduler-docker-local/install @@ -26,7 +26,7 @@ After=docker.service [Service] Type=oneshot User=$DOKKU_SYSTEM_USER -ExecStart=$DOKKU_PATH ps:restore +ExecStart=$DOKKU_PATH ps:retire [Install] WantedBy=docker.service diff --git a/plugins/scheduler-docker-local/internal-functions b/plugins/scheduler-docker-local/internal-functions index 7dc24d0bf..b787a067d 100755 --- a/plugins/scheduler-docker-local/internal-functions +++ b/plugins/scheduler-docker-local/internal-functions @@ -95,7 +95,7 @@ fn-scheduler-docker-local-retire-container() { declare APP="$1" CID="$2" DEAD_TIME="$3" local STATE - STATE="$(docker inspect -f "{{ .State.Status }}" "$CID" 2>/dev/null || true)" + STATE="$("$DOCKER_BIN" inspect -f "{{ .State.Status }}" "$CID" 2>/dev/null || true)" if [[ -z "$STATE" ]]; then return fi @@ -104,7 +104,7 @@ fn-scheduler-docker-local-retire-container() { [[ $DOKKU_DOCKER_STOP_TIMEOUT ]] && DOCKER_STOP_TIME_ARG="--time=${DOKKU_DOCKER_STOP_TIMEOUT}" if [[ "$STATE" == "restarting" ]]; then - docker update --restart=no "$CID" >/dev/null 2>&1 + "$DOCKER_BIN" update --restart=no "$CID" >/dev/null 2>&1 fi if [[ "$STATE" != "dead" ]] && [[ "$STATE" != "exited" ]]; then @@ -112,18 +112,18 @@ fn-scheduler-docker-local-retire-container() { # to not send SIGKILL as the docs would indicate. If that fails, move # on to the next. # shellcheck disable=SC2086 - docker stop $DOCKER_STOP_TIME_ARG "$CID" \ - || docker kill "$CID" \ + "$DOCKER_BIN" stop $DOCKER_STOP_TIME_ARG "$CID" \ + || "$DOCKER_BIN" kill "$CID" \ || dokku_log_warn "Unable to kill container ${CID}" fi - STATE="$(docker inspect -f "{{ .State.Status }}" "$CID" 2>/dev/null || true)" + STATE="$("$DOCKER_BIN" inspect -f "{{ .State.Status }}" "$CID" 2>/dev/null || true)" if [[ -z "$STATE" ]]; then return fi if [[ "$STATE" != "dead" ]] && [[ "$STATE" != "exited" ]]; then - if ! docker kill "$CID"; then + if ! "$DOCKER_BIN" kill "$CID"; then dokku_log_warn "Unable to kill container ${CID}" fi fi diff --git a/plugins/scheduler-docker-local/plugin.toml b/plugins/scheduler-docker-local/plugin.toml index f8e3c5236..92777fa4f 100644 --- a/plugins/scheduler-docker-local/plugin.toml +++ b/plugins/scheduler-docker-local/plugin.toml @@ -1,4 +1,4 @@ [plugin] description = "dokku core scheduler-docker-local plugin" -version = "0.14.6" +version = "0.17.9" [plugin.config] diff --git a/plugins/scheduler-docker-local/post-delete b/plugins/scheduler-docker-local/post-delete index c180c0905..405ecde4f 100755 --- a/plugins/scheduler-docker-local/post-delete +++ b/plugins/scheduler-docker-local/post-delete @@ -18,14 +18,14 @@ scheduler-docker-local-post-delete() { # remove all application containers & images # shellcheck disable=SC2046 - local DOKKU_APP_CIDS=$(docker ps -a --no-trunc | egrep "dokku/${APP}:" | awk '{ print $1 }' | xargs) + local DOKKU_APP_CIDS=$("$DOCKER_BIN" ps -a --no-trunc | egrep "dokku/${APP}:" | awk '{ print $1 }' | xargs) if [[ -n "$DOKKU_APP_CIDS" ]]; then # shellcheck disable=SC2086 - docker rm -f $DOKKU_APP_CIDS >/dev/null 2>&1 || true + "$DOCKER_BIN" rm -f $DOKKU_APP_CIDS >/dev/null 2>&1 || true fi # shellcheck disable=SC2046 - docker rmi $(docker images -q "$IMAGE_REPO" | xargs) &>/dev/null || true + "$DOCKER_BIN" rmi $("$DOCKER_BIN" images -q "$IMAGE_REPO" | xargs) &>/dev/null || true } scheduler-docker-local-post-delete "$@" diff --git a/plugins/scheduler-docker-local/pre-deploy b/plugins/scheduler-docker-local/pre-deploy index d1b798799..244b65830 100755 --- a/plugins/scheduler-docker-local/pre-deploy +++ b/plugins/scheduler-docker-local/pre-deploy @@ -42,7 +42,7 @@ scheduler-docker-local-pre-deploy() { fi # shellcheck disable=SC2086 - docker run $DOKKU_GLOBAL_RUN_ARGS "${ARG_ARRAY[@]}" $IMAGE /bin/bash -c "find $CONTAINER_PATHS -not -user $DOKKU_APP_USER -print0 | xargs -0 -r chown -R $DOKKU_APP_USER" || true + "$DOCKER_BIN" run $DOKKU_GLOBAL_RUN_ARGS "${ARG_ARRAY[@]}" $IMAGE /bin/bash -c "find $CONTAINER_PATHS -not -user $DOKKU_APP_USER -print0 | xargs -0 -r chown -R $DOKKU_APP_USER" || true } scheduler-docker-local-pre-deploy "$@" diff --git a/plugins/scheduler-docker-local/pre-restore b/plugins/scheduler-docker-local/pre-restore index 7423a636f..802167ffb 100755 --- a/plugins/scheduler-docker-local/pre-restore +++ b/plugins/scheduler-docker-local/pre-restore @@ -14,7 +14,7 @@ scheduler-docker-local-pre-restore() { # delete all "old" containers # shellcheck disable=SC2046 - docker rm $(docker ps --format "{{.Names}}" -a -f "label=$DOKKU_CONTAINER_LABEL" -q | grep -E '(.+\..+\.[0-9]+\.[0-9]+$)') &>/dev/null || true + "$DOCKER_BIN" rm $("$DOCKER_BIN" ps --format "{{.Names}}" -a -f "label=$DOKKU_CONTAINER_LABEL" -q | grep -E '(.+\..+\.[0-9]+\.[0-9]+$)') &>/dev/null || true } scheduler-docker-local-pre-restore "$@" diff --git a/plugins/scheduler-docker-local/scheduler-app-status b/plugins/scheduler-docker-local/scheduler-app-status new file mode 100755 index 000000000..89eddeb50 --- /dev/null +++ b/plugins/scheduler-docker-local/scheduler-app-status @@ -0,0 +1,38 @@ +#!/usr/bin/env bash +set -eo pipefail +[[ $DOKKU_TRACE ]] && set -x +source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" + +scheduler-app-status() { + declare desc="fetches the status for a given app" + declare trigger="scheduler-docker-local scheduler-app-status" + declare DOKKU_SCHEDULER="$1" APP="$2" + + if [[ "$DOKKU_SCHEDULER" != "docker-local" ]]; then + return + fi + + local PROCS=0 RUNNING="" + local APP_CIDS=$(get_app_container_ids "$APP") + + for CID in $APP_CIDS; do + if (is_container_status "$CID" "Running"); 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 + + echo "$PROCS $RUNNING" +} + +scheduler-app-status "$@" diff --git a/plugins/scheduler-docker-local/scheduler-deploy b/plugins/scheduler-docker-local/scheduler-deploy index ef042625d..8fd16640f 100755 --- a/plugins/scheduler-docker-local/scheduler-deploy +++ b/plugins/scheduler-docker-local/scheduler-deploy @@ -25,6 +25,8 @@ scheduler-docker-local-scheduler-deploy() { plugn trigger pre-deploy "$APP" "$IMAGE_TAG" is_image_herokuish_based "$IMAGE" && DOKKU_HEROKUISH=true + local IMAGE_SOURCE_TYPE="dockerfile" + [[ "$DOKKU_HEROKUISH" == "true" ]] && IMAGE_SOURCE_TYPE="herokuish" local DOKKU_SCALE_FILE="$DOKKU_ROOT/$APP/DOKKU_SCALE" local oldids=$(get_app_container_ids "$APP") @@ -50,10 +52,10 @@ scheduler-docker-local-scheduler-deploy() { dokku_log_info2 "stopping $APP.$PROC_TYPE ($cid)" # Disable the container restart policy - docker container update --restart=no "$cid" &>/dev/null || true + "$DOCKER_BIN" container update --restart=no "$cid" &>/dev/null || true # shellcheck disable=SC2086 - docker stop $DOCKER_STOP_TIME_ARG "$cid" &>/dev/null + "$DOCKER_BIN" 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 @@ -69,6 +71,7 @@ scheduler-docker-local-scheduler-deploy() { local DOCKER_ARGS DOCKER_ARGS=$(: | plugn trigger docker-args-deploy "$APP" "$IMAGE_TAG" "$PROC_TYPE" "$CONTAINER_INDEX") DOCKER_ARGS+=" -e DYNO=$PROC_TYPE.$CONTAINER_INDEX " + DOCKER_ARGS+=$(: | plugn trigger docker-args-process-deploy "$APP" "$IMAGE_TAG" "$IMAGE_SOURCE_TYPE" "$PROC_TYPE" "$CONTAINER_INDEX") [[ "$DOKKU_TRACE" ]] && DOCKER_ARGS+=" -e TRACE=true " declare -a ARG_ARRAY @@ -91,16 +94,16 @@ scheduler-docker-local-scheduler-deploy() { START_CMD=$(fn-scheduler-docker-local-extract-start-cmd "$APP" "$PROC_TYPE" "$START_CMD" "$DOKKU_HEROKUISH" "$DOKKU_PORT") if [[ "$DOKKU_NETWORK_BIND_ALL" == "false" ]]; then # shellcheck disable=SC2086 - cid=$(docker run $DOKKU_GLOBAL_RUN_ARGS -d -e PORT=$DOKKU_PORT "${ARG_ARRAY[@]}" $IMAGE $START_CMD) + cid=$("$DOCKER_BIN" run $DOKKU_GLOBAL_RUN_ARGS -d -e PORT=$DOKKU_PORT "${ARG_ARRAY[@]}" $IMAGE $START_CMD) else # shellcheck disable=SC2086 - cid=$(docker run $DOKKU_GLOBAL_RUN_ARGS -d $DOKKU_DOCKER_PORT_ARGS -e PORT=$DOKKU_PORT "${ARG_ARRAY[@]}" $IMAGE $START_CMD) + cid=$("$DOCKER_BIN" run $DOKKU_GLOBAL_RUN_ARGS -d $DOKKU_DOCKER_PORT_ARGS -e PORT=$DOKKU_PORT "${ARG_ARRAY[@]}" $IMAGE $START_CMD) fi else START_CMD=$(fn-scheduler-docker-local-extract-start-cmd "$APP" "$PROC_TYPE" "$START_CMD" "$DOKKU_HEROKUISH") # shellcheck disable=SC2086 - cid=$(docker run $DOKKU_GLOBAL_RUN_ARGS -d "${ARG_ARRAY[@]}" $IMAGE $START_CMD) + cid=$("$DOCKER_BIN" run $DOKKU_GLOBAL_RUN_ARGS -d "${ARG_ARRAY[@]}" $IMAGE $START_CMD) fi ipaddr=$(plugn trigger network-get-ipaddr "$APP" "$PROC_TYPE" "$cid") @@ -111,10 +114,10 @@ scheduler-docker-local-scheduler-deploy() { declare CID="$1" PROC_TYPE="$2" CONTAINER_INDEX="$3" mkdir -p "${DOKKU_LIB_ROOT}/data/scheduler-docker-local/$APP" echo "${CID} ${PROC_TYPE}.${CONTAINER_INDEX}" >>"${DOKKU_LIB_ROOT}/data/scheduler-docker-local/$APP/failed-containers" - docker inspect "$CID" &>/dev/null && { + "$DOCKER_BIN" inspect "$CID" &>/dev/null && { # Disable the container restart policy - docker container update --restart=no "$CID" &>/dev/null || true - docker stop "$CID" >/dev/null && docker kill "$CID" &>/dev/null + "$DOCKER_BIN" container update --restart=no "$CID" &>/dev/null || true + "$DOCKER_BIN" stop "$CID" >/dev/null && "$DOCKER_BIN" kill "$CID" &>/dev/null } trap - INT TERM EXIT kill -9 $$ @@ -178,14 +181,14 @@ scheduler-docker-local-scheduler-deploy() { sleep "$WAIT" for oldid in $oldids; do # Disable the container restart policy - docker container update --restart=no "$oldid" &>/dev/null || true + "$DOCKER_BIN" container update --restart=no "$oldid" &>/dev/null || true # Attempt to stop, if that fails, then force a kill as docker seems # to not send SIGKILL as the docs would indicate. If that fails, move # on to the next. # shellcheck disable=SC2086 - docker stop $DOCKER_STOP_TIME_ARG "$oldid" \ - || docker kill "$oldid" \ + "$DOCKER_BIN" stop $DOCKER_STOP_TIME_ARG "$oldid" \ + || "$DOCKER_BIN" kill "$oldid" \ || plugn trigger retire-container-failed "$APP" "$oldid" # plugin trigger for event logging done ) & diff --git a/plugins/scheduler-docker-local/scheduler-docker-cleanup b/plugins/scheduler-docker-local/scheduler-docker-cleanup index 73e6e7a7d..5e89ef8cd 100755 --- a/plugins/scheduler-docker-local/scheduler-docker-cleanup +++ b/plugins/scheduler-docker-local/scheduler-docker-cleanup @@ -14,15 +14,15 @@ scheduler-docker-local-scheduler-docker-cleanup() { # delete all non-running containers # shellcheck disable=SC2046 - docker rm $(docker ps -a -f "status=exited" -f "label=$DOKKU_CONTAINER_LABEL" -q) &>/dev/null || true + "$DOCKER_BIN" rm $("$DOCKER_BIN" ps -a -f "status=exited" -f "label=$DOKKU_CONTAINER_LABEL" -q) &>/dev/null || true # delete all dead containers # shellcheck disable=SC2046 - docker rm $(docker ps -a -f "status=dead" -f "label=$DOKKU_CONTAINER_LABEL" -q) &>/dev/null || true + "$DOCKER_BIN" rm $("$DOCKER_BIN" ps -a -f "status=dead" -f "label=$DOKKU_CONTAINER_LABEL" -q) &>/dev/null || true # delete unused images # shellcheck disable=SC2046 - docker rmi $(docker images -f 'dangling=true' -q) &>/dev/null & + "$DOCKER_BIN" rmi $("$DOCKER_BIN" images -f 'dangling=true' -q) &>/dev/null & } scheduler-docker-local-scheduler-docker-cleanup "$@" diff --git a/plugins/scheduler-docker-local/scheduler-inspect b/plugins/scheduler-docker-local/scheduler-inspect index 86c26b0b3..5353f3291 100755 --- a/plugins/scheduler-docker-local/scheduler-inspect +++ b/plugins/scheduler-docker-local/scheduler-inspect @@ -13,8 +13,8 @@ scheduler-docker-local-scheduler-inspect() { return fi - local TMP_INSPECT_CMD=$(mktemp "/tmp/${FUNCNAME[0]}.XXXX") - trap 'rm -rf "$TMP_INSPECT_CMD" >/dev/null' RETURN INT TERM EXIT + local TMP_INSPECT_CMD=$(mktemp "/tmp/dokku-${FUNCNAME[0]}.XXXX") + trap "rm -rf '$TMP_INSPECT_CMD' >/dev/null" RETURN INT TERM EXIT cat >"$TMP_INSPECT_CMD" </dev/null + echo $? + ) -eq 0 ]]; then + return 0 + fi + + return 1 +} + +scheduler-docker-local-scheduler-is-deployed "$@" diff --git a/plugins/scheduler-docker-local/scheduler-logs b/plugins/scheduler-docker-local/scheduler-logs new file mode 100755 index 000000000..d785388e8 --- /dev/null +++ b/plugins/scheduler-docker-local/scheduler-logs @@ -0,0 +1,50 @@ +#!/usr/bin/env bash +set -eo pipefail +[[ $DOKKU_TRACE ]] && set -x +source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" + +scheduler-docker-local-scheduler-logs() { + declare desc="scheduler-docker-local scheduler-logs plugin trigger" + declare trigger="scheduler-docker-local scheduler-logs" + declare DOKKU_SCHEDULER="$1" APP="$2" PROCESS_TYPE="$3" TAIL="$4" PRETTY_PRINT="$5" NUM="$6" + local DOKKU_LOGS_ARGS="" + + if [[ "$DOKKU_SCHEDULER" != "docker-local" ]]; then + return + fi + + if [[ "$TAIL" == "true" ]]; then + DOKKU_LOGS_ARGS="--follow " + fi + + local APP_ROOT="$DOKKU_ROOT/$APP" + local COLORS=(36 33 32 35 31) + if [[ -n "$PROCESS_TYPE" ]]; then + local CONTAINERS=("$APP_ROOT/CONTAINER.$PROCESS_TYPE".*) + else + local CONTAINERS=("$APP_ROOT"/CONTAINER.*) + fi + [[ -z $(stat -t "${CONTAINERS[0]}" 2>/dev/null) ]] && exit 0 + + local DOKKU_LOGS_ARGS+="--tail $NUM" + ((MAX_INDEX = ${#CONTAINERS[*]} - 1)) || true + for i in ${!CONTAINERS[*]}; do + local DYNO=$(echo "${CONTAINERS[i]}" | sed -r 's/.*CONTAINER\.(.*)/\1/') + local CID=$(<"${CONTAINERS[i]}") + local COLOR=${COLORS[i % ${#COLORS[*]}]} + if [[ $PRETTY_PRINT == "true" ]]; then + local DOKKU_LOGS_CMD+="($DOCKER_BIN logs $DOKKU_LOGS_ARGS $CID 2>&1)" + else + local DOKKU_LOGS_PRETTY_PRINT_CMD="sed -r 's/^([^Z]+Z )/\x1b[${COLOR}m\1app[$DYNO]:\x1b[0m /gm'" + local DOKKU_LOGS_CMD+="($DOCKER_BIN logs -t $DOKKU_LOGS_ARGS $CID 2>&1 | $DOKKU_LOGS_PRETTY_PRINT_CMD)" + fi + if [[ $i != "$MAX_INDEX" ]]; then + local DOKKU_LOGS_CMD+="& " + else + local DOKKU_LOGS_CMD+="; " + fi + done + bash -c "($DOKKU_LOGS_CMD)" +} + +scheduler-docker-local-scheduler-logs "$@" diff --git a/plugins/scheduler-docker-local/scheduler-logs-failed b/plugins/scheduler-docker-local/scheduler-logs-failed index d816155e2..476e91b72 100755 --- a/plugins/scheduler-docker-local/scheduler-logs-failed +++ b/plugins/scheduler-docker-local/scheduler-logs-failed @@ -24,7 +24,7 @@ scheduler-docker-local-scheduler-logs-failed() { while read -r LINE || [[ -n "$LINE" ]]; do CID="$(echo "$LINE" | cut -d ' ' -f1)" PREFIX="$(echo "$LINE" | cut -d ' ' -f2)" - if docker inspect "${CID}" >/dev/null 2>&1; then + if "$DOCKER_BIN" inspect "${CID}" >/dev/null 2>&1; then RUNNING_CONTAINERS+=("$CID") else DEAD_CONTAINERS+=("$CID") @@ -44,7 +44,7 @@ scheduler-docker-local-scheduler-logs-failed() { ((MAX_INDEX = ${#RUNNING_CONTAINERS[*]} - 1)) || true for i in ${!RUNNING_CONTAINERS[*]}; do local CID="${RUNNING_CONTAINERS[i]}" - DOKKU_LOGS_CMD+="(docker logs $DOKKU_LOGS_ARGS $CID 2>&1)" + DOKKU_LOGS_CMD+="($DOCKER_BIN logs $DOKKU_LOGS_ARGS $CID 2>&1)" if [[ $i != "$MAX_INDEX" ]]; then local DOKKU_LOGS_CMD+="& " else diff --git a/plugins/scheduler-docker-local/scheduler-retire b/plugins/scheduler-docker-local/scheduler-retire index c65c2c66e..af24e75ad 100755 --- a/plugins/scheduler-docker-local/scheduler-retire +++ b/plugins/scheduler-docker-local/scheduler-retire @@ -27,7 +27,7 @@ scheduler-docker-local-scheduler-retire() { fi fn-scheduler-docker-local-retire-container "$APP" "$CID" "$DEAD_TIME" - STATE="$(docker inspect -f "{{ .State.Status }}" "$CID" 2>/dev/null || true)" + STATE="$("$DOCKER_BIN" inspect -f "{{ .State.Status }}" "$CID" 2>/dev/null || true)" if [[ -z "$STATE" ]]; then DEAD_CONTAINERS+=("$CID") continue @@ -38,8 +38,8 @@ scheduler-docker-local-scheduler-retire() { continue fi - docker rm -f "$CID" >/dev/null 2>&1 || true - if docker inspect "${CID}" >/dev/null 2>&1; then + "$DOCKER_BIN" rm -f "$CID" >/dev/null 2>&1 || true + if "$DOCKER_BIN" inspect "${CID}" >/dev/null 2>&1; then dokku_log_warn "Container ${CID} still running" continue fi diff --git a/plugins/scheduler-docker-local/scheduler-run b/plugins/scheduler-docker-local/scheduler-run index c28318c60..478a429a1 100755 --- a/plugins/scheduler-docker-local/scheduler-run +++ b/plugins/scheduler-docker-local/scheduler-run @@ -8,8 +8,11 @@ source "$PLUGIN_AVAILABLE_PATH/ps/functions" scheduler-docker-local-scheduler-run() { declare desc="runs command in container based on app image" declare trigger="scheduler-docker-local scheduler-run" - declare DOKKU_SCHEDULER="$1" APP="$2" - shift 2 + declare DOKKU_SCHEDULER="$1" APP="$2" ENV_COUNT="$3" + shift 3 + + declare RUN_ENV=("${@:1:ENV_COUNT}") + shift "$ENV_COUNT" if [[ "$DOKKU_SCHEDULER" != "docker-local" ]]; then return @@ -25,34 +28,45 @@ scheduler-docker-local-scheduler-run() { fi local DOCKER_ARGS=$(: | plugn trigger docker-args-run "$APP" "$IMAGE_TAG") - [[ "$DOKKU_TRACE" ]] && local DOCKER_ARGS+=" -e TRACE=true " + [[ "$DOKKU_TRACE" ]] && DOCKER_ARGS+=" -e TRACE=true " + + local IMAGE_SOURCE_TYPE="dockerfile" + is_image_herokuish_based "$IMAGE" && IMAGE_SOURCE_TYPE="herokuish" + DOCKER_ARGS+=$(: | plugn trigger docker-args-process-run "$APP" "$IMAGE_TAG" "$IMAGE_SOURCE_TYPE") + DOCKER_ARGS+=" -e DYNO=run.$RANDOM" declare -a ARG_ARRAY eval "ARG_ARRAY=($DOCKER_ARGS)" local DOKKU_RUN_OPTS=" -e DYNO=run.$RANDOM" if [[ "$DOKKU_RM_CONTAINER" ]]; then - DOKKU_RUN_OPTS+=" --rm" + DOCKER_ARGS+=" --rm" elif [[ "$DOKKU_DETACH_CONTAINER" ]]; then - DOKKU_RUN_OPTS+=" --detach" + DOCKER_ARGS+=" --detach" fi + has_tty && DOCKER_ARGS+=" -i -t" + # the output of docker-args-run is possibly quoted/escaped so eval to normalize + declare -a DOCKER_ARGS_ARRAY + eval "DOCKER_ARGS_ARRAY=($DOCKER_ARGS)" + for env_pair in "${RUN_ENV[@]}"; do + DOCKER_ARGS_ARRAY+=("--env=$env_pair") + done - has_tty && DOKKU_RUN_OPTS+=" -i -t" - is_image_herokuish_based "$IMAGE" && local EXEC_CMD="/exec" + [[ "$IMAGE_SOURCE_TYPE" == "herokuish" ]] && local EXEC_CMD="/exec" DOKKU_QUIET_OUTPUT=1 extract_procfile "$APP" POTENTIAL_PROCFILE_KEY="$1" - PROC_CMD=$(get_cmd_from_procfile "$APP" "$POTENTIAL_PROCFILE_KEY" || echo '') + PROC_CMD=$(get_cmd_from_procfile "$APP" "$POTENTIAL_PROCFILE_KEY" 2>/dev/null || echo '') remove_procfile "$APP" if [[ -n "$PROC_CMD" ]]; then - dokku_log_info1 "Found '$POTENTIAL_PROCFILE_KEY' in Procfile, running that command" + dokku_log_info1_quiet "Found '$POTENTIAL_PROCFILE_KEY' in Procfile, running that command" set -- "$PROC_CMD" "${@:2}" fi # shellcheck disable=SC2086 - docker run $DOKKU_GLOBAL_RUN_ARGS $DOKKU_RUN_OPTS "${ARG_ARRAY[@]}" $IMAGE $EXEC_CMD "$@" + "$DOCKER_BIN" run $DOKKU_GLOBAL_RUN_ARGS "${DOCKER_ARGS_ARRAY[@]}" $IMAGE $EXEC_CMD "$@" } scheduler-docker-local-scheduler-run "$@" diff --git a/plugins/scheduler-docker-local/scheduler-stop b/plugins/scheduler-docker-local/scheduler-stop index 3edb3431a..553b950bd 100755 --- a/plugins/scheduler-docker-local/scheduler-stop +++ b/plugins/scheduler-docker-local/scheduler-stop @@ -21,10 +21,10 @@ scheduler-docker-local-scheduler-stop() { if [[ -n "$DOKKU_APP_RUNNING_CONTAINER_IDS" ]]; then # Disable the container restart policy # shellcheck disable=SC2086 - docker container update --restart=no $DOKKU_APP_RUNNING_CONTAINER_IDS &>/dev/null || true + "$DOCKER_BIN" container update --restart=no $DOKKU_APP_RUNNING_CONTAINER_IDS &>/dev/null || true # shellcheck disable=SC2086 - docker stop $DOCKER_STOP_TIME_ARG $DOKKU_APP_RUNNING_CONTAINER_IDS >/dev/null || true + "$DOCKER_BIN" stop $DOCKER_STOP_TIME_ARG $DOKKU_APP_RUNNING_CONTAINER_IDS >/dev/null || true fi if [[ "$REMOVE_CONTAINERS" == "true" ]]; then @@ -32,7 +32,7 @@ scheduler-docker-local-scheduler-stop() { if [[ -n "$DOKKU_APP_CIDS" ]]; then # shellcheck disable=SC2086 - docker rm -f $DOKKU_APP_CIDS >/dev/null 2>&1 || true + "$DOCKER_BIN" rm -f $DOKKU_APP_CIDS >/dev/null 2>&1 || true fi fi } diff --git a/plugins/scheduler-docker-local/scheduler-tags-create b/plugins/scheduler-docker-local/scheduler-tags-create index c4c41d81d..5d04aa6f7 100755 --- a/plugins/scheduler-docker-local/scheduler-tags-create +++ b/plugins/scheduler-docker-local/scheduler-tags-create @@ -15,7 +15,7 @@ scheduler-docker-local-scheduler-tags-create() { local TAG_OPTS="" [[ $(is_tag_force_available) ]] && TAG_OPTS="-f" # shellcheck disable=SC2086 - docker tag $TAG_OPTS "$SOURCE_IMAGE" "$TARGET_IMAGE" + "$DOCKER_BIN" tag $TAG_OPTS "$SOURCE_IMAGE" "$TARGET_IMAGE" } is_tag_force_available() { diff --git a/plugins/scheduler-docker-local/scheduler-tags-destroy b/plugins/scheduler-docker-local/scheduler-tags-destroy index 5c3ccfb1d..0fbaeab2f 100755 --- a/plugins/scheduler-docker-local/scheduler-tags-destroy +++ b/plugins/scheduler-docker-local/scheduler-tags-destroy @@ -12,7 +12,7 @@ scheduler-docker-local-scheduler-tags-destroy() { return fi - docker rmi "$IMAGE_REPO:$IMAGE_TAG" + "$DOCKER_BIN" rmi "$IMAGE_REPO:$IMAGE_TAG" } scheduler-docker-local-scheduler-tags-destroy "$@" diff --git a/plugins/shell/plugin.toml b/plugins/shell/plugin.toml index 4adb0b2c1..f50aeec8f 100644 --- a/plugins/shell/plugin.toml +++ b/plugins/shell/plugin.toml @@ -1,4 +1,4 @@ [plugin] description = "dokku core shell plugin" -version = "0.14.6" +version = "0.17.9" [plugin.config] diff --git a/plugins/ssh-keys/functions b/plugins/ssh-keys/functions index f18200830..4569d9f1c 100755 --- a/plugins/ssh-keys/functions +++ b/plugins/ssh-keys/functions @@ -14,9 +14,15 @@ 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." local key line=0 + local TMP_KEY_FILE + TMP_KEY_FILE=$(mktemp "/tmp/dokku-${FUNCNAME[0]}.XXXX") + trap "rm -rf '$TMP_KEY_FILE' >/dev/null" RETURN INT TERM EXIT + 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." + [[ -z "$key" ]] && continue + echo "$key" >"$TMP_KEY_FILE" + ssh-keygen -lf "$TMP_KEY_FILE" &>/dev/null || dokku_log_fail "${DOKKU_ROOT}/.ssh/authorized_keys line $line failed ssh-keygen check." done <"${DOKKU_ROOT}/.ssh/authorized_keys" } diff --git a/plugins/ssh-keys/plugin.toml b/plugins/ssh-keys/plugin.toml index c4e2cc3f5..009424674 100644 --- a/plugins/ssh-keys/plugin.toml +++ b/plugins/ssh-keys/plugin.toml @@ -1,4 +1,4 @@ [plugin] description = "dokku core ssh-keys plugin" -version = "0.14.6" +version = "0.17.9" [plugin.config] diff --git a/plugins/ssh-keys/subcommands/add b/plugins/ssh-keys/subcommands/add index ecd6c9746..b6fa76bce 100755 --- a/plugins/ssh-keys/subcommands/add +++ b/plugins/ssh-keys/subcommands/add @@ -7,22 +7,34 @@ source "$PLUGIN_AVAILABLE_PATH/ssh-keys/functions" add_keys() { declare desc="add a new key via sshcommand" local cmd="ssh-keys:add" - shift - local name="$1" key_file="$2" key_contents key_from_pipe - [[ -p /dev/stdin ]] && read -r key_from_pipe - if [[ -n "$key_from_pipe" ]]; then - ssh-keygen -lf /dev/stdin <<<"$key_from_pipe" &>/dev/null || dokku_log_fail "Key piped in is not a valid ssh public key" - key_contents="$key_from_pipe" - elif [[ -n "$key_file" ]]; then - key_contents="$(cat "$key_file")" - ssh-keygen -lf /dev/stdin <<<"$key_contents" &>/dev/null || dokku_log_fail "Key from file is not a valid ssh public key" + [[ "$1" == "$cmd" ]] && shift 1 + declare NAME="$1" KEY_FILE="$2" + local TMP_KEY_FILE + TMP_KEY_FILE=$(mktemp "/tmp/dokku-${FUNCNAME[0]}.XXXX") + trap "rm -rf '$TMP_KEY_FILE' >/dev/null" RETURN INT TERM EXIT + + if [[ -n "$KEY_FILE" ]]; then + true + elif [[ -p /dev/stdin ]]; then + local key_contents + read -r key_contents + echo "$key_contents" >"$TMP_KEY_FILE" + KEY_FILE="$TMP_KEY_FILE" + else + dokku_log_fail "No key specified via file or pipe" 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 ' + + if [[ ! -s "$KEY_FILE" ]]; then + dokku_log_fail "Empty key was specified" + fi + + local count="$(wc -l "$KEY_FILE" | awk '{ print $1; }')" + # Don't check for "-eq 1" as no newlines is also a valid SSH key + [[ "$count" -lt 2 ]] || dokku_log_fail "Too many keys provided, set one per invocation of dokku ssh-keys:add " + ssh-keygen -lf "$KEY_FILE" &>/dev/null || dokku_log_fail "Key specified in is not a valid ssh public key" create_ssh_key_file verify_ssh_key_exists - echo "$key_contents" | sshcommand acl-add dokku "$name" || dokku_log_fail "sshcommand returned an error: $?" + sshcommand acl-add dokku "$NAME" "$KEY_FILE" || dokku_log_fail "sshcommand returned an error: $?" verify_ssh_key_file } diff --git a/plugins/storage/plugin.toml b/plugins/storage/plugin.toml index 38d34477f..112890449 100644 --- a/plugins/storage/plugin.toml +++ b/plugins/storage/plugin.toml @@ -1,4 +1,4 @@ [plugin] description = "dokku core storage plugin" -version = "0.14.6" +version = "0.17.9" [plugin.config] diff --git a/plugins/tags/plugin.toml b/plugins/tags/plugin.toml index a745009d7..426b3e53d 100644 --- a/plugins/tags/plugin.toml +++ b/plugins/tags/plugin.toml @@ -1,4 +1,4 @@ [plugin] description = "dokku core tags plugin" -version = "0.14.6" +version = "0.17.9" [plugin.config] diff --git a/plugins/tags/subcommands/default b/plugins/tags/subcommands/default index 1c3595123..7ccecf21e 100755 --- a/plugins/tags/subcommands/default +++ b/plugins/tags/subcommands/default @@ -12,7 +12,7 @@ tags_main_cmd() { verify_app_name "$APP" dokku_log_info2_quiet "Image tags for $IMAGE_REPO" - docker images "$IMAGE_REPO" + "$DOCKER_BIN" images "$IMAGE_REPO" } tags_main_cmd "$@" diff --git a/plugins/tar/functions b/plugins/tar/functions index d2c5e6624..c399ecc52 100755 --- a/plugins/tar/functions +++ b/plugins/tar/functions @@ -11,8 +11,8 @@ tar_build() { shift 1 # clean up after ourselves - local TAR_BUILD_TMP_WORK_DIR=$(mktemp -d "/tmp/dokku_tar.XXXX") - trap 'rm -rf "$TAR_BUILD_TMP_WORK_DIR" >/dev/null' RETURN INT TERM EXIT + local TAR_BUILD_TMP_WORK_DIR=$(mktemp -d "/tmp/dokku-${FUNCNAME[0]}.XXXX") + trap "rm -rf '$TAR_BUILD_TMP_WORK_DIR' >/dev/null" RETURN INT TERM EXIT # extract tar file chmod 755 "$TAR_BUILD_TMP_WORK_DIR" diff --git a/plugins/tar/plugin.toml b/plugins/tar/plugin.toml index 1b2a835c8..7afea3137 100644 --- a/plugins/tar/plugin.toml +++ b/plugins/tar/plugin.toml @@ -1,4 +1,4 @@ [plugin] description = "dokku core tar plugin" -version = "0.14.6" +version = "0.17.9" [plugin.config] diff --git a/plugins/trace/commands b/plugins/trace/commands new file mode 100755 index 000000000..4e8ffb885 --- /dev/null +++ b/plugins/trace/commands @@ -0,0 +1,16 @@ +#!/usr/bin/env bash +[[ " help trace:help " == *" $1 "* ]] || exit "$DOKKU_NOT_IMPLEMENTED_EXIT" +source "$PLUGIN_AVAILABLE_PATH/trace/internal-functions" +set -eo pipefail +[[ $DOKKU_TRACE ]] && set -x + +case "$1" in + help | trace:help) + trace_help_cmd "$@" + ;; + + *) + exit "$DOKKU_NOT_IMPLEMENTED_EXIT" + ;; + +esac diff --git a/plugins/trace/internal-functions b/plugins/trace/internal-functions new file mode 100755 index 000000000..a29331500 --- /dev/null +++ b/plugins/trace/internal-functions @@ -0,0 +1,28 @@ +#!/usr/bin/env bash +source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" + +trace_help_content_func() { + declare desc="return trace plugin help content" + cat <"$DOKKU_ROOT/.dokkurc/DOKKU_TRACE" fi if [[ "$2" == "off" ]]; then - echo "Disabling dokku trace" + dokku_log_info1 "Disabling trace mode" rm -f "$DOKKU_ROOT/.dokkurc/DOKKU_TRACE" fi } -dokku_trace_cmd "$@" +trace_default_cmd "$@" diff --git a/plugins/trace/subcommands/off b/plugins/trace/subcommands/off new file mode 100755 index 000000000..a7f44c0eb --- /dev/null +++ b/plugins/trace/subcommands/off @@ -0,0 +1,15 @@ +#!/usr/bin/env bash +set -eo pipefail +[[ $DOKKU_TRACE ]] && set -x +source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" + +trace_off_cmd() { + declare desc="disables trace mode" + local cmd="trace" + [[ -d $DOKKU_ROOT/.dokkurc ]] || mkdir -p "$DOKKU_ROOT/.dokkurc" + + dokku_log_info1 "Disabling trace mode" + rm -f "$DOKKU_ROOT/.dokkurc/DOKKU_TRACE" +} + +trace_off_cmd "$@" diff --git a/plugins/trace/subcommands/on b/plugins/trace/subcommands/on new file mode 100755 index 000000000..694791b16 --- /dev/null +++ b/plugins/trace/subcommands/on @@ -0,0 +1,15 @@ +#!/usr/bin/env bash +set -eo pipefail +[[ $DOKKU_TRACE ]] && set -x +source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" + +trace_on_cmd() { + declare desc="enables trace mode" + local cmd="trace" + [[ -d $DOKKU_ROOT/.dokkurc ]] || mkdir -p "$DOKKU_ROOT/.dokkurc" + + dokku_log_info1 "Enabling trace mode" + echo "export DOKKU_TRACE=1" >"$DOKKU_ROOT/.dokkurc/DOKKU_TRACE" +} + +trace_on_cmd "$@" diff --git a/rpm.mk b/rpm.mk index 9bb455a60..02dca57ea 100644 --- a/rpm.mk +++ b/rpm.mk @@ -46,61 +46,25 @@ rpm-herokuish: --license 'MIT License' \ . -rpm-dokku: - rm -rf /tmp/tmp /tmp/build $(BUILD_DIRECTORY)/dokku_*_$(RPM_ARCHITECTURE).rpm - mkdir -p /tmp/tmp /tmp/build +rpm-dokku: /tmp/build-dokku/var/lib/dokku/GIT_REV + rm -f $(BUILD_DIRECTORY)/dokku_*_$(RPM_ARCHITECTURE).rpm - mkdir -p /tmp/build/usr/share/bash-completion/completions - 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 - cp contrib/bash-completion /tmp/build/usr/share/bash-completion/completions/dokku - 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 + cat /tmp/build-dokku/var/lib/dokku/VERSION | cut -d '-' -f 1 | cut -d 'v' -f 2 > /tmp/build-dokku/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 + sed -i.bak -e "s/^/`date +%s`-/" /tmp/build-dokku/var/lib/dokku/STABLE_VERSION && rm /tmp/build-dokku/var/lib/dokku/STABLE_VERSION.bak 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 \ + VERSION=$$(cat /tmp/build-dokku/var/lib/dokku/STABLE_VERSION); \ + sudo fpm -t rpm -s dir -C /tmp/build-dokku -n dokku \ --version "$$VERSION" \ --architecture $(RPM_ARCHITECTURE) \ --package "$(BUILD_DIRECTORY)/$(DOKKU_RPM_PACKAGE_NAME)" \ --depends '/usr/bin/docker' \ --depends 'bind-utils' \ --depends 'curl' \ - --depends 'gcc' \ --depends 'git' \ --depends 'gliderlabs-sigil' \ - --depends 'make' \ --depends 'man-db' \ --depends 'nc' \ --depends 'nginx >= 1.8.0' \ diff --git a/tests.mk b/tests.mk index 6638e6da6..c42dc22b3 100644 --- a/tests.mk +++ b/tests.mk @@ -69,10 +69,11 @@ else ifeq ($(shell grep dokku.me /root/.ssh/config),) endif ifneq ($(wildcard /etc/ssh/sshd_config),) + sed --in-place "s/^#Port 22$\/Port 22/g" /etc/ssh/sshd_config ifeq ($(shell grep 22333 /etc/ssh/sshd_config),) sed --in-place "s:^Port 22:Port 22 \\nPort 22333:g" /etc/ssh/sshd_config - restart ssh endif + service ssh restart endif @echo "-----> Installing SSH public key..." diff --git a/tests/apps/dockerfile-dokku-scale/CHECKS b/tests/apps/dockerfile-dokku-scale/CHECKS new file mode 100644 index 000000000..068926825 --- /dev/null +++ b/tests/apps/dockerfile-dokku-scale/CHECKS @@ -0,0 +1,5 @@ +WAIT=2 # wait 2 seconds +TIMEOUT=5 # set timeout to 5 seconds +ATTEMPTS=2 # try 2 times + +/ nodejs/express diff --git a/tests/apps/dockerfile-dokku-scale/DOKKU_SCALE b/tests/apps/dockerfile-dokku-scale/DOKKU_SCALE new file mode 100644 index 000000000..3cb9829e6 --- /dev/null +++ b/tests/apps/dockerfile-dokku-scale/DOKKU_SCALE @@ -0,0 +1 @@ +web=1 diff --git a/tests/apps/dockerfile-dokku-scale/Dockerfile b/tests/apps/dockerfile-dokku-scale/Dockerfile new file mode 100644 index 000000000..fa82185d5 --- /dev/null +++ b/tests/apps/dockerfile-dokku-scale/Dockerfile @@ -0,0 +1,11 @@ +FROM node:4 + +EXPOSE 3001/udp +EXPOSE 3000/tcp +EXPOSE 3003 + +COPY . /app +WORKDIR /app +RUN npm install + +CMD npm start diff --git a/tests/apps/dockerfile-dokku-scale/Procfile b/tests/apps/dockerfile-dokku-scale/Procfile new file mode 100644 index 000000000..9c49227a7 --- /dev/null +++ b/tests/apps/dockerfile-dokku-scale/Procfile @@ -0,0 +1,8 @@ +############################### +# DEVELOPMENT # +############################### + +# Procfile for development using the new threaded worker (scheduler, twitter stream and delayed job) +cron: node worker.js +web: node web.js +worker: node worker.js diff --git a/tests/apps/dockerfile-dokku-scale/check_deploy b/tests/apps/dockerfile-dokku-scale/check_deploy new file mode 100755 index 000000000..e4519b442 --- /dev/null +++ b/tests/apps/dockerfile-dokku-scale/check_deploy @@ -0,0 +1,5 @@ +#!/usr/bin/env bash +set -e +output="$(curl -s -S "$1")" +echo "$output" +test "$output" == "nodejs/express" diff --git a/tests/apps/dockerfile-dokku-scale/package.json b/tests/apps/dockerfile-dokku-scale/package.json new file mode 100644 index 000000000..397b0ce84 --- /dev/null +++ b/tests/apps/dockerfile-dokku-scale/package.json @@ -0,0 +1,13 @@ +{ + "name": "node-example", + "version": "0.0.1", + "dependencies": { + "express": "2.5.x" + }, + "engines": { + "node": "4.2.x" + }, + "scripts": { + "start": "false" + } +} diff --git a/tests/apps/dockerfile-dokku-scale/web.js b/tests/apps/dockerfile-dokku-scale/web.js new file mode 100644 index 000000000..6b8f4d819 --- /dev/null +++ b/tests/apps/dockerfile-dokku-scale/web.js @@ -0,0 +1,12 @@ +var express = require('express'); + +var app = express.createServer(express.logger()); + +app.get('/', function(request, response) { + response.send('nodejs/express'); +}); + +var port = process.env.PORT || 5000; +app.listen(port, function() { + console.log("Listening on " + port); +}); diff --git a/tests/apps/dockerfile-dokku-scale/worker.js b/tests/apps/dockerfile-dokku-scale/worker.js new file mode 100644 index 000000000..12133530d --- /dev/null +++ b/tests/apps/dockerfile-dokku-scale/worker.js @@ -0,0 +1,6 @@ +function worker() { + console.log('sleeping for 60 seconds'); + setTimeout(worker, 60 * 1000); +} + +worker(); diff --git a/tests/apps/multi/Gemfile b/tests/apps/multi/Gemfile index 6d078ad4d..e30d42237 100644 --- a/tests/apps/multi/Gemfile +++ b/tests/apps/multi/Gemfile @@ -1,6 +1,6 @@ source "https://rubygems.org" -ruby '2.2.4' +ruby '2.6.2' gem 'compass', ">= 1.0.1" gem 'sass-globbing' diff --git a/tests/shellcheck-exclude b/tests/shellcheck-exclude index 57f97bf7d..57fc5acee 100644 --- a/tests/shellcheck-exclude +++ b/tests/shellcheck-exclude @@ -6,6 +6,7 @@ # SC2029 - Note that, unescaped, this expands on the client side - https://github.com/koalaman/shellcheck/wiki/SC2029 # SC2034 - VAR appears unused - https://github.com/koalaman/shellcheck/wiki/SC2034 # SC2046 - Quote this to prevent word splitting - https://github.com/koalaman/shellcheck/wiki/SC2046 +# SC2064 - Use single quotes, otherwise this expands now rather than when signalled. # SC2068 - Double quote array expansions to avoid re-splitting elements - https://github.com/koalaman/shellcheck/wiki/SC2068 # SC2086 - Double quote to prevent globbing and word splitting - https://github.com/koalaman/shellcheck/wiki/SC2086 # SC2119 - Use 'function "$@"' if function's $1 should mean script's $1 - https://github.com/koalaman/shellcheck/wiki/SC2119 diff --git a/tests/unit/10_apps.bats b/tests/unit/10_apps.bats old mode 100755 new mode 100644 index e92e07a2c..ccb4bbf44 --- a/tests/unit/10_apps.bats +++ b/tests/unit/10_apps.bats @@ -2,11 +2,11 @@ load test_helper -setup () { +setup() { global_setup } -teardown () { +teardown() { global_teardown } diff --git a/tests/unit/10_ps-dockerfile.bats b/tests/unit/10_ps-dockerfile.bats index 3101dc6b0..2c0061f08 100644 --- a/tests/unit/10_ps-dockerfile.bats +++ b/tests/unit/10_ps-dockerfile.bats @@ -134,6 +134,35 @@ teardown() { assert_success } +@test "(ps:scale) dockerfile dokku-scale" { + run /bin/bash -c "dokku ps:scale $TEST_APP web=2" + echo "output: $output" + echo "status: $status" + assert_success + + deploy_app dockerfile-dokku-scale + CIDS="" + for CID_FILE in $DOKKU_ROOT/$TEST_APP/CONTAINER.web.*; do + CIDS+=$(< $CID_FILE) + CIDS+=" " + done + CIDS_PATTERN=$(echo $CIDS | sed -e "s: :|:g") + run /bin/bash -c "docker ps -q --no-trunc | egrep \"$CIDS_PATTERN\" | wc -l | grep 1" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku ps:scale $TEST_APP web=1" + echo "output: $output" + echo "status: $status" + assert_failure + + run /bin/bash -c "dokku ps:report $TEST_APP --ps-can-scale" + echo "output: $output" + echo "status: $status" + assert_output "false" +} + @test "(ps) dockerfile with procfile" { deploy_app dockerfile-procfile run /bin/bash -c "dokku ps:stop $TEST_APP" diff --git a/tests/unit/10_ps-general.bats b/tests/unit/10_ps-general.bats index 737120cce..fe585de35 100644 --- a/tests/unit/10_ps-general.bats +++ b/tests/unit/10_ps-general.bats @@ -13,6 +13,7 @@ teardown() { } @test "(ps) ps:inspect" { + dokku config:set "$TEST_APP" key=value key=value=value deploy_app dockerfile CID=$(< $DOKKU_ROOT/$TEST_APP/CONTAINER.web.1) diff --git a/tests/unit/10_ps-herokuish.bats b/tests/unit/10_ps-herokuish.bats index 8fc613441..3adc11150 100644 --- a/tests/unit/10_ps-herokuish.bats +++ b/tests/unit/10_ps-herokuish.bats @@ -109,11 +109,11 @@ teardown() { assert_success done - run /bin/bash -c "dokku ps:scale $TEST_APP web=0 worker=0" + run /bin/bash -c "dokku ps:scale $TEST_APP web=0 worker=0 invalid-process=0" echo "output: $output" echo "status: $status" assert_success - for PROC_TYPE in web worker; do + for PROC_TYPE in web worker invalid-process; do CIDS="" shopt -s nullglob for CID_FILE in $DOKKU_ROOT/$TEST_APP/CONTAINER.$PROC_TYPE.*; do diff --git a/tests/unit/20_config.bats b/tests/unit/20_config.bats index d62972353..754ed78a9 100644 --- a/tests/unit/20_config.bats +++ b/tests/unit/20_config.bats @@ -155,6 +155,32 @@ teardown() { run /bin/bash -c "dokku --app $TEST_APP config:show" echo "output: $output" echo "status: "$stat - assert_output "=====> $TEST_APP env vars"$'\nBKEY: true\naKey: true\nbKey: true\nzKey: true' } + +@test "(config) config:export" { + run /bin/bash -c "dokku --app $TEST_APP config:set zKey=true bKey=true BKEY=true aKey=true" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku config:export --format docker-args $TEST_APP" + echo "output: $output" + echo "status: "$stat + assert_output "--env=BKEY='true' --env=aKey='true' --env=bKey='true' --env=zKey='true'" + + run /bin/bash -c "dokku config:export --format shell $TEST_APP" + echo "output: $output" + echo "status: "$stat + assert_output "BKEY='true' aKey='true' bKey='true' zKey='true' " + + run /bin/bash -c "dokku config:export --format json $TEST_APP" + echo "output: $output" + echo "status: "$stat + assert_output '{"BKEY":"true","aKey":"true","bKey":"true","zKey":"true"}' + + run /bin/bash -c "dokku config:export --format json-list $TEST_APP" + echo "output: $output" + echo "status: "$stat + assert_output '[{"name":"BKEY","value":"true"},{"name":"aKey","value":"true"},{"name":"bKey","value":"true"},{"name":"zKey","value":"true"}]' +} diff --git a/tests/unit/20_domains.bats b/tests/unit/20_domains.bats index 8c05f8994..1fee4f290 100644 --- a/tests/unit/20_domains.bats +++ b/tests/unit/20_domains.bats @@ -45,6 +45,11 @@ teardown() { echo "status: $status" assert_success + run /bin/bash -c "dokku domains:add $TEST_APP .dokku.me" + echo "output: $output" + echo "status: $status" + assert_success + run /bin/bash -c "dokku domains $TEST_APP 2>/dev/null" echo "output: $output" echo "status: $status" @@ -173,6 +178,11 @@ teardown() { echo "status: $status" assert_success + run /bin/bash -c "dokku domains:report $TEST_APP" + echo "output: $output" + echo "status: $status" + assert_success + run /bin/bash -c "dokku domains:clear $TEST_APP" echo "output: $output" echo "status: $status" @@ -183,6 +193,11 @@ teardown() { echo "status: $status" assert_success refute_line test.app.dokku.me + + run /bin/bash -c "dokku domains:report $TEST_APP" + echo "output: $output" + echo "status: $status" + assert_success } @test "(domains) domains:add-global" { @@ -191,6 +206,11 @@ teardown() { echo "status: $status" assert_success + run /bin/bash -c "dokku domains:report --global" + echo "output: $output" + echo "status: $status" + assert_success + run /bin/bash -c "dokku domains 2>/dev/null | egrep -qw '^global.dokku.me\$'" echo "output: $output" echo "status: $status" @@ -219,6 +239,25 @@ teardown() { assert_success } +@test "(domains) domains:clear-global" { + run /bin/bash -c "dokku domains:add-global global.dokku.invalid" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku domains:add-global global.dokku.me" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku domains:clear-global" + echo "output: $output" + echo "status: $status" + assert_success + refute_line "global.dokku.invalid" + refute_line "global.dokku.me" +} + @test "(domains) domains:remove-global" { run /bin/bash -c "dokku domains:add-global global.dokku.me" echo "output: $output" @@ -269,3 +308,22 @@ teardown() { assert_line ${TEST_APP}.global3.dokku.me assert_line ${TEST_APP}.global4.dokku.me } + +@test "(domains) app name overlaps with global domain.tld" { + run /bin/bash -c "dokku domains:set-global dokku.test" + echo "output: $output" + echo "status: $status" + assert_success + + # run domains:clear in order to invoke default vhost creation + dokku --quiet apps:create test.dokku.test + dokku --quiet domains:clear test.dokku.test + + run /bin/bash -c "dokku domains:report test.dokku.test --domains-app-vhosts" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "test.dokku.test" + + dokku --force apps:destroy test.dokku.test +} diff --git a/tests/unit/20_nginx-vhosts_1.bats b/tests/unit/20_nginx-vhosts_1.bats index 8a5c8eeb3..c71d9bc55 100644 --- a/tests/unit/20_nginx-vhosts_1.bats +++ b/tests/unit/20_nginx-vhosts_1.bats @@ -136,7 +136,7 @@ teardown() { deploy_app create_attach_network - run dokku nginx:build-config "$TEST_APP" + run /bin/bash -c "dokku nginx:build-config $TEST_APP" echo "output: "$output echo "status: "$status assert_success diff --git a/tests/unit/20_resource.bats b/tests/unit/20_resource.bats new file mode 100644 index 000000000..31b2f53e5 --- /dev/null +++ b/tests/unit/20_resource.bats @@ -0,0 +1,194 @@ +#!/usr/bin/env bats + +load test_helper + +setup() { + global_setup + create_app +} + +teardown() { + destroy_app + global_teardown +} + +@test "(resource) resource:limit" { + run /bin/bash -c "dokku resource:limit $TEST_APP" + echo "output: $output" + echo "status: $status" + assert_output_contains "resource limits $TEST_APP information" + + deploy_app + run /bin/bash -c "dokku resource:limit --memory 512MB $TEST_APP" + echo "output: $output" + echo "status: $status" + assert_success + + CID=$(< $DOKKU_ROOT/$TEST_APP/CONTAINER.web.1) + run /bin/bash -c "docker inspect --format '{{.HostConfig.Memory}}' $CID" + echo "output: $output" + echo "status: $status" + assert_output "0" + + dokku ps:rebuild "$TEST_APP" + CID=$(< $DOKKU_ROOT/$TEST_APP/CONTAINER.web.1) + run /bin/bash -c "docker inspect --format '{{.HostConfig.Memory}}' $CID" + echo "output: $output" + echo "status: $status" + assert_output "536870912" + + run /bin/bash -c "dokku resource:limit --memory 1024MB --process-type worker $TEST_APP" + echo "output: $output" + echo "status: $status" + assert_success + + dokku ps:rebuild "$TEST_APP" + CID=$(< $DOKKU_ROOT/$TEST_APP/CONTAINER.web.1) + run /bin/bash -c "docker inspect --format '{{.HostConfig.Memory}}' $CID" + echo "output: $output" + echo "status: $status" + assert_output "536870912" + + run /bin/bash -c "dokku resource:limit-clear --process-type worker $TEST_APP" + echo "output: $output" + echo "status: $status" + assert_success + + dokku ps:rebuild "$TEST_APP" + CID=$(< $DOKKU_ROOT/$TEST_APP/CONTAINER.web.1) + run /bin/bash -c "docker inspect --format '{{.HostConfig.Memory}}' $CID" + echo "output: $output" + echo "status: $status" + assert_output "536870912" + + run /bin/bash -c "dokku resource:limit-clear --process-type web $TEST_APP" + echo "output: $output" + echo "status: $status" + assert_success + + dokku ps:rebuild "$TEST_APP" + CID=$(< $DOKKU_ROOT/$TEST_APP/CONTAINER.web.1) + run /bin/bash -c "docker inspect --format '{{.HostConfig.Memory}}' $CID" + echo "output: $output" + echo "status: $status" + assert_output "536870912" + + run /bin/bash -c "dokku resource:limit-clear $TEST_APP" + echo "output: $output" + echo "status: $status" + assert_success + + dokku ps:rebuild "$TEST_APP" + CID=$(< $DOKKU_ROOT/$TEST_APP/CONTAINER.web.1) + run /bin/bash -c "docker inspect --format '{{.HostConfig.Memory}}' $CID" + echo "output: $output" + echo "status: $status" + assert_output "0" +} + +@test "(resource) resource:reserve" { + run /bin/bash -c "dokku resource:reserve $TEST_APP" + echo "output: $output" + echo "status: $status" + assert_output_contains "resource reservation $TEST_APP information" + + deploy_app + run /bin/bash -c "dokku resource:reserve --memory 512MB $TEST_APP" + echo "output: $output" + echo "status: $status" + assert_success + + CID=$(< $DOKKU_ROOT/$TEST_APP/CONTAINER.web.1) + run /bin/bash -c "docker inspect --format '{{.HostConfig.MemoryReservation}}' $CID" + echo "output: $output" + echo "status: $status" + assert_output "0" + + dokku ps:rebuild "$TEST_APP" + CID=$(< $DOKKU_ROOT/$TEST_APP/CONTAINER.web.1) + run /bin/bash -c "docker inspect --format '{{.HostConfig.MemoryReservation}}' $CID" + echo "output: $output" + echo "status: $status" + assert_output "536870912" + + run /bin/bash -c "dokku resource:reserve --memory 1024MB --process-type worker $TEST_APP" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku resource:reserve --cpu 0.5 --process-type worker $TEST_APP" + echo "output: $output" + echo "status: $status" + assert_success + + dokku ps:rebuild "$TEST_APP" + CID=$(< $DOKKU_ROOT/$TEST_APP/CONTAINER.web.1) + run /bin/bash -c "docker inspect --format '{{.HostConfig.MemoryReservation}}' $CID" + echo "output: $output" + echo "status: $status" + assert_output "536870912" + # run /bin/bash -c "docker inspect --format '{{.HostConfig.NanoCpus}}' $CID" + # echo "output: $output" + # echo "status: $status" + # assert_output "500000000" + + run /bin/bash -c "dokku resource:reserve-clear --process-type worker $TEST_APP" + echo "output: $output" + echo "status: $status" + assert_success + + dokku ps:rebuild "$TEST_APP" + CID=$(< $DOKKU_ROOT/$TEST_APP/CONTAINER.web.1) + run /bin/bash -c "docker inspect --format '{{.HostConfig.MemoryReservation}}' $CID" + echo "output: $output" + echo "status: $status" + assert_output "536870912" + + run /bin/bash -c "dokku resource:reserve-clear --process-type web $TEST_APP" + echo "output: $output" + echo "status: $status" + assert_success + + dokku ps:rebuild "$TEST_APP" + CID=$(< $DOKKU_ROOT/$TEST_APP/CONTAINER.web.1) + run /bin/bash -c "docker inspect --format '{{.HostConfig.MemoryReservation}}' $CID" + echo "output: $output" + echo "status: $status" + assert_output "536870912" + + run /bin/bash -c "dokku resource:reserve-clear $TEST_APP" + echo "output: $output" + echo "status: $status" + assert_success + + dokku ps:rebuild "$TEST_APP" + CID=$(< $DOKKU_ROOT/$TEST_APP/CONTAINER.web.1) + run /bin/bash -c "docker inspect --format '{{.HostConfig.MemoryReservation}}' $CID" + echo "output: $output" + echo "status: $status" + assert_output "0" +} + +@test "(resource) resource:limit-clear" { + run /bin/bash -c "dokku resource:limit-clear $TEST_APP" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku resource:limit-clear --process-type web $TEST_APP" + echo "output: $output" + echo "status: $status" + assert_success +} + +@test "(resource) resource:reserve-clear" { + run /bin/bash -c "dokku resource:reserve-clear $TEST_APP" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku resource:reserve-clear --process-type web $TEST_APP" + echo "output: $output" + echo "status: $status" + assert_success +} diff --git a/tests/unit/30_buildpacks.bats b/tests/unit/30_buildpacks.bats index 687da8f41..ae955ac69 100644 --- a/tests/unit/30_buildpacks.bats +++ b/tests/unit/30_buildpacks.bats @@ -209,3 +209,25 @@ teardown() { echo "status: $status" assert_output_not_exists } + +@test "(buildpacks) buildpacks deploy" { + destroy_app + echo "output: $output" + echo "status: $status" + assert_success + + create_app + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku buildpacks:set $TEST_APP https://github.com/heroku/heroku-buildpack-nodejs" + echo "output: $output" + echo "status: $status" + assert_success + + run deploy_app + echo "output: $output" + echo "status: $status" + assert_success +} diff --git a/tests/unit/30_ssh_keys.bats b/tests/unit/30_ssh_keys.bats index 3b23aefea..b14d9a06a 100644 --- a/tests/unit/30_ssh_keys.bats +++ b/tests/unit/30_ssh_keys.bats @@ -13,6 +13,9 @@ setup() { # now create key that will be really used create_key + # Test key without a trailing newline + echo -n "$KEY" > /tmp/testkey-no-newline.pub + # 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 @@ -50,6 +53,15 @@ teardown() { echo "output: $output" echo "status: $status" assert_success + run /bin/bash -c 'echo "" >> "${DOKKU_ROOT:-/home/dokku}/.ssh/authorized_keys"' + run /bin/bash -c 'echo "" >> "${DOKKU_ROOT:-/home/dokku}/.ssh/authorized_keys"' + echo "output: $output" + echo "status: $status" + assert_success + run /bin/bash -c "dokku ssh-keys:list" + echo "output: $output" + echo "status: $status" + assert_success run /bin/bash -c "dokku ssh-keys:list | grep name1" echo "output: $output" echo "status: $status" @@ -70,6 +82,10 @@ teardown() { echo "output: $output" echo "status: $status" assert_success + run /bin/bash -c "dokku ssh-keys:add name5 /tmp/testkey-no-newline.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"' diff --git a/tests/unit/40_core_2.bats b/tests/unit/40_core_2.bats index 22ec06ca2..ffd74ff6b 100644 --- a/tests/unit/40_core_2.bats +++ b/tests/unit/40_core_2.bats @@ -39,6 +39,18 @@ assert_url() { assert_success } +@test "(core) run (with --env / -e)" { + deploy_app + run /bin/bash -c "dokku run --env TEST=testvalue -e TEST2=testvalue2 $TEST_APP env | egrep '^TEST=testvalue'" + echo "output: $output" + echo "status: $status" + + run /bin/bash -c "dokku run --env TEST=testvalue -e TEST2=testvalue2 $TEST_APP env | egrep '^TEST2=testvalue2'" + echo "output: $output" + echo "status: $status" + assert_success +} + @test "(core) unknown command" { run /bin/bash -c "dokku fakecommand" echo "output: $output" diff --git a/tests/unit/40_plugin.bats b/tests/unit/40_plugin.bats index 7f2e0e0e5..a2f94b51a 100644 --- a/tests/unit/40_plugin.bats +++ b/tests/unit/40_plugin.bats @@ -3,13 +3,20 @@ load test_helper TEST_PLUGIN_NAME=smoke-test-plugin TEST_PLUGIN_GIT_REPO=https://github.com/dokku/${TEST_PLUGIN_NAME}.git +TEST_PLUGIN_LOCAL_REPO="$(mktemp -d)/$TEST_PLUGIN_NAME" + +clone_test_plugin() { + git clone "$TEST_PLUGIN_GIT_REPO" "$TEST_PLUGIN_LOCAL_REPO" +} setup() { global_setup + clone_test_plugin } remove_test_plugin() { rm -rf $PLUGIN_ENABLED_PATH/$TEST_PLUGIN_NAME $PLUGIN_AVAILABLE_PATH/$TEST_PLUGIN_NAME + rm -rf $TEST_PLUGIN_LOCAL_REPO } teardown() { @@ -59,6 +66,48 @@ teardown() { assert_failure } +@test "(plugin) plugin:install, plugin:disable, plugin:update plugin:uninstall (with file://)" { + run /bin/bash -c "dokku plugin:install file://$TEST_PLUGIN_LOCAL_REPO --name $TEST_PLUGIN_NAME" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku plugin:update $TEST_PLUGIN_NAME" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku plugin | grep enabled | grep $TEST_PLUGIN_NAME" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "sudo -E -u nobody dokku plugin:uninstall $TEST_PLUGIN_NAME" + echo "output: $output" + echo "status: $status" + assert_failure + + run /bin/bash -c "dokku plugin:disable $TEST_PLUGIN_NAME" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku plugin | grep disabled | grep $TEST_PLUGIN_NAME" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku plugin:uninstall $TEST_PLUGIN_NAME" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku plugin | grep $TEST_PLUGIN_NAME" + echo "output: $output" + echo "status: $status" + assert_failure +} + @test "(plugin) plugin:install plugin:update (with tag)" { run /bin/bash -c "dokku plugin:install $TEST_PLUGIN_GIT_REPO --committish v0.2.0 --name $TEST_PLUGIN_NAME" echo "output: $output" diff --git a/tests/unit/40_report.bats b/tests/unit/40_report.bats new file mode 100644 index 000000000..d4ddedfcb --- /dev/null +++ b/tests/unit/40_report.bats @@ -0,0 +1,80 @@ +#!/usr/bin/env bats + +load test_helper + +setup() { + global_setup + deploy_app + rm -f /tmp/fake-docker-bin +} + +teardown() { + rm -f /tmp/fake-docker-bin + destroy_app + global_teardown +} + +@test "(report) report" { + run /bin/bash -c "dokku report" + echo "output: $output" + echo "status: $status" + assert_output_contains "herokuish version:" + assert_success + + run /bin/bash -c "dokku report $TEST_APP 2>&1" + echo "output: $output" + echo "status: $status" + assert_output_contains "herokuish version:" + assert_output_contains "not deployed" "0" + assert_success + + run /bin/bash -c "dokku report fake-app-name" + echo "output: $output" + echo "status: $status" + assert_failure + + run /bin/bash -c "dokku report fake-app-name 2>&1" + echo "output: $output" + echo "status: $status" + assert_output_contains "App fake-app-name does not exist" + assert_failure + + dokku apps:create "${TEST_APP}-2" + run /bin/bash -c "dokku report ${TEST_APP}-2 2>&1" + echo "output: $output" + echo "status: $status" + assert_output_contains "not deployed" + assert_success + + dokku --force apps:destroy "${TEST_APP}-2" +} + +@test "(report) custom docker bin" { + export DOCKER_BIN="docker" + run /bin/bash -c "dokku report" + echo "output: $output" + echo "status: $status" + assert_output_contains "herokuish version:" + assert_success + + export DOCKER_BIN="/usr/bin/docker" + run /bin/bash -c "dokku report" + echo "output: $output" + echo "status: $status" + assert_output_contains "herokuish version:" + assert_success + + touch /tmp/fake-docker-bin + echo '#!/usr/bin/env bash' >/tmp/fake-docker-bin + echo '/usr/bin/docker "$@"' >>/tmp/fake-docker-bin + chmod +x /tmp/fake-docker-bin + + export DOCKER_BIN="/tmp/fake-docker-bin" + run /bin/bash -c "dokku report" + echo "output: $output" + echo "status: $status" + assert_output_contains "herokuish version:" + assert_success + + unset DOCKER_BIN +} diff --git a/tests/unit/40_trace.bats b/tests/unit/40_trace.bats new file mode 100644 index 000000000..5a16b139a --- /dev/null +++ b/tests/unit/40_trace.bats @@ -0,0 +1,62 @@ +#!/usr/bin/env bats + +load test_helper + +setup() { + global_setup + rm -f "$DOKKU_ROOT/.dokkurc/DOKKU_TRACE" +} + +teardown() { + rm -f "$DOKKU_ROOT/.dokkurc/DOKKU_TRACE" + global_teardown +} + +@test "(trace) trace:on" { + run /bin/bash -c "dokku trace:on" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "test -f $DOKKU_ROOT/.dokkurc/DOKKU_TRACE" + echo "output: $output" + echo "status: $status" + assert_success +} + +@test "(trace) trace on" { + run /bin/bash -c "dokku trace on" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "test -f $DOKKU_ROOT/.dokkurc/DOKKU_TRACE" + echo "output: $output" + echo "status: $status" + assert_success +} + +@test "(trace) trace:off, trace off" { + touch "$DOKKU_ROOT/.dokkurc/DOKKU_TRACE" + run /bin/bash -c "dokku trace:off" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "test -f $DOKKU_ROOT/.dokkurc/DOKKU_TRACE" + echo "output: $output" + echo "status: $status" + assert_failure + + touch "$DOKKU_ROOT/.dokkurc/DOKKU_TRACE" + run /bin/bash -c "dokku trace off" + echo "output: $output" + echo "status: $status" + assert_success + + + run /bin/bash -c "test -f $DOKKU_ROOT/.dokkurc/DOKKU_TRACE" + echo "output: $output" + echo "status: $status" + assert_failure +}