Updated vendor, for container-diff

This commit is contained in:
Priya Wadhwa
2018-04-11 14:06:23 -07:00
parent ce2b515d49
commit 4d048d1607
181 changed files with 3326 additions and 1841 deletions
@@ -1,5 +1,5 @@
/*
Copyright 2017 Google, Inc. All rights reserved.
Copyright 2018 Google, Inc. All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -1,5 +1,5 @@
/*
Copyright 2017 Google, Inc. All rights reserved.
Copyright 2018 Google, Inc. All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -20,13 +20,14 @@ import (
"bytes"
"compress/gzip"
"encoding/json"
img "github.com/containers/image/image"
"github.com/containers/image/types"
"io"
"io/ioutil"
"strings"
"time"
"github.com/containers/image/manifest"
"github.com/containers/image/types"
digest "github.com/opencontainers/go-digest"
)
@@ -76,28 +77,46 @@ func (m *MutableSource) GetManifest(_ *digest.Digest) ([]byte, string, error) {
// populateManifestAndConfig parses the raw manifest and configs, storing them on the struct.
func (m *MutableSource) populateManifestAndConfig() error {
mfstBytes, _, err := m.ProxySource.GetManifest(nil)
context := &types.SystemContext{
OSChoice: "linux",
ArchitectureChoice: "amd64",
}
image, err := m.ProxySource.Ref.NewImage(context)
if err != nil {
return err
}
defer image.Close()
// First get manifest
mfstBytes, mfstType, err := image.Manifest()
if err != nil {
return err
}
if mfstType == manifest.DockerV2ListMediaType {
// We need to select a manifest digest from the manifest list
unparsedImage := img.UnparsedInstance(m.ImageSource, nil)
mfstDigest, err := img.ChooseManifestInstanceFromManifestList(context, unparsedImage)
if err != nil {
return err
}
mfstBytes, _, err = m.ProxySource.GetManifest(&mfstDigest)
if err != nil {
return err
}
}
m.mfst, err = manifest.Schema2FromManifest(mfstBytes)
if err != nil {
return err
}
bi := types.BlobInfo{Digest: m.mfst.ConfigDescriptor.Digest}
r, _, err := m.GetBlob(bi)
// Now, get config
configBlob, err := image.ConfigBlob()
if err != nil {
return err
}
cfgBytes, err := ioutil.ReadAll(r)
if err != nil {
return err
}
return json.Unmarshal(cfgBytes, &m.cfg)
return json.Unmarshal(configBlob, &m.cfg)
}
// GetBlob first checks the stored "extra" blobs, then proxies the call to the original source.
@@ -1,5 +1,5 @@
/*
Copyright 2017 Google, Inc. All rights reserved.
Copyright 2018 Google, Inc. All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -32,6 +32,7 @@ func NewProxySource(ref types.ImageReference) (*ProxySource, error) {
if err != nil {
return nil, err
}
defer src.Close()
img, err := ref.NewImage(nil)
if err != nil {
return nil, err
@@ -1,5 +1,5 @@
/*
Copyright 2017 Google, Inc. All rights reserved.
Copyright 2018 Google, Inc. All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -32,21 +32,25 @@ type CloudPrepper struct {
ImageSource types.ImageSource
}
func (p CloudPrepper) Name() string {
func (p *CloudPrepper) Name() string {
return "Cloud Registry"
}
func (p CloudPrepper) GetSource() string {
func (p *CloudPrepper) GetSource() string {
return p.Source
}
func (p CloudPrepper) GetImage() (Image, error) {
func (p *CloudPrepper) SetSource(source string) {
p.Source = source
}
func (p *CloudPrepper) GetImage() (Image, error) {
image, err := getImage(p)
image.Type = ImageTypeCloud
return image, err
}
func (p CloudPrepper) GetFileSystem() (string, error) {
func (p *CloudPrepper) GetFileSystem() (string, error) {
ref, err := docker.ParseReference("//" + p.Source)
if err != nil {
return "", err
@@ -62,7 +66,7 @@ func (p CloudPrepper) GetFileSystem() (string, error) {
return path, GetFileSystemFromReference(ref, p.ImageSource, path, nil)
}
func (p CloudPrepper) GetConfig() (ConfigSchema, error) {
func (p *CloudPrepper) GetConfig() (ConfigSchema, error) {
ref, err := docker.ParseReference("//" + p.Source)
if err != nil {
return ConfigSchema{}, err
@@ -1,5 +1,5 @@
/*
Copyright 2017 Google, Inc. All rights reserved.
Copyright 2018 Google, Inc. All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -32,21 +32,25 @@ type DaemonPrepper struct {
Client *client.Client
}
func (p DaemonPrepper) Name() string {
func (p *DaemonPrepper) Name() string {
return "Local Daemon"
}
func (p DaemonPrepper) GetSource() string {
func (p *DaemonPrepper) GetSource() string {
return p.Source
}
func (p DaemonPrepper) GetImage() (Image, error) {
func (p *DaemonPrepper) SetSource(source string) {
p.Source = source
}
func (p *DaemonPrepper) GetImage() (Image, error) {
image, err := getImage(p)
image.Type = ImageTypeDaemon
return image, err
}
func (p DaemonPrepper) GetFileSystem() (string, error) {
func (p *DaemonPrepper) GetFileSystem() (string, error) {
ref, err := daemon.ParseReference(p.Source)
if err != nil {
return "", err
@@ -56,6 +60,7 @@ func (p DaemonPrepper) GetFileSystem() (string, error) {
if err != nil {
return "", err
}
defer src.Close()
sanitizedName := strings.Replace(p.Source, ":", "", -1)
sanitizedName = strings.Replace(sanitizedName, "/", "", -1)
@@ -67,7 +72,7 @@ func (p DaemonPrepper) GetFileSystem() (string, error) {
return path, GetFileSystemFromReference(ref, src, path, nil)
}
func (p DaemonPrepper) GetConfig() (ConfigSchema, error) {
func (p *DaemonPrepper) GetConfig() (ConfigSchema, error) {
ref, err := daemon.ParseReference(p.Source)
if err != nil {
return ConfigSchema{}, err
@@ -75,7 +80,7 @@ func (p DaemonPrepper) GetConfig() (ConfigSchema, error) {
return getConfigFromReference(ref, p.Source)
}
func (p DaemonPrepper) GetHistory() []ImageHistoryItem {
func (p *DaemonPrepper) GetHistory() []ImageHistoryItem {
history, err := p.Client.ImageHistory(context.Background(), p.Source)
if err != nil {
logrus.Errorf("Could not obtain image history for %s: %s", p.Source, err)
@@ -1,5 +1,5 @@
/*
Copyright 2017 Google, Inc. All rights reserved.
Copyright 2018 Google, Inc. All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
+1 -1
View File
@@ -1,5 +1,5 @@
/*
Copyright 2017 Google, Inc. All rights reserved.
Copyright 2018 Google, Inc. All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -1,5 +1,5 @@
/*
Copyright 2017 Google, Inc. All rights reserved.
Copyright 2018 Google, Inc. All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -20,8 +20,10 @@ import (
"archive/tar"
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"os"
"sort"
"strings"
"github.com/GoogleCloudPlatform/container-diff/cmd/util/output"
@@ -38,6 +40,7 @@ type Prepper interface {
GetFileSystem() (string, error)
GetImage() (Image, error)
GetSource() string
SetSource(string)
}
type ImageType int
@@ -84,13 +87,60 @@ type ImageHistoryItem struct {
}
type ConfigObject struct {
Env []string `json:"Env"`
Entrypoint []string `json:"Entrypoint"`
Hostname string
Domainname string
User string
AttachStdin bool
AttachStdout bool
AttachStderr bool
ExposedPorts map[string]struct{} `json:"ExposedPorts"`
Cmd []string `json:"Cmd"`
Volumes map[string]struct{} `json:"Volumes"`
Workdir string `json:"WorkingDir"`
Labels map[string]string `json:"Labels"`
Tty bool
OpenStdin bool
StdinOnce bool
Env []string `json:"Env"`
Cmd []string `json:"Cmd"`
// Healthcheck *HealthConfig
ArgsEscaped bool `json:",omitempty"`
Image string
Volumes map[string]struct{} `json:"Volumes"`
Workdir string `json:"WorkingDir"`
Entrypoint []string `json:"Entrypoint"`
NetworkDisabled bool `json:",omitempty"`
MacAddress string `json:",omitempty"`
OnBuild []string
Labels map[string]string `json:"Labels"`
StopSignal string `json:",omitempty"`
StopTimeout *int `json:",omitempty"`
Shell []string `json:",omitempty"`
}
func (c ConfigObject) AsList() []string {
return []string{
fmt.Sprintf("Hostname: %s", c.Hostname),
fmt.Sprintf("Domainname: %s", c.Domainname),
fmt.Sprintf("User: %s", c.User),
fmt.Sprintf("AttachStdin: %t", c.AttachStdin),
fmt.Sprintf("AttachStdout: %t", c.AttachStdout),
fmt.Sprintf("AttachStderr: %t", c.AttachStderr),
fmt.Sprintf("ExposedPorts: %v", sortMap(c.ExposedPorts)),
fmt.Sprintf("Tty: %t", c.Tty),
fmt.Sprintf("OpenStdin: %t", c.OpenStdin),
fmt.Sprintf("StdinOnce: %t", c.StdinOnce),
fmt.Sprintf("Env: %s", strings.Join(c.Env, ",")),
fmt.Sprintf("Cmd: %s", strings.Join(c.Cmd, ",")),
fmt.Sprintf("ArgsEscaped: %t", c.ArgsEscaped),
fmt.Sprintf("Image: %s", c.Image),
fmt.Sprintf("Volumes: %v", sortMap(c.Volumes)),
fmt.Sprintf("Workdir: %s", c.Workdir),
fmt.Sprintf("Entrypoint: %s", strings.Join(c.Entrypoint, ",")),
fmt.Sprintf("NetworkDisabled: %t", c.NetworkDisabled),
fmt.Sprintf("MacAddress: %s", c.MacAddress),
fmt.Sprintf("OnBuild: %s", strings.Join(c.OnBuild, ",")),
fmt.Sprintf("Labels: %v", c.Labels),
fmt.Sprintf("StopSignal: %s", c.StopSignal),
fmt.Sprintf("StopTimeout: %d", c.StopTimeout),
fmt.Sprintf("Shell: %s", strings.Join(c.Shell, ",")),
}
}
type ConfigSchema struct {
@@ -99,10 +149,17 @@ type ConfigSchema struct {
}
func getImage(p Prepper) (Image, error) {
// see if the image name has tag provided, if not add latest as tag
if !IsTar(p.GetSource()) && !HasTag(p.GetSource()) {
p.SetSource(p.GetSource() + LatestTag)
}
output.PrintToStdErr("Retrieving image %s from source %s\n", p.GetSource(), p.Name())
imgPath, err := p.GetFileSystem()
if err != nil {
return Image{}, err
// return image with FSPath so it can be cleaned up
return Image{
FSPath: imgPath,
}, err
}
config, err := p.GetConfig()
@@ -128,6 +185,14 @@ func getImageFromTar(tarPath string) (string, error) {
}
func GetFileSystemFromReference(ref types.ImageReference, imgSrc types.ImageSource, path string, whitelist []string) error {
var err error
if imgSrc == nil {
imgSrc, err = ref.NewImageSource(nil)
}
if err != nil {
return err
}
defer imgSrc.Close()
img, err := ref.NewImage(nil)
if err != nil {
return err
@@ -208,7 +273,16 @@ func CleanupImage(image Image) {
if image.FSPath != "" {
logrus.Infof("Removing image filesystem directory %s from system", image.FSPath)
if err := os.RemoveAll(image.FSPath); err != nil {
logrus.Error(err.Error())
logrus.Warn(err.Error())
}
}
}
func sortMap(m map[string]struct{}) string {
pairs := make([]string, 0)
for key := range m {
pairs = append(pairs, fmt.Sprintf("%s:%s", key, m[key]))
}
sort.Strings(pairs)
return strings.Join(pairs, " ")
}
@@ -1,5 +1,5 @@
/*
Copyright 2017 Google, Inc. All rights reserved.
Copyright 2018 Google, Inc. All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -21,11 +21,14 @@ import (
"io/ioutil"
"os"
"path/filepath"
"regexp"
"github.com/docker/docker/pkg/system"
"github.com/sirupsen/logrus"
)
const LatestTag string = ":latest"
func GetImageLayers(pathToImage string) []string {
layers := []string{}
contents, err := ioutil.ReadDir(pathToImage)
@@ -67,3 +70,9 @@ func copyToFile(outfile string, r io.Reader) error {
return nil
}
// checks to see if an image string contains a tag.
func HasTag(image string) bool {
tagRegex := regexp.MustCompile(".*:[^/]+$")
return tagRegex.MatchString(image)
}
@@ -1,5 +1,5 @@
/*
Copyright 2017 Google, Inc. All rights reserved.
Copyright 2018 Google, Inc. All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -32,25 +32,29 @@ type TarPrepper struct {
Client *client.Client
}
func (p TarPrepper) Name() string {
func (p *TarPrepper) Name() string {
return "Tar Archive"
}
func (p TarPrepper) GetSource() string {
func (p *TarPrepper) GetSource() string {
return p.Source
}
func (p TarPrepper) GetImage() (Image, error) {
func (p *TarPrepper) SetSource(source string) {
p.Source = source
}
func (p *TarPrepper) GetImage() (Image, error) {
image, err := getImage(p)
image.Type = ImageTypeTar
return image, err
}
func (p TarPrepper) GetFileSystem() (string, error) {
func (p *TarPrepper) GetFileSystem() (string, error) {
return getImageFromTar(p.Source)
}
func (p TarPrepper) GetConfig() (ConfigSchema, error) {
func (p *TarPrepper) GetConfig() (ConfigSchema, error) {
tempDir, err := ioutil.TempDir("", ".container-diff")
if err != nil {
return ConfigSchema{}, nil
+62 -4
View File
@@ -1,5 +1,5 @@
/*
Copyright 2017 Google, Inc. All rights reserved.
Copyright 2018 Google, Inc. All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -18,14 +18,26 @@ package util
import (
"archive/tar"
"github.com/sirupsen/logrus"
"fmt"
"io"
"os"
"path/filepath"
"strings"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
)
// Map of target:linkname
var hardlinks = make(map[string]string)
type OriginalPerm struct {
path string
perm os.FileMode
}
func unpackTar(tr *tar.Reader, path string, whitelist []string) error {
originalPerms := make([]OriginalPerm, 0)
for {
header, err := tr.Next()
if err == io.EOF {
@@ -37,7 +49,7 @@ func unpackTar(tr *tar.Reader, path string, whitelist []string) error {
return err
}
if strings.Contains(header.Name, ".wh.") {
rmPath := filepath.Join(path, header.Name)
rmPath := filepath.Clean(filepath.Join(path, header.Name))
// Remove the .wh file if it was extracted.
if _, err := os.Stat(rmPath); !os.IsNotExist(err) {
if err := os.Remove(rmPath); err != nil {
@@ -52,7 +64,7 @@ func unpackTar(tr *tar.Reader, path string, whitelist []string) error {
}
continue
}
target := filepath.Join(path, header.Name)
target := filepath.Clean(filepath.Join(path, header.Name))
// Make sure the target isn't part of the whitelist
if checkWhitelist(target, whitelist) {
continue
@@ -63,6 +75,17 @@ func unpackTar(tr *tar.Reader, path string, whitelist []string) error {
// if its a dir and it doesn't exist create it
case tar.TypeDir:
if _, err := os.Stat(target); os.IsNotExist(err) {
if mode.Perm()&(1<<(uint(7))) == 0 {
logrus.Debugf("Write permission bit not set on %s by default; setting manually", target)
originalMode := mode
mode = mode | (1 << uint(7))
// keep track of original file permission to reset later
originalPerms = append(originalPerms, OriginalPerm{
path: target,
perm: originalMode,
})
}
logrus.Debugf("Creating directory %s with permissions %v", target, mode)
if err := os.MkdirAll(target, mode); err != nil {
return err
}
@@ -91,6 +114,7 @@ func unpackTar(tr *tar.Reader, path string, whitelist []string) error {
}
}
logrus.Debugf("Creating file %s with permissions %v", target, mode)
currFile, err := os.Create(target)
if err != nil {
logrus.Errorf("Error creating file %s %s", target, err)
@@ -119,8 +143,42 @@ func unpackTar(tr *tar.Reader, path string, whitelist []string) error {
if err = os.Symlink(header.Linkname, target); err != nil {
logrus.Errorf("Failed to create symlink between %s and %s: %s", header.Linkname, target, err)
}
case tar.TypeLink:
linkname := filepath.Clean(filepath.Join(path, header.Linkname))
// Check if the linkname already exists
if _, err := os.Stat(linkname); !os.IsNotExist(err) {
// If it exists, create the hard link
resolveHardlink(linkname, target)
} else {
hardlinks[target] = linkname
}
}
}
for target, linkname := range hardlinks {
logrus.Info("Resolving hard links.")
if _, err := os.Stat(linkname); !os.IsNotExist(err) {
// If it exists, create the hard link
if err := resolveHardlink(linkname, target); err != nil {
return errors.Wrap(err, fmt.Sprintf("Unable to create hard link from %s to %s", linkname, target))
}
}
}
// reset all original file
for _, perm := range originalPerms {
if err := os.Chmod(perm.path, perm.perm); err != nil {
return err
}
}
return nil
}
func resolveHardlink(linkname, target string) error {
if err := os.Link(linkname, target); err != nil {
return err
}
logrus.Debugf("Created hard link from %s to %s", linkname, target)
return nil
}
+29 -5
View File
@@ -77,15 +77,20 @@ func NewHighBiased(epsilon float64) *Stream {
// is guaranteed to be within (Quantile±Epsilon).
//
// See http://www.cs.rutgers.edu/~muthu/bquant.pdf for time, space, and error properties.
func NewTargeted(targets map[float64]float64) *Stream {
func NewTargeted(targetMap map[float64]float64) *Stream {
// Convert map to slice to avoid slow iterations on a map.
// ƒ is called on the hot path, so converting the map to a slice
// beforehand results in significant CPU savings.
targets := targetMapToSlice(targetMap)
ƒ := func(s *stream, r float64) float64 {
var m = math.MaxFloat64
var f float64
for quantile, epsilon := range targets {
if quantile*s.n <= r {
f = (2 * epsilon * r) / quantile
for _, t := range targets {
if t.quantile*s.n <= r {
f = (2 * t.epsilon * r) / t.quantile
} else {
f = (2 * epsilon * (s.n - r)) / (1 - quantile)
f = (2 * t.epsilon * (s.n - r)) / (1 - t.quantile)
}
if f < m {
m = f
@@ -96,6 +101,25 @@ func NewTargeted(targets map[float64]float64) *Stream {
return newStream(ƒ)
}
type target struct {
quantile float64
epsilon float64
}
func targetMapToSlice(targetMap map[float64]float64) []target {
targets := make([]target, 0, len(targetMap))
for quantile, epsilon := range targetMap {
t := target{
quantile: quantile,
epsilon: epsilon,
}
targets = append(targets, t)
}
return targets
}
// Stream computes quantiles for a stream of float64s. It is not thread-safe by
// design. Take care when using across multiple goroutines.
type Stream struct {
@@ -45,13 +45,13 @@ type Manager interface {
// to a backend.
func NewSimpleManager() Manager {
return &simpleManager{
Challanges: make(map[string][]Challenge),
Challenges: make(map[string][]Challenge),
}
}
type simpleManager struct {
sync.RWMutex
Challanges map[string][]Challenge
Challenges map[string][]Challenge
}
func normalizeURL(endpoint *url.URL) {
@@ -64,7 +64,7 @@ func (m *simpleManager) GetChallenges(endpoint url.URL) ([]Challenge, error) {
m.RLock()
defer m.RUnlock()
challenges := m.Challanges[endpoint.String()]
challenges := m.Challenges[endpoint.String()]
return challenges, nil
}
@@ -82,7 +82,7 @@ func (m *simpleManager) AddResponse(resp *http.Response) error {
m.Lock()
defer m.Unlock()
m.Challanges[urlCopy.String()] = challenges
m.Challenges[urlCopy.String()] = challenges
return nil
}
+1 -1
View File
@@ -31,7 +31,7 @@ type unitMap map[string]int64
var (
decimalMap = unitMap{"k": KB, "m": MB, "g": GB, "t": TB, "p": PB}
binaryMap = unitMap{"k": KiB, "m": MiB, "g": GiB, "t": TiB, "p": PiB}
sizeRegex = regexp.MustCompile(`^(\d+(\.\d+)*) ?([kKmMgGtTpP])?[bB]?$`)
sizeRegex = regexp.MustCompile(`^(\d+(\.\d+)*) ?([kKmMgGtTpP])?[iI]?[bB]?$`)
)
var decimapAbbrs = []string{"B", "kB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"}
+7 -7
View File
@@ -31,8 +31,8 @@ func isExported(field reflect.StructField) bool {
// Traverses recursively both values, assigning src's fields values to dst.
// The map argument tracks comparisons that have already been seen, which allows
// short circuiting on recursive types.
func deepMap(dst, src reflect.Value, visited map[uintptr]*visit, depth int, config *config) (err error) {
overwrite := config.overwrite
func deepMap(dst, src reflect.Value, visited map[uintptr]*visit, depth int, config *Config) (err error) {
overwrite := config.Overwrite
if dst.CanAddr() {
addr := dst.UnsafeAddr()
h := 17 * addr
@@ -128,23 +128,23 @@ func deepMap(dst, src reflect.Value, visited map[uintptr]*visit, depth int, conf
// doesn't apply if dst is a map.
// This is separated method from Merge because it is cleaner and it keeps sane
// semantics: merging equal types, mapping different (restricted) types.
func Map(dst, src interface{}, opts ...func(*config)) error {
func Map(dst, src interface{}, opts ...func(*Config)) error {
return _map(dst, src, opts...)
}
// MapWithOverwrite will do the same as Map except that non-empty dst attributes will be overriden by
// MapWithOverwrite will do the same as Map except that non-empty dst attributes will be overridden by
// non-empty src attribute values.
// Deprecated: Use Map(…) with WithOverride
func MapWithOverwrite(dst, src interface{}, opts ...func(*config)) error {
func MapWithOverwrite(dst, src interface{}, opts ...func(*Config)) error {
return _map(dst, src, append(opts, WithOverride)...)
}
func _map(dst, src interface{}, opts ...func(*config)) error {
func _map(dst, src interface{}, opts ...func(*Config)) error {
var (
vDst, vSrc reflect.Value
err error
)
config := &config{}
config := &Config{}
for _, opt := range opts {
opt(config)
+37 -23
View File
@@ -8,7 +8,9 @@
package mergo
import "reflect"
import (
"reflect"
)
func hasExportedField(dst reflect.Value) (exported bool) {
for i, n := 0, dst.NumField(); i < n; i++ {
@@ -22,20 +24,21 @@ func hasExportedField(dst reflect.Value) (exported bool) {
return
}
type config struct {
overwrite bool
transformers transformers
type Config struct {
Overwrite bool
AppendSlice bool
Transformers Transformers
}
type transformers interface {
type Transformers interface {
Transformer(reflect.Type) func(dst, src reflect.Value) error
}
// Traverses recursively both values, assigning src's fields values to dst.
// The map argument tracks comparisons that have already been seen, which allows
// short circuiting on recursive types.
func deepMerge(dst, src reflect.Value, visited map[uintptr]*visit, depth int, config *config) (err error) {
overwrite := config.overwrite
func deepMerge(dst, src reflect.Value, visited map[uintptr]*visit, depth int, config *Config) (err error) {
overwrite := config.Overwrite
if !src.IsValid() {
return
@@ -54,8 +57,8 @@ func deepMerge(dst, src reflect.Value, visited map[uintptr]*visit, depth int, co
visited[h] = &visit{addr, typ, seen}
}
if config.transformers != nil && !isEmptyValue(dst) {
if fn := config.transformers.Transformer(dst.Type()); fn != nil {
if config.Transformers != nil && !isEmptyValue(dst) {
if fn := config.Transformers.Transformer(dst.Type()); fn != nil {
err = fn(dst, src)
return
}
@@ -75,9 +78,8 @@ func deepMerge(dst, src reflect.Value, visited map[uintptr]*visit, depth int, co
}
}
case reflect.Map:
if len(src.MapKeys()) == 0 && !src.IsNil() && len(dst.MapKeys()) == 0 {
if dst.IsNil() && !src.IsNil() {
dst.Set(reflect.MakeMap(dst.Type()))
return
}
for _, key := range src.MapKeys() {
srcElement := src.MapIndex(key)
@@ -86,7 +88,7 @@ func deepMerge(dst, src reflect.Value, visited map[uintptr]*visit, depth int, co
}
dstElement := dst.MapIndex(key)
switch srcElement.Kind() {
case reflect.Chan, reflect.Func, reflect.Map, reflect.Ptr, reflect.Interface, reflect.Slice:
case reflect.Chan, reflect.Func, reflect.Map, reflect.Interface, reflect.Slice:
if srcElement.IsNil() {
continue
}
@@ -122,7 +124,7 @@ func deepMerge(dst, src reflect.Value, visited map[uintptr]*visit, depth int, co
continue
}
if !isEmptyValue(srcElement) && (overwrite || (!dstElement.IsValid() || isEmptyValue(dst))) {
if srcElement.IsValid() && (overwrite || (!dstElement.IsValid() || isEmptyValue(dst))) {
if dst.IsNil() {
dst.Set(reflect.MakeMap(dst.Type()))
}
@@ -130,7 +132,14 @@ func deepMerge(dst, src reflect.Value, visited map[uintptr]*visit, depth int, co
}
}
case reflect.Slice:
dst.Set(reflect.AppendSlice(dst, src))
if !dst.CanSet() {
break
}
if !isEmptyValue(src) && (overwrite || isEmptyValue(dst)) && !config.AppendSlice {
dst.Set(src)
} else {
dst.Set(reflect.AppendSlice(dst, src))
}
case reflect.Ptr:
fallthrough
case reflect.Interface:
@@ -174,36 +183,41 @@ func deepMerge(dst, src reflect.Value, visited map[uintptr]*visit, depth int, co
// src attributes if they themselves are not empty. dst and src must be valid same-type structs
// and dst must be a pointer to struct.
// It won't merge unexported (private) fields and will do recursively any exported field.
func Merge(dst, src interface{}, opts ...func(*config)) error {
func Merge(dst, src interface{}, opts ...func(*Config)) error {
return merge(dst, src, opts...)
}
// MergeWithOverwrite will do the same as Merge except that non-empty dst attributes will be overriden by
// non-empty src attribute values.
// Deprecated: use Merge(…) with WithOverride
func MergeWithOverwrite(dst, src interface{}, opts ...func(*config)) error {
func MergeWithOverwrite(dst, src interface{}, opts ...func(*Config)) error {
return merge(dst, src, append(opts, WithOverride)...)
}
// WithTransformers adds transformers to merge, allowing to customize the merging of some types.
func WithTransformers(transformers transformers) func(*config) {
return func(config *config) {
config.transformers = transformers
func WithTransformers(transformers Transformers) func(*Config) {
return func(config *Config) {
config.Transformers = transformers
}
}
// WithOverride will make merge override non-empty dst attributes with non-empty src attributes values.
func WithOverride(config *config) {
config.overwrite = true
func WithOverride(config *Config) {
config.Overwrite = true
}
func merge(dst, src interface{}, opts ...func(*config)) error {
// WithAppendSlice will make merge append slices instead of overwriting it
func WithAppendSlice(config *Config) {
config.AppendSlice = true
}
func merge(dst, src interface{}, opts ...func(*Config)) error {
var (
vDst, vSrc reflect.Value
err error
)
config := &config{}
config := &Config{}
for _, opt := range opts {
opt(config)
+1 -1
View File
@@ -184,7 +184,7 @@ func (nd NetDev) parseLine(rawLine string) (*NetDevLine, error) {
}
// Total aggregates the values across interfaces and returns a new NetDevLine.
// The Name field will be a sorted comma seperated list of interface names.
// The Name field will be a sorted comma separated list of interface names.
func (nd NetDev) Total() NetDevLine {
total := NetDevLine{}
+10 -10
View File
@@ -11,7 +11,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
// Package nfsd implements parsing of /proc/net/rpc/nfsd.
// Package nfs implements parsing of /proc/net/rpc/nfsd.
// Fields are documented in https://www.svennd.be/nfsd-stats-explained-procnetrpcnfsd/
package nfs
@@ -136,8 +136,8 @@ type ClientV4Stats struct {
Setattr uint64
FsInfo uint64
Renew uint64
SetClientId uint64
SetClientIdConfirm uint64
SetClientID uint64
SetClientIDConfirm uint64
Lock uint64
Lockt uint64
Locku uint64
@@ -156,13 +156,13 @@ type ClientV4Stats struct {
ReadDir uint64
ServerCaps uint64
DelegReturn uint64
GetAcl uint64
SetAcl uint64
GetACL uint64
SetACL uint64
FsLocations uint64
ReleaseLockowner uint64
Secinfo uint64
FsidPresent uint64
ExchangeId uint64
ExchangeID uint64
CreateSession uint64
DestroySession uint64
Sequence uint64
@@ -173,11 +173,11 @@ type ClientV4Stats struct {
LayoutCommit uint64
LayoutReturn uint64
SecinfoNoName uint64
TestStateId uint64
FreeStateId uint64
TestStateID uint64
FreeStateID uint64
GetDeviceList uint64
BindConnToSession uint64
DestroyClientId uint64
DestroyClientID uint64
Seek uint64
Allocate uint64
DeAllocate uint64
@@ -238,7 +238,7 @@ type V4Ops struct {
RelLockOwner uint64
}
// RPCStats models all stats from /proc/net/rpc/nfs.
// ClientRPCStats models all stats from /proc/net/rpc/nfs.
type ClientRPCStats struct {
Network Network
ClientRPC ClientRPC
+8 -8
View File
@@ -204,8 +204,8 @@ func parseClientV4Stats(v []uint64) (ClientV4Stats, error) {
Setattr: v[10],
FsInfo: v[11],
Renew: v[12],
SetClientId: v[13],
SetClientIdConfirm: v[14],
SetClientID: v[13],
SetClientIDConfirm: v[14],
Lock: v[15],
Lockt: v[16],
Locku: v[17],
@@ -224,13 +224,13 @@ func parseClientV4Stats(v []uint64) (ClientV4Stats, error) {
ReadDir: v[30],
ServerCaps: v[31],
DelegReturn: v[32],
GetAcl: v[33],
SetAcl: v[34],
GetACL: v[33],
SetACL: v[34],
FsLocations: v[35],
ReleaseLockowner: v[36],
Secinfo: v[37],
FsidPresent: v[38],
ExchangeId: v[39],
ExchangeID: v[39],
CreateSession: v[40],
DestroySession: v[41],
Sequence: v[42],
@@ -241,11 +241,11 @@ func parseClientV4Stats(v []uint64) (ClientV4Stats, error) {
LayoutCommit: v[47],
LayoutReturn: v[48],
SecinfoNoName: v[49],
TestStateId: v[50],
FreeStateId: v[51],
TestStateID: v[50],
FreeStateID: v[51],
GetDeviceList: v[52],
BindConnToSession: v[53],
DestroyClientId: v[54],
DestroyClientID: v[54],
Seek: v[55],
Allocate: v[56],
DeAllocate: v[57],
+30 -21
View File
@@ -94,32 +94,16 @@ func (entry Entry) log(level Level, msg string) {
entry.Level = level
entry.Message = msg
entry.Logger.mu.Lock()
err := entry.Logger.Hooks.Fire(level, &entry)
entry.Logger.mu.Unlock()
if err != nil {
entry.Logger.mu.Lock()
fmt.Fprintf(os.Stderr, "Failed to fire hook: %v\n", err)
entry.Logger.mu.Unlock()
}
entry.fireHooks()
buffer = bufferPool.Get().(*bytes.Buffer)
buffer.Reset()
defer bufferPool.Put(buffer)
entry.Buffer = buffer
serialized, err := entry.Logger.Formatter.Format(&entry)
entry.write()
entry.Buffer = nil
if err != nil {
entry.Logger.mu.Lock()
fmt.Fprintf(os.Stderr, "Failed to obtain reader, %v\n", err)
entry.Logger.mu.Unlock()
} else {
entry.Logger.mu.Lock()
_, err = entry.Logger.Out.Write(serialized)
if err != nil {
fmt.Fprintf(os.Stderr, "Failed to write to log, %v\n", err)
}
entry.Logger.mu.Unlock()
}
// To avoid Entry#log() returning a value that only would make sense for
// panic() to use in Entry#Panic(), we avoid the allocation by checking
@@ -129,6 +113,31 @@ func (entry Entry) log(level Level, msg string) {
}
}
// This function is not declared with a pointer value because otherwise
// race conditions will occur when using multiple goroutines
func (entry Entry) fireHooks() {
entry.Logger.mu.Lock()
defer entry.Logger.mu.Unlock()
err := entry.Logger.Hooks.Fire(entry.Level, &entry)
if err != nil {
fmt.Fprintf(os.Stderr, "Failed to fire hook: %v\n", err)
}
}
func (entry *Entry) write() {
serialized, err := entry.Logger.Formatter.Format(entry)
entry.Logger.mu.Lock()
defer entry.Logger.mu.Unlock()
if err != nil {
fmt.Fprintf(os.Stderr, "Failed to obtain reader, %v\n", err)
} else {
_, err = entry.Logger.Out.Write(serialized)
if err != nil {
fmt.Fprintf(os.Stderr, "Failed to write to log, %v\n", err)
}
}
}
func (entry *Entry) Debug(args ...interface{}) {
if entry.Logger.level() >= DebugLevel {
entry.log(DebugLevel, fmt.Sprint(args...))
+1 -1
View File
@@ -1,5 +1,5 @@
// +build darwin freebsd openbsd netbsd dragonfly
// +build !appengine
// +build !appengine,!gopherjs
package logrus
+1 -1
View File
@@ -1,4 +1,4 @@
// +build appengine
// +build appengine gopherjs
package logrus
+1 -1
View File
@@ -1,4 +1,4 @@
// +build !appengine
// +build !appengine,!gopherjs
package logrus
+1 -1
View File
@@ -3,7 +3,7 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build !appengine
// +build !appengine,!gopherjs
package logrus
+8 -17
View File
@@ -16,14 +16,14 @@ func legacyArgs(cmd *Command, args []string) error {
return nil
}
// root command with subcommands, do subcommand checking
// root command with subcommands, do subcommand checking.
if !cmd.HasParent() && len(args) > 0 {
return fmt.Errorf("unknown command %q for %q%s", args[0], cmd.CommandPath(), cmd.findSuggestions(args[0]))
}
return nil
}
// NoArgs returns an error if any args are included
// NoArgs returns an error if any args are included.
func NoArgs(cmd *Command, args []string) error {
if len(args) > 0 {
return fmt.Errorf("unknown command %q for %q", args[0], cmd.CommandPath())
@@ -31,7 +31,7 @@ func NoArgs(cmd *Command, args []string) error {
return nil
}
// OnlyValidArgs returns an error if any args are not in the list of ValidArgs
// OnlyValidArgs returns an error if any args are not in the list of ValidArgs.
func OnlyValidArgs(cmd *Command, args []string) error {
if len(cmd.ValidArgs) > 0 {
for _, v := range args {
@@ -43,21 +43,12 @@ func OnlyValidArgs(cmd *Command, args []string) error {
return nil
}
func stringInSlice(a string, list []string) bool {
for _, b := range list {
if b == a {
return true
}
}
return false
}
// ArbitraryArgs never returns an error
// ArbitraryArgs never returns an error.
func ArbitraryArgs(cmd *Command, args []string) error {
return nil
}
// MinimumNArgs returns an error if there is not at least N args
// MinimumNArgs returns an error if there is not at least N args.
func MinimumNArgs(n int) PositionalArgs {
return func(cmd *Command, args []string) error {
if len(args) < n {
@@ -67,7 +58,7 @@ func MinimumNArgs(n int) PositionalArgs {
}
}
// MaximumNArgs returns an error if there are more than N args
// MaximumNArgs returns an error if there are more than N args.
func MaximumNArgs(n int) PositionalArgs {
return func(cmd *Command, args []string) error {
if len(args) > n {
@@ -77,7 +68,7 @@ func MaximumNArgs(n int) PositionalArgs {
}
}
// ExactArgs returns an error if there are not exactly n args
// ExactArgs returns an error if there are not exactly n args.
func ExactArgs(n int) PositionalArgs {
return func(cmd *Command, args []string) error {
if len(args) != n {
@@ -87,7 +78,7 @@ func ExactArgs(n int) PositionalArgs {
}
}
// RangeArgs returns an error if the number of args is not within the expected range
// RangeArgs returns an error if the number of args is not within the expected range.
func RangeArgs(min int, max int) PositionalArgs {
return func(cmd *Command, args []string) error {
if len(args) < min || len(args) > max {
+78 -60
View File
@@ -21,8 +21,8 @@ const (
func writePreamble(buf *bytes.Buffer, name string) {
buf.WriteString(fmt.Sprintf("# bash completion for %-36s -*- shell-script -*-\n", name))
buf.WriteString(`
__debug()
buf.WriteString(fmt.Sprintf(`
__%[1]s_debug()
{
if [[ -n ${BASH_COMP_DEBUG_FILE} ]]; then
echo "$*" >> "${BASH_COMP_DEBUG_FILE}"
@@ -31,13 +31,13 @@ __debug()
# Homebrew on Macs have version 1.3 of bash-completion which doesn't include
# _init_completion. This is a very minimal version of that function.
__my_init_completion()
__%[1]s_init_completion()
{
COMPREPLY=()
_get_comp_words_by_ref "$@" cur prev words cword
}
__index_of_word()
__%[1]s_index_of_word()
{
local w word=$1
shift
@@ -49,7 +49,7 @@ __index_of_word()
index=-1
}
__contains_word()
__%[1]s_contains_word()
{
local w word=$1; shift
for w in "$@"; do
@@ -58,9 +58,9 @@ __contains_word()
return 1
}
__handle_reply()
__%[1]s_handle_reply()
{
__debug "${FUNCNAME[0]}"
__%[1]s_debug "${FUNCNAME[0]}"
case $cur in
-*)
if [[ $(type -t compopt) = "builtin" ]]; then
@@ -85,7 +85,7 @@ __handle_reply()
local index flag
flag="${cur%%=*}"
__index_of_word "${flag}" "${flags_with_completion[@]}"
__%[1]s_index_of_word "${flag}" "${flags_with_completion[@]}"
COMPREPLY=()
if [[ ${index} -ge 0 ]]; then
PREFIX=""
@@ -103,7 +103,7 @@ __handle_reply()
# check if we are handling a flag with special work handling
local index
__index_of_word "${prev}" "${flags_with_completion[@]}"
__%[1]s_index_of_word "${prev}" "${flags_with_completion[@]}"
if [[ ${index} -ge 0 ]]; then
${flags_completion[${index}]}
return
@@ -136,24 +136,30 @@ __handle_reply()
if declare -F __ltrim_colon_completions >/dev/null; then
__ltrim_colon_completions "$cur"
fi
# If there is only 1 completion and it is a flag with an = it will be completed
# but we don't want a space after the =
if [[ "${#COMPREPLY[@]}" -eq "1" ]] && [[ $(type -t compopt) = "builtin" ]] && [[ "${COMPREPLY[0]}" == --*= ]]; then
compopt -o nospace
fi
}
# The arguments should be in the form "ext1|ext2|extn"
__handle_filename_extension_flag()
__%[1]s_handle_filename_extension_flag()
{
local ext="$1"
_filedir "@(${ext})"
}
__handle_subdirs_in_dir_flag()
__%[1]s_handle_subdirs_in_dir_flag()
{
local dir="$1"
pushd "${dir}" >/dev/null 2>&1 && _filedir -d && popd >/dev/null 2>&1
}
__handle_flag()
__%[1]s_handle_flag()
{
__debug "${FUNCNAME[0]}: c is $c words[c] is ${words[c]}"
__%[1]s_debug "${FUNCNAME[0]}: c is $c words[c] is ${words[c]}"
# if a command required a flag, and we found it, unset must_have_one_flag()
local flagname=${words[c]}
@@ -164,27 +170,30 @@ __handle_flag()
flagname=${flagname%%=*} # strip everything after the =
flagname="${flagname}=" # but put the = back
fi
__debug "${FUNCNAME[0]}: looking for ${flagname}"
if __contains_word "${flagname}" "${must_have_one_flag[@]}"; then
__%[1]s_debug "${FUNCNAME[0]}: looking for ${flagname}"
if __%[1]s_contains_word "${flagname}" "${must_have_one_flag[@]}"; then
must_have_one_flag=()
fi
# if you set a flag which only applies to this command, don't show subcommands
if __contains_word "${flagname}" "${local_nonpersistent_flags[@]}"; then
if __%[1]s_contains_word "${flagname}" "${local_nonpersistent_flags[@]}"; then
commands=()
fi
# keep flag value with flagname as flaghash
if [ -n "${flagvalue}" ] ; then
flaghash[${flagname}]=${flagvalue}
elif [ -n "${words[ $((c+1)) ]}" ] ; then
flaghash[${flagname}]=${words[ $((c+1)) ]}
else
flaghash[${flagname}]="true" # pad "true" for bool flag
# flaghash variable is an associative array which is only supported in bash > 3.
if [[ -z "${BASH_VERSION}" || "${BASH_VERSINFO[0]}" -gt 3 ]]; then
if [ -n "${flagvalue}" ] ; then
flaghash[${flagname}]=${flagvalue}
elif [ -n "${words[ $((c+1)) ]}" ] ; then
flaghash[${flagname}]=${words[ $((c+1)) ]}
else
flaghash[${flagname}]="true" # pad "true" for bool flag
fi
fi
# skip the argument to a two word flag
if __contains_word "${words[c]}" "${two_word_flags[@]}"; then
if __%[1]s_contains_word "${words[c]}" "${two_word_flags[@]}"; then
c=$((c+1))
# if we are looking for a flags value, don't show commands
if [[ $c -eq $cword ]]; then
@@ -196,13 +205,13 @@ __handle_flag()
}
__handle_noun()
__%[1]s_handle_noun()
{
__debug "${FUNCNAME[0]}: c is $c words[c] is ${words[c]}"
__%[1]s_debug "${FUNCNAME[0]}: c is $c words[c] is ${words[c]}"
if __contains_word "${words[c]}" "${must_have_one_noun[@]}"; then
if __%[1]s_contains_word "${words[c]}" "${must_have_one_noun[@]}"; then
must_have_one_noun=()
elif __contains_word "${words[c]}" "${noun_aliases[@]}"; then
elif __%[1]s_contains_word "${words[c]}" "${noun_aliases[@]}"; then
must_have_one_noun=()
fi
@@ -210,45 +219,45 @@ __handle_noun()
c=$((c+1))
}
__handle_command()
__%[1]s_handle_command()
{
__debug "${FUNCNAME[0]}: c is $c words[c] is ${words[c]}"
__%[1]s_debug "${FUNCNAME[0]}: c is $c words[c] is ${words[c]}"
local next_command
if [[ -n ${last_command} ]]; then
next_command="_${last_command}_${words[c]//:/__}"
else
if [[ $c -eq 0 ]]; then
next_command="_$(basename "${words[c]//:/__}")"
next_command="_%[1]s_root_command"
else
next_command="_${words[c]//:/__}"
fi
fi
c=$((c+1))
__debug "${FUNCNAME[0]}: looking for ${next_command}"
__%[1]s_debug "${FUNCNAME[0]}: looking for ${next_command}"
declare -F "$next_command" >/dev/null && $next_command
}
__handle_word()
__%[1]s_handle_word()
{
if [[ $c -ge $cword ]]; then
__handle_reply
__%[1]s_handle_reply
return
fi
__debug "${FUNCNAME[0]}: c is $c words[c] is ${words[c]}"
__%[1]s_debug "${FUNCNAME[0]}: c is $c words[c] is ${words[c]}"
if [[ "${words[c]}" == -* ]]; then
__handle_flag
elif __contains_word "${words[c]}" "${commands[@]}"; then
__handle_command
elif [[ $c -eq 0 ]] && __contains_word "$(basename "${words[c]}")" "${commands[@]}"; then
__handle_command
__%[1]s_handle_flag
elif __%[1]s_contains_word "${words[c]}" "${commands[@]}"; then
__%[1]s_handle_command
elif [[ $c -eq 0 ]]; then
__%[1]s_handle_command
else
__handle_noun
__%[1]s_handle_noun
fi
__handle_word
__%[1]s_handle_word
}
`)
`, name))
}
func writePostscript(buf *bytes.Buffer, name string) {
@@ -260,7 +269,7 @@ func writePostscript(buf *bytes.Buffer, name string) {
if declare -F _init_completion >/dev/null 2>&1; then
_init_completion -s || return
else
__my_init_completion -n "=" || return
__%[1]s_init_completion -n "=" || return
fi
local c=0
@@ -269,13 +278,13 @@ func writePostscript(buf *bytes.Buffer, name string) {
local local_nonpersistent_flags=()
local flags_with_completion=()
local flags_completion=()
local commands=("%s")
local commands=("%[1]s")
local must_have_one_flag=()
local must_have_one_noun=()
local last_command
local nouns=()
__handle_word
__%[1]s_handle_word
}
`, name))
@@ -300,7 +309,7 @@ func writeCommands(buf *bytes.Buffer, cmd *Command) {
buf.WriteString("\n")
}
func writeFlagHandler(buf *bytes.Buffer, name string, annotations map[string][]string) {
func writeFlagHandler(buf *bytes.Buffer, name string, annotations map[string][]string, cmd *Command) {
for key, value := range annotations {
switch key {
case BashCompFilenameExt:
@@ -308,7 +317,7 @@ func writeFlagHandler(buf *bytes.Buffer, name string, annotations map[string][]s
var ext string
if len(value) > 0 {
ext = "__handle_filename_extension_flag " + strings.Join(value, "|")
ext = fmt.Sprintf("__%s_handle_filename_extension_flag ", cmd.Root().Name()) + strings.Join(value, "|")
} else {
ext = "_filedir"
}
@@ -326,7 +335,7 @@ func writeFlagHandler(buf *bytes.Buffer, name string, annotations map[string][]s
var ext string
if len(value) == 1 {
ext = "__handle_subdirs_in_dir_flag " + value[0]
ext = fmt.Sprintf("__%s_handle_subdirs_in_dir_flag ", cmd.Root().Name()) + value[0]
} else {
ext = "_filedir -d"
}
@@ -335,7 +344,7 @@ func writeFlagHandler(buf *bytes.Buffer, name string, annotations map[string][]s
}
}
func writeShortFlag(buf *bytes.Buffer, flag *pflag.Flag) {
func writeShortFlag(buf *bytes.Buffer, flag *pflag.Flag, cmd *Command) {
name := flag.Shorthand
format := " "
if len(flag.NoOptDefVal) == 0 {
@@ -343,10 +352,10 @@ func writeShortFlag(buf *bytes.Buffer, flag *pflag.Flag) {
}
format += "flags+=(\"-%s\")\n"
buf.WriteString(fmt.Sprintf(format, name))
writeFlagHandler(buf, "-"+name, flag.Annotations)
writeFlagHandler(buf, "-"+name, flag.Annotations, cmd)
}
func writeFlag(buf *bytes.Buffer, flag *pflag.Flag) {
func writeFlag(buf *bytes.Buffer, flag *pflag.Flag, cmd *Command) {
name := flag.Name
format := " flags+=(\"--%s"
if len(flag.NoOptDefVal) == 0 {
@@ -354,7 +363,7 @@ func writeFlag(buf *bytes.Buffer, flag *pflag.Flag) {
}
format += "\")\n"
buf.WriteString(fmt.Sprintf(format, name))
writeFlagHandler(buf, "--"+name, flag.Annotations)
writeFlagHandler(buf, "--"+name, flag.Annotations, cmd)
}
func writeLocalNonPersistentFlag(buf *bytes.Buffer, flag *pflag.Flag) {
@@ -380,9 +389,9 @@ func writeFlags(buf *bytes.Buffer, cmd *Command) {
if nonCompletableFlag(flag) {
return
}
writeFlag(buf, flag)
writeFlag(buf, flag, cmd)
if len(flag.Shorthand) > 0 {
writeShortFlag(buf, flag)
writeShortFlag(buf, flag, cmd)
}
if localNonPersistentFlags.Lookup(flag.Name) != nil {
writeLocalNonPersistentFlag(buf, flag)
@@ -392,9 +401,9 @@ func writeFlags(buf *bytes.Buffer, cmd *Command) {
if nonCompletableFlag(flag) {
return
}
writeFlag(buf, flag)
writeFlag(buf, flag, cmd)
if len(flag.Shorthand) > 0 {
writeShortFlag(buf, flag)
writeShortFlag(buf, flag, cmd)
}
})
@@ -452,7 +461,13 @@ func gen(buf *bytes.Buffer, cmd *Command) {
commandName := cmd.CommandPath()
commandName = strings.Replace(commandName, " ", "_", -1)
commandName = strings.Replace(commandName, ":", "__", -1)
buf.WriteString(fmt.Sprintf("_%s()\n{\n", commandName))
if cmd.Root() == cmd {
buf.WriteString(fmt.Sprintf("_%s_root_command()\n{\n", commandName))
} else {
buf.WriteString(fmt.Sprintf("_%s()\n{\n", commandName))
}
buf.WriteString(fmt.Sprintf(" last_command=%q\n", commandName))
writeCommands(buf, cmd)
writeFlags(buf, cmd)
@@ -491,17 +506,20 @@ func (c *Command) GenBashCompletionFile(filename string) error {
return c.GenBashCompletion(outFile)
}
// MarkFlagRequired adds the BashCompOneRequiredFlag annotation to the named flag, if it exists.
// MarkFlagRequired adds the BashCompOneRequiredFlag annotation to the named flag if it exists,
// and causes your command to report an error if invoked without the flag.
func (c *Command) MarkFlagRequired(name string) error {
return MarkFlagRequired(c.Flags(), name)
}
// MarkPersistentFlagRequired adds the BashCompOneRequiredFlag annotation to the named persistent flag, if it exists.
// MarkPersistentFlagRequired adds the BashCompOneRequiredFlag annotation to the named persistent flag if it exists,
// and causes your command to report an error if invoked without the flag.
func (c *Command) MarkPersistentFlagRequired(name string) error {
return MarkFlagRequired(c.PersistentFlags(), name)
}
// MarkFlagRequired adds the BashCompOneRequiredFlag annotation to the named flag in the flag set, if it exists.
// MarkFlagRequired adds the BashCompOneRequiredFlag annotation to the named flag if it exists,
// and causes your command to report an error if invoked without the flag.
func MarkFlagRequired(flags *pflag.FlagSet, name string) error {
return flags.SetAnnotation(name, BashCompOneRequiredFlag, []string{"true"})
}
+11 -1
View File
@@ -70,7 +70,8 @@ func AddTemplateFuncs(tmplFuncs template.FuncMap) {
}
}
// OnInitialize takes a series of func() arguments and appends them to a slice of func().
// OnInitialize sets the passed functions to be run when each command's
// Execute method is called.
func OnInitialize(y ...func()) {
initializers = append(initializers, y...)
}
@@ -188,3 +189,12 @@ func ld(s, t string, ignoreCase bool) int {
}
return d[len(s)][len(t)]
}
func stringInSlice(a string, list []string) bool {
for _, b := range list {
if b == a {
return true
}
}
return false
}
+109 -11
View File
@@ -75,6 +75,11 @@ type Command struct {
// group commands.
Annotations map[string]string
// Version defines the version for this command. If this value is non-empty and the command does not
// define a "version" flag, a "version" boolean flag will be added to the command and, if specified,
// will print content of the "Version" variable.
Version string
// The *Run functions are executed in the following order:
// * PersistentPreRun()
// * PreRun()
@@ -118,6 +123,10 @@ type Command struct {
// will be printed by generating docs for this command.
DisableAutoGenTag bool
// DisableFlagsInUseLine will disable the addition of [flags] to the usage
// line of a command when printing help or generating docs
DisableFlagsInUseLine bool
// DisableSuggestions disables the suggestions based on Levenshtein distance
// that go along with 'unknown command' messages.
DisableSuggestions bool
@@ -138,6 +147,11 @@ type Command struct {
commandsMaxNameLen int
// commandsAreSorted defines, if command slice are sorted or not.
commandsAreSorted bool
// commandCalledAs is the name or alias value used to call this command.
commandCalledAs struct {
name string
called bool
}
// args is actual args parsed from flags.
args []string
@@ -173,6 +187,8 @@ type Command struct {
// helpCommand is command with usage 'help'. If it's not defined by user,
// cobra uses default help command.
helpCommand *Command
// versionTemplate is the version template defined by user.
versionTemplate string
}
// SetArgs sets arguments for the command. It is set to os.Args[1:] by default, if desired, can be overridden
@@ -218,6 +234,11 @@ func (c *Command) SetHelpTemplate(s string) {
c.helpTemplate = s
}
// SetVersionTemplate sets version template to be used. Application can use it to set custom template.
func (c *Command) SetVersionTemplate(s string) {
c.versionTemplate = s
}
// SetGlobalNormalizationFunc sets a normalization function to all flag sets and also to child commands.
// The user should not have a cyclic dependency on commands.
func (c *Command) SetGlobalNormalizationFunc(n func(f *flag.FlagSet, name string) flag.NormalizedName) {
@@ -407,6 +428,19 @@ func (c *Command) HelpTemplate() string {
{{end}}{{if or .Runnable .HasSubCommands}}{{.UsageString}}{{end}}`
}
// VersionTemplate return version template for the command.
func (c *Command) VersionTemplate() string {
if c.versionTemplate != "" {
return c.versionTemplate
}
if c.HasParent() {
return c.parent.VersionTemplate()
}
return `{{with .Name}}{{printf "%s " .}}{{end}}{{printf "version %s" .Version}}
`
}
func hasNoOptDefVal(name string, fs *flag.FlagSet) bool {
flag := fs.Lookup(name)
if flag == nil {
@@ -441,6 +475,9 @@ Loop:
s := args[0]
args = args[1:]
switch {
case s == "--":
// "--" terminates the flags
break Loop
case strings.HasPrefix(s, "--") && !strings.Contains(s, "=") && !hasNoOptDefVal(s[2:], flags):
// If '--flag arg' then
// delete arg from args.
@@ -528,6 +565,7 @@ func (c *Command) findNext(next string) *Command {
matches := make([]*Command, 0)
for _, cmd := range c.commands {
if cmd.Name() == next || cmd.HasAlias(next) {
cmd.commandCalledAs.name = next
return cmd
}
if EnablePrefixMatching && cmd.hasNameOrAliasPrefix(next) {
@@ -538,6 +576,7 @@ func (c *Command) findNext(next string) *Command {
if len(matches) == 1 {
return matches[0]
}
return nil
}
@@ -621,10 +660,8 @@ func (c *Command) Root() *Command {
return c
}
// ArgsLenAtDash will return the length of f.Args at the moment when a -- was
// found during arg parsing. This allows your program to know which args were
// before the -- and which came after. (Description from
// https://godoc.org/github.com/spf13/pflag#FlagSet.ArgsLenAtDash).
// ArgsLenAtDash will return the length of c.Flags().Args at the moment
// when a -- was found during args parsing.
func (c *Command) ArgsLenAtDash() int {
return c.Flags().ArgsLenAtDash()
}
@@ -638,9 +675,10 @@ func (c *Command) execute(a []string) (err error) {
c.Printf("Command %q is deprecated, %s\n", c.Name(), c.Deprecated)
}
// initialize help flag as the last point possible to allow for user
// initialize help and version flag at the last point possible to allow for user
// overriding
c.InitDefaultHelpFlag()
c.InitDefaultVersionFlag()
err = c.ParseFlags(a)
if err != nil {
@@ -657,7 +695,27 @@ func (c *Command) execute(a []string) (err error) {
return err
}
if helpVal || !c.Runnable() {
if helpVal {
return flag.ErrHelp
}
// for back-compat, only add version flag behavior if version is defined
if c.Version != "" {
versionVal, err := c.Flags().GetBool("version")
if err != nil {
c.Println("\"version\" flag declared as non-bool. Please correct your code")
return err
}
if versionVal {
err := tmpl(c.OutOrStdout(), c.VersionTemplate(), c)
if err != nil {
c.Println(err)
}
return err
}
}
if !c.Runnable() {
return flag.ErrHelp
}
@@ -780,6 +838,11 @@ func (c *Command) ExecuteC() (cmd *Command, err error) {
return c, err
}
cmd.commandCalledAs.called = true
if cmd.commandCalledAs.name == "" {
cmd.commandCalledAs.name = cmd.Name()
}
err = cmd.execute(flags)
if err != nil {
// Always show help if requested, even if SilenceErrors is in
@@ -825,7 +888,7 @@ func (c *Command) validateRequiredFlags() error {
})
if len(missingFlagNames) > 0 {
return fmt.Errorf(`Required flag(s) "%s" have/has not been set`, strings.Join(missingFlagNames, `", "`))
return fmt.Errorf(`required flag(s) "%s" not set`, strings.Join(missingFlagNames, `", "`))
}
return nil
}
@@ -846,6 +909,27 @@ func (c *Command) InitDefaultHelpFlag() {
}
}
// InitDefaultVersionFlag adds default version flag to c.
// It is called automatically by executing the c.
// If c already has a version flag, it will do nothing.
// If c.Version is empty, it will do nothing.
func (c *Command) InitDefaultVersionFlag() {
if c.Version == "" {
return
}
c.mergePersistentFlags()
if c.Flags().Lookup("version") == nil {
usage := "version for "
if c.Name() == "" {
usage += "this command"
} else {
usage += c.Name()
}
c.Flags().Bool("version", false, usage)
}
}
// InitDefaultHelpCmd adds default help command to c.
// It is called automatically by executing the c or by calling help and usage.
// If c already has help command or c has no subcommands, it will do nothing.
@@ -877,7 +961,7 @@ Simply type ` + c.Name() + ` help [path to command] for full details.`,
c.AddCommand(c.helpCommand)
}
// ResetCommands used for testing.
// ResetCommands delete parent, subcommand and help command from c.
func (c *Command) ResetCommands() {
c.parent = nil
c.commands = nil
@@ -996,6 +1080,9 @@ func (c *Command) UseLine() string {
} else {
useline = c.Use
}
if c.DisableFlagsInUseLine {
return useline
}
if c.HasAvailableFlags() && !strings.Contains(useline, "[flags]") {
useline += " [flags]"
}
@@ -1063,14 +1150,25 @@ func (c *Command) HasAlias(s string) bool {
return false
}
// CalledAs returns the command name or alias that was used to invoke
// this command or an empty string if the command has not been called.
func (c *Command) CalledAs() string {
if c.commandCalledAs.called {
return c.commandCalledAs.name
}
return ""
}
// hasNameOrAliasPrefix returns true if the Name or any of aliases start
// with prefix
func (c *Command) hasNameOrAliasPrefix(prefix string) bool {
if strings.HasPrefix(c.Name(), prefix) {
c.commandCalledAs.name = c.Name()
return true
}
for _, alias := range c.Aliases {
if strings.HasPrefix(alias, prefix) {
c.commandCalledAs.name = alias
return true
}
}
@@ -1163,7 +1261,7 @@ func (c *Command) HasAvailableSubCommands() bool {
}
}
// the command either has no sub comamnds, or no available (non deprecated/help/hidden)
// the command either has no sub commands, or no available (non deprecated/help/hidden)
// sub commands
return false
}
@@ -1173,7 +1271,7 @@ func (c *Command) HasParent() bool {
return c.parent != nil
}
// GlobalNormalizationFunc returns the global normalization function or nil if doesn't exists.
// GlobalNormalizationFunc returns the global normalization function or nil if it doesn't exist.
func (c *Command) GlobalNormalizationFunc() func(f *flag.FlagSet, name string) flag.NormalizedName {
return c.globNormFunc
}
@@ -1273,7 +1371,7 @@ func (c *Command) PersistentFlags() *flag.FlagSet {
return c.pflags
}
// ResetFlags is used in testing.
// ResetFlags deletes all flags from command.
func (c *Command) ResetFlags() {
c.flagErrorBuf = new(bytes.Buffer)
c.flagErrorBuf.Reset()