diff --git a/cmd/cloudinitexecute/cloudinitexecute.go b/cmd/cloudinitexecute/cloudinitexecute.go index ff2395f8..d172e8b6 100644 --- a/cmd/cloudinitexecute/cloudinitexecute.go +++ b/cmd/cloudinitexecute/cloudinitexecute.go @@ -12,8 +12,10 @@ import ( log "github.com/Sirupsen/logrus" "github.com/coreos/coreos-cloudinit/system" "github.com/docker/docker/pkg/mount" - "github.com/rancher/os/config" + rancherConfig "github.com/rancher/os/config" + "github.com/rancher/os/docker" "github.com/rancher/os/util" + "golang.org/x/net/context" ) const ( @@ -38,7 +40,7 @@ func Main() { log.Infof("Running cloud-init-execute: pre-console=%v, console=%v", preConsole, console) - cfg := config.LoadConfig() + cfg := rancherConfig.LoadConfig() if !console && !preConsole { console = true @@ -53,21 +55,13 @@ func Main() { } } -func ApplyConsole(cfg *config.CloudConfig) { +func ApplyConsole(cfg *rancherConfig.CloudConfig) { if len(cfg.SSHAuthorizedKeys) > 0 { authorizeSSHKeys("rancher", cfg.SSHAuthorizedKeys, sshKeyName) authorizeSSHKeys("docker", cfg.SSHAuthorizedKeys, sshKeyName) } - for _, file := range cfg.WriteFiles { - f := system.File{File: file} - fullPath, err := system.WriteFile(&f, "/") - if err != nil { - log.WithFields(log.Fields{"err": err, "path": fullPath}).Error("Error writing file") - continue - } - log.Printf("Wrote file %s to filesystem", fullPath) - } + WriteFiles(cfg, "console") for _, configMount := range cfg.Mounts { if len(configMount) != 4 { @@ -88,7 +82,29 @@ func ApplyConsole(cfg *config.CloudConfig) { } } -func applyPreConsole(cfg *config.CloudConfig) { +func WriteFiles(cfg *rancherConfig.CloudConfig, container string) { + for _, file := range cfg.WriteFiles { + fileContainer := file.Container + if fileContainer == "" { + fileContainer = "console" + } + if fileContainer != container { + continue + } + + f := system.File{ + File: file.File, + } + fullPath, err := system.WriteFile(&f, "/") + if err != nil { + log.WithFields(log.Fields{"err": err, "path": fullPath}).Error("Error writing file") + continue + } + log.Printf("Wrote file %s to filesystem", fullPath) + } +} + +func applyPreConsole(cfg *rancherConfig.CloudConfig) { if _, err := os.Stat(resizeStamp); os.IsNotExist(err) && cfg.Rancher.ResizeDevice != "" { if err := resizeDevice(cfg); err == nil { os.Create(resizeStamp) @@ -105,9 +121,20 @@ func applyPreConsole(cfg *config.CloudConfig) { log.Errorf("Failed to set sysctl key %s: %s", k, err) } } + + client, err := docker.NewSystemClient() + if err != nil { + log.Error(err) + } + + for _, restart := range cfg.Rancher.RestartServices { + if err = client.ContainerRestart(context.Background(), restart, 10); err != nil { + log.Error(err) + } + } } -func resizeDevice(cfg *config.CloudConfig) error { +func resizeDevice(cfg *rancherConfig.CloudConfig) error { cmd := exec.Command("growpart", cfg.Rancher.ResizeDevice, "1") err := cmd.Run() if err != nil { diff --git a/cmd/cloudinitsave/cloudinitsave.go b/cmd/cloudinitsave/cloudinitsave.go index dd78d4aa..f1b0503a 100644 --- a/cmd/cloudinitsave/cloudinitsave.go +++ b/cmd/cloudinitsave/cloudinitsave.go @@ -70,9 +70,6 @@ func Main() { func saveFiles(cloudConfigBytes, scriptBytes []byte, metadata datasource.Metadata) error { os.MkdirAll(rancherConfig.CloudConfigDir, os.ModeDir|0600) - os.Remove(rancherConfig.CloudConfigScriptFile) - os.Remove(rancherConfig.CloudConfigBootFile) - os.Remove(rancherConfig.MetaDataFile) if len(scriptBytes) > 0 { log.Infof("Writing to %s", rancherConfig.CloudConfigScriptFile) @@ -82,10 +79,12 @@ func saveFiles(cloudConfigBytes, scriptBytes []byte, metadata datasource.Metadat } } - if err := util.WriteFileAtomic(rancherConfig.CloudConfigBootFile, cloudConfigBytes, 400); err != nil { - return err + if len(cloudConfigBytes) > 0 { + if err := util.WriteFileAtomic(rancherConfig.CloudConfigBootFile, cloudConfigBytes, 400); err != nil { + return err + } + log.Infof("Written to %s:\n%s", rancherConfig.CloudConfigBootFile, string(cloudConfigBytes)) } - log.Infof("Written to %s:\n%s", rancherConfig.CloudConfigBootFile, string(cloudConfigBytes)) metaDataBytes, err := yaml.Marshal(metadata) if err != nil { diff --git a/cmd/control/cli.go b/cmd/control/cli.go index f5d7b833..62157730 100644 --- a/cmd/control/cli.go +++ b/cmd/control/cli.go @@ -45,6 +45,12 @@ func Main() { SkipFlagParsing: true, Action: devAction, }, + { + Name: "entrypoint", + HideHelp: true, + SkipFlagParsing: true, + Action: entrypointAction, + }, { Name: "env", ShortName: "e", diff --git a/cmd/control/entrypoint.go b/cmd/control/entrypoint.go new file mode 100644 index 00000000..7af95dfc --- /dev/null +++ b/cmd/control/entrypoint.go @@ -0,0 +1,74 @@ +package control + +import ( + "os" + "os/exec" + "syscall" + + log "github.com/Sirupsen/logrus" + "github.com/codegangsta/cli" + "golang.org/x/net/context" + + "github.com/docker/docker/pkg/mount" + "github.com/rancher/os/cmd/cloudinitexecute" + "github.com/rancher/os/config" + "github.com/rancher/os/docker" + "github.com/rancher/os/util" +) + +const ( + ca = "/etc/ssl/certs/ca-certificates.crt" + caBase = "/etc/ssl/certs/ca-certificates.crt.rancher" +) + +func entrypointAction(c *cli.Context) error { + if err := mount.Mount("/host/dev", "/dev", "", "rbind"); err != nil { + log.Error(err) + } + + if err := util.FileCopy(caBase, ca); err != nil && !os.IsNotExist(err) { + log.Error(err) + } + + cfg := config.LoadConfig() + + shouldWriteFiles := false + for _, file := range cfg.WriteFiles { + if file.Container != "" { + shouldWriteFiles = true + } + } + + if shouldWriteFiles { + writeFiles(cfg) + } + + if len(os.Args) < 3 { + return nil + } + + binary, err := exec.LookPath(os.Args[2]) + if err != nil { + return err + } + + return syscall.Exec(binary, os.Args[2:], os.Environ()) +} + +func writeFiles(cfg *config.CloudConfig) error { + id, err := util.GetCurrentContainerId() + if err != nil { + return err + } + client, err := docker.NewSystemClient() + if err != nil { + return err + } + info, err := client.ContainerInspect(context.Background(), id) + if err != nil { + return err + } + + cloudinitexecute.WriteFiles(cfg, info.Name[1:]) + return nil +} diff --git a/config/types.go b/config/types.go index 04fb7e6f..5c9b8113 100644 --- a/config/types.go +++ b/config/types.go @@ -85,12 +85,17 @@ type Repositories map[string]Repository type CloudConfig struct { SSHAuthorizedKeys []string `yaml:"ssh_authorized_keys"` - WriteFiles []config.File `yaml:"write_files"` + WriteFiles []File `yaml:"write_files"` Hostname string `yaml:"hostname"` Mounts [][]string `yaml:"mounts,omitempty"` Rancher RancherConfig `yaml:"rancher,omitempty"` } +type File struct { + config.File + Container string `yaml:"container,omitempty"` +} + type RancherConfig struct { Console string `yaml:"console,omitempty"` Environment map[string]string `yaml:"environment,omitempty"` @@ -119,6 +124,7 @@ type RancherConfig struct { Defaults Defaults `yaml:"defaults,omitempty"` ResizeDevice string `yaml:"resize_device,omitempty"` Sysctl map[string]string `yaml:"sysctl,omitempty"` + RestartServices []string `yaml:"restart_services,omitempty"` } type UpgradeConfig struct { diff --git a/images/01-base/Dockerfile b/images/01-base/Dockerfile index ede8632e..b328c2d3 100644 --- a/images/01-base/Dockerfile +++ b/images/01-base/Dockerfile @@ -32,8 +32,7 @@ RUN rm /sbin/poweroff /sbin/reboot /sbin/halt && \ adduser docker sudo && \ echo '%sudo ALL=(ALL) ALL' >> /etc/sudoers COPY inputrc /etc/inputrc -COPY entry.sh /usr/sbin/entry.sh COPY growpart /usr/bin/growpart RUN sed -i -e 's/duid/clientid/g' /etc/dhcpcd.conf -ENTRYPOINT ["/usr/sbin/entry.sh"] +ENTRYPOINT ["/usr/bin/ros", "entrypoint"] diff --git a/os-config.tpl.yml b/os-config.tpl.yml index 64fab571..80298129 100644 --- a/os-config.tpl.yml +++ b/os-config.tpl.yml @@ -250,6 +250,9 @@ rancher: uts: host privileged: true restart: always + volumes_from: + - command-volumes + - system-volumes preload-system-images: image: {{.OS_REPO}}/os-preload:{{.VERSION}}{{.SUFFIX}} labels: @@ -289,6 +292,7 @@ rancher: privileged: true restart: always volumes_from: + - command-volumes - system-volumes system-volumes: image: {{.OS_REPO}}/os-base:{{.VERSION}}{{.SUFFIX}} @@ -325,9 +329,8 @@ rancher: uts: host privileged: true volumes_from: + - command-volumes - system-volumes - volumes: - - /usr/bin/ros:/usr/bin/ros:ro udev: image: {{.OS_REPO}}/os-udev:{{.VERSION}}{{.SUFFIX}} environment: @@ -340,6 +343,7 @@ rancher: privileged: true restart: always volumes_from: + - command-volumes - system-volumes user-volumes: image: {{.OS_REPO}}/os-base:{{.VERSION}}{{.SUFFIX}} diff --git a/scripts/package-rootfs b/scripts/package-rootfs index 187975b2..09534fa5 100755 --- a/scripts/package-rootfs +++ b/scripts/package-rootfs @@ -15,19 +15,19 @@ INITRD=${ARTIFACTS}/initrd mkdir -p ${ARTIFACTS} ${PREPOP_DIR} -if [ "$(docker info | grep 'Storage Driver: ' | sed 's/Storage Driver: //')" != "overlay" ]; then - echo Overlay storage driver is require to prepackage exploded images - echo packaging images.tar instead - tar czf ${ARTIFACTS}/rootfs.tar.gz --exclude lib/modules --exclude lib/firmware -C ${INITRD_DIR} . - exit 0 -fi +#if [ "$(docker info | grep 'Storage Driver: ' | sed 's/Storage Driver: //')" != "overlay" ]; then +echo Overlay storage driver is require to prepackage exploded images +echo packaging images.tar instead +tar czf ${ARTIFACTS}/rootfs.tar.gz --exclude lib/modules --exclude lib/firmware -C ${INITRD_DIR} . +exit 0 +#fi DFS=$(docker run -d --privileged -v /lib/modules/$(uname -r):/lib/modules/$(uname -r) ${DFS_IMAGE}${SUFFIX} ${DFS_ARGS}) trap "docker rm -fv ${DFS_ARCH} ${DFS}" EXIT docker exec -i ${DFS} docker load < ${INITRD_DIR}/usr/share/ros/images.tar docker stop ${DFS} -docker run --rm --volumes-from=${DFS} rancher/os-base tar -c -C /var/lib/docker ./image | tar -x -C ${PREPOP_DIR} -docker run --rm --volumes-from=${DFS} rancher/os-base tar -c -C /var/lib/docker ./overlay | tar -x -C ${PREPOP_DIR} +docker run --rm --volumes-from=${DFS} busybox tar -c -C /var/lib/docker ./image | tar -x -C ${PREPOP_DIR} +docker run --rm --volumes-from=${DFS} busybox tar -c -C /var/lib/docker ./overlay | tar -x -C ${PREPOP_DIR} tar -cf ${ARTIFACTS}/rootfs.tar --exclude usr/share/ros/images.tar --exclude lib/modules --exclude lib/firmware -C ${INITRD_DIR} . tar -rf ${ARTIFACTS}/rootfs.tar -C ${IMAGE_CACHE} . diff --git a/tests/assets/test_23/cloud-config.yml b/tests/assets/test_23/cloud-config.yml new file mode 100644 index 00000000..f121e2d0 --- /dev/null +++ b/tests/assets/test_23/cloud-config.yml @@ -0,0 +1,29 @@ +#cloud-config +write_files: +- path: "/test" + permissions: "0644" + owner: "root" + content: | + console content +- path: "/test2" + container: console + permissions: "0644" + owner: "root" + content: | + console content +- path: "/test" + container: ntp + permissions: "0644" + owner: "root" + content: | + ntp content +- path: "/test" + container: syslog + permissions: "0644" + owner: "root" + content: | + syslog content +rancher: + restart_services: [syslog] +ssh_authorized_keys: + - ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC85w9stZyiLQp/DkVO6fqwiShYcj1ClKdtCqgHtf+PLpJkFReSFu8y21y+ev09gsSMRRrjF7yt0pUHV6zncQhVeqsZtgc5WbELY2DOYUGmRn/CCvPbXovoBrQjSorqlBmpuPwsStYLr92Xn+VVsMNSUIegHY22DphGbDKG85vrKB8HxUxGIDxFBds/uE8FhSy+xsoyT/jUZDK6pgq2HnGl6D81ViIlKecpOpWlW3B+fea99ADNyZNVvDzbHE5pcI3VRw8u59WmpWOUgT6qacNVACl8GqpBvQk8sw7O/X9DSZHCKafeD9G5k+GYbAUz92fKWrx/lOXfUXPS3+c8dRIF diff --git a/tests/kernel_headers_test.go b/tests/kernel_headers_test.go index 3297b85d..cbba30f3 100644 --- a/tests/kernel_headers_test.go +++ b/tests/kernel_headers_test.go @@ -7,6 +7,6 @@ func (s *QemuSuite) TestKernelHeaders(c *C) { c.Assert(err, IsNil) s.CheckCall(c, ` -sleep 10 +sleep 15 docker inspect kernel-headers`) } diff --git a/tests/write_files_test.go b/tests/write_files_test.go new file mode 100644 index 00000000..206f3341 --- /dev/null +++ b/tests/write_files_test.go @@ -0,0 +1,13 @@ +package integration + +import . "gopkg.in/check.v1" + +func (s *QemuSuite) TestWriteFiles(c *C) { + err := s.RunQemu("--cloud-config", "./tests/assets/test_23/cloud-config.yml") + c.Assert(err, IsNil) + + s.CheckCall(c, "sudo cat /test | grep 'console content'") + s.CheckCall(c, "sudo cat /test2 | grep 'console content'") + s.CheckCall(c, "sudo system-docker exec ntp cat /test | grep 'ntp content'") + s.CheckCall(c, "sudo system-docker exec syslog cat /test | grep 'syslog content'") +} diff --git a/trash.conf b/trash.conf index d96db347..2f4ca59b 100644 --- a/trash.conf +++ b/trash.conf @@ -4,7 +4,7 @@ github.com/Sirupsen/logrus v0.9.0 github.com/boltdb/bolt v1.2.0 github.com/cloudfoundry-incubator/candiedyaml 01cbc92901719f599b11f3a7e3b1768d7002b0bb https://github.com/rancher/candiedyaml github.com/cloudfoundry/gosigar 3ed7c74352dae6dc00bdc8c74045375352e3ec05 -github.com/codegangsta/cli 95199f812193f6f1e8bbe0a916d9f3ed50729909 https://github.com/ibuildthecloud/cli-1.git +github.com/codegangsta/cli d2b9ba9c38eb353ba3c6df3f57072348e19cc5c7 https://github.com/rancher/cli-1 github.com/containernetworking/cni a8e4fa0dffdac6a236f85be91502603ec06957f9 https://github.com/rancher/cni.git github.com/coreos/coreos-cloudinit v1.11.0-3-gb1c1753 https://github.com/rancher/coreos-cloudinit.git github.com/coreos/go-iptables fbb73372b87f6e89951c2b6b31470c2c9d5cfae3 diff --git a/vendor/github.com/codegangsta/cli/.gitignore b/vendor/github.com/codegangsta/cli/.gitignore index 78237789..faf70c4c 100644 --- a/vendor/github.com/codegangsta/cli/.gitignore +++ b/vendor/github.com/codegangsta/cli/.gitignore @@ -1 +1,2 @@ *.coverprofile +node_modules/ diff --git a/vendor/github.com/codegangsta/cli/.travis.yml b/vendor/github.com/codegangsta/cli/.travis.yml index 657e96a9..273d017b 100644 --- a/vendor/github.com/codegangsta/cli/.travis.yml +++ b/vendor/github.com/codegangsta/cli/.travis.yml @@ -2,6 +2,10 @@ language: go sudo: false +cache: + directories: + - node_modules + go: - 1.2.2 - 1.3.3 @@ -25,8 +29,12 @@ matrix: before_script: - go get github.com/urfave/gfmxr/... +- if [ ! -f node_modules/.bin/markdown-toc ] ; then + npm install markdown-toc ; + fi script: - ./runtests vet - ./runtests test - ./runtests gfmxr +- ./runtests toc diff --git a/vendor/github.com/codegangsta/cli/CHANGELOG.md b/vendor/github.com/codegangsta/cli/CHANGELOG.md index 0d51d2a5..d1904fe0 100644 --- a/vendor/github.com/codegangsta/cli/CHANGELOG.md +++ b/vendor/github.com/codegangsta/cli/CHANGELOG.md @@ -3,13 +3,24 @@ **ATTN**: This project uses [semantic versioning](http://semver.org/). ## [Unreleased] + +## [1.18.0] - 2016-06-27 ### Added - `./runtests` test runner with coverage tracking by default - testing on OS X - testing on Windows +- `UintFlag`, `Uint64Flag`, and `Int64Flag` types and supporting code + +### Changed +- Use spaces for alignment in help/usage output instead of tabs, making the + output alignment consistent regardless of tab width ### Fixed - Printing of command aliases in help text +- Printing of visible flags for both struct and struct pointer flags +- Display the `help` subcommand when using `CommandCategories` +- No longer swallows `panic`s that occur within the `Action`s themselves when + detecting the signature of the `Action` field ## [1.17.0] - 2016-05-09 ### Added @@ -290,7 +301,8 @@ signature of `func(*cli.Context) error`, as defined by `cli.ActionFunc`. ### Added - Initial implementation. -[Unreleased]: https://github.com/urfave/cli/compare/v1.17.0...HEAD +[Unreleased]: https://github.com/urfave/cli/compare/v1.18.0...HEAD +[1.18.0]: https://github.com/urfave/cli/compare/v1.17.0...v1.18.0 [1.17.0]: https://github.com/urfave/cli/compare/v1.16.0...v1.17.0 [1.16.0]: https://github.com/urfave/cli/compare/v1.15.0...v1.16.0 [1.15.0]: https://github.com/urfave/cli/compare/v1.14.0...v1.15.0 diff --git a/vendor/github.com/codegangsta/cli/LICENSE b/vendor/github.com/codegangsta/cli/LICENSE index 5515ccfb..42a597e2 100644 --- a/vendor/github.com/codegangsta/cli/LICENSE +++ b/vendor/github.com/codegangsta/cli/LICENSE @@ -1,21 +1,21 @@ -Copyright (C) 2013 Jeremy Saenz -All Rights Reserved. +MIT License -MIT LICENSE +Copyright (c) 2016 Jeremy Saenz & Contributors -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: +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. +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 index 80337c56..ebb1d741 100644 --- a/vendor/github.com/codegangsta/cli/README.md +++ b/vendor/github.com/codegangsta/cli/README.md @@ -1,25 +1,62 @@ +cli +=== + [![Build Status](https://travis-ci.org/urfave/cli.svg?branch=master)](https://travis-ci.org/urfave/cli) -[![Windows Build Status](https://ci.appveyor.com/api/projects/status/rtgk5xufi932pb2v?svg=true)](https://ci.appveyor.com/project/meatballhat/cli) +[![Windows Build Status](https://ci.appveyor.com/api/projects/status/rtgk5xufi932pb2v?svg=true)](https://ci.appveyor.com/project/urfave/cli) [![GoDoc](https://godoc.org/github.com/urfave/cli?status.svg)](https://godoc.org/github.com/urfave/cli) [![codebeat](https://codebeat.co/badges/0a8f30aa-f975-404b-b878-5fab3ae1cc5f)](https://codebeat.co/projects/github-com-urfave-cli) [![Go Report Card](https://goreportcard.com/badge/urfave/cli)](https://goreportcard.com/report/urfave/cli) [![top level coverage](https://gocover.io/_badge/github.com/urfave/cli?0 "top level coverage")](http://gocover.io/github.com/urfave/cli) / [![altsrc coverage](https://gocover.io/_badge/github.com/urfave/cli/altsrc?0 "altsrc coverage")](http://gocover.io/github.com/urfave/cli/altsrc) - -# cli - **Notice:** This is the library formerly known as `github.com/codegangsta/cli` -- Github will automatically redirect requests to this repository, but we recommend updating your references for clarity. -cli is a 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. +cli is a 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](#overview) +- [Installation](#installation) + * [Supported platforms](#supported-platforms) + * [Using the `v2` branch](#using-the-v2-branch) + * [Pinning to the `v1` branch](#pinning-to-the-v1-branch) +- [Getting Started](#getting-started) +- [Examples](#examples) + * [Arguments](#arguments) + * [Flags](#flags) + + [Placeholder Values](#placeholder-values) + + [Alternate Names](#alternate-names) + + [Values from the Environment](#values-from-the-environment) + + [Values from alternate input sources (YAML and others)](#values-from-alternate-input-sources-yaml-and-others) + * [Subcommands](#subcommands) + * [Subcommands categories](#subcommands-categories) + * [Exit code](#exit-code) + * [Bash Completion](#bash-completion) + + [Enabling](#enabling) + + [Distribution](#distribution) + + [Customization](#customization) + * [Generated Help Text](#generated-help-text) + + [Customization](#customization-1) + * [Version Flag](#version-flag) + + [Customization](#customization-2) + + [Full API Example](#full-api-example) +- [Contribution Guidelines](#contribution-guidelines) + + ## 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. +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 comes into play.** cli makes command line programming fun, organized, and expressive! +**This is where cli comes into play.** cli makes command line programming fun, +organized, and expressive! ## Installation @@ -33,7 +70,8 @@ To install cli, simply run: $ go get github.com/urfave/cli ``` -Make sure your `PATH` includes to the `$GOPATH/bin` directory so your commands can be easily used: +Make sure your `PATH` includes to the `$GOPATH/bin` directory so your commands +can be easily used: ``` export PATH=$PATH:$GOPATH/bin ``` @@ -46,6 +84,8 @@ released version of Go on OS X and Windows. For full details, see ### Using the `v2` branch +**Warning**: The `v2` branch is currently unreleased and considered unstable. + There is currently a long-lived branch named `v2` that is intended to land as the new `master` branch once development there has settled down. The current `master` branch (mirrored as `v1`) is being manually merged into `v2` on @@ -86,13 +126,19 @@ import ( ## Getting Started -One of the philosophies behind cli is that an API should be playful and full of discovery. So a cli app can be as little as one line of code in `main()`. +One of the philosophies behind cli is that an API should be playful and full of +discovery. So a cli app can be as little as one line of code in `main()`. + ``` go package main import ( "os" + "github.com/urfave/cli" ) @@ -101,7 +147,8 @@ func main() { } ``` -This app will run and show help text, but is not very useful. Let's give an action to execute and some help documentation: +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 -... -app.Action = func(c *cli.Context) error { - fmt.Println("Hello", c.Args()[0]) - return nil +package main + +import ( + "fmt" + "os" + + "github.com/urfave/cli" +) + +func main() { + app := cli.NewApp() + + app.Action = func(c *cli.Context) error { + fmt.Printf("Hello %q", c.Args().Get(0)) + return nil + } + + app.Run(os.Args) } -... ``` ### 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) error { - name := "someone" - if c.NArg() > 0 { - name = c.Args()[0] +package main + +import ( + "fmt" + "os" + + "github.com/urfave/cli" +) + +func main() { + app := cli.NewApp() + + app.Flags = []cli.Flag { + cli.StringFlag{ + Name: "lang", + Value: "english", + Usage: "language for the greeting", + }, } - if c.String("lang") == "spanish" { - fmt.Println("Hola", name) - } else { - fmt.Println("Hello", name) + + app.Action = func(c *cli.Context) error { + name := "Nefertiti" + if c.NArg() > 0 { + name = c.Args().Get(0) + } + if c.String("lang") == "spanish" { + fmt.Println("Hola", name) + } else { + fmt.Println("Hello", name) + } + return nil } - return nil + + app.Run(os.Args) } -... ``` -You can also set a destination variable for a flag, to which the content will be scanned. +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) error { - name := "someone" - if c.NArg() > 0 { - name = c.Args()[0] +package main + +import ( + "os" + "fmt" + + "github.com/urfave/cli" +) + +func main() { + var language string + + app := cli.NewApp() + + app.Flags = []cli.Flag { + cli.StringFlag{ + Name: "lang", + Value: "english", + Usage: "language for the greeting", + Destination: &language, + }, } - if language == "spanish" { - fmt.Println("Hola", name) - } else { - fmt.Println("Hello", name) + + app.Action = func(c *cli.Context) error { + name := "someone" + if c.NArg() > 0 { + name = c.Args()[0] + } + if language == "spanish" { + fmt.Println("Hola", name) + } else { + fmt.Println("Hello", name) + } + return nil } - return nil + + app.Run(os.Args) } -... ``` See full list of flags at http://godoc.org/github.com/urfave/cli #### Placeholder Values -Sometimes it's useful to specify a flag's value within the usage string itself. Such placeholders are -indicated with back quotes. +Sometimes it's useful to specify a flag's value within the usage string itself. +Such placeholders are indicated with back quotes. For example this: + ```go -cli.StringFlag{ - Name: "config, c", - Usage: "Load configuration from `FILE`", +package main + +import ( + "os" + + "github.com/urfave/cli" +) + +func main() { + app := cli.NewApp() + + app.Flags = []cli.Flag{ + cli.StringFlag{ + Name: "config, c", + Usage: "Load configuration from `FILE`", + }, + } + + app.Run(os.Args) } ``` @@ -287,145 +410,244 @@ Will result in help output like: --config FILE, -c FILE Load configuration from FILE ``` -Note that only the first placeholder is used. Subsequent back-quoted words will be left as-is. +Note that only the first placeholder is used. Subsequent back-quoted words will +be left as-is. #### Alternate Names -You can set alternate (or short) names for flags by providing a comma-delimited list for the `Name`. e.g. +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", - }, +package main + +import ( + "os" + + "github.com/urfave/cli" +) + +func main() { + app := cli.NewApp() + + app.Flags = []cli.Flag { + cli.StringFlag{ + Name: "lang, l", + Value: "english", + Usage: "language for the greeting", + }, + } + + app.Run(os.Args) } ``` -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. +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", - }, +package main + +import ( + "os" + + "github.com/urfave/cli" +) + +func main() { + app := cli.NewApp() + + app.Flags = []cli.Flag { + cli.StringFlag{ + Name: "lang, l", + Value: "english", + Usage: "language for the greeting", + EnvVar: "APP_LANG", + }, + } + + app.Run(os.Args) } ``` -The `EnvVar` may also be given as a comma-delimited "cascade", where the first environment variable that resolves is used as the default. +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", - }, +package main + +import ( + "os" + + "github.com/urfave/cli" +) + +func main() { + app := cli.NewApp() + + app.Flags = []cli.Flag { + cli.StringFlag{ + Name: "lang, l", + Value: "english", + Usage: "language for the greeting", + EnvVar: "LEGACY_COMPAT_LANG,APP_LANG,LANG", + }, + } + + app.Run(os.Args) } ``` #### 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. +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: +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. +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. +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. +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) error { - // Action to run - return nil - }, - Flags: []cli.Flag{ - NewIntFlag(cli.IntFlag{Name: "test"}), - cli.StringFlag{Name: "load"}}, +package notmain + +import ( + "fmt" + "os" + + "github.com/urfave/cli" + "github.com/urfave/cli/altsrc" +) + +func main() { + app := cli.NewApp() + + flags := []cli.Flag{ + altsrc.NewIntFlag(cli.IntFlag{Name: "test"}), + cli.StringFlag{Name: "load"}, } - command.Before = InitInputSourceWithContext(command.Flags, NewYamlSourceFromFlagFunc("load")) - err := command.Run(c) + + app.Action = func(c *cli.Context) error { + fmt.Println("yaml ist rad") + return nil + } + + app.Before = altsrc.InitInputSourceWithContext(flags, altsrc.NewYamlSourceFromFlagFunc("load")) + app.Flags = flags + + app.Run(os.Args) +} ``` ### 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) error { - fmt.Println("added task: ", c.Args().First()) - return nil - }, - }, - { - Name: "complete", - Aliases: []string{"c"}, - Usage: "complete a task on the list", - Action: func(c *cli.Context) error { - fmt.Println("completed task: ", c.Args().First()) - return nil - }, - }, - { - 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) error { - fmt.Println("new task template: ", c.Args().First()) - return nil - }, +package main + +import ( + "fmt" + "os" + + "github.com/urfave/cli" +) + +func main() { + app := cli.NewApp() + + app.Commands = []cli.Command{ + { + Name: "add", + Aliases: []string{"a"}, + Usage: "add a task to the list", + Action: func(c *cli.Context) error { + fmt.Println("added task: ", c.Args().First()) + return nil }, - { - Name: "remove", - Usage: "remove an existing template", - Action: func(c *cli.Context) error { - fmt.Println("removed task template: ", c.Args().First()) - return nil + }, + { + Name: "complete", + Aliases: []string{"c"}, + Usage: "complete a task on the list", + Action: func(c *cli.Context) error { + fmt.Println("completed task: ", c.Args().First()) + return nil + }, + }, + { + Name: "template", + Aliases: []string{"t"}, + Usage: "options for task templates", + Subcommands: []cli.Command{ + { + Name: "add", + Usage: "add a new template", + Action: func(c *cli.Context) error { + fmt.Println("new task template: ", c.Args().First()) + return nil + }, + }, + { + Name: "remove", + Usage: "remove an existing template", + Action: func(c *cli.Context) error { + fmt.Println("removed task template: ", c.Args().First()) + return nil + }, }, }, }, - }, + } + + app.Run(os.Args) } -... ``` ### Subcommands categories @@ -437,7 +659,17 @@ output. E.g. ```go -... +package main + +import ( + "os" + + "github.com/urfave/cli" +) + +func main() { + app := cli.NewApp() + app.Commands = []cli.Command{ { Name: "noop", @@ -451,20 +683,20 @@ E.g. Category: "template", }, } -... + + app.Run(os.Args) +} ``` Will include: ``` -... COMMANDS: noop Template actions: add remove -... ``` ### Exit code @@ -509,42 +741,58 @@ 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) error { - fmt.Println("completed task: ", c.Args().First()) - return nil - }, - 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) - } + +``` go +package main + +import ( + "fmt" + "os" + + "github.com/urfave/cli" +) + +func main() { + 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) error { + fmt.Println("completed task: ", c.Args().First()) + return nil + }, + 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) + } + }, }, } + + app.Run(os.Args) } -... ``` -#### To Enable +#### Enabling 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 +#### Distribution 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 @@ -560,7 +808,48 @@ 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). -### Generated Help Text Customization +#### Customization + +The default bash completion flag (`--generate-bash-completion`) is defined as +`cli.BashCompletionFlag`, and may be redefined if desired, e.g.: + + +``` go +package main + +import ( + "os" + + "github.com/urfave/cli" +) + +func main() { + cli.BashCompletionFlag = cli.BoolFlag{ + Name: "compgen", + Hidden: true, + } + + app := cli.NewApp() + app.EnableBashCompletion = true + app.Commands = []cli.Command{ + { + Name: "wat", + }, + } + app.Run(os.Args) +} +``` + +### Generated Help Text + +The default help flag (`-h/--help`) is defined as `cli.HelpFlag` and is checked +by the cli internals in order to print generated help text for the app, command, +or subcommand, and break execution. + +#### Customization All of the help text generation may be customized, and at multiple levels. The templates are exposed as variables `AppHelpTemplate`, `CommandHelpTemplate`, and @@ -626,6 +915,383 @@ VERSION: } ``` +The default flag may be customized to something other than `-h/--help` by +setting `cli.HelpFlag`, e.g.: + + +``` go +package main + +import ( + "os" + + "github.com/urfave/cli" +) + +func main() { + cli.HelpFlag = cli.BoolFlag{ + Name: "halp, haaaaalp", + Usage: "HALP", + EnvVar: "SHOW_HALP,HALPPLZ", + } + + cli.NewApp().Run(os.Args) +} +``` + +### Version Flag + +The default version flag (`-v/--version`) is defined as `cli.VersionFlag`, which +is checked by the cli internals in order to print the `App.Version` via +`cli.VersionPrinter` and break execution. + +#### Customization + +The default flag may be cusomized to something other than `-v/--version` by +setting `cli.VersionFlag`, e.g.: + + +``` go +package main + +import ( + "os" + + "github.com/urfave/cli" +) + +func main() { + cli.VersionFlag = cli.BoolFlag{ + Name: "print-version, V", + Usage: "print only the version", + } + + app := cli.NewApp() + app.Name = "partay" + app.Version = "v19.99.0" + app.Run(os.Args) +} +``` + +Alternatively, the version printer at `cli.VersionPrinter` may be overridden, e.g.: + + +``` go +package main + +import ( + "fmt" + "os" + + "github.com/urfave/cli" +) + +var ( + Revision = "fafafaf" +) + +func main() { + cli.VersionPrinter = func(c *cli.Context) { + fmt.Printf("version=%s revision=%s\n", c.App.Version, Revision) + } + + app := cli.NewApp() + app.Name = "partay" + app.Version = "v19.99.0" + app.Run(os.Args) +} +``` + +#### Full API Example + +**Notice**: This is a contrived (functioning) example meant strictly for API +demonstration purposes. Use of one's imagination is encouraged. + + +``` go +package main + +import ( + "errors" + "flag" + "fmt" + "io" + "io/ioutil" + "os" + "time" + + "github.com/urfave/cli" +) + +func init() { + cli.AppHelpTemplate += "\nCUSTOMIZED: you bet ur muffins\n" + cli.CommandHelpTemplate += "\nYMMV\n" + cli.SubcommandHelpTemplate += "\nor something\n" + + cli.HelpFlag = cli.BoolFlag{Name: "halp"} + cli.BashCompletionFlag = cli.BoolFlag{Name: "compgen", Hidden: true} + cli.VersionFlag = cli.BoolFlag{Name: "print-version, V"} + + cli.HelpPrinter = func(w io.Writer, templ string, data interface{}) { + fmt.Fprintf(w, "best of luck to you\n") + } + cli.VersionPrinter = func(c *cli.Context) { + fmt.Fprintf(c.App.Writer, "version=%s\n", c.App.Version) + } + cli.OsExiter = func(c int) { + fmt.Fprintf(cli.ErrWriter, "refusing to exit %d\n", c) + } + cli.ErrWriter = ioutil.Discard + cli.FlagStringer = func(fl cli.Flag) string { + return fmt.Sprintf("\t\t%s", fl.GetName()) + } +} + +type hexWriter struct{} + +func (w *hexWriter) Write(p []byte) (int, error) { + for _, b := range p { + fmt.Printf("%x", b) + } + fmt.Printf("\n") + + return len(p), nil +} + +type genericType struct{ + s string +} + +func (g *genericType) Set(value string) error { + g.s = value + return nil +} + +func (g *genericType) String() string { + return g.s +} + +func main() { + app := cli.NewApp() + app.Name = "kənˈtrīv" + app.Version = "v19.99.0" + app.Compiled = time.Now() + app.Authors = []cli.Author{ + cli.Author{ + Name: "Example Human", + Email: "human@example.com", + }, + } + app.Copyright = "(c) 1999 Serious Enterprise" + app.HelpName = "contrive" + app.Usage = "demonstrate available API" + app.UsageText = "contrive - demonstrating the available API" + app.ArgsUsage = "[args and such]" + app.Commands = []cli.Command{ + cli.Command{ + Name: "doo", + Aliases: []string{"do"}, + Category: "motion", + Usage: "do the doo", + UsageText: "doo - does the dooing", + Description: "no really, there is a lot of dooing to be done", + ArgsUsage: "[arrgh]", + Flags: []cli.Flag{ + cli.BoolFlag{Name: "forever, forevvarr"}, + }, + Subcommands: cli.Commands{ + cli.Command{ + Name: "wop", + Action: wopAction, + }, + }, + SkipFlagParsing: false, + HideHelp: false, + Hidden: false, + HelpName: "doo!", + BashComplete: func(c *cli.Context) { + fmt.Fprintf(c.App.Writer, "--better\n") + }, + Before: func(c *cli.Context) error { + fmt.Fprintf(c.App.Writer, "brace for impact\n") + return nil + }, + After: func(c *cli.Context) error { + fmt.Fprintf(c.App.Writer, "did we lose anyone?\n") + return nil + }, + Action: func(c *cli.Context) error { + c.Command.FullName() + c.Command.HasName("wop") + c.Command.Names() + c.Command.VisibleFlags() + fmt.Fprintf(c.App.Writer, "dodododododoodododddooooododododooo\n") + if c.Bool("forever") { + c.Command.Run(c) + } + return nil + }, + OnUsageError: func(c *cli.Context, err error, isSubcommand bool) error { + fmt.Fprintf(c.App.Writer, "for shame\n") + return err + }, + }, + } + app.Flags = []cli.Flag{ + cli.BoolFlag{Name: "fancy"}, + cli.BoolTFlag{Name: "fancier"}, + cli.DurationFlag{Name: "howlong, H", Value: time.Second * 3}, + cli.Float64Flag{Name: "howmuch"}, + cli.GenericFlag{Name: "wat", Value: &genericType{}}, + cli.Int64Flag{Name: "longdistance"}, + cli.Int64SliceFlag{Name: "intervals"}, + cli.IntFlag{Name: "distance"}, + cli.IntSliceFlag{Name: "times"}, + cli.StringFlag{Name: "dance-move, d"}, + cli.StringSliceFlag{Name: "names, N"}, + cli.UintFlag{Name: "age"}, + cli.Uint64Flag{Name: "bigage"}, + } + app.EnableBashCompletion = true + app.HideHelp = false + app.HideVersion = false + app.BashComplete = func(c *cli.Context) { + fmt.Fprintf(c.App.Writer, "lipstick\nkiss\nme\nlipstick\nringo\n") + } + app.Before = func(c *cli.Context) error { + fmt.Fprintf(c.App.Writer, "HEEEERE GOES\n") + return nil + } + app.After = func(c *cli.Context) error { + fmt.Fprintf(c.App.Writer, "Phew!\n") + return nil + } + app.CommandNotFound = func(c *cli.Context, command string) { + fmt.Fprintf(c.App.Writer, "Thar be no %q here.\n", command) + } + app.OnUsageError = func(c *cli.Context, err error, isSubcommand bool) error { + if isSubcommand { + return err + } + + fmt.Fprintf(c.App.Writer, "WRONG: %#v\n", err) + return nil + } + app.Action = func(c *cli.Context) error { + cli.DefaultAppComplete(c) + cli.HandleExitCoder(errors.New("not an exit coder, though")) + cli.ShowAppHelp(c) + cli.ShowCommandCompletions(c, "nope") + cli.ShowCommandHelp(c, "also-nope") + cli.ShowCompletions(c) + cli.ShowSubcommandHelp(c) + cli.ShowVersion(c) + + categories := c.App.Categories() + categories.AddCommand("sounds", cli.Command{ + Name: "bloop", + }) + + for _, category := range c.App.Categories() { + fmt.Fprintf(c.App.Writer, "%s\n", category.Name) + fmt.Fprintf(c.App.Writer, "%#v\n", category.Commands) + fmt.Fprintf(c.App.Writer, "%#v\n", category.VisibleCommands()) + } + + fmt.Printf("%#v\n", c.App.Command("doo")) + if c.Bool("infinite") { + c.App.Run([]string{"app", "doo", "wop"}) + } + + if c.Bool("forevar") { + c.App.RunAsSubcommand(c) + } + c.App.Setup() + fmt.Printf("%#v\n", c.App.VisibleCategories()) + fmt.Printf("%#v\n", c.App.VisibleCommands()) + fmt.Printf("%#v\n", c.App.VisibleFlags()) + + fmt.Printf("%#v\n", c.Args().First()) + if len(c.Args()) > 0 { + fmt.Printf("%#v\n", c.Args()[1]) + } + fmt.Printf("%#v\n", c.Args().Present()) + fmt.Printf("%#v\n", c.Args().Tail()) + + set := flag.NewFlagSet("contrive", 0) + nc := cli.NewContext(c.App, set, c) + + fmt.Printf("%#v\n", nc.Args()) + fmt.Printf("%#v\n", nc.Bool("nope")) + fmt.Printf("%#v\n", nc.BoolT("nerp")) + fmt.Printf("%#v\n", nc.Duration("howlong")) + fmt.Printf("%#v\n", nc.Float64("hay")) + fmt.Printf("%#v\n", nc.Generic("bloop")) + fmt.Printf("%#v\n", nc.Int64("bonk")) + fmt.Printf("%#v\n", nc.Int64Slice("burnks")) + fmt.Printf("%#v\n", nc.Int("bips")) + fmt.Printf("%#v\n", nc.IntSlice("blups")) + fmt.Printf("%#v\n", nc.String("snurt")) + fmt.Printf("%#v\n", nc.StringSlice("snurkles")) + fmt.Printf("%#v\n", nc.Uint("flub")) + fmt.Printf("%#v\n", nc.Uint64("florb")) + fmt.Printf("%#v\n", nc.GlobalBool("global-nope")) + fmt.Printf("%#v\n", nc.GlobalBoolT("global-nerp")) + fmt.Printf("%#v\n", nc.GlobalDuration("global-howlong")) + fmt.Printf("%#v\n", nc.GlobalFloat64("global-hay")) + fmt.Printf("%#v\n", nc.GlobalGeneric("global-bloop")) + fmt.Printf("%#v\n", nc.GlobalInt("global-bips")) + fmt.Printf("%#v\n", nc.GlobalIntSlice("global-blups")) + fmt.Printf("%#v\n", nc.GlobalString("global-snurt")) + fmt.Printf("%#v\n", nc.GlobalStringSlice("global-snurkles")) + + fmt.Printf("%#v\n", nc.FlagNames()) + fmt.Printf("%#v\n", nc.GlobalFlagNames()) + fmt.Printf("%#v\n", nc.GlobalIsSet("wat")) + fmt.Printf("%#v\n", nc.GlobalSet("wat", "nope")) + fmt.Printf("%#v\n", nc.NArg()) + fmt.Printf("%#v\n", nc.NumFlags()) + fmt.Printf("%#v\n", nc.Parent()) + + nc.Set("wat", "also-nope") + + ec := cli.NewExitError("ohwell", 86) + fmt.Fprintf(c.App.Writer, "%d", ec.ExitCode()) + fmt.Printf("made it!\n") + return ec + } + + if os.Getenv("HEXY") != "" { + app.Writer = &hexWriter{} + app.ErrWriter = &hexWriter{} + } + + app.Metadata = map[string]interface{}{ + "layers": "many", + "explicable": false, + "whatever-values": 19.99, + } + + app.Run(os.Args) +} + +func wopAction(c *cli.Context) error { + fmt.Fprintf(c.App.Writer, ":wave: over here, eh\n") + return nil +} +``` + ## Contribution Guidelines Feel free to put up a pull request to fix a bug or maybe add a feature. I will diff --git a/vendor/github.com/codegangsta/cli/app.go b/vendor/github.com/codegangsta/cli/app.go index 1e33cdc7..3d1dabbb 100644 --- a/vendor/github.com/codegangsta/cli/app.go +++ b/vendor/github.com/codegangsta/cli/app.go @@ -8,6 +8,7 @@ import ( "path/filepath" "reflect" "sort" + "strings" "time" ) @@ -139,13 +140,6 @@ func (a *App) Setup() { } 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{}) { @@ -153,7 +147,6 @@ func (a *App) Setup() { } } - //append version/help flags if a.EnableBashCompletion { a.appendFlag(BashCompletionFlag) } @@ -161,6 +154,12 @@ func (a *App) Setup() { if !a.HideVersion { a.appendFlag(VersionFlag) } + + a.categories = CommandCategories{} + for _, command := range a.Commands { + a.categories = a.categories.AddCommand(command.Category, command) + } + sort.Sort(a.categories) } // Run is the entry point to the cli app. Parses the arguments slice and routes @@ -461,11 +460,13 @@ func (a Author) String() string { func HandleAction(action interface{}, context *Context) (err error) { defer func() { if r := recover(); r != nil { - switch r.(type) { - case error: - err = r.(error) - default: - err = NewExitError(fmt.Sprintf("ERROR unknown Action error: %v. See %s", r, appActionDeprecationURL), 2) + // Try to detect a known reflection error from *this scope*, rather than + // swallowing all panics that may happen when calling an Action func. + s := fmt.Sprintf("%v", r) + if strings.HasPrefix(s, "reflect: ") && strings.Contains(s, "too many input arguments") { + err = NewExitError(fmt.Sprintf("ERROR unknown Action error: %v. See %s", r, appActionDeprecationURL), 2) + } else { + panic(r) } } }() diff --git a/vendor/github.com/codegangsta/cli/context.go b/vendor/github.com/codegangsta/cli/context.go index c3424636..879bae5e 100644 --- a/vendor/github.com/codegangsta/cli/context.go +++ b/vendor/github.com/codegangsta/cli/context.go @@ -31,6 +31,21 @@ func (c *Context) Int(name string) int { return lookupInt(name, c.flagSet) } +// Int64 looks up the value of a local int flag, returns 0 if no int flag exists +func (c *Context) Int64(name string) int64 { + return lookupInt64(name, c.flagSet) +} + +// Uint looks up the value of a local int flag, returns 0 if no int flag exists +func (c *Context) Uint(name string) uint { + return lookupUint(name, c.flagSet) +} + +// Uint64 looks up the value of a local int flag, returns 0 if no int flag exists +func (c *Context) Uint64(name string) uint64 { + return lookupUint64(name, c.flagSet) +} + // Duration 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 { @@ -70,6 +85,12 @@ func (c *Context) IntSlice(name string) []int { return lookupIntSlice(name, c.flagSet) } +// Int64Slice looks up the value of a local int slice flag, returns nil if no int +// slice flag exists +func (c *Context) Int64Slice(name string) []int64 { + return lookupInt64Slice(name, c.flagSet) +} + // Generic looks up the value of a local generic flag, returns nil if no generic // flag exists func (c *Context) Generic(name string) interface{} { @@ -84,6 +105,30 @@ func (c *Context) GlobalInt(name string) int { return 0 } +// GlobalInt64 looks up the value of a global int flag, returns 0 if no int flag exists +func (c *Context) GlobalInt64(name string) int64 { + if fs := lookupGlobalFlagSet(name, c); fs != nil { + return lookupInt64(name, fs) + } + return 0 +} + +// GlobalUint looks up the value of a global int flag, returns 0 if no int flag exists +func (c *Context) GlobalUint(name string) uint { + if fs := lookupGlobalFlagSet(name, c); fs != nil { + return lookupUint(name, fs) + } + return 0 +} + +// GlobalUint64 looks up the value of a global int flag, returns 0 if no int flag exists +func (c *Context) GlobalUint64(name string) uint64 { + if fs := lookupGlobalFlagSet(name, c); fs != nil { + return lookupUint64(name, fs) + } + return 0 +} + // GlobalFloat64 looks up the value of a global float64 flag, returns float64(0) // if no float64 flag exists func (c *Context) GlobalFloat64(name string) float64 { @@ -147,6 +192,15 @@ func (c *Context) GlobalIntSlice(name string) []int { return nil } +// GlobalInt64Slice looks up the value of a global int slice flag, returns nil if +// no int slice flag exists +func (c *Context) GlobalInt64Slice(name string) []int64 { + if fs := lookupGlobalFlagSet(name, c); fs != nil { + return lookupInt64Slice(name, fs) + } + return nil +} + // GlobalGeneric looks up the value of a global generic flag, returns nil if no // generic flag exists func (c *Context) GlobalGeneric(name string) interface{} { @@ -306,7 +360,46 @@ func lookupGlobalFlagSet(name string, ctx *Context) *flag.FlagSet { func lookupInt(name string, set *flag.FlagSet) int { f := set.Lookup(name) if f != nil { - val, err := strconv.Atoi(f.Value.String()) + val, err := strconv.ParseInt(f.Value.String(), 0, 64) + if err != nil { + return 0 + } + return int(val) + } + + return 0 +} + +func lookupInt64(name string, set *flag.FlagSet) int64 { + f := set.Lookup(name) + if f != nil { + val, err := strconv.ParseInt(f.Value.String(), 0, 64) + if err != nil { + return 0 + } + return val + } + + return 0 +} + +func lookupUint(name string, set *flag.FlagSet) uint { + f := set.Lookup(name) + if f != nil { + val, err := strconv.ParseUint(f.Value.String(), 0, 64) + if err != nil { + return 0 + } + return uint(val) + } + + return 0 +} + +func lookupUint64(name string, set *flag.FlagSet) uint64 { + f := set.Lookup(name) + if f != nil { + val, err := strconv.ParseUint(f.Value.String(), 0, 64) if err != nil { return 0 } @@ -370,6 +463,16 @@ func lookupIntSlice(name string, set *flag.FlagSet) []int { return nil } +func lookupInt64Slice(name string, set *flag.FlagSet) []int64 { + f := set.Lookup(name) + if f != nil { + return (f.Value.(*Int64Slice)).Value() + + } + + return nil +} + func lookupGeneric(name string, set *flag.FlagSet) interface{} { f := set.Lookup(name) if f != nil { diff --git a/vendor/github.com/codegangsta/cli/flag.go b/vendor/github.com/codegangsta/cli/flag.go index 1e8112e7..f8a28d11 100644 --- a/vendor/github.com/codegangsta/cli/flag.go +++ b/vendor/github.com/codegangsta/cli/flag.go @@ -189,7 +189,7 @@ func (f *IntSlice) Set(value string) error { // String returns a readable representation of this value (for usage defaults) func (f *IntSlice) String() string { - return fmt.Sprintf("%d", *f) + return fmt.Sprintf("%#v", *f) } // Value returns the slice of ints set by this flag @@ -245,6 +245,77 @@ func (f IntSliceFlag) GetName() string { return f.Name } +// Int64Slice is an opaque type for []int to satisfy flag.Value +type Int64Slice []int64 + +// Set parses the value into an integer and appends it to the list of values +func (f *Int64Slice) Set(value string) error { + tmp, err := strconv.ParseInt(value, 10, 64) + if err != nil { + return err + } + *f = append(*f, tmp) + return nil +} + +// String returns a readable representation of this value (for usage defaults) +func (f *Int64Slice) String() string { + return fmt.Sprintf("%#v", *f) +} + +// Value returns the slice of ints set by this flag +func (f *Int64Slice) Value() []int64 { + return *f +} + +// Int64SliceFlag is an int flag that can be specified multiple times on the +// command-line +type Int64SliceFlag struct { + Name string + Value *Int64Slice + Usage string + EnvVar string + Hidden bool +} + +// String returns the usage +func (f Int64SliceFlag) String() string { + return FlagStringer(f) +} + +// Apply populates the flag given the flag set and environment +func (f Int64SliceFlag) 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 := &Int64Slice{} + for _, s := range strings.Split(envVal, ",") { + s = strings.TrimSpace(s) + err := newVal.Set(s) + if err != nil { + fmt.Fprintf(ErrWriter, err.Error()) + } + } + f.Value = newVal + break + } + } + } + + eachName(f.Name, func(name string) { + if f.Value == nil { + f.Value = &Int64Slice{} + } + set.Var(f.Value, name, f.Usage) + }) +} + +// GetName returns the name of the flag. +func (f Int64SliceFlag) GetName() string { + return f.Name +} + // BoolFlag is a switch that defaults to false type BoolFlag struct { Name string @@ -376,7 +447,6 @@ func (f StringFlag) GetName() string { } // IntFlag is a flag that takes an integer -// Errors if the value provided cannot be parsed type IntFlag struct { Name string Value int @@ -420,6 +490,138 @@ func (f IntFlag) GetName() string { return f.Name } +// Int64Flag is a flag that takes a 64-bit integer +type Int64Flag struct { + Name string + Value int64 + Usage string + EnvVar string + Destination *int64 + Hidden bool +} + +// String returns the usage +func (f Int64Flag) String() string { + return FlagStringer(f) +} + +// Apply populates the flag given the flag set and environment +func (f Int64Flag) 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 = envValInt + break + } + } + } + } + + eachName(f.Name, func(name string) { + if f.Destination != nil { + set.Int64Var(f.Destination, name, f.Value, f.Usage) + return + } + set.Int64(name, f.Value, f.Usage) + }) +} + +// GetName returns the name of the flag. +func (f Int64Flag) GetName() string { + return f.Name +} + +// UintFlag is a flag that takes an unsigned integer +type UintFlag struct { + Name string + Value uint + Usage string + EnvVar string + Destination *uint + Hidden bool +} + +// String returns the usage +func (f UintFlag) String() string { + return FlagStringer(f) +} + +// Apply populates the flag given the flag set and environment +func (f UintFlag) 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.ParseUint(envVal, 0, 64) + if err == nil { + f.Value = uint(envValInt) + break + } + } + } + } + + eachName(f.Name, func(name string) { + if f.Destination != nil { + set.UintVar(f.Destination, name, f.Value, f.Usage) + return + } + set.Uint(name, f.Value, f.Usage) + }) +} + +// GetName returns the name of the flag. +func (f UintFlag) GetName() string { + return f.Name +} + +// Uint64Flag is a flag that takes an unsigned 64-bit integer +type Uint64Flag struct { + Name string + Value uint64 + Usage string + EnvVar string + Destination *uint64 + Hidden bool +} + +// String returns the usage +func (f Uint64Flag) String() string { + return FlagStringer(f) +} + +// Apply populates the flag given the flag set and environment +func (f Uint64Flag) 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.ParseUint(envVal, 0, 64) + if err == nil { + f.Value = uint64(envValInt) + break + } + } + } + } + + eachName(f.Name, func(name string) { + if f.Destination != nil { + set.Uint64Var(f.Destination, name, f.Value, f.Usage) + return + } + set.Uint64(name, f.Value, f.Usage) + }) +} + +// GetName returns the name of the flag. +func (f Uint64Flag) 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 { @@ -466,7 +668,6 @@ func (f DurationFlag) GetName() string { } // Float64Flag is a flag that takes an float value -// Errors if the value provided cannot be parsed type Float64Flag struct { Name string Value float64 @@ -512,7 +713,7 @@ func (f Float64Flag) GetName() string { func visibleFlags(fl []Flag) []Flag { visible := []Flag{} for _, flag := range fl { - if !reflect.ValueOf(flag).FieldByName("Hidden").Bool() { + if !flagValue(flag).FieldByName("Hidden").Bool() { visible = append(visible, flag) } } @@ -578,13 +779,24 @@ func withEnvHint(envVar, str string) string { return str + envText } -func stringifyFlag(f Flag) string { +func flagValue(f Flag) reflect.Value { fv := reflect.ValueOf(f) + for fv.Kind() == reflect.Ptr { + fv = reflect.Indirect(fv) + } + return fv +} + +func stringifyFlag(f Flag) string { + fv := flagValue(f) switch f.(type) { case IntSliceFlag: return withEnvHint(fv.FieldByName("EnvVar").String(), stringifyIntSliceFlag(f.(IntSliceFlag))) + case Int64SliceFlag: + return withEnvHint(fv.FieldByName("EnvVar").String(), + stringifyInt64SliceFlag(f.(Int64SliceFlag))) case StringSliceFlag: return withEnvHint(fv.FieldByName("EnvVar").String(), stringifyStringSliceFlag(f.(StringSliceFlag))) @@ -630,6 +842,17 @@ func stringifyIntSliceFlag(f IntSliceFlag) string { return stringifySliceFlag(f.Usage, f.Name, defaultVals) } +func stringifyInt64SliceFlag(f Int64SliceFlag) string { + defaultVals := []string{} + if f.Value != nil && len(f.Value.Value()) > 0 { + for _, i := range f.Value.Value() { + defaultVals = append(defaultVals, fmt.Sprintf("%d", i)) + } + } + + return stringifySliceFlag(f.Usage, f.Name, defaultVals) +} + func stringifyStringSliceFlag(f StringSliceFlag) string { defaultVals := []string{} if f.Value != nil && len(f.Value.Value()) > 0 { diff --git a/vendor/github.com/codegangsta/cli/help.go b/vendor/github.com/codegangsta/cli/help.go index a9e7327e..0f4cf14c 100644 --- a/vendor/github.com/codegangsta/cli/help.go +++ b/vendor/github.com/codegangsta/cli/help.go @@ -117,8 +117,9 @@ var HelpPrinter helpPrinter = printHelp var VersionPrinter = printVersion // ShowAppHelp is an action that displays the help. -func ShowAppHelp(c *Context) { +func ShowAppHelp(c *Context) error { HelpPrinter(c.App.Writer, AppHelpTemplate, c.App) + return nil } // DefaultAppComplete prints the list of subcommands as the default app completion method @@ -191,7 +192,7 @@ func printHelp(out io.Writer, templ string, data interface{}) { "join": strings.Join, } - w := tabwriter.NewWriter(out, 0, 8, 1, '\t', 0) + w := tabwriter.NewWriter(out, 1, 8, 2, ' ', 0) t := template.Must(template.New("help").Funcs(funcMap).Parse(templ)) err := t.Execute(w, data) if err != nil { diff --git a/vendor/github.com/codegangsta/cli/runtests b/vendor/github.com/codegangsta/cli/runtests index 72c1f0dd..0a7b483e 100755 --- a/vendor/github.com/codegangsta/cli/runtests +++ b/vendor/github.com/codegangsta/cli/runtests @@ -18,7 +18,8 @@ def main(sysargs=sys.argv[:]): targets = { 'vet': _vet, 'test': _test, - 'gfmxr': _gfmxr + 'gfmxr': _gfmxr, + 'toc': _toc, } parser = argparse.ArgumentParser() @@ -62,6 +63,11 @@ def _vet(): _run('go vet ./...'.split()) +def _toc(): + _run(['node_modules/.bin/markdown-toc', '-i', 'README.md']) + _run(['git', 'diff', '--quiet']) + + def _run(command): print('runtests: {}'.format(' '.join(command)), file=sys.stderr) check_call(command)