mirror of
https://github.com/dokku/dokku.git
synced 2025-12-28 16:06:40 +01:00
Merge pull request #3413 from dokku/3256-buildpack-management
Implement buildpacks plugin
This commit is contained in:
@@ -1,13 +1,165 @@
|
||||
# Buildpack Deployment
|
||||
|
||||
> Subcommands new as of 0.15.0
|
||||
|
||||
```
|
||||
buildpacks:add [--index 1] <app> <buildpack> # Add new app buildpack while inserting into list of buildpacks if necessary
|
||||
buildpacks:clear <app> # Clear all buildpacks set on the app
|
||||
buildpacks:list <app> # List all buildpacks for an app
|
||||
buildpacks:remove <app> <buildpack> # Remove a buildpack set on the app
|
||||
buildpacks:report [<app>] [<flag>] # Displays a buildpack report for one or more apps
|
||||
buildpacks:set [--index 1] <app> <buildpack> # Set new app buildpack at a given position defaulting to the first buildpack if no index is specified
|
||||
```
|
||||
|
||||
> Warning: If using the `buildpacks` plugin, be sure to unset any `BUILDPACK_URL` and remove any such entries from a committed `.env` file. A specified `BUILDPACK_URL` will always override a `.buildpacks` file or the buildpacks plugin.
|
||||
|
||||
Dokku normally defaults to using [Heroku buildpacks](https://devcenter.heroku.com/articles/buildpacks) for deployment, though this may be overridden by committing a valid `Dockerfile` to the root of your repository and pushing the repository to your Dokku installation. To avoid this automatic `Dockerfile` deployment detection, you may do one of the following:
|
||||
|
||||
- Use `dokku config:set` to set the `BUILDPACK_URL` environment variable.
|
||||
- Add `BUILDPACK_URL` to a committed `.env` file in the root of your repository.
|
||||
- See the [environment variable documentation](/docs/configuration/environment-variables.md) for more details.
|
||||
- Set a `BUILDPACK_URL` environment variable
|
||||
- This can be done via `dokku config:set` or via a committed `.env` file in the root of the repository. See the [environment variable documentation](/docs/configuration/environment-variables.md) for more details.
|
||||
- Create a `.buildpacks` file in the root of your repository.
|
||||
- This can be via a committed `.buildpacks` file or managed via the `buildpacks` plugin commands.
|
||||
|
||||
## Switching from Dockerfile deployments
|
||||
This page will cover usage of the `buildpacks` plugin.
|
||||
|
||||
## Usage
|
||||
|
||||
### Listing Buildpacks in Use
|
||||
|
||||
The `buildpacks:list` command can be used to show buildpacks that have been set for an app. This will omit any auto-detected buildpacks.
|
||||
|
||||
```shell
|
||||
# running for an app with no buildpacks specified
|
||||
dokku buildpacks:list node-js-app
|
||||
```
|
||||
|
||||
```
|
||||
-----> test buildpack urls
|
||||
```
|
||||
|
||||
|
||||
```shell
|
||||
# running for an app with two buildpacks specified
|
||||
dokku buildpacks:list node-js-app
|
||||
```
|
||||
|
||||
```
|
||||
-----> test buildpack urls
|
||||
https://github.com/heroku/heroku-buildpack-python.git
|
||||
https://github.com/heroku/heroku-buildpack-nodejs.git
|
||||
```
|
||||
|
||||
### Adding custom buildpacks
|
||||
|
||||
> Please check the documentation for your particular buildpack as you may need to include configuration files (such as a Procfile) in your project root.
|
||||
|
||||
To add a custom buildpack, use the `buildpacks:add` command:
|
||||
|
||||
```shell
|
||||
dokku buildpacks:add node-js-app https://github.com/heroku/heroku-buildpack-nodejs.git
|
||||
```
|
||||
|
||||
When no buildpacks are currently specified, the specified buildpack will be the only one executed for detection and compilation.
|
||||
|
||||
Multiple buildpacks may be specified by using the `buildpacks:add` command multiple times.
|
||||
|
||||
```shell
|
||||
dokku buildpacks:add node-js-app https://github.com/heroku/heroku-buildpack-ruby.git
|
||||
dokku buildpacks:add node-js-app https://github.com/heroku/heroku-buildpack-nodejs.git
|
||||
```
|
||||
|
||||
Buildpacks are executed in order, may be inserted at a specified index via the `--index` flag. This flag is specified starting at a 1-index value.
|
||||
|
||||
```shell
|
||||
# will add the golang buildpack at the second position, bumping all proceeding ones by 1 position
|
||||
dokku buildpacks:add --index 2 node-js-app https://github.com/heroku/heroku-buildpack-golang.git
|
||||
```
|
||||
|
||||
### Overwriting a buildpack position
|
||||
|
||||
In some cases, it may be necessary to swap out a given buildpack. Rather than needing to re-specify each buildpack, the `buildpacks:set` command can be used to overwrite a buildpack at a given position.
|
||||
|
||||
```shell
|
||||
dokku buildpacks:set node-js-app https://github.com/heroku/heroku-buildpack-ruby.git
|
||||
```
|
||||
|
||||
By default, this will overwrite the _first_ buildpack specified. To specify an index, the `--index` flag may be used. This flag is specified starting at a 1-index value, and defaults to `1`.
|
||||
|
||||
```shell
|
||||
# the following are equivalent commands
|
||||
dokku buildpacks:set node-js-app https://github.com/heroku/heroku-buildpack-ruby.git
|
||||
dokku buildpacks:set --index 1 node-js-app https://github.com/heroku/heroku-buildpack-ruby.git
|
||||
```
|
||||
|
||||
If the index specified is larger than the number of buildpacks currently configured, the buildpack will be appended to the end of the list.
|
||||
|
||||
```shell
|
||||
dokku buildpacks:set --index 99 node-js-app https://github.com/heroku/heroku-buildpack-ruby.git
|
||||
```
|
||||
|
||||
### Removing a buildpack
|
||||
|
||||
> At least one of a buildpack or index must be specified
|
||||
|
||||
A single buildpack can be removed by name via the `buildpacks:remove` command.
|
||||
|
||||
```shell
|
||||
dokku buildpacks:remove node-js-app https://github.com/heroku/heroku-buildpack-ruby.git
|
||||
```
|
||||
|
||||
Buildpacks can also be removed by index via the `--index` flag. This flag is specified starting at a 1-index value.
|
||||
|
||||
```shell
|
||||
dokku buildpacks:remove node-js-app --index 1
|
||||
```
|
||||
|
||||
### Clearing all buildpacks
|
||||
|
||||
> This does not affect automatically detected buildpacks, nor does it impact any specified `BUILDPACK_URL` environment variable.
|
||||
|
||||
The `buildpacks:clear` command can be used to clear all configured buildpacks for a specified app.
|
||||
|
||||
```shell
|
||||
dokku buildpacks:clear node-js-app
|
||||
```
|
||||
|
||||
### Displaying buildpack reports about an app
|
||||
|
||||
You can get a report about the app's buildpacks status using the `buildpacks:report` command:
|
||||
|
||||
```shell
|
||||
dokku buildpacks:report
|
||||
```
|
||||
|
||||
```
|
||||
=====> node-js-app buildpacks information
|
||||
Buildpacks list: https://github.com/heroku/heroku-buildpack-nodejs.git
|
||||
=====> python-sample buildpacks information
|
||||
Buildpacks list: https://github.com/heroku/heroku-buildpack-nodejs.git,https://github.com/heroku/heroku-buildpack-python.git
|
||||
=====> ruby-sample buildpacks information
|
||||
Buildpacks list:
|
||||
```
|
||||
|
||||
You can run the command for a specific app also.
|
||||
|
||||
```shell
|
||||
dokku buildpacks:report node-js-app
|
||||
```
|
||||
|
||||
```
|
||||
=====> node-js-app buildpacks information
|
||||
Buildpacks list: https://github.com/heroku/heroku-buildpack-nodejs.git
|
||||
```
|
||||
|
||||
You can pass flags which will output only the value of the specific information you want. For example:
|
||||
|
||||
```shell
|
||||
dokku buildpacks:report node-js-app --buildpacks-list
|
||||
```
|
||||
|
||||
## Errata
|
||||
|
||||
### Switching from Dockerfile deployments
|
||||
|
||||
If an application was previously deployed via Dockerfile, the following commands should be run before a buildpack deploy will succeed:
|
||||
|
||||
@@ -15,54 +167,25 @@ If an application was previously deployed via Dockerfile, the following commands
|
||||
dokku config:unset --no-restart node-js-app DOKKU_DOCKERFILE_CMD DOKKU_DOCKERFILE_ENTRYPOINT DOKKU_PROXY_PORT_MAP
|
||||
```
|
||||
|
||||
## Specifying a custom buildpack
|
||||
|
||||
In certain cases you may want to specify a custom buildpack. While Dokku uses Herokuish to support all the [official Heroku buildpacks](https://github.com/gliderlabs/herokuish#buildpacks), it is possible that the buildpack detection does not work well for your application. As well, you may wish to use a custom buildpack to handle specific application logic.
|
||||
|
||||
To use a specific buildpack, you can run the following Dokku command:
|
||||
|
||||
```shell
|
||||
# replace REPOSITORY_URL with your buildpack's url
|
||||
dokku config:set node-js-app BUILDPACK_URL=REPOSITORY_URL
|
||||
|
||||
# example: using a specific ruby buildpack version
|
||||
dokku config:set node-js-app BUILDPACK_URL=https://github.com/heroku/heroku-buildpack-ruby.git#v142
|
||||
```
|
||||
|
||||
Please check the documentation for your particular buildpack as you may need to include configuration files (such as a Procfile) in your project root.
|
||||
|
||||
## Using multiple buildpacks
|
||||
|
||||
You can only set a single buildpack using the `BUILDPACK_URL`, though there may be times when you wish to use multiple buildpacks. To do so, simply create a `.buildpacks` file in the base of your repository. This file should list all the buildpacks, one-per-line. For instance, if you wish to use both the `nodejs` and `ruby` buildpacks, your `.buildpacks` file should contain the following:
|
||||
|
||||
```shell
|
||||
https://github.com/heroku/heroku-buildpack-nodejs.git#v87
|
||||
https://github.com/heroku/heroku-buildpack-ruby.git#v142
|
||||
```
|
||||
### Using a specific buildpack version
|
||||
|
||||
> Always remember to pin your buildpack versions when using the multi-buildpacks method, or you may find deploys changing your deployed environment.
|
||||
|
||||
You may also choose to set just a single buildpack in this file, though that is up to you.
|
||||
|
||||
Please check the documentation for your particular buildpack(s) as you may need to include configuration files (such as a Procfile) in your project root.
|
||||
|
||||
## Using a specific buildpack version
|
||||
|
||||
As Dokku pins all buildpacks via Herokuish releases, there may be occasions where a local buildpack version is out of date. If you wish to use a more recent version of the buildpack, you may use any of the above methods to specify a buildpack *without* the Git SHA attached like so:
|
||||
By default, Dokku uses the [gliderlabs/herokuish](https://github.com/gliderlabs/herokuish/) project, which pins all of it's vendored buildpacks. There may be occasions where the pinned version results in a broken deploy, or does not have a particular feature that is required to build your project. To use a more recent version of a given buildpack, the buildpack may be specified *without* a Git commit SHA like so:
|
||||
|
||||
```shell
|
||||
# using the latest nodejs buildpack
|
||||
dokku config:set node-js-app BUILDPACK_URL=https://github.com/heroku/heroku-buildpack-nodejs
|
||||
dokku buildpacks:set node-js-app https://github.com/heroku/heroku-buildpack-nodejs
|
||||
```
|
||||
|
||||
You may also wish to use a *specific* version of a buildpack, which is also simple
|
||||
This will use the latest commit on the `master` branch of the specified buildpack. To pin to a newer version of a buildpack, a sha may also be specified by using the form `REPOSITORY_URL#COMMIT_SHA`, where `COMMIT_SHA` is any tree-ish git object - usually a git tag.
|
||||
|
||||
```shell
|
||||
# using v87 of the nodejs buildpack
|
||||
dokku config:set node-js-app BUILDPACK_URL=https://github.com/heroku/heroku-buildpack-nodejs#v87
|
||||
dokku buildpacks:set node-js-app https://github.com/heroku/heroku-buildpack-nodejs#v87
|
||||
```
|
||||
|
||||
## Specifying commands via Procfile
|
||||
### Specifying commands via Procfile
|
||||
|
||||
While many buildpacks have a default command that is run when a detected repository is pushed, it is possible to override this command via a Procfile. A Procfile can also be used to specify multiple commands, each of which is subject to process scaling. See the [process scaling documentation](/docs/deployment/process-management.md) for more details around scaling individual processes.
|
||||
|
||||
@@ -87,17 +210,18 @@ importantworker: env QUEUE=important bundle exec rake resque:work
|
||||
|
||||
The `web` process type holds some significance in that it is the only process type that is automatically scaled to `1` on the initial application deploy. See the [process scaling documentation](/docs/deployment/process-management.md) for more details around scaling individual processes.
|
||||
|
||||
## cURL build timeouts
|
||||
|
||||
Certain buildpacks may time out in retrieving dependencies via cURL. This can happen when your network connection is poor or if there is significant network congestion. You may see a message similar to `gzip: stdin: unexpected end of file` after a cURL command.
|
||||
### `curl` build timeouts
|
||||
|
||||
If you see output similar this when deploying , you may need to override the cURL timeouts to increase the length of time allotted to those tasks. You can do so via the `config` plugin:
|
||||
Certain buildpacks may time out in retrieving dependencies via `curl`. This can happen when your network connection is poor or if there is significant network congestion. You may see a message similar to `gzip: stdin: unexpected end of file` after a `curl` command.
|
||||
|
||||
If you see output similar this when deploying , you may need to override the `curl` timeouts to increase the length of time allotted to those tasks. You can do so via the `config` plugin:
|
||||
|
||||
```shell
|
||||
dokku config:set --global CURL_TIMEOUT=1200
|
||||
dokku config:set --global CURL_CONNECT_TIMEOUT=180
|
||||
```
|
||||
|
||||
## Clearing buildpack cache
|
||||
### Clearing buildpack cache
|
||||
|
||||
See the [repository management documentation](/docs/advanced-usage/repository-management.md#clearing-app-cache).
|
||||
|
||||
6
plugins/buildpacks/.gitignore
vendored
Normal file
6
plugins/buildpacks/.gitignore
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
/commands
|
||||
/subcommands/*
|
||||
/triggers/*
|
||||
/install
|
||||
/post-delete
|
||||
/report
|
||||
37
plugins/buildpacks/Makefile
Normal file
37
plugins/buildpacks/Makefile
Normal file
@@ -0,0 +1,37 @@
|
||||
include ../../common.mk
|
||||
|
||||
GO_ARGS ?= -a
|
||||
|
||||
SUBCOMMANDS = subcommands/add subcommands/clear subcommands/list subcommands/remove subcommands/report subcommands/set
|
||||
TRIGGERS = triggers/install triggers/post-delete triggers/post-extract triggers/report
|
||||
build-in-docker: clean
|
||||
docker run --rm \
|
||||
-v $$PWD/../..:$(GO_REPO_ROOT) \
|
||||
-w $(GO_REPO_ROOT)/plugins/buildpacks \
|
||||
$(BUILD_IMAGE) \
|
||||
bash -c "GO_ARGS='$(GO_ARGS)' make -j4 build" || exit $$?
|
||||
|
||||
build: commands subcommands triggers
|
||||
$(MAKE) triggers-copy
|
||||
|
||||
commands: **/**/commands.go
|
||||
go build $(GO_ARGS) -o commands src/commands/commands.go
|
||||
|
||||
subcommands: $(SUBCOMMANDS)
|
||||
|
||||
subcommands/%: src/subcommands/*/%.go
|
||||
go build $(GO_ARGS) -o $@ $<
|
||||
|
||||
clean:
|
||||
rm -rf commands subcommands triggers install post-delete post-extract report
|
||||
|
||||
src-clean:
|
||||
rm -rf .gitignore src triggers vendor Makefile *.go
|
||||
|
||||
triggers: $(TRIGGERS)
|
||||
|
||||
triggers/%: src/triggers/*/%.go
|
||||
go build $(GO_ARGS) -o $@ $<
|
||||
|
||||
triggers-copy:
|
||||
cp triggers/* .
|
||||
61
plugins/buildpacks/buildpacks.go
Normal file
61
plugins/buildpacks/buildpacks.go
Normal file
@@ -0,0 +1,61 @@
|
||||
package buildpacks
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
"github.com/dokku/dokku/plugins/common"
|
||||
)
|
||||
|
||||
// ReportSingleApp is an internal function that displays the app report for one or more apps
|
||||
func ReportSingleApp(appName, infoFlag string) {
|
||||
if err := common.VerifyAppName(appName); err != nil {
|
||||
common.LogFail(err.Error())
|
||||
}
|
||||
|
||||
buildpacks, err := common.PropertyListGet("buildpacks", appName, "buildpacks")
|
||||
if err != nil {
|
||||
common.LogFail(err.Error())
|
||||
}
|
||||
|
||||
infoFlags := map[string]string{
|
||||
"--buildpacks-list": strings.Join(buildpacks, ","),
|
||||
}
|
||||
|
||||
if len(infoFlag) == 0 {
|
||||
common.LogInfo2Quiet(fmt.Sprintf("%s buildpacks information", appName))
|
||||
for k, v := range infoFlags {
|
||||
key := common.UcFirst(strings.Replace(strings.TrimPrefix(k, "--"), "-", " ", -1))
|
||||
common.LogVerbose(fmt.Sprintf("%s%s", Right(fmt.Sprintf("%s:", key), 31, " "), 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, ", ")))
|
||||
}
|
||||
|
||||
func times(str string, n int) (out string) {
|
||||
for i := 0; i < n; i++ {
|
||||
out += str
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Right right-pads the string with pad up to len runes
|
||||
func Right(str string, length int, pad string) string {
|
||||
return str + times(pad, length-len(str))
|
||||
}
|
||||
3
plugins/buildpacks/glide.yaml
Normal file
3
plugins/buildpacks/glide.yaml
Normal file
@@ -0,0 +1,3 @@
|
||||
package: .
|
||||
import:
|
||||
- package: github.com/ryanuber/columnize
|
||||
4
plugins/buildpacks/plugin.toml
Normal file
4
plugins/buildpacks/plugin.toml
Normal file
@@ -0,0 +1,4 @@
|
||||
[plugin]
|
||||
description = "dokku core buildpacks plugin"
|
||||
version = "0.14.2"
|
||||
[plugin.config]
|
||||
67
plugins/buildpacks/src/commands/commands.go
Normal file
67
plugins/buildpacks/src/commands/commands.go
Normal file
@@ -0,0 +1,67 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/dokku/dokku/plugins/common"
|
||||
columnize "github.com/ryanuber/columnize"
|
||||
)
|
||||
|
||||
const (
|
||||
helpHeader = `Usage: dokku buildpacks[:COMMAND]
|
||||
|
||||
Manages buildpacks settings for an app
|
||||
|
||||
Additional commands:`
|
||||
|
||||
helpContent = `
|
||||
buildpacks:add [--index 1] <app> <buildpack>, Add new app buildpack while inserting into list of buildpacks if necessary
|
||||
buildpacks:clear <app>, Clear all buildpacks set on the app
|
||||
buildpacks:list <app>, List all buildpacks for an app
|
||||
buildpacks:remove <app> <buildpack>, Remove a buildpack set on the app
|
||||
buildpacks:report [<app>] [<flag>], Displays a buildpack report for one or more apps
|
||||
buildpacks:set [--index 1] <app> <buildpack>, Set new app buildpack at a given position defaulting to the first buildpack if no index is specified
|
||||
`
|
||||
)
|
||||
|
||||
func main() {
|
||||
flag.Usage = usage
|
||||
flag.Parse()
|
||||
|
||||
cmd := flag.Arg(0)
|
||||
switch cmd {
|
||||
case "buildpacks", "buildpacks:help":
|
||||
usage()
|
||||
case "help":
|
||||
command := common.NewShellCmd(fmt.Sprintf("ps -o command= %d", os.Getppid()))
|
||||
command.ShowOutput = false
|
||||
output, err := command.Output()
|
||||
|
||||
if err == nil && strings.Contains(string(output), "--all") {
|
||||
fmt.Println(helpContent)
|
||||
} else {
|
||||
fmt.Print("\n buildpacks, Manages buildpack settings for an app\n")
|
||||
}
|
||||
default:
|
||||
dokkuNotImplementExitCode, err := strconv.Atoi(os.Getenv("DOKKU_NOT_IMPLEMENTED_EXIT"))
|
||||
if err != nil {
|
||||
fmt.Println("failed to retrieve DOKKU_NOT_IMPLEMENTED_EXIT environment variable")
|
||||
dokkuNotImplementExitCode = 10
|
||||
}
|
||||
os.Exit(dokkuNotImplementExitCode)
|
||||
}
|
||||
}
|
||||
|
||||
func usage() {
|
||||
config := columnize.DefaultConfig()
|
||||
config.Delim = ","
|
||||
config.Prefix = " "
|
||||
config.Empty = ""
|
||||
content := strings.Split(helpContent, "\n")[1:]
|
||||
fmt.Println(helpHeader)
|
||||
fmt.Println(columnize.Format(content, config))
|
||||
}
|
||||
6
plugins/buildpacks/src/glide.lock
generated
Normal file
6
plugins/buildpacks/src/glide.lock
generated
Normal file
@@ -0,0 +1,6 @@
|
||||
hash: 1ddab5de41d1514c2722bd7e24758ad4b60bf6956eb5b9b925fa071a1427f149
|
||||
updated: 2017-01-03T17:16:50.97156327-08:00
|
||||
imports:
|
||||
- name: github.com/ryanuber/columnize
|
||||
version: 0fbbb3f0e3fbdc5bae7c6cd5f6c1887ebfb76360
|
||||
testImports: []
|
||||
3
plugins/buildpacks/src/glide.yaml
Normal file
3
plugins/buildpacks/src/glide.yaml
Normal file
@@ -0,0 +1,3 @@
|
||||
package: .
|
||||
import:
|
||||
- package: github.com/ryanuber/columnize
|
||||
19
plugins/buildpacks/src/subcommands/add/add.go
Normal file
19
plugins/buildpacks/src/subcommands/add/add.go
Normal file
@@ -0,0 +1,19 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"os"
|
||||
|
||||
"github.com/dokku/dokku/plugins/buildpacks"
|
||||
"github.com/dokku/dokku/plugins/common"
|
||||
)
|
||||
|
||||
func main() {
|
||||
args := flag.NewFlagSet("buildpacks:add", flag.ExitOnError)
|
||||
index := args.Int("index", 0, "--index: the 1-based index of the URL in the list of URLs")
|
||||
args.Parse(os.Args[2:])
|
||||
err := buildpacks.CommandAdd(args.Args(), *index)
|
||||
if err != nil {
|
||||
common.LogFail(err.Error())
|
||||
}
|
||||
}
|
||||
18
plugins/buildpacks/src/subcommands/clear/clear.go
Normal file
18
plugins/buildpacks/src/subcommands/clear/clear.go
Normal file
@@ -0,0 +1,18 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"os"
|
||||
|
||||
"github.com/dokku/dokku/plugins/buildpacks"
|
||||
"github.com/dokku/dokku/plugins/common"
|
||||
)
|
||||
|
||||
func main() {
|
||||
args := flag.NewFlagSet("buildpacks:clear", flag.ExitOnError)
|
||||
args.Parse(os.Args[2:])
|
||||
err := buildpacks.CommandClear(args.Args())
|
||||
if err != nil {
|
||||
common.LogFail(err.Error())
|
||||
}
|
||||
}
|
||||
14
plugins/buildpacks/src/subcommands/list/list.go
Normal file
14
plugins/buildpacks/src/subcommands/list/list.go
Normal file
@@ -0,0 +1,14 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"os"
|
||||
|
||||
"github.com/dokku/dokku/plugins/buildpacks"
|
||||
)
|
||||
|
||||
func main() {
|
||||
args := flag.NewFlagSet("buildpacks:list", flag.ExitOnError)
|
||||
args.Parse(os.Args[2:])
|
||||
buildpacks.CommandList(args.Args())
|
||||
}
|
||||
19
plugins/buildpacks/src/subcommands/remove/remove.go
Normal file
19
plugins/buildpacks/src/subcommands/remove/remove.go
Normal file
@@ -0,0 +1,19 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"os"
|
||||
|
||||
"github.com/dokku/dokku/plugins/buildpacks"
|
||||
"github.com/dokku/dokku/plugins/common"
|
||||
)
|
||||
|
||||
func main() {
|
||||
args := flag.NewFlagSet("buildpacks:remove", flag.ExitOnError)
|
||||
index := args.Int("index", 0, "--index: the 1-based index of the URL in the list of URLs")
|
||||
args.Parse(os.Args[2:])
|
||||
err := buildpacks.CommandRemove(args.Args(), *index)
|
||||
if err != nil {
|
||||
common.LogFail(err.Error())
|
||||
}
|
||||
}
|
||||
34
plugins/buildpacks/src/subcommands/report/report.go
Normal file
34
plugins/buildpacks/src/subcommands/report/report.go
Normal file
@@ -0,0 +1,34 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"strings"
|
||||
|
||||
"github.com/dokku/dokku/plugins/buildpacks"
|
||||
"github.com/dokku/dokku/plugins/common"
|
||||
)
|
||||
|
||||
// displays a buildpacks report for one or more apps
|
||||
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 {
|
||||
buildpacks.ReportSingleApp(appName, infoFlag)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
buildpacks.ReportSingleApp(appName, infoFlag)
|
||||
}
|
||||
19
plugins/buildpacks/src/subcommands/set/set.go
Normal file
19
plugins/buildpacks/src/subcommands/set/set.go
Normal file
@@ -0,0 +1,19 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"os"
|
||||
|
||||
"github.com/dokku/dokku/plugins/buildpacks"
|
||||
"github.com/dokku/dokku/plugins/common"
|
||||
)
|
||||
|
||||
func main() {
|
||||
args := flag.NewFlagSet("buildpacks:set", flag.ExitOnError)
|
||||
index := args.Int("index", 0, "--index: the 1-based index of the URL in the list of URLs")
|
||||
args.Parse(os.Args[2:])
|
||||
err := buildpacks.CommandSet(args.Args(), *index)
|
||||
if err != nil {
|
||||
common.LogFail(err.Error())
|
||||
}
|
||||
}
|
||||
14
plugins/buildpacks/src/triggers/install/install.go
Normal file
14
plugins/buildpacks/src/triggers/install/install.go
Normal file
@@ -0,0 +1,14 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/dokku/dokku/plugins/common"
|
||||
)
|
||||
|
||||
// runs the install step for the buildpacks plugin
|
||||
func main() {
|
||||
if err := common.PropertySetup("buildpacks"); err != nil {
|
||||
common.LogFail(fmt.Sprintf("Unable to install the buildpacks plugin: %s", err.Error()))
|
||||
}
|
||||
}
|
||||
18
plugins/buildpacks/src/triggers/post-delete/post-delete.go
Normal file
18
plugins/buildpacks/src/triggers/post-delete/post-delete.go
Normal file
@@ -0,0 +1,18 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
|
||||
"github.com/dokku/dokku/plugins/common"
|
||||
)
|
||||
|
||||
// destroys the buildpacks property for a given app container
|
||||
func main() {
|
||||
flag.Parse()
|
||||
appName := flag.Arg(0)
|
||||
|
||||
err := common.PropertyDestroy("buildpacks", appName)
|
||||
if err != nil {
|
||||
common.LogFail(err.Error())
|
||||
}
|
||||
}
|
||||
45
plugins/buildpacks/src/triggers/post-extract/post-extract.go
Normal file
45
plugins/buildpacks/src/triggers/post-extract/post-extract.go
Normal file
@@ -0,0 +1,45 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"flag"
|
||||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
|
||||
"github.com/dokku/dokku/plugins/common"
|
||||
)
|
||||
|
||||
// writes a .buildpacks file into the app
|
||||
func main() {
|
||||
flag.Parse()
|
||||
appName := flag.Arg(0)
|
||||
tmpWorkDir := flag.Arg(1)
|
||||
|
||||
buildpacks, err := common.PropertyListGet("buildpacks", appName, "buildpacks")
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if len(buildpacks) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
buildpacksPath := path.Join(tmpWorkDir, ".buildpacks")
|
||||
file, err := os.OpenFile(buildpacksPath, os.O_RDWR|os.O_TRUNC, 0600)
|
||||
if err != nil {
|
||||
common.LogFail(fmt.Sprintf("Error writing .buildpacks file: %s", err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
w := bufio.NewWriter(file)
|
||||
for _, buildpack := range buildpacks {
|
||||
fmt.Fprintln(w, buildpack)
|
||||
}
|
||||
|
||||
if err = w.Flush(); err != nil {
|
||||
common.LogFail(fmt.Sprintf("Error writing .buildpacks file: %s", err.Error()))
|
||||
return
|
||||
}
|
||||
file.Chmod(0600)
|
||||
}
|
||||
15
plugins/buildpacks/src/triggers/report/report.go
Normal file
15
plugins/buildpacks/src/triggers/report/report.go
Normal file
@@ -0,0 +1,15 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
|
||||
"github.com/dokku/dokku/plugins/buildpacks"
|
||||
)
|
||||
|
||||
// displays a buildpacks report for one or more apps
|
||||
func main() {
|
||||
flag.Parse()
|
||||
appName := flag.Arg(0)
|
||||
|
||||
buildpacks.ReportSingleApp(appName, "")
|
||||
}
|
||||
3
plugins/buildpacks/src/vendor/github.com/ryanuber/columnize/.travis.yml
generated
vendored
Normal file
3
plugins/buildpacks/src/vendor/github.com/ryanuber/columnize/.travis.yml
generated
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
language: go
|
||||
go:
|
||||
- tip
|
||||
20
plugins/buildpacks/src/vendor/github.com/ryanuber/columnize/LICENSE
generated
vendored
Normal file
20
plugins/buildpacks/src/vendor/github.com/ryanuber/columnize/LICENSE
generated
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
Copyright (c) 2016 Ryan Uber
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
"Software"), to deal in the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to
|
||||
permit persons to whom the Software is furnished to do so, subject to
|
||||
the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
69
plugins/buildpacks/src/vendor/github.com/ryanuber/columnize/README.md
generated
vendored
Normal file
69
plugins/buildpacks/src/vendor/github.com/ryanuber/columnize/README.md
generated
vendored
Normal file
@@ -0,0 +1,69 @@
|
||||
Columnize
|
||||
=========
|
||||
|
||||
Easy column-formatted output for golang
|
||||
|
||||
[](https://travis-ci.org/ryanuber/columnize)
|
||||
[](https://godoc.org/github.com/ryanuber/columnize)
|
||||
|
||||
Columnize is a really small Go package that makes building CLI's a little bit
|
||||
easier. In some CLI designs, you want to output a number similar items in a
|
||||
human-readable way with nicely aligned columns. However, figuring out how wide
|
||||
to make each column is a boring problem to solve and eats your valuable time.
|
||||
|
||||
Here is an example:
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/ryanuber/columnize"
|
||||
)
|
||||
|
||||
func main() {
|
||||
output := []string{
|
||||
"Name | Gender | Age",
|
||||
"Bob | Male | 38",
|
||||
"Sally | Female | 26",
|
||||
}
|
||||
result := columnize.SimpleFormat(output)
|
||||
fmt.Println(result)
|
||||
}
|
||||
```
|
||||
|
||||
As you can see, you just pass in a list of strings. And the result:
|
||||
|
||||
```
|
||||
Name Gender Age
|
||||
Bob Male 38
|
||||
Sally Female 26
|
||||
```
|
||||
|
||||
Columnize is tolerant of missing or empty fields, or even empty lines, so
|
||||
passing in extra lines for spacing should show up as you would expect.
|
||||
|
||||
Configuration
|
||||
=============
|
||||
|
||||
Columnize is configured using a `Config`, which can be obtained by calling the
|
||||
`DefaultConfig()` method. You can then tweak the settings in the resulting
|
||||
`Config`:
|
||||
|
||||
```
|
||||
config := columnize.DefaultConfig()
|
||||
config.Delim = "|"
|
||||
config.Glue = " "
|
||||
config.Prefix = ""
|
||||
config.Empty = ""
|
||||
```
|
||||
|
||||
* `Delim` is the string by which columns of **input** are delimited
|
||||
* `Glue` is the string by which columns of **output** are delimited
|
||||
* `Prefix` is a string by which each line of **output** is prefixed
|
||||
* `Empty` is a string used to replace blank values found in output
|
||||
|
||||
You can then pass the `Config` in using the `Format` method (signature below) to
|
||||
have text formatted to your liking.
|
||||
|
||||
See the [godoc](https://godoc.org/github.com/ryanuber/columnize) page for usage.
|
||||
178
plugins/buildpacks/src/vendor/github.com/ryanuber/columnize/columnize.go
generated
vendored
Normal file
178
plugins/buildpacks/src/vendor/github.com/ryanuber/columnize/columnize.go
generated
vendored
Normal file
@@ -0,0 +1,178 @@
|
||||
package columnize
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Config can be used to tune certain parameters which affect the way
|
||||
// in which Columnize will format output text.
|
||||
type Config struct {
|
||||
// The string by which the lines of input will be split.
|
||||
Delim string
|
||||
|
||||
// The string by which columns of output will be separated.
|
||||
Glue string
|
||||
|
||||
// The string by which columns of output will be prefixed.
|
||||
Prefix string
|
||||
|
||||
// A replacement string to replace empty fields
|
||||
Empty string
|
||||
}
|
||||
|
||||
// DefaultConfig returns a *Config with default values.
|
||||
func DefaultConfig() *Config {
|
||||
return &Config{
|
||||
Delim: "|",
|
||||
Glue: " ",
|
||||
Prefix: "",
|
||||
Empty: "",
|
||||
}
|
||||
}
|
||||
|
||||
// MergeConfig merges two config objects together and returns the resulting
|
||||
// configuration. Values from the right take precedence over the left side.
|
||||
func MergeConfig(a, b *Config) *Config {
|
||||
var result Config = *a
|
||||
|
||||
// Return quickly if either side was nil
|
||||
if a == nil || b == nil {
|
||||
return &result
|
||||
}
|
||||
|
||||
if b.Delim != "" {
|
||||
result.Delim = b.Delim
|
||||
}
|
||||
if b.Glue != "" {
|
||||
result.Glue = b.Glue
|
||||
}
|
||||
if b.Prefix != "" {
|
||||
result.Prefix = b.Prefix
|
||||
}
|
||||
if b.Empty != "" {
|
||||
result.Empty = b.Empty
|
||||
}
|
||||
|
||||
return &result
|
||||
}
|
||||
|
||||
// stringFormat, given a set of column widths and the number of columns in
|
||||
// the current line, returns a sprintf-style format string which can be used
|
||||
// to print output aligned properly with other lines using the same widths set.
|
||||
func stringFormat(c *Config, widths []int, columns int) string {
|
||||
// Create the buffer with an estimate of the length
|
||||
buf := bytes.NewBuffer(make([]byte, 0, (6+len(c.Glue))*columns))
|
||||
|
||||
// Start with the prefix, if any was given. The buffer will not return an
|
||||
// error so it does not need to be handled
|
||||
buf.WriteString(c.Prefix)
|
||||
|
||||
// Create the format string from the discovered widths
|
||||
for i := 0; i < columns && i < len(widths); i++ {
|
||||
if i == columns-1 {
|
||||
buf.WriteString("%s\n")
|
||||
} else {
|
||||
fmt.Fprintf(buf, "%%-%ds%s", widths[i], c.Glue)
|
||||
}
|
||||
}
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
// elementsFromLine returns a list of elements, each representing a single
|
||||
// item which will belong to a column of output.
|
||||
func elementsFromLine(config *Config, line string) []interface{} {
|
||||
seperated := strings.Split(line, config.Delim)
|
||||
elements := make([]interface{}, len(seperated))
|
||||
for i, field := range seperated {
|
||||
value := strings.TrimSpace(field)
|
||||
|
||||
// Apply the empty value, if configured.
|
||||
if value == "" && config.Empty != "" {
|
||||
value = config.Empty
|
||||
}
|
||||
elements[i] = value
|
||||
}
|
||||
return elements
|
||||
}
|
||||
|
||||
// runeLen calculates the number of visible "characters" in a string
|
||||
func runeLen(s string) int {
|
||||
l := 0
|
||||
for _ = range s {
|
||||
l++
|
||||
}
|
||||
return l
|
||||
}
|
||||
|
||||
// widthsFromLines examines a list of strings and determines how wide each
|
||||
// column should be considering all of the elements that need to be printed
|
||||
// within it.
|
||||
func widthsFromLines(config *Config, lines []string) []int {
|
||||
widths := make([]int, 0, 8)
|
||||
|
||||
for _, line := range lines {
|
||||
elems := elementsFromLine(config, line)
|
||||
for i := 0; i < len(elems); i++ {
|
||||
l := runeLen(elems[i].(string))
|
||||
if len(widths) <= i {
|
||||
widths = append(widths, l)
|
||||
} else if widths[i] < l {
|
||||
widths[i] = l
|
||||
}
|
||||
}
|
||||
}
|
||||
return widths
|
||||
}
|
||||
|
||||
// Format is the public-facing interface that takes a list of strings and
|
||||
// returns nicely aligned column-formatted text.
|
||||
func Format(lines []string, config *Config) string {
|
||||
conf := MergeConfig(DefaultConfig(), config)
|
||||
widths := widthsFromLines(conf, lines)
|
||||
|
||||
// Estimate the buffer size
|
||||
glueSize := len(conf.Glue)
|
||||
var size int
|
||||
for _, w := range widths {
|
||||
size += w + glueSize
|
||||
}
|
||||
size *= len(lines)
|
||||
|
||||
// Create the buffer
|
||||
buf := bytes.NewBuffer(make([]byte, 0, size))
|
||||
|
||||
// Create a cache for the string formats
|
||||
fmtCache := make(map[int]string, 16)
|
||||
|
||||
// Create the formatted output using the format string
|
||||
for _, line := range lines {
|
||||
elems := elementsFromLine(conf, line)
|
||||
|
||||
// Get the string format using cache
|
||||
numElems := len(elems)
|
||||
stringfmt, ok := fmtCache[numElems]
|
||||
if !ok {
|
||||
stringfmt = stringFormat(conf, widths, numElems)
|
||||
fmtCache[numElems] = stringfmt
|
||||
}
|
||||
|
||||
fmt.Fprintf(buf, stringfmt, elems...)
|
||||
}
|
||||
|
||||
// Get the string result
|
||||
result := buf.String()
|
||||
|
||||
// Remove trailing newline without removing leading/trailing space
|
||||
if n := len(result); n > 0 && result[n-1] == '\n' {
|
||||
result = result[:n-1]
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// SimpleFormat is a convenience function to format text with the defaults.
|
||||
func SimpleFormat(lines []string) string {
|
||||
return Format(lines, nil)
|
||||
}
|
||||
306
plugins/buildpacks/src/vendor/github.com/ryanuber/columnize/columnize_test.go
generated
vendored
Normal file
306
plugins/buildpacks/src/vendor/github.com/ryanuber/columnize/columnize_test.go
generated
vendored
Normal file
@@ -0,0 +1,306 @@
|
||||
package columnize
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
crand "crypto/rand"
|
||||
)
|
||||
|
||||
func TestListOfStringsInput(t *testing.T) {
|
||||
input := []string{
|
||||
"Column A | Column B | Column C",
|
||||
"x | y | z",
|
||||
}
|
||||
|
||||
config := DefaultConfig()
|
||||
output := Format(input, config)
|
||||
|
||||
expected := "Column A Column B Column C\n"
|
||||
expected += "x y z"
|
||||
|
||||
if output != expected {
|
||||
t.Fatalf("\nexpected:\n%s\n\ngot:\n%s", expected, output)
|
||||
}
|
||||
}
|
||||
|
||||
func TestEmptyLinesOutput(t *testing.T) {
|
||||
input := []string{
|
||||
"Column A | Column B | Column C",
|
||||
"",
|
||||
"x | y | z",
|
||||
}
|
||||
|
||||
config := DefaultConfig()
|
||||
output := Format(input, config)
|
||||
|
||||
expected := "Column A Column B Column C\n"
|
||||
expected += "\n"
|
||||
expected += "x y z"
|
||||
|
||||
if output != expected {
|
||||
t.Fatalf("\nexpected:\n%s\n\ngot:\n%s", expected, output)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLeadingSpacePreserved(t *testing.T) {
|
||||
input := []string{
|
||||
"| Column B | Column C",
|
||||
"x | y | z",
|
||||
}
|
||||
|
||||
config := DefaultConfig()
|
||||
output := Format(input, config)
|
||||
|
||||
expected := " Column B Column C\n"
|
||||
expected += "x y z"
|
||||
|
||||
if output != expected {
|
||||
t.Fatalf("\nexpected:\n%s\n\ngot:\n%s", expected, output)
|
||||
}
|
||||
}
|
||||
|
||||
func TestColumnWidthCalculator(t *testing.T) {
|
||||
input := []string{
|
||||
"Column A | Column B | Column C",
|
||||
"Longer than A | Longer than B | Longer than C",
|
||||
"short | short | short",
|
||||
}
|
||||
|
||||
config := DefaultConfig()
|
||||
output := Format(input, config)
|
||||
|
||||
expected := "Column A Column B Column C\n"
|
||||
expected += "Longer than A Longer than B Longer than C\n"
|
||||
expected += "short short short"
|
||||
|
||||
if output != expected {
|
||||
printableProof := fmt.Sprintf("\nGot: %+q", output)
|
||||
printableProof += fmt.Sprintf("\nExpected: %+q", expected)
|
||||
t.Fatalf("\n%s", printableProof)
|
||||
}
|
||||
}
|
||||
|
||||
func TestColumnWidthCalculatorNonASCII(t *testing.T) {
|
||||
input := []string{
|
||||
"Column A | Column B | Column C",
|
||||
"⌘⌘⌘⌘⌘⌘⌘⌘ | Longer than B | Longer than C",
|
||||
"short | short | short",
|
||||
}
|
||||
|
||||
config := DefaultConfig()
|
||||
output := Format(input, config)
|
||||
|
||||
expected := "Column A Column B Column C\n"
|
||||
expected += "⌘⌘⌘⌘⌘⌘⌘⌘ Longer than B Longer than C\n"
|
||||
expected += "short short short"
|
||||
|
||||
if output != expected {
|
||||
printableProof := fmt.Sprintf("\nGot: %+q", output)
|
||||
printableProof += fmt.Sprintf("\nExpected: %+q", expected)
|
||||
t.Fatalf("\n%s", printableProof)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkColumnWidthCalculator(b *testing.B) {
|
||||
// Generate the input
|
||||
input := []string{
|
||||
"UUID A | UUID B | UUID C | Column D | Column E",
|
||||
}
|
||||
|
||||
format := "%s|%s|%s|%s"
|
||||
short := "short"
|
||||
|
||||
uuid := func() string {
|
||||
buf := make([]byte, 16)
|
||||
if _, err := crand.Read(buf); err != nil {
|
||||
panic(fmt.Errorf("failed to read random bytes: %v", err))
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%08x-%04x-%04x-%04x-%12x",
|
||||
buf[0:4],
|
||||
buf[4:6],
|
||||
buf[6:8],
|
||||
buf[8:10],
|
||||
buf[10:16])
|
||||
}
|
||||
|
||||
for i := 0; i < 1000; i++ {
|
||||
l := fmt.Sprintf(format, uuid()[:8], uuid()[:12], uuid(), short, short)
|
||||
input = append(input, l)
|
||||
}
|
||||
|
||||
config := DefaultConfig()
|
||||
b.ResetTimer()
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
Format(input, config)
|
||||
}
|
||||
}
|
||||
|
||||
func TestVariedInputSpacing(t *testing.T) {
|
||||
input := []string{
|
||||
"Column A |Column B| Column C",
|
||||
"x|y| z",
|
||||
}
|
||||
|
||||
config := DefaultConfig()
|
||||
output := Format(input, config)
|
||||
|
||||
expected := "Column A Column B Column C\n"
|
||||
expected += "x y z"
|
||||
|
||||
if output != expected {
|
||||
t.Fatalf("\nexpected:\n%s\n\ngot:\n%s", expected, output)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUnmatchedColumnCounts(t *testing.T) {
|
||||
input := []string{
|
||||
"Column A | Column B | Column C",
|
||||
"Value A | Value B",
|
||||
"Value A | Value B | Value C | Value D",
|
||||
}
|
||||
|
||||
config := DefaultConfig()
|
||||
output := Format(input, config)
|
||||
|
||||
expected := "Column A Column B Column C\n"
|
||||
expected += "Value A Value B\n"
|
||||
expected += "Value A Value B Value C Value D"
|
||||
|
||||
if output != expected {
|
||||
t.Fatalf("\nexpected:\n%s\n\ngot:\n%s", expected, output)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAlternateDelimiter(t *testing.T) {
|
||||
input := []string{
|
||||
"Column | A % Column | B % Column | C",
|
||||
"Value A % Value B % Value C",
|
||||
}
|
||||
|
||||
config := DefaultConfig()
|
||||
config.Delim = "%"
|
||||
output := Format(input, config)
|
||||
|
||||
expected := "Column | A Column | B Column | C\n"
|
||||
expected += "Value A Value B Value C"
|
||||
|
||||
if output != expected {
|
||||
t.Fatalf("\nexpected:\n%s\n\ngot:\n%s", expected, output)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAlternateSpacingString(t *testing.T) {
|
||||
input := []string{
|
||||
"Column A | Column B | Column C",
|
||||
"x | y | z",
|
||||
}
|
||||
|
||||
config := DefaultConfig()
|
||||
config.Glue = " "
|
||||
output := Format(input, config)
|
||||
|
||||
expected := "Column A Column B Column C\n"
|
||||
expected += "x y z"
|
||||
|
||||
if output != expected {
|
||||
t.Fatalf("\nexpected:\n%s\n\ngot:\n%s", expected, output)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSimpleFormat(t *testing.T) {
|
||||
input := []string{
|
||||
"Column A | Column B | Column C",
|
||||
"x | y | z",
|
||||
}
|
||||
|
||||
output := SimpleFormat(input)
|
||||
|
||||
expected := "Column A Column B Column C\n"
|
||||
expected += "x y z"
|
||||
|
||||
if output != expected {
|
||||
t.Fatalf("\nexpected:\n%s\n\ngot:\n%s", expected, output)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAlternatePrefixString(t *testing.T) {
|
||||
input := []string{
|
||||
"Column A | Column B | Column C",
|
||||
"x | y | z",
|
||||
}
|
||||
|
||||
config := DefaultConfig()
|
||||
config.Prefix = " "
|
||||
output := Format(input, config)
|
||||
|
||||
expected := " Column A Column B Column C\n"
|
||||
expected += " x y z"
|
||||
|
||||
if output != expected {
|
||||
t.Fatalf("\nexpected:\n%s\n\ngot:\n%s", expected, output)
|
||||
}
|
||||
}
|
||||
|
||||
func TestEmptyFieldReplacement(t *testing.T) {
|
||||
input := []string{
|
||||
"Column A | Column B | Column C",
|
||||
"x | | z",
|
||||
}
|
||||
|
||||
config := DefaultConfig()
|
||||
config.Empty = "<none>"
|
||||
output := Format(input, config)
|
||||
|
||||
expected := "Column A Column B Column C\n"
|
||||
expected += "x <none> z"
|
||||
|
||||
if output != expected {
|
||||
t.Fatalf("\nexpected:\n%s\n\ngot:\n%s", expected, output)
|
||||
}
|
||||
}
|
||||
|
||||
func TestEmptyConfigValues(t *testing.T) {
|
||||
input := []string{
|
||||
"Column A | Column B | Column C",
|
||||
"x | y | z",
|
||||
}
|
||||
|
||||
config := Config{}
|
||||
output := Format(input, &config)
|
||||
|
||||
expected := "Column A Column B Column C\n"
|
||||
expected += "x y z"
|
||||
|
||||
if output != expected {
|
||||
t.Fatalf("\nexpected:\n%s\n\ngot:\n%s", expected, output)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMergeConfig(t *testing.T) {
|
||||
conf1 := &Config{Delim: "a", Glue: "a", Prefix: "a", Empty: "a"}
|
||||
conf2 := &Config{Delim: "b", Glue: "b", Prefix: "b", Empty: "b"}
|
||||
conf3 := &Config{Delim: "c", Prefix: "c"}
|
||||
|
||||
m := MergeConfig(conf1, conf2)
|
||||
if m.Delim != "b" || m.Glue != "b" || m.Prefix != "b" || m.Empty != "b" {
|
||||
t.Fatalf("bad: %#v", m)
|
||||
}
|
||||
|
||||
m = MergeConfig(conf1, conf3)
|
||||
if m.Delim != "c" || m.Glue != "a" || m.Prefix != "c" || m.Empty != "a" {
|
||||
t.Fatalf("bad: %#v", m)
|
||||
}
|
||||
|
||||
m = MergeConfig(conf1, nil)
|
||||
if m.Delim != "a" || m.Glue != "a" || m.Prefix != "a" || m.Empty != "a" {
|
||||
t.Fatalf("bad: %#v", m)
|
||||
}
|
||||
|
||||
m = MergeConfig(conf1, &Config{})
|
||||
if m.Delim != "a" || m.Glue != "a" || m.Prefix != "a" || m.Empty != "a" {
|
||||
t.Fatalf("bad: %#v", m)
|
||||
}
|
||||
}
|
||||
154
plugins/buildpacks/subcommands.go
Normal file
154
plugins/buildpacks/subcommands.go
Normal file
@@ -0,0 +1,154 @@
|
||||
package buildpacks
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/dokku/dokku/plugins/common"
|
||||
)
|
||||
|
||||
// CommandAdd implements buildpacks:add
|
||||
func CommandAdd(args []string, index int) (err error) {
|
||||
var appName string
|
||||
appName, err = getAppName(args)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
buildpack := ""
|
||||
if len(args) >= 2 {
|
||||
buildpack = args[1]
|
||||
}
|
||||
if buildpack == "" {
|
||||
return errors.New("Must specify a buildpack to add")
|
||||
}
|
||||
|
||||
err = common.PropertyListAdd("buildpacks", appName, "buildpacks", buildpack, index)
|
||||
return
|
||||
}
|
||||
|
||||
// CommandClear implements buildpacks:clear
|
||||
func CommandClear(args []string) (err error) {
|
||||
var appName string
|
||||
appName, err = getAppName(args)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
common.PropertyDelete("buildpacks", appName, "buildpacks")
|
||||
return
|
||||
}
|
||||
|
||||
// CommandList implements buildpacks:list
|
||||
func CommandList(args []string) (err error) {
|
||||
var appName string
|
||||
appName, err = getAppName(args)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
buildpacks, err := common.PropertyListGet("buildpacks", appName, "buildpacks")
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
common.LogInfo1Quiet(fmt.Sprintf("%s buildpack urls", appName))
|
||||
for _, buildpack := range buildpacks {
|
||||
common.LogVerbose(buildpack)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// CommandRemove implements buildpacks:remove
|
||||
func CommandRemove(args []string, index int) (err error) {
|
||||
var appName string
|
||||
appName, err = getAppName(args)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
buildpack := ""
|
||||
if len(args) >= 2 {
|
||||
buildpack = args[1]
|
||||
}
|
||||
if index != 0 && buildpack != "" {
|
||||
err = errors.New("Please choose either index or Buildpack, but not both")
|
||||
return
|
||||
}
|
||||
|
||||
if index == 0 && buildpack == "" {
|
||||
err = errors.New("Must specify a buildpack to remove, either by index or URL")
|
||||
return
|
||||
}
|
||||
|
||||
var buildpacks []string
|
||||
buildpacks, err = common.PropertyListGet("buildpacks", appName, "buildpacks")
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if len(buildpacks) == 0 {
|
||||
err = fmt.Errorf("No buildpacks were found, next release on %s will detect buildpack normally", appName)
|
||||
return
|
||||
}
|
||||
|
||||
if index != 0 {
|
||||
var value string
|
||||
value, err = common.PropertyListGetByIndex("buildpacks", appName, "buildpacks", index-1)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
buildpack = value
|
||||
} else {
|
||||
_, err = common.PropertyListGetByValue("buildpacks", appName, "buildpacks", buildpack)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
common.LogInfo1Quiet(fmt.Sprintf("Removing %s", buildpack))
|
||||
err = common.PropertyListRemove("buildpacks", appName, "buildpacks", buildpack)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// CommandSet implements buildpacks:set
|
||||
func CommandSet(args []string, index int) (err error) {
|
||||
var appName string
|
||||
appName, err = getAppName(args)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
buildpack := ""
|
||||
if len(args) >= 2 {
|
||||
buildpack = args[1]
|
||||
}
|
||||
if buildpack == "" {
|
||||
return errors.New("Must specify a buildpack to add")
|
||||
}
|
||||
|
||||
if index > 0 {
|
||||
index--
|
||||
}
|
||||
|
||||
err = common.PropertyListSet("buildpacks", appName, "buildpacks", buildpack, index)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func getAppName(args []string) (appName string, err error) {
|
||||
if len(args) >= 1 {
|
||||
appName = args[0]
|
||||
} else {
|
||||
err = errors.New("Please specify an app to run the command on")
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
3
plugins/buildpacks/vendor/github.com/ryanuber/columnize/.travis.yml
generated
vendored
Normal file
3
plugins/buildpacks/vendor/github.com/ryanuber/columnize/.travis.yml
generated
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
language: go
|
||||
go:
|
||||
- tip
|
||||
20
plugins/buildpacks/vendor/github.com/ryanuber/columnize/LICENSE
generated
vendored
Normal file
20
plugins/buildpacks/vendor/github.com/ryanuber/columnize/LICENSE
generated
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
Copyright (c) 2016 Ryan Uber
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
"Software"), to deal in the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to
|
||||
permit persons to whom the Software is furnished to do so, subject to
|
||||
the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
69
plugins/buildpacks/vendor/github.com/ryanuber/columnize/README.md
generated
vendored
Normal file
69
plugins/buildpacks/vendor/github.com/ryanuber/columnize/README.md
generated
vendored
Normal file
@@ -0,0 +1,69 @@
|
||||
Columnize
|
||||
=========
|
||||
|
||||
Easy column-formatted output for golang
|
||||
|
||||
[](https://travis-ci.org/ryanuber/columnize)
|
||||
[](https://godoc.org/github.com/ryanuber/columnize)
|
||||
|
||||
Columnize is a really small Go package that makes building CLI's a little bit
|
||||
easier. In some CLI designs, you want to output a number similar items in a
|
||||
human-readable way with nicely aligned columns. However, figuring out how wide
|
||||
to make each column is a boring problem to solve and eats your valuable time.
|
||||
|
||||
Here is an example:
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/ryanuber/columnize"
|
||||
)
|
||||
|
||||
func main() {
|
||||
output := []string{
|
||||
"Name | Gender | Age",
|
||||
"Bob | Male | 38",
|
||||
"Sally | Female | 26",
|
||||
}
|
||||
result := columnize.SimpleFormat(output)
|
||||
fmt.Println(result)
|
||||
}
|
||||
```
|
||||
|
||||
As you can see, you just pass in a list of strings. And the result:
|
||||
|
||||
```
|
||||
Name Gender Age
|
||||
Bob Male 38
|
||||
Sally Female 26
|
||||
```
|
||||
|
||||
Columnize is tolerant of missing or empty fields, or even empty lines, so
|
||||
passing in extra lines for spacing should show up as you would expect.
|
||||
|
||||
Configuration
|
||||
=============
|
||||
|
||||
Columnize is configured using a `Config`, which can be obtained by calling the
|
||||
`DefaultConfig()` method. You can then tweak the settings in the resulting
|
||||
`Config`:
|
||||
|
||||
```
|
||||
config := columnize.DefaultConfig()
|
||||
config.Delim = "|"
|
||||
config.Glue = " "
|
||||
config.Prefix = ""
|
||||
config.Empty = ""
|
||||
```
|
||||
|
||||
* `Delim` is the string by which columns of **input** are delimited
|
||||
* `Glue` is the string by which columns of **output** are delimited
|
||||
* `Prefix` is a string by which each line of **output** is prefixed
|
||||
* `Empty` is a string used to replace blank values found in output
|
||||
|
||||
You can then pass the `Config` in using the `Format` method (signature below) to
|
||||
have text formatted to your liking.
|
||||
|
||||
See the [godoc](https://godoc.org/github.com/ryanuber/columnize) page for usage.
|
||||
178
plugins/buildpacks/vendor/github.com/ryanuber/columnize/columnize.go
generated
vendored
Normal file
178
plugins/buildpacks/vendor/github.com/ryanuber/columnize/columnize.go
generated
vendored
Normal file
@@ -0,0 +1,178 @@
|
||||
package columnize
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Config can be used to tune certain parameters which affect the way
|
||||
// in which Columnize will format output text.
|
||||
type Config struct {
|
||||
// The string by which the lines of input will be split.
|
||||
Delim string
|
||||
|
||||
// The string by which columns of output will be separated.
|
||||
Glue string
|
||||
|
||||
// The string by which columns of output will be prefixed.
|
||||
Prefix string
|
||||
|
||||
// A replacement string to replace empty fields
|
||||
Empty string
|
||||
}
|
||||
|
||||
// DefaultConfig returns a *Config with default values.
|
||||
func DefaultConfig() *Config {
|
||||
return &Config{
|
||||
Delim: "|",
|
||||
Glue: " ",
|
||||
Prefix: "",
|
||||
Empty: "",
|
||||
}
|
||||
}
|
||||
|
||||
// MergeConfig merges two config objects together and returns the resulting
|
||||
// configuration. Values from the right take precedence over the left side.
|
||||
func MergeConfig(a, b *Config) *Config {
|
||||
var result Config = *a
|
||||
|
||||
// Return quickly if either side was nil
|
||||
if a == nil || b == nil {
|
||||
return &result
|
||||
}
|
||||
|
||||
if b.Delim != "" {
|
||||
result.Delim = b.Delim
|
||||
}
|
||||
if b.Glue != "" {
|
||||
result.Glue = b.Glue
|
||||
}
|
||||
if b.Prefix != "" {
|
||||
result.Prefix = b.Prefix
|
||||
}
|
||||
if b.Empty != "" {
|
||||
result.Empty = b.Empty
|
||||
}
|
||||
|
||||
return &result
|
||||
}
|
||||
|
||||
// stringFormat, given a set of column widths and the number of columns in
|
||||
// the current line, returns a sprintf-style format string which can be used
|
||||
// to print output aligned properly with other lines using the same widths set.
|
||||
func stringFormat(c *Config, widths []int, columns int) string {
|
||||
// Create the buffer with an estimate of the length
|
||||
buf := bytes.NewBuffer(make([]byte, 0, (6+len(c.Glue))*columns))
|
||||
|
||||
// Start with the prefix, if any was given. The buffer will not return an
|
||||
// error so it does not need to be handled
|
||||
buf.WriteString(c.Prefix)
|
||||
|
||||
// Create the format string from the discovered widths
|
||||
for i := 0; i < columns && i < len(widths); i++ {
|
||||
if i == columns-1 {
|
||||
buf.WriteString("%s\n")
|
||||
} else {
|
||||
fmt.Fprintf(buf, "%%-%ds%s", widths[i], c.Glue)
|
||||
}
|
||||
}
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
// elementsFromLine returns a list of elements, each representing a single
|
||||
// item which will belong to a column of output.
|
||||
func elementsFromLine(config *Config, line string) []interface{} {
|
||||
seperated := strings.Split(line, config.Delim)
|
||||
elements := make([]interface{}, len(seperated))
|
||||
for i, field := range seperated {
|
||||
value := strings.TrimSpace(field)
|
||||
|
||||
// Apply the empty value, if configured.
|
||||
if value == "" && config.Empty != "" {
|
||||
value = config.Empty
|
||||
}
|
||||
elements[i] = value
|
||||
}
|
||||
return elements
|
||||
}
|
||||
|
||||
// runeLen calculates the number of visible "characters" in a string
|
||||
func runeLen(s string) int {
|
||||
l := 0
|
||||
for _ = range s {
|
||||
l++
|
||||
}
|
||||
return l
|
||||
}
|
||||
|
||||
// widthsFromLines examines a list of strings and determines how wide each
|
||||
// column should be considering all of the elements that need to be printed
|
||||
// within it.
|
||||
func widthsFromLines(config *Config, lines []string) []int {
|
||||
widths := make([]int, 0, 8)
|
||||
|
||||
for _, line := range lines {
|
||||
elems := elementsFromLine(config, line)
|
||||
for i := 0; i < len(elems); i++ {
|
||||
l := runeLen(elems[i].(string))
|
||||
if len(widths) <= i {
|
||||
widths = append(widths, l)
|
||||
} else if widths[i] < l {
|
||||
widths[i] = l
|
||||
}
|
||||
}
|
||||
}
|
||||
return widths
|
||||
}
|
||||
|
||||
// Format is the public-facing interface that takes a list of strings and
|
||||
// returns nicely aligned column-formatted text.
|
||||
func Format(lines []string, config *Config) string {
|
||||
conf := MergeConfig(DefaultConfig(), config)
|
||||
widths := widthsFromLines(conf, lines)
|
||||
|
||||
// Estimate the buffer size
|
||||
glueSize := len(conf.Glue)
|
||||
var size int
|
||||
for _, w := range widths {
|
||||
size += w + glueSize
|
||||
}
|
||||
size *= len(lines)
|
||||
|
||||
// Create the buffer
|
||||
buf := bytes.NewBuffer(make([]byte, 0, size))
|
||||
|
||||
// Create a cache for the string formats
|
||||
fmtCache := make(map[int]string, 16)
|
||||
|
||||
// Create the formatted output using the format string
|
||||
for _, line := range lines {
|
||||
elems := elementsFromLine(conf, line)
|
||||
|
||||
// Get the string format using cache
|
||||
numElems := len(elems)
|
||||
stringfmt, ok := fmtCache[numElems]
|
||||
if !ok {
|
||||
stringfmt = stringFormat(conf, widths, numElems)
|
||||
fmtCache[numElems] = stringfmt
|
||||
}
|
||||
|
||||
fmt.Fprintf(buf, stringfmt, elems...)
|
||||
}
|
||||
|
||||
// Get the string result
|
||||
result := buf.String()
|
||||
|
||||
// Remove trailing newline without removing leading/trailing space
|
||||
if n := len(result); n > 0 && result[n-1] == '\n' {
|
||||
result = result[:n-1]
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// SimpleFormat is a convenience function to format text with the defaults.
|
||||
func SimpleFormat(lines []string) string {
|
||||
return Format(lines, nil)
|
||||
}
|
||||
306
plugins/buildpacks/vendor/github.com/ryanuber/columnize/columnize_test.go
generated
vendored
Normal file
306
plugins/buildpacks/vendor/github.com/ryanuber/columnize/columnize_test.go
generated
vendored
Normal file
@@ -0,0 +1,306 @@
|
||||
package columnize
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
crand "crypto/rand"
|
||||
)
|
||||
|
||||
func TestListOfStringsInput(t *testing.T) {
|
||||
input := []string{
|
||||
"Column A | Column B | Column C",
|
||||
"x | y | z",
|
||||
}
|
||||
|
||||
config := DefaultConfig()
|
||||
output := Format(input, config)
|
||||
|
||||
expected := "Column A Column B Column C\n"
|
||||
expected += "x y z"
|
||||
|
||||
if output != expected {
|
||||
t.Fatalf("\nexpected:\n%s\n\ngot:\n%s", expected, output)
|
||||
}
|
||||
}
|
||||
|
||||
func TestEmptyLinesOutput(t *testing.T) {
|
||||
input := []string{
|
||||
"Column A | Column B | Column C",
|
||||
"",
|
||||
"x | y | z",
|
||||
}
|
||||
|
||||
config := DefaultConfig()
|
||||
output := Format(input, config)
|
||||
|
||||
expected := "Column A Column B Column C\n"
|
||||
expected += "\n"
|
||||
expected += "x y z"
|
||||
|
||||
if output != expected {
|
||||
t.Fatalf("\nexpected:\n%s\n\ngot:\n%s", expected, output)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLeadingSpacePreserved(t *testing.T) {
|
||||
input := []string{
|
||||
"| Column B | Column C",
|
||||
"x | y | z",
|
||||
}
|
||||
|
||||
config := DefaultConfig()
|
||||
output := Format(input, config)
|
||||
|
||||
expected := " Column B Column C\n"
|
||||
expected += "x y z"
|
||||
|
||||
if output != expected {
|
||||
t.Fatalf("\nexpected:\n%s\n\ngot:\n%s", expected, output)
|
||||
}
|
||||
}
|
||||
|
||||
func TestColumnWidthCalculator(t *testing.T) {
|
||||
input := []string{
|
||||
"Column A | Column B | Column C",
|
||||
"Longer than A | Longer than B | Longer than C",
|
||||
"short | short | short",
|
||||
}
|
||||
|
||||
config := DefaultConfig()
|
||||
output := Format(input, config)
|
||||
|
||||
expected := "Column A Column B Column C\n"
|
||||
expected += "Longer than A Longer than B Longer than C\n"
|
||||
expected += "short short short"
|
||||
|
||||
if output != expected {
|
||||
printableProof := fmt.Sprintf("\nGot: %+q", output)
|
||||
printableProof += fmt.Sprintf("\nExpected: %+q", expected)
|
||||
t.Fatalf("\n%s", printableProof)
|
||||
}
|
||||
}
|
||||
|
||||
func TestColumnWidthCalculatorNonASCII(t *testing.T) {
|
||||
input := []string{
|
||||
"Column A | Column B | Column C",
|
||||
"⌘⌘⌘⌘⌘⌘⌘⌘ | Longer than B | Longer than C",
|
||||
"short | short | short",
|
||||
}
|
||||
|
||||
config := DefaultConfig()
|
||||
output := Format(input, config)
|
||||
|
||||
expected := "Column A Column B Column C\n"
|
||||
expected += "⌘⌘⌘⌘⌘⌘⌘⌘ Longer than B Longer than C\n"
|
||||
expected += "short short short"
|
||||
|
||||
if output != expected {
|
||||
printableProof := fmt.Sprintf("\nGot: %+q", output)
|
||||
printableProof += fmt.Sprintf("\nExpected: %+q", expected)
|
||||
t.Fatalf("\n%s", printableProof)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkColumnWidthCalculator(b *testing.B) {
|
||||
// Generate the input
|
||||
input := []string{
|
||||
"UUID A | UUID B | UUID C | Column D | Column E",
|
||||
}
|
||||
|
||||
format := "%s|%s|%s|%s"
|
||||
short := "short"
|
||||
|
||||
uuid := func() string {
|
||||
buf := make([]byte, 16)
|
||||
if _, err := crand.Read(buf); err != nil {
|
||||
panic(fmt.Errorf("failed to read random bytes: %v", err))
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%08x-%04x-%04x-%04x-%12x",
|
||||
buf[0:4],
|
||||
buf[4:6],
|
||||
buf[6:8],
|
||||
buf[8:10],
|
||||
buf[10:16])
|
||||
}
|
||||
|
||||
for i := 0; i < 1000; i++ {
|
||||
l := fmt.Sprintf(format, uuid()[:8], uuid()[:12], uuid(), short, short)
|
||||
input = append(input, l)
|
||||
}
|
||||
|
||||
config := DefaultConfig()
|
||||
b.ResetTimer()
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
Format(input, config)
|
||||
}
|
||||
}
|
||||
|
||||
func TestVariedInputSpacing(t *testing.T) {
|
||||
input := []string{
|
||||
"Column A |Column B| Column C",
|
||||
"x|y| z",
|
||||
}
|
||||
|
||||
config := DefaultConfig()
|
||||
output := Format(input, config)
|
||||
|
||||
expected := "Column A Column B Column C\n"
|
||||
expected += "x y z"
|
||||
|
||||
if output != expected {
|
||||
t.Fatalf("\nexpected:\n%s\n\ngot:\n%s", expected, output)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUnmatchedColumnCounts(t *testing.T) {
|
||||
input := []string{
|
||||
"Column A | Column B | Column C",
|
||||
"Value A | Value B",
|
||||
"Value A | Value B | Value C | Value D",
|
||||
}
|
||||
|
||||
config := DefaultConfig()
|
||||
output := Format(input, config)
|
||||
|
||||
expected := "Column A Column B Column C\n"
|
||||
expected += "Value A Value B\n"
|
||||
expected += "Value A Value B Value C Value D"
|
||||
|
||||
if output != expected {
|
||||
t.Fatalf("\nexpected:\n%s\n\ngot:\n%s", expected, output)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAlternateDelimiter(t *testing.T) {
|
||||
input := []string{
|
||||
"Column | A % Column | B % Column | C",
|
||||
"Value A % Value B % Value C",
|
||||
}
|
||||
|
||||
config := DefaultConfig()
|
||||
config.Delim = "%"
|
||||
output := Format(input, config)
|
||||
|
||||
expected := "Column | A Column | B Column | C\n"
|
||||
expected += "Value A Value B Value C"
|
||||
|
||||
if output != expected {
|
||||
t.Fatalf("\nexpected:\n%s\n\ngot:\n%s", expected, output)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAlternateSpacingString(t *testing.T) {
|
||||
input := []string{
|
||||
"Column A | Column B | Column C",
|
||||
"x | y | z",
|
||||
}
|
||||
|
||||
config := DefaultConfig()
|
||||
config.Glue = " "
|
||||
output := Format(input, config)
|
||||
|
||||
expected := "Column A Column B Column C\n"
|
||||
expected += "x y z"
|
||||
|
||||
if output != expected {
|
||||
t.Fatalf("\nexpected:\n%s\n\ngot:\n%s", expected, output)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSimpleFormat(t *testing.T) {
|
||||
input := []string{
|
||||
"Column A | Column B | Column C",
|
||||
"x | y | z",
|
||||
}
|
||||
|
||||
output := SimpleFormat(input)
|
||||
|
||||
expected := "Column A Column B Column C\n"
|
||||
expected += "x y z"
|
||||
|
||||
if output != expected {
|
||||
t.Fatalf("\nexpected:\n%s\n\ngot:\n%s", expected, output)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAlternatePrefixString(t *testing.T) {
|
||||
input := []string{
|
||||
"Column A | Column B | Column C",
|
||||
"x | y | z",
|
||||
}
|
||||
|
||||
config := DefaultConfig()
|
||||
config.Prefix = " "
|
||||
output := Format(input, config)
|
||||
|
||||
expected := " Column A Column B Column C\n"
|
||||
expected += " x y z"
|
||||
|
||||
if output != expected {
|
||||
t.Fatalf("\nexpected:\n%s\n\ngot:\n%s", expected, output)
|
||||
}
|
||||
}
|
||||
|
||||
func TestEmptyFieldReplacement(t *testing.T) {
|
||||
input := []string{
|
||||
"Column A | Column B | Column C",
|
||||
"x | | z",
|
||||
}
|
||||
|
||||
config := DefaultConfig()
|
||||
config.Empty = "<none>"
|
||||
output := Format(input, config)
|
||||
|
||||
expected := "Column A Column B Column C\n"
|
||||
expected += "x <none> z"
|
||||
|
||||
if output != expected {
|
||||
t.Fatalf("\nexpected:\n%s\n\ngot:\n%s", expected, output)
|
||||
}
|
||||
}
|
||||
|
||||
func TestEmptyConfigValues(t *testing.T) {
|
||||
input := []string{
|
||||
"Column A | Column B | Column C",
|
||||
"x | y | z",
|
||||
}
|
||||
|
||||
config := Config{}
|
||||
output := Format(input, &config)
|
||||
|
||||
expected := "Column A Column B Column C\n"
|
||||
expected += "x y z"
|
||||
|
||||
if output != expected {
|
||||
t.Fatalf("\nexpected:\n%s\n\ngot:\n%s", expected, output)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMergeConfig(t *testing.T) {
|
||||
conf1 := &Config{Delim: "a", Glue: "a", Prefix: "a", Empty: "a"}
|
||||
conf2 := &Config{Delim: "b", Glue: "b", Prefix: "b", Empty: "b"}
|
||||
conf3 := &Config{Delim: "c", Prefix: "c"}
|
||||
|
||||
m := MergeConfig(conf1, conf2)
|
||||
if m.Delim != "b" || m.Glue != "b" || m.Prefix != "b" || m.Empty != "b" {
|
||||
t.Fatalf("bad: %#v", m)
|
||||
}
|
||||
|
||||
m = MergeConfig(conf1, conf3)
|
||||
if m.Delim != "c" || m.Glue != "a" || m.Prefix != "c" || m.Empty != "a" {
|
||||
t.Fatalf("bad: %#v", m)
|
||||
}
|
||||
|
||||
m = MergeConfig(conf1, nil)
|
||||
if m.Delim != "a" || m.Glue != "a" || m.Prefix != "a" || m.Empty != "a" {
|
||||
t.Fatalf("bad: %#v", m)
|
||||
}
|
||||
|
||||
m = MergeConfig(conf1, &Config{})
|
||||
if m.Delim != "a" || m.Glue != "a" || m.Prefix != "a" || m.Empty != "a" {
|
||||
t.Fatalf("bad: %#v", m)
|
||||
}
|
||||
}
|
||||
@@ -19,7 +19,7 @@ func LogInfo1(text string) {
|
||||
|
||||
// LogInfo1Quiet is the info1 header formatter (with quiet option)
|
||||
func LogInfo1Quiet(text string) {
|
||||
if os.Getenv("DOKKU_QUIET_OUTPUT") != "" {
|
||||
if os.Getenv("DOKKU_QUIET_OUTPUT") == "" {
|
||||
LogInfo1(text)
|
||||
}
|
||||
}
|
||||
@@ -45,7 +45,7 @@ func LogVerbose(text string) {
|
||||
// LogVerboseQuiet is the verbose log formatter
|
||||
// prints indented text to stdout (with quiet option)
|
||||
func LogVerboseQuiet(text string) {
|
||||
if os.Getenv("DOKKU_QUIET_OUTPUT") != "" {
|
||||
if os.Getenv("DOKKU_QUIET_OUTPUT") == "" {
|
||||
LogVerbose(text)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/user"
|
||||
"path"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
@@ -34,34 +37,37 @@ func CommandPropertySet(pluginName, appName, property, value string, properties
|
||||
PropertyWrite(pluginName, appName, property, value)
|
||||
} else {
|
||||
LogInfo2Quiet(fmt.Sprintf("Unsetting %s", property))
|
||||
PropertyDelete(pluginName, appName, property)
|
||||
err := PropertyDelete(pluginName, appName, property)
|
||||
if err != nil {
|
||||
LogFail(err.Error())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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}, "/")
|
||||
func PropertyDelete(pluginName string, appName string, property string) error {
|
||||
propertyPath := getPropertyPath(pluginName, appName, property)
|
||||
if err := os.Remove(propertyPath); err != nil {
|
||||
LogFail(fmt.Sprintf("Unable to remove %s property %s.%s", pluginName, appName, property))
|
||||
return fmt.Errorf("Unable to remove %s property %s.%s", pluginName, appName, property)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// PropertyDestroy destroys the plugin properties for an app
|
||||
func PropertyDestroy(pluginName string, appName string) {
|
||||
func PropertyDestroy(pluginName string, appName string) error {
|
||||
if appName == "_all_" {
|
||||
pluginConfigPath := getPluginConfigPath(pluginName)
|
||||
os.RemoveAll(pluginConfigPath)
|
||||
} else {
|
||||
pluginAppConfigRoot := getPluginAppPropertyPath(pluginName, appName)
|
||||
os.RemoveAll(pluginAppConfigRoot)
|
||||
return os.RemoveAll(pluginConfigPath)
|
||||
}
|
||||
|
||||
pluginAppConfigRoot := getPluginAppPropertyPath(pluginName, appName)
|
||||
return 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}, "/")
|
||||
propertyPath := getPropertyPath(pluginName, appName, property)
|
||||
_, err := os.Stat(propertyPath)
|
||||
return !os.IsNotExist(err)
|
||||
}
|
||||
@@ -77,9 +83,7 @@ func PropertyGetDefault(pluginName, appName, property, defaultValue string) (val
|
||||
return
|
||||
}
|
||||
|
||||
pluginAppConfigRoot := getPluginAppPropertyPath(pluginName, appName)
|
||||
propertyPath := strings.Join([]string{pluginAppConfigRoot, property}, "/")
|
||||
|
||||
propertyPath := getPropertyPath(pluginName, appName, property)
|
||||
b, err := ioutil.ReadFile(propertyPath)
|
||||
if err != nil {
|
||||
LogWarn(fmt.Sprintf("Unable to read %s property %s.%s", pluginName, appName, property))
|
||||
@@ -89,23 +93,240 @@ func PropertyGetDefault(pluginName, appName, property, defaultValue string) (val
|
||||
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()))
|
||||
// PropertyListAdd adds a property to a list at an optionally specified index
|
||||
func PropertyListAdd(pluginName string, appName string, property string, value string, index int) error {
|
||||
if err := PropertyTouch(pluginName, appName, property); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
scannedLines, err := PropertyListGet(pluginName, appName, property)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
value = strings.TrimSpace(value)
|
||||
|
||||
var lines []string
|
||||
for i, line := range scannedLines {
|
||||
if index != 0 && i == (index-1) {
|
||||
lines = append(lines, value)
|
||||
}
|
||||
lines = append(lines, line)
|
||||
}
|
||||
|
||||
if index == 0 || index > len(scannedLines) {
|
||||
lines = append(lines, value)
|
||||
}
|
||||
|
||||
propertyPath := getPropertyPath(pluginName, appName, property)
|
||||
file, err := os.OpenFile(propertyPath, os.O_RDWR|os.O_TRUNC, 0600)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
w := bufio.NewWriter(file)
|
||||
for _, line := range lines {
|
||||
fmt.Fprintln(w, line)
|
||||
}
|
||||
if err = w.Flush(); err != nil {
|
||||
return fmt.Errorf("Unable to write %s config value %s.%s: %s", pluginName, appName, property, err.Error())
|
||||
}
|
||||
|
||||
file.Chmod(0600)
|
||||
setPermissions(propertyPath, 0600)
|
||||
return nil
|
||||
}
|
||||
|
||||
// PropertyListGet returns a property list
|
||||
func PropertyListGet(pluginName string, appName string, property string) (lines []string, err error) {
|
||||
if !PropertyExists(pluginName, appName, property) {
|
||||
return lines, nil
|
||||
}
|
||||
|
||||
propertyPath := getPropertyPath(pluginName, appName, property)
|
||||
file, err := os.Open(propertyPath)
|
||||
if err != nil {
|
||||
return lines, err
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
scanner := bufio.NewScanner(file)
|
||||
for scanner.Scan() {
|
||||
lines = append(lines, scanner.Text())
|
||||
}
|
||||
|
||||
if err = scanner.Err(); err != nil {
|
||||
return lines, fmt.Errorf("Unable to read %s config value for %s.%s: %s", pluginName, appName, property, err.Error())
|
||||
}
|
||||
|
||||
return lines, nil
|
||||
}
|
||||
|
||||
// PropertyListGetByIndex returns an entry within property list by index
|
||||
func PropertyListGetByIndex(pluginName string, appName string, property string, index int) (propertyValue string, err error) {
|
||||
lines, err := PropertyListGet(pluginName, appName, property)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
found := false
|
||||
for i, line := range lines {
|
||||
if i == index {
|
||||
propertyValue = line
|
||||
found = true
|
||||
}
|
||||
}
|
||||
|
||||
if !found {
|
||||
err = errors.New("Index not found")
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// PropertyListGetByValue returns an entry within property list by value
|
||||
func PropertyListGetByValue(pluginName string, appName string, property string, value string) (propertyValue string, err error) {
|
||||
lines, err := PropertyListGet(pluginName, appName, property)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
found := false
|
||||
for _, line := range lines {
|
||||
if line == value {
|
||||
propertyValue = line
|
||||
found = true
|
||||
}
|
||||
}
|
||||
|
||||
if !found {
|
||||
err = errors.New("Value not found")
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// PropertyListRemove removes a value from a property list
|
||||
func PropertyListRemove(pluginName string, appName string, property string, value string) error {
|
||||
lines, err := PropertyListGet(pluginName, appName, property)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
propertyPath := getPropertyPath(pluginName, appName, property)
|
||||
file, err := os.OpenFile(propertyPath, os.O_RDWR|os.O_TRUNC, 0600)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
found := false
|
||||
w := bufio.NewWriter(file)
|
||||
for _, line := range lines {
|
||||
if line == value {
|
||||
found = true
|
||||
continue
|
||||
}
|
||||
fmt.Fprintln(w, line)
|
||||
}
|
||||
if err = w.Flush(); err != nil {
|
||||
return fmt.Errorf("Unable to write %s config value %s.%s: %s", pluginName, appName, property, err.Error())
|
||||
}
|
||||
|
||||
file.Chmod(0600)
|
||||
setPermissions(propertyPath, 0600)
|
||||
|
||||
if !found {
|
||||
return errors.New("Property not found, nothing was removed")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// PropertyListSet sets a value within a property list at a specified index
|
||||
func PropertyListSet(pluginName string, appName string, property string, value string, index int) error {
|
||||
if err := PropertyTouch(pluginName, appName, property); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
scannedLines, err := PropertyListGet(pluginName, appName, property)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
value = strings.TrimSpace(value)
|
||||
|
||||
var lines []string
|
||||
if index >= len(scannedLines) {
|
||||
for _, line := range scannedLines {
|
||||
lines = append(lines, line)
|
||||
}
|
||||
lines = append(lines, value)
|
||||
} else {
|
||||
for i, line := range scannedLines {
|
||||
if i == index {
|
||||
lines = append(lines, value)
|
||||
} else {
|
||||
lines = append(lines, line)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
propertyPath := getPropertyPath(pluginName, appName, property)
|
||||
file, err := os.OpenFile(propertyPath, os.O_RDWR|os.O_TRUNC, 0600)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
w := bufio.NewWriter(file)
|
||||
for _, line := range lines {
|
||||
fmt.Fprintln(w, line)
|
||||
}
|
||||
if err = w.Flush(); err != nil {
|
||||
return fmt.Errorf("Unable to write %s config value %s.%s: %s", pluginName, appName, property, err.Error())
|
||||
}
|
||||
|
||||
file.Chmod(0600)
|
||||
setPermissions(propertyPath, 0600)
|
||||
return nil
|
||||
}
|
||||
|
||||
// PropertyTouch ensures a given application property file exists
|
||||
func PropertyTouch(pluginName string, appName string, property string) error {
|
||||
if err := makePluginAppPropertyPath(pluginName, appName); err != nil {
|
||||
return fmt.Errorf("Unable to create %s config directory for %s: %s", pluginName, appName, err.Error())
|
||||
}
|
||||
|
||||
propertyPath := getPropertyPath(pluginName, appName, property)
|
||||
if PropertyExists(pluginName, appName, property) {
|
||||
return nil
|
||||
}
|
||||
|
||||
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()))
|
||||
return fmt.Errorf("Unable to write %s config value %s.%s: %s", pluginName, appName, property, err.Error())
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// PropertyWrite writes a value for a given application property
|
||||
func PropertyWrite(pluginName string, appName string, property string, value string) error {
|
||||
if err := PropertyTouch(pluginName, appName, property); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
propertyPath := getPropertyPath(pluginName, appName, property)
|
||||
file, err := os.Create(propertyPath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("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)
|
||||
return nil
|
||||
}
|
||||
|
||||
// PropertySetup creates the plugin config root
|
||||
@@ -117,18 +338,23 @@ func PropertySetup(pluginName string) (err error) {
|
||||
return setPermissions(pluginConfigRoot, 0755)
|
||||
}
|
||||
|
||||
func getPropertyPath(pluginName string, appName string, property string) string {
|
||||
pluginAppConfigRoot := getPluginAppPropertyPath(pluginName, appName)
|
||||
return path.Join(pluginAppConfigRoot, property)
|
||||
}
|
||||
|
||||
// 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}, "/")
|
||||
return path.Join(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}, "/")
|
||||
return path.Join(MustGetEnv("DOKKU_LIB_ROOT"), "config", pluginName)
|
||||
}
|
||||
|
||||
// makePropertyPath ensures that a property path exists
|
||||
func makePropertyPath(pluginName string, appName string) (err error) {
|
||||
// makePluginAppPropertyPath ensures that a property path exists
|
||||
func makePluginAppPropertyPath(pluginName string, appName string) (err error) {
|
||||
pluginAppConfigRoot := getPluginAppPropertyPath(pluginName, appName)
|
||||
if err = os.MkdirAll(pluginAppConfigRoot, 0755); err != nil {
|
||||
return
|
||||
|
||||
@@ -67,11 +67,11 @@ func main() {
|
||||
}
|
||||
|
||||
func usage() {
|
||||
fmt.Println(helpHeader)
|
||||
config := columnize.DefaultConfig()
|
||||
config.Delim = ","
|
||||
config.Prefix = " "
|
||||
config.Empty = ""
|
||||
content := strings.Split(helpContent, "\n")[1:]
|
||||
fmt.Println(helpHeader)
|
||||
fmt.Println(columnize.Format(content, config))
|
||||
}
|
||||
|
||||
@@ -55,11 +55,11 @@ func main() {
|
||||
}
|
||||
|
||||
func usage() {
|
||||
fmt.Println(helpHeader)
|
||||
config := columnize.DefaultConfig()
|
||||
config.Delim = ","
|
||||
config.Prefix = " "
|
||||
config.Empty = ""
|
||||
content := strings.Split(helpContent, "\n")[1:]
|
||||
fmt.Println(helpHeader)
|
||||
fmt.Println(columnize.Format(content, config))
|
||||
}
|
||||
|
||||
@@ -23,10 +23,14 @@ func main() {
|
||||
}
|
||||
if proxy.IsAppProxyEnabled(appName) {
|
||||
common.LogVerboseQuiet("Setting %s network property 'bind-all-interfaces' to false")
|
||||
common.PropertyWrite("network", appName, "bind-all-interfaces", "false")
|
||||
if err := common.PropertyWrite("network", appName, "bind-all-interfaces", "false"); err != nil {
|
||||
common.LogWarn(err.Error())
|
||||
}
|
||||
} else {
|
||||
common.LogVerboseQuiet("Setting %s network property 'bind-all-interfaces' to true")
|
||||
common.PropertyWrite("network", appName, "bind-all-interfaces", "true")
|
||||
if err := common.PropertyWrite("network", appName, "bind-all-interfaces", "true"); err != nil {
|
||||
common.LogWarn(err.Error())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,5 +11,8 @@ func main() {
|
||||
flag.Parse()
|
||||
appName := flag.Arg(0)
|
||||
|
||||
common.PropertyWrite("network", appName, "bind-all-interfaces", "false")
|
||||
err := common.PropertyWrite("network", appName, "bind-all-interfaces", "false")
|
||||
if err != nil {
|
||||
common.LogWarn(err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,5 +11,8 @@ func main() {
|
||||
flag.Parse()
|
||||
appName := flag.Arg(0)
|
||||
|
||||
common.PropertyDestroy("network", appName)
|
||||
err := common.PropertyDestroy("network", appName)
|
||||
if err != nil {
|
||||
common.LogFail(err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,11 +53,11 @@ func main() {
|
||||
}
|
||||
|
||||
func usage() {
|
||||
fmt.Println(helpHeader)
|
||||
config := columnize.DefaultConfig()
|
||||
config.Delim = ","
|
||||
config.Prefix = " "
|
||||
config.Empty = ""
|
||||
content := strings.Split(helpContent, "\n")[1:]
|
||||
fmt.Println(helpHeader)
|
||||
fmt.Println(columnize.Format(content, config))
|
||||
}
|
||||
|
||||
211
tests/unit/30_buildpacks.bats
Normal file
211
tests/unit/30_buildpacks.bats
Normal file
@@ -0,0 +1,211 @@
|
||||
#!/usr/bin/env bats
|
||||
|
||||
load test_helper
|
||||
|
||||
setup() {
|
||||
global_setup
|
||||
deploy_app
|
||||
}
|
||||
|
||||
teardown() {
|
||||
destroy_app
|
||||
global_teardown
|
||||
}
|
||||
|
||||
@test "(buildpacks) buildpacks:help" {
|
||||
run /bin/bash -c "dokku buildpacks"
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_output_contains "Manages buildpacks settings for an app"
|
||||
help_output="$output"
|
||||
|
||||
run /bin/bash -c "dokku buildpacks:help"
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_output_contains "Manages buildpacks settings for an app"
|
||||
assert_output "$help_output"
|
||||
}
|
||||
|
||||
@test "(buildpacks) buildpacks:add" {
|
||||
run /bin/bash -c "dokku buildpacks:add $TEST_APP heroku/nodejs"
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_success
|
||||
|
||||
run /bin/bash -c "dokku --quiet buildpacks:list $TEST_APP | xargs"
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_output_contains "heroku/nodejs"
|
||||
|
||||
run /bin/bash -c "dokku buildpacks:add $TEST_APP heroku/ruby"
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_success
|
||||
|
||||
run /bin/bash -c "dokku --quiet buildpacks:list $TEST_APP | xargs"
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_output "heroku/nodejs heroku/ruby"
|
||||
|
||||
run /bin/bash -c "dokku buildpacks:add --index 1 $TEST_APP heroku/golang"
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_success
|
||||
|
||||
run /bin/bash -c "dokku --quiet buildpacks:list $TEST_APP | xargs"
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_output "heroku/golang heroku/nodejs heroku/ruby"
|
||||
|
||||
run /bin/bash -c "dokku buildpacks:add --index 2 $TEST_APP heroku/python"
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_success
|
||||
|
||||
run /bin/bash -c "dokku --quiet buildpacks:list $TEST_APP | xargs"
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_output "heroku/golang heroku/python heroku/nodejs heroku/ruby"
|
||||
|
||||
run /bin/bash -c "dokku buildpacks:add --index 100 $TEST_APP heroku/php"
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_success
|
||||
|
||||
run /bin/bash -c "dokku --quiet buildpacks:list $TEST_APP | xargs"
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_output "heroku/golang heroku/python heroku/nodejs heroku/ruby heroku/php"
|
||||
}
|
||||
|
||||
@test "(buildpacks) buildpacks:set" {
|
||||
run /bin/bash -c "dokku buildpacks:set $TEST_APP heroku/nodejs"
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_success
|
||||
|
||||
run /bin/bash -c "dokku --quiet buildpacks:list $TEST_APP | xargs"
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_output_contains "heroku/nodejs"
|
||||
|
||||
run /bin/bash -c "dokku buildpacks:set $TEST_APP heroku/ruby"
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_success
|
||||
|
||||
run /bin/bash -c "dokku --quiet buildpacks:list $TEST_APP | xargs"
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_output "heroku/ruby"
|
||||
|
||||
run /bin/bash -c "dokku buildpacks:set --index 1 $TEST_APP heroku/golang"
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_success
|
||||
|
||||
run /bin/bash -c "dokku --quiet buildpacks:list $TEST_APP | xargs"
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_output "heroku/golang"
|
||||
|
||||
run /bin/bash -c "dokku buildpacks:set --index 2 $TEST_APP heroku/python"
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_success
|
||||
|
||||
run /bin/bash -c "dokku --quiet buildpacks:list $TEST_APP | xargs"
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_output "heroku/golang heroku/python"
|
||||
|
||||
run /bin/bash -c "dokku buildpacks:set --index 100 $TEST_APP heroku/php"
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_success
|
||||
|
||||
run /bin/bash -c "dokku --quiet buildpacks:list $TEST_APP | xargs"
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_output "heroku/golang heroku/python heroku/php"
|
||||
}
|
||||
|
||||
@test "(buildpacks) buildpacks:remove" {
|
||||
run /bin/bash -c "dokku buildpacks:set $TEST_APP heroku/nodejs"
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_success
|
||||
|
||||
run /bin/bash -c "dokku buildpacks:set --index 2 $TEST_APP heroku/ruby"
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_success
|
||||
|
||||
run /bin/bash -c "dokku --quiet buildpacks:list $TEST_APP | xargs"
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_output_contains "heroku/nodejs heroku/ruby"
|
||||
|
||||
run /bin/bash -c "dokku buildpacks:remove $TEST_APP heroku/nodejs"
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_success
|
||||
|
||||
run /bin/bash -c "dokku --quiet buildpacks:list $TEST_APP | xargs"
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_output_contains "heroku/ruby"
|
||||
|
||||
run /bin/bash -c "dokku buildpacks:remove $TEST_APP heroku/php"
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_failure
|
||||
|
||||
run /bin/bash -c "dokku buildpacks:remove $TEST_APP heroku/ruby"
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_success
|
||||
|
||||
run /bin/bash -c "dokku --quiet buildpacks:list $TEST_APP"
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_output_not_exists
|
||||
}
|
||||
|
||||
|
||||
@test "(buildpacks) buildpacks:clear" {
|
||||
run /bin/bash -c "dokku buildpacks:set $TEST_APP heroku/nodejs"
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_success
|
||||
|
||||
run /bin/bash -c "dokku buildpacks:set --index 2 $TEST_APP heroku/ruby"
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_success
|
||||
|
||||
run /bin/bash -c "dokku buildpacks:clear $TEST_APP"
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_success
|
||||
|
||||
run /bin/bash -c "dokku --quiet buildpacks:list $TEST_APP"
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_output_not_exists
|
||||
|
||||
run /bin/bash -c "dokku buildpacks:clear $TEST_APP"
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_success
|
||||
|
||||
run /bin/bash -c "dokku buildpacks:clear $TEST_APP"
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_success
|
||||
|
||||
run /bin/bash -c "dokku --quiet buildpacks:list $TEST_APP"
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_output_not_exists
|
||||
}
|
||||
@@ -82,6 +82,12 @@ assert_output_exists() {
|
||||
[[ -n "$output" ]] || flunk "expected output, found none"
|
||||
}
|
||||
|
||||
# ShellCheck doesn't know about $output from Bats
|
||||
# shellcheck disable=SC2154
|
||||
assert_output_not_exists() {
|
||||
[[ -z "$output" ]] || flunk "expected no output, found some"
|
||||
}
|
||||
|
||||
# ShellCheck doesn't know about $output from Bats
|
||||
# shellcheck disable=SC2154
|
||||
assert_output_contains() {
|
||||
|
||||
Reference in New Issue
Block a user