Merge branch 'master' into feature-config-golang

This commit is contained in:
Jose Diaz-Gonzalez
2017-10-04 19:04:31 -04:00
committed by GitHub
143 changed files with 4954 additions and 302 deletions

View File

@@ -20,10 +20,12 @@ jobs:
- run: |
case $CIRCLE_NODE_INDEX in
0) sudo -E make -e lint ;;
1) sudo -E make -e go-tests ;;
1) sudo -E make -e go-tests ci-go-coverage;;
2) sudo -E make -e deploy-test-checks-root deploy-test-config deploy-test-multi deploy-test-go-fail-predeploy deploy-test-go-fail-postdeploy ;;
esac
- run:
shell: /bin/bash
command: sudo -E make -e test-ci
no_output_timeout: 60m
- store_artifacts:
path: ./coverage.out

5
.codacy.yml Normal file
View File

@@ -0,0 +1,5 @@
exclude_paths:
- vendor/**/*
- tests/**/*
- "**_test.go"
- contrib/*

View File

@@ -17,5 +17,5 @@ indent_size = 4
[*.go]
insert_final_newline = true
indent_style = space
indent_style = tab
indent_size = 4

1
.gitignore vendored
View File

@@ -6,3 +6,4 @@
data/
stack.tgz
tmp
coverage.out

View File

@@ -1,5 +1,20 @@
# History
## 0.10.5
### Bug Fixes
- #2912: @josegonzalez Add missing depends statement for rsyslog
- #2906: @manuel-colmenero Check the location of nginx in a central way
- #2895: @josegonzalez cd to app directory when calling git worktree add
### Documentation
- #2922: @axilleas Clarify the minimum Nginx version for HTTP/2 support
- #2919: @wootwoot1234 Update nginx documentation surrounding file uploading for php buildpack users
- #2913: @znz Fix a typo in the rpm release script
- #2910: @buckle2000 Add a note about using the full git url for non-compliant toolchains
## 0.10.4
### Bug Fixes

View File

@@ -99,8 +99,8 @@ A fresh VM running any of the following operating systems:
To install the latest stable release, run the following commands as a user who has access to `sudo`:
```shell
wget https://raw.githubusercontent.com/dokku/dokku/v0.10.4/bootstrap.sh
sudo DOKKU_TAG=v0.10.4 bash bootstrap.sh
wget https://raw.githubusercontent.com/dokku/dokku/v0.10.5/bootstrap.sh
sudo DOKKU_TAG=v0.10.5 bash bootstrap.sh
```
You can then proceed to the ip address or domain name associated with your server to complete the web-based installation.

View File

@@ -10,7 +10,7 @@ import subprocess
import sys
import threading
VERSION = 'v0.10.4'
VERSION = 'v0.10.5'
hostname = ''
try:

2
debian/control vendored
View File

@@ -1,5 +1,5 @@
Package: dokku
Version: 0.10.4
Version: 0.10.5
Section: web
Priority: optional
Architecture: amd64

View File

@@ -31,30 +31,30 @@ dokku plugin
```
plugn: dev
00_dokku-standard 0.10.4 enabled dokku core standard plugin
20_events 0.10.4 enabled dokku core events logging plugin
apps 0.10.4 enabled dokku core apps plugin
build-env 0.10.4 enabled dokku core build-env plugin
certs 0.10.4 enabled dokku core certificate management plugin
checks 0.10.4 enabled dokku core checks plugin
common 0.10.4 enabled dokku core common plugin
config 0.10.4 enabled dokku core config plugin
docker-options 0.10.4 enabled dokku core docker-options plugin
domains 0.10.4 enabled dokku core domains plugin
enter 0.10.4 enabled dokku core enter plugin
git 0.10.4 enabled dokku core git plugin
logs 0.10.4 enabled dokku core logs plugin
named-containers 0.10.4 enabled dokku core named containers plugin
nginx-vhosts 0.10.4 enabled dokku core nginx-vhosts plugin
plugin 0.10.4 enabled dokku core plugin plugin
proxy 0.10.4 enabled dokku core proxy plugin
ps 0.10.4 enabled dokku core ps plugin
repo 0.10.4 enabled dokku core repo plugin
shell 0.10.4 enabled dokku core shell plugin
ssh-keys 0.10.4 enabled dokku core ssh-keys plugin
storage 0.10.4 enabled dokku core storage plugin
tags 0.10.4 enabled dokku core tags plugin
tar 0.10.4 enabled dokku core tar plugin
00_dokku-standard 0.10.5 enabled dokku core standard plugin
20_events 0.10.5 enabled dokku core events logging plugin
apps 0.10.5 enabled dokku core apps plugin
build-env 0.10.5 enabled dokku core build-env plugin
certs 0.10.5 enabled dokku core certificate management plugin
checks 0.10.5 enabled dokku core checks plugin
common 0.10.5 enabled dokku core common plugin
config 0.10.5 enabled dokku core config plugin
docker-options 0.10.5 enabled dokku core docker-options plugin
domains 0.10.5 enabled dokku core domains plugin
enter 0.10.5 enabled dokku core enter plugin
git 0.10.5 enabled dokku core git plugin
logs 0.10.5 enabled dokku core logs plugin
named-containers 0.10.5 enabled dokku core named containers plugin
nginx-vhosts 0.10.5 enabled dokku core nginx-vhosts plugin
plugin 0.10.5 enabled dokku core plugin plugin
proxy 0.10.5 enabled dokku core proxy plugin
ps 0.10.5 enabled dokku core ps plugin
repo 0.10.5 enabled dokku core repo plugin
shell 0.10.5 enabled dokku core shell plugin
ssh-keys 0.10.5 enabled dokku core ssh-keys plugin
storage 0.10.5 enabled dokku core storage plugin
tags 0.10.5 enabled dokku core tags plugin
tar 0.10.5 enabled dokku core tar plugin
```
Installing a plugin is easy as well using the `plugin:install` command. This command will also trigger the `install` pluginhook on all existing plugins.

View File

@@ -2,10 +2,10 @@
<browserconfig>
<msapplication>
<tile>
<square70x70logo src="https://cdn.rawgit.com/dokku/dokku/v0.10.4/docs/assets/favicons/mstile-70x70.png"/>
<square150x150logo src="https://cdn.rawgit.com/dokku/dokku/v0.10.4/docs/assets/favicons/mstile-150x150.png"/>
<square310x310logo src="https://cdn.rawgit.com/dokku/dokku/v0.10.4/docs/assets/favicons/mstile-310x310.png"/>
<wide310x150logo src="https://cdn.rawgit.com/dokku/dokku/v0.10.4/docs/assets/favicons/mstile-310x150.png"/>
<square70x70logo src="https://cdn.rawgit.com/dokku/dokku/v0.10.5/docs/assets/favicons/mstile-70x70.png"/>
<square150x150logo src="https://cdn.rawgit.com/dokku/dokku/v0.10.5/docs/assets/favicons/mstile-150x150.png"/>
<square310x310logo src="https://cdn.rawgit.com/dokku/dokku/v0.10.5/docs/assets/favicons/mstile-310x310.png"/>
<wide310x150logo src="https://cdn.rawgit.com/dokku/dokku/v0.10.5/docs/assets/favicons/mstile-310x150.png"/>
<TileColor>#da532c</TileColor>
</tile>
</msapplication>

View File

@@ -2,37 +2,37 @@
"name": "Dokku",
"icons": [
{
"src": "https:\/\/cdn.rawgit.com\/progrium\/dokku\/v0.10.4\/docs\/assets\/favicons\/android-chrome-36x36.png",
"src": "https:\/\/cdn.rawgit.com\/progrium\/dokku\/v0.10.5\/docs\/assets\/favicons\/android-chrome-36x36.png",
"sizes": "36x36",
"type": "image\/png",
"density": "0.75"
},
{
"src": "https:\/\/cdn.rawgit.com\/progrium\/dokku\/v0.10.4\/docs\/assets\/favicons\/android-chrome-48x48.png",
"src": "https:\/\/cdn.rawgit.com\/progrium\/dokku\/v0.10.5\/docs\/assets\/favicons\/android-chrome-48x48.png",
"sizes": "48x48",
"type": "image\/png",
"density": "1.0"
},
{
"src": "https:\/\/cdn.rawgit.com\/progrium\/dokku\/v0.10.4\/docs\/assets\/favicons\/android-chrome-72x72.png",
"src": "https:\/\/cdn.rawgit.com\/progrium\/dokku\/v0.10.5\/docs\/assets\/favicons\/android-chrome-72x72.png",
"sizes": "72x72",
"type": "image\/png",
"density": "1.5"
},
{
"src": "https:\/\/cdn.rawgit.com\/progrium\/dokku\/v0.10.4\/docs\/assets\/favicons\/android-chrome-96x96.png",
"src": "https:\/\/cdn.rawgit.com\/progrium\/dokku\/v0.10.5\/docs\/assets\/favicons\/android-chrome-96x96.png",
"sizes": "96x96",
"type": "image\/png",
"density": "2.0"
},
{
"src": "https:\/\/cdn.rawgit.com\/progrium\/dokku\/v0.10.4\/docs\/assets\/favicons\/android-chrome-144x144.png",
"src": "https:\/\/cdn.rawgit.com\/progrium\/dokku\/v0.10.5\/docs\/assets\/favicons\/android-chrome-144x144.png",
"sizes": "144x144",
"type": "image\/png",
"density": "3.0"
},
{
"src": "https:\/\/cdn.rawgit.com\/progrium\/dokku\/v0.10.4\/docs\/assets\/favicons\/android-chrome-192x192.png",
"src": "https:\/\/cdn.rawgit.com\/progrium\/dokku\/v0.10.5\/docs\/assets\/favicons\/android-chrome-192x192.png",
"sizes": "192x192",
"type": "image\/png",
"density": "4.0"

View File

@@ -35,13 +35,13 @@ h1 {
background-repeat: no-repeat;
}
.header .navbar-brand a {
background-image: url(https://cdn.rawgit.com/dokku/dokku/v0.10.4/docs/assets/dokku.png);
background-image: url(https://cdn.rawgit.com/dokku/dokku/v0.10.5/docs/assets/dokku.png);
text-indent: 40px;
}
.blurb {
color: #424242;
background-color: #ededed;
background-image: url(https://cdn.rawgit.com/dokku/dokku/v0.10.4/docs/assets/gplaypattern.png);
background-image: url(https://cdn.rawgit.com/dokku/dokku/v0.10.5/docs/assets/gplaypattern.png);
padding: 45px 0;
text-align: center;
}

View File

@@ -7,6 +7,6 @@
"0.7.2",
"0.8.2",
"0.9.4",
"0.10.4"
"0.10.5"
]
}

View File

@@ -117,6 +117,10 @@ The [HSTS header](https://en.wikipedia.org/wiki/HTTP_Strict_Transport_Security)
Beware that if you enable the header and a subsequent deploy of your application results in an HTTP deploy (for whatever reason), the way the header works means that a browser will not attempt to request the HTTP version of your site if the HTTPS version fails.
## HTTP/2 support
Certain versions of nginx have bugs that prevent [HTTP/2](https://nginx.org/en/docs/http/ngx_http_v2_module.html) from properly responding to all clients, thus causing applications to be unavailable. For HTTP/2 to be enabled in your applications' nginx configs, you need to have installed nginx 1.11.5 or higher. See [issue 2435](https://github.com/dokku/dokku/issues/2435) for more details.
## Running behind a load balancer
Your application has access to the HTTP headers `X-Forwarded-Proto`, `X-Forwarded-Port` and `X-Forwarded-For`. These headers indicate the protocol of the original request (HTTP or HTTPS), the port number, and the IP address of the client making the request, respectively. The default configuration is for Nginx to set these headers.
@@ -162,4 +166,4 @@ This could result in security issue, for example, if your application looks at t
### SSL Port Exposure
When your app is served from port `80` then the `/home/dokku/APP/nginx.conf` file will automatically be updated to instruct nginx to respond to ssl on port 443 as a new cert is added. If your app uses a non-standard port (perhaps you have a dockerfile deploy exposing port `99999`) you may need to manually expose an ssl port via `dokku proxy:ports-add <APP> https:443:99999`.
When your app is served from port `80` then the `/home/dokku/APP/nginx.conf` file will automatically be updated to instruct nginx to respond to ssl on port 443 as a new cert is added. If your app uses a non-standard port (perhaps you have a dockerfile deploy exposing port `99999`) you may need to manually expose an ssl port via `dokku proxy:ports-add <APP> https:443:99999`.

View File

@@ -60,7 +60,7 @@ git push dokku master
```
> Note: Some tools may not support the short-upstream syntax referenced above, and you may need to prefix
> the upstream with the scheme `ssh://` like so: `ssh://dokku@dokku.me/ruby-rails-sample`
> the upstream with the scheme `ssh://` like so: `ssh://dokku@dokku.me:ruby-rails-sample`
> Please see the [Git](https://git-scm.com/docs/git-clone#_git_urls_a_id_urls_a) documentation for more details.
```

View File

@@ -344,6 +344,141 @@ if [[ ! -f "$DOKKU_ROOT/HOSTNAME" ]]; then
fi
```
### `network-build-config`
- Description: Rebuilds network configuration
- Invoked by: `internally triggered by proxy-build-config within proxy implementations`
- Arguments: `$APP`
- Example:
```shell
#!/usr/bin/env bash
set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x
# TODO
```
### `network-compute-ports`
- Description: Computes the ports for a given app container
- Invoked by: `internally triggered by proxy-build-config within proxy implementations`
- Arguments: `$APP`
- Example:
```shell
#!/usr/bin/env bash
set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x
# TODO
```
### `network-config-exists`
- Description: Returns whether the network configuration for a given app exists
- Invoked by: `internally triggered by core-post-deploy within proxy implementations`
- Arguments: `$APP`
- Example:
```shell
#!/usr/bin/env bash
set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x
# TODO
```
### `network-get-ipaddr`
- Description: Return the ipaddr for a given app container
- Invoked by: `internally triggered by a deploy`
- Arguments: `$APP $PROC_TYPE $CONTAINER_ID`
- Example:
```shell
#!/usr/bin/env bash
set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x
# TODO
```
### `network-get-listeners`
- Description: Return the listeners (host:port combinations) for a given app container
- Invoked by: `internally triggered by a deploy`
- Arguments: `$APP`
- Example:
```shell
#!/usr/bin/env bash
set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x
# TODO
```
### `network-get-property`
- Description: Return the network value for an application's property
- Invoked by: `internally triggered by a deploy`
- Arguments: `$APP $KEY`
- Example:
```shell
#!/usr/bin/env bash
set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x
# TODO
```
### `network-get-port`
- Description: Return the port for a given app container
- Invoked by: `internally triggered by a deploy`
- Arguments: `$APP $PROC_TYPE $CONTAINER_ID $IS_HEROKUISH_CONTAINER`
- Example:
```shell
#!/usr/bin/env bash
set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x
# TODO
```
### `network-write-ipaddr`
- Description: Write the ipaddr for a given app index
- Invoked by: `internally triggered by a deploy`
- Arguments: `$APP $PROC_TYPE $CONTAINER_INDEX $IP_ADDRESS`
- Example:
```shell
#!/usr/bin/env bash
set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x
# TODO
```
### `network-write-port`
- Description: Write the port for a given app index
- Invoked by: `internally triggered by a deploy`
- Arguments: `$APP $PROC_TYPE $CONTAINER_INDEX $PORT`
- Example:
```shell
#!/usr/bin/env bash
set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x
# TODO
```
### `nginx-hostname`
- Description: Allows you to customize the hostname for a given application.
@@ -410,6 +545,40 @@ set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x
# TODO
```
### `post-certs-remove`
- Description: Allows you to run commands after a cert is removed
- Invoked by: `dokku certs:remove`
- Arguments: `$APP`
- Example:
```shell
#!/usr/bin/env bash
set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x
source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions"
APP="$1"; verify_app_name "$APP"
# TODO
```
### `post-certs-update`
- Description: Allows you to run commands after a cert is added/updated
- Invoked by: `dokku certs:add`, `dokku certs:update`
- Arguments: `$APP`
- Example:
```shell
#!/usr/bin/env bash
set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x
source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions"
APP="$1"; verify_app_name "$APP"
# TODO
```
### `post-create`
- Description: Can be used to run commands after an application is created.
@@ -763,10 +932,10 @@ APP="$1"; verify_app_name "$APP"
# TODO
```
### `post-certs-update`
### `proxy-build-config`
- Description: Allows you to run commands after a cert is added/updated
- Invoked by: `dokku certs:add`, `dokku certs:update`
- Description: Builds the proxy implementation configuration for a given app
- Invoked by: `internally triggered by ps:restore`
- Arguments: `$APP`
- Example:
@@ -774,16 +943,29 @@ APP="$1"; verify_app_name "$APP"
#!/usr/bin/env bash
set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x
source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions"
APP="$1"; verify_app_name "$APP"
# TODO
```
### `post-certs-remove`
### `proxy-enable`
- Description: Allows you to run commands after a cert is removed
- Invoked by: `dokku certs:remove`
- Description: Enables the configured proxy implementation for an app
- Invoked by: `internally triggered by ps:restore`
- Arguments: `$APP`
- Example:
```shell
#!/usr/bin/env bash
set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x
# TODO
```
### `proxy-disable`
- Description: Disables the configured proxy implementation for an app
- Invoked by: `internally triggered by ps:restore`
- Arguments: `$APP`
- Example:
@@ -791,8 +973,6 @@ APP="$1"; verify_app_name "$APP"
#!/usr/bin/env bash
set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x
source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions"
APP="$1"; verify_app_name "$APP"
# TODO
```

View File

@@ -67,7 +67,7 @@ The workflow looks like this:
```shell
# having dokku-arch in ../dokku-arch
vagrant up build-arch
# wait for "==> build-arch: ==> Finished making: dokku 0.10.4-2 (Mon Feb 22 23:20:37 CET 2016)"
# wait for "==> build-arch: ==> Finished making: dokku 0.10.5-2 (Mon Feb 22 23:20:37 CET 2016)"
cd ../dokku-arch
git add PKGBUILD .SRCINFO
git commit -m 'Update to dokku 0.9.9'

View File

@@ -21,11 +21,11 @@ To install the latest stable version of dokku, you can run the following shell c
```shell
# for debian systems, installs Dokku via apt-get
wget https://raw.githubusercontent.com/dokku/dokku/v0.10.4/bootstrap.sh;
sudo DOKKU_TAG=v0.10.4 bash bootstrap.sh
wget https://raw.githubusercontent.com/dokku/dokku/v0.10.5/bootstrap.sh;
sudo DOKKU_TAG=v0.10.5 bash bootstrap.sh
```
The installation process takes about 5-10 minutes, depending upon internet connection speed.
The installation process takes about 5-10 minutes, depending upon internet connection speed.
If you're using Debian 8 or Ubuntu 14.04, make sure your package manager is configured to install a sufficiently recent version of nginx<sup>[3]</sup>, otherwise, the installation may fail due to "unmet dependencies" relating nginx.
@@ -60,4 +60,4 @@ As well, you may wish to customize your installation in some other fashion. or e
- <sup>[1]: To check whether your system has an fqdn set, run `sudo hostname -f`</sup>
- <sup>[2]: If your system has less than 1GB of memory, you can use [this workaround](/docs/getting-started/advanced-installation.md#vms-with-less-than-1gb-of-memory).</sup>
- <sup>[3]: nginx >= 1.8.0 can be installed by enabling backports, or by adding [this PPA](https://launchpad.net/~nginx/+archive/ubuntu/stable) if you're using Ubuntu.</sup>
- <sup>[3]: nginx >= 1.8.0 can be installed via the [nginx repositories](https://www.nginx.com/resources/admin-guide/installing-nginx-open-source/), or by adding [this PPA](https://launchpad.net/~nginx/+archive/ubuntu/stable) if you're using Ubuntu. nginx >= 1.11.5 is necessary for HTTP/2 support</sup>

View File

@@ -10,30 +10,30 @@
<link href="https://fonts.googleapis.com/css?family=Montserrat:400,700" rel="stylesheet" type="text/css">
<title>Dokku - The smallest PaaS implementation you've ever seen</title>
<link rel="apple-touch-icon" sizes="57x57" href="https://cdn.rawgit.com/dokku/dokku/v0.10.4/docs/assets/favicons/apple-touch-icon-57x57.png">
<link rel="apple-touch-icon" sizes="60x60" href="https://cdn.rawgit.com/dokku/dokku/v0.10.4/docs/assets/favicons/apple-touch-icon-60x60.png">
<link rel="apple-touch-icon" sizes="72x72" href="https://cdn.rawgit.com/dokku/dokku/v0.10.4/docs/assets/favicons/apple-touch-icon-72x72.png">
<link rel="apple-touch-icon" sizes="76x76" href="https://cdn.rawgit.com/dokku/dokku/v0.10.4/docs/assets/favicons/apple-touch-icon-76x76.png">
<link rel="apple-touch-icon" sizes="114x114" href="https://cdn.rawgit.com/dokku/dokku/v0.10.4/docs/assets/favicons/apple-touch-icon-114x114.png">
<link rel="apple-touch-icon" sizes="120x120" href="https://cdn.rawgit.com/dokku/dokku/v0.10.4/docs/assets/favicons/apple-touch-icon-120x120.png">
<link rel="apple-touch-icon" sizes="144x144" href="https://cdn.rawgit.com/dokku/dokku/v0.10.4/docs/assets/favicons/apple-touch-icon-144x144.png">
<link rel="apple-touch-icon" sizes="152x152" href="https://cdn.rawgit.com/dokku/dokku/v0.10.4/docs/assets/favicons/apple-touch-icon-152x152.png">
<link rel="apple-touch-icon" sizes="180x180" href="https://cdn.rawgit.com/dokku/dokku/v0.10.4/docs/assets/favicons/apple-touch-icon-180x180.png">
<link rel="icon" type="image/png" href="https://cdn.rawgit.com/dokku/dokku/v0.10.4/docs/assets/favicons/favicon-32x32.png" sizes="32x32">
<link rel="icon" type="image/png" href="https://cdn.rawgit.com/dokku/dokku/v0.10.4/docs/assets/favicons/android-chrome-192x192.png" sizes="192x192">
<link rel="icon" type="image/png" href="https://cdn.rawgit.com/dokku/dokku/v0.10.4/docs/assets/favicons/favicon-96x96.png" sizes="96x96">
<link rel="icon" type="image/png" href="https://cdn.rawgit.com/dokku/dokku/v0.10.4/docs/assets/favicons/favicon-16x16.png" sizes="16x16">
<link rel="manifest" href="https://cdn.rawgit.com/dokku/dokku/v0.10.4/docs/assets/favicons/manifest.json">
<link rel="shortcut icon" href="https://cdn.rawgit.com/dokku/dokku/v0.10.4/docs/assets/favicons/favicon.ico">
<link rel="apple-touch-icon" sizes="57x57" href="https://cdn.rawgit.com/dokku/dokku/v0.10.5/docs/assets/favicons/apple-touch-icon-57x57.png">
<link rel="apple-touch-icon" sizes="60x60" href="https://cdn.rawgit.com/dokku/dokku/v0.10.5/docs/assets/favicons/apple-touch-icon-60x60.png">
<link rel="apple-touch-icon" sizes="72x72" href="https://cdn.rawgit.com/dokku/dokku/v0.10.5/docs/assets/favicons/apple-touch-icon-72x72.png">
<link rel="apple-touch-icon" sizes="76x76" href="https://cdn.rawgit.com/dokku/dokku/v0.10.5/docs/assets/favicons/apple-touch-icon-76x76.png">
<link rel="apple-touch-icon" sizes="114x114" href="https://cdn.rawgit.com/dokku/dokku/v0.10.5/docs/assets/favicons/apple-touch-icon-114x114.png">
<link rel="apple-touch-icon" sizes="120x120" href="https://cdn.rawgit.com/dokku/dokku/v0.10.5/docs/assets/favicons/apple-touch-icon-120x120.png">
<link rel="apple-touch-icon" sizes="144x144" href="https://cdn.rawgit.com/dokku/dokku/v0.10.5/docs/assets/favicons/apple-touch-icon-144x144.png">
<link rel="apple-touch-icon" sizes="152x152" href="https://cdn.rawgit.com/dokku/dokku/v0.10.5/docs/assets/favicons/apple-touch-icon-152x152.png">
<link rel="apple-touch-icon" sizes="180x180" href="https://cdn.rawgit.com/dokku/dokku/v0.10.5/docs/assets/favicons/apple-touch-icon-180x180.png">
<link rel="icon" type="image/png" href="https://cdn.rawgit.com/dokku/dokku/v0.10.5/docs/assets/favicons/favicon-32x32.png" sizes="32x32">
<link rel="icon" type="image/png" href="https://cdn.rawgit.com/dokku/dokku/v0.10.5/docs/assets/favicons/android-chrome-192x192.png" sizes="192x192">
<link rel="icon" type="image/png" href="https://cdn.rawgit.com/dokku/dokku/v0.10.5/docs/assets/favicons/favicon-96x96.png" sizes="96x96">
<link rel="icon" type="image/png" href="https://cdn.rawgit.com/dokku/dokku/v0.10.5/docs/assets/favicons/favicon-16x16.png" sizes="16x16">
<link rel="manifest" href="https://cdn.rawgit.com/dokku/dokku/v0.10.5/docs/assets/favicons/manifest.json">
<link rel="shortcut icon" href="https://cdn.rawgit.com/dokku/dokku/v0.10.5/docs/assets/favicons/favicon.ico">
<meta name="apple-mobile-web-app-title" content="Dokku">
<meta name="application-name" content="Dokku">
<meta name="msapplication-TileColor" content="#da532c">
<meta name="msapplication-TileImage" content="https://cdn.rawgit.com/dokku/dokku/v0.10.4/docs/assets/favicons/mstile-144x144.png">
<meta name="msapplication-config" content="https://cdn.rawgit.com/dokku/dokku/v0.10.4/docs/assets/favicons/browserconfig.xml">
<meta name="msapplication-TileImage" content="https://cdn.rawgit.com/dokku/dokku/v0.10.5/docs/assets/favicons/mstile-144x144.png">
<meta name="msapplication-config" content="https://cdn.rawgit.com/dokku/dokku/v0.10.5/docs/assets/favicons/browserconfig.xml">
<meta name="theme-color" content="#ffffff">
<link href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.0.0-alpha.2/css/bootstrap.min.css" rel="stylesheet">
<link href="https://cdn.rawgit.com/dokku/dokku/v0.10.4/docs/assets/style.css" rel="stylesheet">
<link href="https://cdn.rawgit.com/dokku/dokku/v0.10.5/docs/assets/style.css" rel="stylesheet">
<!-- <link href="/dokku/docs/assets/style.css" rel="stylesheet"> -->
<style>
</style>
@@ -98,12 +98,12 @@
<p class="line">
<span class="path"></span>
<span class="prompt">$</span>
<span class="command">wget https://raw.githubusercontent.com/dokku/dokku/v0.10.4/bootstrap.sh</span>
<span class="command">wget https://raw.githubusercontent.com/dokku/dokku/v0.10.5/bootstrap.sh</span>
</p>
<p class="line">
<span class="path"></span>
<span class="prompt">$</span>
<span class="command">sudo DOKKU_TAG=v0.10.4 bash bootstrap.sh</span>
<span class="command">sudo DOKKU_TAG=v0.10.5 bash bootstrap.sh</span>
</p>
<p class="line">
<span class="output">&nbsp;# go to your server's IP and follow the web installer</span>

124
docs/networking/network.md Normal file
View File

@@ -0,0 +1,124 @@
# Network Management
> New as of 0.11.0
```
network:report [<app>] [<flag>] # Displays a network report for one or more apps
network:rebuild <app> # Rebuilds network settings for an app
network:rebuildall # Rebuild network settings for all apps
network:set <app> <key> (<value>) # Set or clear a network property for an app
```
The Network plugin allows developers to abstract the concept of container network management, allowing developers to both change what networks a given container is attached to as well as rebuild the configuration on the fly.
## Usage
### Rebuilding network settings
There are cases where you may need to rebuild the network configuration for an app, such as on app boot or container restart. In these cases, you can use the `network:rebuild` command:
```shell
dokku network:rebuild node-js-app
```
> This command will exit a non-zero number that depends on the number of containers for which configuration could not be built
### Rebuilding all network settings
In some cases, a docker upgrade may reset container IPs or Ports. In both cases, you can quickly rewrite those files by using the `network:rebuildall` command:
```shell
dokku network:rebuildall
```
> This command will exit a non-zero number that depends on the number of containers for which configuration could not be built
### Container network interface binding
> This functionality does not control the `--network` docker flag. Please use the [docker-options plugin](docs/advanced-usage/docker-options.md) to manage this flag.
By default, an application will only bind to the internal interface. This behavior can be modified per app by changing the `bind-all-interfaces` network property.
```shell
# bind to the default docker interface (`docker0`) with a random internal ip
# this is the default behavior
dokku network:set node-js-app bind-all-interfaces false
# bind to all interfaces (`0.0.0.0`) on a random port for each upstream port
# this will make the app container directly accessible by other hosts on your network
# ports are randomized for every deploy, e.g. `0.0.0.0:32771->5000/tcp`.
dokku network:set node-js-app bind-all-interfaces true
```
By way of example, in the default case, each container is bound to the docker interface:
```shell
docker ps
```
```
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
1b88d8aec3d1 dokku/node-js-app:latest "/bin/bash -c '/star About a minute ago Up About a minute node-js-app.web.1
```
As such, the container's IP address will be an internal IP, and thus it is only accessible on the host itself:
```
docker inspect --format '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' node-js-app.web.1
```
```
172.17.0.6
```
However, you can disable the internal proxying via the `network:set` command so that it will listen on the host's IP address:
```shell
dokku network:set node-js-app bind-all-interfaces true
# container bound to all interfaces
docker ps
```
```
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
d6499edb0edb dokku/node-js-app:latest "/bin/bash -c '/star About a minute ago Up About a minute 0.0.0.0:49153->5000/tcp node-js-app.web.1
```
### Displaying network reports about an app
You can get a report about the app's network status using the `network:report` command:
```shell
dokku network:report
```
```
=====> node-js-app network information
Network bind all interfaces: false
Network listeners: 172.17.0.1:5000
=====> python-sample network information
Network bind all interfaces: false
Network listeners: 172.17.0.2:5000
=====> ruby-sample network information
Network bind all interfaces: true
Network listeners:
```
You can run the command for a specific app also.
```shell
dokku network:report node-js-app
```
```
=====> node-js-app network information
Network bind all interfaces: false
Network listeners: 172.17.0.1:5000
```
You can pass flags which will output only the value of the specific information you want. For example:
```shell
dokku network:report node-js-app --network-bind-all-interfaces
```

View File

@@ -19,46 +19,9 @@ In Dokku 0.5.0, port proxying was decoupled from the `nginx-vhosts` plugin into
### Container network interface binding
> New as of 0.5.0
> Changed as of 0.11.0
By default, the deployed docker container running your app's web process will bind to the internal docker network interface (i.e. `docker inspect --format '{{ .NetworkSettings.IPAddress }}' $CONTAINER_ID`). This behavior can be modified per app by disabling the proxy (i.e. `dokku proxy:disable <app>`). In this case, the container will bind to an external interface (i.e. `0.0.0.0`) and your app container will be directly accessible by other hosts on your network.
> If a proxy is disabled, Dokku will bind your container's port to a random port on the host for every deploy, e.g. `0.0.0.0:32771->5000/tcp`.
By way of example, in the default case, each container is bound to the docker interface:
```shell
docker ps
```
```
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
1b88d8aec3d1 dokku/node-js-app:latest "/bin/bash -c '/star About a minute ago Up About a minute node-js-app.web.1
```
As such, the container's IP address will be an internal IP, and thus it is only accessible on the host itself:
```
docker inspect --format '{{ .NetworkSettings.IPAddress }}' node-js-app.web.1
```
```
172.17.0.6
```
However, you can disable the internal proxying via the `proxy:disable` command so that it will listen on the host's IP address:
```shell
dokku proxy:disable node-js-app
# container bound to all interfaces
docker ps
```
```
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
d6499edb0edb dokku/node-js-app:latest "/bin/bash -c '/star About a minute ago Up About a minute 0.0.0.0:49153->5000/tcp node-js-app.web.1
```
From Dokku versions `0.5.0` until `0.11.0`, enabling or disabling an application's proxy would **also** control whether or not the application was bound to all interfaces - e.g. `0.0.0.0`. As of `0.10.0`, this is now controlled by the network plugin. Please see the [network documentation](/docs/networking/network.md#container-network-interface-binding) for more information.
### Displaying proxy reports about an app

View File

@@ -9,26 +9,26 @@
<meta name="author" content="">
<title>Dokku - The smallest PaaS implementation you've ever seen</title>
<link rel="apple-touch-icon" sizes="57x57" href="https://cdn.rawgit.com/dokku/dokku/v0.10.4/docs/assets/favicons/apple-touch-icon-57x57.png">
<link rel="apple-touch-icon" sizes="60x60" href="https://cdn.rawgit.com/dokku/dokku/v0.10.4/docs/assets/favicons/apple-touch-icon-60x60.png">
<link rel="apple-touch-icon" sizes="72x72" href="https://cdn.rawgit.com/dokku/dokku/v0.10.4/docs/assets/favicons/apple-touch-icon-72x72.png">
<link rel="apple-touch-icon" sizes="76x76" href="https://cdn.rawgit.com/dokku/dokku/v0.10.4/docs/assets/favicons/apple-touch-icon-76x76.png">
<link rel="apple-touch-icon" sizes="114x114" href="https://cdn.rawgit.com/dokku/dokku/v0.10.4/docs/assets/favicons/apple-touch-icon-114x114.png">
<link rel="apple-touch-icon" sizes="120x120" href="https://cdn.rawgit.com/dokku/dokku/v0.10.4/docs/assets/favicons/apple-touch-icon-120x120.png">
<link rel="apple-touch-icon" sizes="144x144" href="https://cdn.rawgit.com/dokku/dokku/v0.10.4/docs/assets/favicons/apple-touch-icon-144x144.png">
<link rel="apple-touch-icon" sizes="152x152" href="https://cdn.rawgit.com/dokku/dokku/v0.10.4/docs/assets/favicons/apple-touch-icon-152x152.png">
<link rel="apple-touch-icon" sizes="180x180" href="https://cdn.rawgit.com/dokku/dokku/v0.10.4/docs/assets/favicons/apple-touch-icon-180x180.png">
<link rel="icon" type="image/png" href="https://cdn.rawgit.com/dokku/dokku/v0.10.4/docs/assets/favicons/favicon-32x32.png" sizes="32x32">
<link rel="icon" type="image/png" href="https://cdn.rawgit.com/dokku/dokku/v0.10.4/docs/assets/favicons/android-chrome-192x192.png" sizes="192x192">
<link rel="icon" type="image/png" href="https://cdn.rawgit.com/dokku/dokku/v0.10.4/docs/assets/favicons/favicon-96x96.png" sizes="96x96">
<link rel="icon" type="image/png" href="https://cdn.rawgit.com/dokku/dokku/v0.10.4/docs/assets/favicons/favicon-16x16.png" sizes="16x16">
<link rel="manifest" href="https://cdn.rawgit.com/dokku/dokku/v0.10.4/docs/assets/favicons/manifest.json">
<link rel="shortcut icon" href="https://cdn.rawgit.com/dokku/dokku/v0.10.4/docs/assets/favicons/favicon.ico">
<link rel="apple-touch-icon" sizes="57x57" href="https://cdn.rawgit.com/dokku/dokku/v0.10.5/docs/assets/favicons/apple-touch-icon-57x57.png">
<link rel="apple-touch-icon" sizes="60x60" href="https://cdn.rawgit.com/dokku/dokku/v0.10.5/docs/assets/favicons/apple-touch-icon-60x60.png">
<link rel="apple-touch-icon" sizes="72x72" href="https://cdn.rawgit.com/dokku/dokku/v0.10.5/docs/assets/favicons/apple-touch-icon-72x72.png">
<link rel="apple-touch-icon" sizes="76x76" href="https://cdn.rawgit.com/dokku/dokku/v0.10.5/docs/assets/favicons/apple-touch-icon-76x76.png">
<link rel="apple-touch-icon" sizes="114x114" href="https://cdn.rawgit.com/dokku/dokku/v0.10.5/docs/assets/favicons/apple-touch-icon-114x114.png">
<link rel="apple-touch-icon" sizes="120x120" href="https://cdn.rawgit.com/dokku/dokku/v0.10.5/docs/assets/favicons/apple-touch-icon-120x120.png">
<link rel="apple-touch-icon" sizes="144x144" href="https://cdn.rawgit.com/dokku/dokku/v0.10.5/docs/assets/favicons/apple-touch-icon-144x144.png">
<link rel="apple-touch-icon" sizes="152x152" href="https://cdn.rawgit.com/dokku/dokku/v0.10.5/docs/assets/favicons/apple-touch-icon-152x152.png">
<link rel="apple-touch-icon" sizes="180x180" href="https://cdn.rawgit.com/dokku/dokku/v0.10.5/docs/assets/favicons/apple-touch-icon-180x180.png">
<link rel="icon" type="image/png" href="https://cdn.rawgit.com/dokku/dokku/v0.10.5/docs/assets/favicons/favicon-32x32.png" sizes="32x32">
<link rel="icon" type="image/png" href="https://cdn.rawgit.com/dokku/dokku/v0.10.5/docs/assets/favicons/android-chrome-192x192.png" sizes="192x192">
<link rel="icon" type="image/png" href="https://cdn.rawgit.com/dokku/dokku/v0.10.5/docs/assets/favicons/favicon-96x96.png" sizes="96x96">
<link rel="icon" type="image/png" href="https://cdn.rawgit.com/dokku/dokku/v0.10.5/docs/assets/favicons/favicon-16x16.png" sizes="16x16">
<link rel="manifest" href="https://cdn.rawgit.com/dokku/dokku/v0.10.5/docs/assets/favicons/manifest.json">
<link rel="shortcut icon" href="https://cdn.rawgit.com/dokku/dokku/v0.10.5/docs/assets/favicons/favicon.ico">
<meta name="apple-mobile-web-app-title" content="Dokku">
<meta name="application-name" content="Dokku">
<meta name="msapplication-TileColor" content="#da532c">
<meta name="msapplication-TileImage" content="https://cdn.rawgit.com/dokku/dokku/v0.10.4/docs/assets/favicons/mstile-144x144.png">
<meta name="msapplication-config" content="https://cdn.rawgit.com/dokku/dokku/v0.10.4/docs/assets/favicons/browserconfig.xml">
<meta name="msapplication-TileImage" content="https://cdn.rawgit.com/dokku/dokku/v0.10.5/docs/assets/favicons/mstile-144x144.png">
<meta name="msapplication-config" content="https://cdn.rawgit.com/dokku/dokku/v0.10.5/docs/assets/favicons/browserconfig.xml">
<meta name="theme-color" content="#ffffff">
<script>
@@ -42,7 +42,7 @@
</script>
<link href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.0.0-alpha.2/css/bootstrap.min.css" rel="stylesheet">
<link href="https://cdn.rawgit.com/dokku/dokku/v0.10.4/docs/assets/style.css" rel="stylesheet">
<link href="https://cdn.rawgit.com/dokku/dokku/v0.10.5/docs/assets/style.css" rel="stylesheet">
<!-- <link href="/dokku/docs/assets/style.css" rel="stylesheet"> -->
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.5.0/css/font-awesome.min.css" rel="stylesheet">
<style>
@@ -136,11 +136,16 @@
<a href="#" class="list-group-item disabled">Configuration</a>
<a href="/{{NAME}}/configuration/environment-variables/" class="list-group-item">Environment Variables</a>
<a href="/{{NAME}}/configuration/dns/" class="list-group-item">DNS Configuration</a>
<a href="/{{NAME}}/configuration/domains/" class="list-group-item">Domain Configuration</a>
<a href="/{{NAME}}/configuration/nginx/" class="list-group-item">Nginx Configuration</a>
<a href="/{{NAME}}/configuration/ssl/" class="list-group-item">SSL Configuration</a>
<a href="#" class="list-group-item disabled">Network Management</a>
<a href="/{{NAME}}/networking/dns/" class="list-group-item">DNS Configuration</a>
<a href="/{{NAME}}/networking/network/" class="list-group-item">Network Management</a>
<a href="/{{NAME}}/networking/proxy-management/" class="list-group-item">Proxy and Port Management</a>
<a href="#" class="list-group-item disabled">Advanced Usage</a>
<a href="/{{NAME}}/advanced-usage/backup-recovery/" class="list-group-item">Backup and Recovery</a>
@@ -149,7 +154,6 @@
<a href="/{{NAME}}/advanced-usage/event-logs/" class="list-group-item">Event Logs</a>
<a href="/{{NAME}}/advanced-usage/persistent-storage/" class="list-group-item">Persistent Storage</a>
<a href="/{{NAME}}/advanced-usage/plugin-management/" class="list-group-item">Plugin Management</a>
<a href="/{{NAME}}/advanced-usage/proxy-management/" class="list-group-item">Proxy and Port Management</a>
<a href="/{{NAME}}/advanced-usage/repository-management/" class="list-group-item">Repository Management</a>
<a href="#" class="list-group-item disabled">Community Contributions</a>

View File

@@ -15,16 +15,19 @@
"deployment/images": "deployment/methods/images/",
"configuration-management": "configuration/environment-variables/",
"deployment/ssl-configuration": "configuration/ssl/",
"dns": "configuration/dns/",
"nginx": "configuration/nginx/",
"dns": "networking/dns/",
"configuration/dns": "networking/dns/",
"proxy": "networking/proxy-management/",
"advanced-usage/proxy-management": "networking/proxy-management/",
"backup-recovery": "advanced-usage/backup-recovery/",
"deployment-tasks": "advanced-usage/deployment-tasks/",
"deployment/deployment-tasks": "advanced-usage/deployment-tasks/",
"docker-options": "advanced-usage/docker-options/",
"dokku-events-logs": "advanced-usage/event-logs/",
"dokku-storage": "advanced-usage/persistent-storage/",
"proxy": "advanced-usage/proxy-management/",
"plugins": "community/plugins/"
}

View File

@@ -1,4 +1,4 @@
[plugin]
description = "dokku core standard plugin"
version = "0.10.4"
version = "0.10.5"
[plugin.config]

View File

@@ -0,0 +1 @@
hook

View File

@@ -0,0 +1 @@
hook

View File

@@ -0,0 +1 @@
hook

View File

@@ -0,0 +1 @@
hook

View File

@@ -0,0 +1 @@
hook

View File

@@ -0,0 +1 @@
hook

View File

@@ -0,0 +1 @@
hook

View File

@@ -0,0 +1 @@
hook

View File

@@ -0,0 +1 @@
hook

View File

@@ -1,4 +1,4 @@
[plugin]
description = "dokku core events logging plugin"
version = "0.10.4"
version = "0.10.5"
[plugin.config]

View File

@@ -0,0 +1 @@
hook

View File

@@ -0,0 +1 @@
hook

View File

@@ -0,0 +1 @@
hook

View File

@@ -1,4 +1,4 @@
[plugin]
description = "dokku core apps plugin"
version = "0.10.4"
version = "0.10.5"
[plugin.config]

View File

@@ -1,4 +1,4 @@
[plugin]
description = "dokku core build-env plugin"
version = "0.10.4"
version = "0.10.5"
[plugin.config]

View File

@@ -1,4 +1,4 @@
[plugin]
description = "dokku core certificate management plugin"
version = "0.10.4"
version = "0.10.5"
[plugin.config]

View File

@@ -1,4 +1,4 @@
[plugin]
description = "dokku core checks plugin"
version = "0.10.4"
version = "0.10.5"
[plugin.config]

View File

@@ -45,18 +45,21 @@ check_process_type() {
check_process() {
local APP="$1" PROC_TYPE="$2" CONTAINER_INDEX="$3"
local DOKKU_CONTAINER_ID_FILE="$DOKKU_ROOT/$APP/CONTAINER.$PROC_TYPE.$CONTAINER_INDEX"
local DOKKU_IP_FILE="$DOKKU_ROOT/$APP/IP.$PROC_TYPE.$CONTAINER_INDEX"
local DOKKU_PORT_FILE="$DOKKU_ROOT/$APP/PORT.$PROC_TYPE.$CONTAINER_INDEX"
local CONTAINER_ID DOKKU_CONTAINER_ID_FILE IMAGE IP IS_HEROKUISH_CONTAINER PORT
DOKKU_CONTAINER_ID_FILE="$DOKKU_ROOT/$APP/CONTAINER.$PROC_TYPE.$CONTAINER_INDEX"
if [[ ! -f "$DOKKU_CONTAINER_ID_FILE" ]]; then
dokku_log_fail "Invalid container index specified ($APP.$PROC_TYPE.$CONTAINER_INDEX)"
fi
IS_HEROKUISH_CONTAINER=false
IMAGE=$(get_app_image_name "$APP")
is_image_herokuish_based "$IMAGE" && IS_HEROKUISH_CONTAINER=true
dokku_log_info1 "Running checks for app ($APP.$PROC_TYPE.$CONTAINER_INDEX)"
local CONTAINER_ID=$(< "$DOKKU_CONTAINER_ID_FILE")
local IP=$(< "$DOKKU_IP_FILE")
local PORT=$(< "$DOKKU_PORT_FILE")
CONTAINER_ID=$(< "$DOKKU_CONTAINER_ID_FILE")
IP="$(plugn trigger network-get-ipaddr "$APP" "$PROC_TYPE" "$CONTAINER_ID")"
PORT="$(plugn trigger network-get-port "$APP" "$PROC_TYPE" "$IS_HEROKUISH_CONTAINER" "$CONTAINER_ID")"
plugn trigger check-deploy "$APP" "$CONTAINER_ID" "$PROC_TYPE" "$PORT" "$IP"
}

View File

@@ -1,11 +1,14 @@
package common
import (
"bufio"
"fmt"
"io/ioutil"
"os"
"os/exec"
"regexp"
"strings"
"unicode"
sh "github.com/codeskyblue/go-sh"
)
@@ -48,42 +51,24 @@ func (sc *ShellCmd) Execute() bool {
sc.Command.Stdout = os.Stdout
sc.Command.Stderr = os.Stderr
}
err := sc.Command.Run()
if err != nil {
if err := sc.Command.Run(); err != nil {
return false
}
return true
}
// VerifyAppName verifies app name format and app existence"
func VerifyAppName(appName string) (err error) {
dokkuRoot := MustGetEnv("DOKKU_ROOT")
appRoot := strings.Join([]string{dokkuRoot, appName}, "/")
_, err = os.Stat(appRoot)
if os.IsNotExist(err) {
return fmt.Errorf("App %s does not exist: %v\n", appName, err)
// Output is a lightweight wrapper around exec.Command.Output()
func (sc *ShellCmd) Output() ([]byte, error) {
env := os.Environ()
for k, v := range sc.Env {
env = append(env, fmt.Sprintf("%s=%s", k, v))
}
r, _ := regexp.Compile("^[a-z].*")
if !r.MatchString(appName) {
return fmt.Errorf("App name (%s) must begin with lowercase alphanumeric character\n", appName)
sc.Command.Env = env
if sc.ShowOutput {
sc.Command.Stdout = os.Stdout
sc.Command.Stderr = os.Stderr
}
return err
}
// MustGetEnv returns env variable or fails if it's not set
func MustGetEnv(key string) string {
value := os.Getenv(key)
if value == "" {
LogFail(fmt.Sprintf("%s not set!", key))
}
return value
}
// LogFail is the failure log formatter
// prints text to stderr and exits with status 1
func LogFail(text string) {
fmt.Fprintln(os.Stderr, fmt.Sprintf("FAILED: %s", text))
os.Exit(1)
return sc.Command.Output()
}
// GetDeployingAppImageName returns deploying image identifier for a given app, tag tuple. validate if tag is presented
@@ -135,14 +120,233 @@ func GetAppImageRepo(appName string) string {
return strings.Join([]string{"dokku", appName}, "/")
}
// ContainerIsRunning checks to see if a container is running
func ContainerIsRunning(containerID string) bool {
b, err := DockerInspect(containerID, "'{{.State.Running}}'")
if err != nil {
return false
}
return strings.TrimSpace(string(b[:])) == "true"
}
// DirectoryExists returns if a path exists and is a directory
func DirectoryExists(filePath string) bool {
fi, err := os.Stat(filePath)
if err != nil {
return false
}
return fi.IsDir()
}
// DockerInspect runs an inspect command with a given format against a container id
func DockerInspect(containerID, format string) (output string, err error) {
b, err := sh.Command("docker", "inspect", "--format", format, containerID).Output()
if err != nil {
return "", err
}
output = strings.TrimSpace(string(b[:]))
if strings.HasPrefix(output, "'") && strings.HasSuffix(output, "'") {
output = strings.TrimSuffix(strings.TrimPrefix(output, "'"), "'")
}
return
}
// DokkuApps returns a list of all local apps
func DokkuApps() (apps []string, err error) {
dokkuRoot := MustGetEnv("DOKKU_ROOT")
files, err := ioutil.ReadDir(dokkuRoot)
if err != nil {
err = fmt.Errorf("You haven't deployed any applications yet")
return
}
for _, f := range files {
appRoot := strings.Join([]string{dokkuRoot, f.Name()}, "/")
if !DirectoryExists(appRoot) {
continue
}
if f.Name() == "tls" || strings.HasPrefix(f.Name(), ".") {
continue
}
apps = append(apps, f.Name())
}
if len(apps) == 0 {
err = fmt.Errorf("You haven't deployed any applications yet")
return
}
return
}
// FileToSlice reads in all the lines from a file into a string slice
func FileToSlice(filePath string) (lines []string, err error) {
f, err := os.Open(filePath)
if err != nil {
return
}
defer f.Close()
scanner := bufio.NewScanner(f)
for scanner.Scan() {
text := strings.TrimSpace(scanner.Text())
if text == "" {
continue
}
lines = append(lines, text)
}
err = scanner.Err()
return
}
// FileExists returns if a path exists and is a file
func FileExists(filePath string) bool {
fi, err := os.Stat(filePath)
if err != nil {
return false
}
return fi.Mode().IsRegular()
}
// GetAppImageName returns image identifier for a given app, tag tuple. validate if tag is presented
func GetAppImageName(appName, imageTag, imageRepo string) (imageName string) {
err := VerifyAppName(appName)
if err != nil {
LogFail(err.Error())
}
if imageRepo == "" {
imageRepo = GetAppImageRepo(appName)
}
if imageTag == "" {
imageName = fmt.Sprintf("%v:latest", imageRepo)
} else {
imageName = fmt.Sprintf("%v:%v", imageRepo, imageTag)
if !VerifyImage(imageName) {
LogFail(fmt.Sprintf("app image (%s) not found", imageName))
}
}
return
}
// IsDeployed returns true if given app has a running container
func IsDeployed(appName string) bool {
dokkuRoot := MustGetEnv("DOKKU_ROOT")
appRoot := strings.Join([]string{dokkuRoot, appName}, "/")
files, err := ioutil.ReadDir(appRoot)
if err != nil {
return false
}
for _, f := range files {
if f.Name() == "CONTAINER" || strings.HasPrefix(f.Name(), "CONTAINER.") {
return true
}
}
return false
}
// IsImageHerokuishBased returns true if app image is based on herokuish
func IsImageHerokuishBased(image string) bool {
// circleci can't support --rm as they run lxc in lxc
dockerArgs := ""
if !FileExists("/home/ubuntu/.circlerc") {
dockerArgs = "--rm"
}
dockerGlobalArgs := os.Getenv("DOKKU_GLOBAL_RUN_ARGS")
parts := []string{"docker", "run", dockerGlobalArgs, "--entrypoint=\"/bin/sh\"", dockerArgs, image, "-c", "\"test -f /exec\""}
var dockerCmdParts []string
for _, str := range parts {
if str != "" {
dockerCmdParts = append(dockerCmdParts, str)
}
}
dockerCmd := NewShellCmd(strings.Join(dockerCmdParts, " "))
dockerCmd.ShowOutput = false
return dockerCmd.Execute()
}
// MustGetEnv returns env variable or fails if it's not set
func MustGetEnv(key string) (val string) {
val = os.Getenv(key)
if val == "" {
LogFail(fmt.Sprintf("%s not set!", key))
}
return
}
// ReadFirstLine gets the first line of a file that has contents and returns it
// if there are no contents, an empty string is returned
// will also return an empty string if the file does not exist
func ReadFirstLine(filename string) (text string) {
if !FileExists(filename) {
return
}
f, err := os.Open(filename)
if err != nil {
return
}
defer f.Close()
scanner := bufio.NewScanner(f)
for scanner.Scan() {
if text = strings.TrimSpace(scanner.Text()); text == "" {
continue
}
return
}
return
}
// StripInlineComments removes bash-style comment from input line
func StripInlineComments(text string) string {
bytes := []byte(text)
re := regexp.MustCompile("(?s)#.*")
bytes = re.ReplaceAll(bytes, nil)
return strings.TrimSpace(string(bytes))
}
// ToBool returns a bool value for a given string
func ToBool(s string) bool {
return s == "true"
}
// UcFirst uppercases the first character in a string
func UcFirst(str string) string {
for i, v := range str {
return string(unicode.ToUpper(v)) + str[i+1:]
}
return ""
}
// VerifyAppName verifies app name format and app existence"
func VerifyAppName(appName string) (err error) {
if appName == "" {
return fmt.Errorf("App name must not be null")
}
dokkuRoot := MustGetEnv("DOKKU_ROOT")
appRoot := strings.Join([]string{dokkuRoot, appName}, "/")
if !DirectoryExists(appRoot) {
return fmt.Errorf("app %s does not exist: %v", appName, err)
}
r, _ := regexp.Compile("^[a-z].*")
if !r.MatchString(appName) {
return fmt.Errorf("app name (%s) must begin with lowercase alphanumeric character", appName)
}
return err
}
// VerifyImage returns true if docker image exists in local repo
func VerifyImage(image string) bool {
imageCmd := NewShellCmd(strings.Join([]string{"docker inspect", image}, " "))
imageCmd.ShowOutput = false
if imageCmd.Execute() {
return true
}
return false
return imageCmd.Execute()
}
//PlugnTrigger fire the given plugn trigger with the given args

View File

@@ -1,38 +1,106 @@
package common
import (
"io/ioutil"
"os"
"strings"
"testing"
. "github.com/onsi/gomega"
)
func TestGetEnv(t *testing.T) {
var (
testAppName = "test-app-1"
testAppDir = strings.Join([]string{"/home/dokku/", testAppName}, "")
testEnvFile = strings.Join([]string{testAppDir, "/ENV"}, "")
testEnvLine = "export testKey=TESTING"
)
func setupTestApp() (err error) {
Expect(os.MkdirAll(testAppDir, 0644)).To(Succeed())
b := []byte(testEnvLine + "\n")
if err = ioutil.WriteFile(testEnvFile, b, 0644); err != nil {
return
}
return
}
func teardownTestApp() {
os.RemoveAll(testAppDir)
}
func TestCommonGetEnv(t *testing.T) {
RegisterTestingT(t)
Expect(MustGetEnv("DOKKU_ROOT")).To(Equal("/home/dokku"))
}
func TestGetAppImageRepo(t *testing.T) {
func TestCommonGetAppImageRepo(t *testing.T) {
RegisterTestingT(t)
Expect(GetAppImageRepo("testapp")).To(Equal("dokku/testapp"))
}
func TestVerifyImageInvalid(t *testing.T) {
func TestCommonVerifyImageInvalid(t *testing.T) {
RegisterTestingT(t)
Expect(VerifyImage("testapp")).To(Equal(false))
}
func TestVerifyAppNameInvalid(t *testing.T) {
func TestCommonVerifyAppNameInvalid(t *testing.T) {
RegisterTestingT(t)
err := VerifyAppName("1994testApp")
Expect(err).To(HaveOccurred())
}
func TestVerifyAppName(t *testing.T) {
func TestCommonVerifyAppName(t *testing.T) {
RegisterTestingT(t)
dir := "/home/dokku/testApp"
os.MkdirAll(dir, 0644)
err := VerifyAppName("testApp")
Expect(err).NotTo(HaveOccurred())
os.RemoveAll(dir)
Expect(setupTestApp()).To(Succeed())
Expect(VerifyAppName(testAppName)).To(Succeed())
teardownTestApp()
}
func TestCommonDokkuAppsError(t *testing.T) {
RegisterTestingT(t)
_, err := DokkuApps()
Expect(err).To(HaveOccurred())
}
func TestCommonDokkuApps(t *testing.T) {
RegisterTestingT(t)
Expect(setupTestApp()).To(Succeed())
apps, err := DokkuApps()
Expect(err).NotTo(HaveOccurred())
Expect(apps).To(HaveLen(1))
Expect(apps[0]).To(Equal(testAppName))
teardownTestApp()
}
func TestCommonFileToSlice(t *testing.T) {
RegisterTestingT(t)
Expect(setupTestApp()).To(Succeed())
lines, err := FileToSlice(testEnvFile)
Expect(err).NotTo(HaveOccurred())
Expect(lines).To(Equal([]string{testEnvLine}))
teardownTestApp()
}
func TestCommonFileExists(t *testing.T) {
RegisterTestingT(t)
Expect(setupTestApp()).To(Succeed())
Expect(FileExists(testEnvFile)).To(BeTrue())
teardownTestApp()
}
func TestCommonReadFirstLine(t *testing.T) {
RegisterTestingT(t)
line := ReadFirstLine(testEnvFile)
Expect(line).To(Equal(""))
Expect(setupTestApp()).To(Succeed())
line = ReadFirstLine(testEnvFile)
Expect(line).To(Equal(testEnvLine))
teardownTestApp()
}
func TestCommonStripInlineComments(t *testing.T) {
RegisterTestingT(t)
text := StripInlineComments(strings.Join([]string{testEnvLine, "# testing comment"}, " "))
Expect(text).To(Equal(testEnvLine))
}

View File

@@ -440,7 +440,7 @@ dokku_build() {
source "$PLUGIN_AVAILABLE_PATH/config/functions"
local APP="$1"; local IMAGE_SOURCE_TYPE="$2"; local TMP_WORK_DIR="$3"; local IMAGE=$(get_app_image_name "$APP")
local id
local cid
verify_app_name "$APP"
local CACHE_DIR="$DOKKU_ROOT/$APP/cache"
@@ -451,9 +451,9 @@ dokku_build() {
case "$IMAGE_SOURCE_TYPE" in
herokuish)
DOKKU_IMAGE="$(config_get "$APP" DOKKU_IMAGE || echo "$DOKKU_IMAGE")"
local id=$(tar -c . | docker run "$DOKKU_GLOBAL_RUN_ARGS" -i -a stdin "$DOKKU_IMAGE" /bin/bash -c "mkdir -p /app && tar -xC /app")
test "$(docker wait "$id")" -eq 0
docker commit "$id" "$IMAGE" > /dev/null
cid=$(tar -c . | docker run "$DOKKU_GLOBAL_RUN_ARGS" -i -a stdin "$DOKKU_IMAGE" /bin/bash -c "mkdir -p /app && tar -xC /app")
test "$(docker wait "$cid")" -eq 0
docker commit "$cid" "$IMAGE" > /dev/null
[[ -d $CACHE_DIR ]] || mkdir -p "$CACHE_DIR"
plugn trigger pre-build-buildpack "$APP"
@@ -462,10 +462,10 @@ dokku_build() {
declare -a ARG_ARRAY
eval "ARG_ARRAY=($DOCKER_ARGS)"
# shellcheck disable=SC2086
id=$(docker run $DOKKU_GLOBAL_RUN_ARGS -d -v $CACHE_DIR:/cache -e CACHE_PATH=/cache "${ARG_ARRAY[@]}" $IMAGE /build)
docker attach "$id"
test "$(docker wait "$id")" -eq 0
docker commit "$id" "$IMAGE" > /dev/null
cid=$(docker run $DOKKU_GLOBAL_RUN_ARGS -d -v $CACHE_DIR:/cache -e CACHE_PATH=/cache "${ARG_ARRAY[@]}" $IMAGE /build)
docker attach "$cid"
test "$(docker wait "$cid")" -eq 0
docker commit "$cid" "$IMAGE" > /dev/null
plugn trigger post-build-buildpack "$APP"
;;
@@ -506,20 +506,21 @@ dokku_release() {
source "$PLUGIN_AVAILABLE_PATH/config/functions"
local APP="$1"; local IMAGE_SOURCE_TYPE="$2"; local IMAGE_TAG="$3"; local IMAGE=$(get_app_image_name "$APP" "$IMAGE_TAG")
local cid
verify_app_name "$APP"
case "$IMAGE_SOURCE_TYPE" in
herokuish)
plugn trigger pre-release-buildpack "$APP" "$IMAGE_TAG"
if [[ -n $(config_export global) ]]; then
local id=$(config_export global | docker run "$DOKKU_GLOBAL_RUN_ARGS" -i -a stdin "$IMAGE" /bin/bash -c "mkdir -p /app/.profile.d && cat > /app/.profile.d/00-global-env.sh")
test "$(docker wait "$id")" -eq 0
docker commit "$id" "$IMAGE" > /dev/null
cid=$(config_export global | docker run "$DOKKU_GLOBAL_RUN_ARGS" -i -a stdin "$IMAGE" /bin/bash -c "mkdir -p /app/.profile.d && cat > /app/.profile.d/00-global-env.sh")
test "$(docker wait "$cid")" -eq 0
docker commit "$cid" "$IMAGE" > /dev/null
fi
if [[ -n $(config_export app "$APP") ]]; then
local id=$(config_export app "$APP" | docker run "$DOKKU_GLOBAL_RUN_ARGS" -i -a stdin "$IMAGE" /bin/bash -c "mkdir -p /app/.profile.d && cat > /app/.profile.d/01-app-env.sh")
test "$(docker wait "$id")" -eq 0
docker commit "$id" "$IMAGE" > /dev/null
cid=$(config_export app "$APP" | docker run "$DOKKU_GLOBAL_RUN_ARGS" -i -a stdin "$IMAGE" /bin/bash -c "mkdir -p /app/.profile.d && cat > /app/.profile.d/01-app-env.sh")
test "$(docker wait "$cid")" -eq 0
docker commit "$cid" "$IMAGE" > /dev/null
fi
plugn trigger post-release-buildpack "$APP" "$IMAGE_TAG"
;;
@@ -541,19 +542,21 @@ dokku_deploy_cmd() {
local cmd="deploy"
source "$PLUGIN_AVAILABLE_PATH/checks/functions"
source "$PLUGIN_AVAILABLE_PATH/config/functions"
source "$PLUGIN_AVAILABLE_PATH/proxy/functions"
[[ -z $1 ]] && dokku_log_fail "Please specify an app to run the command on"
local APP="$1"; local IMAGE_TAG="$2"; local IMAGE=$(get_deploying_app_image_name "$APP" "$IMAGE_TAG")
local APP="$1" IMAGE_TAG="$2"
local DOKKU_DOCKER_STOP_TIMEOUT DOKKU_HEROKUISH DOKKU_NETWORK_BIND_ALL IMAGE
DOKKU_HEROKUISH=false
IMAGE=$(get_deploying_app_image_name "$APP" "$IMAGE_TAG")
verify_app_name "$APP"
plugn trigger pre-deploy "$APP" "$IMAGE_TAG"
is_image_herokuish_based "$IMAGE" && local DOKKU_HEROKUISH=true
is_image_herokuish_based "$IMAGE" && DOKKU_HEROKUISH=true
local DOKKU_SCALE_FILE="$DOKKU_ROOT/$APP/DOKKU_SCALE"
local oldids=$(get_app_container_ids "$APP")
local DOKKU_IS_APP_PROXY_ENABLED="$(is_app_proxy_enabled "$APP")"
local DOKKU_DOCKER_STOP_TIMEOUT="$(config_get "$APP" DOKKU_DOCKER_STOP_TIMEOUT || true)"
DOKKU_NETWORK_BIND_ALL="$(plugn trigger network-get-property "$APP" bind-all-interfaces)"
DOKKU_DOCKER_STOP_TIMEOUT="$(config_get "$APP" DOKKU_DOCKER_STOP_TIMEOUT || true)"
[[ $DOKKU_DOCKER_STOP_TIMEOUT ]] && DOCKER_STOP_TIME_ARG="--time=${DOKKU_DOCKER_STOP_TIMEOUT}"
local line; local PROC_TYPE; local PROC_COUNT; local CONTAINER_INDEX
@@ -577,10 +580,8 @@ dokku_deploy_cmd() {
fi
while [[ $CONTAINER_INDEX -le $PROC_COUNT ]]; do
local id=""; local port=""; local ipaddr=""
local cid=""; local port=""; local ipaddr=""
local DOKKU_CONTAINER_ID_FILE="$DOKKU_ROOT/$APP/CONTAINER.$PROC_TYPE.$CONTAINER_INDEX"
local DOKKU_IP_FILE="$DOKKU_ROOT/$APP/IP.$PROC_TYPE.$CONTAINER_INDEX"
local DOKKU_PORT_FILE="$DOKKU_ROOT/$APP/PORT.$PROC_TYPE.$CONTAINER_INDEX"
# start the app
local DOCKER_ARGS
@@ -591,69 +592,60 @@ dokku_deploy_cmd() {
declare -a ARG_ARRAY
eval "ARG_ARRAY=($DOCKER_ARGS)"
[[ -n "$DOKKU_HEROKUISH" ]] && local START_CMD="/start $PROC_TYPE"
[[ "$DOKKU_HEROKUISH" == "true" ]] && local START_CMD="/start $PROC_TYPE"
if [[ -z "$DOKKU_HEROKUISH" ]]; then
local DOKKU_DOCKERFILE_PORTS=($(config_get "$APP" DOKKU_DOCKERFILE_PORTS || true))
if [[ "$DOKKU_HEROKUISH" == "false" ]]; then
local DOKKU_DOCKERFILE_START_CMD=$(config_get "$APP" DOKKU_DOCKERFILE_START_CMD || true)
local DOKKU_PROCFILE_START_CMD=$(get_cmd_from_procfile "$APP" "$PROC_TYPE")
local START_CMD=${DOKKU_DOCKERFILE_START_CMD:-$DOKKU_PROCFILE_START_CMD}
fi
if [[ "$PROC_TYPE" == "web" ]]; then
if [[ -z "${DOKKU_DOCKERFILE_PORTS[*]}" ]]; then
local port=5000
local DOKKU_DOCKER_PORT_ARGS+="-p $port"
else
local p
for p in ${DOKKU_DOCKERFILE_PORTS[*]};do
if [[ ! "$p" =~ .*udp.* ]]; then
# set port to first non-udp port
local p=${p//\/tcp}
local port=${port:="$p"}
fi
local DOKKU_DOCKER_PORT_ARGS+=" -p $p "
done
fi
if [[ "$DOKKU_IS_APP_PROXY_ENABLED" == "true" ]]; then
# shellcheck disable=SC2086
id=$(docker run $DOKKU_GLOBAL_RUN_ARGS -d -e PORT=$port "${ARG_ARRAY[@]}" $IMAGE $START_CMD)
local ipaddr=$(docker inspect --format='{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' "$id")
# Docker < 1.9 compatibility
if [[ -z $ipaddr ]]; then
local ipaddr=$(docker inspect --format '{{ .NetworkSettings.IPAddress }}' "$id")
ports=($(plugn trigger network-compute-ports "$APP" "$PROC_TYPE" "$DOKKU_HEROKUISH"))
local DOKKU_DOCKER_PORT_ARGS=""
local DOKKU_PORT=""
for p in "${ports[@]}"; do
if [[ ! "$p" =~ .*udp.* ]]; then
DOKKU_PORT=${DOKKU_PORT:="$p"}
fi
DOKKU_DOCKER_PORT_ARGS+=" -p $p "
done
if [[ "$DOKKU_NETWORK_BIND_ALL" == "false" ]]; then
# shellcheck disable=SC2086
cid=$(docker run $DOKKU_GLOBAL_RUN_ARGS -d -e PORT=$DOKKU_PORT "${ARG_ARRAY[@]}" $IMAGE $START_CMD)
else
# shellcheck disable=SC2086
id=$(docker run $DOKKU_GLOBAL_RUN_ARGS -d $DOKKU_DOCKER_PORT_ARGS -e PORT=$port "${ARG_ARRAY[@]}" $IMAGE $START_CMD)
local port=$(docker port "$id" "$port" | sed 's/[0-9.]*://')
local ipaddr=127.0.0.1
cid=$(docker run $DOKKU_GLOBAL_RUN_ARGS -d $DOKKU_DOCKER_PORT_ARGS -e PORT=$DOKKU_PORT "${ARG_ARRAY[@]}" $IMAGE $START_CMD)
fi
else
# shellcheck disable=SC2086
id=$(docker run $DOKKU_GLOBAL_RUN_ARGS -d "${ARG_ARRAY[@]}" $IMAGE $START_CMD)
cid=$(docker run $DOKKU_GLOBAL_RUN_ARGS -d "${ARG_ARRAY[@]}" $IMAGE $START_CMD)
fi
ipaddr=$(plugn trigger network-get-ipaddr "$APP" "$PROC_TYPE" "$cid")
port=$(plugn trigger network-get-port "$APP" "$PROC_TYPE" "$DOKKU_HEROKUISH" "$cid")
kill_new() {
declare desc="wrapper function to kill newly started app container"
local id="$1"
docker inspect "$id" &> /dev/null && docker stop "$id" > /dev/null && docker kill "$id" &> /dev/null
local cid="$1"
docker inspect "$cid" &> /dev/null && docker stop "$cid" > /dev/null && docker kill "$cid" &> /dev/null
trap - INT TERM EXIT
kill -9 $$
}
# run checks first, then post-deploy hooks, which switches proxy traffic
trap 'kill_new $id' INT TERM EXIT
trap 'kill_new $cid' INT TERM EXIT
if [[ "$(is_app_proctype_checks_disabled "$APP" "$PROC_TYPE")" == "false" ]]; then
dokku_log_info1 "Attempting pre-flight checks"
plugn trigger check-deploy "$APP" "$id" "$PROC_TYPE" "$port" "$ipaddr"
plugn trigger check-deploy "$APP" "$cid" "$PROC_TYPE" "$port" "$ipaddr"
fi
trap - INT TERM EXIT
# now using the new container
[[ -n "$id" ]] && echo "$id" > "$DOKKU_CONTAINER_ID_FILE"
[[ -n "$ipaddr" ]] && echo "$ipaddr" > "$DOKKU_IP_FILE"
[[ -n "$port" ]] && echo "$port" > "$DOKKU_PORT_FILE"
[[ -n "$cid" ]] && echo "$cid" > "$DOKKU_CONTAINER_ID_FILE"
[[ -n "$ipaddr" ]] && plugn trigger network-write-ipaddr "$APP" "$PROC_TYPE" "$CONTAINER_INDEX" "$ipaddr"
[[ -n "$port" ]] && plugn trigger network-write-port "$APP" "$PROC_TYPE" "$CONTAINER_INDEX" "$port"
# cleanup pre-migration files
rm -f "$DOKKU_ROOT/$APP/CONTAINER" "$DOKKU_ROOT/$APP/IP" "$DOKKU_ROOT/$APP/PORT"
@@ -1054,9 +1046,11 @@ get_app_urls() {
echo "$(< "$DOKKU_ROOT/HOSTNAME"):$app_port (container)"
done
else
shopt -s nullglob
for PORT_FILE in $DOKKU_ROOT/$APP/PORT.*; do
echo "$SCHEME://$(< "$DOKKU_ROOT/HOSTNAME"):$(< "$PORT_FILE") (container)"
local DOKKU_APP_LISTENERS PORT
DOKKU_APP_LISTENERS="$(plugn trigger network-get-listeners "$APP" | xargs)"
for DOKKU_APP_LISTENER in $DOKKU_APP_LISTENERS; do
PORT="$(echo "$DOKKU_APP_LISTENER" | cut -d ':' -f2)"
echo "$SCHEME://$(< "$DOKKU_ROOT/HOSTNAME"):$PORT (container)"
done
shopt -u nullglob
fi

56
plugins/common/log.go Normal file
View File

@@ -0,0 +1,56 @@
package common
import (
"fmt"
"os"
)
// LogFail is the failure log formatter
// prints text to stderr and exits with status 1
func LogFail(text string) {
fmt.Fprintln(os.Stderr, fmt.Sprintf("FAILED: %s", text))
os.Exit(1)
}
// LogInfo1 is the info1 header formatter
func LogInfo1(text string) {
fmt.Fprintln(os.Stdout, fmt.Sprintf("-----> %s", text))
}
// LogInfo1Quiet is the info1 header formatter (with quiet option)
func LogInfo1Quiet(text string) {
if os.Getenv("DOKKU_QUIET_OUTPUT") != "" {
LogInfo1(text)
}
}
// LogInfo2 is the info2 header formatter
func LogInfo2(text string) {
fmt.Fprintln(os.Stdout, fmt.Sprintf("=====> %s", text))
}
// LogInfo2Quiet is the info2 header formatter (with quiet option)
func LogInfo2Quiet(text string) {
if os.Getenv("DOKKU_QUIET_OUTPUT") == "" {
LogInfo2(text)
}
}
// LogVerbose is the verbose log formatter
// prints indented text to stdout
func LogVerbose(text string) {
fmt.Fprintln(os.Stdout, fmt.Sprintf(" %s", text))
}
// LogVerboseQuiet is the verbose log formatter
// prints indented text to stdout (with quiet option)
func LogVerboseQuiet(text string) {
if os.Getenv("DOKKU_QUIET_OUTPUT") != "" {
LogVerbose(text)
}
}
// LogWarn is the warning log formatter
func LogWarn(text string) {
fmt.Fprintln(os.Stderr, fmt.Sprintf(" ! %s", text))
}

View File

@@ -1,4 +1,4 @@
[plugin]
description = "dokku core common plugin"
version = "0.10.4"
version = "0.10.5"
[plugin.config]

View File

@@ -0,0 +1,164 @@
package common
import (
"fmt"
"io/ioutil"
"os"
"os/user"
"reflect"
"strconv"
"strings"
)
// CommandPropertySet is a generic function that will set a property for a given plugin/app combination
func CommandPropertySet(pluginName, appName, property, value string, properties map[string]string) {
if err := VerifyAppName(appName); err != nil {
LogFail(err.Error())
}
if property == "" {
LogFail("No property specified")
}
if _, ok := properties[property]; !ok {
properties := reflect.ValueOf(properties).MapKeys()
validPropertyList := make([]string, len(properties))
for i := 0; i < len(properties); i++ {
validPropertyList[i] = properties[i].String()
}
LogFail(fmt.Sprintf("Invalid property specified, valid properties include: %s", strings.Join(validPropertyList, ", ")))
}
if value != "" {
LogInfo2Quiet(fmt.Sprintf("Setting %s to %s", property, value))
PropertyWrite(pluginName, appName, property, value)
} else {
LogInfo2Quiet(fmt.Sprintf("Unsetting %s", property))
PropertyDelete(pluginName, appName, property)
}
}
// PropertyDelete deletes a property from the plugin properties for an app
func PropertyDelete(pluginName string, appName string, property string) {
pluginAppConfigRoot := getPluginAppPropertyPath(pluginName, appName)
propertyPath := strings.Join([]string{pluginAppConfigRoot, property}, "/")
if err := os.Remove(propertyPath); err != nil {
LogFail(fmt.Sprintf("Unable to remove %s property %s.%s", pluginName, appName, property))
}
}
// PropertyDestroy destroys the plugin properties for an app
func PropertyDestroy(pluginName string, appName string) {
if appName == "_all_" {
pluginConfigPath := getPluginConfigPath(pluginName)
os.RemoveAll(pluginConfigPath)
} else {
pluginAppConfigRoot := getPluginAppPropertyPath(pluginName, appName)
os.RemoveAll(pluginAppConfigRoot)
}
}
// PropertyExists returns whether a property exists or not
func PropertyExists(pluginName string, appName string, property string) bool {
pluginAppConfigRoot := getPluginAppPropertyPath(pluginName, appName)
propertyPath := strings.Join([]string{pluginAppConfigRoot, property}, "/")
_, err := os.Stat(propertyPath)
return !os.IsNotExist(err)
}
// PropertyGet returns the value for a given property
func PropertyGet(pluginName string, appName string, property string) string {
return PropertyGetDefault(pluginName, appName, property, "")
}
// 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) {
return
}
pluginAppConfigRoot := getPluginAppPropertyPath(pluginName, appName)
propertyPath := strings.Join([]string{pluginAppConfigRoot, property}, "/")
b, err := ioutil.ReadFile(propertyPath)
if err != nil {
LogWarn(fmt.Sprintf("Unable to read %s property %s.%s", pluginName, appName, property))
return
}
val = string(b)
return
}
// PropertyWrite writes a value for a given application property
func PropertyWrite(pluginName string, appName string, property string, value string) {
if err := makePropertyPath(pluginName, appName); err != nil {
LogFail(fmt.Sprintf("Unable to create %s config directory for %s: %s", pluginName, appName, err.Error()))
}
pluginAppConfigRoot := getPluginAppPropertyPath(pluginName, appName)
propertyPath := strings.Join([]string{pluginAppConfigRoot, property}, "/")
file, err := os.Create(propertyPath)
if err != nil {
LogFail(fmt.Sprintf("Unable to write %s config value %s.%s: %s", pluginName, appName, property, err.Error()))
}
defer file.Close()
fmt.Fprintf(file, value)
file.Chmod(0600)
setPermissions(propertyPath, 0600)
}
// PropertySetup creates the plugin config root
func PropertySetup(pluginName string) (err error) {
pluginConfigRoot := getPluginConfigPath(pluginName)
if err = os.MkdirAll(pluginConfigRoot, 0755); err != nil {
return
}
return setPermissions(pluginConfigRoot, 0755)
}
// getPluginAppPropertyPath returns the plugin property path for a given plugin/app combination
func getPluginAppPropertyPath(pluginName string, appName string) string {
return strings.Join([]string{getPluginConfigPath(pluginName), appName}, "/")
}
// getPluginConfigPath returns the plugin property path for a given plugin
func getPluginConfigPath(pluginName string) string {
return strings.Join([]string{MustGetEnv("DOKKU_LIB_ROOT"), "config", pluginName}, "/")
}
// makePropertyPath ensures that a property path exists
func makePropertyPath(pluginName string, appName string) (err error) {
pluginAppConfigRoot := getPluginAppPropertyPath(pluginName, appName)
if err = os.MkdirAll(pluginAppConfigRoot, 0755); err != nil {
return
}
return setPermissions(pluginAppConfigRoot, 0755)
}
// setPermissions sets the proper owner and filemode for a given file
func setPermissions(path string, fileMode os.FileMode) (err error) {
if err = os.Chmod(path, fileMode); err != nil {
return err
}
group, err := user.LookupGroup("dokku")
if err != nil {
return
}
user, err := user.Lookup("dokku")
if err != nil {
return
}
uid, err := strconv.Atoi(user.Uid)
if err != nil {
return
}
gid, err := strconv.Atoi(group.Gid)
if err != nil {
return
}
return os.Chown(path, uid, gid)
}

View File

@@ -17,6 +17,28 @@ func GetWithDefault(appName string, key string, defaultValue string) string {
return env.GetDefault(key, defaultValue)
}
// GetWithDefault returns the value set for a given key, returning defaultValue if none found
// func GetWithDefault(appName string, key string, defaultValue string) (value string) {
// value = defaultValue
// envFile := strings.Join([]string{common.MustGetEnv("DOKKU_ROOT"), appName, "ENV"}, "/")
// lines, err := common.FileToSlice(envFile)
// if err != nil {
// return
// }
// prefix := fmt.Sprintf("export %v=", key)
// for _, line := range lines {
// if !strings.HasPrefix(line, prefix) {
// continue
// }
// value = strings.TrimPrefix(line, prefix)
// if strings.HasPrefix(value, "'") && strings.HasSuffix(value, "'") {
// value = strings.TrimPrefix(strings.TrimSuffix(value, "'"), "'")
// }
// }
// return
// }
//HasKey determines if the config given by appName has a value for the given key
func HasKey(appName string, key string) bool {
env, err := loadConfig(appName)

View File

@@ -0,0 +1,36 @@
package config
import (
"io/ioutil"
"os"
"strings"
"testing"
. "github.com/onsi/gomega"
)
var (
testAppName = "test-app-1"
testAppDir = strings.Join([]string{"/home/dokku/", testAppName}, "")
)
func setupTestApp() (err error) {
Expect(os.MkdirAll(testAppDir, 0644)).To(Succeed())
b := []byte("export testKey=TESTING\n")
if err = ioutil.WriteFile(strings.Join([]string{testAppDir, "/ENV"}, ""), b, 0644); err != nil {
return
}
return
}
func teardownTestApp() {
os.RemoveAll(testAppDir)
}
func TestConfigGetWithDefault(t *testing.T) {
RegisterTestingT(t)
Expect(setupTestApp()).To(Succeed())
Expect(GetWithDefault(testAppName, "unknownKey", "UNKNOWN")).To(Equal("UNKNOWN"))
Expect(GetWithDefault(testAppName, "testKey", "testKey")).To(Equal("TESTING"))
teardownTestApp()
}

View File

@@ -1,4 +1,4 @@
[plugin]
description = "dokku core config plugin"
version = "0.10.4"
version = "0.10.5"
[plugin.config]

View File

@@ -1,4 +1,4 @@
[plugin]
description = "dokku core docker-options plugin"
version = "0.10.4"
version = "0.10.5"
[plugin.config]

View File

@@ -1,4 +1,4 @@
[plugin]
description = "dokku core domains plugin"
version = "0.10.4"
version = "0.10.5"
[plugin.config]

View File

@@ -1,4 +1,4 @@
[plugin]
description = "dokku core enter plugin"
version = "0.10.4"
version = "0.10.5"
[plugin.config]

View File

@@ -1,4 +1,4 @@
[plugin]
description = "dokku core git plugin"
version = "0.10.4"
version = "0.10.5"
[plugin.config]

View File

@@ -1,4 +1,4 @@
[plugin]
description = "dokku core logs plugin"
version = "0.10.4"
version = "0.10.5"
[plugin.config]

View File

@@ -1,4 +1,4 @@
[plugin]
description = "dokku core named containers plugin"
version = "0.10.4"
version = "0.10.5"
[plugin.config]

6
plugins/network/.gitignore vendored Normal file
View File

@@ -0,0 +1,6 @@
/commands
/subcommands/*
/network-*
/install
/post-create
/post-delete

68
plugins/network/Makefile Normal file
View File

@@ -0,0 +1,68 @@
include ../../common.mk
build-in-docker: clean
docker run --rm \
-v $$PWD/../..:$(GO_REPO_ROOT) \
-w $(GO_REPO_ROOT)/plugins/network \
$(BUILD_IMAGE) \
bash -c "make build" || exit $$?
build: commands subcommands triggers
subcommands: subcommands/rebuild subcommands/rebuildall subcommands/report subcommands/set
triggers: install network-build-config network-compute-ports network-config-exists network-get-ipaddr network-get-listeners network-get-port network-get-property network-write-ipaddr network-write-port post-create post-delete
commands: **/**/commands.go
go build -a -o commands src/commands/commands.go
subcommands/rebuild: **/**/**/rebuild.go
go build -a -o subcommands/rebuild src/subcommands/rebuild/rebuild.go
subcommands/rebuildall: **/**/**/rebuildall.go
go build -a -o subcommands/rebuildall src/subcommands/rebuildall/rebuildall.go
subcommands/report: **/**/**/report.go
go build -a -o subcommands/report src/subcommands/report/report.go
subcommands/set: **/**/**/set.go
go build -a -o subcommands/set src/subcommands/set/set.go
install: **/**/**/install.go
go build -a -o install src/triggers/install/install.go
network-build-config: **/**/**/network-build-config.go
go build -a -o network-build-config src/triggers/network-build-config/network-build-config.go
network-compute-ports: **/**/**/network-compute-ports.go
go build -a -o network-compute-ports src/triggers/network-compute-ports/network-compute-ports.go
network-config-exists: **/**/**/network-config-exists.go
go build -a -o network-config-exists src/triggers/network-config-exists/network-config-exists.go
network-get-ipaddr: **/**/**/network-get-ipaddr.go
go build -a -o network-get-ipaddr src/triggers/network-get-ipaddr/network-get-ipaddr.go
network-get-listeners: **/**/**/network-get-listeners.go
go build -a -o network-get-listeners src/triggers/network-get-listeners/network-get-listeners.go
network-get-port: **/**/**/network-get-port.go
go build -a -o network-get-port src/triggers/network-get-port/network-get-port.go
network-get-property: **/**/**/network-get-property.go
go build -a -o network-get-property src/triggers/network-get-property/network-get-property.go
network-write-ipaddr: **/**/**/network-write-ipaddr.go
go build -a -o network-write-ipaddr src/triggers/network-write-ipaddr/network-write-ipaddr.go
network-write-port: **/**/**/network-write-port.go
go build -a -o network-write-port src/triggers/network-write-port/network-write-port.go
post-create: **/**/**/post-create.go
go build -a -o post-create src/triggers/post-create/post-create.go
post-delete: **/**/**/post-delete.go
go build -a -o post-delete src/triggers/post-delete/post-delete.go
clean:
rm -rf commands subcommands network-* install post-create post-delete
src-clean:
rm -rf .gitignore src vendor Makefile

View File

@@ -0,0 +1,4 @@
package: .
import:
- package: github.com/codeskyblue/go-sh
- package: github.com/ryanuber/columnize

176
plugins/network/network.go Normal file
View File

@@ -0,0 +1,176 @@
package network
import (
"fmt"
"path/filepath"
"strconv"
"strings"
"github.com/dokku/dokku/plugins/common"
"github.com/dokku/dokku/plugins/config"
sh "github.com/codeskyblue/go-sh"
)
var (
// DefaultProperties is a map of all valid network properties with corresponding default property values
DefaultProperties = map[string]string{
"bind-all-interfaces": "false",
}
)
// BuildConfig builds network config files
func BuildConfig(appName string) {
if err := common.VerifyAppName(appName); err != nil {
common.LogFail(err.Error())
}
if !common.IsDeployed(appName) {
return
}
appRoot := strings.Join([]string{common.MustGetEnv("DOKKU_ROOT"), appName}, "/")
scaleFile := strings.Join([]string{appRoot, "DOKKU_SCALE"}, "/")
if !common.FileExists(scaleFile) {
return
}
image := common.GetAppImageName(appName, "", "")
isHerokuishContainer := common.IsImageHerokuishBased(image)
common.LogInfo1(fmt.Sprintf("Ensuring network configuration is in sync for %s", appName))
lines, err := common.FileToSlice(scaleFile)
if err != nil {
return
}
for _, line := range lines {
if line == "" || strings.HasPrefix(line, "#") {
continue
}
procParts := strings.SplitN(line, "=", 2)
if len(procParts) != 2 {
continue
}
procType := procParts[0]
procCount, err := strconv.Atoi(procParts[1])
if err != nil {
continue
}
containerIndex := 0
for containerIndex < procCount {
containerIndex++
containerIndexString := strconv.Itoa(containerIndex)
containerIDFile := fmt.Sprintf("%v/CONTAINER.%v.%v", appRoot, procType, containerIndex)
containerID := common.ReadFirstLine(containerIDFile)
if containerID == "" || !common.ContainerIsRunning(containerID) {
continue
}
ipAddress := GetContainerIpaddress(appName, procType, containerID)
port := GetContainerPort(appName, procType, isHerokuishContainer, containerID)
if ipAddress != "" {
_, err := sh.Command("plugn", "trigger", "network-write-ipaddr", appName, procType, 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()
if err != nil {
common.LogWarn(err.Error())
}
}
}
}
}
// GetContainerIpaddress returns the ipaddr for a given app container
func GetContainerIpaddress(appName, procType, containerID string) (ipAddr string) {
if procType != "web" {
return
}
b, err := common.DockerInspect(containerID, "'{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}'")
if err != nil || len(b) == 0 {
// docker < 1.9 compatibility
b, err = common.DockerInspect(containerID, "'{{ .NetworkSettings.IPAddress }}'")
}
if err == nil {
return string(b[:])
}
return
}
// GetContainerPort returns the port for a given app container
func GetContainerPort(appName, procType string, isHerokuishContainer bool, containerID string) (port string) {
if procType != "web" {
return
}
dockerfilePorts := make([]string, 0)
if !isHerokuishContainer {
configValue := config.GetWithDefault(appName, "DOKKU_DOCKERFILE_PORTS", "")
if configValue != "" {
dockerfilePorts = strings.Split(configValue, " ")
}
}
if len(dockerfilePorts) > 0 {
for _, p := range dockerfilePorts {
if strings.HasSuffix(p, "/udp") {
continue
}
port = strings.TrimSuffix(p, "/tcp")
if port != "" {
break
}
}
b, err := sh.Command("docker", "port", containerID, port).Output()
if err == nil {
port = strings.Split(string(b[:]), ":")[1]
}
} else {
port = "5000"
}
return
}
// GetDefaultValue returns the default value for a given property
func GetDefaultValue(property string) (value string) {
value, ok := DefaultProperties[property]
if ok {
return
}
return
}
// GetListeners returns a string array of app listeners
func GetListeners(appName string) []string {
dokkuRoot := common.MustGetEnv("DOKKU_ROOT")
appRoot := strings.Join([]string{dokkuRoot, appName}, "/")
files, _ := filepath.Glob(appRoot + "/IP.web.*")
var listeners []string
for _, ipfile := range files {
portfile := strings.Replace(ipfile, "/IP.web.", "/PORT.web.", 1)
ipAddress := common.ReadFirstLine(ipfile)
port := common.ReadFirstLine(portfile)
listeners = append(listeners, fmt.Sprintf("%s:%s", ipAddress, port))
}
return listeners
}
// HasNetworkConfig returns whether the network configuration for a given app exists
func HasNetworkConfig(appName string) bool {
appRoot := strings.Join([]string{common.MustGetEnv("DOKKU_ROOT"), appName}, "/")
ipfile := fmt.Sprintf("%v/IP.web.1", appRoot)
portfile := fmt.Sprintf("%v/PORT.web.1", appRoot)
return common.FileExists(ipfile) && common.FileExists(portfile)
}

View File

@@ -0,0 +1,12 @@
package network
import (
"testing"
. "github.com/onsi/gomega"
)
func TestNetworkGetDefaultValue(t *testing.T) {
RegisterTestingT(t)
Expect(GetDefaultValue("bind-all-interfaces")).To(Equal("false"))
}

View File

@@ -0,0 +1,4 @@
[plugin]
description = "dokku core network plugin"
version = "0.9.4"
[plugin.config]

View File

@@ -0,0 +1,56 @@
package main
import (
"flag"
"fmt"
"os"
"strconv"
"strings"
columnize "github.com/ryanuber/columnize"
)
const (
helpHeader = `Usage: dokku network[:COMMAND]
Manages network settings for an app
Additional commands:`
helpContent = `
network:report [<app>] [<flag>], Displays a network report for one or more apps
network:rebuild <app>, Rebuilds network settings for an app
network:rebuildall, Rebuild network settings for all apps
network:set <app> <property> (<value>), Set or clear a network property for an app
`
)
func main() {
flag.Usage = usage
flag.Parse()
cmd := flag.Arg(0)
switch cmd {
case "network", "network:help":
usage()
case "help":
fmt.Print(helpContent)
default:
dokkuNotImplementExitCode, err := strconv.Atoi(os.Getenv("DOKKU_NOT_IMPLEMENTED_EXIT"))
if err != nil {
fmt.Println("failed to retrieve DOKKU_NOT_IMPLEMENTED_EXIT environment variable")
dokkuNotImplementExitCode = 10
}
os.Exit(dokkuNotImplementExitCode)
}
}
func usage() {
config := columnize.DefaultConfig()
config.Delim = ","
config.Prefix = "\t"
config.Empty = ""
content := strings.Split(helpContent, "\n")[1:]
fmt.Println(helpHeader)
fmt.Println(columnize.Format(content, config))
}

6
plugins/network/src/glide.lock generated Normal file
View 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: []

View File

@@ -0,0 +1,3 @@
package: .
import:
- package: github.com/ryanuber/columnize

View File

@@ -0,0 +1,15 @@
package main
import (
"flag"
"github.com/dokku/dokku/plugins/network"
)
// rebuilds network settings for an app
func main() {
flag.Parse()
appName := flag.Arg(1)
network.BuildConfig(appName)
}

View File

@@ -0,0 +1,17 @@
package main
import (
"github.com/dokku/dokku/plugins/common"
"github.com/dokku/dokku/plugins/network"
)
// rebuilds network settings for all apps
func main() {
apps, err := common.DokkuApps()
if err != nil {
common.LogFail(err.Error())
}
for _, appName := range apps {
network.BuildConfig(appName)
}
}

View File

@@ -0,0 +1,67 @@
package main
import (
"flag"
"fmt"
"os"
"reflect"
"strings"
"github.com/dokku/dokku/plugins/common"
"github.com/dokku/dokku/plugins/network"
)
func reportSingleApp(appName, infoFlag string) {
infoFlags := map[string]string{
"--network-bind-all-interfaces": common.PropertyGet("network", appName, "bind-all-interfaces"),
"--network-listeners": strings.Join(network.GetListeners(appName), " "),
}
if len(infoFlag) == 0 {
common.LogInfo2Quiet(fmt.Sprintf("%s network information", appName))
for k, v := range infoFlags {
key := common.UcFirst(strings.Replace(strings.TrimPrefix(k, "--"), "-", " ", -1))
common.LogVerbose(fmt.Sprintf("%s: %s", key, v))
}
return
}
for k, v := range infoFlags {
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, ", ")))
}
// set or clear a network property for an app
func main() {
flag.Parse()
appName := flag.Arg(1)
infoFlag := flag.Arg(2)
if strings.HasPrefix(appName, "--") {
infoFlag = appName
appName = ""
}
if len(appName) == 0 {
apps, err := common.DokkuApps()
if err != nil {
return
}
for _, appName := range apps {
reportSingleApp(appName, infoFlag)
}
return
}
reportSingleApp(appName, infoFlag)
}

View File

@@ -0,0 +1,22 @@
package main
import (
"flag"
"github.com/dokku/dokku/plugins/common"
"github.com/dokku/dokku/plugins/network"
)
// set or clear a network property for an app
func main() {
flag.Parse()
appName := flag.Arg(1)
property := flag.Arg(2)
value := flag.Arg(3)
if property == "bind-all-interfaces" && value == "" {
value = "false"
}
common.CommandPropertySet("network", appName, property, value, network.DefaultProperties)
}

View File

@@ -0,0 +1,32 @@
package main
import (
"fmt"
"github.com/dokku/dokku/plugins/common"
"github.com/dokku/dokku/plugins/proxy"
)
// runs the install step for the network plugin
func main() {
if err := common.PropertySetup("network"); err != nil {
common.LogFail(fmt.Sprintf("Unable to install the network plugin: %s", err.Error()))
}
apps, err := common.DokkuApps()
if err != nil {
return
}
for _, appName := range apps {
if common.PropertyExists("network", appName, "bind-all-interfaces") {
continue
}
if proxy.IsAppProxyEnabled(appName) {
common.LogVerboseQuiet("Setting %s network property 'bind-all-interfaces' to false")
common.PropertyWrite("network", appName, "bind-all-interfaces", "false")
} else {
common.LogVerboseQuiet("Setting %s network property 'bind-all-interfaces' to true")
common.PropertyWrite("network", appName, "bind-all-interfaces", "true")
}
}
}

View File

@@ -0,0 +1,15 @@
package main
import (
"flag"
"github.com/dokku/dokku/plugins/network"
)
// rebuilds network settings for an app
func main() {
flag.Parse()
appName := flag.Arg(0)
network.BuildConfig(appName)
}

View File

@@ -0,0 +1,42 @@
package main
import (
"flag"
"fmt"
"os"
"strings"
"github.com/dokku/dokku/plugins/common"
"github.com/dokku/dokku/plugins/config"
)
// computes the ports for a given app container
func main() {
flag.Parse()
appName := flag.Arg(0)
procType := flag.Arg(1)
isHerokuishContainer := common.ToBool(flag.Arg(2))
if procType != "web" {
return
}
var dockerfilePorts []string
if !isHerokuishContainer {
dockerfilePorts = strings.Split(config.GetWithDefault(appName, "DOKKU_DOCKERFILE_PORTS", ""), " ")
}
var ports []string
if len(dockerfilePorts) == 0 {
ports = append(ports, "5000")
} else {
for _, port := range dockerfilePorts {
port = strings.TrimSuffix(strings.TrimSpace(port), "/tcp")
if port == "" || strings.HasSuffix(port, "/udp") {
continue
}
ports = append(ports, port)
}
}
fmt.Fprint(os.Stdout, strings.Join(ports, " "))
}

View File

@@ -0,0 +1,22 @@
package main
import (
"flag"
"fmt"
"os"
"github.com/dokku/dokku/plugins/network"
)
// write the ipaddress to stdout for a given app container
func main() {
flag.Parse()
appName := flag.Arg(0)
if network.HasNetworkConfig(appName) {
fmt.Fprintln(os.Stdout, "true")
return
}
fmt.Fprintln(os.Stdout, "false")
}

View File

@@ -0,0 +1,20 @@
package main
import (
"flag"
"fmt"
"os"
"github.com/dokku/dokku/plugins/network"
)
// write the ipaddress to stdout for a given app container
func main() {
flag.Parse()
appName := flag.Arg(0)
procType := flag.Arg(1)
containerID := flag.Arg(2)
ipAddress := network.GetContainerIpaddress(appName, procType, containerID)
fmt.Fprintln(os.Stdout, ipAddress)
}

View File

@@ -0,0 +1,19 @@
package main
import (
"flag"
"fmt"
"os"
"strings"
"github.com/dokku/dokku/plugins/network"
)
// returns the listeners (host:port combinations) for a given app container
func main() {
flag.Parse()
appName := flag.Arg(0)
listeners := network.GetListeners(appName)
fmt.Fprint(os.Stdout, strings.Join(listeners, " "))
}

View File

@@ -0,0 +1,22 @@
package main
import (
"flag"
"fmt"
"os"
"github.com/dokku/dokku/plugins/common"
network "github.com/dokku/dokku/plugins/network"
)
// write the port to stdout for a given app container
func main() {
flag.Parse()
appName := flag.Arg(0)
procType := flag.Arg(1)
isHerokuishContainer := common.ToBool(flag.Arg(2))
containerID := flag.Arg(3)
port := network.GetContainerPort(appName, procType, isHerokuishContainer, containerID)
fmt.Fprintln(os.Stdout, port)
}

View File

@@ -0,0 +1,21 @@
package main
import (
"flag"
"fmt"
"os"
"github.com/dokku/dokku/plugins/common"
network "github.com/dokku/dokku/plugins/network"
)
// write the port to stdout for a given app container
func main() {
flag.Parse()
appName := flag.Arg(0)
property := flag.Arg(1)
defaultValue := network.GetDefaultValue(property)
value := common.PropertyGetDefault("network", appName, property, defaultValue)
fmt.Fprintln(os.Stdout, value)
}

View File

@@ -0,0 +1,41 @@
package main
import (
"flag"
"fmt"
"os"
"strings"
"github.com/dokku/dokku/plugins/common"
)
// writes the ip to disk
func main() {
flag.Parse()
appName := flag.Arg(0)
procType := flag.Arg(1)
containerIndex := flag.Arg(2)
ip := flag.Arg(3)
if appName == "" {
common.LogFail("Please specify an app to run the command on")
}
err := common.VerifyAppName(appName)
if err != nil {
common.LogFail(err.Error())
}
appRoot := strings.Join([]string{common.MustGetEnv("DOKKU_ROOT"), appName}, "/")
filename := fmt.Sprintf("%v/IP.%v.%v", appRoot, procType, containerIndex)
f, err := os.Create(filename)
if err != nil {
common.LogFail(err.Error())
}
defer f.Close()
ipBytes := []byte(ip)
_, err = f.Write(ipBytes)
if err != nil {
common.LogFail(err.Error())
}
}

View File

@@ -0,0 +1,41 @@
package main
import (
"flag"
"fmt"
"os"
"strings"
"github.com/dokku/dokku/plugins/common"
)
// writes the port to disk
func main() {
flag.Parse()
appName := flag.Arg(0)
procType := flag.Arg(1)
containerIndex := flag.Arg(2)
port := flag.Arg(3)
if appName == "" {
common.LogFail("Please specify an app to run the command on")
}
err := common.VerifyAppName(appName)
if err != nil {
common.LogFail(err.Error())
}
appRoot := strings.Join([]string{common.MustGetEnv("DOKKU_ROOT"), appName}, "/")
filename := fmt.Sprintf("%v/PORT.%v.%v", appRoot, procType, containerIndex)
f, err := os.Create(filename)
if err != nil {
common.LogFail(err.Error())
}
defer f.Close()
portBytes := []byte(port)
_, err = f.Write(portBytes)
if err != nil {
common.LogFail(err.Error())
}
}

View File

@@ -0,0 +1,15 @@
package main
import (
"flag"
"github.com/dokku/dokku/plugins/common"
)
// set bind-all-interfaces to false by default
func main() {
flag.Parse()
appName := flag.Arg(0)
common.PropertyWrite("network", appName, "bind-all-interfaces", "false")
}

View File

@@ -0,0 +1,15 @@
package main
import (
"flag"
"github.com/dokku/dokku/plugins/common"
)
// write the port to stdout for a given app container
func main() {
flag.Parse()
appName := flag.Arg(0)
common.PropertyDestroy("network", appName)
}

View File

@@ -0,0 +1,3 @@
language: go
go:
- tip

View 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.

View File

@@ -0,0 +1,69 @@
Columnize
=========
Easy column-formatted output for golang
[![Build Status](https://travis-ci.org/ryanuber/columnize.svg)](https://travis-ci.org/ryanuber/columnize)
[![GoDoc](https://godoc.org/github.com/ryanuber/columnize?status.svg)](https://godoc.org/github.com/ryanuber/columnize)
Columnize is a really small Go package that makes building CLI's a little bit
easier. In some CLI designs, you want to output a number similar items in a
human-readable way with nicely aligned columns. However, figuring out how wide
to make each column is a boring problem to solve and eats your valuable time.
Here is an example:
```go
package main
import (
"fmt"
"github.com/ryanuber/columnize"
)
func main() {
output := []string{
"Name | Gender | Age",
"Bob | Male | 38",
"Sally | Female | 26",
}
result := columnize.SimpleFormat(output)
fmt.Println(result)
}
```
As you can see, you just pass in a list of strings. And the result:
```
Name Gender Age
Bob Male 38
Sally Female 26
```
Columnize is tolerant of missing or empty fields, or even empty lines, so
passing in extra lines for spacing should show up as you would expect.
Configuration
=============
Columnize is configured using a `Config`, which can be obtained by calling the
`DefaultConfig()` method. You can then tweak the settings in the resulting
`Config`:
```
config := columnize.DefaultConfig()
config.Delim = "|"
config.Glue = " "
config.Prefix = ""
config.Empty = ""
```
* `Delim` is the string by which columns of **input** are delimited
* `Glue` is the string by which columns of **output** are delimited
* `Prefix` is a string by which each line of **output** is prefixed
* `Empty` is a string used to replace blank values found in output
You can then pass the `Config` in using the `Format` method (signature below) to
have text formatted to your liking.
See the [godoc](https://godoc.org/github.com/ryanuber/columnize) page for usage.

View 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)
}

View 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)
}
}

View File

@@ -0,0 +1,2 @@
inject
inject.test

View File

@@ -0,0 +1,20 @@
The MIT License (MIT)
Copyright (c) 2013 Jeremy Saenz
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View File

@@ -0,0 +1,92 @@
# inject
--
import "github.com/codegangsta/inject"
Package inject provides utilities for mapping and injecting dependencies in
various ways.
Language Translations:
* [简体中文](translations/README_zh_cn.md)
## Usage
#### func InterfaceOf
```go
func InterfaceOf(value interface{}) reflect.Type
```
InterfaceOf dereferences a pointer to an Interface type. It panics if value is
not an pointer to an interface.
#### type Applicator
```go
type Applicator interface {
// Maps dependencies in the Type map to each field in the struct
// that is tagged with 'inject'. Returns an error if the injection
// fails.
Apply(interface{}) error
}
```
Applicator represents an interface for mapping dependencies to a struct.
#### type Injector
```go
type Injector interface {
Applicator
Invoker
TypeMapper
// SetParent sets the parent of the injector. If the injector cannot find a
// dependency in its Type map it will check its parent before returning an
// error.
SetParent(Injector)
}
```
Injector represents an interface for mapping and injecting dependencies into
structs and function arguments.
#### func New
```go
func New() Injector
```
New returns a new Injector.
#### type Invoker
```go
type Invoker interface {
// Invoke attempts to call the interface{} provided as a function,
// providing dependencies for function arguments based on Type. Returns
// a slice of reflect.Value representing the returned values of the function.
// Returns an error if the injection fails.
Invoke(interface{}) ([]reflect.Value, error)
}
```
Invoker represents an interface for calling functions via reflection.
#### type TypeMapper
```go
type TypeMapper interface {
// Maps the interface{} value based on its immediate type from reflect.TypeOf.
Map(interface{}) TypeMapper
// Maps the interface{} value based on the pointer of an Interface provided.
// This is really only useful for mapping a value as an interface, as interfaces
// cannot at this time be referenced directly without a pointer.
MapTo(interface{}, interface{}) TypeMapper
// Provides a possibility to directly insert a mapping based on type and value.
// This makes it possible to directly map type arguments not possible to instantiate
// with reflect like unidirectional channels.
Set(reflect.Type, reflect.Value) TypeMapper
// Returns the Value that is mapped to the current type. Returns a zeroed Value if
// the Type has not been mapped.
Get(reflect.Type) reflect.Value
}
```
TypeMapper represents an interface for mapping interface{} values based on type.

View File

@@ -0,0 +1,187 @@
// Package inject provides utilities for mapping and injecting dependencies in various ways.
package inject
import (
"fmt"
"reflect"
)
// Injector represents an interface for mapping and injecting dependencies into structs
// and function arguments.
type Injector interface {
Applicator
Invoker
TypeMapper
// SetParent sets the parent of the injector. If the injector cannot find a
// dependency in its Type map it will check its parent before returning an
// error.
SetParent(Injector)
}
// Applicator represents an interface for mapping dependencies to a struct.
type Applicator interface {
// Maps dependencies in the Type map to each field in the struct
// that is tagged with 'inject'. Returns an error if the injection
// fails.
Apply(interface{}) error
}
// Invoker represents an interface for calling functions via reflection.
type Invoker interface {
// Invoke attempts to call the interface{} provided as a function,
// providing dependencies for function arguments based on Type. Returns
// a slice of reflect.Value representing the returned values of the function.
// Returns an error if the injection fails.
Invoke(interface{}) ([]reflect.Value, error)
}
// TypeMapper represents an interface for mapping interface{} values based on type.
type TypeMapper interface {
// Maps the interface{} value based on its immediate type from reflect.TypeOf.
Map(interface{}) TypeMapper
// Maps the interface{} value based on the pointer of an Interface provided.
// This is really only useful for mapping a value as an interface, as interfaces
// cannot at this time be referenced directly without a pointer.
MapTo(interface{}, interface{}) TypeMapper
// Provides a possibility to directly insert a mapping based on type and value.
// This makes it possible to directly map type arguments not possible to instantiate
// with reflect like unidirectional channels.
Set(reflect.Type, reflect.Value) TypeMapper
// Returns the Value that is mapped to the current type. Returns a zeroed Value if
// the Type has not been mapped.
Get(reflect.Type) reflect.Value
}
type injector struct {
values map[reflect.Type]reflect.Value
parent Injector
}
// InterfaceOf dereferences a pointer to an Interface type.
// It panics if value is not an pointer to an interface.
func InterfaceOf(value interface{}) reflect.Type {
t := reflect.TypeOf(value)
for t.Kind() == reflect.Ptr {
t = t.Elem()
}
if t.Kind() != reflect.Interface {
panic("Called inject.InterfaceOf with a value that is not a pointer to an interface. (*MyInterface)(nil)")
}
return t
}
// New returns a new Injector.
func New() Injector {
return &injector{
values: make(map[reflect.Type]reflect.Value),
}
}
// Invoke attempts to call the interface{} provided as a function,
// providing dependencies for function arguments based on Type.
// Returns a slice of reflect.Value representing the returned values of the function.
// Returns an error if the injection fails.
// It panics if f is not a function
func (inj *injector) Invoke(f interface{}) ([]reflect.Value, error) {
t := reflect.TypeOf(f)
var in = make([]reflect.Value, t.NumIn()) //Panic if t is not kind of Func
for i := 0; i < t.NumIn(); i++ {
argType := t.In(i)
val := inj.Get(argType)
if !val.IsValid() {
return nil, fmt.Errorf("Value not found for type %v", argType)
}
in[i] = val
}
return reflect.ValueOf(f).Call(in), nil
}
// Maps dependencies in the Type map to each field in the struct
// that is tagged with 'inject'.
// Returns an error if the injection fails.
func (inj *injector) Apply(val interface{}) error {
v := reflect.ValueOf(val)
for v.Kind() == reflect.Ptr {
v = v.Elem()
}
if v.Kind() != reflect.Struct {
return nil // Should not panic here ?
}
t := v.Type()
for i := 0; i < v.NumField(); i++ {
f := v.Field(i)
structField := t.Field(i)
if f.CanSet() && (structField.Tag == "inject" || structField.Tag.Get("inject") != "") {
ft := f.Type()
v := inj.Get(ft)
if !v.IsValid() {
return fmt.Errorf("Value not found for type %v", ft)
}
f.Set(v)
}
}
return nil
}
// Maps the concrete value of val to its dynamic type using reflect.TypeOf,
// It returns the TypeMapper registered in.
func (i *injector) Map(val interface{}) TypeMapper {
i.values[reflect.TypeOf(val)] = reflect.ValueOf(val)
return i
}
func (i *injector) MapTo(val interface{}, ifacePtr interface{}) TypeMapper {
i.values[InterfaceOf(ifacePtr)] = reflect.ValueOf(val)
return i
}
// Maps the given reflect.Type to the given reflect.Value and returns
// the Typemapper the mapping has been registered in.
func (i *injector) Set(typ reflect.Type, val reflect.Value) TypeMapper {
i.values[typ] = val
return i
}
func (i *injector) Get(t reflect.Type) reflect.Value {
val := i.values[t]
if val.IsValid() {
return val
}
// no concrete types found, try to find implementors
// if t is an interface
if t.Kind() == reflect.Interface {
for k, v := range i.values {
if k.Implements(t) {
val = v
break
}
}
}
// Still no type found, try to look it up on the parent
if !val.IsValid() && i.parent != nil {
val = i.parent.Get(t)
}
return val
}
func (i *injector) SetParent(parent Injector) {
i.parent = parent
}

View File

@@ -0,0 +1,159 @@
package inject_test
import (
"fmt"
"github.com/codegangsta/inject"
"reflect"
"testing"
)
type SpecialString interface {
}
type TestStruct struct {
Dep1 string `inject:"t" json:"-"`
Dep2 SpecialString `inject`
Dep3 string
}
type Greeter struct {
Name string
}
func (g *Greeter) String() string {
return "Hello, My name is" + g.Name
}
/* Test Helpers */
func expect(t *testing.T, a interface{}, b interface{}) {
if a != b {
t.Errorf("Expected %v (type %v) - Got %v (type %v)", b, reflect.TypeOf(b), a, reflect.TypeOf(a))
}
}
func refute(t *testing.T, a interface{}, b interface{}) {
if a == b {
t.Errorf("Did not expect %v (type %v) - Got %v (type %v)", b, reflect.TypeOf(b), a, reflect.TypeOf(a))
}
}
func Test_InjectorInvoke(t *testing.T) {
injector := inject.New()
expect(t, injector == nil, false)
dep := "some dependency"
injector.Map(dep)
dep2 := "another dep"
injector.MapTo(dep2, (*SpecialString)(nil))
dep3 := make(chan *SpecialString)
dep4 := make(chan *SpecialString)
typRecv := reflect.ChanOf(reflect.RecvDir, reflect.TypeOf(dep3).Elem())
typSend := reflect.ChanOf(reflect.SendDir, reflect.TypeOf(dep4).Elem())
injector.Set(typRecv, reflect.ValueOf(dep3))
injector.Set(typSend, reflect.ValueOf(dep4))
_, err := injector.Invoke(func(d1 string, d2 SpecialString, d3 <-chan *SpecialString, d4 chan<- *SpecialString) {
expect(t, d1, dep)
expect(t, d2, dep2)
expect(t, reflect.TypeOf(d3).Elem(), reflect.TypeOf(dep3).Elem())
expect(t, reflect.TypeOf(d4).Elem(), reflect.TypeOf(dep4).Elem())
expect(t, reflect.TypeOf(d3).ChanDir(), reflect.RecvDir)
expect(t, reflect.TypeOf(d4).ChanDir(), reflect.SendDir)
})
expect(t, err, nil)
}
func Test_InjectorInvokeReturnValues(t *testing.T) {
injector := inject.New()
expect(t, injector == nil, false)
dep := "some dependency"
injector.Map(dep)
dep2 := "another dep"
injector.MapTo(dep2, (*SpecialString)(nil))
result, err := injector.Invoke(func(d1 string, d2 SpecialString) string {
expect(t, d1, dep)
expect(t, d2, dep2)
return "Hello world"
})
expect(t, result[0].String(), "Hello world")
expect(t, err, nil)
}
func Test_InjectorApply(t *testing.T) {
injector := inject.New()
injector.Map("a dep").MapTo("another dep", (*SpecialString)(nil))
s := TestStruct{}
err := injector.Apply(&s)
expect(t, err, nil)
expect(t, s.Dep1, "a dep")
expect(t, s.Dep2, "another dep")
expect(t, s.Dep3, "")
}
func Test_InterfaceOf(t *testing.T) {
iType := inject.InterfaceOf((*SpecialString)(nil))
expect(t, iType.Kind(), reflect.Interface)
iType = inject.InterfaceOf((**SpecialString)(nil))
expect(t, iType.Kind(), reflect.Interface)
// Expecting nil
defer func() {
rec := recover()
refute(t, rec, nil)
}()
iType = inject.InterfaceOf((*testing.T)(nil))
}
func Test_InjectorSet(t *testing.T) {
injector := inject.New()
typ := reflect.TypeOf("string")
typSend := reflect.ChanOf(reflect.SendDir, typ)
typRecv := reflect.ChanOf(reflect.RecvDir, typ)
// instantiating unidirectional channels is not possible using reflect
// http://golang.org/src/pkg/reflect/value.go?s=60463:60504#L2064
chanRecv := reflect.MakeChan(reflect.ChanOf(reflect.BothDir, typ), 0)
chanSend := reflect.MakeChan(reflect.ChanOf(reflect.BothDir, typ), 0)
injector.Set(typSend, chanSend)
injector.Set(typRecv, chanRecv)
expect(t, injector.Get(typSend).IsValid(), true)
expect(t, injector.Get(typRecv).IsValid(), true)
expect(t, injector.Get(chanSend.Type()).IsValid(), false)
}
func Test_InjectorGet(t *testing.T) {
injector := inject.New()
injector.Map("some dependency")
expect(t, injector.Get(reflect.TypeOf("string")).IsValid(), true)
expect(t, injector.Get(reflect.TypeOf(11)).IsValid(), false)
}
func Test_InjectorSetParent(t *testing.T) {
injector := inject.New()
injector.MapTo("another dep", (*SpecialString)(nil))
injector2 := inject.New()
injector2.SetParent(injector)
expect(t, injector2.Get(inject.InterfaceOf((*SpecialString)(nil))).IsValid(), true)
}
func TestInjectImplementors(t *testing.T) {
injector := inject.New()
g := &Greeter{"Jeremy"}
injector.Map(g)
expect(t, injector.Get(inject.InterfaceOf((*fmt.Stringer)(nil))).IsValid(), true)
}

View File

@@ -0,0 +1,85 @@
# inject
--
import "github.com/codegangsta/inject"
inject包提供了多种对实体的映射和依赖注入方式。
## 用法
#### func InterfaceOf
```go
func InterfaceOf(value interface{}) reflect.Type
```
函数InterfaceOf返回指向接口类型的指针。如果传入的value值不是指向接口的指针将抛出一个panic异常。
#### type Applicator
```go
type Applicator interface {
// 在Type map中维持对结构体中每个域的引用并用'inject'来标记
// 如果注入失败将会返回一个error.
Apply(interface{}) error
}
```
Applicator接口表示到结构体的依赖映射关系。
#### type Injector
```go
type Injector interface {
Applicator
Invoker
TypeMapper
// SetParent用来设置父injector. 如果在当前injector的Type map中找不到依赖
// 将会继续从它的父injector中找直到返回error.
SetParent(Injector)
}
```
Injector接口表示对结构体、函数参数的映射和依赖注入。
#### func New
```go
func New() Injector
```
New创建并返回一个Injector.
#### type Invoker
```go
type Invoker interface {
// Invoke尝试将interface{}作为一个函数来调用并基于Type为函数提供参数。
// 它将返回reflect.Value的切片其中存放原函数的返回值。
// 如果注入失败则返回error.
Invoke(interface{}) ([]reflect.Value, error)
}
```
Invoker接口表示通过反射进行函数调用。
#### type TypeMapper
```go
type TypeMapper interface {
// 基于调用reflect.TypeOf得到的类型映射interface{}的值。
Map(interface{}) TypeMapper
// 基于提供的接口的指针映射interface{}的值。
// 该函数仅用来将一个值映射为接口,因为接口无法不通过指针而直接引用到。
MapTo(interface{}, interface{}) TypeMapper
// 为直接插入基于类型和值的map提供一种可能性。
// 它使得这一类直接映射成为可能:无法通过反射直接实例化的类型参数,如单向管道。
Set(reflect.Type, reflect.Value) TypeMapper
// 返回映射到当前类型的Value. 如果Type没被映射将返回对应的零值。
Get(reflect.Type) reflect.Value
}
```
TypeMapper接口用来表示基于类型到接口值的映射。
## 译者
张强 (qqbunny@yeah.net)

View File

@@ -0,0 +1,3 @@
#!/bin/bash
go get github.com/robertkrimen/godocdown/godocdown
godocdown > README.md

View File

@@ -0,0 +1,202 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "{}"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright {yyyy} {name of copyright owner}
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

View File

@@ -0,0 +1,69 @@
## OLD README
First give you a full example, I will explain every command below.
session := sh.NewSession()
session.Env["PATH"] = "/usr/bin:/bin"
session.Stdout = os.Stdout
session.Stderr = os.Stderr
session.Alias("ll", "ls", "-l")
session.ShowCMD = true // enable for debug
var err error
err = session.Call("ll", "/")
if err != nil {
log.Fatal(err)
}
ret, err := session.Capture("pwd", sh.Dir("/home")) # wraper of session.Call
if err != nil {
log.Fatal(err)
}
# ret is "/home\n"
fmt.Println(ret)
create a new Session
session := sh.NewSession()
use alias like this
session.Alias("ll", "ls", "-l") # like alias ll='ls -l'
set current env like this
session.Env["BUILD_ID"] = "123" # like export BUILD_ID=123
set current directory
session.Set(sh.Dir("/")) # like cd /
pipe is also supported
session.Command("echo", "hello\tworld").Command("cut", "-f2")
// output should be "world"
session.Run()
test, the build in command support
session.Test("d", "dir") // test dir
session.Test("f", "file) // test regular file
with `Alias Env Set Call Capture Command` a shell scripts can be easily converted into golang program. below is a shell script.
#!/bin/bash -
#
export PATH=/usr/bin:/bin
alias ll='ls -l'
cd /usr
if test -d "local"
then
ll local | awk '{print $1, $NF}'
fi
convert to golang, will be
s := sh.NewSession()
s.Env["PATH"] = "/usr/bin:/bin"
s.Set(sh.Dir("/usr"))
s.Alias("ll", "ls", "-l")
if s.Test("d", "local") {
s.Command("ll", "local").Command("awk", "{print $1, $NF}").Run()
}

View File

@@ -0,0 +1,85 @@
## go-sh
[![wercker status](https://app.wercker.com/status/009acbd4f00ccc6de7e2554e12a50d84/s "wercker status")](https://app.wercker.com/project/bykey/009acbd4f00ccc6de7e2554e12a50d84)
[![Go Walker](http://gowalker.org/api/v1/badge)](http://gowalker.org/github.com/codeskyblue/go-sh)
*If you depend on the old api, see tag: v.0.1*
install: `go get github.com/codeskyblue/go-sh`
Pipe Example:
package main
import "github.com/codeskyblue/go-sh"
func main() {
sh.Command("echo", "hello\tworld").Command("cut", "-f2").Run()
}
Because I like os/exec, `go-sh` is very much modelled after it. However, `go-sh` provides a better experience.
These are some of its features:
* keep the variable environment (e.g. export)
* alias support (e.g. alias in shell)
* remember current dir
* pipe command
* shell build-in commands echo & test
* timeout support
Examples are important:
sh: echo hello
go: sh.Command("echo", "hello").Run()
sh: export BUILD_ID=123
go: s = sh.NewSession().SetEnv("BUILD_ID", "123")
sh: alias ll='ls -l'
go: s = sh.NewSession().Alias('ll', 'ls', '-l')
sh: (cd /; pwd)
go: sh.Command("pwd", sh.Dir("/")).Run()
sh: test -d data || mkdir data
go: if ! sh.Test("dir", "data") { sh.Command("mkdir", "data").Run() }
sh: cat first second | awk '{print $1}'
go: sh.Command("cat", "first", "second").Command("awk", "{print $1}").Run()
sh: count=$(echo "one two three" | wc -w)
go: count, err := sh.Echo("one two three").Command("wc", "-w").Output()
sh(in ubuntu): timeout 1s sleep 3
go: c := sh.Command("sleep", "3"); c.Start(); c.WaitTimeout(time.Second) # default SIGKILL
go: out, err := sh.Command("sleep", "3").SetTimeout(time.Second).Output() # set session timeout and get output)
sh: echo hello | cat
go: out, err := sh.Command("cat").SetInput("hello").Output()
sh: cat # read from stdin
go: out, err := sh.Command("cat").SetStdin(os.Stdin).Output()
If you need to keep env and dir, it is better to create a session
session := sh.NewSession()
session.SetEnv("BUILD_ID", "123")
session.SetDir("/")
# then call cmd
session.Command("echo", "hello").Run()
# set ShowCMD to true for easily debug
session.ShowCMD = true
for more information, it better to see docs.
[![Go Walker](http://gowalker.org/api/v1/badge)](http://gowalker.org/github.com/codeskyblue/go-sh)
### contribute
If you love this project, starring it will encourage the coder. Pull requests are welcome.
support the author: [alipay](https://me.alipay.com/goskyblue)
### thanks
this project is based on <http://github.com/codegangsta/inject>. thanks for the author.
# the reason to use Go shell
Sometimes we need to write shell scripts, but shell scripts are not good at working cross platform, Go, on the other hand, is good at that. Is there a good way to use Go to write shell like scripts? Using go-sh we can do this now.

View File

@@ -0,0 +1,41 @@
package main
import (
"fmt"
"log"
"github.com/codeskyblue/go-sh"
)
func main() {
sh.Command("echo", "hello").Run()
out, err := sh.Command("echo", "hello").Output()
if err != nil {
log.Fatal(err)
}
fmt.Println("output is", string(out))
var a int
sh.Command("echo", "2").UnmarshalJSON(&a)
fmt.Println("a =", a)
s := sh.NewSession()
s.Alias("hi", "echo", "hi")
s.Command("hi", "boy").Run()
fmt.Print("pwd = ")
s.Command("pwd", sh.Dir("/")).Run()
if !sh.Test("dir", "data") {
sh.Command("echo", "mkdir", "data").Run()
}
sh.Command("echo", "hello", "world").
Command("awk", `{print "second arg is "$2}`).Run()
s.ShowCMD = true
s.Command("echo", "hello", "world").
Command("awk", `{print "second arg is "$2}`).Run()
s.SetEnv("BUILD_ID", "123").Command("bash", "-c", "echo $BUILD_ID").Run()
s.Command("bash", "-c", "echo current shell is $SHELL").Run()
}

View File

@@ -0,0 +1,7 @@
package main
import "github.com/codeskyblue/go-sh"
func main() {
sh.Command("less", "less.go").Run()
}

Some files were not shown because too many files have changed in this diff Show More