Update Godeps

Signed-off-by: Jana Radhakrishnan <mrjana@docker.com>
This commit is contained in:
Jana Radhakrishnan 2016-09-07 13:46:57 -07:00
parent f5516d817d
commit 68ed10ff07
45 changed files with 1239 additions and 440 deletions

View file

@ -1,7 +1,6 @@
{
"ImportPath": "github.com/docker/libnetwork",
"GoVersion": "go1.5",
"GodepVersion": "v74",
"Packages": [
"./..."
],
@ -81,105 +80,110 @@
"Comment": "v1-26-gef32fa3",
"Rev": "ef32fa3046d9f249d399f98ebaf9be944430fd1d"
},
{
"ImportPath": "github.com/docker/docker/api/types/filters",
"Comment": "docs-v1.12.0-rc4-2016-07-15-1316-g426a0af",
"Rev": "426a0af0759798d8e3332b38236ee40df6d14798"
},
{
"ImportPath": "github.com/docker/docker/api/types/network",
"Comment": "docs-v1.12.0-rc4-2016-07-15-1316-g426a0af",
"Rev": "426a0af0759798d8e3332b38236ee40df6d14798"
},
{
"ImportPath": "github.com/docker/docker/api/types/versions",
"Comment": "docs-v1.12.0-rc4-2016-07-15-1316-g426a0af",
"Rev": "426a0af0759798d8e3332b38236ee40df6d14798"
},
{
"ImportPath": "github.com/docker/docker/opts",
"Comment": "v1.4.1-12227-g86a7632",
"Rev": "86a7632d63bdddb95aaf1472648056a4fb737d38"
"Comment": "docs-v1.12.0-rc4-2016-07-15-1316-g426a0af",
"Rev": "426a0af0759798d8e3332b38236ee40df6d14798"
},
{
"ImportPath": "github.com/docker/docker/pkg/discovery",
"Comment": "v1.4.1-12227-g86a7632",
"Rev": "86a7632d63bdddb95aaf1472648056a4fb737d38"
"Comment": "docs-v1.12.0-rc4-2016-07-15-1316-g426a0af",
"Rev": "426a0af0759798d8e3332b38236ee40df6d14798"
},
{
"ImportPath": "github.com/docker/docker/pkg/discovery/kv",
"Comment": "v1.4.1-12227-g86a7632",
"Rev": "86a7632d63bdddb95aaf1472648056a4fb737d38"
"Comment": "docs-v1.12.0-rc4-2016-07-15-1316-g426a0af",
"Rev": "426a0af0759798d8e3332b38236ee40df6d14798"
},
{
"ImportPath": "github.com/docker/docker/pkg/homedir",
"Comment": "v1.4.1-12227-g86a7632",
"Rev": "86a7632d63bdddb95aaf1472648056a4fb737d38"
"Comment": "docs-v1.12.0-rc4-2016-07-15-1316-g426a0af",
"Rev": "426a0af0759798d8e3332b38236ee40df6d14798"
},
{
"ImportPath": "github.com/docker/docker/pkg/ioutils",
"Comment": "v1.4.1-12227-g86a7632",
"Rev": "86a7632d63bdddb95aaf1472648056a4fb737d38"
"Comment": "docs-v1.12.0-rc4-2016-07-15-1316-g426a0af",
"Rev": "426a0af0759798d8e3332b38236ee40df6d14798"
},
{
"ImportPath": "github.com/docker/docker/pkg/longpath",
"Comment": "v1.4.1-12227-g86a7632",
"Rev": "86a7632d63bdddb95aaf1472648056a4fb737d38"
},
{
"ImportPath": "github.com/docker/docker/pkg/mflag",
"Comment": "v1.4.1-12227-g86a7632",
"Rev": "86a7632d63bdddb95aaf1472648056a4fb737d38"
"Comment": "docs-v1.12.0-rc4-2016-07-15-1316-g426a0af",
"Rev": "426a0af0759798d8e3332b38236ee40df6d14798"
},
{
"ImportPath": "github.com/docker/docker/pkg/mount",
"Comment": "v1.4.1-12227-g86a7632",
"Rev": "86a7632d63bdddb95aaf1472648056a4fb737d38"
"Comment": "docs-v1.12.0-rc4-2016-07-15-1316-g426a0af",
"Rev": "426a0af0759798d8e3332b38236ee40df6d14798"
},
{
"ImportPath": "github.com/docker/docker/pkg/parsers/kernel",
"Comment": "v1.4.1-12227-g86a7632",
"Rev": "86a7632d63bdddb95aaf1472648056a4fb737d38"
"Comment": "docs-v1.12.0-rc4-2016-07-15-1316-g426a0af",
"Rev": "426a0af0759798d8e3332b38236ee40df6d14798"
},
{
"ImportPath": "github.com/docker/docker/pkg/plugins",
"Comment": "v1.4.1-12227-g86a7632",
"Rev": "86a7632d63bdddb95aaf1472648056a4fb737d38"
"Comment": "docs-v1.12.0-rc4-2016-07-15-1316-g426a0af",
"Rev": "426a0af0759798d8e3332b38236ee40df6d14798"
},
{
"ImportPath": "github.com/docker/docker/pkg/plugins/transport",
"Comment": "v1.4.1-12227-g86a7632",
"Rev": "86a7632d63bdddb95aaf1472648056a4fb737d38"
"Comment": "docs-v1.12.0-rc4-2016-07-15-1316-g426a0af",
"Rev": "426a0af0759798d8e3332b38236ee40df6d14798"
},
{
"ImportPath": "github.com/docker/docker/pkg/random",
"Comment": "v1.4.1-12227-g86a7632",
"Rev": "86a7632d63bdddb95aaf1472648056a4fb737d38"
"Comment": "docs-v1.12.0-rc4-2016-07-15-1316-g426a0af",
"Rev": "426a0af0759798d8e3332b38236ee40df6d14798"
},
{
"ImportPath": "github.com/docker/docker/pkg/reexec",
"Comment": "v1.4.1-12227-g86a7632",
"Rev": "86a7632d63bdddb95aaf1472648056a4fb737d38"
"Comment": "docs-v1.12.0-rc4-2016-07-15-1316-g426a0af",
"Rev": "426a0af0759798d8e3332b38236ee40df6d14798"
},
{
"ImportPath": "github.com/docker/docker/pkg/signal",
"Comment": "v1.4.1-12227-g86a7632",
"Rev": "86a7632d63bdddb95aaf1472648056a4fb737d38"
"Comment": "docs-v1.12.0-rc4-2016-07-15-1316-g426a0af",
"Rev": "426a0af0759798d8e3332b38236ee40df6d14798"
},
{
"ImportPath": "github.com/docker/docker/pkg/stringid",
"Comment": "v1.4.1-12227-g86a7632",
"Rev": "86a7632d63bdddb95aaf1472648056a4fb737d38"
"Comment": "docs-v1.12.0-rc4-2016-07-15-1316-g426a0af",
"Rev": "426a0af0759798d8e3332b38236ee40df6d14798"
},
{
"ImportPath": "github.com/docker/docker/pkg/symlink",
"Comment": "v1.4.1-12227-g86a7632",
"Rev": "86a7632d63bdddb95aaf1472648056a4fb737d38"
"Comment": "docs-v1.12.0-rc4-2016-07-15-1316-g426a0af",
"Rev": "426a0af0759798d8e3332b38236ee40df6d14798"
},
{
"ImportPath": "github.com/docker/docker/pkg/system",
"Comment": "v1.4.1-12227-g86a7632",
"Rev": "86a7632d63bdddb95aaf1472648056a4fb737d38"
"Comment": "docs-v1.12.0-rc4-2016-07-15-1316-g426a0af",
"Rev": "426a0af0759798d8e3332b38236ee40df6d14798"
},
{
"ImportPath": "github.com/docker/docker/pkg/term",
"Comment": "v1.4.1-12227-g86a7632",
"Rev": "86a7632d63bdddb95aaf1472648056a4fb737d38"
"Comment": "docs-v1.12.0-rc4-2016-07-15-1316-g426a0af",
"Rev": "426a0af0759798d8e3332b38236ee40df6d14798"
},
{
"ImportPath": "github.com/docker/docker/pkg/term/windows",
"Comment": "v1.4.1-12227-g86a7632",
"Rev": "86a7632d63bdddb95aaf1472648056a4fb737d38"
},
{
"ImportPath": "github.com/docker/engine-api/types/network",
"Comment": "v0.3.1-210-g25941ec",
"Rev": "25941ecf6e8351810e8530c60de8dda7d5e1baba"
"Comment": "docs-v1.12.0-rc4-2016-07-15-1316-g426a0af",
"Rev": "426a0af0759798d8e3332b38236ee40df6d14798"
},
{
"ImportPath": "github.com/docker/go-connections/sockets",
@ -289,6 +293,11 @@
"Comment": "v0.7.0-47-g598c548",
"Rev": "598c54895cc5a7b1a24a398d635e8c0ea0959870"
},
{
"ImportPath": "github.com/mattn/go-shellwords",
"Comment": "v1.0.0-1-g525bede",
"Rev": "525bedee691b5a8df547cb5cf9f86b7fb1883e24"
},
{
"ImportPath": "github.com/miekg/dns",
"Rev": "d27455715200c7d3e321a1e5cadb27c9ee0b0f02"

View file

@ -0,0 +1,307 @@
// Package filters provides helper function to parse and handle command line
// filter, used for example in docker ps or docker images commands.
package filters
import (
"encoding/json"
"errors"
"fmt"
"regexp"
"strings"
"github.com/docker/docker/api/types/versions"
)
// Args stores filter arguments as map key:{map key: bool}.
// It contains an aggregation of the map of arguments (which are in the form
// of -f 'key=value') based on the key, and stores values for the same key
// in a map with string keys and boolean values.
// e.g given -f 'label=label1=1' -f 'label=label2=2' -f 'image.name=ubuntu'
// the args will be {"image.name":{"ubuntu":true},"label":{"label1=1":true,"label2=2":true}}
type Args struct {
fields map[string]map[string]bool
}
// NewArgs initializes a new Args struct.
func NewArgs() Args {
return Args{fields: map[string]map[string]bool{}}
}
// ParseFlag parses the argument to the filter flag. Like
//
// `docker ps -f 'created=today' -f 'image.name=ubuntu*'`
//
// If prev map is provided, then it is appended to, and returned. By default a new
// map is created.
func ParseFlag(arg string, prev Args) (Args, error) {
filters := prev
if len(arg) == 0 {
return filters, nil
}
if !strings.Contains(arg, "=") {
return filters, ErrBadFormat
}
f := strings.SplitN(arg, "=", 2)
name := strings.ToLower(strings.TrimSpace(f[0]))
value := strings.TrimSpace(f[1])
filters.Add(name, value)
return filters, nil
}
// ErrBadFormat is an error returned in case of bad format for a filter.
var ErrBadFormat = errors.New("bad format of filter (expected name=value)")
// ToParam packs the Args into a string for easy transport from client to server.
func ToParam(a Args) (string, error) {
// this way we don't URL encode {}, just empty space
if a.Len() == 0 {
return "", nil
}
buf, err := json.Marshal(a.fields)
if err != nil {
return "", err
}
return string(buf), nil
}
// ToParamWithVersion packs the Args into a string for easy transport from client to server.
// The generated string will depend on the specified version (corresponding to the API version).
func ToParamWithVersion(version string, a Args) (string, error) {
// this way we don't URL encode {}, just empty space
if a.Len() == 0 {
return "", nil
}
// for daemons older than v1.10, filter must be of the form map[string][]string
buf := []byte{}
err := errors.New("")
if version != "" && versions.LessThan(version, "1.22") {
buf, err = json.Marshal(convertArgsToSlice(a.fields))
} else {
buf, err = json.Marshal(a.fields)
}
if err != nil {
return "", err
}
return string(buf), nil
}
// FromParam unpacks the filter Args.
func FromParam(p string) (Args, error) {
if len(p) == 0 {
return NewArgs(), nil
}
r := strings.NewReader(p)
d := json.NewDecoder(r)
m := map[string]map[string]bool{}
if err := d.Decode(&m); err != nil {
r.Seek(0, 0)
// Allow parsing old arguments in slice format.
// Because other libraries might be sending them in this format.
deprecated := map[string][]string{}
if deprecatedErr := d.Decode(&deprecated); deprecatedErr == nil {
m = deprecatedArgs(deprecated)
} else {
return NewArgs(), err
}
}
return Args{m}, nil
}
// Get returns the list of values associates with a field.
// It returns a slice of strings to keep backwards compatibility with old code.
func (filters Args) Get(field string) []string {
values := filters.fields[field]
if values == nil {
return make([]string, 0)
}
slice := make([]string, 0, len(values))
for key := range values {
slice = append(slice, key)
}
return slice
}
// Add adds a new value to a filter field.
func (filters Args) Add(name, value string) {
if _, ok := filters.fields[name]; ok {
filters.fields[name][value] = true
} else {
filters.fields[name] = map[string]bool{value: true}
}
}
// Del removes a value from a filter field.
func (filters Args) Del(name, value string) {
if _, ok := filters.fields[name]; ok {
delete(filters.fields[name], value)
}
}
// Len returns the number of fields in the arguments.
func (filters Args) Len() int {
return len(filters.fields)
}
// MatchKVList returns true if the values for the specified field matches the ones
// from the sources.
// e.g. given Args are {'label': {'label1=1','label2=1'}, 'image.name', {'ubuntu'}},
// field is 'label' and sources are {'label1': '1', 'label2': '2'}
// it returns true.
func (filters Args) MatchKVList(field string, sources map[string]string) bool {
fieldValues := filters.fields[field]
//do not filter if there is no filter set or cannot determine filter
if len(fieldValues) == 0 {
return true
}
if sources == nil || len(sources) == 0 {
return false
}
for name2match := range fieldValues {
testKV := strings.SplitN(name2match, "=", 2)
v, ok := sources[testKV[0]]
if !ok {
return false
}
if len(testKV) == 2 && testKV[1] != v {
return false
}
}
return true
}
// Match returns true if the values for the specified field matches the source string
// e.g. given Args are {'label': {'label1=1','label2=1'}, 'image.name', {'ubuntu'}},
// field is 'image.name' and source is 'ubuntu'
// it returns true.
func (filters Args) Match(field, source string) bool {
if filters.ExactMatch(field, source) {
return true
}
fieldValues := filters.fields[field]
for name2match := range fieldValues {
match, err := regexp.MatchString(name2match, source)
if err != nil {
continue
}
if match {
return true
}
}
return false
}
// ExactMatch returns true if the source matches exactly one of the filters.
func (filters Args) ExactMatch(field, source string) bool {
fieldValues, ok := filters.fields[field]
//do not filter if there is no filter set or cannot determine filter
if !ok || len(fieldValues) == 0 {
return true
}
// try to match full name value to avoid O(N) regular expression matching
return fieldValues[source]
}
// UniqueExactMatch returns true if there is only one filter and the source matches exactly this one.
func (filters Args) UniqueExactMatch(field, source string) bool {
fieldValues := filters.fields[field]
//do not filter if there is no filter set or cannot determine filter
if len(fieldValues) == 0 {
return true
}
if len(filters.fields[field]) != 1 {
return false
}
// try to match full name value to avoid O(N) regular expression matching
return fieldValues[source]
}
// FuzzyMatch returns true if the source matches exactly one of the filters,
// or the source has one of the filters as a prefix.
func (filters Args) FuzzyMatch(field, source string) bool {
if filters.ExactMatch(field, source) {
return true
}
fieldValues := filters.fields[field]
for prefix := range fieldValues {
if strings.HasPrefix(source, prefix) {
return true
}
}
return false
}
// Include returns true if the name of the field to filter is in the filters.
func (filters Args) Include(field string) bool {
_, ok := filters.fields[field]
return ok
}
// Validate ensures that all the fields in the filter are valid.
// It returns an error as soon as it finds an invalid field.
func (filters Args) Validate(accepted map[string]bool) error {
for name := range filters.fields {
if !accepted[name] {
return fmt.Errorf("Invalid filter '%s'", name)
}
}
return nil
}
// WalkValues iterates over the list of filtered values for a field.
// It stops the iteration if it finds an error and it returns that error.
func (filters Args) WalkValues(field string, op func(value string) error) error {
if _, ok := filters.fields[field]; !ok {
return nil
}
for v := range filters.fields[field] {
if err := op(v); err != nil {
return err
}
}
return nil
}
func deprecatedArgs(d map[string][]string) map[string]map[string]bool {
m := map[string]map[string]bool{}
for k, v := range d {
values := map[string]bool{}
for _, vv := range v {
values[vv] = true
}
m[k] = values
}
return m
}
func convertArgsToSlice(f map[string]map[string]bool) map[string][]string {
m := map[string][]string{}
for k, v := range f {
values := []string{}
for kk := range v {
if v[kk] {
values = append(values, kk)
}
}
m[k] = values
}
return m
}

View file

@ -0,0 +1,14 @@
## Legacy API type versions
This package includes types for legacy API versions. The stable version of the API types live in `api/types/*.go`.
Consider moving a type here when you need to keep backwards compatibility in the API. This legacy types are organized by the latest API version they appear in. For instance, types in the `v1p19` package are valid for API versions below or equal `1.19`. Types in the `v1p20` package are valid for the API version `1.20`, since the versions below that will use the legacy types in `v1p19`.
### Package name conventions
The package name convention is to use `v` as a prefix for the version number and `p`(patch) as a separator. We use this nomenclature due to a few restrictions in the Go package name convention:
1. We cannot use `.` because it's interpreted by the language, think of `v1.20.CallFunction`.
2. We cannot use `_` because golint complains about it. The code is actually valid, but it looks probably more weird: `v1_20.CallFunction`.
For instance, if you want to modify a type that was available in the version `1.21` of the API but it will have different fields in the version `1.22`, you want to create a new package under `api/types/versions/v1p21`.

View file

@ -0,0 +1,62 @@
package versions
import (
"strconv"
"strings"
)
// compare compares two version strings
// returns -1 if v1 < v2, 1 if v1 > v2, 0 otherwise.
func compare(v1, v2 string) int {
var (
currTab = strings.Split(v1, ".")
otherTab = strings.Split(v2, ".")
)
max := len(currTab)
if len(otherTab) > max {
max = len(otherTab)
}
for i := 0; i < max; i++ {
var currInt, otherInt int
if len(currTab) > i {
currInt, _ = strconv.Atoi(currTab[i])
}
if len(otherTab) > i {
otherInt, _ = strconv.Atoi(otherTab[i])
}
if currInt > otherInt {
return 1
}
if otherInt > currInt {
return -1
}
}
return 0
}
// LessThan checks if a version is less than another
func LessThan(v, other string) bool {
return compare(v, other) == -1
}
// LessThanOrEqualTo checks if a version is less than or equal to another
func LessThanOrEqualTo(v, other string) bool {
return compare(v, other) <= 0
}
// GreaterThan checks if a version is greater than another
func GreaterThan(v, other string) bool {
return compare(v, other) == 1
}
// GreaterThanOrEqualTo checks if a version is greater than or equal to another
func GreaterThanOrEqualTo(v, other string) bool {
return compare(v, other) >= 0
}
// Equal checks if a version is equal to another
func Equal(v, other string) bool {
return compare(v, other) == 0
}

View file

@ -63,14 +63,14 @@ func ParseHost(defaultToTLS bool, val string) (string, error) {
// parseDockerDaemonHost parses the specified address and returns an address that will be used as the host.
// Depending of the address specified, this may return one of the global Default* strings defined in hosts.go.
func parseDockerDaemonHost(addr string) (string, error) {
addrParts := strings.Split(addr, "://")
addrParts := strings.SplitN(addr, "://", 2)
if len(addrParts) == 1 && addrParts[0] != "" {
addrParts = []string{"tcp", addrParts[0]}
}
switch addrParts[0] {
case "tcp":
return parseTCPAddr(addrParts[1], DefaultTCPHost)
return ParseTCPAddr(addrParts[1], DefaultTCPHost)
case "unix":
return parseSimpleProtoAddr("unix", addrParts[1], DefaultUnixSocket)
case "npipe":
@ -97,12 +97,12 @@ func parseSimpleProtoAddr(proto, addr, defaultAddr string) (string, error) {
return fmt.Sprintf("%s://%s", proto, addr), nil
}
// parseTCPAddr parses and validates that the specified address is a valid TCP
// ParseTCPAddr parses and validates that the specified address is a valid TCP
// address. It returns a formatted TCP address, either using the address parsed
// from tryAddr, or the contents of defaultAddr if tryAddr is a blank string.
// tryAddr is expected to have already been Trim()'d
// defaultAddr must be in the full `tcp://host:port` form
func parseTCPAddr(tryAddr string, defaultAddr string) (string, error) {
func ParseTCPAddr(tryAddr string, defaultAddr string) (string, error) {
if tryAddr == "" || tryAddr == "tcp://" {
return defaultAddr, nil
}
@ -127,8 +127,11 @@ func parseTCPAddr(tryAddr string, defaultAddr string) (string, error) {
if err != nil {
return "", err
}
host, port, err := net.SplitHostPort(u.Host)
if err != nil {
// try port addition once
host, port, err = net.SplitHostPort(net.JoinHostPort(u.Host, defaultPort))
}
if err != nil {
return "", fmt.Errorf("Invalid bind address format: %s", tryAddr)
}

View file

@ -40,3 +40,8 @@ func (o *IPOpt) String() string {
}
return o.IP.String()
}
// Type returns the type of the option
func (o *IPOpt) Type() string {
return "ip"
}

View file

@ -5,6 +5,8 @@ import (
"net"
"regexp"
"strings"
"github.com/docker/docker/api/types/filters"
)
var (
@ -100,6 +102,11 @@ func (opts *ListOpts) Len() int {
return len((*opts.values))
}
// Type returns a string name for this Option type
func (opts *ListOpts) Type() string {
return "list"
}
// NamedOption is an interface that list and map options
// with names implement.
type NamedOption interface {
@ -129,7 +136,7 @@ func (o *NamedListOpts) Name() string {
return o.name
}
//MapOpts holds a map of values and a validation function.
// MapOpts holds a map of values and a validation function.
type MapOpts struct {
values map[string]string
validator ValidatorFctType
@ -163,6 +170,11 @@ func (opts *MapOpts) String() string {
return fmt.Sprintf("%v", map[string]string((opts.values)))
}
// Type returns a string name for this Option type
func (opts *MapOpts) Type() string {
return "map"
}
// NewMapOpts creates a new MapOpts with the specified map of values and a validator.
func NewMapOpts(values map[string]string, validator ValidatorFctType) *MapOpts {
if values == nil {
@ -241,7 +253,7 @@ func ValidateLabel(val string) (string, error) {
return val, nil
}
// ValidateSysctl validates an sysctl and returns it.
// ValidateSysctl validates a sysctl and returns it.
func ValidateSysctl(val string) (string, error) {
validSysctlMap := map[string]bool{
"kernel.msgmax": true,
@ -272,3 +284,38 @@ func ValidateSysctl(val string) (string, error) {
}
return "", fmt.Errorf("sysctl '%s' is not whitelisted", val)
}
// FilterOpt is a flag type for validating filters
type FilterOpt struct {
filter filters.Args
}
// NewFilterOpt returns a new FilterOpt
func NewFilterOpt() FilterOpt {
return FilterOpt{filter: filters.NewArgs()}
}
func (o *FilterOpt) String() string {
repr, err := filters.ToParam(o.filter)
if err != nil {
return "invalid filters"
}
return repr
}
// Set sets the value of the opt by parsing the command line value
func (o *FilterOpt) Set(value string) error {
var err error
o.filter, err = filters.ParseFlag(value, o.filter)
return err
}
// Type returns the option type
func (o *FilterOpt) Type() string {
return "filter"
}
// Value returns the value of this option
func (o *FilterOpt) Value() filters.Args {
return o.filter
}

View file

@ -6,7 +6,7 @@ import (
"strings"
"time"
log "github.com/Sirupsen/logrus"
"github.com/Sirupsen/logrus"
)
var (
@ -21,7 +21,7 @@ func Register(scheme string, d Backend) error {
if _, exists := backends[scheme]; exists {
return fmt.Errorf("scheme already registered %s", scheme)
}
log.WithField("name", scheme).Debug("Registering discovery service")
logrus.WithField("name", scheme).Debugf("Registering discovery service")
backends[scheme] = d
return nil
}
@ -57,7 +57,7 @@ func ParseAdvertise(advertise string) (string, error) {
return advertise, nil
}
// If advertise is a valid interface name, get the valid ipv4 address and use it to advertise
// If advertise is a valid interface name, get the valid IPv4 address and use it to advertise
ifaceName := addr
iface, err = net.InterfaceByName(ifaceName)
if err != nil {
@ -98,7 +98,7 @@ func ParseAdvertise(advertise string) (string, error) {
func New(rawurl string, heartbeat time.Duration, ttl time.Duration, clusterOpts map[string]string) (Backend, error) {
scheme, uri := parse(rawurl)
if backend, exists := backends[scheme]; exists {
log.WithFields(log.Fields{"name": scheme, "uri": uri}).Debug("Initializing discovery service")
logrus.WithFields(logrus.Fields{"name": scheme, "uri": uri}).Debugf("Initializing discovery service")
err := backend.Initialize(uri, heartbeat, ttl, clusterOpts)
return backend, err
}

View file

@ -6,7 +6,7 @@ import (
"strings"
"time"
log "github.com/Sirupsen/logrus"
"github.com/Sirupsen/logrus"
"github.com/docker/docker/pkg/discovery"
"github.com/docker/go-connections/tlsconfig"
"github.com/docker/libkv"
@ -73,7 +73,7 @@ func (s *Discovery) Initialize(uris string, heartbeat time.Duration, ttl time.Du
var config *store.Config
if clusterOpts["kv.cacertfile"] != "" && clusterOpts["kv.certfile"] != "" && clusterOpts["kv.keyfile"] != "" {
log.Info("Initializing discovery with TLS")
logrus.Infof("Initializing discovery with TLS")
tlsConfig, err := tlsconfig.Client(tlsconfig.Options{
CAFile: clusterOpts["kv.cacertfile"],
CertFile: clusterOpts["kv.certfile"],
@ -93,7 +93,7 @@ func (s *Discovery) Initialize(uris string, heartbeat time.Duration, ttl time.Du
TLS: tlsConfig,
}
} else {
log.Info("Initializing discovery without TLS")
logrus.Infof("Initializing discovery without TLS")
}
// Creates a new store, will ignore options given
@ -112,7 +112,7 @@ func (s *Discovery) watchOnce(stopCh <-chan struct{}, watchCh <-chan []*store.KV
return true
}
log.WithField("discovery", s.backend).Debugf("Watch triggered with %d nodes", len(pairs))
logrus.WithField("discovery", s.backend).Debugf("Watch triggered with %d nodes", len(pairs))
// Convert `KVPair` into `discovery.Entry`.
addrs := make([]string, len(pairs))

View file

@ -133,8 +133,9 @@ func (bp *BytesPipe) Read(p []byte) (n int, err error) {
}
bp.wait.Wait()
if bp.bufLen == 0 && bp.closeErr != nil {
err := bp.closeErr
bp.mu.Unlock()
return 0, bp.closeErr
return 0, err
}
}

View file

@ -15,13 +15,15 @@ func NewAtomicFileWriter(filename string, perm os.FileMode) (io.WriteCloser, err
if err != nil {
return nil, err
}
abspath, err := filepath.Abs(filename)
if err != nil {
return nil, err
}
return &atomicFileWriter{
f: f,
fn: abspath,
f: f,
fn: abspath,
perm: perm,
}, nil
}
@ -34,6 +36,7 @@ func AtomicWriteFile(filename string, data []byte, perm os.FileMode) error {
n, err := f.Write(data)
if err == nil && n < len(data) {
err = io.ErrShortWrite
f.(*atomicFileWriter).writeErr = err
}
if err1 := f.Close(); err == nil {
err = err1
@ -45,6 +48,7 @@ type atomicFileWriter struct {
f *os.File
fn string
writeErr error
perm os.FileMode
}
func (w *atomicFileWriter) Write(dt []byte) (int, error) {
@ -57,7 +61,7 @@ func (w *atomicFileWriter) Write(dt []byte) (int, error) {
func (w *atomicFileWriter) Close() (retErr error) {
defer func() {
if retErr != nil {
if retErr != nil || w.writeErr != nil {
os.Remove(w.f.Name())
}
}()
@ -68,6 +72,9 @@ func (w *atomicFileWriter) Close() (retErr error) {
if err := w.f.Close(); err != nil {
return err
}
if err := os.Chmod(w.f.Name(), w.perm); err != nil {
return err
}
if w.writeErr == nil {
return os.Rename(w.f.Name(), w.fn)
}

View file

@ -97,27 +97,24 @@ func (r *multiReadSeeker) Seek(offset int64, whence int) (int64, error) {
}
func (r *multiReadSeeker) getReaderForOffset(offset int64) (io.ReadSeeker, int64, error) {
var rdr io.ReadSeeker
var rdrOffset int64
for i, rdr := range r.readers {
offsetTo, err := r.getOffsetToReader(rdr)
var offsetTo int64
for _, rdr := range r.readers {
size, err := getReadSeekerSize(rdr)
if err != nil {
return nil, -1, err
}
if offsetTo > offset {
rdr = r.readers[i-1]
rdrOffset = offsetTo - offset
break
if offsetTo+size > offset {
return rdr, offset - offsetTo, nil
}
if rdr == r.readers[len(r.readers)-1] {
rdrOffset = offsetTo + offset
break
return rdr, offsetTo + offset, nil
}
offsetTo += size
}
return rdr, rdrOffset, nil
return nil, 0, nil
}
func (r *multiReadSeeker) getCurOffset() (int64, error) {

View file

@ -55,7 +55,7 @@ func HashData(src io.Reader) (string, error) {
return "sha256:" + hex.EncodeToString(h.Sum(nil)), nil
}
// OnEOFReader wraps a io.ReadCloser and a function
// OnEOFReader wraps an io.ReadCloser and a function
// the function will run at the end of file or close the file.
type OnEOFReader struct {
Rc io.ReadCloser

View file

@ -5,6 +5,112 @@ import (
"strings"
)
var flags = map[string]struct {
clear bool
flag int
}{
"defaults": {false, 0},
"ro": {false, RDONLY},
"rw": {true, RDONLY},
"suid": {true, NOSUID},
"nosuid": {false, NOSUID},
"dev": {true, NODEV},
"nodev": {false, NODEV},
"exec": {true, NOEXEC},
"noexec": {false, NOEXEC},
"sync": {false, SYNCHRONOUS},
"async": {true, SYNCHRONOUS},
"dirsync": {false, DIRSYNC},
"remount": {false, REMOUNT},
"mand": {false, MANDLOCK},
"nomand": {true, MANDLOCK},
"atime": {true, NOATIME},
"noatime": {false, NOATIME},
"diratime": {true, NODIRATIME},
"nodiratime": {false, NODIRATIME},
"bind": {false, BIND},
"rbind": {false, RBIND},
"unbindable": {false, UNBINDABLE},
"runbindable": {false, RUNBINDABLE},
"private": {false, PRIVATE},
"rprivate": {false, RPRIVATE},
"shared": {false, SHARED},
"rshared": {false, RSHARED},
"slave": {false, SLAVE},
"rslave": {false, RSLAVE},
"relatime": {false, RELATIME},
"norelatime": {true, RELATIME},
"strictatime": {false, STRICTATIME},
"nostrictatime": {true, STRICTATIME},
}
var validFlags = map[string]bool{
"": true,
"size": true,
"mode": true,
"uid": true,
"gid": true,
"nr_inodes": true,
"nr_blocks": true,
"mpol": true,
}
var propagationFlags = map[string]bool{
"bind": true,
"rbind": true,
"unbindable": true,
"runbindable": true,
"private": true,
"rprivate": true,
"shared": true,
"rshared": true,
"slave": true,
"rslave": true,
}
// MergeTmpfsOptions merge mount options to make sure there is no duplicate.
func MergeTmpfsOptions(options []string) ([]string, error) {
// We use collisions maps to remove duplicates.
// For flag, the key is the flag value (the key for propagation flag is -1)
// For data=value, the key is the data
flagCollisions := map[int]bool{}
dataCollisions := map[string]bool{}
var newOptions []string
// We process in reverse order
for i := len(options) - 1; i >= 0; i-- {
option := options[i]
if option == "defaults" {
continue
}
if f, ok := flags[option]; ok && f.flag != 0 {
// There is only one propagation mode
key := f.flag
if propagationFlags[option] {
key = -1
}
// Check to see if there is collision for flag
if !flagCollisions[key] {
// We prepend the option and add to collision map
newOptions = append([]string{option}, newOptions...)
flagCollisions[key] = true
}
continue
}
opt := strings.SplitN(option, "=", 2)
if len(opt) != 2 || !validFlags[opt[0]] {
return nil, fmt.Errorf("Invalid tmpfs option %q", opt)
}
if !dataCollisions[opt[0]] {
// We prepend the option and add to collision map
newOptions = append([]string{option}, newOptions...)
dataCollisions[opt[0]] = true
}
}
return newOptions, nil
}
// Parse fstab type mount options into mount() flags
// and device specific data
func parseOptions(options string) (int, string) {
@ -13,45 +119,6 @@ func parseOptions(options string) (int, string) {
data []string
)
flags := map[string]struct {
clear bool
flag int
}{
"defaults": {false, 0},
"ro": {false, RDONLY},
"rw": {true, RDONLY},
"suid": {true, NOSUID},
"nosuid": {false, NOSUID},
"dev": {true, NODEV},
"nodev": {false, NODEV},
"exec": {true, NOEXEC},
"noexec": {false, NOEXEC},
"sync": {false, SYNCHRONOUS},
"async": {true, SYNCHRONOUS},
"dirsync": {false, DIRSYNC},
"remount": {false, REMOUNT},
"mand": {false, MANDLOCK},
"nomand": {true, MANDLOCK},
"atime": {true, NOATIME},
"noatime": {false, NOATIME},
"diratime": {true, NODIRATIME},
"nodiratime": {false, NODIRATIME},
"bind": {false, BIND},
"rbind": {false, RBIND},
"unbindable": {false, UNBINDABLE},
"runbindable": {false, RUNBINDABLE},
"private": {false, PRIVATE},
"rprivate": {false, RPRIVATE},
"shared": {false, SHARED},
"rshared": {false, RSHARED},
"slave": {false, SLAVE},
"rslave": {false, RSLAVE},
"relatime": {false, RELATIME},
"norelatime": {true, RELATIME},
"strictatime": {false, STRICTATIME},
"nostrictatime": {true, STRICTATIME},
}
for _, o := range strings.Split(options, ",") {
// If the option does not exist in the flags table or the flag
// is not supported on the platform,
@ -72,16 +139,6 @@ func parseOptions(options string) (int, string) {
// ParseTmpfsOptions parse fstab type mount options into flags and data
func ParseTmpfsOptions(options string) (int, string, error) {
flags, data := parseOptions(options)
validFlags := map[string]bool{
"": true,
"size": true,
"mode": true,
"uid": true,
"gid": true,
"nr_inodes": true,
"nr_blocks": true,
"mpol": true,
}
for _, o := range strings.Split(data, ",") {
opt := strings.SplitN(o, "=", 2)
if !validFlags[opt[0]] {

View file

@ -5,7 +5,6 @@
package kernel
import (
"bytes"
"errors"
"fmt"
)
@ -46,31 +45,6 @@ func CompareKernelVersion(a, b VersionInfo) int {
return 0
}
// GetKernelVersion gets the current kernel version.
func GetKernelVersion() (*VersionInfo, error) {
var (
err error
)
uts, err := uname()
if err != nil {
return nil, err
}
release := make([]byte, len(uts.Release))
i := 0
for _, c := range uts.Release {
release[i] = byte(c)
i++
}
// Remove the \x00 from the release for Atoi to parse correctly
release = release[:bytes.IndexByte(release, 0)]
return ParseRelease(string(release))
}
// ParseRelease parses a string and creates a VersionInfo based on it.
func ParseRelease(release string) (*VersionInfo, error) {
var (

View file

@ -0,0 +1,56 @@
// +build darwin
// Package kernel provides helper function to get, parse and compare kernel
// versions for different platforms.
package kernel
import (
"fmt"
"os/exec"
"strings"
"github.com/mattn/go-shellwords"
)
// GetKernelVersion gets the current kernel version.
func GetKernelVersion() (*VersionInfo, error) {
release, err := getRelease()
if err != nil {
return nil, err
}
return ParseRelease(release)
}
// getRelease uses `system_profiler SPSoftwareDataType` to get OSX kernel version
func getRelease() (string, error) {
cmd := exec.Command("system_profiler", "SPSoftwareDataType")
osName, err := cmd.Output()
if err != nil {
return "", err
}
var release string
data := strings.Split(string(osName), "\n")
for _, line := range data {
if strings.Contains(line, "Kernel Version") {
// It has the format like ' Kernel Version: Darwin 14.5.0'
content := strings.SplitN(line, ":", 2)
if len(content) != 2 {
return "", fmt.Errorf("Kernel Version is invalid")
}
prettyNames, err := shellwords.Parse(content[1])
if err != nil {
return "", fmt.Errorf("Kernel Version is invalid: %s", err.Error())
}
if len(prettyNames) != 2 {
return "", fmt.Errorf("Kernel Version needs to be 'Darwin x.x.x' ")
}
release = prettyNames[1]
}
}
return release, nil
}

View file

@ -0,0 +1,45 @@
// +build linux freebsd solaris
// Package kernel provides helper function to get, parse and compare kernel
// versions for different platforms.
package kernel
import (
"bytes"
"github.com/Sirupsen/logrus"
)
// GetKernelVersion gets the current kernel version.
func GetKernelVersion() (*VersionInfo, error) {
uts, err := uname()
if err != nil {
return nil, err
}
release := make([]byte, len(uts.Release))
i := 0
for _, c := range uts.Release {
release[i] = byte(c)
i++
}
// Remove the \x00 from the release for Atoi to parse correctly
release = release[:bytes.IndexByte(release, 0)]
return ParseRelease(string(release))
}
// CheckKernelVersion checks if current kernel is newer than (or equal to)
// the given version.
func CheckKernelVersion(k, major, minor int) bool {
if v, err := GetKernelVersion(); err != nil {
logrus.Warnf("error getting kernel version: %s", err)
} else {
if CompareKernelVersion(*v, VersionInfo{Kernel: k, Major: major, Minor: minor}) < 0 {
return false
}
}
return true
}

View file

@ -1,3 +1,5 @@
// +build windows
package kernel
import (

View file

@ -20,14 +20,16 @@ const (
)
// NewClient creates a new plugin client (http).
func NewClient(addr string, tlsConfig tlsconfig.Options) (*Client, error) {
func NewClient(addr string, tlsConfig *tlsconfig.Options) (*Client, error) {
tr := &http.Transport{}
c, err := tlsconfig.Client(tlsConfig)
if err != nil {
return nil, err
if tlsConfig != nil {
c, err := tlsconfig.Client(*tlsConfig)
if err != nil {
return nil, err
}
tr.TLSClientConfig = c
}
tr.TLSClientConfig = c
u, err := url.Parse(addr)
if err != nil {
@ -130,7 +132,7 @@ func (c *Client) callWithRetry(serviceMethod string, data io.Reader, retry bool)
return nil, err
}
retries++
logrus.Warnf("Unable to connect to plugin: %s:%s, retrying in %v", req.URL.Host, req.URL.Path, timeOff)
logrus.Warnf("Unable to connect to plugin: %s%s: %v, retrying in %v", req.URL.Host, req.URL.Path, err, timeOff)
time.Sleep(timeOff)
continue
}

View file

@ -16,7 +16,6 @@ var (
// ErrNotFound plugin not found
ErrNotFound = errors.New("plugin not found")
socketsPath = "/run/docker/plugins"
specsPaths = []string{"/etc/docker/plugins", "/usr/lib/docker/plugins"}
)
// localRegistry defines a registry that is local (using unix socket).
@ -64,7 +63,7 @@ func (l *localRegistry) Plugin(name string) (*Plugin, error) {
for _, p := range socketpaths {
if fi, err := os.Stat(p); err == nil && fi.Mode()&os.ModeSocket != 0 {
return newLocalPlugin(name, "unix://"+p), nil
return NewLocalPlugin(name, "unix://"+p), nil
}
}
@ -101,7 +100,7 @@ func readPluginInfo(name, path string) (*Plugin, error) {
return nil, fmt.Errorf("Unknown protocol")
}
return newLocalPlugin(name, addr), nil
return NewLocalPlugin(name, addr), nil
}
func readPluginJSONInfo(name, path string) (*Plugin, error) {
@ -115,8 +114,8 @@ func readPluginJSONInfo(name, path string) (*Plugin, error) {
if err := json.NewDecoder(f).Decode(&p); err != nil {
return nil, err
}
p.Name = name
if len(p.TLSConfig.CAFile) == 0 {
p.name = name
if p.TLSConfig != nil && len(p.TLSConfig.CAFile) == 0 {
p.TLSConfig.InsecureSkipVerify = true
}
p.activateWait = sync.NewCond(&sync.Mutex{})

View file

@ -0,0 +1,5 @@
// +build !windows
package plugins
var specsPaths = []string{"/etc/docker/plugins", "/usr/lib/docker/plugins"}

View file

@ -0,0 +1,8 @@
package plugins
import (
"os"
"path/filepath"
)
var specsPaths = []string{filepath.Join(os.Getenv("programdata"), "docker", "plugins")}

View file

@ -55,13 +55,13 @@ type Manifest struct {
// Plugin is the definition of a docker plugin.
type Plugin struct {
// Name of the plugin
Name string `json:"-"`
name string
// Address of the plugin
Addr string
// TLS configuration of the plugin
TLSConfig tlsconfig.Options
TLSConfig *tlsconfig.Options
// Client attached to the plugin
Client *Client `json:"-"`
client *Client
// Manifest of the plugin (see above)
Manifest *Manifest `json:"-"`
@ -73,11 +73,28 @@ type Plugin struct {
activateWait *sync.Cond
}
func newLocalPlugin(name, addr string) *Plugin {
// Name returns the name of the plugin.
func (p *Plugin) Name() string {
return p.name
}
// Client returns a ready-to-use plugin client that can be used to communicate with the plugin.
func (p *Plugin) Client() *Client {
return p.client
}
// IsLegacy returns true for legacy plugins and false otherwise.
func (p *Plugin) IsLegacy() bool {
return true
}
// NewLocalPlugin creates a new local plugin.
func NewLocalPlugin(name, addr string) *Plugin {
return &Plugin{
Name: name,
Addr: addr,
TLSConfig: tlsconfig.Options{InsecureSkipVerify: true},
name: name,
Addr: addr,
// TODO: change to nil
TLSConfig: &tlsconfig.Options{InsecureSkipVerify: true},
activateWait: sync.NewCond(&sync.Mutex{}),
}
}
@ -102,10 +119,10 @@ func (p *Plugin) activateWithLock() error {
if err != nil {
return err
}
p.Client = c
p.client = c
m := new(Manifest)
if err = p.Client.Call("Plugin.Activate", nil, m); err != nil {
if err = p.client.Call("Plugin.Activate", nil, m); err != nil {
return err
}
@ -116,7 +133,7 @@ func (p *Plugin) activateWithLock() error {
if !handled {
continue
}
handler(p.Name, p.Client)
handler(p.name, p.client)
}
return nil
}

View file

@ -13,7 +13,7 @@ func Self() string {
return "/proc/self/exe"
}
// Command returns *exec.Cmd which have Path as current binary. Also it setting
// Command returns *exec.Cmd which has Path as current binary. Also it setting
// SysProcAttr.Pdeathsig to SIGTERM.
// This will use the in-memory version (/proc/self/exe) of the current binary,
// it is thus safe to delete or replace the on-disk binary (os.Args[0]).

View file

@ -1,4 +1,4 @@
// +build freebsd solaris
// +build freebsd solaris darwin
package reexec
@ -12,7 +12,7 @@ func Self() string {
return naiveSelf()
}
// Command returns *exec.Cmd which have Path as current binary.
// Command returns *exec.Cmd which has Path as current binary.
// For example if current binary is "docker" at "/usr/bin/", then cmd.Path will
// be set to "/usr/bin/docker".
func Command(args ...string) *exec.Cmd {

View file

@ -1,4 +1,4 @@
// +build !linux,!windows,!freebsd,!solaris
// +build !linux,!windows,!freebsd,!solaris,!darwin
package reexec
@ -6,7 +6,7 @@ import (
"os/exec"
)
// Command is unsupported on operating systems apart from Linux and Windows.
// Command is unsupported on operating systems apart from Linux, Windows, Solaris and Darwin.
func Command(args ...string) *exec.Cmd {
return nil
}

View file

@ -12,7 +12,7 @@ func Self() string {
return naiveSelf()
}
// Command returns *exec.Cmd which have Path as current binary.
// Command returns *exec.Cmd which has Path as current binary.
// For example if current binary is "docker.exe" at "C:\", then cmd.Path will
// be set to "C:\docker.exe".
func Command(args ...string) *exec.Cmd {

View file

@ -3,9 +3,11 @@ package signal
import (
"os"
gosignal "os/signal"
"path/filepath"
"runtime"
"sync/atomic"
"syscall"
"time"
"github.com/Sirupsen/logrus"
)
@ -18,15 +20,22 @@ import (
// * If SIGINT or SIGTERM are received 3 times before cleanup is complete, then cleanup is
// skipped and the process is terminated immediately (allows force quit of stuck daemon)
// * A SIGQUIT always causes an exit without cleanup, with a goroutine dump preceding exit.
// * Ignore SIGPIPE events. These are generated by systemd when journald is restarted while
// the docker daemon is not restarted and also running under systemd.
// Fixes https://github.com/docker/docker/issues/19728
//
func Trap(cleanup func()) {
c := make(chan os.Signal, 1)
// we will handle INT, TERM, QUIT here
signals := []os.Signal{os.Interrupt, syscall.SIGTERM, syscall.SIGQUIT}
// we will handle INT, TERM, QUIT, SIGPIPE here
signals := []os.Signal{os.Interrupt, syscall.SIGTERM, syscall.SIGQUIT, syscall.SIGPIPE}
gosignal.Notify(c, signals...)
go func() {
interruptCount := uint32(0)
for sig := range c {
if sig == syscall.SIGPIPE {
continue
}
go func(sig os.Signal) {
logrus.Infof("Processing signal '%v'", sig)
switch sig {
@ -42,11 +51,11 @@ func Trap(cleanup func()) {
}
} else {
// 3 SIGTERM/INT signals received; force exit without cleanup
logrus.Infof("Forcing docker daemon shutdown without cleanup; 3 interrupts received")
logrus.Info("Forcing docker daemon shutdown without cleanup; 3 interrupts received")
}
case syscall.SIGQUIT:
DumpStacks()
logrus.Infof("Forcing docker daemon shutdown without cleanup on SIGQUIT")
DumpStacks("")
logrus.Info("Forcing docker daemon shutdown without cleanup on SIGQUIT")
}
//for the SIGINT/TERM, and SIGQUIT non-clean shutdown case, exit with 128 + signal #
os.Exit(128 + int(sig.(syscall.Signal)))
@ -56,7 +65,7 @@ func Trap(cleanup func()) {
}
// DumpStacks dumps the runtime stack.
func DumpStacks() {
func DumpStacks(root string) {
var (
buf []byte
stackSize int
@ -70,5 +79,30 @@ func DumpStacks() {
buf = buf[:stackSize]
// Note that if the daemon is started with a less-verbose log-level than "info" (the default), the goroutine
// traces won't show up in the log.
logrus.Infof("=== BEGIN goroutine stack dump ===\n%s\n=== END goroutine stack dump ===", buf)
if root == "" {
logrus.Infof("=== BEGIN goroutine stack dump ===\n%s\n=== END goroutine stack dump ===", buf)
} else {
// Dumps the stacks to a file in the root directory of the daemon
// On Windows, this overcomes two issues - one being that if the stack is too big, it doesn't
// get written to the event log when the Windows daemon is running as a service.
// Second, using logrus, the tabs and new-lines end up getting written as literal
// \t and \n's, meaning you need to use something like notepad++ to convert the
// output into something readable using 'type' from a command line or notepad/notepad++ etc.
path := filepath.Join(root, "goroutine-stacks.log")
f, err := os.OpenFile(path, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0666)
if err != nil {
logrus.Warnf("Could not open %s to write the goroutine stacks: %v", path, err)
return
}
defer f.Close()
f.WriteString("=== BEGIN goroutine stack dump ===\n")
f.WriteString(time.Now().String() + "\n")
if _, err := f.Write(buf); err != nil {
logrus.Warnf("Could not write goroutine stacks to %s: %v", path, err)
return
}
f.WriteString("=== END goroutine stack dump ===\n")
f.Sync()
logrus.Infof("goroutine stacks written to %s", path)
}
}

View file

@ -29,11 +29,10 @@ func TruncateID(id string) string {
if i := strings.IndexRune(id, ':'); i >= 0 {
id = id[i+1:]
}
trimTo := shortLen
if len(id) < shortLen {
trimTo = len(id)
if len(id) > shortLen {
id = id[:shortLen]
}
return id[:trimTo]
return id
}
func generateID(crypto bool) string {
@ -60,7 +59,6 @@ func generateID(crypto bool) string {
// GenerateRandomID returns a unique id.
func GenerateRandomID() string {
return generateID(true)
}
// GenerateNonCryptoID generates unique id without using cryptographically

View file

@ -0,0 +1,32 @@
package system
import (
"syscall"
)
// fromStatT creates a system.StatT type from a syscall.Stat_t type
func fromStatT(s *syscall.Stat_t) (*StatT, error) {
return &StatT{size: s.Size,
mode: uint32(s.Mode),
uid: s.Uid,
gid: s.Gid,
rdev: uint64(s.Rdev),
mtim: s.Mtimespec}, nil
}
// FromStatT loads a system.StatT from a syscall.Stat_t.
func FromStatT(s *syscall.Stat_t) (*StatT, error) {
return fromStatT(s)
}
// Stat takes a path to a file and returns
// a system.StatT type pertaining to that file.
//
// Throws an error if the file does not exist
func Stat(path string) (*StatT, error) {
s := &syscall.Stat_t{}
if err := syscall.Stat(path, s); err != nil {
return nil, err
}
return fromStatT(s)
}

View file

@ -1,4 +1,4 @@
// +build !linux,!windows,!freebsd,!solaris,!openbsd
// +build !linux,!windows,!freebsd,!solaris,!openbsd,!darwin
package system

View file

@ -1,8 +0,0 @@
package system
import "syscall"
// LUtimesNano is not supported by darwin platform.
func LUtimesNano(path string, ts []syscall.Timespec) error {
return ErrNotSupportedPlatform
}

View file

@ -1,10 +1,10 @@
// +build !linux,!freebsd,!darwin
// +build !linux,!freebsd
package system
import "syscall"
// LUtimesNano is not supported on platforms other than linux, freebsd and darwin.
// LUtimesNano is only supported on linux and freebsd.
func LUtimesNano(path string, ts []syscall.Timespec) error {
return ErrNotSupportedPlatform
}

View file

@ -1,11 +1,12 @@
// +build !windows
// Package term provides provides structures and helper functions to work with
// Package term provides structures and helper functions to work with
// terminal (state, sizes).
package term
import (
"errors"
"fmt"
"io"
"os"
"os/signal"
@ -88,7 +89,8 @@ func DisableEcho(fd uintptr, state *State) error {
}
// SetRawTerminal puts the terminal connected to the given file descriptor into
// raw mode and returns the previous state.
// raw mode and returns the previous state. On UNIX, this puts both the input
// and output into raw mode. On Windows, it only puts the input into raw mode.
func SetRawTerminal(fd uintptr) (*State, error) {
oldState, err := MakeRaw(fd)
if err != nil {
@ -98,12 +100,24 @@ func SetRawTerminal(fd uintptr) (*State, error) {
return oldState, err
}
// SetRawTerminalOutput puts the output of terminal connected to the given file
// descriptor into raw mode. On UNIX, this does nothing and returns nil for the
// state. On Windows, it disables LF -> CRLF translation.
func SetRawTerminalOutput(fd uintptr) (*State, error) {
return nil, nil
}
func handleInterrupt(fd uintptr, state *State) {
sigchan := make(chan os.Signal, 1)
signal.Notify(sigchan, os.Interrupt)
go func() {
_ = <-sigchan
RestoreTerminal(fd, state)
for range sigchan {
// quit cleanly and the new terminal item is on a new line
fmt.Println()
signal.Stop(sigchan)
close(sigchan)
RestoreTerminal(fd, state)
os.Exit(1)
}
}()
}

View file

@ -9,14 +9,12 @@ import (
"syscall"
"github.com/Azure/go-ansiterm/winterm"
"github.com/docker/docker/pkg/system"
"github.com/docker/docker/pkg/term/windows"
)
// State holds the console mode for the terminal.
type State struct {
inMode, outMode uint32
inHandle, outHandle syscall.Handle
mode uint32
}
// Winsize is used for window size.
@ -32,143 +30,72 @@ const (
disableNewlineAutoReturn = 0x0008
)
// usingNativeConsole is true if we are using the Windows native console
var usingNativeConsole bool
// vtInputSupported is true if enableVirtualTerminalInput is supported by the console
var vtInputSupported bool
// StdStreams returns the standard streams (stdin, stdout, stedrr).
func StdStreams() (stdIn io.ReadCloser, stdOut, stdErr io.Writer) {
switch {
case os.Getenv("ConEmuANSI") == "ON":
// Turn on VT handling on all std handles, if possible. This might
// fail, in which case we will fall back to terminal emulation.
var emulateStdin, emulateStdout, emulateStderr bool
fd := os.Stdin.Fd()
if mode, err := winterm.GetConsoleMode(fd); err == nil {
// Validate that enableVirtualTerminalInput is supported, but do not set it.
if err = winterm.SetConsoleMode(fd, mode|enableVirtualTerminalInput); err != nil {
emulateStdin = true
} else {
vtInputSupported = true
}
// Unconditionally set the console mode back even on failure because SetConsoleMode
// remembers invalid bits on input handles.
winterm.SetConsoleMode(fd, mode)
}
fd = os.Stdout.Fd()
if mode, err := winterm.GetConsoleMode(fd); err == nil {
// Validate disableNewlineAutoReturn is supported, but do not set it.
if err = winterm.SetConsoleMode(fd, mode|enableVirtualTerminalProcessing|disableNewlineAutoReturn); err != nil {
emulateStdout = true
} else {
winterm.SetConsoleMode(fd, mode|enableVirtualTerminalProcessing)
}
}
fd = os.Stderr.Fd()
if mode, err := winterm.GetConsoleMode(fd); err == nil {
// Validate disableNewlineAutoReturn is supported, but do not set it.
if err = winterm.SetConsoleMode(fd, mode|enableVirtualTerminalProcessing|disableNewlineAutoReturn); err != nil {
emulateStderr = true
} else {
winterm.SetConsoleMode(fd, mode|enableVirtualTerminalProcessing)
}
}
if os.Getenv("ConEmuANSI") == "ON" {
// The ConEmu terminal emulates ANSI on output streams well.
return windows.ConEmuStreams()
case os.Getenv("MSYSTEM") != "":
// MSYS (mingw) does not emulate ANSI well.
return windows.ConsoleStreams()
default:
if useNativeConsole() {
usingNativeConsole = true
return os.Stdin, os.Stdout, os.Stderr
}
return windows.ConsoleStreams()
}
}
// useNativeConsole determines if the docker client should use the built-in
// console which supports ANSI emulation, or fall-back to the golang emulator
// (github.com/azure/go-ansiterm).
func useNativeConsole() bool {
osv := system.GetOSVersion()
// Native console is not available before major version 10
if osv.MajorVersion < 10 {
return false
emulateStdout = false
emulateStderr = false
}
// Get the console modes. If this fails, we can't use the native console
state, err := getNativeConsole()
if err != nil {
return false
if emulateStdin {
stdIn = windows.NewAnsiReader(syscall.STD_INPUT_HANDLE)
} else {
stdIn = os.Stdin
}
// Probe the console to see if it can be enabled.
if nil != probeNativeConsole(state) {
return false
if emulateStdout {
stdOut = windows.NewAnsiWriter(syscall.STD_OUTPUT_HANDLE)
} else {
stdOut = os.Stdout
}
// Environment variable override
if e := os.Getenv("USE_NATIVE_CONSOLE"); e != "" {
if e == "1" {
return true
}
return false
if emulateStderr {
stdErr = windows.NewAnsiWriter(syscall.STD_ERROR_HANDLE)
} else {
stdErr = os.Stderr
}
// Must have a post-TP5 RS1 build of Windows Server 2016/Windows 10 for
// the native console to be usable.
if osv.Build < 14350 {
return false
}
return true
}
// getNativeConsole returns the console modes ('state') for the native Windows console
func getNativeConsole() (State, error) {
var (
err error
state State
)
// Get the handle to stdout
if state.outHandle, err = syscall.GetStdHandle(syscall.STD_OUTPUT_HANDLE); err != nil {
return state, err
}
// Get the console mode from the consoles stdout handle
if err = syscall.GetConsoleMode(state.outHandle, &state.outMode); err != nil {
return state, err
}
// Get the handle to stdin
if state.inHandle, err = syscall.GetStdHandle(syscall.STD_INPUT_HANDLE); err != nil {
return state, err
}
// Get the console mode from the consoles stdin handle
if err = syscall.GetConsoleMode(state.inHandle, &state.inMode); err != nil {
return state, err
}
return state, nil
}
// probeNativeConsole probes the console to determine if native can be supported,
func probeNativeConsole(state State) error {
if err := winterm.SetConsoleMode(uintptr(state.outHandle), state.outMode|enableVirtualTerminalProcessing); err != nil {
return err
}
defer winterm.SetConsoleMode(uintptr(state.outHandle), state.outMode)
if err := winterm.SetConsoleMode(uintptr(state.inHandle), state.inMode|enableVirtualTerminalInput); err != nil {
return err
}
defer winterm.SetConsoleMode(uintptr(state.inHandle), state.inMode)
return nil
}
// enableNativeConsole turns on native console mode
func enableNativeConsole(state State) error {
// First attempt both enableVirtualTerminalProcessing and disableNewlineAutoReturn
if err := winterm.SetConsoleMode(uintptr(state.outHandle),
state.outMode|(enableVirtualTerminalProcessing|disableNewlineAutoReturn)); err != nil {
// That may fail, so fallback to trying just enableVirtualTerminalProcessing
if err := winterm.SetConsoleMode(uintptr(state.outHandle), state.outMode|enableVirtualTerminalProcessing); err != nil {
return err
}
}
if err := winterm.SetConsoleMode(uintptr(state.inHandle), state.inMode|enableVirtualTerminalInput); err != nil {
winterm.SetConsoleMode(uintptr(state.outHandle), state.outMode) // restore out if we can
return err
}
return nil
}
// disableNativeConsole turns off native console mode
func disableNativeConsole(state *State) error {
// Try and restore both in an out before error checking.
errout := winterm.SetConsoleMode(uintptr(state.outHandle), state.outMode)
errin := winterm.SetConsoleMode(uintptr(state.inHandle), state.inMode)
if errout != nil {
return errout
}
if errin != nil {
return errin
}
return nil
return
}
// GetFdInfo returns the file descriptor for an os.File and indicates whether the file represents a terminal.
@ -199,34 +126,23 @@ func IsTerminal(fd uintptr) bool {
// RestoreTerminal restores the terminal connected to the given file descriptor
// to a previous state.
func RestoreTerminal(fd uintptr, state *State) error {
if usingNativeConsole {
return disableNativeConsole(state)
}
return winterm.SetConsoleMode(fd, state.outMode)
return winterm.SetConsoleMode(fd, state.mode)
}
// SaveState saves the state of the terminal connected to the given file descriptor.
func SaveState(fd uintptr) (*State, error) {
if usingNativeConsole {
state, err := getNativeConsole()
if err != nil {
return nil, err
}
return &state, nil
}
mode, e := winterm.GetConsoleMode(fd)
if e != nil {
return nil, e
}
return &State{outMode: mode}, nil
return &State{mode: mode}, nil
}
// DisableEcho disables echo for the terminal connected to the given file descriptor.
// -- See https://msdn.microsoft.com/en-us/library/windows/desktop/ms683462(v=vs.85).aspx
func DisableEcho(fd uintptr, state *State) error {
mode := state.inMode
mode := state.mode
mode &^= winterm.ENABLE_ECHO_INPUT
mode |= winterm.ENABLE_PROCESSED_INPUT | winterm.ENABLE_LINE_INPUT
err := winterm.SetConsoleMode(fd, mode)
@ -239,8 +155,9 @@ func DisableEcho(fd uintptr, state *State) error {
return nil
}
// SetRawTerminal puts the terminal connected to the given file descriptor into raw
// mode and returns the previous state.
// SetRawTerminal puts the terminal connected to the given file descriptor into
// raw mode and returns the previous state. On UNIX, this puts both the input
// and output into raw mode. On Windows, it only puts the input into raw mode.
func SetRawTerminal(fd uintptr) (*State, error) {
state, err := MakeRaw(fd)
if err != nil {
@ -252,6 +169,21 @@ func SetRawTerminal(fd uintptr) (*State, error) {
return state, err
}
// SetRawTerminalOutput puts the output of terminal connected to the given file
// descriptor into raw mode. On UNIX, this does nothing and returns nil for the
// state. On Windows, it disables LF -> CRLF translation.
func SetRawTerminalOutput(fd uintptr) (*State, error) {
state, err := SaveState(fd)
if err != nil {
return nil, err
}
// Ignore failures, since disableNewlineAutoReturn might not be supported on this
// version of Windows.
winterm.SetConsoleMode(fd, state.mode|disableNewlineAutoReturn)
return state, err
}
// MakeRaw puts the terminal (Windows Console) connected to the given file descriptor into raw
// mode and returns the previous state of the terminal so that it can be restored.
func MakeRaw(fd uintptr) (*State, error) {
@ -260,13 +192,7 @@ func MakeRaw(fd uintptr) (*State, error) {
return nil, err
}
mode := state.inMode
if usingNativeConsole {
if err := enableNativeConsole(*state); err != nil {
return nil, err
}
mode |= enableVirtualTerminalInput
}
mode := state.mode
// See
// -- https://msdn.microsoft.com/en-us/library/windows/desktop/ms686033(v=vs.85).aspx
@ -283,6 +209,9 @@ func MakeRaw(fd uintptr) (*State, error) {
mode |= winterm.ENABLE_EXTENDED_FLAGS
mode |= winterm.ENABLE_INSERT_MODE
mode |= winterm.ENABLE_QUICK_EDIT_MODE
if vtInputSupported {
mode |= enableVirtualTerminalInput
}
err = winterm.SetConsoleMode(fd, mode)
if err != nil {

View file

@ -6,6 +6,7 @@ import (
"bytes"
"errors"
"fmt"
"io"
"os"
"strings"
"unsafe"
@ -27,7 +28,10 @@ type ansiReader struct {
command []byte
}
func newAnsiReader(nFile int) *ansiReader {
// NewAnsiReader returns an io.ReadCloser that provides VT100 terminal emulation on top of a
// Windows console input handle.
func NewAnsiReader(nFile int) io.ReadCloser {
initLogger()
file, fd := winterm.GetStdFile(nFile)
return &ansiReader{
file: file,

View file

@ -3,16 +3,13 @@
package windows
import (
"io/ioutil"
"io"
"os"
ansiterm "github.com/Azure/go-ansiterm"
"github.com/Azure/go-ansiterm/winterm"
"github.com/Sirupsen/logrus"
)
var logger *logrus.Logger
// ansiWriter wraps a standard output file (e.g., os.Stdout) providing ANSI sequence translation.
type ansiWriter struct {
file *os.File
@ -24,19 +21,10 @@ type ansiWriter struct {
parser *ansiterm.AnsiParser
}
func newAnsiWriter(nFile int) *ansiWriter {
logFile := ioutil.Discard
if isDebugEnv := os.Getenv(ansiterm.LogEnv); isDebugEnv == "1" {
logFile, _ = os.Create("ansiReaderWriter.log")
}
logger = &logrus.Logger{
Out: logFile,
Formatter: new(logrus.TextFormatter),
Level: logrus.DebugLevel,
}
// NewAnsiWriter returns an io.Writer that provides VT100 terminal emulation on top of a
// Windows console output handle.
func NewAnsiWriter(nFile int) io.Writer {
initLogger()
file, fd := winterm.GetStdFile(nFile)
info, err := winterm.GetConsoleScreenBufferInfo(fd)
if err != nil {

View file

@ -3,73 +3,11 @@
package windows
import (
"io"
"os"
"syscall"
"github.com/Azure/go-ansiterm/winterm"
ansiterm "github.com/Azure/go-ansiterm"
"github.com/Sirupsen/logrus"
"io/ioutil"
)
// ConEmuStreams returns prepared versions of console streams,
// for proper use in ConEmu terminal.
// The ConEmu terminal emulates ANSI on output streams well by default.
func ConEmuStreams() (stdIn io.ReadCloser, stdOut, stdErr io.Writer) {
if IsConsole(os.Stdin.Fd()) {
stdIn = newAnsiReader(syscall.STD_INPUT_HANDLE)
} else {
stdIn = os.Stdin
}
stdOut = os.Stdout
stdErr = os.Stderr
// WARNING (BEGIN): sourced from newAnsiWriter
logFile := ioutil.Discard
if isDebugEnv := os.Getenv(ansiterm.LogEnv); isDebugEnv == "1" {
logFile, _ = os.Create("ansiReaderWriter.log")
}
logger = &logrus.Logger{
Out: logFile,
Formatter: new(logrus.TextFormatter),
Level: logrus.DebugLevel,
}
// WARNING (END): sourced from newAnsiWriter
return stdIn, stdOut, stdErr
}
// ConsoleStreams returns a wrapped version for each standard stream referencing a console,
// that handles ANSI character sequences.
func ConsoleStreams() (stdIn io.ReadCloser, stdOut, stdErr io.Writer) {
if IsConsole(os.Stdin.Fd()) {
stdIn = newAnsiReader(syscall.STD_INPUT_HANDLE)
} else {
stdIn = os.Stdin
}
if IsConsole(os.Stdout.Fd()) {
stdOut = newAnsiWriter(syscall.STD_OUTPUT_HANDLE)
} else {
stdOut = os.Stdout
}
if IsConsole(os.Stderr.Fd()) {
stdErr = newAnsiWriter(syscall.STD_ERROR_HANDLE)
} else {
stdErr = os.Stderr
}
return stdIn, stdOut, stdErr
}
// GetHandleInfo returns file descriptor and bool indicating whether the file is a console.
func GetHandleInfo(in interface{}) (uintptr, bool) {
switch t := in.(type) {

View file

@ -3,3 +3,31 @@
// and return pseudo-streams that convert ANSI sequences to / from Windows Console API calls.
package windows
import (
"io/ioutil"
"os"
"sync"
ansiterm "github.com/Azure/go-ansiterm"
"github.com/Sirupsen/logrus"
)
var logger *logrus.Logger
var initOnce sync.Once
func initLogger() {
initOnce.Do(func() {
logFile := ioutil.Discard
if isDebugEnv := os.Getenv(ansiterm.LogEnv); isDebugEnv == "1" {
logFile, _ = os.Create("ansiReaderWriter.log")
}
logger = &logrus.Logger{
Out: logFile,
Formatter: new(logrus.TextFormatter),
Level: logrus.DebugLevel,
}
})
}

View file

@ -0,0 +1,8 @@
language: go
go:
- tip
before_install:
- go get github.com/mattn/goveralls
- go get golang.org/x/tools/cmd/cover
script:
- $HOME/gopath/bin/goveralls -repotoken 2FMhp57u8LcstKL9B190fLTcEnBtAAiEL

View file

@ -0,0 +1,47 @@
# go-shellwords
[![Coverage Status](https://coveralls.io/repos/mattn/go-shellwords/badge.png?branch=master)](https://coveralls.io/r/mattn/go-shellwords?branch=master)
[![Build Status](https://travis-ci.org/mattn/go-shellwords.svg?branch=master)](https://travis-ci.org/mattn/go-shellwords)
Parse line as shell words.
## Usage
```go
args, err := shellwords.Parse("./foo --bar=baz")
// args should be ["./foo", "--bar=baz"]
```
```go
os.Setenv("FOO", "bar")
p := shellwords.NewParser()
p.ParseEnv = true
args, err := p.Parse("./foo $FOO")
// args should be ["./foo", "bar"]
```
```go
p := shellwords.NewParser()
p.ParseBacktick = true
args, err := p.Parse("./foo `echo $SHELL`")
// args should be ["./foo", "/bin/bash"]
```
```go
shellwords.ParseBacktick = true
p := shellwords.NewParser()
args, err := p.Parse("./foo `echo $SHELL`")
// args should be ["./foo", "/bin/bash"]
```
# Thanks
This is based on cpan module [Parse::CommandLine](https://metacpan.org/pod/Parse::CommandLine).
# License
under the MIT License: http://mattn.mit-license.org/2014
# Author
Yasuhiro Matsumoto (a.k.a mattn)

View file

@ -0,0 +1,134 @@
package shellwords
import (
"errors"
"os"
"regexp"
"strings"
)
var (
ParseEnv bool = false
ParseBacktick bool = false
)
var envRe = regexp.MustCompile(`\$({[a-zA-Z0-9_]+}|[a-zA-Z0-9_]+)`)
func isSpace(r rune) bool {
switch r {
case ' ', '\t', '\r', '\n':
return true
}
return false
}
func replaceEnv(s string) string {
return envRe.ReplaceAllStringFunc(s, func(s string) string {
s = s[1:]
if s[0] == '{' {
s = s[1 : len(s)-1]
}
return os.Getenv(s)
})
}
type Parser struct {
ParseEnv bool
ParseBacktick bool
}
func NewParser() *Parser {
return &Parser{ParseEnv, ParseBacktick}
}
func (p *Parser) Parse(line string) ([]string, error) {
line = strings.TrimSpace(line)
args := []string{}
buf := ""
var escaped, doubleQuoted, singleQuoted, backQuote bool
backtick := ""
for _, r := range line {
if escaped {
buf += string(r)
escaped = false
continue
}
if r == '\\' {
if singleQuoted {
buf += string(r)
} else {
escaped = true
}
continue
}
if isSpace(r) {
if singleQuoted || doubleQuoted || backQuote {
buf += string(r)
backtick += string(r)
} else if buf != "" {
if p.ParseEnv {
buf = replaceEnv(buf)
}
args = append(args, buf)
buf = ""
}
continue
}
switch r {
case '`':
if !singleQuoted && !doubleQuoted {
if p.ParseBacktick {
if backQuote {
out, err := shellRun(backtick)
if err != nil {
return nil, err
}
buf = out
}
backtick = ""
backQuote = !backQuote
continue
}
backtick = ""
backQuote = !backQuote
}
case '"':
if !singleQuoted {
doubleQuoted = !doubleQuoted
continue
}
case '\'':
if !doubleQuoted {
singleQuoted = !singleQuoted
continue
}
}
buf += string(r)
if backQuote {
backtick += string(r)
}
}
if buf != "" {
if p.ParseEnv {
buf = replaceEnv(buf)
}
args = append(args, buf)
}
if escaped || singleQuoted || doubleQuoted || backQuote {
return nil, errors.New("invalid command line string")
}
return args, nil
}
func Parse(line string) ([]string, error) {
return NewParser().Parse(line)
}

View file

@ -0,0 +1,19 @@
// +build !windows
package shellwords
import (
"errors"
"os"
"os/exec"
"strings"
)
func shellRun(line string) (string, error) {
shell := os.Getenv("SHELL")
b, err := exec.Command(shell, "-c", line).Output()
if err != nil {
return "", errors.New(err.Error() + ":" + string(b))
}
return strings.TrimSpace(string(b)), nil
}

View file

@ -0,0 +1,17 @@
package shellwords
import (
"errors"
"os"
"os/exec"
"strings"
)
func shellRun(line string) (string, error) {
shell := os.Getenv("COMSPEC")
b, err := exec.Command(shell, "/c", line).Output()
if err != nil {
return "", errors.New(err.Error() + ":" + string(b))
}
return strings.TrimSpace(string(b)), nil
}