From 2f460f48f8c02c209b1559d34e862d4e33da309b Mon Sep 17 00:00:00 2001 From: Mikkel Oscar Lyderik Date: Thu, 28 Jan 2016 19:08:18 +0100 Subject: [PATCH 01/12] Change deprecated argument -d -> daemon Running `docker -d` is deprecated since [1]. [1]: https://docs.docker.com/engine/misc/deprecated/#old-command-line-options --- main.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main.go b/main.go index c29ef5b..3e22df8 100644 --- a/main.go +++ b/main.go @@ -93,7 +93,7 @@ func main() { } go func() { - args := []string{"-d"} + args := []string{"daemon"} if len(vargs.Storage) != 0 { args = append(args, "-s", vargs.Storage) From 4023d446745e1926fad3d5c29079780501126e31 Mon Sep 17 00:00:00 2001 From: Martin Honermeyer Date: Sat, 2 Jan 2016 18:02:08 +0100 Subject: [PATCH 02/12] Use /drone/docker for the Docker daemon's graph directory This allows caching the whole Docker graph with the cache plugin. (Caching works efficiently with the overlay storage driver at least. Probably shouldn't be used with other storage drivers.) --- DOCS.md | 32 +++++++++++++++++++++++++++++--- main.go | 2 +- 2 files changed, 30 insertions(+), 4 deletions(-) diff --git a/DOCS.md b/DOCS.md index 8c549d2..3049624 100644 --- a/DOCS.md +++ b/DOCS.md @@ -77,10 +77,36 @@ publish: build_args: - HTTP_PROXY=http://yourproxy.com ``` - -## Layer Caching -The Drone build environment is, by default, ephemeral meaning that you layers are not saved between builds. The below example combines Drone's caching feature and Docker's `save` and `load` capabilities to cache and restore image layers between builds: +## Caching + +The Drone build environment is, by default, ephemeral meaning that you layers are not saved between builds. There are two methods for caching layers. + +### Graph directory caching + +This is the preferred method when using the `overlay` storage driver. Just use Drone's caching feature to backup and restore the directory `/drone/docker`, as shown in the following example: + +```yaml +publish: + docker: + username: kevinbacon + password: pa55word + email: kevin.bacon@mail.com + repo: foo/bar + tag: + - latest + - "1.0.1" + +cache: + mount: + - /drone/docker +``` + +NOTE: This probably won't work correctly with the `btrfs` driver, and it will be very inefficient with the `devicemapper` driver. Please make sure to use the `overlay` storage driver with this method. + +### Layer Caching + +The below example combines Drone's caching feature and Docker's `save` and `load` capabilities to cache and restore image layers between builds: ```yaml publish: diff --git a/main.go b/main.go index 3e22df8..3abc89e 100644 --- a/main.go +++ b/main.go @@ -93,7 +93,7 @@ func main() { } go func() { - args := []string{"daemon"} + args := []string{"daemon", "-g", "/drone/docker"} if len(vargs.Storage) != 0 { args = append(args, "-s", vargs.Storage) From 2bf7ebf219fe9bba4bf4f6418e8c17601a588c88 Mon Sep 17 00:00:00 2001 From: Martin Honermeyer Date: Sat, 2 Jan 2016 20:01:59 +0100 Subject: [PATCH 03/12] Clean out untagged Docker images after build --- main.go | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/main.go b/main.go index 3abc89e..8b2426d 100644 --- a/main.go +++ b/main.go @@ -1,6 +1,7 @@ package main import ( + "bytes" "fmt" "io/ioutil" "os" @@ -228,6 +229,29 @@ func main() { } } + // Remove untagged images, if any + var outbuf bytes.Buffer + cmd = exec.Command("sh", "-c", "docker images | grep '^' | awk '{print $3}'") + cmd.Stdout = &outbuf + cmd.Stderr = os.Stderr + trace(cmd) + err = cmd.Run() + if err != nil { + os.Exit(1) + } + + if outbuf.Len() > 0 { + images := strings.Split(strings.TrimSpace(outbuf.String()), "\n") + cmd = exec.Command("docker", append([]string{"rmi"}, images...)...) + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + trace(cmd) + err := cmd.Run() + if err != nil { + os.Exit(1) + } + } + // Save to tarred image repository if len(vargs.Save.File) != 0 { // if the destination directory does not exist, create it From 2ab987b917c07957f49f6d4adf30eb409b61f601 Mon Sep 17 00:00:00 2001 From: Martin Honermeyer Date: Sun, 3 Jan 2016 14:17:15 +0100 Subject: [PATCH 04/12] Use "dangling" filter instead of grepping when cleaning up images --- main.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main.go b/main.go index 8b2426d..b4fb348 100644 --- a/main.go +++ b/main.go @@ -231,7 +231,7 @@ func main() { // Remove untagged images, if any var outbuf bytes.Buffer - cmd = exec.Command("sh", "-c", "docker images | grep '^' | awk '{print $3}'") + cmd = exec.Command("docker", "images", "-q", "-f", "dangling=true") cmd.Stdout = &outbuf cmd.Stderr = os.Stderr trace(cmd) From b11030ead33a6a533f1614b85728a6aafe227f72 Mon Sep 17 00:00:00 2001 From: Martin Honermeyer Date: Sun, 3 Jan 2016 16:55:01 +0100 Subject: [PATCH 05/12] Adjust wording in caching docs --- DOCS.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DOCS.md b/DOCS.md index 3049624..e6f4ace 100644 --- a/DOCS.md +++ b/DOCS.md @@ -80,7 +80,7 @@ publish: ## Caching -The Drone build environment is, by default, ephemeral meaning that you layers are not saved between builds. There are two methods for caching layers. +The Drone build environment is, by default, ephemeral meaning that you layers are not saved between builds. There are two methods for caching your layers. ### Graph directory caching From f2316d74bcf51a7a57c823f90e364e7918ef94c9 Mon Sep 17 00:00:00 2001 From: Martin Honermeyer Date: Wed, 20 Jan 2016 00:19:44 +0100 Subject: [PATCH 06/12] Adjust docs to state AUFS can be used with graph caching as well --- DOCS.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/DOCS.md b/DOCS.md index e6f4ace..8d8d34b 100644 --- a/DOCS.md +++ b/DOCS.md @@ -84,7 +84,7 @@ The Drone build environment is, by default, ephemeral meaning that you layers ar ### Graph directory caching -This is the preferred method when using the `overlay` storage driver. Just use Drone's caching feature to backup and restore the directory `/drone/docker`, as shown in the following example: +This is the preferred method when using the `overlay` or `aufs` storage drivers. Just use Drone's caching feature to backup and restore the directory `/drone/docker`, as shown in the following example: ```yaml publish: @@ -102,7 +102,7 @@ cache: - /drone/docker ``` -NOTE: This probably won't work correctly with the `btrfs` driver, and it will be very inefficient with the `devicemapper` driver. Please make sure to use the `overlay` storage driver with this method. +NOTE: This probably won't work correctly with the `btrfs` driver, and it will be very inefficient with the `devicemapper` driver. Please make sure to use the `overlay` or `aufs` storage driver with this method. ### Layer Caching From 16cd3a4968da6458ecb91fa07044ba3e02e3f241 Mon Sep 17 00:00:00 2001 From: Austen Lacy Date: Tue, 15 Mar 2016 15:08:38 -0400 Subject: [PATCH 07/12] upgraded docker version --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 41bb0f9..49232e2 100644 --- a/Dockerfile +++ b/Dockerfile @@ -2,7 +2,7 @@ # # docker build --rm=true -t plugins/drone-docker . -FROM rancher/docker:1.9.1 +FROM rancher/docker:v1.10.2 ADD drone-docker /go/bin/ VOLUME /var/lib/docker From 5825a3d79702152c790344e05876066c948f3670 Mon Sep 17 00:00:00 2001 From: Brad Rydzewski Date: Tue, 3 May 2016 16:17:16 -0700 Subject: [PATCH 08/12] initial commit for 0.5 release --- .drone.yml | 29 +- .gitignore | 3 +- Dockerfile | 2 +- Makefile | 32 - README.md | 78 +-- main.go | 454 ++++++--------- plugin.go | 205 +++++++ types.go | 45 -- vendor/github.com/codegangsta/cli/LICENSE | 21 + vendor/github.com/codegangsta/cli/README.md | 434 ++++++++++++++ vendor/github.com/codegangsta/cli/app.go | 363 ++++++++++++ .../github.com/codegangsta/cli/appveyor.yml | 16 + vendor/github.com/codegangsta/cli/category.go | 30 + vendor/github.com/codegangsta/cli/cli.go | 40 ++ vendor/github.com/codegangsta/cli/command.go | 262 +++++++++ vendor/github.com/codegangsta/cli/context.go | 393 +++++++++++++ vendor/github.com/codegangsta/cli/flag.go | 546 ++++++++++++++++++ vendor/github.com/codegangsta/cli/help.go | 253 ++++++++ vendor/github.com/joho/godotenv/LICENCE | 23 + vendor/github.com/joho/godotenv/README.md | 127 ++++ .../joho/godotenv/autoload/autoload.go | 15 + vendor/github.com/joho/godotenv/godotenv.go | 229 ++++++++ vendor/github.com/joho/godotenv/wercker.yml | 1 + vendor/vendor.json | 21 + 24 files changed, 3201 insertions(+), 421 deletions(-) delete mode 100644 Makefile create mode 100644 plugin.go delete mode 100644 types.go create mode 100644 vendor/github.com/codegangsta/cli/LICENSE create mode 100644 vendor/github.com/codegangsta/cli/README.md create mode 100644 vendor/github.com/codegangsta/cli/app.go create mode 100644 vendor/github.com/codegangsta/cli/appveyor.yml create mode 100644 vendor/github.com/codegangsta/cli/category.go create mode 100644 vendor/github.com/codegangsta/cli/cli.go create mode 100644 vendor/github.com/codegangsta/cli/command.go create mode 100644 vendor/github.com/codegangsta/cli/context.go create mode 100644 vendor/github.com/codegangsta/cli/flag.go create mode 100644 vendor/github.com/codegangsta/cli/help.go create mode 100644 vendor/github.com/joho/godotenv/LICENCE create mode 100644 vendor/github.com/joho/godotenv/README.md create mode 100644 vendor/github.com/joho/godotenv/autoload/autoload.go create mode 100644 vendor/github.com/joho/godotenv/godotenv.go create mode 100644 vendor/github.com/joho/godotenv/wercker.yml create mode 100644 vendor/vendor.json diff --git a/.drone.yml b/.drone.yml index 0aeada9..9c4bd7a 100644 --- a/.drone.yml +++ b/.drone.yml @@ -1,12 +1,13 @@ build: - image: golang:1.5 - environment: - - CGO_ENABLED=0 - commands: - - make deps - - make vet - - make build - - make test + test: + image: golang:1.5 + environment: + - CGO_ENABLED=0 + - GO15VENDOREXPERIMENT=1 + commands: + - go vet + - go test -cover -coverprofile=coverage.out + - go build -ldflags "-s -w -X main.build=$DRONE_BUILD_NUMBER" publish: coverage: @@ -16,24 +17,16 @@ publish: username: $$DOCKER_USER password: $$DOCKER_PASS email: $$DOCKER_EMAIL - repo: plugins/drone-docker + repo: plugins/docker tag: latest when: branch: master - docker: - username: $$DOCKER_USER - password: $$DOCKER_PASS - email: $$DOCKER_EMAIL - repo: plugins/drone-docker - tag: develop - when: - branch: develop plugin: name: Docker desc: Build and publish images to a Docker registry type: publish - image: plugins/drone-docker + image: plugins/docker labels: - docker - image diff --git a/.gitignore b/.gitignore index 825a85a..185184b 100644 --- a/.gitignore +++ b/.gitignore @@ -22,5 +22,6 @@ _testmain.go *.exe *.test *.prof +.env -drone-docker \ No newline at end of file +drone-docker diff --git a/Dockerfile b/Dockerfile index 49232e2..105954e 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,6 +1,6 @@ # Docker image for the docker plugin # -# docker build --rm=true -t plugins/drone-docker . +# docker build --rm=true -t plugins/docker . FROM rancher/docker:v1.10.2 diff --git a/Makefile b/Makefile deleted file mode 100644 index aea4dd4..0000000 --- a/Makefile +++ /dev/null @@ -1,32 +0,0 @@ -.PHONY: clean deps fmt vet test docker - -EXECUTABLE ?= drone-docker -IMAGE ?= plugins/$(EXECUTABLE) -CI_BUILD_NUMBER ?= 0 - -LDFLAGS = -X "main.buildDate=$(shell date -u '+%Y-%m-%d %H:%M:%S %Z')" -PACKAGES = $(shell go list ./... | grep -v /vendor/) - -clean: - go clean -i ./... - -deps: - go get -t ./... - -fmt: - go fmt $(PACKAGES) - -vet: - go vet $(PACKAGES) - -test: - @for PKG in $(PACKAGES); do go test -cover -coverprofile $$GOPATH/src/$$PKG/coverage.out $$PKG || exit 1; done; - -docker: - GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -ldflags '-s -w $(LDFLAGS)' - docker build --rm -t $(IMAGE) . - -$(EXECUTABLE): $(wildcard *.go) - go build -ldflags '-s -w $(LDFLAGS)' - -build: $(EXECUTABLE) diff --git a/README.md b/README.md index b7b6a46..4e8fd4a 100644 --- a/README.md +++ b/README.md @@ -1,57 +1,43 @@ -# drone-docker +# drone-git -[![Build Status](http://beta.drone.io/api/badges/drone-plugins/drone-docker/status.svg)](http://beta.drone.io/drone-plugins/drone-docker) -[![Coverage Status](https://aircover.co/badges/drone-plugins/drone-docker/coverage.svg)](https://aircover.co/drone-plugins/drone-docker) -[![](https://badge.imagelayers.io/plugins/drone-docker:latest.svg)](https://imagelayers.io/?images=plugins/drone-docker:latest 'Get your own badge on imagelayers.io') +Drone plugin to build and publish Docker images to a container registry. For the usage information and a listing of the available options please take a look at [the docs](DOCS.md). -Drone plugin to build and publish images to a Docker registry +## Build + +Build the binary with the following commands: + +``` +export GO15VENDOREXPERIMENT=1 +go build +go test +``` ## Docker -Build the container using `make`: +Build the docker image with the following commands: ``` -make deps docker +export GO15VENDOREXPERIMENT=1 +GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -a -tags netgo ``` -### Example +Please note incorrectly building the image for the correct x64 linux and with GCO disabled will result in an error when running the Docker image: -```sh -docker run -i --privileged -v $(pwd):/drone/src plugins/drone-docker < 0 { - images := strings.Split(strings.TrimSpace(outbuf.String()), "\n") - cmd = exec.Command("docker", append([]string{"rmi"}, images...)...) - cmd.Stdout = os.Stdout - cmd.Stderr = os.Stderr - trace(cmd) - err := cmd.Run() - if err != nil { - os.Exit(1) - } - } - - // Save to tarred image repository - if len(vargs.Save.File) != 0 { - // if the destination directory does not exist, create it - dir := filepath.Dir(vargs.Save.File) - os.MkdirAll(dir, 0755) - - cmd = exec.Command("/usr/bin/docker", "save", "-o", vargs.Save.File) - - // Limit saving to the given tags - if vargs.Save.Tags.Len() != 0 { - for _, tag := range vargs.Save.Tags.Slice() { - name_ := fmt.Sprintf("%s:%s", vargs.Repo, tag) - cmd.Args = append(cmd.Args, name_) - } - } else { - cmd.Args = append(cmd.Args, vargs.Repo) - } - - cmd.Dir = workspace.Path - cmd.Stdout = os.Stdout - cmd.Stderr = os.Stderr - trace(cmd) - err := cmd.Run() - if err != nil { - os.Exit(1) - } - } + // TODO execute code remove dangling images + // this is problematic because we are running docker in scratch which does + // not have bash, so we need to hack something together + // docker images --quiet --filter=dangling=true | xargs --no-run-if-empty docker rmi } -// Trace writes each command to standard error (preceded by a ‘$ ’) before it -// is executed. Used for debugging your build. -func trace(cmd *exec.Cmd) { - fmt.Println("$", strings.Join(cmd.Args, " ")) -} - -// authorize is a helper function that authorizes the Docker client -// by manually creating the Docker authentication file. -func authorize(d *Docker) error { - var path = "/root/.dockercfg" // TODO should probably use user.Home() for good measure - var data = fmt.Sprintf(dockerconf, d.Registry, d.Auth, d.Email) - return ioutil.WriteFile(path, []byte(data), 0644) -} - -var dockerconf = ` -{ - "%s": { - "auth": "%s", - "email": "%s" - } -} -` +/* +cmd = exec.Command("docker", "images", "-q", "-f", "dangling=true") +cmd = exec.Command("docker", append([]string{"rmi"}, images...)...) +*/ diff --git a/plugin.go b/plugin.go new file mode 100644 index 0000000..e4e5313 --- /dev/null +++ b/plugin.go @@ -0,0 +1,205 @@ +package main + +import ( + "fmt" + "io/ioutil" + "os" + "os/exec" + "strings" + "time" +) + +type ( + // Daemon defines Docker daemon parameters. + Daemon struct { + Registry string // Docker registry + Mirror string // Docker registry mirror + Insecure bool // Docker daemon enable insecure registries + StorageDriver string // Docker daemon storage driver + StoragePath string // Docker daemon storage path + Disabled bool // DOcker daemon is disabled (already running) + Debug bool // Docker daemon started in debug mode + Bip string // Docker daemon network bridge IP address + DNS []string // Docker daemon dns server + } + + // Login defines Docker login parameters. + Login struct { + Registry string // Docker registry address + Username string // Docker registry username + Password string // Docker registry password + Email string // Docker registry email + } + + // Build defines Docker build parameters. + Build struct { + Name string // Docker build using default named tag + Dockerfile string // Docker build Dockerfile + Context string // Docker build context + Tags []string // Docker build tags + Args []string // Docker build args + Repo string // Docker build repository + Force bool // Docker build forces image tag creation + } + + // Plugin defines the Docker plugin parameters. + Plugin struct { + Login Login // Docker login configuration + Build Build // Docker build configuration + Daemon Daemon // Docker daemon configuration + Dryrun bool // Docker push is skipped + } +) + +// Exec executes the plugin step +func (p Plugin) Exec() error { + + // start the Docker daemon server + if !p.Daemon.Disabled { + cmd := commandDaemon(p.Daemon) + if p.Daemon.Debug { + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + } else { + cmd.Stdout = ioutil.Discard + cmd.Stderr = ioutil.Discard + } + go func() { + trace(cmd) + cmd.Run() + }() + } + + // poll the docker daemon until it is started. This ensures the daemon is + // ready to accept connections before we proceed. + for i := 0; i < 15; i++ { + cmd := commandInfo() + err := cmd.Run() + if err == nil { + break + } + time.Sleep(time.Second * 1) + } + + // login to the Docker registry + if p.Login.Password != "" { + cmd := commandLogin(p.Login) + err := cmd.Run() + if err != nil { + return fmt.Errorf("Error authenticating: %s", err) + } + } else { + fmt.Println("Registry credentials not provided. Guest mode enabled.") + } + + var cmds []*exec.Cmd + cmds = append(cmds, commandVersion()) // docker version + cmds = append(cmds, commandInfo()) // docker info + cmds = append(cmds, commandBuild(p.Build)) // docker build + + for _, tag := range p.Build.Tags { + cmds = append(cmds, commandTag(p.Build, tag)) // docker tag + + if p.Dryrun == false { + cmds = append(cmds, commandPush(tag)) // docker push + } + } + + // execute all commands in batch mode. + for _, cmd := range cmds { + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + trace(cmd) + + err := cmd.Run() + if err != nil { + return err + } + } + + return nil +} + +// helper function to create the docker login command. +func commandLogin(login Login) *exec.Cmd { + return exec.Command( + "/usr/bin/docker", "login", + "-u", login.Username, + "-p", login.Password, + "-e", login.Email, + login.Registry, + ) +} + +// helper function to create the docker info command. +func commandVersion() *exec.Cmd { + return exec.Command("/usr/bin/docker", "version") +} + +// helper function to create the docker info command. +func commandInfo() *exec.Cmd { + return exec.Command("/usr/bin/docker", "info") +} + +// helper function to create the docker build command. +func commandBuild(build Build) *exec.Cmd { + cmd := exec.Command( + "/usr/bin/docker", "build", + "--pull=true", + "--rm=true", + "-f", build.Dockerfile, + "-t", build.Name, + ) + for _, arg := range build.Args { + cmd.Args = append(cmd.Args, "--build-arg", arg) + } + cmd.Args = append(cmd.Args, build.Context) + return cmd +} + +// helper function to create the docker tag command. +func commandTag(build Build, tag string) *exec.Cmd { + var ( + force = fmt.Sprintf("--force=%v", build.Force) + from = build.Name + to = fmt.Sprintf("%s:%s", build.Repo, tag) + ) + return exec.Command( + "/usr/bin/docker", "tag", + force, + from, to, + ) +} + +// helper function to create the docker push command. +func commandPush(tag string) *exec.Cmd { + return exec.Command("/usr/bin/docker", "push", tag) +} + +// helper function to create the docker daemon command. +func commandDaemon(daemon Daemon) *exec.Cmd { + args := []string{"daemon", "-g", daemon.StoragePath} + + if daemon.StorageDriver != "" { + args = append(args, "-s", daemon.StorageDriver) + } + if daemon.Insecure && daemon.Registry != "" { + args = append(args, "--insecure-registry", daemon.Registry) + } + if len(daemon.Mirror) != 0 { + args = append(args, "--registry-mirror", daemon.Mirror) + } + if len(daemon.Bip) != 0 { + args = append(args, "--bip", daemon.Bip) + } + for _, dns := range daemon.DNS { + args = append(args, "--dns", dns) + } + return exec.Command("/usr/bin/docker", args...) +} + +// trace writes each command to stdout with the command wrapped in an xml +// tag so that it can be extracted and displayed in the logs. +func trace(cmd *exec.Cmd) { + fmt.Fprintf(os.Stdout, "%s", strings.Join(cmd.Args, " ")) +} diff --git a/types.go b/types.go deleted file mode 100644 index 7831e0c..0000000 --- a/types.go +++ /dev/null @@ -1,45 +0,0 @@ -package main - -import "encoding/json" - -// StrSlice representes a string or an array of strings. -// We need to override the json decoder to accept both options. -type StrSlice struct { - parts []string -} - -// UnmarshalJSON decodes the byte slice whether it's a string or an array of strings. -// This method is needed to implement json.Unmarshaler. -func (e *StrSlice) UnmarshalJSON(b []byte) error { - if len(b) == 0 { - return nil - } - - p := make([]string, 0, 1) - if err := json.Unmarshal(b, &p); err != nil { - var s string - if err := json.Unmarshal(b, &s); err != nil { - return err - } - p = append(p, s) - } - - e.parts = p - return nil -} - -// Len returns the number of parts of the StrSlice. -func (e *StrSlice) Len() int { - if e == nil { - return 0 - } - return len(e.parts) -} - -// Slice gets the parts of the StrSlice as a Slice of string. -func (e *StrSlice) Slice() []string { - if e == nil { - return nil - } - return e.parts -} diff --git a/vendor/github.com/codegangsta/cli/LICENSE b/vendor/github.com/codegangsta/cli/LICENSE new file mode 100644 index 0000000..5515ccf --- /dev/null +++ b/vendor/github.com/codegangsta/cli/LICENSE @@ -0,0 +1,21 @@ +Copyright (C) 2013 Jeremy Saenz +All Rights Reserved. + +MIT LICENSE + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/vendor/github.com/codegangsta/cli/README.md b/vendor/github.com/codegangsta/cli/README.md new file mode 100644 index 0000000..d9371cf --- /dev/null +++ b/vendor/github.com/codegangsta/cli/README.md @@ -0,0 +1,434 @@ +[![Coverage](http://gocover.io/_badge/github.com/codegangsta/cli?0)](http://gocover.io/github.com/codegangsta/cli) +[![Build Status](https://travis-ci.org/codegangsta/cli.svg?branch=master)](https://travis-ci.org/codegangsta/cli) +[![GoDoc](https://godoc.org/github.com/codegangsta/cli?status.svg)](https://godoc.org/github.com/codegangsta/cli) +[![codebeat](https://codebeat.co/badges/0a8f30aa-f975-404b-b878-5fab3ae1cc5f)](https://codebeat.co/projects/github-com-codegangsta-cli) + +# cli.go + +`cli.go` is simple, fast, and fun package for building command line apps in Go. The goal is to enable developers to write fast and distributable command line applications in an expressive way. + +## Overview + +Command line apps are usually so tiny that there is absolutely no reason why your code should *not* be self-documenting. Things like generating help text and parsing command flags/options should not hinder productivity when writing a command line app. + +**This is where `cli.go` comes into play.** `cli.go` makes command line programming fun, organized, and expressive! + +## Installation + +Make sure you have a working Go environment (go 1.1+ is *required*). [See the install instructions](http://golang.org/doc/install.html). + +To install `cli.go`, simply run: +``` +$ go get github.com/codegangsta/cli +``` + +Make sure your `PATH` includes to the `$GOPATH/bin` directory so your commands can be easily used: +``` +export PATH=$PATH:$GOPATH/bin +``` + +## Getting Started + +One of the philosophies behind `cli.go` is that an API should be playful and full of discovery. So a `cli.go` app can be as little as one line of code in `main()`. + +``` go +package main + +import ( + "os" + "github.com/codegangsta/cli" +) + +func main() { + cli.NewApp().Run(os.Args) +} +``` + +This app will run and show help text, but is not very useful. Let's give an action to execute and some help documentation: + +``` go +package main + +import ( + "os" + "github.com/codegangsta/cli" +) + +func main() { + app := cli.NewApp() + app.Name = "boom" + app.Usage = "make an explosive entrance" + app.Action = func(c *cli.Context) { + println("boom! I say!") + } + + app.Run(os.Args) +} +``` + +Running this already gives you a ton of functionality, plus support for things like subcommands and flags, which are covered below. + +## Example + +Being a programmer can be a lonely job. Thankfully by the power of automation that is not the case! Let's create a greeter app to fend off our demons of loneliness! + +Start by creating a directory named `greet`, and within it, add a file, `greet.go` with the following code in it: + +``` go +package main + +import ( + "os" + "github.com/codegangsta/cli" +) + +func main() { + app := cli.NewApp() + app.Name = "greet" + app.Usage = "fight the loneliness!" + app.Action = func(c *cli.Context) { + println("Hello friend!") + } + + app.Run(os.Args) +} +``` + +Install our command to the `$GOPATH/bin` directory: + +``` +$ go install +``` + +Finally run our new command: + +``` +$ greet +Hello friend! +``` + +`cli.go` also generates neat help text: + +``` +$ greet help +NAME: + greet - fight the loneliness! + +USAGE: + greet [global options] command [command options] [arguments...] + +VERSION: + 0.0.0 + +COMMANDS: + help, h Shows a list of commands or help for one command + +GLOBAL OPTIONS + --version Shows version information +``` + +### Arguments + +You can lookup arguments by calling the `Args` function on `cli.Context`. + +``` go +... +app.Action = func(c *cli.Context) { + println("Hello", c.Args()[0]) +} +... +``` + +### Flags + +Setting and querying flags is simple. + +``` go +... +app.Flags = []cli.Flag { + cli.StringFlag{ + Name: "lang", + Value: "english", + Usage: "language for the greeting", + }, +} +app.Action = func(c *cli.Context) { + name := "someone" + if c.NArg() > 0 { + name = c.Args()[0] + } + if c.String("lang") == "spanish" { + println("Hola", name) + } else { + println("Hello", name) + } +} +... +``` + +You can also set a destination variable for a flag, to which the content will be scanned. + +``` go +... +var language string +app.Flags = []cli.Flag { + cli.StringFlag{ + Name: "lang", + Value: "english", + Usage: "language for the greeting", + Destination: &language, + }, +} +app.Action = func(c *cli.Context) { + name := "someone" + if c.NArg() > 0 { + name = c.Args()[0] + } + if language == "spanish" { + println("Hola", name) + } else { + println("Hello", name) + } +} +... +``` + +See full list of flags at http://godoc.org/github.com/codegangsta/cli + +#### Alternate Names + +You can set alternate (or short) names for flags by providing a comma-delimited list for the `Name`. e.g. + +``` go +app.Flags = []cli.Flag { + cli.StringFlag{ + Name: "lang, l", + Value: "english", + Usage: "language for the greeting", + }, +} +``` + +That flag can then be set with `--lang spanish` or `-l spanish`. Note that giving two different forms of the same flag in the same command invocation is an error. + +#### Values from the Environment + +You can also have the default value set from the environment via `EnvVar`. e.g. + +``` go +app.Flags = []cli.Flag { + cli.StringFlag{ + Name: "lang, l", + Value: "english", + Usage: "language for the greeting", + EnvVar: "APP_LANG", + }, +} +``` + +The `EnvVar` may also be given as a comma-delimited "cascade", where the first environment variable that resolves is used as the default. + +``` go +app.Flags = []cli.Flag { + cli.StringFlag{ + Name: "lang, l", + Value: "english", + Usage: "language for the greeting", + EnvVar: "LEGACY_COMPAT_LANG,APP_LANG,LANG", + }, +} +``` + +#### Values from alternate input sources (YAML and others) + +There is a separate package altsrc that adds support for getting flag values from other input sources like YAML. + +In order to get values for a flag from an alternate input source the following code would be added to wrap an existing cli.Flag like below: + +``` go + altsrc.NewIntFlag(cli.IntFlag{Name: "test"}) +``` + +Initialization must also occur for these flags. Below is an example initializing getting data from a yaml file below. + +``` go + command.Before = altsrc.InitInputSourceWithContext(command.Flags, NewYamlSourceFromFlagFunc("load")) +``` + +The code above will use the "load" string as a flag name to get the file name of a yaml file from the cli.Context. +It will then use that file name to initialize the yaml input source for any flags that are defined on that command. +As a note the "load" flag used would also have to be defined on the command flags in order for this code snipped to work. + +Currently only YAML files are supported but developers can add support for other input sources by implementing the +altsrc.InputSourceContext for their given sources. + +Here is a more complete sample of a command using YAML support: + +``` go + command := &cli.Command{ + Name: "test-cmd", + Aliases: []string{"tc"}, + Usage: "this is for testing", + Description: "testing", + Action: func(c *cli.Context) { + // Action to run + }, + Flags: []cli.Flag{ + NewIntFlag(cli.IntFlag{Name: "test"}), + cli.StringFlag{Name: "load"}}, + } + command.Before = InitInputSourceWithContext(command.Flags, NewYamlSourceFromFlagFunc("load")) + err := command.Run(c) +``` + +### Subcommands + +Subcommands can be defined for a more git-like command line app. + +```go +... +app.Commands = []cli.Command{ + { + Name: "add", + Aliases: []string{"a"}, + Usage: "add a task to the list", + Action: func(c *cli.Context) { + println("added task: ", c.Args().First()) + }, + }, + { + Name: "complete", + Aliases: []string{"c"}, + Usage: "complete a task on the list", + Action: func(c *cli.Context) { + println("completed task: ", c.Args().First()) + }, + }, + { + Name: "template", + Aliases: []string{"r"}, + Usage: "options for task templates", + Subcommands: []cli.Command{ + { + Name: "add", + Usage: "add a new template", + Action: func(c *cli.Context) { + println("new task template: ", c.Args().First()) + }, + }, + { + Name: "remove", + Usage: "remove an existing template", + Action: func(c *cli.Context) { + println("removed task template: ", c.Args().First()) + }, + }, + }, + }, +} +... +``` + +### Subcommands categories + +For additional organization in apps that have many subcommands, you can +associate a category for each command to group them together in the help +output. + +E.g. + +```go +... + app.Commands = []cli.Command{ + { + Name: "noop", + }, + { + Name: "add", + Category: "template", + }, + { + Name: "remove", + Category: "template", + }, + } +... +``` + +Will include: + +``` +... +COMMANDS: + noop + + Template actions: + add + remove +... +``` + +### Bash Completion + +You can enable completion commands by setting the `EnableBashCompletion` +flag on the `App` object. By default, this setting will only auto-complete to +show an app's subcommands, but you can write your own completion methods for +the App or its subcommands. + +```go +... +var tasks = []string{"cook", "clean", "laundry", "eat", "sleep", "code"} +app := cli.NewApp() +app.EnableBashCompletion = true +app.Commands = []cli.Command{ + { + Name: "complete", + Aliases: []string{"c"}, + Usage: "complete a task on the list", + Action: func(c *cli.Context) { + println("completed task: ", c.Args().First()) + }, + BashComplete: func(c *cli.Context) { + // This will complete if no args are passed + if c.NArg() > 0 { + return + } + for _, t := range tasks { + fmt.Println(t) + } + }, + } +} +... +``` + +#### To Enable + +Source the `autocomplete/bash_autocomplete` file in your `.bashrc` file while +setting the `PROG` variable to the name of your program: + +`PROG=myprogram source /.../cli/autocomplete/bash_autocomplete` + +#### To Distribute + +Copy `autocomplete/bash_autocomplete` into `/etc/bash_completion.d/` and rename +it to the name of the program you wish to add autocomplete support for (or +automatically install it there if you are distributing a package). Don't forget +to source the file to make it active in the current shell. + +``` +sudo cp src/bash_autocomplete /etc/bash_completion.d/ +source /etc/bash_completion.d/ +``` + +Alternatively, you can just document that users should source the generic +`autocomplete/bash_autocomplete` in their bash configuration with `$PROG` set +to the name of their program (as above). + +## Contribution Guidelines + +Feel free to put up a pull request to fix a bug or maybe add a feature. I will give it a code review and make sure that it does not break backwards compatibility. If I or any other collaborators agree that it is in line with the vision of the project, we will work with you to get the code into a mergeable state and merge it into the master branch. + +If you have contributed something significant to the project, I will most likely add you as a collaborator. As a collaborator you are given the ability to merge others pull requests. It is very important that new code does not break existing code, so be careful about what code you do choose to merge. If you have any questions feel free to link @codegangsta to the issue in question and we can review it together. + +If you feel like you have contributed to the project but have not yet been added as a collaborator, I probably forgot to add you. Hit @codegangsta up over email and we will get it figured out. diff --git a/vendor/github.com/codegangsta/cli/app.go b/vendor/github.com/codegangsta/cli/app.go new file mode 100644 index 0000000..bd20a2d --- /dev/null +++ b/vendor/github.com/codegangsta/cli/app.go @@ -0,0 +1,363 @@ +package cli + +import ( + "fmt" + "io" + "io/ioutil" + "os" + "path/filepath" + "sort" + "time" +) + +// App is the main structure of a cli application. It is recommended that +// an app be created with the cli.NewApp() function +type App struct { + // The name of the program. Defaults to path.Base(os.Args[0]) + Name string + // Full name of command for help, defaults to Name + HelpName string + // Description of the program. + Usage string + // Text to override the USAGE section of help + UsageText string + // Description of the program argument format. + ArgsUsage string + // Version of the program + Version string + // List of commands to execute + Commands []Command + // List of flags to parse + Flags []Flag + // Boolean to enable bash completion commands + EnableBashCompletion bool + // Boolean to hide built-in help command + HideHelp bool + // Boolean to hide built-in version flag and the VERSION section of help + HideVersion bool + // Populate on app startup, only gettable throught method Categories() + categories CommandCategories + // An action to execute when the bash-completion flag is set + BashComplete func(context *Context) + // An action to execute before any subcommands are run, but after the context is ready + // If a non-nil error is returned, no subcommands are run + Before func(context *Context) error + // An action to execute after any subcommands are run, but after the subcommand has finished + // It is run even if Action() panics + After func(context *Context) error + // The action to execute when no subcommands are specified + Action func(context *Context) + // Execute this function if the proper command cannot be found + CommandNotFound func(context *Context, command string) + // Execute this function, if an usage error occurs. This is useful for displaying customized usage error messages. + // This function is able to replace the original error messages. + // If this function is not set, the "Incorrect usage" is displayed and the execution is interrupted. + OnUsageError func(context *Context, err error, isSubcommand bool) error + // Compilation date + Compiled time.Time + // List of all authors who contributed + Authors []Author + // Copyright of the binary if any + Copyright string + // Name of Author (Note: Use App.Authors, this is deprecated) + Author string + // Email of Author (Note: Use App.Authors, this is deprecated) + Email string + // Writer writer to write output to + Writer io.Writer +} + +// Tries to find out when this binary was compiled. +// Returns the current time if it fails to find it. +func compileTime() time.Time { + info, err := os.Stat(os.Args[0]) + if err != nil { + return time.Now() + } + return info.ModTime() +} + +// Creates a new cli Application with some reasonable defaults for Name, Usage, Version and Action. +func NewApp() *App { + return &App{ + Name: filepath.Base(os.Args[0]), + HelpName: filepath.Base(os.Args[0]), + Usage: "A new cli application", + UsageText: "", + Version: "0.0.0", + BashComplete: DefaultAppComplete, + Action: helpCommand.Action, + Compiled: compileTime(), + Writer: os.Stdout, + } +} + +// Entry point to the cli app. Parses the arguments slice and routes to the proper flag/args combination +func (a *App) Run(arguments []string) (err error) { + if a.Author != "" || a.Email != "" { + a.Authors = append(a.Authors, Author{Name: a.Author, Email: a.Email}) + } + + newCmds := []Command{} + for _, c := range a.Commands { + if c.HelpName == "" { + c.HelpName = fmt.Sprintf("%s %s", a.HelpName, c.Name) + } + newCmds = append(newCmds, c) + } + a.Commands = newCmds + + a.categories = CommandCategories{} + for _, command := range a.Commands { + a.categories = a.categories.AddCommand(command.Category, command) + } + sort.Sort(a.categories) + + // append help to commands + if a.Command(helpCommand.Name) == nil && !a.HideHelp { + a.Commands = append(a.Commands, helpCommand) + if (HelpFlag != BoolFlag{}) { + a.appendFlag(HelpFlag) + } + } + + //append version/help flags + if a.EnableBashCompletion { + a.appendFlag(BashCompletionFlag) + } + + if !a.HideVersion { + a.appendFlag(VersionFlag) + } + + // parse flags + set := flagSet(a.Name, a.Flags) + set.SetOutput(ioutil.Discard) + err = set.Parse(arguments[1:]) + nerr := normalizeFlags(a.Flags, set) + context := NewContext(a, set, nil) + if nerr != nil { + fmt.Fprintln(a.Writer, nerr) + ShowAppHelp(context) + return nerr + } + + if checkCompletions(context) { + return nil + } + + if err != nil { + if a.OnUsageError != nil { + err := a.OnUsageError(context, err, false) + return err + } else { + fmt.Fprintf(a.Writer, "%s\n\n", "Incorrect Usage.") + ShowAppHelp(context) + return err + } + } + + if !a.HideHelp && checkHelp(context) { + ShowAppHelp(context) + return nil + } + + if !a.HideVersion && checkVersion(context) { + ShowVersion(context) + return nil + } + + if a.After != nil { + defer func() { + if afterErr := a.After(context); afterErr != nil { + if err != nil { + err = NewMultiError(err, afterErr) + } else { + err = afterErr + } + } + }() + } + + if a.Before != nil { + err = a.Before(context) + if err != nil { + fmt.Fprintf(a.Writer, "%v\n\n", err) + ShowAppHelp(context) + return err + } + } + + args := context.Args() + if args.Present() { + name := args.First() + c := a.Command(name) + if c != nil { + return c.Run(context) + } + } + + // Run default Action + a.Action(context) + return nil +} + +// Another entry point to the cli app, takes care of passing arguments and error handling +func (a *App) RunAndExitOnError() { + if err := a.Run(os.Args); err != nil { + fmt.Fprintln(os.Stderr, err) + os.Exit(1) + } +} + +// Invokes the subcommand given the context, parses ctx.Args() to generate command-specific flags +func (a *App) RunAsSubcommand(ctx *Context) (err error) { + // append help to commands + if len(a.Commands) > 0 { + if a.Command(helpCommand.Name) == nil && !a.HideHelp { + a.Commands = append(a.Commands, helpCommand) + if (HelpFlag != BoolFlag{}) { + a.appendFlag(HelpFlag) + } + } + } + + newCmds := []Command{} + for _, c := range a.Commands { + if c.HelpName == "" { + c.HelpName = fmt.Sprintf("%s %s", a.HelpName, c.Name) + } + newCmds = append(newCmds, c) + } + a.Commands = newCmds + + // append flags + if a.EnableBashCompletion { + a.appendFlag(BashCompletionFlag) + } + + // parse flags + set := flagSet(a.Name, a.Flags) + set.SetOutput(ioutil.Discard) + err = set.Parse(ctx.Args().Tail()) + nerr := normalizeFlags(a.Flags, set) + context := NewContext(a, set, ctx) + + if nerr != nil { + fmt.Fprintln(a.Writer, nerr) + fmt.Fprintln(a.Writer) + if len(a.Commands) > 0 { + ShowSubcommandHelp(context) + } else { + ShowCommandHelp(ctx, context.Args().First()) + } + return nerr + } + + if checkCompletions(context) { + return nil + } + + if err != nil { + if a.OnUsageError != nil { + err = a.OnUsageError(context, err, true) + return err + } else { + fmt.Fprintf(a.Writer, "%s\n\n", "Incorrect Usage.") + ShowSubcommandHelp(context) + return err + } + } + + if len(a.Commands) > 0 { + if checkSubcommandHelp(context) { + return nil + } + } else { + if checkCommandHelp(ctx, context.Args().First()) { + return nil + } + } + + if a.After != nil { + defer func() { + afterErr := a.After(context) + if afterErr != nil { + if err != nil { + err = NewMultiError(err, afterErr) + } else { + err = afterErr + } + } + }() + } + + if a.Before != nil { + err := a.Before(context) + if err != nil { + return err + } + } + + args := context.Args() + if args.Present() { + name := args.First() + c := a.Command(name) + if c != nil { + return c.Run(context) + } + } + + // Run default Action + a.Action(context) + + return nil +} + +// Returns the named command on App. Returns nil if the command does not exist +func (a *App) Command(name string) *Command { + for _, c := range a.Commands { + if c.HasName(name) { + return &c + } + } + + return nil +} + +// Returnes the array containing all the categories with the commands they contain +func (a *App) Categories() CommandCategories { + return a.categories +} + +func (a *App) hasFlag(flag Flag) bool { + for _, f := range a.Flags { + if flag == f { + return true + } + } + + return false +} + +func (a *App) appendFlag(flag Flag) { + if !a.hasFlag(flag) { + a.Flags = append(a.Flags, flag) + } +} + +// Author represents someone who has contributed to a cli project. +type Author struct { + Name string // The Authors name + Email string // The Authors email +} + +// String makes Author comply to the Stringer interface, to allow an easy print in the templating process +func (a Author) String() string { + e := "" + if a.Email != "" { + e = "<" + a.Email + "> " + } + + return fmt.Sprintf("%v %v", a.Name, e) +} diff --git a/vendor/github.com/codegangsta/cli/appveyor.yml b/vendor/github.com/codegangsta/cli/appveyor.yml new file mode 100644 index 0000000..3ca7afa --- /dev/null +++ b/vendor/github.com/codegangsta/cli/appveyor.yml @@ -0,0 +1,16 @@ +version: "{build}" + +os: Windows Server 2012 R2 + +install: + - go version + - go env + +build_script: + - cd %APPVEYOR_BUILD_FOLDER% + - go vet ./... + - go test -v ./... + +test: off + +deploy: off diff --git a/vendor/github.com/codegangsta/cli/category.go b/vendor/github.com/codegangsta/cli/category.go new file mode 100644 index 0000000..7dbf218 --- /dev/null +++ b/vendor/github.com/codegangsta/cli/category.go @@ -0,0 +1,30 @@ +package cli + +type CommandCategories []*CommandCategory + +type CommandCategory struct { + Name string + Commands Commands +} + +func (c CommandCategories) Less(i, j int) bool { + return c[i].Name < c[j].Name +} + +func (c CommandCategories) Len() int { + return len(c) +} + +func (c CommandCategories) Swap(i, j int) { + c[i], c[j] = c[j], c[i] +} + +func (c CommandCategories) AddCommand(category string, command Command) CommandCategories { + for _, commandCategory := range c { + if commandCategory.Name == category { + commandCategory.Commands = append(commandCategory.Commands, command) + return c + } + } + return append(c, &CommandCategory{Name: category, Commands: []Command{command}}) +} diff --git a/vendor/github.com/codegangsta/cli/cli.go b/vendor/github.com/codegangsta/cli/cli.go new file mode 100644 index 0000000..31dc912 --- /dev/null +++ b/vendor/github.com/codegangsta/cli/cli.go @@ -0,0 +1,40 @@ +// Package cli provides a minimal framework for creating and organizing command line +// Go applications. cli is designed to be easy to understand and write, the most simple +// cli application can be written as follows: +// func main() { +// cli.NewApp().Run(os.Args) +// } +// +// Of course this application does not do much, so let's make this an actual application: +// func main() { +// app := cli.NewApp() +// app.Name = "greet" +// app.Usage = "say a greeting" +// app.Action = func(c *cli.Context) { +// println("Greetings") +// } +// +// app.Run(os.Args) +// } +package cli + +import ( + "strings" +) + +type MultiError struct { + Errors []error +} + +func NewMultiError(err ...error) MultiError { + return MultiError{Errors: err} +} + +func (m MultiError) Error() string { + errs := make([]string, len(m.Errors)) + for i, err := range m.Errors { + errs[i] = err.Error() + } + + return strings.Join(errs, "\n") +} diff --git a/vendor/github.com/codegangsta/cli/command.go b/vendor/github.com/codegangsta/cli/command.go new file mode 100644 index 0000000..1a05b54 --- /dev/null +++ b/vendor/github.com/codegangsta/cli/command.go @@ -0,0 +1,262 @@ +package cli + +import ( + "fmt" + "io/ioutil" + "sort" + "strings" +) + +// Command is a subcommand for a cli.App. +type Command struct { + // The name of the command + Name string + // short name of the command. Typically one character (deprecated, use `Aliases`) + ShortName string + // A list of aliases for the command + Aliases []string + // A short description of the usage of this command + Usage string + // Custom text to show on USAGE section of help + UsageText string + // A longer explanation of how the command works + Description string + // A short description of the arguments of this command + ArgsUsage string + // The category the command is part of + Category string + // The function to call when checking for bash command completions + BashComplete func(context *Context) + // An action to execute before any sub-subcommands are run, but after the context is ready + // If a non-nil error is returned, no sub-subcommands are run + Before func(context *Context) error + // An action to execute after any subcommands are run, but before the subcommand has finished + // It is run even if Action() panics + After func(context *Context) error + // The function to call when this command is invoked + Action func(context *Context) + // Execute this function, if an usage error occurs. This is useful for displaying customized usage error messages. + // This function is able to replace the original error messages. + // If this function is not set, the "Incorrect usage" is displayed and the execution is interrupted. + OnUsageError func(context *Context, err error) error + // List of child commands + Subcommands Commands + // List of flags to parse + Flags []Flag + // Treat all flags as normal arguments if true + SkipFlagParsing bool + // Boolean to hide built-in help command + HideHelp bool + + // Full name of command for help, defaults to full command name, including parent commands. + HelpName string + commandNamePath []string +} + +// Returns the full name of the command. +// For subcommands this ensures that parent commands are part of the command path +func (c Command) FullName() string { + if c.commandNamePath == nil { + return c.Name + } + return strings.Join(c.commandNamePath, " ") +} + +type Commands []Command + +// Invokes the command given the context, parses ctx.Args() to generate command-specific flags +func (c Command) Run(ctx *Context) (err error) { + if len(c.Subcommands) > 0 { + return c.startApp(ctx) + } + + if !c.HideHelp && (HelpFlag != BoolFlag{}) { + // append help to flags + c.Flags = append( + c.Flags, + HelpFlag, + ) + } + + if ctx.App.EnableBashCompletion { + c.Flags = append(c.Flags, BashCompletionFlag) + } + + set := flagSet(c.Name, c.Flags) + set.SetOutput(ioutil.Discard) + + if !c.SkipFlagParsing { + firstFlagIndex := -1 + terminatorIndex := -1 + for index, arg := range ctx.Args() { + if arg == "--" { + terminatorIndex = index + break + } else if arg == "-" { + // Do nothing. A dash alone is not really a flag. + continue + } else if strings.HasPrefix(arg, "-") && firstFlagIndex == -1 { + firstFlagIndex = index + } + } + + if firstFlagIndex > -1 { + args := ctx.Args() + regularArgs := make([]string, len(args[1:firstFlagIndex])) + copy(regularArgs, args[1:firstFlagIndex]) + + var flagArgs []string + if terminatorIndex > -1 { + flagArgs = args[firstFlagIndex:terminatorIndex] + regularArgs = append(regularArgs, args[terminatorIndex:]...) + } else { + flagArgs = args[firstFlagIndex:] + } + + err = set.Parse(append(flagArgs, regularArgs...)) + } else { + err = set.Parse(ctx.Args().Tail()) + } + } else { + if c.SkipFlagParsing { + err = set.Parse(append([]string{"--"}, ctx.Args().Tail()...)) + } + } + + if err != nil { + if c.OnUsageError != nil { + err := c.OnUsageError(ctx, err) + return err + } else { + fmt.Fprintln(ctx.App.Writer, "Incorrect Usage.") + fmt.Fprintln(ctx.App.Writer) + ShowCommandHelp(ctx, c.Name) + return err + } + } + + nerr := normalizeFlags(c.Flags, set) + if nerr != nil { + fmt.Fprintln(ctx.App.Writer, nerr) + fmt.Fprintln(ctx.App.Writer) + ShowCommandHelp(ctx, c.Name) + return nerr + } + context := NewContext(ctx.App, set, ctx) + + if checkCommandCompletions(context, c.Name) { + return nil + } + + if checkCommandHelp(context, c.Name) { + return nil + } + + if c.After != nil { + defer func() { + afterErr := c.After(context) + if afterErr != nil { + if err != nil { + err = NewMultiError(err, afterErr) + } else { + err = afterErr + } + } + }() + } + + if c.Before != nil { + err := c.Before(context) + if err != nil { + fmt.Fprintln(ctx.App.Writer, err) + fmt.Fprintln(ctx.App.Writer) + ShowCommandHelp(ctx, c.Name) + return err + } + } + + context.Command = c + c.Action(context) + return nil +} + +func (c Command) Names() []string { + names := []string{c.Name} + + if c.ShortName != "" { + names = append(names, c.ShortName) + } + + return append(names, c.Aliases...) +} + +// Returns true if Command.Name or Command.ShortName matches given name +func (c Command) HasName(name string) bool { + for _, n := range c.Names() { + if n == name { + return true + } + } + return false +} + +func (c Command) startApp(ctx *Context) error { + app := NewApp() + + // set the name and usage + app.Name = fmt.Sprintf("%s %s", ctx.App.Name, c.Name) + if c.HelpName == "" { + app.HelpName = c.HelpName + } else { + app.HelpName = app.Name + } + + if c.Description != "" { + app.Usage = c.Description + } else { + app.Usage = c.Usage + } + + // set CommandNotFound + app.CommandNotFound = ctx.App.CommandNotFound + + // set the flags and commands + app.Commands = c.Subcommands + app.Flags = c.Flags + app.HideHelp = c.HideHelp + + app.Version = ctx.App.Version + app.HideVersion = ctx.App.HideVersion + app.Compiled = ctx.App.Compiled + app.Author = ctx.App.Author + app.Email = ctx.App.Email + app.Writer = ctx.App.Writer + + app.categories = CommandCategories{} + for _, command := range c.Subcommands { + app.categories = app.categories.AddCommand(command.Category, command) + } + + sort.Sort(app.categories) + + // bash completion + app.EnableBashCompletion = ctx.App.EnableBashCompletion + if c.BashComplete != nil { + app.BashComplete = c.BashComplete + } + + // set the actions + app.Before = c.Before + app.After = c.After + if c.Action != nil { + app.Action = c.Action + } else { + app.Action = helpSubcommand.Action + } + + for index, cc := range app.Commands { + app.Commands[index].commandNamePath = []string{c.Name, cc.Name} + } + + return app.RunAsSubcommand(ctx) +} diff --git a/vendor/github.com/codegangsta/cli/context.go b/vendor/github.com/codegangsta/cli/context.go new file mode 100644 index 0000000..b66d278 --- /dev/null +++ b/vendor/github.com/codegangsta/cli/context.go @@ -0,0 +1,393 @@ +package cli + +import ( + "errors" + "flag" + "strconv" + "strings" + "time" +) + +// Context is a type that is passed through to +// each Handler action in a cli application. Context +// can be used to retrieve context-specific Args and +// parsed command-line options. +type Context struct { + App *App + Command Command + flagSet *flag.FlagSet + setFlags map[string]bool + globalSetFlags map[string]bool + parentContext *Context +} + +// Creates a new context. For use in when invoking an App or Command action. +func NewContext(app *App, set *flag.FlagSet, parentCtx *Context) *Context { + return &Context{App: app, flagSet: set, parentContext: parentCtx} +} + +// Looks up the value of a local int flag, returns 0 if no int flag exists +func (c *Context) Int(name string) int { + return lookupInt(name, c.flagSet) +} + +// Looks up the value of a local time.Duration flag, returns 0 if no time.Duration flag exists +func (c *Context) Duration(name string) time.Duration { + return lookupDuration(name, c.flagSet) +} + +// Looks up the value of a local float64 flag, returns 0 if no float64 flag exists +func (c *Context) Float64(name string) float64 { + return lookupFloat64(name, c.flagSet) +} + +// Looks up the value of a local bool flag, returns false if no bool flag exists +func (c *Context) Bool(name string) bool { + return lookupBool(name, c.flagSet) +} + +// Looks up the value of a local boolT flag, returns false if no bool flag exists +func (c *Context) BoolT(name string) bool { + return lookupBoolT(name, c.flagSet) +} + +// Looks up the value of a local string flag, returns "" if no string flag exists +func (c *Context) String(name string) string { + return lookupString(name, c.flagSet) +} + +// Looks up the value of a local string slice flag, returns nil if no string slice flag exists +func (c *Context) StringSlice(name string) []string { + return lookupStringSlice(name, c.flagSet) +} + +// Looks up the value of a local int slice flag, returns nil if no int slice flag exists +func (c *Context) IntSlice(name string) []int { + return lookupIntSlice(name, c.flagSet) +} + +// Looks up the value of a local generic flag, returns nil if no generic flag exists +func (c *Context) Generic(name string) interface{} { + return lookupGeneric(name, c.flagSet) +} + +// Looks up the value of a global int flag, returns 0 if no int flag exists +func (c *Context) GlobalInt(name string) int { + if fs := lookupGlobalFlagSet(name, c); fs != nil { + return lookupInt(name, fs) + } + return 0 +} + +// Looks up the value of a global time.Duration flag, returns 0 if no time.Duration flag exists +func (c *Context) GlobalDuration(name string) time.Duration { + if fs := lookupGlobalFlagSet(name, c); fs != nil { + return lookupDuration(name, fs) + } + return 0 +} + +// Looks up the value of a global bool flag, returns false if no bool flag exists +func (c *Context) GlobalBool(name string) bool { + if fs := lookupGlobalFlagSet(name, c); fs != nil { + return lookupBool(name, fs) + } + return false +} + +// Looks up the value of a global string flag, returns "" if no string flag exists +func (c *Context) GlobalString(name string) string { + if fs := lookupGlobalFlagSet(name, c); fs != nil { + return lookupString(name, fs) + } + return "" +} + +// Looks up the value of a global string slice flag, returns nil if no string slice flag exists +func (c *Context) GlobalStringSlice(name string) []string { + if fs := lookupGlobalFlagSet(name, c); fs != nil { + return lookupStringSlice(name, fs) + } + return nil +} + +// Looks up the value of a global int slice flag, returns nil if no int slice flag exists +func (c *Context) GlobalIntSlice(name string) []int { + if fs := lookupGlobalFlagSet(name, c); fs != nil { + return lookupIntSlice(name, fs) + } + return nil +} + +// Looks up the value of a global generic flag, returns nil if no generic flag exists +func (c *Context) GlobalGeneric(name string) interface{} { + if fs := lookupGlobalFlagSet(name, c); fs != nil { + return lookupGeneric(name, fs) + } + return nil +} + +// Returns the number of flags set +func (c *Context) NumFlags() int { + return c.flagSet.NFlag() +} + +// Determines if the flag was actually set +func (c *Context) IsSet(name string) bool { + if c.setFlags == nil { + c.setFlags = make(map[string]bool) + c.flagSet.Visit(func(f *flag.Flag) { + c.setFlags[f.Name] = true + }) + } + return c.setFlags[name] == true +} + +// Determines if the global flag was actually set +func (c *Context) GlobalIsSet(name string) bool { + if c.globalSetFlags == nil { + c.globalSetFlags = make(map[string]bool) + ctx := c + if ctx.parentContext != nil { + ctx = ctx.parentContext + } + for ; ctx != nil && c.globalSetFlags[name] == false; ctx = ctx.parentContext { + ctx.flagSet.Visit(func(f *flag.Flag) { + c.globalSetFlags[f.Name] = true + }) + } + } + return c.globalSetFlags[name] +} + +// Returns a slice of flag names used in this context. +func (c *Context) FlagNames() (names []string) { + for _, flag := range c.Command.Flags { + name := strings.Split(flag.GetName(), ",")[0] + if name == "help" { + continue + } + names = append(names, name) + } + return +} + +// Returns a slice of global flag names used by the app. +func (c *Context) GlobalFlagNames() (names []string) { + for _, flag := range c.App.Flags { + name := strings.Split(flag.GetName(), ",")[0] + if name == "help" || name == "version" { + continue + } + names = append(names, name) + } + return +} + +// Returns the parent context, if any +func (c *Context) Parent() *Context { + return c.parentContext +} + +type Args []string + +// Returns the command line arguments associated with the context. +func (c *Context) Args() Args { + args := Args(c.flagSet.Args()) + return args +} + +// Returns the number of the command line arguments. +func (c *Context) NArg() int { + return len(c.Args()) +} + +// Returns the nth argument, or else a blank string +func (a Args) Get(n int) string { + if len(a) > n { + return a[n] + } + return "" +} + +// Returns the first argument, or else a blank string +func (a Args) First() string { + return a.Get(0) +} + +// Return the rest of the arguments (not the first one) +// or else an empty string slice +func (a Args) Tail() []string { + if len(a) >= 2 { + return []string(a)[1:] + } + return []string{} +} + +// Checks if there are any arguments present +func (a Args) Present() bool { + return len(a) != 0 +} + +// Swaps arguments at the given indexes +func (a Args) Swap(from, to int) error { + if from >= len(a) || to >= len(a) { + return errors.New("index out of range") + } + a[from], a[to] = a[to], a[from] + return nil +} + +func lookupGlobalFlagSet(name string, ctx *Context) *flag.FlagSet { + if ctx.parentContext != nil { + ctx = ctx.parentContext + } + for ; ctx != nil; ctx = ctx.parentContext { + if f := ctx.flagSet.Lookup(name); f != nil { + return ctx.flagSet + } + } + return nil +} + +func lookupInt(name string, set *flag.FlagSet) int { + f := set.Lookup(name) + if f != nil { + val, err := strconv.Atoi(f.Value.String()) + if err != nil { + return 0 + } + return val + } + + return 0 +} + +func lookupDuration(name string, set *flag.FlagSet) time.Duration { + f := set.Lookup(name) + if f != nil { + val, err := time.ParseDuration(f.Value.String()) + if err == nil { + return val + } + } + + return 0 +} + +func lookupFloat64(name string, set *flag.FlagSet) float64 { + f := set.Lookup(name) + if f != nil { + val, err := strconv.ParseFloat(f.Value.String(), 64) + if err != nil { + return 0 + } + return val + } + + return 0 +} + +func lookupString(name string, set *flag.FlagSet) string { + f := set.Lookup(name) + if f != nil { + return f.Value.String() + } + + return "" +} + +func lookupStringSlice(name string, set *flag.FlagSet) []string { + f := set.Lookup(name) + if f != nil { + return (f.Value.(*StringSlice)).Value() + + } + + return nil +} + +func lookupIntSlice(name string, set *flag.FlagSet) []int { + f := set.Lookup(name) + if f != nil { + return (f.Value.(*IntSlice)).Value() + + } + + return nil +} + +func lookupGeneric(name string, set *flag.FlagSet) interface{} { + f := set.Lookup(name) + if f != nil { + return f.Value + } + return nil +} + +func lookupBool(name string, set *flag.FlagSet) bool { + f := set.Lookup(name) + if f != nil { + val, err := strconv.ParseBool(f.Value.String()) + if err != nil { + return false + } + return val + } + + return false +} + +func lookupBoolT(name string, set *flag.FlagSet) bool { + f := set.Lookup(name) + if f != nil { + val, err := strconv.ParseBool(f.Value.String()) + if err != nil { + return true + } + return val + } + + return false +} + +func copyFlag(name string, ff *flag.Flag, set *flag.FlagSet) { + switch ff.Value.(type) { + case *StringSlice: + default: + set.Set(name, ff.Value.String()) + } +} + +func normalizeFlags(flags []Flag, set *flag.FlagSet) error { + visited := make(map[string]bool) + set.Visit(func(f *flag.Flag) { + visited[f.Name] = true + }) + for _, f := range flags { + parts := strings.Split(f.GetName(), ",") + if len(parts) == 1 { + continue + } + var ff *flag.Flag + for _, name := range parts { + name = strings.Trim(name, " ") + if visited[name] { + if ff != nil { + return errors.New("Cannot use two forms of the same flag: " + name + " " + ff.Name) + } + ff = set.Lookup(name) + } + } + if ff == nil { + continue + } + for _, name := range parts { + name = strings.Trim(name, " ") + if !visited[name] { + copyFlag(name, ff, set) + } + } + } + return nil +} diff --git a/vendor/github.com/codegangsta/cli/flag.go b/vendor/github.com/codegangsta/cli/flag.go new file mode 100644 index 0000000..e951c2d --- /dev/null +++ b/vendor/github.com/codegangsta/cli/flag.go @@ -0,0 +1,546 @@ +package cli + +import ( + "flag" + "fmt" + "os" + "runtime" + "strconv" + "strings" + "time" +) + +// This flag enables bash-completion for all commands and subcommands +var BashCompletionFlag = BoolFlag{ + Name: "generate-bash-completion", +} + +// This flag prints the version for the application +var VersionFlag = BoolFlag{ + Name: "version, v", + Usage: "print the version", +} + +// This flag prints the help for all commands and subcommands +// Set to the zero value (BoolFlag{}) to disable flag -- keeps subcommand +// unless HideHelp is set to true) +var HelpFlag = BoolFlag{ + Name: "help, h", + Usage: "show help", +} + +// Flag is a common interface related to parsing flags in cli. +// For more advanced flag parsing techniques, it is recommended that +// this interface be implemented. +type Flag interface { + fmt.Stringer + // Apply Flag settings to the given flag set + Apply(*flag.FlagSet) + GetName() string +} + +func flagSet(name string, flags []Flag) *flag.FlagSet { + set := flag.NewFlagSet(name, flag.ContinueOnError) + + for _, f := range flags { + f.Apply(set) + } + return set +} + +func eachName(longName string, fn func(string)) { + parts := strings.Split(longName, ",") + for _, name := range parts { + name = strings.Trim(name, " ") + fn(name) + } +} + +// Generic is a generic parseable type identified by a specific flag +type Generic interface { + Set(value string) error + String() string +} + +// GenericFlag is the flag type for types implementing Generic +type GenericFlag struct { + Name string + Value Generic + Usage string + EnvVar string +} + +// String returns the string representation of the generic flag to display the +// help text to the user (uses the String() method of the generic flag to show +// the value) +func (f GenericFlag) String() string { + return withEnvHint(f.EnvVar, fmt.Sprintf("%s %v\t%v", prefixedNames(f.Name), f.FormatValueHelp(), f.Usage)) +} + +func (f GenericFlag) FormatValueHelp() string { + if f.Value == nil { + return "" + } + s := f.Value.String() + if len(s) == 0 { + return "" + } + return fmt.Sprintf("\"%s\"", s) +} + +// Apply takes the flagset and calls Set on the generic flag with the value +// provided by the user for parsing by the flag +func (f GenericFlag) Apply(set *flag.FlagSet) { + val := f.Value + if f.EnvVar != "" { + for _, envVar := range strings.Split(f.EnvVar, ",") { + envVar = strings.TrimSpace(envVar) + if envVal := os.Getenv(envVar); envVal != "" { + val.Set(envVal) + break + } + } + } + + eachName(f.Name, func(name string) { + set.Var(f.Value, name, f.Usage) + }) +} + +func (f GenericFlag) GetName() string { + return f.Name +} + +// StringSlice is an opaque type for []string to satisfy flag.Value +type StringSlice []string + +// Set appends the string value to the list of values +func (f *StringSlice) Set(value string) error { + *f = append(*f, value) + return nil +} + +// String returns a readable representation of this value (for usage defaults) +func (f *StringSlice) String() string { + return fmt.Sprintf("%s", *f) +} + +// Value returns the slice of strings set by this flag +func (f *StringSlice) Value() []string { + return *f +} + +// StringSlice is a string flag that can be specified multiple times on the +// command-line +type StringSliceFlag struct { + Name string + Value *StringSlice + Usage string + EnvVar string +} + +// String returns the usage +func (f StringSliceFlag) String() string { + firstName := strings.Trim(strings.Split(f.Name, ",")[0], " ") + pref := prefixFor(firstName) + return withEnvHint(f.EnvVar, fmt.Sprintf("%s [%v]\t%v", prefixedNames(f.Name), pref+firstName+" option "+pref+firstName+" option", f.Usage)) +} + +// Apply populates the flag given the flag set and environment +func (f StringSliceFlag) Apply(set *flag.FlagSet) { + if f.EnvVar != "" { + for _, envVar := range strings.Split(f.EnvVar, ",") { + envVar = strings.TrimSpace(envVar) + if envVal := os.Getenv(envVar); envVal != "" { + newVal := &StringSlice{} + for _, s := range strings.Split(envVal, ",") { + s = strings.TrimSpace(s) + newVal.Set(s) + } + f.Value = newVal + break + } + } + } + + eachName(f.Name, func(name string) { + if f.Value == nil { + f.Value = &StringSlice{} + } + set.Var(f.Value, name, f.Usage) + }) +} + +func (f StringSliceFlag) GetName() string { + return f.Name +} + +// StringSlice is an opaque type for []int to satisfy flag.Value +type IntSlice []int + +// Set parses the value into an integer and appends it to the list of values +func (f *IntSlice) Set(value string) error { + tmp, err := strconv.Atoi(value) + if err != nil { + return err + } else { + *f = append(*f, tmp) + } + return nil +} + +// String returns a readable representation of this value (for usage defaults) +func (f *IntSlice) String() string { + return fmt.Sprintf("%d", *f) +} + +// Value returns the slice of ints set by this flag +func (f *IntSlice) Value() []int { + return *f +} + +// IntSliceFlag is an int flag that can be specified multiple times on the +// command-line +type IntSliceFlag struct { + Name string + Value *IntSlice + Usage string + EnvVar string +} + +// String returns the usage +func (f IntSliceFlag) String() string { + firstName := strings.Trim(strings.Split(f.Name, ",")[0], " ") + pref := prefixFor(firstName) + return withEnvHint(f.EnvVar, fmt.Sprintf("%s [%v]\t%v", prefixedNames(f.Name), pref+firstName+" option "+pref+firstName+" option", f.Usage)) +} + +// Apply populates the flag given the flag set and environment +func (f IntSliceFlag) Apply(set *flag.FlagSet) { + if f.EnvVar != "" { + for _, envVar := range strings.Split(f.EnvVar, ",") { + envVar = strings.TrimSpace(envVar) + if envVal := os.Getenv(envVar); envVal != "" { + newVal := &IntSlice{} + for _, s := range strings.Split(envVal, ",") { + s = strings.TrimSpace(s) + err := newVal.Set(s) + if err != nil { + fmt.Fprintf(os.Stderr, err.Error()) + } + } + f.Value = newVal + break + } + } + } + + eachName(f.Name, func(name string) { + if f.Value == nil { + f.Value = &IntSlice{} + } + set.Var(f.Value, name, f.Usage) + }) +} + +func (f IntSliceFlag) GetName() string { + return f.Name +} + +// BoolFlag is a switch that defaults to false +type BoolFlag struct { + Name string + Usage string + EnvVar string + Destination *bool +} + +// String returns a readable representation of this value (for usage defaults) +func (f BoolFlag) String() string { + return withEnvHint(f.EnvVar, fmt.Sprintf("%s\t%v", prefixedNames(f.Name), f.Usage)) +} + +// Apply populates the flag given the flag set and environment +func (f BoolFlag) Apply(set *flag.FlagSet) { + val := false + if f.EnvVar != "" { + for _, envVar := range strings.Split(f.EnvVar, ",") { + envVar = strings.TrimSpace(envVar) + if envVal := os.Getenv(envVar); envVal != "" { + envValBool, err := strconv.ParseBool(envVal) + if err == nil { + val = envValBool + } + break + } + } + } + + eachName(f.Name, func(name string) { + if f.Destination != nil { + set.BoolVar(f.Destination, name, val, f.Usage) + return + } + set.Bool(name, val, f.Usage) + }) +} + +func (f BoolFlag) GetName() string { + return f.Name +} + +// BoolTFlag this represents a boolean flag that is true by default, but can +// still be set to false by --some-flag=false +type BoolTFlag struct { + Name string + Usage string + EnvVar string + Destination *bool +} + +// String returns a readable representation of this value (for usage defaults) +func (f BoolTFlag) String() string { + return withEnvHint(f.EnvVar, fmt.Sprintf("%s\t%v", prefixedNames(f.Name), f.Usage)) +} + +// Apply populates the flag given the flag set and environment +func (f BoolTFlag) Apply(set *flag.FlagSet) { + val := true + if f.EnvVar != "" { + for _, envVar := range strings.Split(f.EnvVar, ",") { + envVar = strings.TrimSpace(envVar) + if envVal := os.Getenv(envVar); envVal != "" { + envValBool, err := strconv.ParseBool(envVal) + if err == nil { + val = envValBool + break + } + } + } + } + + eachName(f.Name, func(name string) { + if f.Destination != nil { + set.BoolVar(f.Destination, name, val, f.Usage) + return + } + set.Bool(name, val, f.Usage) + }) +} + +func (f BoolTFlag) GetName() string { + return f.Name +} + +// StringFlag represents a flag that takes as string value +type StringFlag struct { + Name string + Value string + Usage string + EnvVar string + Destination *string +} + +// String returns the usage +func (f StringFlag) String() string { + return withEnvHint(f.EnvVar, fmt.Sprintf("%s %v\t%v", prefixedNames(f.Name), f.FormatValueHelp(), f.Usage)) +} + +func (f StringFlag) FormatValueHelp() string { + s := f.Value + if len(s) == 0 { + return "" + } + return fmt.Sprintf("\"%s\"", s) +} + +// Apply populates the flag given the flag set and environment +func (f StringFlag) Apply(set *flag.FlagSet) { + if f.EnvVar != "" { + for _, envVar := range strings.Split(f.EnvVar, ",") { + envVar = strings.TrimSpace(envVar) + if envVal := os.Getenv(envVar); envVal != "" { + f.Value = envVal + break + } + } + } + + eachName(f.Name, func(name string) { + if f.Destination != nil { + set.StringVar(f.Destination, name, f.Value, f.Usage) + return + } + set.String(name, f.Value, f.Usage) + }) +} + +func (f StringFlag) GetName() string { + return f.Name +} + +// IntFlag is a flag that takes an integer +// Errors if the value provided cannot be parsed +type IntFlag struct { + Name string + Value int + Usage string + EnvVar string + Destination *int +} + +// String returns the usage +func (f IntFlag) String() string { + return withEnvHint(f.EnvVar, fmt.Sprintf("%s \"%v\"\t%v", prefixedNames(f.Name), f.Value, f.Usage)) +} + +// Apply populates the flag given the flag set and environment +func (f IntFlag) Apply(set *flag.FlagSet) { + if f.EnvVar != "" { + for _, envVar := range strings.Split(f.EnvVar, ",") { + envVar = strings.TrimSpace(envVar) + if envVal := os.Getenv(envVar); envVal != "" { + envValInt, err := strconv.ParseInt(envVal, 0, 64) + if err == nil { + f.Value = int(envValInt) + break + } + } + } + } + + eachName(f.Name, func(name string) { + if f.Destination != nil { + set.IntVar(f.Destination, name, f.Value, f.Usage) + return + } + set.Int(name, f.Value, f.Usage) + }) +} + +func (f IntFlag) GetName() string { + return f.Name +} + +// DurationFlag is a flag that takes a duration specified in Go's duration +// format: https://golang.org/pkg/time/#ParseDuration +type DurationFlag struct { + Name string + Value time.Duration + Usage string + EnvVar string + Destination *time.Duration +} + +// String returns a readable representation of this value (for usage defaults) +func (f DurationFlag) String() string { + return withEnvHint(f.EnvVar, fmt.Sprintf("%s \"%v\"\t%v", prefixedNames(f.Name), f.Value, f.Usage)) +} + +// Apply populates the flag given the flag set and environment +func (f DurationFlag) Apply(set *flag.FlagSet) { + if f.EnvVar != "" { + for _, envVar := range strings.Split(f.EnvVar, ",") { + envVar = strings.TrimSpace(envVar) + if envVal := os.Getenv(envVar); envVal != "" { + envValDuration, err := time.ParseDuration(envVal) + if err == nil { + f.Value = envValDuration + break + } + } + } + } + + eachName(f.Name, func(name string) { + if f.Destination != nil { + set.DurationVar(f.Destination, name, f.Value, f.Usage) + return + } + set.Duration(name, f.Value, f.Usage) + }) +} + +func (f DurationFlag) GetName() string { + return f.Name +} + +// Float64Flag is a flag that takes an float value +// Errors if the value provided cannot be parsed +type Float64Flag struct { + Name string + Value float64 + Usage string + EnvVar string + Destination *float64 +} + +// String returns the usage +func (f Float64Flag) String() string { + return withEnvHint(f.EnvVar, fmt.Sprintf("%s \"%v\"\t%v", prefixedNames(f.Name), f.Value, f.Usage)) +} + +// Apply populates the flag given the flag set and environment +func (f Float64Flag) Apply(set *flag.FlagSet) { + if f.EnvVar != "" { + for _, envVar := range strings.Split(f.EnvVar, ",") { + envVar = strings.TrimSpace(envVar) + if envVal := os.Getenv(envVar); envVal != "" { + envValFloat, err := strconv.ParseFloat(envVal, 10) + if err == nil { + f.Value = float64(envValFloat) + } + } + } + } + + eachName(f.Name, func(name string) { + if f.Destination != nil { + set.Float64Var(f.Destination, name, f.Value, f.Usage) + return + } + set.Float64(name, f.Value, f.Usage) + }) +} + +func (f Float64Flag) GetName() string { + return f.Name +} + +func prefixFor(name string) (prefix string) { + if len(name) == 1 { + prefix = "-" + } else { + prefix = "--" + } + + return +} + +func prefixedNames(fullName string) (prefixed string) { + parts := strings.Split(fullName, ",") + for i, name := range parts { + name = strings.Trim(name, " ") + prefixed += prefixFor(name) + name + if i < len(parts)-1 { + prefixed += ", " + } + } + return +} + +func withEnvHint(envVar, str string) string { + envText := "" + if envVar != "" { + prefix := "$" + suffix := "" + sep := ", $" + if runtime.GOOS == "windows" { + prefix = "%" + suffix = "%" + sep = "%, %" + } + envText = fmt.Sprintf(" [%s%s%s]", prefix, strings.Join(strings.Split(envVar, ","), sep), suffix) + } + return str + envText +} diff --git a/vendor/github.com/codegangsta/cli/help.go b/vendor/github.com/codegangsta/cli/help.go new file mode 100644 index 0000000..adf157d --- /dev/null +++ b/vendor/github.com/codegangsta/cli/help.go @@ -0,0 +1,253 @@ +package cli + +import ( + "fmt" + "io" + "strings" + "text/tabwriter" + "text/template" +) + +// The text template for the Default help topic. +// cli.go uses text/template to render templates. You can +// render custom help text by setting this variable. +var AppHelpTemplate = `NAME: + {{.Name}} - {{.Usage}} + +USAGE: + {{if .UsageText}}{{.UsageText}}{{else}}{{.HelpName}} {{if .Flags}}[global options]{{end}}{{if .Commands}} command [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}}{{end}} + {{if .Version}}{{if not .HideVersion}} +VERSION: + {{.Version}} + {{end}}{{end}}{{if len .Authors}} +AUTHOR(S): + {{range .Authors}}{{ . }}{{end}} + {{end}}{{if .Commands}} +COMMANDS:{{range .Categories}}{{if .Name}} + {{.Name}}{{ ":" }}{{end}}{{range .Commands}} + {{.Name}}{{with .ShortName}}, {{.}}{{end}}{{ "\t" }}{{.Usage}}{{end}} +{{end}}{{end}}{{if .Flags}} +GLOBAL OPTIONS: + {{range .Flags}}{{.}} + {{end}}{{end}}{{if .Copyright }} +COPYRIGHT: + {{.Copyright}} + {{end}} +` + +// The text template for the command help topic. +// cli.go uses text/template to render templates. You can +// render custom help text by setting this variable. +var CommandHelpTemplate = `NAME: + {{.HelpName}} - {{.Usage}} + +USAGE: + {{.HelpName}}{{if .Flags}} [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}}{{if .Category}} + +CATEGORY: + {{.Category}}{{end}}{{if .Description}} + +DESCRIPTION: + {{.Description}}{{end}}{{if .Flags}} + +OPTIONS: + {{range .Flags}}{{.}} + {{end}}{{ end }} +` + +// The text template for the subcommand help topic. +// cli.go uses text/template to render templates. You can +// render custom help text by setting this variable. +var SubcommandHelpTemplate = `NAME: + {{.HelpName}} - {{.Usage}} + +USAGE: + {{.HelpName}} command{{if .Flags}} [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}} + +COMMANDS:{{range .Categories}}{{if .Name}} + {{.Name}}{{ ":" }}{{end}}{{range .Commands}} + {{.Name}}{{with .ShortName}}, {{.}}{{end}}{{ "\t" }}{{.Usage}}{{end}} +{{end}}{{if .Flags}} +OPTIONS: + {{range .Flags}}{{.}} + {{end}}{{end}} +` + +var helpCommand = Command{ + Name: "help", + Aliases: []string{"h"}, + Usage: "Shows a list of commands or help for one command", + ArgsUsage: "[command]", + Action: func(c *Context) { + args := c.Args() + if args.Present() { + ShowCommandHelp(c, args.First()) + } else { + ShowAppHelp(c) + } + }, +} + +var helpSubcommand = Command{ + Name: "help", + Aliases: []string{"h"}, + Usage: "Shows a list of commands or help for one command", + ArgsUsage: "[command]", + Action: func(c *Context) { + args := c.Args() + if args.Present() { + ShowCommandHelp(c, args.First()) + } else { + ShowSubcommandHelp(c) + } + }, +} + +// Prints help for the App or Command +type helpPrinter func(w io.Writer, templ string, data interface{}) + +var HelpPrinter helpPrinter = printHelp + +// Prints version for the App +var VersionPrinter = printVersion + +func ShowAppHelp(c *Context) { + HelpPrinter(c.App.Writer, AppHelpTemplate, c.App) +} + +// Prints the list of subcommands as the default app completion method +func DefaultAppComplete(c *Context) { + for _, command := range c.App.Commands { + for _, name := range command.Names() { + fmt.Fprintln(c.App.Writer, name) + } + } +} + +// Prints help for the given command +func ShowCommandHelp(ctx *Context, command string) { + // show the subcommand help for a command with subcommands + if command == "" { + HelpPrinter(ctx.App.Writer, SubcommandHelpTemplate, ctx.App) + return + } + + for _, c := range ctx.App.Commands { + if c.HasName(command) { + HelpPrinter(ctx.App.Writer, CommandHelpTemplate, c) + return + } + } + + if ctx.App.CommandNotFound != nil { + ctx.App.CommandNotFound(ctx, command) + } else { + fmt.Fprintf(ctx.App.Writer, "No help topic for '%v'\n", command) + } +} + +// Prints help for the given subcommand +func ShowSubcommandHelp(c *Context) { + ShowCommandHelp(c, c.Command.Name) +} + +// Prints the version number of the App +func ShowVersion(c *Context) { + VersionPrinter(c) +} + +func printVersion(c *Context) { + fmt.Fprintf(c.App.Writer, "%v version %v\n", c.App.Name, c.App.Version) +} + +// Prints the lists of commands within a given context +func ShowCompletions(c *Context) { + a := c.App + if a != nil && a.BashComplete != nil { + a.BashComplete(c) + } +} + +// Prints the custom completions for a given command +func ShowCommandCompletions(ctx *Context, command string) { + c := ctx.App.Command(command) + if c != nil && c.BashComplete != nil { + c.BashComplete(ctx) + } +} + +func printHelp(out io.Writer, templ string, data interface{}) { + funcMap := template.FuncMap{ + "join": strings.Join, + } + + w := tabwriter.NewWriter(out, 0, 8, 1, '\t', 0) + t := template.Must(template.New("help").Funcs(funcMap).Parse(templ)) + err := t.Execute(w, data) + if err != nil { + // If the writer is closed, t.Execute will fail, and there's nothing + // we can do to recover. We could send this to os.Stderr if we need. + return + } + w.Flush() +} + +func checkVersion(c *Context) bool { + found := false + if VersionFlag.Name != "" { + eachName(VersionFlag.Name, func(name string) { + if c.GlobalBool(name) || c.Bool(name) { + found = true + } + }) + } + return found +} + +func checkHelp(c *Context) bool { + found := false + if HelpFlag.Name != "" { + eachName(HelpFlag.Name, func(name string) { + if c.GlobalBool(name) || c.Bool(name) { + found = true + } + }) + } + return found +} + +func checkCommandHelp(c *Context, name string) bool { + if c.Bool("h") || c.Bool("help") { + ShowCommandHelp(c, name) + return true + } + + return false +} + +func checkSubcommandHelp(c *Context) bool { + if c.GlobalBool("h") || c.GlobalBool("help") { + ShowSubcommandHelp(c) + return true + } + + return false +} + +func checkCompletions(c *Context) bool { + if (c.GlobalBool(BashCompletionFlag.Name) || c.Bool(BashCompletionFlag.Name)) && c.App.EnableBashCompletion { + ShowCompletions(c) + return true + } + + return false +} + +func checkCommandCompletions(c *Context, name string) bool { + if c.Bool(BashCompletionFlag.Name) && c.App.EnableBashCompletion { + ShowCommandCompletions(c, name) + return true + } + + return false +} diff --git a/vendor/github.com/joho/godotenv/LICENCE b/vendor/github.com/joho/godotenv/LICENCE new file mode 100644 index 0000000..e7ddd51 --- /dev/null +++ b/vendor/github.com/joho/godotenv/LICENCE @@ -0,0 +1,23 @@ +Copyright (c) 2013 John Barton + +MIT License + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + diff --git a/vendor/github.com/joho/godotenv/README.md b/vendor/github.com/joho/godotenv/README.md new file mode 100644 index 0000000..05c47e6 --- /dev/null +++ b/vendor/github.com/joho/godotenv/README.md @@ -0,0 +1,127 @@ +# GoDotEnv [![wercker status](https://app.wercker.com/status/507594c2ec7e60f19403a568dfea0f78 "wercker status")](https://app.wercker.com/project/bykey/507594c2ec7e60f19403a568dfea0f78) + +A Go (golang) port of the Ruby dotenv project (which loads env vars from a .env file) + +From the original Library: + +> Storing configuration in the environment is one of the tenets of a twelve-factor app. Anything that is likely to change between deployment environments–such as resource handles for databases or credentials for external services–should be extracted from the code into environment variables. +> +> But it is not always practical to set environment variables on development machines or continuous integration servers where multiple projects are run. Dotenv load variables from a .env file into ENV when the environment is bootstrapped. + +It can be used as a library (for loading in env for your own daemons etc) or as a bin command. + +There is test coverage and CI for both linuxish and windows environments, but I make no guarantees about the bin version working on windows. + +## Installation + +As a library + +```shell +go get github.com/joho/godotenv +``` + +or if you want to use it as a bin command +```shell +go get github.com/joho/godotenv/cmd/godotenv +``` + +## Usage + +Add your application configuration to your `.env` file in the root of your project: + +```shell +S3_BUCKET=YOURS3BUCKET +SECRET_KEY=YOURSECRETKEYGOESHERE +``` + +Then in your Go app you can do something like + +```go +package main + +import ( + "github.com/joho/godotenv" + "log" + "os" +) + +func main() { + err := godotenv.Load() + if err != nil { + log.Fatal("Error loading .env file") + } + + s3Bucket := os.Getenv("S3_BUCKET") + secretKey := os.Getenv("SECRET_KEY") + + // now do something with s3 or whatever +} +``` + +If you're even lazier than that, you can just take advantage of the autoload package which will read in `.env` on import + +```go +import _ "github.com/joho/godotenv/autoload" +``` + +While `.env` in the project root is the default, you don't have to be constrained, both examples below are 100% legit + +```go +_ = godotenv.Load("somerandomfile") +_ = godotenv.Load("filenumberone.env", "filenumbertwo.env") +``` + +If you want to be really fancy with your env file you can do comments and exports (below is a valid env file) + +```shell +# I am a comment and that is OK +SOME_VAR=someval +FOO=BAR # comments at line end are OK too +export BAR=BAZ +``` + +Or finally you can do YAML(ish) style + +```yaml +FOO: bar +BAR: baz +``` + +as a final aside, if you don't want godotenv munging your env you can just get a map back instead + +```go +var myEnv map[string]string +myEnv, err := godotenv.Read() + +s3Bucket := myEnv["S3_BUCKET"] +``` + +### Command Mode + +Assuming you've installed the command as above and you've got `$GOPATH/bin` in your `$PATH` + +``` +godotenv -f /some/path/to/.env some_command with some args +``` + +If you don't specify `-f` it will fall back on the default of loading `.env` in `PWD` + +## Contributing + +Contributions are most welcome! The parser itself is pretty stupidly naive and I wouldn't be surprised if it breaks with edge cases. + +*code changes without tests will not be accepted* + +1. Fork it +2. Create your feature branch (`git checkout -b my-new-feature`) +3. Commit your changes (`git commit -am 'Added some feature'`) +4. Push to the branch (`git push origin my-new-feature`) +5. Create new Pull Request + +## CI + +Linux: [![wercker status](https://app.wercker.com/status/507594c2ec7e60f19403a568dfea0f78/m "wercker status")](https://app.wercker.com/project/bykey/507594c2ec7e60f19403a568dfea0f78) Windows: [![Build status](https://ci.appveyor.com/api/projects/status/9v40vnfvvgde64u4)](https://ci.appveyor.com/project/joho/godotenv) + +## Who? + +The original library [dotenv](https://github.com/bkeepers/dotenv) was written by [Brandon Keepers](http://opensoul.org/), and this port was done by [John Barton](http://whoisjohnbarton.com) based off the tests/fixtures in the original library. diff --git a/vendor/github.com/joho/godotenv/autoload/autoload.go b/vendor/github.com/joho/godotenv/autoload/autoload.go new file mode 100644 index 0000000..fbcd2bd --- /dev/null +++ b/vendor/github.com/joho/godotenv/autoload/autoload.go @@ -0,0 +1,15 @@ +package autoload + +/* + You can just read the .env file on import just by doing + + import _ "github.com/joho/godotenv/autoload" + + And bob's your mother's brother +*/ + +import "github.com/joho/godotenv" + +func init() { + godotenv.Load() +} diff --git a/vendor/github.com/joho/godotenv/godotenv.go b/vendor/github.com/joho/godotenv/godotenv.go new file mode 100644 index 0000000..94b2676 --- /dev/null +++ b/vendor/github.com/joho/godotenv/godotenv.go @@ -0,0 +1,229 @@ +// Package godotenv is a go port of the ruby dotenv library (https://github.com/bkeepers/dotenv) +// +// Examples/readme can be found on the github page at https://github.com/joho/godotenv +// +// The TL;DR is that you make a .env file that looks something like +// +// SOME_ENV_VAR=somevalue +// +// and then in your go code you can call +// +// godotenv.Load() +// +// and all the env vars declared in .env will be avaiable through os.Getenv("SOME_ENV_VAR") +package godotenv + +import ( + "bufio" + "errors" + "os" + "os/exec" + "strings" +) + +// Load will read your env file(s) and load them into ENV for this process. +// +// Call this function as close as possible to the start of your program (ideally in main) +// +// If you call Load without any args it will default to loading .env in the current path +// +// You can otherwise tell it which files to load (there can be more than one) like +// +// godotenv.Load("fileone", "filetwo") +// +// It's important to note that it WILL NOT OVERRIDE an env variable that already exists - consider the .env file to set dev vars or sensible defaults +func Load(filenames ...string) (err error) { + filenames = filenamesOrDefault(filenames) + + for _, filename := range filenames { + err = loadFile(filename, false) + if err != nil { + return // return early on a spazout + } + } + return +} + +// Overload will read your env file(s) and load them into ENV for this process. +// +// Call this function as close as possible to the start of your program (ideally in main) +// +// If you call Overload without any args it will default to loading .env in the current path +// +// You can otherwise tell it which files to load (there can be more than one) like +// +// godotenv.Overload("fileone", "filetwo") +// +// It's important to note this WILL OVERRIDE an env variable that already exists - consider the .env file to forcefilly set all vars. +func Overload(filenames ...string) (err error) { + filenames = filenamesOrDefault(filenames) + + for _, filename := range filenames { + err = loadFile(filename, true) + if err != nil { + return // return early on a spazout + } + } + return +} + +// Read all env (with same file loading semantics as Load) but return values as +// a map rather than automatically writing values into env +func Read(filenames ...string) (envMap map[string]string, err error) { + filenames = filenamesOrDefault(filenames) + envMap = make(map[string]string) + + for _, filename := range filenames { + individualEnvMap, individualErr := readFile(filename) + + if individualErr != nil { + err = individualErr + return // return early on a spazout + } + + for key, value := range individualEnvMap { + envMap[key] = value + } + } + + return +} + +// Exec loads env vars from the specified filenames (empty map falls back to default) +// then executes the cmd specified. +// +// Simply hooks up os.Stdin/err/out to the command and calls Run() +// +// If you want more fine grained control over your command it's recommended +// that you use `Load()` or `Read()` and the `os/exec` package yourself. +func Exec(filenames []string, cmd string, cmdArgs []string) error { + Load(filenames...) + + command := exec.Command(cmd, cmdArgs...) + command.Stdin = os.Stdin + command.Stdout = os.Stdout + command.Stderr = os.Stderr + return command.Run() +} + +func filenamesOrDefault(filenames []string) []string { + if len(filenames) == 0 { + return []string{".env"} + } + return filenames +} + +func loadFile(filename string, overload bool) error { + envMap, err := readFile(filename) + if err != nil { + return err + } + + for key, value := range envMap { + if os.Getenv(key) == "" || overload { + os.Setenv(key, value) + } + } + + return nil +} + +func readFile(filename string) (envMap map[string]string, err error) { + file, err := os.Open(filename) + if err != nil { + return + } + defer file.Close() + + envMap = make(map[string]string) + + var lines []string + scanner := bufio.NewScanner(file) + for scanner.Scan() { + lines = append(lines, scanner.Text()) + } + + for _, fullLine := range lines { + if !isIgnoredLine(fullLine) { + key, value, err := parseLine(fullLine) + + if err == nil { + envMap[key] = value + } + } + } + return +} + +func parseLine(line string) (key string, value string, err error) { + if len(line) == 0 { + err = errors.New("zero length string") + return + } + + // ditch the comments (but keep quoted hashes) + if strings.Contains(line, "#") { + segmentsBetweenHashes := strings.Split(line, "#") + quotesAreOpen := false + var segmentsToKeep []string + for _, segment := range segmentsBetweenHashes { + if strings.Count(segment, "\"") == 1 || strings.Count(segment, "'") == 1 { + if quotesAreOpen { + quotesAreOpen = false + segmentsToKeep = append(segmentsToKeep, segment) + } else { + quotesAreOpen = true + } + } + + if len(segmentsToKeep) == 0 || quotesAreOpen { + segmentsToKeep = append(segmentsToKeep, segment) + } + } + + line = strings.Join(segmentsToKeep, "#") + } + + // now split key from value + splitString := strings.SplitN(line, "=", 2) + + if len(splitString) != 2 { + // try yaml mode! + splitString = strings.SplitN(line, ":", 2) + } + + if len(splitString) != 2 { + err = errors.New("Can't separate key from value") + return + } + + // Parse the key + key = splitString[0] + if strings.HasPrefix(key, "export") { + key = strings.TrimPrefix(key, "export") + } + key = strings.Trim(key, " ") + + // Parse the value + value = splitString[1] + // trim + value = strings.Trim(value, " ") + + // check if we've got quoted values + if strings.Count(value, "\"") == 2 || strings.Count(value, "'") == 2 { + // pull the quotes off the edges + value = strings.Trim(value, "\"'") + + // expand quotes + value = strings.Replace(value, "\\\"", "\"", -1) + // expand newlines + value = strings.Replace(value, "\\n", "\n", -1) + } + + return +} + +func isIgnoredLine(line string) bool { + trimmedLine := strings.Trim(line, " \n\t") + return len(trimmedLine) == 0 || strings.HasPrefix(trimmedLine, "#") +} diff --git a/vendor/github.com/joho/godotenv/wercker.yml b/vendor/github.com/joho/godotenv/wercker.yml new file mode 100644 index 0000000..c716ac9 --- /dev/null +++ b/vendor/github.com/joho/godotenv/wercker.yml @@ -0,0 +1 @@ +box: pjvds/golang diff --git a/vendor/vendor.json b/vendor/vendor.json new file mode 100644 index 0000000..e42a79e --- /dev/null +++ b/vendor/vendor.json @@ -0,0 +1,21 @@ +{ + "comment": "", + "ignore": "test", + "package": [ + { + "path": "github.com/codegangsta/cli", + "revision": "71f57d300dd6a780ac1856c005c4b518cfd498ec", + "revisionTime": "2016-04-03T18:54:34-07:00" + }, + { + "path": "github.com/joho/godotenv", + "revision": "4ed13390c0acd2ff4e371e64d8b97c8954138243", + "revisionTime": "2015-09-07T11:02:28+10:00" + }, + { + "path": "github.com/joho/godotenv/autoload", + "revision": "4ed13390c0acd2ff4e371e64d8b97c8954138243", + "revisionTime": "2015-09-07T11:02:28+10:00" + } + ] +} From 7a1e6b27668961ed238a514100b239b18c3da409 Mon Sep 17 00:00:00 2001 From: Brad Rydzewski Date: Tue, 3 May 2016 16:34:08 -0700 Subject: [PATCH 09/12] update plugin for 0.5 release --- .drone.sec | 2 +- .drone.yml | 2 +- DOCS.md | 65 +++++------------------------------------------------- README.md | 5 +++-- main.go | 22 +++++------------- plugin.go | 10 +++------ 6 files changed, 20 insertions(+), 86 deletions(-) diff --git a/.drone.sec b/.drone.sec index 4c29b57..7d03135 100644 --- a/.drone.sec +++ b/.drone.sec @@ -1 +1 @@ -eyJhbGciOiJSU0EtT0FFUCIsImVuYyI6IkExMjhHQ00ifQ.G6Nny2XGFBtLfgrL0cPtC35a5yZkANTXmCQNlV4d91wNW1FSZWdHhTC9uwn5OaGq8TaeSlg3SLdt02ypmx38WenTc1zFbSjElfVWm8nQJP689ggy5EL0vTIXczbzwKk3Y7KVFAH0g4Xi_Lr6SdJJTIZREACkKcVDWVod44e32bFF7iF-u9vhIQ0GpB76d-WFJgSzBlh4ojBrCA_un2ItjJ3rVPfJi00VbTkh8fiovOsiTWg1MhgcTDyh78tqlD-f9fdeP4W5a88-v7l6H45ttrmhfPFyT46Xju7uhtdLX5MwtqZ_augSelw8qkXmUVM_yNqBDA04HGJBSGxT8rKWKg.QuQsEmFZNiVkEwAJ.XuYkMZlmfeQRaHFpRCg0Y6Ynkj7bxHyPLQ1Okmnk-F7HGogeaS4zxrbc9GQK_EKe5mEaDE_NkS76tAABChSnsXONmTezeRakyatc00PZYykARf2dwodwENI9rrHqZFJqnC0l5moFQyC9iFICeLP9RVdQ9DZhc0t-GU2hCK_CglZ09Ag2eHtvi8x75483_x_ZtVkpF1vsMXOxUMCDO_wCVcWy6o-Fkx7gPBR6AR56qD-MRBGeNn975IQ3bV1fxRTYnvRC3tX6xslZoQ6M-lIZTWFTTvZkEWYe3FSwD2HYUbm7rI4iivoNsFzmsMl38SqSQ_s_MyLcju9CcJsVmXokixKk-TcVCoDDG62gUQWTXEG4jz2sMxfwU4H2Fgg7AlFh2_At3gSVr2GXdgMtB1TYr0e1KQIVW_3DbNtYi6_neHg.k_x3SoJ2TUSAx_awKAYQWw \ No newline at end of file +eyJhbGciOiJSU0EtT0FFUCIsImVuYyI6IkExMjhHQ00ifQ.FfwnPUF8FCShz3ATEQvcWR2tTmdU8O-KvxK5APYiusOEP6y9CyRbkXsjIYGKTCcO3ZsfKyJwhZ_ljP0M1WRqXo90OF53-o-gJs359Zpa-3buxKrsSX_8IUvlA62y15uMS0KLuiS-h-78k8CannuSl2afVsHtwYOT0sVx20dYk3-E0Qw_P-E3jUmUTNDMJbG6FjB4UM2Q7pPh8jHx-lN9nkI6tz9M3gchZpfzPVSgm2EJlZ66iobvuLgLmpSA6VS4a9zhijgry5sb_2KW0IGzXsYIW9mYPk3uXGzZyT__Lcmck0nf1yed5ATzaGyoI4EmogLu5sVNzq-xDbBDzyuDiQ.H5viJYEJ76XkzfFu.JU6vKDHJFye0CEFqLhcQu9ZmK0yBJF-WvpZuk-wH-lnLS8DlygL4arAHT_1rTAdWdHfM5dNbx5lt0EUJ_-0ibWsilSEOtBmjP61Ryc-1HNaqeeXFXzo8dU6JKGwc0AanZiuL1N8QP4sNgMMGJoloIhzfVJz8VDw1uQ7NtmpUY9_vFJBZGeYTbhkpEmijTdTcM9HuXkYqQqoueILWRhK6YlxyU0SUrLYAAXqlX_D42zoI_kP42qWQYTCKz2QF0TU.tfBboktLMAa9E1r1T-m2Rg \ No newline at end of file diff --git a/.drone.yml b/.drone.yml index 9c4bd7a..4421738 100644 --- a/.drone.yml +++ b/.drone.yml @@ -20,7 +20,7 @@ publish: repo: plugins/docker tag: latest when: - branch: master + branch: release/0.5 plugin: name: Docker diff --git a/DOCS.md b/DOCS.md index 8d8d34b..5decc2f 100644 --- a/DOCS.md +++ b/DOCS.md @@ -1,5 +1,4 @@ -Use the Docker plugin to build and push Docker images to a registry. -The following parameters are used to configure this plugin: +Use the Docker plugin to build and push Docker images to a registry. The following parameters are used to configure this plugin: * `registry` - authenticates to this registry * `username` - authenticates with this username @@ -8,18 +7,13 @@ The following parameters are used to configure this plugin: * `repo` - repository name for the image * `tag` - repository tag for the image * `file` - dockerfile to be used, defaults to Dockerfile -* `auth` - auth token for the registry * `context` - the context path to use, defaults to root of the git repo -* `force_tag` - replace existing matched image tags * `insecure` - enable insecure communication to this registry * `mirror` - use a mirror registry instead of pulling images directly from the central Hub * `bip` - use for pass bridge ip * `dns` - set custom dns servers for the container * `storage_driver` - use `aufs`, `devicemapper`, `btrfs` or `overlay` driver -* `save` - save image layers to the specified tar file (see [docker save](https://docs.docker.com/engine/reference/commandline/save/)) - * `destination` - absolute / relative destination path - * `tag` - cherry-pick tags to save (optional) -* `load` - restore image layers from the specified tar file +* `storage_path` - location of docker daemon storage on disk * `build_args` - [build arguments](https://docs.docker.com/engine/reference/commandline/build/#set-build-time-variables-build-arg) to pass to `docker build` The following is a sample Docker configuration in your .drone.yml file: @@ -36,19 +30,7 @@ publish: insecure: false ``` -You may want to dynamically tag your image. Use the `$$BRANCH`, `$$COMMIT` and `$$BUILD_NUMBER` variables to tag your image with the branch, commit sha or build number: - -```yaml -publish: - docker: - username: kevinbacon - password: pa55word - email: kevin.bacon@mail.com - repo: foo/bar - tag: $$BRANCH -``` - -Or you may prefer to build an image with multiple tags: +Publish and image with multiple tags: ```yaml publish: @@ -59,13 +41,11 @@ publish: repo: foo/bar tag: - latest - - "1.0.1" + - 1.0.1 - "1.0" ``` -Note that in the above example we quote the version numbers. If the yaml parser interprets the value as a number it will cause a parsing error. - -It's also possible to pass build arguments to docker: +Build an image with arguments: ```yaml publish: @@ -89,6 +69,7 @@ This is the preferred method when using the `overlay` or `aufs` storage drivers. ```yaml publish: docker: + storage_path: /drone/docker username: kevinbacon password: pa55word email: kevin.bacon@mail.com @@ -102,40 +83,6 @@ cache: - /drone/docker ``` -NOTE: This probably won't work correctly with the `btrfs` driver, and it will be very inefficient with the `devicemapper` driver. Please make sure to use the `overlay` or `aufs` storage driver with this method. - -### Layer Caching - -The below example combines Drone's caching feature and Docker's `save` and `load` capabilities to cache and restore image layers between builds: - -```yaml -publish: - docker: - username: kevinbacon - password: pa55word - email: kevin.bacon@mail.com - repo: foo/bar - tag: - - latest - - "1.0.1" - load: docker/image.tar - save: - destination: docker/image.tar - tag: latest - -cache: - mount: - - docker/image.tar -``` - -You might also want to create a `.dockerignore` file in your repo to exclude `image.tar` from Docker build context: - -``` -docker/* -``` - -In some cases caching will greatly improve build performance, however, the tradeoff is that caching Docker image layers may consume very large amounts of disk space. - ## Troubleshooting For detailed output you can set the `DOCKER_LAUNCH_DEBUG` environment variable in your plugin configuration. This starts Docker with verbose logging enabled. diff --git a/README.md b/README.md index 4e8fd4a..1564e78 100644 --- a/README.md +++ b/README.md @@ -37,7 +37,8 @@ docker run --rm \ -e PLUGIN_TAG=latest \ -e PLUGIN_REPO=octocat/hello-world \ -e DRONE_COMMIT_SHA=d8dbe4d94f15fe89232e0402c6e8a0ddf21af3ab \ - -v $(pwd)/$(pwd) - -w $(pwd) + -v $(pwd)/$(pwd) \ + -w $(pwd) \ + --privileged \ plugins/docker --dry-run ``` diff --git a/main.go b/main.go index a5407b2..0c20b0b 100644 --- a/main.go +++ b/main.go @@ -17,8 +17,8 @@ const defaultRegistry = "https://index.docker.io/v1/" func main() { app := cli.NewApp() - app.Name = "my plugin" - app.Usage = "my plugin usage" + app.Name = "docker plugin" + app.Usage = "docker plugin" app.Action = run app.Version = version app.Flags = []cli.Flag{ @@ -33,6 +33,7 @@ func main() { Name: "commit.sha", Usage: "git commit sha", EnvVar: "DRONE_COMMIT_SHA", + Value: "00000000", }, // daemon parameters @@ -47,7 +48,7 @@ func main() { EnvVar: "PLUGIN_STORAGE_DRIVER", }, cli.StringFlag{ - Name: "daemon.storage-driver", + Name: "daemon.storage-path", Usage: "docker daemon storage path", Value: "/tmp/docker", EnvVar: "PLUGIN_STORAGE_PATH", @@ -67,11 +68,6 @@ func main() { Usage: "docker daemon allows insecure registries", EnvVar: "PLUGIN_INSECURE", }, - cli.BoolFlag{ - Name: "daemon.debug", - Usage: "docker daemon executes in debug mode", - EnvVar: "PLUGIN_DEBUG", - }, cli.BoolFlag{ Name: "daemon.debug", Usage: "docker daemon executes in debug mode", @@ -103,15 +99,10 @@ func main() { Value: &cli.StringSlice{"latest"}, EnvVar: "PLUGIN_TAG,PLUGIN_TAGS", }, - cli.BoolFlag{ - Name: "force", - Usage: "build tags are forced", - EnvVar: "PLUGIN_FORCE", - }, cli.StringSliceFlag{ Name: "args", Usage: "build args", - EnvVar: "PLUGIN_ARGS", + EnvVar: "PLUGIN_BUILD_ARGS", }, cli.StringFlag{ Name: "repo", @@ -162,7 +153,6 @@ func run(c *cli.Context) { Tags: c.StringSlice("tags"), Args: c.StringSlice("args"), Repo: c.String("repo"), - Force: c.Bool("force"), }, Daemon: Daemon{ Registry: c.String("docker.registry"), @@ -171,7 +161,7 @@ func run(c *cli.Context) { StoragePath: c.String("daemon.storage-path"), Insecure: c.Bool("daemon.insecure"), Disabled: c.Bool("daemon.off"), - Debug: c.Bool("deamon.debug"), + Debug: c.Bool("daemon.debug"), Bip: c.String("daemon.bip"), DNS: c.StringSlice("daemon.dns"), }, diff --git a/plugin.go b/plugin.go index e4e5313..52bda2a 100644 --- a/plugin.go +++ b/plugin.go @@ -39,7 +39,6 @@ type ( Tags []string // Docker build tags Args []string // Docker build args Repo string // Docker build repository - Force bool // Docker build forces image tag creation } // Plugin defines the Docker plugin parameters. @@ -160,14 +159,11 @@ func commandBuild(build Build) *exec.Cmd { // helper function to create the docker tag command. func commandTag(build Build, tag string) *exec.Cmd { var ( - force = fmt.Sprintf("--force=%v", build.Force) - from = build.Name - to = fmt.Sprintf("%s:%s", build.Repo, tag) + source = build.Name + target = fmt.Sprintf("%s:%s", build.Repo, tag) ) return exec.Command( - "/usr/bin/docker", "tag", - force, - from, to, + "/usr/bin/docker", "tag", source, target, ) } From 0998cbd75701462a5f1ed7d3aac91946ccea62ce Mon Sep 17 00:00:00 2001 From: Brad Rydzewski Date: Tue, 3 May 2016 16:37:12 -0700 Subject: [PATCH 10/12] disable go vet for now --- .drone.sec | 2 +- .drone.yml | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/.drone.sec b/.drone.sec index 7d03135..db6ecc0 100644 --- a/.drone.sec +++ b/.drone.sec @@ -1 +1 @@ -eyJhbGciOiJSU0EtT0FFUCIsImVuYyI6IkExMjhHQ00ifQ.FfwnPUF8FCShz3ATEQvcWR2tTmdU8O-KvxK5APYiusOEP6y9CyRbkXsjIYGKTCcO3ZsfKyJwhZ_ljP0M1WRqXo90OF53-o-gJs359Zpa-3buxKrsSX_8IUvlA62y15uMS0KLuiS-h-78k8CannuSl2afVsHtwYOT0sVx20dYk3-E0Qw_P-E3jUmUTNDMJbG6FjB4UM2Q7pPh8jHx-lN9nkI6tz9M3gchZpfzPVSgm2EJlZ66iobvuLgLmpSA6VS4a9zhijgry5sb_2KW0IGzXsYIW9mYPk3uXGzZyT__Lcmck0nf1yed5ATzaGyoI4EmogLu5sVNzq-xDbBDzyuDiQ.H5viJYEJ76XkzfFu.JU6vKDHJFye0CEFqLhcQu9ZmK0yBJF-WvpZuk-wH-lnLS8DlygL4arAHT_1rTAdWdHfM5dNbx5lt0EUJ_-0ibWsilSEOtBmjP61Ryc-1HNaqeeXFXzo8dU6JKGwc0AanZiuL1N8QP4sNgMMGJoloIhzfVJz8VDw1uQ7NtmpUY9_vFJBZGeYTbhkpEmijTdTcM9HuXkYqQqoueILWRhK6YlxyU0SUrLYAAXqlX_D42zoI_kP42qWQYTCKz2QF0TU.tfBboktLMAa9E1r1T-m2Rg \ No newline at end of file +eyJhbGciOiJSU0EtT0FFUCIsImVuYyI6IkExMjhHQ00ifQ.JlxE-t1MnDlzY5ysWCSDXIjcwUHofIan5P9cfBYGZ6NchhQWXyDlC3daezqCfGqA4aOQk0Ig4Z1Vz1fI3RX_BPE9vRGmZMOLWMpc-BBgBtXke_prDPRmDKZkqFTKC2Ys7raCWgbj3Q8r_AKaJNdWxWNELAENOocsZNF2QZv-LdHb6KAGubeKXYoU2tKfcqrIiR9ChUiaW3ZuS_4rYl7xJsFhn5_hh964qCOVfOyO49kLd7kFhAE72OKIH_Oz_Xs0NYNI3O-5L1rhvVgdaI_70ZejzeNer_e85dt1B1HW63inx3hnFkab0WdASypxGNjOlZuvnu2VmlJi-lRG37cEWA.HgW6X32JWV04WHcP.DASErZ7lvDzxWz9jFUJ3lJpRNbdEU5tRMBTe01GEdP9wveHVlZTS_cruE-1i-Nbro7v8vYxuP7Hdruk36cnCMAcaSc4Z_PqkPsR3PyLMd_VV0A1zlabo706huy0wliMnr0F8G17x043J2yyYMPgiVjbImYD4xC2OL7SbQH0zkIuctGuzQ6K2Xwpc89n2-zPP6fddX4D1Wpwi7CP_f-Cr5lP0jevZW8vv__H0tRqxSRaw57aPh4viLQVgMqWxXMA.WypEt7MPRG9ujeCLP5800w \ No newline at end of file diff --git a/.drone.yml b/.drone.yml index 4421738..0d57238 100644 --- a/.drone.yml +++ b/.drone.yml @@ -5,7 +5,6 @@ build: - CGO_ENABLED=0 - GO15VENDOREXPERIMENT=1 commands: - - go vet - go test -cover -coverprofile=coverage.out - go build -ldflags "-s -w -X main.build=$DRONE_BUILD_NUMBER" From a10a18b0329094f954e1572a38cb856896d28717 Mon Sep 17 00:00:00 2001 From: Brad Rydzewski Date: Tue, 3 May 2016 16:53:41 -0700 Subject: [PATCH 11/12] remove dry-run flag --- main.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/main.go b/main.go index 0c20b0b..7569da3 100644 --- a/main.go +++ b/main.go @@ -167,8 +167,6 @@ func run(c *cli.Context) { }, } - plugin.Dryrun = true // TODO remote this line - // this code attempts to normalize the repository name by appending the fully // qualified registry name if otherwise omitted. if plugin.Login.Registry != defaultRegistry && From 63d8305bb71d18c41fd1b3361990aa02688cdae5 Mon Sep 17 00:00:00 2001 From: Brad Rydzewski Date: Wed, 4 May 2016 12:51:06 -0700 Subject: [PATCH 12/12] fixed issue with incorrect push command --- .drone.sec | 1 - .drone.yml | 20 ++++++++------------ .drone.yml.sig | 1 + README.md | 4 +++- plugin.go | 7 ++++--- 5 files changed, 16 insertions(+), 17 deletions(-) delete mode 100644 .drone.sec create mode 100644 .drone.yml.sig diff --git a/.drone.sec b/.drone.sec deleted file mode 100644 index db6ecc0..0000000 --- a/.drone.sec +++ /dev/null @@ -1 +0,0 @@ -eyJhbGciOiJSU0EtT0FFUCIsImVuYyI6IkExMjhHQ00ifQ.JlxE-t1MnDlzY5ysWCSDXIjcwUHofIan5P9cfBYGZ6NchhQWXyDlC3daezqCfGqA4aOQk0Ig4Z1Vz1fI3RX_BPE9vRGmZMOLWMpc-BBgBtXke_prDPRmDKZkqFTKC2Ys7raCWgbj3Q8r_AKaJNdWxWNELAENOocsZNF2QZv-LdHb6KAGubeKXYoU2tKfcqrIiR9ChUiaW3ZuS_4rYl7xJsFhn5_hh964qCOVfOyO49kLd7kFhAE72OKIH_Oz_Xs0NYNI3O-5L1rhvVgdaI_70ZejzeNer_e85dt1B1HW63inx3hnFkab0WdASypxGNjOlZuvnu2VmlJi-lRG37cEWA.HgW6X32JWV04WHcP.DASErZ7lvDzxWz9jFUJ3lJpRNbdEU5tRMBTe01GEdP9wveHVlZTS_cruE-1i-Nbro7v8vYxuP7Hdruk36cnCMAcaSc4Z_PqkPsR3PyLMd_VV0A1zlabo706huy0wliMnr0F8G17x043J2yyYMPgiVjbImYD4xC2OL7SbQH0zkIuctGuzQ6K2Xwpc89n2-zPP6fddX4D1Wpwi7CP_f-Cr5lP0jevZW8vv__H0tRqxSRaw57aPh4viLQVgMqWxXMA.WypEt7MPRG9ujeCLP5800w \ No newline at end of file diff --git a/.drone.yml b/.drone.yml index 0d57238..4c1e2a9 100644 --- a/.drone.yml +++ b/.drone.yml @@ -1,25 +1,21 @@ -build: +workspace: + base: /go + +script: test: - image: golang:1.5 + image: golang:1.6 environment: - CGO_ENABLED=0 - - GO15VENDOREXPERIMENT=1 commands: - go test -cover -coverprofile=coverage.out - go build -ldflags "-s -w -X main.build=$DRONE_BUILD_NUMBER" - -publish: - coverage: - when: - branch: master docker: - username: $$DOCKER_USER - password: $$DOCKER_PASS - email: $$DOCKER_EMAIL + storage_driver: overlay repo: plugins/docker tag: latest when: - branch: release/0.5 + branch: master + event: push plugin: name: Docker diff --git a/.drone.yml.sig b/.drone.yml.sig new file mode 100644 index 0000000..6d1dc44 --- /dev/null +++ b/.drone.yml.sig @@ -0,0 +1 @@ +eyJhbGciOiJIUzI1NiJ9.d29ya3NwYWNlOgogIGJhc2U6IC9nbwoKc2NyaXB0OgogIHRlc3Q6CiAgICBpbWFnZTogZ29sYW5nOjEuNgogICAgZW52aXJvbm1lbnQ6CiAgICAgIC0gQ0dPX0VOQUJMRUQ9MAogICAgY29tbWFuZHM6CiAgICAgIC0gZ28gdGVzdCAtY292ZXIgLWNvdmVycHJvZmlsZT1jb3ZlcmFnZS5vdXQKICAgICAgLSBnbyBidWlsZCAtbGRmbGFncyAiLXMgLXcgLVggbWFpbi5idWlsZD0kRFJPTkVfQlVJTERfTlVNQkVSIgogIGRvY2tlcjoKICAgIHN0b3JhZ2VfZHJpdmVyOiBvdmVybGF5CiAgICByZXBvOiBwbHVnaW5zL2RvY2tlcgogICAgdGFnOiBsYXRlc3QKICAgIHdoZW46CiAgICAgIGJyYW5jaDogbWFzdGVyCiAgICAgIGV2ZW50OiBwdXNoCgpwbHVnaW46CiAgbmFtZTogRG9ja2VyCiAgZGVzYzogQnVpbGQgYW5kIHB1Ymxpc2ggaW1hZ2VzIHRvIGEgRG9ja2VyIHJlZ2lzdHJ5CiAgdHlwZTogcHVibGlzaAogIGltYWdlOiBwbHVnaW5zL2RvY2tlcgogIGxhYmVsczoKICAgIC0gZG9ja2VyCiAgICAtIGltYWdlCiAgICAtIGNvbnRhaW5lcgo.Dq7c7ARGLJBH7O4swFpXpulXAjuyHMfiJzRq6Xpc5RM \ No newline at end of file diff --git a/README.md b/README.md index 1564e78..5370367 100644 --- a/README.md +++ b/README.md @@ -19,6 +19,8 @@ Build the docker image with the following commands: ``` export GO15VENDOREXPERIMENT=1 GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -a -tags netgo + +docker build --rm=true -t plugins/docker . ``` Please note incorrectly building the image for the correct x64 linux and with GCO disabled will result in an error when running the Docker image: @@ -37,7 +39,7 @@ docker run --rm \ -e PLUGIN_TAG=latest \ -e PLUGIN_REPO=octocat/hello-world \ -e DRONE_COMMIT_SHA=d8dbe4d94f15fe89232e0402c6e8a0ddf21af3ab \ - -v $(pwd)/$(pwd) \ + -v $(pwd):$(pwd) \ -w $(pwd) \ --privileged \ plugins/docker --dry-run diff --git a/plugin.go b/plugin.go index 52bda2a..1a9dd45 100644 --- a/plugin.go +++ b/plugin.go @@ -100,7 +100,7 @@ func (p Plugin) Exec() error { cmds = append(cmds, commandTag(p.Build, tag)) // docker tag if p.Dryrun == false { - cmds = append(cmds, commandPush(tag)) // docker push + cmds = append(cmds, commandPush(p.Build, tag)) // docker push } } @@ -168,8 +168,9 @@ func commandTag(build Build, tag string) *exec.Cmd { } // helper function to create the docker push command. -func commandPush(tag string) *exec.Cmd { - return exec.Command("/usr/bin/docker", "push", tag) +func commandPush(build Build, tag string) *exec.Cmd { + target := fmt.Sprintf("%s:%s", build.Repo, tag) + return exec.Command("/usr/bin/docker", "push", target) } // helper function to create the docker daemon command.