1
0
mirror of https://github.com/rancher/os.git synced 2025-07-01 09:11: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"
"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 {

View File

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

View File

@ -45,6 +45,12 @@ func Main() {
SkipFlagParsing: true,
Action: devAction,
},
{
Name: "entrypoint",
HideHelp: true,
SkipFlagParsing: true,
Action: entrypointAction,
},
{
Name: "env",
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 {
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 {

View File

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

View File

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

View File

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

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)
s.CheckCall(c, `
sleep 10
sleep 15
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/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

View File

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

View File

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

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

View File

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

View File

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