Merge pull request #3469 from dokku/resource-management
Resource management
This commit is contained in:
1
Makefile
1
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
|
||||
|
||||
55
deb.mk
55
deb.mk
@@ -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
|
||||
|
||||
381
docs/advanced-usage/resource-management.md
Normal file
381
docs/advanced-usage/resource-management.md
Normal file
@@ -0,0 +1,381 @@
|
||||
# Resource Management
|
||||
|
||||
> New as of 0.15.0
|
||||
|
||||
```
|
||||
resource:limit [--process-type <process-type>] [RESOURCE_OPTS...] <app> # Limit resources for a given app/process-type combination
|
||||
resource:limit-clear [--process-type <process-type>] <app> # Limit resources for a given app/process-type combination
|
||||
resource:report [<app>] [<flag>] # Displays a resource report for one or more apps
|
||||
resource:reserve [--process-type <process-type>] [RESOURCE_OPTS...] <app> # Reserve resources for a given app/process-type combination
|
||||
resource:reserve-clear [--process-type <process-type>] <app> # 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:limits` command without any flags will display the currently configured app reservation.
|
||||
|
||||
```shell
|
||||
dokku resource:limits node-js-app
|
||||
=====> resource limits node-js-app information
|
||||
cpu: 100
|
||||
memory: 100
|
||||
memory-swap:
|
||||
network:
|
||||
network-ingress:
|
||||
network-egress:
|
||||
```
|
||||
|
||||
#### Global Resource Limits
|
||||
|
||||
By default, Dokku does not limit any resources, though these can be specified at the server level via the `--global` flag on the `resource:limit` command.
|
||||
|
||||
```shell
|
||||
dokku resource:limit --global --memory 200
|
||||
```
|
||||
|
||||
```
|
||||
=====> Setting default resource limits
|
||||
memory: 200
|
||||
```
|
||||
|
||||
This may also be combined with the `--process-type` flag to set global limits on a process-type level:
|
||||
|
||||
```shell
|
||||
dokku resource:limit --global --memory 150 --process-type web node-js-app
|
||||
```
|
||||
|
||||
```
|
||||
=====> Setting default resource limits (web)
|
||||
memory: 150
|
||||
```
|
||||
|
||||
#### Displaying Global Resource Limits
|
||||
|
||||
Running the `resource:limit` command with only the `--global` flag will display the currently configured default limits.
|
||||
|
||||
```shell
|
||||
dokku resource:limit --global
|
||||
```
|
||||
|
||||
```
|
||||
=====> resource limits default information
|
||||
cpu:
|
||||
memory: 200
|
||||
memory-swap:
|
||||
network:
|
||||
network-ingress:
|
||||
network-egress:
|
||||
```
|
||||
|
||||
This may also be combined with the `--process-type` flag to see global limits on a process-type level:
|
||||
|
||||
```shell
|
||||
dokku resource:limit --global --process-type web
|
||||
```
|
||||
|
||||
```
|
||||
=====> resource limits default information (web)
|
||||
cpu:
|
||||
memory-swap:
|
||||
memory: 150
|
||||
network-egress:
|
||||
network-ingress:
|
||||
network:
|
||||
```
|
||||
|
||||
#### 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 app reservation.
|
||||
|
||||
```shell
|
||||
dokku resource:reserve node-js-app
|
||||
=====> resource reservation node-js-app information
|
||||
cpu: 100
|
||||
memory: 100
|
||||
memory-swap:
|
||||
network:
|
||||
network-ingress:
|
||||
network-egress:
|
||||
```
|
||||
|
||||
#### Global Resource Reservations
|
||||
|
||||
By default, Dokku does not reserve any resources, though these can be specified at the server level via the `--global` flag on the `resource:reserve` command.
|
||||
|
||||
```shell
|
||||
dokku resource:reserve --global --memory 200
|
||||
```
|
||||
|
||||
```
|
||||
=====> Setting default resource reservation
|
||||
memory: 200
|
||||
```
|
||||
|
||||
This may also be combined with the `--process-type` flag to set global reservations on a process-type level:
|
||||
|
||||
```shell
|
||||
dokku resource:reserve --global --memory 150 --process-type web node-js-app
|
||||
```
|
||||
|
||||
```
|
||||
=====> Setting default resource reservation (web)
|
||||
memory: 150
|
||||
```
|
||||
|
||||
#### Displaying Global Resource Reservations
|
||||
|
||||
Running the `resource:reserve` command with only the `--global` flag will display the currently configured default reservation.
|
||||
|
||||
```shell
|
||||
dokku resource:reserve --global
|
||||
```
|
||||
|
||||
```
|
||||
=====> resource reservation default information
|
||||
cpu:
|
||||
memory: 200
|
||||
memory-swap:
|
||||
network:
|
||||
network-ingress:
|
||||
network-egress:
|
||||
```
|
||||
|
||||
This may also be combined with the `--process-type` flag to see global reservations on a process-type level:
|
||||
|
||||
```shell
|
||||
dokku resource:reserve --global --process-type web
|
||||
```
|
||||
|
||||
```
|
||||
=====> resource reservation default information (web)
|
||||
cpu:
|
||||
memory-swap:
|
||||
memory: 150
|
||||
network-egress:
|
||||
network-ingress:
|
||||
network:
|
||||
```
|
||||
|
||||
#### 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
|
||||
```
|
||||
@@ -53,3 +53,17 @@ This plugin implements various functionality through `plugn` triggers to integra
|
||||
- `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)
|
||||
|
||||
@@ -248,6 +248,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 +273,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]`
|
||||
@@ -289,6 +293,8 @@ verify_app_name "$APP"
|
||||
|
||||
### `docker-args-run`
|
||||
|
||||
> Warning: Deprecated, please use `docker-args-process-run` instead
|
||||
|
||||
- Description:
|
||||
- Invoked by: `dokku run`
|
||||
- Arguments: `$APP $IMAGE_TAG`
|
||||
@@ -305,6 +311,42 @@ 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 [$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
|
||||
```
|
||||
|
||||
### `git-post-pull`
|
||||
|
||||
- Description:
|
||||
@@ -1157,6 +1199,22 @@ fi
|
||||
plugn trigger receive-app $APP $newrev
|
||||
```
|
||||
|
||||
### `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
|
||||
|
||||
35
package.mk
Normal file
35
package.mk
Normal file
@@ -0,0 +1,35 @@
|
||||
/tmp/build-dokku/var/lib/dokku/GIT_REV:
|
||||
mkdir -p /tmp/build-dokku
|
||||
mkdir -p /tmp/build-dokku/usr/share/bash-completion/completions
|
||||
mkdir -p /tmp/build-dokku/usr/bin
|
||||
mkdir -p /tmp/build-dokku/usr/share/doc/dokku
|
||||
mkdir -p /tmp/build-dokku/usr/share/dokku/contrib
|
||||
mkdir -p /tmp/build-dokku/usr/share/lintian/overrides
|
||||
mkdir -p /tmp/build-dokku/usr/share/man/man1
|
||||
mkdir -p /tmp/build-dokku/var/lib/dokku/core-plugins/available
|
||||
|
||||
cp dokku /tmp/build-dokku/usr/bin
|
||||
cp LICENSE /tmp/build-dokku/usr/share/doc/dokku/copyright
|
||||
cp contrib/bash-completion /tmp/build-dokku/usr/share/bash-completion/completions/dokku
|
||||
find . -name ".DS_Store" -depth -exec rm {} \;
|
||||
$(MAKE) go-build
|
||||
cp common.mk /tmp/build-dokku/var/lib/dokku/core-plugins/common.mk
|
||||
cp -r plugins/* /tmp/build-dokku/var/lib/dokku/core-plugins/available
|
||||
find plugins/ -mindepth 1 -maxdepth 1 -type d -printf '%f\n' | while read plugin; do cd /tmp/build-dokku/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-dokku/var/lib/dokku/core-plugins/available/$$plugin/.core; done
|
||||
rm /tmp/build-dokku/var/lib/dokku/core-plugins/common.mk
|
||||
$(MAKE) help2man
|
||||
$(MAKE) addman
|
||||
cp /usr/local/share/man/man1/dokku.1 /tmp/build-dokku/usr/share/man/man1/dokku.1
|
||||
gzip -9 /tmp/build-dokku/usr/share/man/man1/dokku.1
|
||||
cp contrib/dokku-installer.py /tmp/build-dokku/usr/share/dokku/contrib
|
||||
ifeq ($(DOKKU_VERSION),master)
|
||||
git describe --tags > /tmp/build-dokku/var/lib/dokku/VERSION
|
||||
else
|
||||
echo $(DOKKU_VERSION) > /tmp/build-dokku/var/lib/dokku/VERSION
|
||||
endif
|
||||
ifdef DOKKU_GIT_REV
|
||||
echo "$(DOKKU_GIT_REV)" > /tmp/build-dokku/var/lib/dokku/GIT_REV
|
||||
else
|
||||
git rev-parse HEAD > /tmp/build-dokku/var/lib/dokku/GIT_REV
|
||||
endif
|
||||
@@ -83,6 +83,10 @@ execute_script() {
|
||||
# strip --restart args from DOCKER_ARGS
|
||||
local DOCKER_ARGS=$(sed -e "s/--restart=[[:graph:]]\+[[:blank:]]\?//g" <<<"$DOCKER_ARGS")
|
||||
|
||||
local IMAGE_SOURCE_TYPE="dockerfile"
|
||||
is_image_herokuish_based "$IMAGE" && IMAGE_SOURCE_TYPE="herokuish"
|
||||
DOCKER_ARGS+=$(: | plugn trigger docker-args-process-deploy "$APP" "$IMAGE_TAG" "$IMAGE_SOURCE_TYPE" "$PHASE_SCRIPT_KEY")
|
||||
|
||||
# eval args as array to respect escapes
|
||||
declare -a ARG_ARRAY
|
||||
eval "ARG_ARRAY=($DOCKER_ARGS)"
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
[plugin]
|
||||
description = "dokku core buildpacks plugin"
|
||||
version = "0.14.2"
|
||||
version = "0.14.6"
|
||||
[plugin.config]
|
||||
|
||||
@@ -328,7 +328,7 @@ func VerifyAppName(appName string) (err error) {
|
||||
dokkuRoot := MustGetEnv("DOKKU_ROOT")
|
||||
appRoot := strings.Join([]string{dokkuRoot, appName}, "/")
|
||||
if !DirectoryExists(appRoot) {
|
||||
return fmt.Errorf("app %s does not exist: %v", appName, err)
|
||||
return fmt.Errorf("app %s does not exist", appName)
|
||||
}
|
||||
r, _ := regexp.Compile("^[a-z0-9].*")
|
||||
if !r.MatchString(appName) {
|
||||
|
||||
@@ -533,6 +533,9 @@ dokku_build() {
|
||||
|
||||
local DOCKER_ARGS=$(: | plugn trigger docker-args-build "$APP" "$IMAGE_SOURCE_TYPE")
|
||||
[[ "$DOKKU_TRACE" ]] && DOCKER_ARGS+=" -e TRACE=true "
|
||||
local IMAGE_SOURCE_TYPE="herokuish"
|
||||
DOCKER_ARGS+=$(: | plugn trigger docker-args-process-run "$APP" "$IMAGE_TAG" "$IMAGE_SOURCE_TYPE" "$PHASE_SCRIPT_KEY")
|
||||
|
||||
declare -a ARG_ARRAY
|
||||
eval "ARG_ARRAY=($DOCKER_ARGS)"
|
||||
# shellcheck disable=SC2086
|
||||
@@ -558,6 +561,9 @@ dokku_build() {
|
||||
|
||||
[[ "$DOKKU_DOCKERFILE_CACHE_BUILD" == "false" ]] && DOKKU_DOCKER_BUILD_OPTS="$DOKKU_DOCKER_BUILD_OPTS --no-cache"
|
||||
local DOCKER_ARGS=$(: | plugn trigger docker-args-build "$APP" "$IMAGE_SOURCE_TYPE")
|
||||
local IMAGE_SOURCE_TYPE="dockerfile"
|
||||
DOCKER_ARGS+=$(: | plugn trigger docker-args-process-run "$APP" "$IMAGE_TAG" "$IMAGE_SOURCE_TYPE" "$PHASE_SCRIPT_KEY")
|
||||
|
||||
# strip --volume and -v args from DOCKER_ARGS
|
||||
local DOCKER_ARGS=$(sed -e "s/--volume=[[:graph:]]\+[[:blank:]]\?//g" -e "s/-v[[:blank:]]\?[[:graph:]]\+[[:blank:]]\?//g" <<<"$DOCKER_ARGS")
|
||||
declare -a ARG_ARRAY
|
||||
|
||||
@@ -77,6 +77,26 @@ func PropertyGet(pluginName string, appName string, property string) string {
|
||||
return PropertyGetDefault(pluginName, appName, property, "")
|
||||
}
|
||||
|
||||
// PropertyGetAll returns a map of all properties for a given app
|
||||
func PropertyGetAll(pluginName string, appName string) (map[string]string, error) {
|
||||
properties := make(map[string]string)
|
||||
pluginAppConfigRoot := getPluginAppPropertyPath(pluginName, appName)
|
||||
files, err := ioutil.ReadDir(pluginAppConfigRoot)
|
||||
if err != nil {
|
||||
return properties, err
|
||||
}
|
||||
|
||||
for _, file := range files {
|
||||
if file.IsDir() {
|
||||
continue
|
||||
}
|
||||
property := file.Name()
|
||||
properties[property] = PropertyGet(pluginName, appName, property)
|
||||
}
|
||||
|
||||
return properties, nil
|
||||
}
|
||||
|
||||
// PropertyGetDefault returns the value for a given property with a specified default value
|
||||
func PropertyGetDefault(pluginName, appName, property, defaultValue string) (val string) {
|
||||
if !PropertyExists(pluginName, appName, property) {
|
||||
|
||||
@@ -52,7 +52,7 @@ func BuildConfig(appName string) {
|
||||
if len(procParts) != 2 {
|
||||
continue
|
||||
}
|
||||
procType := procParts[0]
|
||||
processType := procParts[0]
|
||||
procCount, err := strconv.Atoi(procParts[1])
|
||||
if err != nil {
|
||||
continue
|
||||
@@ -62,25 +62,25 @@ func BuildConfig(appName string) {
|
||||
for containerIndex < procCount {
|
||||
containerIndex++
|
||||
containerIndexString := strconv.Itoa(containerIndex)
|
||||
containerIDFile := fmt.Sprintf("%v/CONTAINER.%v.%v", appRoot, procType, containerIndex)
|
||||
containerIDFile := fmt.Sprintf("%v/CONTAINER.%v.%v", appRoot, processType, containerIndex)
|
||||
|
||||
containerID := common.ReadFirstLine(containerIDFile)
|
||||
if containerID == "" || !common.ContainerIsRunning(containerID) {
|
||||
continue
|
||||
}
|
||||
|
||||
ipAddress := GetContainerIpaddress(appName, procType, containerID)
|
||||
port := GetContainerPort(appName, procType, isHerokuishContainer, containerID)
|
||||
ipAddress := GetContainerIpaddress(appName, processType, containerID)
|
||||
port := GetContainerPort(appName, processType, isHerokuishContainer, containerID)
|
||||
|
||||
if ipAddress != "" {
|
||||
_, err := sh.Command("plugn", "trigger", "network-write-ipaddr", appName, procType, containerIndexString, ipAddress).Output()
|
||||
_, err := sh.Command("plugn", "trigger", "network-write-ipaddr", appName, processType, containerIndexString, ipAddress).Output()
|
||||
if err != nil {
|
||||
common.LogWarn(err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
if port != "" {
|
||||
_, err := sh.Command("plugn", "trigger", "network-write-port", appName, procType, containerIndexString, port).Output()
|
||||
_, err := sh.Command("plugn", "trigger", "network-write-port", appName, processType, containerIndexString, port).Output()
|
||||
if err != nil {
|
||||
common.LogWarn(err.Error())
|
||||
}
|
||||
@@ -90,8 +90,8 @@ func BuildConfig(appName string) {
|
||||
}
|
||||
|
||||
// GetContainerIpaddress returns the ipaddr for a given app container
|
||||
func GetContainerIpaddress(appName, procType, containerID string) (ipAddr string) {
|
||||
if procType != "web" {
|
||||
func GetContainerIpaddress(appName, processType, containerID string) (ipAddr string) {
|
||||
if processType != "web" {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -109,8 +109,8 @@ func GetContainerIpaddress(appName, procType, containerID string) (ipAddr string
|
||||
}
|
||||
|
||||
// GetContainerPort returns the port for a given app container
|
||||
func GetContainerPort(appName, procType string, isHerokuishContainer bool, containerID string) (port string) {
|
||||
if procType != "web" {
|
||||
func GetContainerPort(appName, processType string, isHerokuishContainer bool, containerID string) (port string) {
|
||||
if processType != "web" {
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -15,10 +15,10 @@ import (
|
||||
func main() {
|
||||
flag.Parse()
|
||||
appName := flag.Arg(0)
|
||||
procType := flag.Arg(1)
|
||||
processType := flag.Arg(1)
|
||||
isHerokuishContainer := common.ToBool(flag.Arg(2))
|
||||
|
||||
if procType != "web" {
|
||||
if processType != "web" {
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -12,9 +12,9 @@ import (
|
||||
func main() {
|
||||
flag.Parse()
|
||||
appName := flag.Arg(0)
|
||||
procType := flag.Arg(1)
|
||||
processType := flag.Arg(1)
|
||||
containerID := flag.Arg(2)
|
||||
|
||||
ipAddress := network.GetContainerIpaddress(appName, procType, containerID)
|
||||
ipAddress := network.GetContainerIpaddress(appName, processType, containerID)
|
||||
fmt.Fprintln(os.Stdout, ipAddress)
|
||||
}
|
||||
|
||||
@@ -13,10 +13,10 @@ import (
|
||||
func main() {
|
||||
flag.Parse()
|
||||
appName := flag.Arg(0)
|
||||
procType := flag.Arg(1)
|
||||
processType := flag.Arg(1)
|
||||
isHerokuishContainer := common.ToBool(flag.Arg(2))
|
||||
containerID := flag.Arg(3)
|
||||
|
||||
port := network.GetContainerPort(appName, procType, isHerokuishContainer, containerID)
|
||||
port := network.GetContainerPort(appName, processType, isHerokuishContainer, containerID)
|
||||
fmt.Fprintln(os.Stdout, port)
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ import (
|
||||
func main() {
|
||||
flag.Parse()
|
||||
appName := flag.Arg(0)
|
||||
procType := flag.Arg(1)
|
||||
processType := flag.Arg(1)
|
||||
containerIndex := flag.Arg(2)
|
||||
ip := flag.Arg(3)
|
||||
|
||||
@@ -26,7 +26,7 @@ func main() {
|
||||
}
|
||||
|
||||
appRoot := strings.Join([]string{common.MustGetEnv("DOKKU_ROOT"), appName}, "/")
|
||||
filename := fmt.Sprintf("%v/IP.%v.%v", appRoot, procType, containerIndex)
|
||||
filename := fmt.Sprintf("%v/IP.%v.%v", appRoot, processType, containerIndex)
|
||||
f, err := os.Create(filename)
|
||||
if err != nil {
|
||||
common.LogFail(err.Error())
|
||||
|
||||
@@ -13,7 +13,7 @@ import (
|
||||
func main() {
|
||||
flag.Parse()
|
||||
appName := flag.Arg(0)
|
||||
procType := flag.Arg(1)
|
||||
processType := flag.Arg(1)
|
||||
containerIndex := flag.Arg(2)
|
||||
port := flag.Arg(3)
|
||||
|
||||
@@ -26,7 +26,7 @@ func main() {
|
||||
}
|
||||
|
||||
appRoot := strings.Join([]string{common.MustGetEnv("DOKKU_ROOT"), appName}, "/")
|
||||
filename := fmt.Sprintf("%v/PORT.%v.%v", appRoot, procType, containerIndex)
|
||||
filename := fmt.Sprintf("%v/PORT.%v.%v", appRoot, processType, containerIndex)
|
||||
f, err := os.Create(filename)
|
||||
if err != nil {
|
||||
common.LogFail(err.Error())
|
||||
|
||||
9
plugins/resource/.gitignore
vendored
Normal file
9
plugins/resource/.gitignore
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
/commands
|
||||
/subcommands/*
|
||||
/triggers/*
|
||||
/install
|
||||
/post-delete
|
||||
/report
|
||||
/docker-args-process-deploy
|
||||
/docker-args-process-run
|
||||
/resource-get-property
|
||||
38
plugins/resource/Makefile
Normal file
38
plugins/resource/Makefile
Normal file
@@ -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
|
||||
|
||||
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
|
||||
3
plugins/resource/glide.yaml
Normal file
3
plugins/resource/glide.yaml
Normal file
@@ -0,0 +1,3 @@
|
||||
package: .
|
||||
import:
|
||||
- package: github.com/ryanuber/columnize
|
||||
4
plugins/resource/plugin.toml
Normal file
4
plugins/resource/plugin.toml
Normal file
@@ -0,0 +1,4 @@
|
||||
[plugin]
|
||||
description = "dokku core resource plugin"
|
||||
version = "0.14.6"
|
||||
[plugin.config]
|
||||
117
plugins/resource/resource.go
Normal file
117
plugins/resource/resource.go
Normal file
@@ -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 {
|
||||
common.LogFail(err.Error())
|
||||
}
|
||||
|
||||
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 key, value := range resources {
|
||||
if key == propertyKey("_default_", resourceType, key) {
|
||||
defaultValue = value
|
||||
}
|
||||
if key == 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
|
||||
}
|
||||
66
plugins/resource/src/commands/commands.go
Normal file
66
plugins/resource/src/commands/commands.go
Normal file
@@ -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 <process-type>] [RESOURCE_OPTS...] <app>, Limit resources for a given app/process-type combination
|
||||
resource:limit-clear [--process-type <process-type>] <app>, Limit resources for a given app/process-type combination
|
||||
resource:report [<app>] [<flag>], Displays a resource report for one or more apps
|
||||
resource:reserve [--process-type <process-type>] [RESOURCE_OPTS...] <app>, Reserve resources for a given app/process-type combination
|
||||
resource:reserve-clear [--process-type <process-type>] <app>, 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))
|
||||
}
|
||||
6
plugins/resource/src/glide.lock
generated
Normal file
6
plugins/resource/src/glide.lock
generated
Normal file
@@ -0,0 +1,6 @@
|
||||
hash: 1ddab5de41d1514c2722bd7e24758ad4b60bf6956eb5b9b925fa071a1427f149
|
||||
updated: 2017-01-03T17:16:50.97156327-08:00
|
||||
imports:
|
||||
- name: github.com/ryanuber/columnize
|
||||
version: 0fbbb3f0e3fbdc5bae7c6cd5f6c1887ebfb76360
|
||||
testImports: []
|
||||
3
plugins/resource/src/glide.yaml
Normal file
3
plugins/resource/src/glide.yaml
Normal file
@@ -0,0 +1,3 @@
|
||||
package: .
|
||||
import:
|
||||
- package: github.com/ryanuber/columnize
|
||||
20
plugins/resource/src/subcommands/limit-clear/limit-clear.go
Normal file
20
plugins/resource/src/subcommands/limit-clear/limit-clear.go
Normal file
@@ -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())
|
||||
}
|
||||
}
|
||||
35
plugins/resource/src/subcommands/limit/limit.go
Normal file
35
plugins/resource/src/subcommands/limit/limit.go
Normal file
@@ -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())
|
||||
}
|
||||
}
|
||||
33
plugins/resource/src/subcommands/report/report.go
Normal file
33
plugins/resource/src/subcommands/report/report.go
Normal file
@@ -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)
|
||||
}
|
||||
@@ -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())
|
||||
}
|
||||
}
|
||||
35
plugins/resource/src/subcommands/reserve/reserve.go
Normal file
35
plugins/resource/src/subcommands/reserve/reserve.go
Normal file
@@ -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())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
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)
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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))
|
||||
}
|
||||
14
plugins/resource/src/triggers/install/install.go
Normal file
14
plugins/resource/src/triggers/install/install.go
Normal file
@@ -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()))
|
||||
}
|
||||
}
|
||||
18
plugins/resource/src/triggers/post-delete/post-delete.go
Normal file
18
plugins/resource/src/triggers/post-delete/post-delete.go
Normal file
@@ -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())
|
||||
}
|
||||
}
|
||||
15
plugins/resource/src/triggers/report/report.go
Normal file
15
plugins/resource/src/triggers/report/report.go
Normal file
@@ -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, "")
|
||||
}
|
||||
@@ -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(1)
|
||||
key := flag.Arg(1)
|
||||
|
||||
value, err := resource.GetResourceValue(appName, processType, resourceType, key)
|
||||
if err != nil {
|
||||
common.LogFail(err.Error())
|
||||
}
|
||||
|
||||
fmt.Fprintln(os.Stdout, value)
|
||||
}
|
||||
3
plugins/resource/src/vendor/github.com/ryanuber/columnize/.travis.yml
generated
vendored
Normal file
3
plugins/resource/src/vendor/github.com/ryanuber/columnize/.travis.yml
generated
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
language: go
|
||||
go:
|
||||
- tip
|
||||
20
plugins/resource/src/vendor/github.com/ryanuber/columnize/LICENSE
generated
vendored
Normal file
20
plugins/resource/src/vendor/github.com/ryanuber/columnize/LICENSE
generated
vendored
Normal file
@@ -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.
|
||||
69
plugins/resource/src/vendor/github.com/ryanuber/columnize/README.md
generated
vendored
Normal file
69
plugins/resource/src/vendor/github.com/ryanuber/columnize/README.md
generated
vendored
Normal file
@@ -0,0 +1,69 @@
|
||||
Columnize
|
||||
=========
|
||||
|
||||
Easy column-formatted output for golang
|
||||
|
||||
[](https://travis-ci.org/ryanuber/columnize)
|
||||
[](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.
|
||||
178
plugins/resource/src/vendor/github.com/ryanuber/columnize/columnize.go
generated
vendored
Normal file
178
plugins/resource/src/vendor/github.com/ryanuber/columnize/columnize.go
generated
vendored
Normal file
@@ -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)
|
||||
}
|
||||
306
plugins/resource/src/vendor/github.com/ryanuber/columnize/columnize_test.go
generated
vendored
Normal file
306
plugins/resource/src/vendor/github.com/ryanuber/columnize/columnize_test.go
generated
vendored
Normal file
@@ -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 = "<none>"
|
||||
output := Format(input, config)
|
||||
|
||||
expected := "Column A Column B Column C\n"
|
||||
expected += "x <none> 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)
|
||||
}
|
||||
}
|
||||
165
plugins/resource/subcommands.go
Normal file
165
plugins/resource/subcommands.go
Normal file
@@ -0,0 +1,165 @@
|
||||
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 != "" {
|
||||
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 (%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
|
||||
}
|
||||
3
plugins/resource/vendor/github.com/ryanuber/columnize/.travis.yml
generated
vendored
Normal file
3
plugins/resource/vendor/github.com/ryanuber/columnize/.travis.yml
generated
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
language: go
|
||||
go:
|
||||
- tip
|
||||
20
plugins/resource/vendor/github.com/ryanuber/columnize/LICENSE
generated
vendored
Normal file
20
plugins/resource/vendor/github.com/ryanuber/columnize/LICENSE
generated
vendored
Normal file
@@ -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.
|
||||
69
plugins/resource/vendor/github.com/ryanuber/columnize/README.md
generated
vendored
Normal file
69
plugins/resource/vendor/github.com/ryanuber/columnize/README.md
generated
vendored
Normal file
@@ -0,0 +1,69 @@
|
||||
Columnize
|
||||
=========
|
||||
|
||||
Easy column-formatted output for golang
|
||||
|
||||
[](https://travis-ci.org/ryanuber/columnize)
|
||||
[](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.
|
||||
178
plugins/resource/vendor/github.com/ryanuber/columnize/columnize.go
generated
vendored
Normal file
178
plugins/resource/vendor/github.com/ryanuber/columnize/columnize.go
generated
vendored
Normal file
@@ -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)
|
||||
}
|
||||
306
plugins/resource/vendor/github.com/ryanuber/columnize/columnize_test.go
generated
vendored
Normal file
306
plugins/resource/vendor/github.com/ryanuber/columnize/columnize_test.go
generated
vendored
Normal file
@@ -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 = "<none>"
|
||||
output := Format(input, config)
|
||||
|
||||
expected := "Column A Column B Column C\n"
|
||||
expected += "x <none> 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)
|
||||
}
|
||||
}
|
||||
@@ -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")
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -27,6 +27,10 @@ scheduler-docker-local-scheduler-run() {
|
||||
local DOCKER_ARGS=$(: | plugn trigger docker-args-run "$APP" "$IMAGE_TAG")
|
||||
[[ "$DOKKU_TRACE" ]] && local 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" "$PHASE_SCRIPT_KEY")
|
||||
|
||||
declare -a ARG_ARRAY
|
||||
eval "ARG_ARRAY=($DOCKER_ARGS)"
|
||||
|
||||
@@ -38,7 +42,7 @@ scheduler-docker-local-scheduler-run() {
|
||||
fi
|
||||
|
||||
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"
|
||||
|
||||
|
||||
46
rpm.mk
46
rpm.mk
@@ -46,51 +46,17 @@ 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)" \
|
||||
|
||||
3
tests.mk
3
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..."
|
||||
|
||||
4
tests/unit/10_apps.bats
Executable file → Normal file
4
tests/unit/10_apps.bats
Executable file → Normal file
@@ -2,11 +2,11 @@
|
||||
|
||||
load test_helper
|
||||
|
||||
setup () {
|
||||
setup() {
|
||||
global_setup
|
||||
}
|
||||
|
||||
teardown () {
|
||||
teardown() {
|
||||
global_teardown
|
||||
}
|
||||
|
||||
|
||||
185
tests/unit/20_resource.bats
Normal file
185
tests/unit/20_resource.bats
Normal file
@@ -0,0 +1,185 @@
|
||||
#!/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
|
||||
|
||||
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 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
|
||||
}
|
||||
Reference in New Issue
Block a user