1
0
mirror of https://github.com/rancher/os.git synced 2025-07-02 01:31:48 +00:00

Merge pull request #1147 from joshwget/write-files-enhancements

Extend write_files to run in all system services
This commit is contained in:
Darren Shepherd 2016-08-15 11:42:48 -07:00 committed by GitHub
commit 64711f9e66
22 changed files with 1431 additions and 253 deletions

View File

@ -12,8 +12,10 @@ import (
log "github.com/Sirupsen/logrus" log "github.com/Sirupsen/logrus"
"github.com/coreos/coreos-cloudinit/system" "github.com/coreos/coreos-cloudinit/system"
"github.com/docker/docker/pkg/mount" "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" "github.com/rancher/os/util"
"golang.org/x/net/context"
) )
const ( const (
@ -38,7 +40,7 @@ func Main() {
log.Infof("Running cloud-init-execute: pre-console=%v, console=%v", preConsole, console) log.Infof("Running cloud-init-execute: pre-console=%v, console=%v", preConsole, console)
cfg := config.LoadConfig() cfg := rancherConfig.LoadConfig()
if !console && !preConsole { if !console && !preConsole {
console = true console = true
@ -53,21 +55,13 @@ func Main() {
} }
} }
func ApplyConsole(cfg *config.CloudConfig) { func ApplyConsole(cfg *rancherConfig.CloudConfig) {
if len(cfg.SSHAuthorizedKeys) > 0 { if len(cfg.SSHAuthorizedKeys) > 0 {
authorizeSSHKeys("rancher", cfg.SSHAuthorizedKeys, sshKeyName) authorizeSSHKeys("rancher", cfg.SSHAuthorizedKeys, sshKeyName)
authorizeSSHKeys("docker", cfg.SSHAuthorizedKeys, sshKeyName) authorizeSSHKeys("docker", cfg.SSHAuthorizedKeys, sshKeyName)
} }
for _, file := range cfg.WriteFiles { WriteFiles(cfg, "console")
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)
}
for _, configMount := range cfg.Mounts { for _, configMount := range cfg.Mounts {
if len(configMount) != 4 { 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 := os.Stat(resizeStamp); os.IsNotExist(err) && cfg.Rancher.ResizeDevice != "" {
if err := resizeDevice(cfg); err == nil { if err := resizeDevice(cfg); err == nil {
os.Create(resizeStamp) os.Create(resizeStamp)
@ -105,9 +121,20 @@ func applyPreConsole(cfg *config.CloudConfig) {
log.Errorf("Failed to set sysctl key %s: %s", k, err) 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") cmd := exec.Command("growpart", cfg.Rancher.ResizeDevice, "1")
err := cmd.Run() err := cmd.Run()
if err != nil { if err != nil {

View File

@ -70,9 +70,6 @@ func Main() {
func saveFiles(cloudConfigBytes, scriptBytes []byte, metadata datasource.Metadata) error { func saveFiles(cloudConfigBytes, scriptBytes []byte, metadata datasource.Metadata) error {
os.MkdirAll(rancherConfig.CloudConfigDir, os.ModeDir|0600) os.MkdirAll(rancherConfig.CloudConfigDir, os.ModeDir|0600)
os.Remove(rancherConfig.CloudConfigScriptFile)
os.Remove(rancherConfig.CloudConfigBootFile)
os.Remove(rancherConfig.MetaDataFile)
if len(scriptBytes) > 0 { if len(scriptBytes) > 0 {
log.Infof("Writing to %s", rancherConfig.CloudConfigScriptFile) 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 { if len(cloudConfigBytes) > 0 {
return err 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) metaDataBytes, err := yaml.Marshal(metadata)
if err != nil { if err != nil {

View File

@ -45,6 +45,12 @@ func Main() {
SkipFlagParsing: true, SkipFlagParsing: true,
Action: devAction, Action: devAction,
}, },
{
Name: "entrypoint",
HideHelp: true,
SkipFlagParsing: true,
Action: entrypointAction,
},
{ {
Name: "env", Name: "env",
ShortName: "e", ShortName: "e",

74
cmd/control/entrypoint.go Normal file
View File

@ -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
}

View File

@ -85,12 +85,17 @@ type Repositories map[string]Repository
type CloudConfig struct { type CloudConfig struct {
SSHAuthorizedKeys []string `yaml:"ssh_authorized_keys"` SSHAuthorizedKeys []string `yaml:"ssh_authorized_keys"`
WriteFiles []config.File `yaml:"write_files"` WriteFiles []File `yaml:"write_files"`
Hostname string `yaml:"hostname"` Hostname string `yaml:"hostname"`
Mounts [][]string `yaml:"mounts,omitempty"` Mounts [][]string `yaml:"mounts,omitempty"`
Rancher RancherConfig `yaml:"rancher,omitempty"` Rancher RancherConfig `yaml:"rancher,omitempty"`
} }
type File struct {
config.File
Container string `yaml:"container,omitempty"`
}
type RancherConfig struct { type RancherConfig struct {
Console string `yaml:"console,omitempty"` Console string `yaml:"console,omitempty"`
Environment map[string]string `yaml:"environment,omitempty"` Environment map[string]string `yaml:"environment,omitempty"`
@ -119,6 +124,7 @@ type RancherConfig struct {
Defaults Defaults `yaml:"defaults,omitempty"` Defaults Defaults `yaml:"defaults,omitempty"`
ResizeDevice string `yaml:"resize_device,omitempty"` ResizeDevice string `yaml:"resize_device,omitempty"`
Sysctl map[string]string `yaml:"sysctl,omitempty"` Sysctl map[string]string `yaml:"sysctl,omitempty"`
RestartServices []string `yaml:"restart_services,omitempty"`
} }
type UpgradeConfig struct { type UpgradeConfig struct {

View File

@ -32,8 +32,7 @@ RUN rm /sbin/poweroff /sbin/reboot /sbin/halt && \
adduser docker sudo && \ adduser docker sudo && \
echo '%sudo ALL=(ALL) ALL' >> /etc/sudoers echo '%sudo ALL=(ALL) ALL' >> /etc/sudoers
COPY inputrc /etc/inputrc COPY inputrc /etc/inputrc
COPY entry.sh /usr/sbin/entry.sh
COPY growpart /usr/bin/growpart COPY growpart /usr/bin/growpart
RUN sed -i -e 's/duid/clientid/g' /etc/dhcpcd.conf RUN sed -i -e 's/duid/clientid/g' /etc/dhcpcd.conf
ENTRYPOINT ["/usr/sbin/entry.sh"] ENTRYPOINT ["/usr/bin/ros", "entrypoint"]

View File

@ -250,6 +250,9 @@ rancher:
uts: host uts: host
privileged: true privileged: true
restart: always restart: always
volumes_from:
- command-volumes
- system-volumes
preload-system-images: preload-system-images:
image: {{.OS_REPO}}/os-preload:{{.VERSION}}{{.SUFFIX}} image: {{.OS_REPO}}/os-preload:{{.VERSION}}{{.SUFFIX}}
labels: labels:
@ -289,6 +292,7 @@ rancher:
privileged: true privileged: true
restart: always restart: always
volumes_from: volumes_from:
- command-volumes
- system-volumes - system-volumes
system-volumes: system-volumes:
image: {{.OS_REPO}}/os-base:{{.VERSION}}{{.SUFFIX}} image: {{.OS_REPO}}/os-base:{{.VERSION}}{{.SUFFIX}}
@ -325,9 +329,8 @@ rancher:
uts: host uts: host
privileged: true privileged: true
volumes_from: volumes_from:
- command-volumes
- system-volumes - system-volumes
volumes:
- /usr/bin/ros:/usr/bin/ros:ro
udev: udev:
image: {{.OS_REPO}}/os-udev:{{.VERSION}}{{.SUFFIX}} image: {{.OS_REPO}}/os-udev:{{.VERSION}}{{.SUFFIX}}
environment: environment:
@ -340,6 +343,7 @@ rancher:
privileged: true privileged: true
restart: always restart: always
volumes_from: volumes_from:
- command-volumes
- system-volumes - system-volumes
user-volumes: user-volumes:
image: {{.OS_REPO}}/os-base:{{.VERSION}}{{.SUFFIX}} image: {{.OS_REPO}}/os-base:{{.VERSION}}{{.SUFFIX}}

View File

@ -15,19 +15,19 @@ INITRD=${ARTIFACTS}/initrd
mkdir -p ${ARTIFACTS} ${PREPOP_DIR} mkdir -p ${ARTIFACTS} ${PREPOP_DIR}
if [ "$(docker info | grep 'Storage Driver: ' | sed 's/Storage Driver: //')" != "overlay" ]; then #if [ "$(docker info | grep 'Storage Driver: ' | sed 's/Storage Driver: //')" != "overlay" ]; then
echo Overlay storage driver is require to prepackage exploded images echo Overlay storage driver is require to prepackage exploded images
echo packaging images.tar instead echo packaging images.tar instead
tar czf ${ARTIFACTS}/rootfs.tar.gz --exclude lib/modules --exclude lib/firmware -C ${INITRD_DIR} . tar czf ${ARTIFACTS}/rootfs.tar.gz --exclude lib/modules --exclude lib/firmware -C ${INITRD_DIR} .
exit 0 exit 0
fi #fi
DFS=$(docker run -d --privileged -v /lib/modules/$(uname -r):/lib/modules/$(uname -r) ${DFS_IMAGE}${SUFFIX} ${DFS_ARGS}) 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 trap "docker rm -fv ${DFS_ARCH} ${DFS}" EXIT
docker exec -i ${DFS} docker load < ${INITRD_DIR}/usr/share/ros/images.tar docker exec -i ${DFS} docker load < ${INITRD_DIR}/usr/share/ros/images.tar
docker stop ${DFS} 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} busybox 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 ./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 -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} . tar -rf ${ARTIFACTS}/rootfs.tar -C ${IMAGE_CACHE} .

View File

@ -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

View File

@ -7,6 +7,6 @@ func (s *QemuSuite) TestKernelHeaders(c *C) {
c.Assert(err, IsNil) c.Assert(err, IsNil)
s.CheckCall(c, ` s.CheckCall(c, `
sleep 10 sleep 15
docker inspect kernel-headers`) docker inspect kernel-headers`)
} }

13
tests/write_files_test.go Normal file
View File

@ -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'")
}

View File

@ -4,7 +4,7 @@ github.com/Sirupsen/logrus v0.9.0
github.com/boltdb/bolt v1.2.0 github.com/boltdb/bolt v1.2.0
github.com/cloudfoundry-incubator/candiedyaml 01cbc92901719f599b11f3a7e3b1768d7002b0bb https://github.com/rancher/candiedyaml github.com/cloudfoundry-incubator/candiedyaml 01cbc92901719f599b11f3a7e3b1768d7002b0bb https://github.com/rancher/candiedyaml
github.com/cloudfoundry/gosigar 3ed7c74352dae6dc00bdc8c74045375352e3ec05 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/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/coreos-cloudinit v1.11.0-3-gb1c1753 https://github.com/rancher/coreos-cloudinit.git
github.com/coreos/go-iptables fbb73372b87f6e89951c2b6b31470c2c9d5cfae3 github.com/coreos/go-iptables fbb73372b87f6e89951c2b6b31470c2c9d5cfae3

View File

@ -1 +1,2 @@
*.coverprofile *.coverprofile
node_modules/

View File

@ -2,6 +2,10 @@ language: go
sudo: false sudo: false
cache:
directories:
- node_modules
go: go:
- 1.2.2 - 1.2.2
- 1.3.3 - 1.3.3
@ -25,8 +29,12 @@ matrix:
before_script: before_script:
- go get github.com/urfave/gfmxr/... - go get github.com/urfave/gfmxr/...
- if [ ! -f node_modules/.bin/markdown-toc ] ; then
npm install markdown-toc ;
fi
script: script:
- ./runtests vet - ./runtests vet
- ./runtests test - ./runtests test
- ./runtests gfmxr - ./runtests gfmxr
- ./runtests toc

View File

@ -3,13 +3,24 @@
**ATTN**: This project uses [semantic versioning](http://semver.org/). **ATTN**: This project uses [semantic versioning](http://semver.org/).
## [Unreleased] ## [Unreleased]
## [1.18.0] - 2016-06-27
### Added ### Added
- `./runtests` test runner with coverage tracking by default - `./runtests` test runner with coverage tracking by default
- testing on OS X - testing on OS X
- testing on Windows - 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 ### Fixed
- Printing of command aliases in help text - 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 ## [1.17.0] - 2016-05-09
### Added ### Added
@ -290,7 +301,8 @@ signature of `func(*cli.Context) error`, as defined by `cli.ActionFunc`.
### Added ### Added
- Initial implementation. - 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.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.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 [1.15.0]: https://github.com/urfave/cli/compare/v1.14.0...v1.15.0

View File

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

File diff suppressed because it is too large Load Diff

View File

@ -8,6 +8,7 @@ import (
"path/filepath" "path/filepath"
"reflect" "reflect"
"sort" "sort"
"strings"
"time" "time"
) )
@ -139,13 +140,6 @@ func (a *App) Setup() {
} }
a.Commands = newCmds 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 { if a.Command(helpCommand.Name) == nil && !a.HideHelp {
a.Commands = append(a.Commands, helpCommand) a.Commands = append(a.Commands, helpCommand)
if (HelpFlag != BoolFlag{}) { if (HelpFlag != BoolFlag{}) {
@ -153,7 +147,6 @@ func (a *App) Setup() {
} }
} }
//append version/help flags
if a.EnableBashCompletion { if a.EnableBashCompletion {
a.appendFlag(BashCompletionFlag) a.appendFlag(BashCompletionFlag)
} }
@ -161,6 +154,12 @@ func (a *App) Setup() {
if !a.HideVersion { if !a.HideVersion {
a.appendFlag(VersionFlag) 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 // 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) { func HandleAction(action interface{}, context *Context) (err error) {
defer func() { defer func() {
if r := recover(); r != nil { if r := recover(); r != nil {
switch r.(type) { // Try to detect a known reflection error from *this scope*, rather than
case error: // swallowing all panics that may happen when calling an Action func.
err = r.(error) s := fmt.Sprintf("%v", r)
default: 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) err = NewExitError(fmt.Sprintf("ERROR unknown Action error: %v. See %s", r, appActionDeprecationURL), 2)
} else {
panic(r)
} }
} }
}() }()

View File

@ -31,6 +31,21 @@ func (c *Context) Int(name string) int {
return lookupInt(name, c.flagSet) 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 // Duration looks up the value of a local time.Duration flag, returns 0 if no
// time.Duration flag exists // time.Duration flag exists
func (c *Context) Duration(name string) time.Duration { func (c *Context) Duration(name string) time.Duration {
@ -70,6 +85,12 @@ func (c *Context) IntSlice(name string) []int {
return lookupIntSlice(name, c.flagSet) 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 // Generic looks up the value of a local generic flag, returns nil if no generic
// flag exists // flag exists
func (c *Context) Generic(name string) interface{} { func (c *Context) Generic(name string) interface{} {
@ -84,6 +105,30 @@ func (c *Context) GlobalInt(name string) int {
return 0 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) // GlobalFloat64 looks up the value of a global float64 flag, returns float64(0)
// if no float64 flag exists // if no float64 flag exists
func (c *Context) GlobalFloat64(name string) float64 { func (c *Context) GlobalFloat64(name string) float64 {
@ -147,6 +192,15 @@ func (c *Context) GlobalIntSlice(name string) []int {
return nil 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 // GlobalGeneric looks up the value of a global generic flag, returns nil if no
// generic flag exists // generic flag exists
func (c *Context) GlobalGeneric(name string) interface{} { 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 { func lookupInt(name string, set *flag.FlagSet) int {
f := set.Lookup(name) f := set.Lookup(name)
if f != nil { 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 { if err != nil {
return 0 return 0
} }
@ -370,6 +463,16 @@ func lookupIntSlice(name string, set *flag.FlagSet) []int {
return nil 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{} { func lookupGeneric(name string, set *flag.FlagSet) interface{} {
f := set.Lookup(name) f := set.Lookup(name)
if f != nil { if f != nil {

View File

@ -189,7 +189,7 @@ func (f *IntSlice) Set(value string) error {
// String returns a readable representation of this value (for usage defaults) // String returns a readable representation of this value (for usage defaults)
func (f *IntSlice) String() string { 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 // Value returns the slice of ints set by this flag
@ -245,6 +245,77 @@ func (f IntSliceFlag) GetName() string {
return f.Name 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 // BoolFlag is a switch that defaults to false
type BoolFlag struct { type BoolFlag struct {
Name string Name string
@ -376,7 +447,6 @@ func (f StringFlag) GetName() string {
} }
// IntFlag is a flag that takes an integer // IntFlag is a flag that takes an integer
// Errors if the value provided cannot be parsed
type IntFlag struct { type IntFlag struct {
Name string Name string
Value int Value int
@ -420,6 +490,138 @@ func (f IntFlag) GetName() string {
return f.Name 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 // DurationFlag is a flag that takes a duration specified in Go's duration
// format: https://golang.org/pkg/time/#ParseDuration // format: https://golang.org/pkg/time/#ParseDuration
type DurationFlag struct { type DurationFlag struct {
@ -466,7 +668,6 @@ func (f DurationFlag) GetName() string {
} }
// Float64Flag is a flag that takes an float value // Float64Flag is a flag that takes an float value
// Errors if the value provided cannot be parsed
type Float64Flag struct { type Float64Flag struct {
Name string Name string
Value float64 Value float64
@ -512,7 +713,7 @@ func (f Float64Flag) GetName() string {
func visibleFlags(fl []Flag) []Flag { func visibleFlags(fl []Flag) []Flag {
visible := []Flag{} visible := []Flag{}
for _, flag := range fl { for _, flag := range fl {
if !reflect.ValueOf(flag).FieldByName("Hidden").Bool() { if !flagValue(flag).FieldByName("Hidden").Bool() {
visible = append(visible, flag) visible = append(visible, flag)
} }
} }
@ -578,13 +779,24 @@ func withEnvHint(envVar, str string) string {
return str + envText return str + envText
} }
func stringifyFlag(f Flag) string { func flagValue(f Flag) reflect.Value {
fv := reflect.ValueOf(f) 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) { switch f.(type) {
case IntSliceFlag: case IntSliceFlag:
return withEnvHint(fv.FieldByName("EnvVar").String(), return withEnvHint(fv.FieldByName("EnvVar").String(),
stringifyIntSliceFlag(f.(IntSliceFlag))) stringifyIntSliceFlag(f.(IntSliceFlag)))
case Int64SliceFlag:
return withEnvHint(fv.FieldByName("EnvVar").String(),
stringifyInt64SliceFlag(f.(Int64SliceFlag)))
case StringSliceFlag: case StringSliceFlag:
return withEnvHint(fv.FieldByName("EnvVar").String(), return withEnvHint(fv.FieldByName("EnvVar").String(),
stringifyStringSliceFlag(f.(StringSliceFlag))) stringifyStringSliceFlag(f.(StringSliceFlag)))
@ -630,6 +842,17 @@ func stringifyIntSliceFlag(f IntSliceFlag) string {
return stringifySliceFlag(f.Usage, f.Name, defaultVals) 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 { func stringifyStringSliceFlag(f StringSliceFlag) string {
defaultVals := []string{} defaultVals := []string{}
if f.Value != nil && len(f.Value.Value()) > 0 { if f.Value != nil && len(f.Value.Value()) > 0 {

View File

@ -117,8 +117,9 @@ var HelpPrinter helpPrinter = printHelp
var VersionPrinter = printVersion var VersionPrinter = printVersion
// ShowAppHelp is an action that displays the help. // ShowAppHelp is an action that displays the help.
func ShowAppHelp(c *Context) { func ShowAppHelp(c *Context) error {
HelpPrinter(c.App.Writer, AppHelpTemplate, c.App) HelpPrinter(c.App.Writer, AppHelpTemplate, c.App)
return nil
} }
// DefaultAppComplete prints the list of subcommands as the default app completion method // 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, "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)) t := template.Must(template.New("help").Funcs(funcMap).Parse(templ))
err := t.Execute(w, data) err := t.Execute(w, data)
if err != nil { if err != nil {

View File

@ -18,7 +18,8 @@ def main(sysargs=sys.argv[:]):
targets = { targets = {
'vet': _vet, 'vet': _vet,
'test': _test, 'test': _test,
'gfmxr': _gfmxr 'gfmxr': _gfmxr,
'toc': _toc,
} }
parser = argparse.ArgumentParser() parser = argparse.ArgumentParser()
@ -62,6 +63,11 @@ def _vet():
_run('go vet ./...'.split()) _run('go vet ./...'.split())
def _toc():
_run(['node_modules/.bin/markdown-toc', '-i', 'README.md'])
_run(['git', 'diff', '--quiet'])
def _run(command): def _run(command):
print('runtests: {}'.format(' '.join(command)), file=sys.stderr) print('runtests: {}'.format(' '.join(command)), file=sys.stderr)
check_call(command) check_call(command)