From 9dc2d0c97729cd336d8a42d9a08f87ed531e06e1 Mon Sep 17 00:00:00 2001 From: Jimmy Xu Date: Fri, 5 Jan 2018 12:18:24 +0800 Subject: [PATCH 1/2] [update-vendor] update go-connection in vendor --- api/client/trust.go | 6 +- registry/registry.go | 10 +- registry/service_v1.go | 4 +- registry/service_v2.go | 4 +- .../docker/go-connections/CONTRIBUTING.md | 55 ++ .../github.com/docker/go-connections/LICENSE | 0 .../docker/go-connections/MAINTAINERS | 27 + .../docker/go-connections/README.md | 13 + .../github.com/docker/go-connections/doc.go | 3 + .../docker/go-connections/nat/nat.go | 173 ++--- .../docker/go-connections/nat/nat_test.go | 583 ++++++++++++++++ .../docker/go-connections/nat/parse.go | 1 + .../docker/go-connections/nat/parse_test.go | 54 ++ .../docker/go-connections/nat/sort.go | 0 .../docker/go-connections/nat/sort_test.go | 85 +++ .../docker/go-connections/proxy/logger.go | 11 + .../proxy/network_proxy_test.go | 216 ++++++ .../docker/go-connections/proxy/proxy.go | 36 + .../docker/go-connections/proxy/stub_proxy.go | 31 + .../docker/go-connections/proxy/tcp_proxy.go | 105 +++ .../docker/go-connections/proxy/udp_proxy.go | 176 +++++ .../docker/go-connections/sockets/README.md | 0 .../go-connections/sockets/inmem_socket.go | 8 - .../sockets/inmem_socket_test.go | 39 ++ .../docker/go-connections/sockets/proxy.go | 0 .../docker/go-connections/sockets/sockets.go | 16 +- .../go-connections/sockets/sockets_unix.go | 20 + .../go-connections/sockets/sockets_windows.go | 27 + .../go-connections/sockets/tcp_socket.go | 2 +- .../go-connections/sockets/unix_socket.go | 56 +- .../go-connections/tlsconfig/certpool_go17.go | 18 + .../tlsconfig/certpool_other.go | 14 + .../docker/go-connections/tlsconfig/config.go | 193 ++++-- .../tlsconfig/config_client_ciphers.go | 17 + .../tlsconfig/config_legacy_client_ciphers.go | 15 + .../go-connections/tlsconfig/config_test.go | 651 ++++++++++++++++++ .../tlsconfig/fixtures/cert.pem | 18 + .../fixtures/cert_of_encrypted_key.pem | 18 + .../tlsconfig/fixtures/encrypted_key.pem | 30 + .../go-connections/tlsconfig/fixtures/key.pem | 27 + .../tlsconfig/fixtures/multi.pem | 28 + 41 files changed, 2589 insertions(+), 201 deletions(-) mode change 100644 => 100755 api/client/trust.go create mode 100755 vendor/src/github.com/docker/go-connections/CONTRIBUTING.md mode change 100644 => 100755 vendor/src/github.com/docker/go-connections/LICENSE create mode 100755 vendor/src/github.com/docker/go-connections/MAINTAINERS create mode 100755 vendor/src/github.com/docker/go-connections/README.md create mode 100755 vendor/src/github.com/docker/go-connections/doc.go mode change 100644 => 100755 vendor/src/github.com/docker/go-connections/nat/nat.go create mode 100755 vendor/src/github.com/docker/go-connections/nat/nat_test.go mode change 100644 => 100755 vendor/src/github.com/docker/go-connections/nat/parse.go create mode 100755 vendor/src/github.com/docker/go-connections/nat/parse_test.go mode change 100644 => 100755 vendor/src/github.com/docker/go-connections/nat/sort.go create mode 100755 vendor/src/github.com/docker/go-connections/nat/sort_test.go create mode 100755 vendor/src/github.com/docker/go-connections/proxy/logger.go create mode 100755 vendor/src/github.com/docker/go-connections/proxy/network_proxy_test.go create mode 100755 vendor/src/github.com/docker/go-connections/proxy/proxy.go create mode 100755 vendor/src/github.com/docker/go-connections/proxy/stub_proxy.go create mode 100755 vendor/src/github.com/docker/go-connections/proxy/tcp_proxy.go create mode 100755 vendor/src/github.com/docker/go-connections/proxy/udp_proxy.go mode change 100644 => 100755 vendor/src/github.com/docker/go-connections/sockets/README.md mode change 100644 => 100755 vendor/src/github.com/docker/go-connections/sockets/inmem_socket.go create mode 100755 vendor/src/github.com/docker/go-connections/sockets/inmem_socket_test.go mode change 100644 => 100755 vendor/src/github.com/docker/go-connections/sockets/proxy.go mode change 100644 => 100755 vendor/src/github.com/docker/go-connections/sockets/sockets.go mode change 100644 => 100755 vendor/src/github.com/docker/go-connections/sockets/sockets_unix.go create mode 100755 vendor/src/github.com/docker/go-connections/sockets/sockets_windows.go mode change 100644 => 100755 vendor/src/github.com/docker/go-connections/sockets/tcp_socket.go mode change 100644 => 100755 vendor/src/github.com/docker/go-connections/sockets/unix_socket.go create mode 100755 vendor/src/github.com/docker/go-connections/tlsconfig/certpool_go17.go create mode 100755 vendor/src/github.com/docker/go-connections/tlsconfig/certpool_other.go mode change 100644 => 100755 vendor/src/github.com/docker/go-connections/tlsconfig/config.go create mode 100755 vendor/src/github.com/docker/go-connections/tlsconfig/config_client_ciphers.go create mode 100755 vendor/src/github.com/docker/go-connections/tlsconfig/config_legacy_client_ciphers.go create mode 100755 vendor/src/github.com/docker/go-connections/tlsconfig/config_test.go create mode 100755 vendor/src/github.com/docker/go-connections/tlsconfig/fixtures/cert.pem create mode 100755 vendor/src/github.com/docker/go-connections/tlsconfig/fixtures/cert_of_encrypted_key.pem create mode 100755 vendor/src/github.com/docker/go-connections/tlsconfig/fixtures/encrypted_key.pem create mode 100755 vendor/src/github.com/docker/go-connections/tlsconfig/fixtures/key.pem create mode 100755 vendor/src/github.com/docker/go-connections/tlsconfig/fixtures/multi.pem diff --git a/api/client/trust.go b/api/client/trust.go old mode 100644 new mode 100755 index 710ef8869..0789b8acb --- a/api/client/trust.go +++ b/api/client/trust.go @@ -114,7 +114,7 @@ func (cli *DockerCli) getNotaryRepository(repoInfo *registry.RepositoryInfo, aut return nil, err } - var cfg = tlsconfig.ClientDefault + var cfg = tlsconfig.ClientDefault() cfg.InsecureSkipVerify = !repoInfo.Index.Secure // Get certificate base directory @@ -124,7 +124,7 @@ func (cli *DockerCli) getNotaryRepository(repoInfo *registry.RepositoryInfo, aut } logrus.Debugf("reading certificate directory: %s", certDir) - if err := registry.ReadCertsDirectory(&cfg, certDir); err != nil { + if err := registry.ReadCertsDirectory(cfg, certDir); err != nil { return nil, err } @@ -136,7 +136,7 @@ func (cli *DockerCli) getNotaryRepository(repoInfo *registry.RepositoryInfo, aut DualStack: true, }).Dial, TLSHandshakeTimeout: 10 * time.Second, - TLSClientConfig: &cfg, + TLSClientConfig: cfg, DisableKeepAlives: true, } diff --git a/registry/registry.go b/registry/registry.go index 6214d41af..759a1d95c 100644 --- a/registry/registry.go +++ b/registry/registry.go @@ -38,19 +38,19 @@ func init() { func newTLSConfig(hostname string, isSecure bool) (*tls.Config, error) { // PreferredServerCipherSuites should have no effect - tlsConfig := tlsconfig.ServerDefault + tlsConfig := tlsconfig.ServerDefault() tlsConfig.InsecureSkipVerify = !isSecure if isSecure && CertsDir != "" { hostDir := filepath.Join(CertsDir, cleanPath(hostname)) logrus.Debugf("hostDir: %s", hostDir) - if err := ReadCertsDirectory(&tlsConfig, hostDir); err != nil { + if err := ReadCertsDirectory(tlsConfig, hostDir); err != nil { return nil, err } } - return &tlsConfig, nil + return tlsConfig, nil } func hasFile(files []os.FileInfo, name string) bool { @@ -218,8 +218,8 @@ func ContinueOnError(err error) bool { // default TLS configuration. func NewTransport(tlsConfig *tls.Config) *http.Transport { if tlsConfig == nil { - var cfg = tlsconfig.ServerDefault - tlsConfig = &cfg + var cfg = tlsconfig.ServerDefault() + tlsConfig = cfg } return &http.Transport{ Proxy: http.ProxyFromEnvironment, diff --git a/registry/service_v1.go b/registry/service_v1.go index 54569ff89..638acb313 100644 --- a/registry/service_v1.go +++ b/registry/service_v1.go @@ -9,8 +9,8 @@ import ( ) func (s *Service) lookupV1Endpoints(repoName reference.Named) (endpoints []APIEndpoint, err error) { - var cfg = tlsconfig.ServerDefault - tlsConfig := &cfg + var cfg = tlsconfig.ServerDefault() + tlsConfig := cfg nameString := repoName.FullName() if strings.HasPrefix(nameString, DefaultNamespace+"/") { endpoints = append(endpoints, APIEndpoint{ diff --git a/registry/service_v2.go b/registry/service_v2.go index cc8d06a5d..47077c94c 100644 --- a/registry/service_v2.go +++ b/registry/service_v2.go @@ -9,8 +9,8 @@ import ( ) func (s *Service) lookupV2Endpoints(repoName reference.Named) (endpoints []APIEndpoint, err error) { - var cfg = tlsconfig.ServerDefault - tlsConfig := &cfg + var cfg = tlsconfig.ServerDefault() + tlsConfig := cfg nameString := repoName.FullName() if strings.HasPrefix(nameString, DefaultNamespace+"/") { // v2 mirrors diff --git a/vendor/src/github.com/docker/go-connections/CONTRIBUTING.md b/vendor/src/github.com/docker/go-connections/CONTRIBUTING.md new file mode 100755 index 000000000..926dcc931 --- /dev/null +++ b/vendor/src/github.com/docker/go-connections/CONTRIBUTING.md @@ -0,0 +1,55 @@ +# Contributing to Docker + +### Sign your work + +The sign-off is a simple line at the end of the explanation for the patch. Your +signature certifies that you wrote the patch or otherwise have the right to pass +it on as an open-source patch. The rules are pretty simple: if you can certify +the below (from [developercertificate.org](http://developercertificate.org/)): + +``` +Developer Certificate of Origin +Version 1.1 + +Copyright (C) 2004, 2006 The Linux Foundation and its contributors. +660 York Street, Suite 102, +San Francisco, CA 94110 USA + +Everyone is permitted to copy and distribute verbatim copies of this +license document, but changing it is not allowed. + +Developer's Certificate of Origin 1.1 + +By making a contribution to this project, I certify that: + +(a) The contribution was created in whole or in part by me and I + have the right to submit it under the open source license + indicated in the file; or + +(b) The contribution is based upon previous work that, to the best + of my knowledge, is covered under an appropriate open source + license and I have the right under that license to submit that + work with modifications, whether created in whole or in part + by me, under the same open source license (unless I am + permitted to submit under a different license), as indicated + in the file; or + +(c) The contribution was provided directly to me by some other + person who certified (a), (b) or (c) and I have not modified + it. + +(d) I understand and agree that this project and the contribution + are public and that a record of the contribution (including all + personal information I submit with it, including my sign-off) is + maintained indefinitely and may be redistributed consistent with + this project or the open source license(s) involved. +``` + +Then you just add a line to every git commit message: + + Signed-off-by: Joe Smith + +Use your real name (sorry, no pseudonyms or anonymous contributions.) + +If you set your `user.name` and `user.email` git configs, you can sign your +commit automatically with `git commit -s`. diff --git a/vendor/src/github.com/docker/go-connections/LICENSE b/vendor/src/github.com/docker/go-connections/LICENSE old mode 100644 new mode 100755 diff --git a/vendor/src/github.com/docker/go-connections/MAINTAINERS b/vendor/src/github.com/docker/go-connections/MAINTAINERS new file mode 100755 index 000000000..477be8b21 --- /dev/null +++ b/vendor/src/github.com/docker/go-connections/MAINTAINERS @@ -0,0 +1,27 @@ +# go-connections maintainers file +# +# This file describes who runs the docker/go-connections project and how. +# This is a living document - if you see something out of date or missing, speak up! +# +# It is structured to be consumable by both humans and programs. +# To extract its contents programmatically, use any TOML-compliant parser. +# +# This file is compiled into the MAINTAINERS file in docker/opensource. +# +[Org] + [Org."Core maintainers"] + people = [ + "calavera", + ] + +[people] + +# A reference list of all people associated with the project. +# All other sections should refer to people by their canonical key +# in the people section. + + # ADD YOURSELF HERE IN ALPHABETICAL ORDER + [people.calavera] + Name = "David Calavera" + Email = "david.calavera@gmail.com" + GitHub = "calavera" diff --git a/vendor/src/github.com/docker/go-connections/README.md b/vendor/src/github.com/docker/go-connections/README.md new file mode 100755 index 000000000..d257e44fd --- /dev/null +++ b/vendor/src/github.com/docker/go-connections/README.md @@ -0,0 +1,13 @@ +[![GoDoc](https://godoc.org/github.com/docker/go-connections?status.svg)](https://godoc.org/github.com/docker/go-connections) + +# Introduction + +go-connections provides common package to work with network connections. + +## Usage + +See the [docs in godoc](https://godoc.org/github.com/docker/go-connections) for examples and documentation. + +## License + +go-connections is licensed under the Apache License, Version 2.0. See [LICENSE](LICENSE) for the full license text. diff --git a/vendor/src/github.com/docker/go-connections/doc.go b/vendor/src/github.com/docker/go-connections/doc.go new file mode 100755 index 000000000..43e27247d --- /dev/null +++ b/vendor/src/github.com/docker/go-connections/doc.go @@ -0,0 +1,3 @@ +// Package connections provides libraries to work with network connections. +// This library is divided in several components for specific usage. +package connections diff --git a/vendor/src/github.com/docker/go-connections/nat/nat.go b/vendor/src/github.com/docker/go-connections/nat/nat.go old mode 100644 new mode 100755 index 3d469165a..4d5f5ae63 --- a/vendor/src/github.com/docker/go-connections/nat/nat.go +++ b/vendor/src/github.com/docker/go-connections/nat/nat.go @@ -85,14 +85,10 @@ func (p Port) Port() string { // Int returns the port number of a Port as an int func (p Port) Int() int { portStr := p.Port() - if len(portStr) == 0 { - return 0 - } - // We don't need to check for an error because we're going to // assume that any error would have been found, and reported, in NewPort() - port, _ := strconv.ParseUint(portStr, 10, 16) - return int(port) + port, _ := ParsePort(portStr) + return port } // Range returns the start/end port numbers of a Port range as ints @@ -132,92 +128,115 @@ func ParsePortSpecs(ports []string) (map[Port]struct{}, map[Port][]PortBinding, exposedPorts = make(map[Port]struct{}, len(ports)) bindings = make(map[Port][]PortBinding) ) - for _, rawPort := range ports { - proto := "tcp" - - if i := strings.LastIndex(rawPort, "/"); i != -1 { - proto = rawPort[i+1:] - rawPort = rawPort[:i] - } - if !strings.Contains(rawPort, ":") { - rawPort = fmt.Sprintf("::%s", rawPort) - } else if len(strings.Split(rawPort, ":")) == 2 { - rawPort = fmt.Sprintf(":%s", rawPort) - } - - parts, err := PartParser(portSpecTemplate, rawPort) + portMappings, err := ParsePortSpec(rawPort) if err != nil { return nil, nil, err } - var ( - containerPort = parts["containerPort"] - rawIP = parts["ip"] - hostPort = parts["hostPort"] - ) - - if rawIP != "" && net.ParseIP(rawIP) == nil { - return nil, nil, fmt.Errorf("Invalid ip address: %s", rawIP) - } - if containerPort == "" { - return nil, nil, fmt.Errorf("No port specified: %s", rawPort) + for _, portMapping := range portMappings { + port := portMapping.Port + if _, exists := exposedPorts[port]; !exists { + exposedPorts[port] = struct{}{} + } + bslice, exists := bindings[port] + if !exists { + bslice = []PortBinding{} + } + bindings[port] = append(bslice, portMapping.Binding) } + } + return exposedPorts, bindings, nil +} + +// PortMapping is a data object mapping a Port to a PortBinding +type PortMapping struct { + Port Port + Binding PortBinding +} + +func splitParts(rawport string) (string, string, string) { + parts := strings.Split(rawport, ":") + n := len(parts) + containerport := parts[n-1] + + switch n { + case 1: + return "", "", containerport + case 2: + return "", parts[0], containerport + case 3: + return parts[0], parts[1], containerport + default: + return strings.Join(parts[:n-2], ":"), parts[n-2], containerport + } +} + +// ParsePortSpec parses a port specification string into a slice of PortMappings +func ParsePortSpec(rawPort string) ([]PortMapping, error) { + var proto string + rawIP, hostPort, containerPort := splitParts(rawPort) + proto, containerPort = SplitProtoPort(containerPort) + + // Strip [] from IPV6 addresses + ip, _, err := net.SplitHostPort(rawIP + ":") + if err != nil { + return nil, fmt.Errorf("Invalid ip address %v: %s", rawIP, err) + } + if ip != "" && net.ParseIP(ip) == nil { + return nil, fmt.Errorf("Invalid ip address: %s", ip) + } + if containerPort == "" { + return nil, fmt.Errorf("No port specified: %s", rawPort) + } + + startPort, endPort, err := ParsePortRange(containerPort) + if err != nil { + return nil, fmt.Errorf("Invalid containerPort: %s", containerPort) + } - startPort, endPort, err := ParsePortRange(containerPort) + var startHostPort, endHostPort uint64 = 0, 0 + if len(hostPort) > 0 { + startHostPort, endHostPort, err = ParsePortRange(hostPort) if err != nil { - return nil, nil, fmt.Errorf("Invalid containerPort: %s", containerPort) + return nil, fmt.Errorf("Invalid hostPort: %s", hostPort) } + } - var startHostPort, endHostPort uint64 = 0, 0 - if len(hostPort) > 0 { - startHostPort, endHostPort, err = ParsePortRange(hostPort) - if err != nil { - return nil, nil, fmt.Errorf("Invalid hostPort: %s", hostPort) - } + if hostPort != "" && (endPort-startPort) != (endHostPort-startHostPort) { + // Allow host port range iff containerPort is not a range. + // In this case, use the host port range as the dynamic + // host port range to allocate into. + if endPort != startPort { + return nil, fmt.Errorf("Invalid ranges specified for container and host Ports: %s and %s", containerPort, hostPort) } + } - if hostPort != "" && (endPort-startPort) != (endHostPort-startHostPort) { - // Allow host port range iff containerPort is not a range. - // In this case, use the host port range as the dynamic - // host port range to allocate into. - if endPort != startPort { - return nil, nil, fmt.Errorf("Invalid ranges specified for container and host Ports: %s and %s", containerPort, hostPort) - } - } + if !validateProto(strings.ToLower(proto)) { + return nil, fmt.Errorf("Invalid proto: %s", proto) + } - if !validateProto(strings.ToLower(proto)) { - return nil, nil, fmt.Errorf("Invalid proto: %s", proto) + ports := []PortMapping{} + for i := uint64(0); i <= (endPort - startPort); i++ { + containerPort = strconv.FormatUint(startPort+i, 10) + if len(hostPort) > 0 { + hostPort = strconv.FormatUint(startHostPort+i, 10) + } + // Set hostPort to a range only if there is a single container port + // and a dynamic host port. + if startPort == endPort && startHostPort != endHostPort { + hostPort = fmt.Sprintf("%s-%s", hostPort, strconv.FormatUint(endHostPort, 10)) + } + port, err := NewPort(strings.ToLower(proto), containerPort) + if err != nil { + return nil, err } - for i := uint64(0); i <= (endPort - startPort); i++ { - containerPort = strconv.FormatUint(startPort+i, 10) - if len(hostPort) > 0 { - hostPort = strconv.FormatUint(startHostPort+i, 10) - } - // Set hostPort to a range only if there is a single container port - // and a dynamic host port. - if startPort == endPort && startHostPort != endHostPort { - hostPort = fmt.Sprintf("%s-%s", hostPort, strconv.FormatUint(endHostPort, 10)) - } - port, err := NewPort(strings.ToLower(proto), containerPort) - if err != nil { - return nil, nil, err - } - if _, exists := exposedPorts[port]; !exists { - exposedPorts[port] = struct{}{} - } - - binding := PortBinding{ - HostIP: rawIP, - HostPort: hostPort, - } - bslice, exists := bindings[port] - if !exists { - bslice = []PortBinding{} - } - bindings[port] = append(bslice, binding) + binding := PortBinding{ + HostIP: ip, + HostPort: hostPort, } + ports = append(ports, PortMapping{Port: port, Binding: binding}) } - return exposedPorts, bindings, nil + return ports, nil } diff --git a/vendor/src/github.com/docker/go-connections/nat/nat_test.go b/vendor/src/github.com/docker/go-connections/nat/nat_test.go new file mode 100755 index 000000000..787d5ac23 --- /dev/null +++ b/vendor/src/github.com/docker/go-connections/nat/nat_test.go @@ -0,0 +1,583 @@ +package nat + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestParsePort(t *testing.T) { + var ( + p int + err error + ) + + p, err = ParsePort("1234") + + if err != nil || p != 1234 { + t.Fatal("Parsing '1234' did not succeed") + } + + // FIXME currently this is a valid port. I don't think it should be. + // I'm leaving this test commented out until we make a decision. + // - erikh + + /* + p, err = ParsePort("0123") + + if err != nil { + t.Fatal("Successfully parsed port '0123' to '123'") + } + */ + + p, err = ParsePort("asdf") + + if err == nil || p != 0 { + t.Fatal("Parsing port 'asdf' succeeded") + } + + p, err = ParsePort("1asdf") + + if err == nil || p != 0 { + t.Fatal("Parsing port '1asdf' succeeded") + } +} + +func TestParsePortRangeToInt(t *testing.T) { + var ( + begin int + end int + err error + ) + + type TestRange struct { + Range string + Begin int + End int + } + validRanges := []TestRange{ + {"1234", 1234, 1234}, + {"1234-1234", 1234, 1234}, + {"1234-1235", 1234, 1235}, + {"8000-9000", 8000, 9000}, + {"0", 0, 0}, + {"0-0", 0, 0}, + } + + for _, r := range validRanges { + begin, end, err = ParsePortRangeToInt(r.Range) + + if err != nil || begin != r.Begin { + t.Fatalf("Parsing port range '%s' did not succeed. Expected begin %d, got %d", r.Range, r.Begin, begin) + } + if err != nil || end != r.End { + t.Fatalf("Parsing port range '%s' did not succeed. Expected end %d, got %d", r.Range, r.End, end) + } + } + + invalidRanges := []string{ + "asdf", + "1asdf", + "9000-8000", + "9000-", + "-8000", + "-8000-", + } + + for _, r := range invalidRanges { + begin, end, err = ParsePortRangeToInt(r) + + if err == nil || begin != 0 || end != 0 { + t.Fatalf("Parsing port range '%s' succeeded", r) + } + } +} + +func TestPort(t *testing.T) { + p, err := NewPort("tcp", "1234") + + if err != nil { + t.Fatalf("tcp, 1234 had a parsing issue: %v", err) + } + + if string(p) != "1234/tcp" { + t.Fatal("tcp, 1234 did not result in the string 1234/tcp") + } + + if p.Proto() != "tcp" { + t.Fatal("protocol was not tcp") + } + + if p.Port() != "1234" { + t.Fatal("port string value was not 1234") + } + + if p.Int() != 1234 { + t.Fatal("port int value was not 1234") + } + + p, err = NewPort("tcp", "asd1234") + if err == nil { + t.Fatal("tcp, asd1234 was supposed to fail") + } + + p, err = NewPort("tcp", "1234-1230") + if err == nil { + t.Fatal("tcp, 1234-1230 was supposed to fail") + } + + p, err = NewPort("tcp", "1234-1242") + if err != nil { + t.Fatalf("tcp, 1234-1242 had a parsing issue: %v", err) + } + + if string(p) != "1234-1242/tcp" { + t.Fatal("tcp, 1234-1242 did not result in the string 1234-1242/tcp") + } +} + +func TestSplitProtoPort(t *testing.T) { + var ( + proto string + port string + ) + + proto, port = SplitProtoPort("1234/tcp") + + if proto != "tcp" || port != "1234" { + t.Fatal("Could not split 1234/tcp properly") + } + + proto, port = SplitProtoPort("") + + if proto != "" || port != "" { + t.Fatal("parsing an empty string yielded surprising results", proto, port) + } + + proto, port = SplitProtoPort("1234") + + if proto != "tcp" || port != "1234" { + t.Fatal("tcp is not the default protocol for portspec '1234'", proto, port) + } + + proto, port = SplitProtoPort("1234/") + + if proto != "tcp" || port != "1234" { + t.Fatal("parsing '1234/' yielded:" + port + "/" + proto) + } + + proto, port = SplitProtoPort("/tcp") + + if proto != "" || port != "" { + t.Fatal("parsing '/tcp' yielded:" + port + "/" + proto) + } +} + +func TestParsePortSpecFull(t *testing.T) { + portMappings, err := ParsePortSpec("0.0.0.0:1234-1235:3333-3334/tcp") + assert.Nil(t, err) + + expected := []PortMapping{ + { + Port: "3333/tcp", + Binding: PortBinding{ + HostIP: "0.0.0.0", + HostPort: "1234", + }, + }, + { + Port: "3334/tcp", + Binding: PortBinding{ + HostIP: "0.0.0.0", + HostPort: "1235", + }, + }, + } + + assert.Equal(t, expected, portMappings) +} + +func TestPartPortSpecIPV6(t *testing.T) { + portMappings, err := ParsePortSpec("[2001:4860:0:2001::68]::333") + assert.Nil(t, err) + + expected := []PortMapping{ + { + Port: "333/tcp", + Binding: PortBinding{ + HostIP: "2001:4860:0:2001::68", + HostPort: "", + }, + }, + } + assert.Equal(t, expected, portMappings) +} + +func TestPartPortSpecIPV6WithHostPort(t *testing.T) { + portMappings, err := ParsePortSpec("[::1]:80:80") + assert.Nil(t, err) + + expected := []PortMapping{ + { + Port: "80/tcp", + Binding: PortBinding{ + HostIP: "::1", + HostPort: "80", + }, + }, + } + assert.Equal(t, expected, portMappings) +} + +func TestParsePortSpecs(t *testing.T) { + var ( + portMap map[Port]struct{} + bindingMap map[Port][]PortBinding + err error + ) + + portMap, bindingMap, err = ParsePortSpecs([]string{"1234/tcp", "2345/udp"}) + + if err != nil { + t.Fatalf("Error while processing ParsePortSpecs: %s", err) + } + + if _, ok := portMap[Port("1234/tcp")]; !ok { + t.Fatal("1234/tcp was not parsed properly") + } + + if _, ok := portMap[Port("2345/udp")]; !ok { + t.Fatal("2345/udp was not parsed properly") + } + + for portspec, bindings := range bindingMap { + if len(bindings) != 1 { + t.Fatalf("%s should have exactly one binding", portspec) + } + + if bindings[0].HostIP != "" { + t.Fatalf("HostIP should not be set for %s", portspec) + } + + if bindings[0].HostPort != "" { + t.Fatalf("HostPort should not be set for %s", portspec) + } + } + + portMap, bindingMap, err = ParsePortSpecs([]string{"1234:1234/tcp", "2345:2345/udp"}) + + if err != nil { + t.Fatalf("Error while processing ParsePortSpecs: %s", err) + } + + if _, ok := portMap[Port("1234/tcp")]; !ok { + t.Fatal("1234/tcp was not parsed properly") + } + + if _, ok := portMap[Port("2345/udp")]; !ok { + t.Fatal("2345/udp was not parsed properly") + } + + for portspec, bindings := range bindingMap { + _, port := SplitProtoPort(string(portspec)) + + if len(bindings) != 1 { + t.Fatalf("%s should have exactly one binding", portspec) + } + + if bindings[0].HostIP != "" { + t.Fatalf("HostIP should not be set for %s", portspec) + } + + if bindings[0].HostPort != port { + t.Fatalf("HostPort should be %s for %s", port, portspec) + } + } + + portMap, bindingMap, err = ParsePortSpecs([]string{"0.0.0.0:1234:1234/tcp", "0.0.0.0:2345:2345/udp"}) + + if err != nil { + t.Fatalf("Error while processing ParsePortSpecs: %s", err) + } + + if _, ok := portMap[Port("1234/tcp")]; !ok { + t.Fatal("1234/tcp was not parsed properly") + } + + if _, ok := portMap[Port("2345/udp")]; !ok { + t.Fatal("2345/udp was not parsed properly") + } + + for portspec, bindings := range bindingMap { + _, port := SplitProtoPort(string(portspec)) + + if len(bindings) != 1 { + t.Fatalf("%s should have exactly one binding", portspec) + } + + if bindings[0].HostIP != "0.0.0.0" { + t.Fatalf("HostIP is not 0.0.0.0 for %s", portspec) + } + + if bindings[0].HostPort != port { + t.Fatalf("HostPort should be %s for %s", port, portspec) + } + } + + _, _, err = ParsePortSpecs([]string{"localhost:1234:1234/tcp"}) + + if err == nil { + t.Fatal("Received no error while trying to parse a hostname instead of ip") + } +} + +func TestParsePortSpecsWithRange(t *testing.T) { + var ( + portMap map[Port]struct{} + bindingMap map[Port][]PortBinding + err error + ) + + portMap, bindingMap, err = ParsePortSpecs([]string{"1234-1236/tcp", "2345-2347/udp"}) + + if err != nil { + t.Fatalf("Error while processing ParsePortSpecs: %s", err) + } + + if _, ok := portMap[Port("1235/tcp")]; !ok { + t.Fatal("1234/tcp was not parsed properly") + } + + if _, ok := portMap[Port("2346/udp")]; !ok { + t.Fatal("2345/udp was not parsed properly") + } + + for portspec, bindings := range bindingMap { + if len(bindings) != 1 { + t.Fatalf("%s should have exactly one binding", portspec) + } + + if bindings[0].HostIP != "" { + t.Fatalf("HostIP should not be set for %s", portspec) + } + + if bindings[0].HostPort != "" { + t.Fatalf("HostPort should not be set for %s", portspec) + } + } + + portMap, bindingMap, err = ParsePortSpecs([]string{"1234-1236:1234-1236/tcp", "2345-2347:2345-2347/udp"}) + + if err != nil { + t.Fatalf("Error while processing ParsePortSpecs: %s", err) + } + + if _, ok := portMap[Port("1235/tcp")]; !ok { + t.Fatal("1234/tcp was not parsed properly") + } + + if _, ok := portMap[Port("2346/udp")]; !ok { + t.Fatal("2345/udp was not parsed properly") + } + + for portspec, bindings := range bindingMap { + _, port := SplitProtoPort(string(portspec)) + if len(bindings) != 1 { + t.Fatalf("%s should have exactly one binding", portspec) + } + + if bindings[0].HostIP != "" { + t.Fatalf("HostIP should not be set for %s", portspec) + } + + if bindings[0].HostPort != port { + t.Fatalf("HostPort should be %s for %s", port, portspec) + } + } + + portMap, bindingMap, err = ParsePortSpecs([]string{"0.0.0.0:1234-1236:1234-1236/tcp", "0.0.0.0:2345-2347:2345-2347/udp"}) + + if err != nil { + t.Fatalf("Error while processing ParsePortSpecs: %s", err) + } + + if _, ok := portMap[Port("1235/tcp")]; !ok { + t.Fatal("1234/tcp was not parsed properly") + } + + if _, ok := portMap[Port("2346/udp")]; !ok { + t.Fatal("2345/udp was not parsed properly") + } + + for portspec, bindings := range bindingMap { + _, port := SplitProtoPort(string(portspec)) + if len(bindings) != 1 || bindings[0].HostIP != "0.0.0.0" || bindings[0].HostPort != port { + t.Fatalf("Expect single binding to port %s but found %s", port, bindings) + } + } + + _, _, err = ParsePortSpecs([]string{"localhost:1234-1236:1234-1236/tcp"}) + + if err == nil { + t.Fatal("Received no error while trying to parse a hostname instead of ip") + } +} + +func TestParseNetworkOptsPrivateOnly(t *testing.T) { + ports, bindings, err := ParsePortSpecs([]string{"192.168.1.100::80"}) + if err != nil { + t.Fatal(err) + } + if len(ports) != 1 { + t.Logf("Expected 1 got %d", len(ports)) + t.FailNow() + } + if len(bindings) != 1 { + t.Logf("Expected 1 got %d", len(bindings)) + t.FailNow() + } + for k := range ports { + if k.Proto() != "tcp" { + t.Logf("Expected tcp got %s", k.Proto()) + t.Fail() + } + if k.Port() != "80" { + t.Logf("Expected 80 got %s", k.Port()) + t.Fail() + } + b, exists := bindings[k] + if !exists { + t.Log("Binding does not exist") + t.FailNow() + } + if len(b) != 1 { + t.Logf("Expected 1 got %d", len(b)) + t.FailNow() + } + s := b[0] + if s.HostPort != "" { + t.Logf("Expected \"\" got %s", s.HostPort) + t.Fail() + } + if s.HostIP != "192.168.1.100" { + t.Fail() + } + } +} + +func TestParseNetworkOptsPublic(t *testing.T) { + ports, bindings, err := ParsePortSpecs([]string{"192.168.1.100:8080:80"}) + if err != nil { + t.Fatal(err) + } + if len(ports) != 1 { + t.Logf("Expected 1 got %d", len(ports)) + t.FailNow() + } + if len(bindings) != 1 { + t.Logf("Expected 1 got %d", len(bindings)) + t.FailNow() + } + for k := range ports { + if k.Proto() != "tcp" { + t.Logf("Expected tcp got %s", k.Proto()) + t.Fail() + } + if k.Port() != "80" { + t.Logf("Expected 80 got %s", k.Port()) + t.Fail() + } + b, exists := bindings[k] + if !exists { + t.Log("Binding does not exist") + t.FailNow() + } + if len(b) != 1 { + t.Logf("Expected 1 got %d", len(b)) + t.FailNow() + } + s := b[0] + if s.HostPort != "8080" { + t.Logf("Expected 8080 got %s", s.HostPort) + t.Fail() + } + if s.HostIP != "192.168.1.100" { + t.Fail() + } + } +} + +func TestParseNetworkOptsPublicNoPort(t *testing.T) { + ports, bindings, err := ParsePortSpecs([]string{"192.168.1.100"}) + + if err == nil { + t.Logf("Expected error Invalid containerPort") + t.Fail() + } + if ports != nil { + t.Logf("Expected nil got %s", ports) + t.Fail() + } + if bindings != nil { + t.Logf("Expected nil got %s", bindings) + t.Fail() + } +} + +func TestParseNetworkOptsNegativePorts(t *testing.T) { + ports, bindings, err := ParsePortSpecs([]string{"192.168.1.100:-1:-1"}) + + if err == nil { + t.Fail() + } + if len(ports) != 0 { + t.Logf("Expected nil got %d", len(ports)) + t.Fail() + } + if len(bindings) != 0 { + t.Logf("Expected 0 got %d", len(bindings)) + t.Fail() + } +} + +func TestParseNetworkOptsUdp(t *testing.T) { + ports, bindings, err := ParsePortSpecs([]string{"192.168.1.100::6000/udp"}) + if err != nil { + t.Fatal(err) + } + if len(ports) != 1 { + t.Logf("Expected 1 got %d", len(ports)) + t.FailNow() + } + if len(bindings) != 1 { + t.Logf("Expected 1 got %d", len(bindings)) + t.FailNow() + } + for k := range ports { + if k.Proto() != "udp" { + t.Logf("Expected udp got %s", k.Proto()) + t.Fail() + } + if k.Port() != "6000" { + t.Logf("Expected 6000 got %s", k.Port()) + t.Fail() + } + b, exists := bindings[k] + if !exists { + t.Log("Binding does not exist") + t.FailNow() + } + if len(b) != 1 { + t.Logf("Expected 1 got %d", len(b)) + t.FailNow() + } + s := b[0] + if s.HostPort != "" { + t.Logf("Expected \"\" got %s", s.HostPort) + t.Fail() + } + if s.HostIP != "192.168.1.100" { + t.Fail() + } + } +} diff --git a/vendor/src/github.com/docker/go-connections/nat/parse.go b/vendor/src/github.com/docker/go-connections/nat/parse.go old mode 100644 new mode 100755 index 872050205..892adf8c6 --- a/vendor/src/github.com/docker/go-connections/nat/parse.go +++ b/vendor/src/github.com/docker/go-connections/nat/parse.go @@ -8,6 +8,7 @@ import ( // PartParser parses and validates the specified string (data) using the specified template // e.g. ip:public:private -> 192.168.0.1:80:8000 +// DEPRECATED: do not use, this function may be removed in a future version func PartParser(template, data string) (map[string]string, error) { // ip:public:private var ( diff --git a/vendor/src/github.com/docker/go-connections/nat/parse_test.go b/vendor/src/github.com/docker/go-connections/nat/parse_test.go new file mode 100755 index 000000000..2ac204a05 --- /dev/null +++ b/vendor/src/github.com/docker/go-connections/nat/parse_test.go @@ -0,0 +1,54 @@ +package nat + +import ( + "strings" + "testing" +) + +func TestParsePortRange(t *testing.T) { + if start, end, err := ParsePortRange("8000-8080"); err != nil || start != 8000 || end != 8080 { + t.Fatalf("Error: %s or Expecting {start,end} values {8000,8080} but found {%d,%d}.", err, start, end) + } +} + +func TestParsePortRangeEmpty(t *testing.T) { + if _, _, err := ParsePortRange(""); err == nil || err.Error() != "Empty string specified for ports." { + t.Fatalf("Expected error 'Empty string specified for ports.', got %v", err) + } +} + +func TestParsePortRangeWithNoRange(t *testing.T) { + start, end, err := ParsePortRange("8080") + if err != nil { + t.Fatal(err) + } + if start != 8080 || end != 8080 { + t.Fatalf("Expected start and end to be the same and equal to 8080, but were %v and %v", start, end) + } +} + +func TestParsePortRangeIncorrectRange(t *testing.T) { + if _, _, err := ParsePortRange("9000-8080"); err == nil || !strings.Contains(err.Error(), "Invalid range specified for the Port") { + t.Fatalf("Expecting error 'Invalid range specified for the Port' but received %s.", err) + } +} + +func TestParsePortRangeIncorrectEndRange(t *testing.T) { + if _, _, err := ParsePortRange("8000-a"); err == nil || !strings.Contains(err.Error(), "invalid syntax") { + t.Fatalf("Expecting error 'Invalid range specified for the Port' but received %s.", err) + } + + if _, _, err := ParsePortRange("8000-30a"); err == nil || !strings.Contains(err.Error(), "invalid syntax") { + t.Fatalf("Expecting error 'Invalid range specified for the Port' but received %s.", err) + } +} + +func TestParsePortRangeIncorrectStartRange(t *testing.T) { + if _, _, err := ParsePortRange("a-8000"); err == nil || !strings.Contains(err.Error(), "invalid syntax") { + t.Fatalf("Expecting error 'Invalid range specified for the Port' but received %s.", err) + } + + if _, _, err := ParsePortRange("30a-8000"); err == nil || !strings.Contains(err.Error(), "invalid syntax") { + t.Fatalf("Expecting error 'Invalid range specified for the Port' but received %s.", err) + } +} diff --git a/vendor/src/github.com/docker/go-connections/nat/sort.go b/vendor/src/github.com/docker/go-connections/nat/sort.go old mode 100644 new mode 100755 diff --git a/vendor/src/github.com/docker/go-connections/nat/sort_test.go b/vendor/src/github.com/docker/go-connections/nat/sort_test.go new file mode 100755 index 000000000..88ed91115 --- /dev/null +++ b/vendor/src/github.com/docker/go-connections/nat/sort_test.go @@ -0,0 +1,85 @@ +package nat + +import ( + "fmt" + "reflect" + "testing" +) + +func TestSortUniquePorts(t *testing.T) { + ports := []Port{ + Port("6379/tcp"), + Port("22/tcp"), + } + + Sort(ports, func(ip, jp Port) bool { + return ip.Int() < jp.Int() || (ip.Int() == jp.Int() && ip.Proto() == "tcp") + }) + + first := ports[0] + if fmt.Sprint(first) != "22/tcp" { + t.Log(fmt.Sprint(first)) + t.Fail() + } +} + +func TestSortSamePortWithDifferentProto(t *testing.T) { + ports := []Port{ + Port("8888/tcp"), + Port("8888/udp"), + Port("6379/tcp"), + Port("6379/udp"), + } + + Sort(ports, func(ip, jp Port) bool { + return ip.Int() < jp.Int() || (ip.Int() == jp.Int() && ip.Proto() == "tcp") + }) + + first := ports[0] + if fmt.Sprint(first) != "6379/tcp" { + t.Fail() + } +} + +func TestSortPortMap(t *testing.T) { + ports := []Port{ + Port("22/tcp"), + Port("22/udp"), + Port("8000/tcp"), + Port("6379/tcp"), + Port("9999/tcp"), + } + + portMap := PortMap{ + Port("22/tcp"): []PortBinding{ + {}, + }, + Port("8000/tcp"): []PortBinding{ + {}, + }, + Port("6379/tcp"): []PortBinding{ + {}, + {HostIP: "0.0.0.0", HostPort: "32749"}, + }, + Port("9999/tcp"): []PortBinding{ + {HostIP: "0.0.0.0", HostPort: "40000"}, + }, + } + + SortPortMap(ports, portMap) + if !reflect.DeepEqual(ports, []Port{ + Port("9999/tcp"), + Port("6379/tcp"), + Port("8000/tcp"), + Port("22/tcp"), + Port("22/udp"), + }) { + t.Errorf("failed to prioritize port with explicit mappings, got %v", ports) + } + if pm := portMap[Port("6379/tcp")]; !reflect.DeepEqual(pm, []PortBinding{ + {HostIP: "0.0.0.0", HostPort: "32749"}, + {}, + }) { + t.Errorf("failed to prioritize bindings with explicit mappings, got %v", pm) + } +} diff --git a/vendor/src/github.com/docker/go-connections/proxy/logger.go b/vendor/src/github.com/docker/go-connections/proxy/logger.go new file mode 100755 index 000000000..cd8b5fd48 --- /dev/null +++ b/vendor/src/github.com/docker/go-connections/proxy/logger.go @@ -0,0 +1,11 @@ +package proxy + +type logger interface { + Printf(format string, args ...interface{}) +} + +type noopLogger struct{} + +func (l *noopLogger) Printf(_ string, _ ...interface{}) { + // Do nothing :) +} diff --git a/vendor/src/github.com/docker/go-connections/proxy/network_proxy_test.go b/vendor/src/github.com/docker/go-connections/proxy/network_proxy_test.go new file mode 100755 index 000000000..9a7354801 --- /dev/null +++ b/vendor/src/github.com/docker/go-connections/proxy/network_proxy_test.go @@ -0,0 +1,216 @@ +package proxy + +import ( + "bytes" + "fmt" + "io" + "net" + "strings" + "testing" + "time" +) + +var testBuf = []byte("Buffalo buffalo Buffalo buffalo buffalo buffalo Buffalo buffalo") +var testBufSize = len(testBuf) + +type EchoServer interface { + Run() + Close() + LocalAddr() net.Addr +} + +type TCPEchoServer struct { + listener net.Listener + testCtx *testing.T +} + +type UDPEchoServer struct { + conn net.PacketConn + testCtx *testing.T +} + +func NewEchoServer(t *testing.T, proto, address string) EchoServer { + var server EchoServer + if strings.HasPrefix(proto, "tcp") { + listener, err := net.Listen(proto, address) + if err != nil { + t.Fatal(err) + } + server = &TCPEchoServer{listener: listener, testCtx: t} + } else { + socket, err := net.ListenPacket(proto, address) + if err != nil { + t.Fatal(err) + } + server = &UDPEchoServer{conn: socket, testCtx: t} + } + return server +} + +func (server *TCPEchoServer) Run() { + go func() { + for { + client, err := server.listener.Accept() + if err != nil { + return + } + go func(client net.Conn) { + if _, err := io.Copy(client, client); err != nil { + server.testCtx.Logf("can't echo to the client: %v\n", err.Error()) + } + client.Close() + }(client) + } + }() +} + +func (server *TCPEchoServer) LocalAddr() net.Addr { return server.listener.Addr() } +func (server *TCPEchoServer) Close() { server.listener.Close() } + +func (server *UDPEchoServer) Run() { + go func() { + readBuf := make([]byte, 1024) + for { + read, from, err := server.conn.ReadFrom(readBuf) + if err != nil { + return + } + for i := 0; i != read; { + written, err := server.conn.WriteTo(readBuf[i:read], from) + if err != nil { + break + } + i += written + } + } + }() +} + +func (server *UDPEchoServer) LocalAddr() net.Addr { return server.conn.LocalAddr() } +func (server *UDPEchoServer) Close() { server.conn.Close() } + +func testProxyAt(t *testing.T, proto string, proxy Proxy, addr string) { + defer proxy.Close() + go proxy.Run() + client, err := net.Dial(proto, addr) + if err != nil { + t.Fatalf("Can't connect to the proxy: %v", err) + } + defer client.Close() + client.SetDeadline(time.Now().Add(10 * time.Second)) + if _, err = client.Write(testBuf); err != nil { + t.Fatal(err) + } + recvBuf := make([]byte, testBufSize) + if _, err = client.Read(recvBuf); err != nil { + t.Fatal(err) + } + if !bytes.Equal(testBuf, recvBuf) { + t.Fatal(fmt.Errorf("Expected [%v] but got [%v]", testBuf, recvBuf)) + } +} + +func testProxy(t *testing.T, proto string, proxy Proxy) { + testProxyAt(t, proto, proxy, proxy.FrontendAddr().String()) +} + +func TestTCP4Proxy(t *testing.T) { + backend := NewEchoServer(t, "tcp", "127.0.0.1:0") + defer backend.Close() + backend.Run() + frontendAddr := &net.TCPAddr{IP: net.IPv4(127, 0, 0, 1), Port: 0} + proxy, err := NewProxy(frontendAddr, backend.LocalAddr()) + if err != nil { + t.Fatal(err) + } + testProxy(t, "tcp", proxy) +} + +func TestTCP6Proxy(t *testing.T) { + backend := NewEchoServer(t, "tcp", "[::1]:0") + defer backend.Close() + backend.Run() + frontendAddr := &net.TCPAddr{IP: net.IPv6loopback, Port: 0} + proxy, err := NewProxy(frontendAddr, backend.LocalAddr()) + if err != nil { + t.Fatal(err) + } + testProxy(t, "tcp", proxy) +} + +func TestTCPDualStackProxy(t *testing.T) { + // If I understand `godoc -src net favoriteAddrFamily` (used by the + // net.Listen* functions) correctly this should work, but it doesn't. + t.Skip("No support for dual stack yet") + backend := NewEchoServer(t, "tcp", "[::1]:0") + defer backend.Close() + backend.Run() + frontendAddr := &net.TCPAddr{IP: net.IPv6loopback, Port: 0} + proxy, err := NewProxy(frontendAddr, backend.LocalAddr()) + if err != nil { + t.Fatal(err) + } + ipv4ProxyAddr := &net.TCPAddr{ + IP: net.IPv4(127, 0, 0, 1), + Port: proxy.FrontendAddr().(*net.TCPAddr).Port, + } + testProxyAt(t, "tcp", proxy, ipv4ProxyAddr.String()) +} + +func TestUDP4Proxy(t *testing.T) { + backend := NewEchoServer(t, "udp", "127.0.0.1:0") + defer backend.Close() + backend.Run() + frontendAddr := &net.UDPAddr{IP: net.IPv4(127, 0, 0, 1), Port: 0} + proxy, err := NewProxy(frontendAddr, backend.LocalAddr()) + if err != nil { + t.Fatal(err) + } + testProxy(t, "udp", proxy) +} + +func TestUDP6Proxy(t *testing.T) { + backend := NewEchoServer(t, "udp", "[::1]:0") + defer backend.Close() + backend.Run() + frontendAddr := &net.UDPAddr{IP: net.IPv6loopback, Port: 0} + proxy, err := NewProxy(frontendAddr, backend.LocalAddr()) + if err != nil { + t.Fatal(err) + } + testProxy(t, "udp", proxy) +} + +func TestUDPWriteError(t *testing.T) { + frontendAddr := &net.UDPAddr{IP: net.IPv4(127, 0, 0, 1), Port: 0} + // Hopefully, this port will be free: */ + backendAddr := &net.UDPAddr{IP: net.IPv4(127, 0, 0, 1), Port: 25587} + proxy, err := NewProxy(frontendAddr, backendAddr) + if err != nil { + t.Fatal(err) + } + defer proxy.Close() + go proxy.Run() + client, err := net.Dial("udp", "127.0.0.1:25587") + if err != nil { + t.Fatalf("Can't connect to the proxy: %v", err) + } + defer client.Close() + // Make sure the proxy doesn't stop when there is no actual backend: + client.Write(testBuf) + client.Write(testBuf) + backend := NewEchoServer(t, "udp", "127.0.0.1:25587") + defer backend.Close() + backend.Run() + client.SetDeadline(time.Now().Add(10 * time.Second)) + if _, err = client.Write(testBuf); err != nil { + t.Fatal(err) + } + recvBuf := make([]byte, testBufSize) + if _, err = client.Read(recvBuf); err != nil { + t.Fatal(err) + } + if !bytes.Equal(testBuf, recvBuf) { + t.Fatal(fmt.Errorf("Expected [%v] but got [%v]", testBuf, recvBuf)) + } +} diff --git a/vendor/src/github.com/docker/go-connections/proxy/proxy.go b/vendor/src/github.com/docker/go-connections/proxy/proxy.go new file mode 100755 index 000000000..537aebb7c --- /dev/null +++ b/vendor/src/github.com/docker/go-connections/proxy/proxy.go @@ -0,0 +1,36 @@ +// Package proxy provides a network Proxy interface and implementations for TCP and UDP. +package proxy + +import ( + "fmt" + "net" +) + +// Proxy defines the behavior of a proxy. It forwards traffic back and forth +// between two endpoints : the frontend and the backend. +// It can be used to do software port-mapping between two addresses. +// e.g. forward all traffic between the frontend (host) 127.0.0.1:3000 +// to the backend (container) at 172.17.42.108:4000. +type Proxy interface { + // Run starts forwarding traffic back and forth between the front + // and back-end addresses. + Run() + // Close stops forwarding traffic and close both ends of the Proxy. + Close() + // FrontendAddr returns the address on which the proxy is listening. + FrontendAddr() net.Addr + // BackendAddr returns the proxied address. + BackendAddr() net.Addr +} + +// NewProxy creates a Proxy according to the specified frontendAddr and backendAddr. +func NewProxy(frontendAddr, backendAddr net.Addr) (Proxy, error) { + switch frontendAddr.(type) { + case *net.UDPAddr: + return NewUDPProxy(frontendAddr.(*net.UDPAddr), backendAddr.(*net.UDPAddr)) + case *net.TCPAddr: + return NewTCPProxy(frontendAddr.(*net.TCPAddr), backendAddr.(*net.TCPAddr)) + default: + panic(fmt.Errorf("Unsupported protocol")) + } +} diff --git a/vendor/src/github.com/docker/go-connections/proxy/stub_proxy.go b/vendor/src/github.com/docker/go-connections/proxy/stub_proxy.go new file mode 100755 index 000000000..571749e46 --- /dev/null +++ b/vendor/src/github.com/docker/go-connections/proxy/stub_proxy.go @@ -0,0 +1,31 @@ +package proxy + +import ( + "net" +) + +// StubProxy is a proxy that is a stub (does nothing). +type StubProxy struct { + frontendAddr net.Addr + backendAddr net.Addr +} + +// Run does nothing. +func (p *StubProxy) Run() {} + +// Close does nothing. +func (p *StubProxy) Close() {} + +// FrontendAddr returns the frontend address. +func (p *StubProxy) FrontendAddr() net.Addr { return p.frontendAddr } + +// BackendAddr returns the backend address. +func (p *StubProxy) BackendAddr() net.Addr { return p.backendAddr } + +// NewStubProxy creates a new StubProxy +func NewStubProxy(frontendAddr, backendAddr net.Addr) (Proxy, error) { + return &StubProxy{ + frontendAddr: frontendAddr, + backendAddr: backendAddr, + }, nil +} diff --git a/vendor/src/github.com/docker/go-connections/proxy/tcp_proxy.go b/vendor/src/github.com/docker/go-connections/proxy/tcp_proxy.go new file mode 100755 index 000000000..8d97196d1 --- /dev/null +++ b/vendor/src/github.com/docker/go-connections/proxy/tcp_proxy.go @@ -0,0 +1,105 @@ +package proxy + +import ( + "io" + "net" + "syscall" +) + +// TCPProxy is a proxy for TCP connections. It implements the Proxy interface to +// handle TCP traffic forwarding between the frontend and backend addresses. +type TCPProxy struct { + Logger logger + listener *net.TCPListener + frontendAddr *net.TCPAddr + backendAddr *net.TCPAddr +} + +// NewTCPProxy creates a new TCPProxy. +func NewTCPProxy(frontendAddr, backendAddr *net.TCPAddr, ops ...func(*TCPProxy)) (*TCPProxy, error) { + listener, err := net.ListenTCP("tcp", frontendAddr) + if err != nil { + return nil, err + } + // If the port in frontendAddr was 0 then ListenTCP will have a picked + // a port to listen on, hence the call to Addr to get that actual port: + proxy := &TCPProxy{ + listener: listener, + frontendAddr: listener.Addr().(*net.TCPAddr), + backendAddr: backendAddr, + Logger: &noopLogger{}, + } + + for _, op := range ops { + op(proxy) + } + + return proxy, nil +} + +func (proxy *TCPProxy) clientLoop(client *net.TCPConn, quit chan bool) { + backend, err := net.DialTCP("tcp", nil, proxy.backendAddr) + if err != nil { + proxy.Logger.Printf("Can't forward traffic to backend tcp/%v: %s\n", proxy.backendAddr, err) + client.Close() + return + } + + event := make(chan int64) + var broker = func(to, from *net.TCPConn) { + written, err := io.Copy(to, from) + if err != nil { + // If the socket we are writing to is shutdown with + // SHUT_WR, forward it to the other end of the pipe: + if err, ok := err.(*net.OpError); ok && err.Err == syscall.EPIPE { + from.CloseWrite() + } + } + to.CloseRead() + event <- written + } + + go broker(client, backend) + go broker(backend, client) + + var transferred int64 + for i := 0; i < 2; i++ { + select { + case written := <-event: + transferred += written + case <-quit: + // Interrupt the two brokers and "join" them. + client.Close() + backend.Close() + for ; i < 2; i++ { + transferred += <-event + } + return + } + } + client.Close() + backend.Close() +} + +// Run starts forwarding the traffic using TCP. +func (proxy *TCPProxy) Run() { + quit := make(chan bool) + defer close(quit) + for { + client, err := proxy.listener.Accept() + if err != nil { + proxy.Logger.Printf("Stopping proxy on tcp/%v for tcp/%v (%s)", proxy.frontendAddr, proxy.backendAddr, err) + return + } + go proxy.clientLoop(client.(*net.TCPConn), quit) + } +} + +// Close stops forwarding the traffic. +func (proxy *TCPProxy) Close() { proxy.listener.Close() } + +// FrontendAddr returns the TCP address on which the proxy is listening. +func (proxy *TCPProxy) FrontendAddr() net.Addr { return proxy.frontendAddr } + +// BackendAddr returns the TCP proxied address. +func (proxy *TCPProxy) BackendAddr() net.Addr { return proxy.backendAddr } diff --git a/vendor/src/github.com/docker/go-connections/proxy/udp_proxy.go b/vendor/src/github.com/docker/go-connections/proxy/udp_proxy.go new file mode 100755 index 000000000..71ddf1268 --- /dev/null +++ b/vendor/src/github.com/docker/go-connections/proxy/udp_proxy.go @@ -0,0 +1,176 @@ +package proxy + +import ( + "encoding/binary" + "net" + "strings" + "sync" + "syscall" + "time" +) + +const ( + // UDPConnTrackTimeout is the timeout used for UDP connection tracking + UDPConnTrackTimeout = 90 * time.Second + // UDPBufSize is the buffer size for the UDP proxy + UDPBufSize = 65507 +) + +// A net.Addr where the IP is split into two fields so you can use it as a key +// in a map: +type connTrackKey struct { + IPHigh uint64 + IPLow uint64 + Port int +} + +func newConnTrackKey(addr *net.UDPAddr) *connTrackKey { + if len(addr.IP) == net.IPv4len { + return &connTrackKey{ + IPHigh: 0, + IPLow: uint64(binary.BigEndian.Uint32(addr.IP)), + Port: addr.Port, + } + } + return &connTrackKey{ + IPHigh: binary.BigEndian.Uint64(addr.IP[:8]), + IPLow: binary.BigEndian.Uint64(addr.IP[8:]), + Port: addr.Port, + } +} + +type connTrackMap map[connTrackKey]*net.UDPConn + +// UDPProxy is proxy for which handles UDP datagrams. It implements the Proxy +// interface to handle UDP traffic forwarding between the frontend and backend +// addresses. +type UDPProxy struct { + Logger logger + listener *net.UDPConn + frontendAddr *net.UDPAddr + backendAddr *net.UDPAddr + connTrackTable connTrackMap + connTrackLock sync.Mutex +} + +// NewUDPProxy creates a new UDPProxy. +func NewUDPProxy(frontendAddr, backendAddr *net.UDPAddr, ops ...func(*UDPProxy)) (*UDPProxy, error) { + listener, err := net.ListenUDP("udp", frontendAddr) + if err != nil { + return nil, err + } + + proxy := &UDPProxy{ + listener: listener, + frontendAddr: listener.LocalAddr().(*net.UDPAddr), + backendAddr: backendAddr, + connTrackTable: make(connTrackMap), + Logger: &noopLogger{}, + } + + for _, op := range ops { + op(proxy) + } + + return proxy, nil +} + +func (proxy *UDPProxy) replyLoop(proxyConn *net.UDPConn, clientAddr *net.UDPAddr, clientKey *connTrackKey) { + defer func() { + proxy.connTrackLock.Lock() + delete(proxy.connTrackTable, *clientKey) + proxy.connTrackLock.Unlock() + proxyConn.Close() + }() + + readBuf := make([]byte, UDPBufSize) + for { + proxyConn.SetReadDeadline(time.Now().Add(UDPConnTrackTimeout)) + again: + read, err := proxyConn.Read(readBuf) + if err != nil { + if err, ok := err.(*net.OpError); ok && err.Err == syscall.ECONNREFUSED { + // This will happen if the last write failed + // (e.g: nothing is actually listening on the + // proxied port on the container), ignore it + // and continue until UDPConnTrackTimeout + // expires: + goto again + } + return + } + for i := 0; i != read; { + written, err := proxy.listener.WriteToUDP(readBuf[i:read], clientAddr) + if err != nil { + return + } + i += written + } + } +} + +// Run starts forwarding the traffic using UDP. +func (proxy *UDPProxy) Run() { + readBuf := make([]byte, UDPBufSize) + for { + read, from, err := proxy.listener.ReadFromUDP(readBuf) + if err != nil { + // NOTE: Apparently ReadFrom doesn't return + // ECONNREFUSED like Read do (see comment in + // UDPProxy.replyLoop) + if !isClosedError(err) { + proxy.Logger.Printf("Stopping proxy on udp/%v for udp/%v (%s)", proxy.frontendAddr, proxy.backendAddr, err) + } + break + } + + fromKey := newConnTrackKey(from) + proxy.connTrackLock.Lock() + proxyConn, hit := proxy.connTrackTable[*fromKey] + if !hit { + proxyConn, err = net.DialUDP("udp", nil, proxy.backendAddr) + if err != nil { + proxy.Logger.Printf("Can't proxy a datagram to udp/%s: %s\n", proxy.backendAddr, err) + proxy.connTrackLock.Unlock() + continue + } + proxy.connTrackTable[*fromKey] = proxyConn + go proxy.replyLoop(proxyConn, from, fromKey) + } + proxy.connTrackLock.Unlock() + for i := 0; i != read; { + written, err := proxyConn.Write(readBuf[i:read]) + if err != nil { + proxy.Logger.Printf("Can't proxy a datagram to udp/%s: %s\n", proxy.backendAddr, err) + break + } + i += written + } + } +} + +// Close stops forwarding the traffic. +func (proxy *UDPProxy) Close() { + proxy.listener.Close() + proxy.connTrackLock.Lock() + defer proxy.connTrackLock.Unlock() + for _, conn := range proxy.connTrackTable { + conn.Close() + } +} + +// FrontendAddr returns the UDP address on which the proxy is listening. +func (proxy *UDPProxy) FrontendAddr() net.Addr { return proxy.frontendAddr } + +// BackendAddr returns the proxied UDP address. +func (proxy *UDPProxy) BackendAddr() net.Addr { return proxy.backendAddr } + +func isClosedError(err error) bool { + /* This comparison is ugly, but unfortunately, net.go doesn't export errClosing. + * See: + * http://golang.org/src/pkg/net/net.go + * https://code.google.com/p/go/issues/detail?id=4337 + * https://groups.google.com/forum/#!msg/golang-nuts/0_aaCvBmOcM/SptmDyX1XJMJ + */ + return strings.HasSuffix(err.Error(), "use of closed network connection") +} diff --git a/vendor/src/github.com/docker/go-connections/sockets/README.md b/vendor/src/github.com/docker/go-connections/sockets/README.md old mode 100644 new mode 100755 diff --git a/vendor/src/github.com/docker/go-connections/sockets/inmem_socket.go b/vendor/src/github.com/docker/go-connections/sockets/inmem_socket.go old mode 100644 new mode 100755 index 3395e4022..99846ffdd --- a/vendor/src/github.com/docker/go-connections/sockets/inmem_socket.go +++ b/vendor/src/github.com/docker/go-connections/sockets/inmem_socket.go @@ -79,11 +79,3 @@ func (a dummyAddr) Network() string { func (a dummyAddr) String() string { return string(a) } - -// timeoutError is used when there is a timeout with a connection -// this implements the net.Error interface -type timeoutError struct{} - -func (e *timeoutError) Error() string { return "i/o timeout" } -func (e *timeoutError) Timeout() bool { return true } -func (e *timeoutError) Temporary() bool { return true } diff --git a/vendor/src/github.com/docker/go-connections/sockets/inmem_socket_test.go b/vendor/src/github.com/docker/go-connections/sockets/inmem_socket_test.go new file mode 100755 index 000000000..24dc1d101 --- /dev/null +++ b/vendor/src/github.com/docker/go-connections/sockets/inmem_socket_test.go @@ -0,0 +1,39 @@ +package sockets + +import "testing" + +func TestInmemSocket(t *testing.T) { + l := NewInmemSocket("test", 0) + defer l.Close() + go func() { + for { + conn, err := l.Accept() + if err != nil { + return + } + conn.Write([]byte("hello")) + conn.Close() + } + }() + + conn, err := l.Dial("test", "test") + if err != nil { + t.Fatal(err) + } + + buf := make([]byte, 5) + _, err = conn.Read(buf) + if err != nil { + t.Fatal(err) + } + + if string(buf) != "hello" { + t.Fatalf("expected `hello`, got %s", string(buf)) + } + + l.Close() + conn, err = l.Dial("test", "test") + if err != errClosed { + t.Fatalf("expected `errClosed` error, got %v", err) + } +} diff --git a/vendor/src/github.com/docker/go-connections/sockets/proxy.go b/vendor/src/github.com/docker/go-connections/sockets/proxy.go old mode 100644 new mode 100755 diff --git a/vendor/src/github.com/docker/go-connections/sockets/sockets.go b/vendor/src/github.com/docker/go-connections/sockets/sockets.go old mode 100644 new mode 100755 index 1739cecf2..a1d7beb4d --- a/vendor/src/github.com/docker/go-connections/sockets/sockets.go +++ b/vendor/src/github.com/docker/go-connections/sockets/sockets.go @@ -2,6 +2,7 @@ package sockets import ( + "errors" "net" "net/http" "time" @@ -10,6 +11,9 @@ import ( // Why 32? See https://github.com/docker/docker/pull/8035. const defaultTimeout = 32 * time.Second +// ErrProtocolNotAvailable is returned when a given transport protocol is not provided by the operating system. +var ErrProtocolNotAvailable = errors.New("protocol not available") + // ConfigureTransport configures the specified Transport according to the // specified proto and addr. // If the proto is unix (using a unix socket to communicate) or npipe the @@ -17,17 +21,9 @@ const defaultTimeout = 32 * time.Second func ConfigureTransport(tr *http.Transport, proto, addr string) error { switch proto { case "unix": - // No need for compression in local communications. - tr.DisableCompression = true - tr.Dial = func(_, _ string) (net.Conn, error) { - return net.DialTimeout(proto, addr, defaultTimeout) - } + return configureUnixTransport(tr, proto, addr) case "npipe": - // No need for compression in local communications. - tr.DisableCompression = true - tr.Dial = func(_, _ string) (net.Conn, error) { - return DialPipe(addr, defaultTimeout) - } + return configureNpipeTransport(tr, proto, addr) default: tr.Proxy = http.ProxyFromEnvironment dialer, err := DialerFromEnvironment(&net.Dialer{ diff --git a/vendor/src/github.com/docker/go-connections/sockets/sockets_unix.go b/vendor/src/github.com/docker/go-connections/sockets/sockets_unix.go old mode 100644 new mode 100755 index b255ac9ac..386cf0dbb --- a/vendor/src/github.com/docker/go-connections/sockets/sockets_unix.go +++ b/vendor/src/github.com/docker/go-connections/sockets/sockets_unix.go @@ -3,11 +3,31 @@ package sockets import ( + "fmt" "net" + "net/http" "syscall" "time" ) +const maxUnixSocketPathSize = len(syscall.RawSockaddrUnix{}.Path) + +func configureUnixTransport(tr *http.Transport, proto, addr string) error { + if len(addr) > maxUnixSocketPathSize { + return fmt.Errorf("Unix socket path %q is too long", addr) + } + // No need for compression in local communications. + tr.DisableCompression = true + tr.Dial = func(_, _ string) (net.Conn, error) { + return net.DialTimeout(proto, addr, defaultTimeout) + } + return nil +} + +func configureNpipeTransport(tr *http.Transport, proto, addr string) error { + return ErrProtocolNotAvailable +} + // DialPipe connects to a Windows named pipe. // This is not supported on other OSes. func DialPipe(_ string, _ time.Duration) (net.Conn, error) { diff --git a/vendor/src/github.com/docker/go-connections/sockets/sockets_windows.go b/vendor/src/github.com/docker/go-connections/sockets/sockets_windows.go new file mode 100755 index 000000000..5c21644e1 --- /dev/null +++ b/vendor/src/github.com/docker/go-connections/sockets/sockets_windows.go @@ -0,0 +1,27 @@ +package sockets + +import ( + "net" + "net/http" + "time" + + "github.com/Microsoft/go-winio" +) + +func configureUnixTransport(tr *http.Transport, proto, addr string) error { + return ErrProtocolNotAvailable +} + +func configureNpipeTransport(tr *http.Transport, proto, addr string) error { + // No need for compression in local communications. + tr.DisableCompression = true + tr.Dial = func(_, _ string) (net.Conn, error) { + return DialPipe(addr, defaultTimeout) + } + return nil +} + +// DialPipe connects to a Windows named pipe. +func DialPipe(addr string, timeout time.Duration) (net.Conn, error) { + return winio.DialPipe(addr, &timeout) +} diff --git a/vendor/src/github.com/docker/go-connections/sockets/tcp_socket.go b/vendor/src/github.com/docker/go-connections/sockets/tcp_socket.go old mode 100644 new mode 100755 index 8a82727df..53cbb6c79 --- a/vendor/src/github.com/docker/go-connections/sockets/tcp_socket.go +++ b/vendor/src/github.com/docker/go-connections/sockets/tcp_socket.go @@ -7,7 +7,7 @@ import ( ) // NewTCPSocket creates a TCP socket listener with the specified address and -// and the specified tls configuration. If TLSConfig is set, will encapsulate the +// the specified tls configuration. If TLSConfig is set, will encapsulate the // TCP listener inside a TLS one. func NewTCPSocket(addr string, tlsConfig *tls.Config) (net.Listener, error) { l, err := net.Listen("tcp", addr) diff --git a/vendor/src/github.com/docker/go-connections/sockets/unix_socket.go b/vendor/src/github.com/docker/go-connections/sockets/unix_socket.go old mode 100644 new mode 100755 index c10acedca..a8b5dbb6f --- a/vendor/src/github.com/docker/go-connections/sockets/unix_socket.go +++ b/vendor/src/github.com/docker/go-connections/sockets/unix_socket.go @@ -1,30 +1,26 @@ -// +build linux freebsd +// +build !windows package sockets import ( - "fmt" "net" "os" - "strconv" "syscall" - - "github.com/Sirupsen/logrus" - "github.com/opencontainers/runc/libcontainer/user" ) // NewUnixSocket creates a unix socket with the specified path and group. -func NewUnixSocket(path, group string) (net.Listener, error) { +func NewUnixSocket(path string, gid int) (net.Listener, error) { if err := syscall.Unlink(path); err != nil && !os.IsNotExist(err) { return nil, err } mask := syscall.Umask(0777) defer syscall.Umask(mask) + l, err := net.Listen("unix", path) if err != nil { return nil, err } - if err := setSocketGroup(path, group); err != nil { + if err := os.Chown(path, 0, gid); err != nil { l.Close() return nil, err } @@ -34,47 +30,3 @@ func NewUnixSocket(path, group string) (net.Listener, error) { } return l, nil } - -func setSocketGroup(path, group string) error { - if group == "" { - return nil - } - if err := changeGroup(path, group); err != nil { - if group != "docker" { - return err - } - logrus.Debugf("Warning: could not change group %s to docker: %v", path, err) - } - return nil -} - -func changeGroup(path string, nameOrGid string) error { - gid, err := lookupGidByName(nameOrGid) - if err != nil { - return err - } - logrus.Debugf("%s group found. gid: %d", nameOrGid, gid) - return os.Chown(path, 0, gid) -} - -func lookupGidByName(nameOrGid string) (int, error) { - groupFile, err := user.GetGroupPath() - if err != nil { - return -1, err - } - groups, err := user.ParseGroupFileFilter(groupFile, func(g user.Group) bool { - return g.Name == nameOrGid || strconv.Itoa(g.Gid) == nameOrGid - }) - if err != nil { - return -1, err - } - if groups != nil && len(groups) > 0 { - return groups[0].Gid, nil - } - gid, err := strconv.Atoi(nameOrGid) - if err == nil { - logrus.Warnf("Could not find GID %d", gid) - return gid, nil - } - return -1, fmt.Errorf("Group %s not found", nameOrGid) -} diff --git a/vendor/src/github.com/docker/go-connections/tlsconfig/certpool_go17.go b/vendor/src/github.com/docker/go-connections/tlsconfig/certpool_go17.go new file mode 100755 index 000000000..1ca0965e0 --- /dev/null +++ b/vendor/src/github.com/docker/go-connections/tlsconfig/certpool_go17.go @@ -0,0 +1,18 @@ +// +build go1.7 + +package tlsconfig + +import ( + "crypto/x509" + "runtime" +) + +// SystemCertPool returns a copy of the system cert pool, +// returns an error if failed to load or empty pool on windows. +func SystemCertPool() (*x509.CertPool, error) { + certpool, err := x509.SystemCertPool() + if err != nil && runtime.GOOS == "windows" { + return x509.NewCertPool(), nil + } + return certpool, err +} diff --git a/vendor/src/github.com/docker/go-connections/tlsconfig/certpool_other.go b/vendor/src/github.com/docker/go-connections/tlsconfig/certpool_other.go new file mode 100755 index 000000000..9ca974539 --- /dev/null +++ b/vendor/src/github.com/docker/go-connections/tlsconfig/certpool_other.go @@ -0,0 +1,14 @@ +// +build !go1.7 + +package tlsconfig + +import ( + "crypto/x509" + +) + +// SystemCertPool returns an new empty cert pool, +// accessing system cert pool is supported in go 1.7 +func SystemCertPool() (*x509.CertPool, error) { + return x509.NewCertPool(), nil +} diff --git a/vendor/src/github.com/docker/go-connections/tlsconfig/config.go b/vendor/src/github.com/docker/go-connections/tlsconfig/config.go old mode 100644 new mode 100755 index e3dfad1f0..1b31bbb8b --- a/vendor/src/github.com/docker/go-connections/tlsconfig/config.go +++ b/vendor/src/github.com/docker/go-connections/tlsconfig/config.go @@ -8,11 +8,12 @@ package tlsconfig import ( "crypto/tls" "crypto/x509" + "encoding/pem" "fmt" "io/ioutil" "os" - "github.com/Sirupsen/logrus" + "github.com/pkg/errors" ) // Options represents the information needed to create client and server TLS configurations. @@ -29,6 +30,14 @@ type Options struct { InsecureSkipVerify bool // server-only option ClientAuth tls.ClientAuthType + // If ExclusiveRootPools is set, then if a CA file is provided, the root pool used for TLS + // creds will include exclusively the roots in that CA file. If no CA file is provided, + // the system pool will be used. + ExclusiveRootPools bool + MinVersion uint16 + // If Passphrase is set, it will be used to decrypt a TLS private key + // if the key is encrypted + Passphrase string } // Extra (server-side) accepted CBC cipher suites - will phase out in the future @@ -41,78 +50,175 @@ var acceptedCBCCiphers = []uint16{ tls.TLS_RSA_WITH_AES_128_CBC_SHA, } -// Client TLS cipher suites (dropping CBC ciphers for client preferred suite set) -var clientCipherSuites = []uint16{ - tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, - tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, -} - // DefaultServerAcceptedCiphers should be uses by code which already has a crypto/tls // options struct but wants to use a commonly accepted set of TLS cipher suites, with // known weak algorithms removed. var DefaultServerAcceptedCiphers = append(clientCipherSuites, acceptedCBCCiphers...) -// ServerDefault is a secure-enough TLS configuration for the server TLS configuration. -var ServerDefault = tls.Config{ - // Avoid fallback to SSL protocols < TLS1.0 - MinVersion: tls.VersionTLS10, - PreferServerCipherSuites: true, - CipherSuites: DefaultServerAcceptedCiphers, +// allTLSVersions lists all the TLS versions and is used by the code that validates +// a uint16 value as a TLS version. +var allTLSVersions = map[uint16]struct{}{ + tls.VersionSSL30: {}, + tls.VersionTLS10: {}, + tls.VersionTLS11: {}, + tls.VersionTLS12: {}, } -// ClientDefault is a secure-enough TLS configuration for the client TLS configuration. -var ClientDefault = tls.Config{ - // Prefer TLS1.2 as the client minimum - MinVersion: tls.VersionTLS12, - CipherSuites: clientCipherSuites, +// ServerDefault returns a secure-enough TLS configuration for the server TLS configuration. +func ServerDefault() *tls.Config { + return &tls.Config{ + // Avoid fallback to SSL protocols < TLS1.0 + MinVersion: tls.VersionTLS10, + PreferServerCipherSuites: true, + CipherSuites: DefaultServerAcceptedCiphers, + } +} + +// ClientDefault returns a secure-enough TLS configuration for the client TLS configuration. +func ClientDefault() *tls.Config { + return &tls.Config{ + // Prefer TLS1.2 as the client minimum + MinVersion: tls.VersionTLS12, + CipherSuites: clientCipherSuites, + } } // certPool returns an X.509 certificate pool from `caFile`, the certificate file. -func certPool(caFile string) (*x509.CertPool, error) { +func certPool(caFile string, exclusivePool bool) (*x509.CertPool, error) { // If we should verify the server, we need to load a trusted ca - certPool := x509.NewCertPool() + var ( + certPool *x509.CertPool + err error + ) + if exclusivePool { + certPool = x509.NewCertPool() + } else { + certPool, err = SystemCertPool() + if err != nil { + return nil, fmt.Errorf("failed to read system certificates: %v", err) + } + } pem, err := ioutil.ReadFile(caFile) if err != nil { - return nil, fmt.Errorf("Could not read CA certificate %q: %v", caFile, err) + return nil, fmt.Errorf("could not read CA certificate %q: %v", caFile, err) } if !certPool.AppendCertsFromPEM(pem) { return nil, fmt.Errorf("failed to append certificates from PEM file: %q", caFile) } - s := certPool.Subjects() - subjects := make([]string, len(s)) - for i, subject := range s { - subjects[i] = string(subject) - } - logrus.Debugf("Trusting certs with subjects: %v", subjects) return certPool, nil } +// isValidMinVersion checks that the input value is a valid tls minimum version +func isValidMinVersion(version uint16) bool { + _, ok := allTLSVersions[version] + return ok +} + +// adjustMinVersion sets the MinVersion on `config`, the input configuration. +// It assumes the current MinVersion on the `config` is the lowest allowed. +func adjustMinVersion(options Options, config *tls.Config) error { + if options.MinVersion > 0 { + if !isValidMinVersion(options.MinVersion) { + return fmt.Errorf("Invalid minimum TLS version: %x", options.MinVersion) + } + if options.MinVersion < config.MinVersion { + return fmt.Errorf("Requested minimum TLS version is too low. Should be at-least: %x", config.MinVersion) + } + config.MinVersion = options.MinVersion + } + + return nil +} + +// IsErrEncryptedKey returns true if the 'err' is an error of incorrect +// password when tryin to decrypt a TLS private key +func IsErrEncryptedKey(err error) bool { + return errors.Cause(err) == x509.IncorrectPasswordError +} + +// getPrivateKey returns the private key in 'keyBytes', in PEM-encoded format. +// If the private key is encrypted, 'passphrase' is used to decrypted the +// private key. +func getPrivateKey(keyBytes []byte, passphrase string) ([]byte, error) { + // this section makes some small changes to code from notary/tuf/utils/x509.go + pemBlock, _ := pem.Decode(keyBytes) + if pemBlock == nil { + return nil, fmt.Errorf("no valid private key found") + } + + var err error + if x509.IsEncryptedPEMBlock(pemBlock) { + keyBytes, err = x509.DecryptPEMBlock(pemBlock, []byte(passphrase)) + if err != nil { + return nil, errors.Wrap(err, "private key is encrypted, but could not decrypt it") + } + keyBytes = pem.EncodeToMemory(&pem.Block{Type: pemBlock.Type, Bytes: keyBytes}) + } + + return keyBytes, nil +} + +// getCert returns a Certificate from the CertFile and KeyFile in 'options', +// if the key is encrypted, the Passphrase in 'options' will be used to +// decrypt it. +func getCert(options Options) ([]tls.Certificate, error) { + if options.CertFile == "" && options.KeyFile == "" { + return nil, nil + } + + errMessage := "Could not load X509 key pair" + + cert, err := ioutil.ReadFile(options.CertFile) + if err != nil { + return nil, errors.Wrap(err, errMessage) + } + + prKeyBytes, err := ioutil.ReadFile(options.KeyFile) + if err != nil { + return nil, errors.Wrap(err, errMessage) + } + + prKeyBytes, err = getPrivateKey(prKeyBytes, options.Passphrase) + if err != nil { + return nil, errors.Wrap(err, errMessage) + } + + tlsCert, err := tls.X509KeyPair(cert, prKeyBytes) + if err != nil { + return nil, errors.Wrap(err, errMessage) + } + + return []tls.Certificate{tlsCert}, nil +} + // Client returns a TLS configuration meant to be used by a client. func Client(options Options) (*tls.Config, error) { - tlsConfig := ClientDefault + tlsConfig := ClientDefault() tlsConfig.InsecureSkipVerify = options.InsecureSkipVerify - if !options.InsecureSkipVerify { - CAs, err := certPool(options.CAFile) + if !options.InsecureSkipVerify && options.CAFile != "" { + CAs, err := certPool(options.CAFile, options.ExclusiveRootPools) if err != nil { return nil, err } tlsConfig.RootCAs = CAs } - if options.CertFile != "" && options.KeyFile != "" { - tlsCert, err := tls.LoadX509KeyPair(options.CertFile, options.KeyFile) - if err != nil { - return nil, fmt.Errorf("Could not load X509 key pair: %v. Make sure the key is not encrypted", err) - } - tlsConfig.Certificates = []tls.Certificate{tlsCert} + tlsCerts, err := getCert(options) + if err != nil { + return nil, err + } + tlsConfig.Certificates = tlsCerts + + if err := adjustMinVersion(options, tlsConfig); err != nil { + return nil, err } - return &tlsConfig, nil + return tlsConfig, nil } // Server returns a TLS configuration meant to be used by a server. func Server(options Options) (*tls.Config, error) { - tlsConfig := ServerDefault + tlsConfig := ServerDefault() tlsConfig.ClientAuth = options.ClientAuth tlsCert, err := tls.LoadX509KeyPair(options.CertFile, options.KeyFile) if err != nil { @@ -122,12 +228,17 @@ func Server(options Options) (*tls.Config, error) { return nil, fmt.Errorf("Error reading X509 key pair (cert: %q, key: %q): %v. Make sure the key is not encrypted.", options.CertFile, options.KeyFile, err) } tlsConfig.Certificates = []tls.Certificate{tlsCert} - if options.ClientAuth >= tls.VerifyClientCertIfGiven { - CAs, err := certPool(options.CAFile) + if options.ClientAuth >= tls.VerifyClientCertIfGiven && options.CAFile != "" { + CAs, err := certPool(options.CAFile, options.ExclusiveRootPools) if err != nil { return nil, err } tlsConfig.ClientCAs = CAs } - return &tlsConfig, nil + + if err := adjustMinVersion(options, tlsConfig); err != nil { + return nil, err + } + + return tlsConfig, nil } diff --git a/vendor/src/github.com/docker/go-connections/tlsconfig/config_client_ciphers.go b/vendor/src/github.com/docker/go-connections/tlsconfig/config_client_ciphers.go new file mode 100755 index 000000000..6b4c6a7c0 --- /dev/null +++ b/vendor/src/github.com/docker/go-connections/tlsconfig/config_client_ciphers.go @@ -0,0 +1,17 @@ +// +build go1.5 + +// Package tlsconfig provides primitives to retrieve secure-enough TLS configurations for both clients and servers. +// +package tlsconfig + +import ( + "crypto/tls" +) + +// Client TLS cipher suites (dropping CBC ciphers for client preferred suite set) +var clientCipherSuites = []uint16{ + tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, + tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, + tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, + tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, +} diff --git a/vendor/src/github.com/docker/go-connections/tlsconfig/config_legacy_client_ciphers.go b/vendor/src/github.com/docker/go-connections/tlsconfig/config_legacy_client_ciphers.go new file mode 100755 index 000000000..ee22df47c --- /dev/null +++ b/vendor/src/github.com/docker/go-connections/tlsconfig/config_legacy_client_ciphers.go @@ -0,0 +1,15 @@ +// +build !go1.5 + +// Package tlsconfig provides primitives to retrieve secure-enough TLS configurations for both clients and servers. +// +package tlsconfig + +import ( + "crypto/tls" +) + +// Client TLS cipher suites (dropping CBC ciphers for client preferred suite set) +var clientCipherSuites = []uint16{ + tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, + tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, +} diff --git a/vendor/src/github.com/docker/go-connections/tlsconfig/config_test.go b/vendor/src/github.com/docker/go-connections/tlsconfig/config_test.go new file mode 100755 index 000000000..02131d6b8 --- /dev/null +++ b/vendor/src/github.com/docker/go-connections/tlsconfig/config_test.go @@ -0,0 +1,651 @@ +package tlsconfig + +import ( + "bytes" + "crypto/tls" + "crypto/x509" + "encoding/pem" + "io/ioutil" + "os" + "reflect" + "testing" +) + +// This is the currently active LetsEncrypt IdenTrust cross-signed CA cert. It expires Mar 17, 2021. +const ( + systemRootTrustedCert = ` +-----BEGIN CERTIFICATE----- +MIIEkjCCA3qgAwIBAgIQCgFBQgAAAVOFc2oLheynCDANBgkqhkiG9w0BAQsFADA/ +MSQwIgYDVQQKExtEaWdpdGFsIFNpZ25hdHVyZSBUcnVzdCBDby4xFzAVBgNVBAMT +DkRTVCBSb290IENBIFgzMB4XDTE2MDMxNzE2NDA0NloXDTIxMDMxNzE2NDA0Nlow +SjELMAkGA1UEBhMCVVMxFjAUBgNVBAoTDUxldCdzIEVuY3J5cHQxIzAhBgNVBAMT +GkxldCdzIEVuY3J5cHQgQXV0aG9yaXR5IFgzMIIBIjANBgkqhkiG9w0BAQEFAAOC +AQ8AMIIBCgKCAQEAnNMM8FrlLke3cl03g7NoYzDq1zUmGSXhvb418XCSL7e4S0EF +q6meNQhY7LEqxGiHC6PjdeTm86dicbp5gWAf15Gan/PQeGdxyGkOlZHP/uaZ6WA8 +SMx+yk13EiSdRxta67nsHjcAHJyse6cF6s5K671B5TaYucv9bTyWaN8jKkKQDIZ0 +Z8h/pZq4UmEUEz9l6YKHy9v6Dlb2honzhT+Xhq+w3Brvaw2VFn3EK6BlspkENnWA +a6xK8xuQSXgvopZPKiAlKQTGdMDQMc2PMTiVFrqoM7hD8bEfwzB/onkxEz0tNvjj +/PIzark5McWvxI0NHWQWM6r6hCm21AvA2H3DkwIDAQABo4IBfTCCAXkwEgYDVR0T +AQH/BAgwBgEB/wIBADAOBgNVHQ8BAf8EBAMCAYYwfwYIKwYBBQUHAQEEczBxMDIG +CCsGAQUFBzABhiZodHRwOi8vaXNyZy50cnVzdGlkLm9jc3AuaWRlbnRydXN0LmNv +bTA7BggrBgEFBQcwAoYvaHR0cDovL2FwcHMuaWRlbnRydXN0LmNvbS9yb290cy9k +c3Ryb290Y2F4My5wN2MwHwYDVR0jBBgwFoAUxKexpHsscfrb4UuQdf/EFWCFiRAw +VAYDVR0gBE0wSzAIBgZngQwBAgEwPwYLKwYBBAGC3xMBAQEwMDAuBggrBgEFBQcC +ARYiaHR0cDovL2Nwcy5yb290LXgxLmxldHNlbmNyeXB0Lm9yZzA8BgNVHR8ENTAz +MDGgL6AthitodHRwOi8vY3JsLmlkZW50cnVzdC5jb20vRFNUUk9PVENBWDNDUkwu +Y3JsMB0GA1UdDgQWBBSoSmpjBH3duubRObemRWXv86jsoTANBgkqhkiG9w0BAQsF +AAOCAQEA3TPXEfNjWDjdGBX7CVW+dla5cEilaUcne8IkCJLxWh9KEik3JHRRHGJo +uM2VcGfl96S8TihRzZvoroed6ti6WqEBmtzw3Wodatg+VyOeph4EYpr/1wXKtx8/ +wApIvJSwtmVi4MFU5aMqrSDE6ea73Mj2tcMyo5jMd6jmeWUHK8so/joWUoHOUgwu +X4Po1QYz+3dszkDqMp4fklxBwXRsW10KXzPMTZ+sOPAveyxindmjkW8lGy+QsRlG +PfZ+G6Z6h7mjem0Y+iWlkYcV4PIWL1iwBi8saCbGS5jN2p8M+X+Q7UNKEkROb3N6 +KOqkqm57TH2H3eDJAkSnh6/DNFu0Qg== +-----END CERTIFICATE----- +` + rsaPrivateKeyFile = "fixtures/key.pem" + certificateFile = "fixtures/cert.pem" + multiCertificateFile = "fixtures/multi.pem" + rsaEncryptedPrivateKeyFile = "fixtures/encrypted_key.pem" + certificateOfEncryptedKeyFile = "fixtures/cert_of_encrypted_key.pem" +) + +// returns the name of a pre-generated, multiple-certificate CA file +// with both RSA and ECDSA certs. +func getMultiCert() string { + return multiCertificateFile +} + +// returns the names of pre-generated key and certificate files. +func getCertAndKey() (string, string) { + return rsaPrivateKeyFile, certificateFile +} + +// returns the names of pre-generated, encrypted private key and +// corresponding certificate file +func getCertAndEncryptedKey() (string, string) { + return rsaEncryptedPrivateKeyFile, certificateOfEncryptedKeyFile +} + +// If the cert files and directory are provided but are invalid, an error is +// returned. +func TestConfigServerTLSFailsIfUnableToLoadCerts(t *testing.T) { + key, cert := getCertAndKey() + ca := getMultiCert() + + tempFile, err := ioutil.TempFile("", "cert-test") + if err != nil { + t.Fatal("Unable to create temporary empty file") + } + defer os.RemoveAll(tempFile.Name()) + tempFile.Close() + + for _, badFile := range []string{"not-a-file", tempFile.Name()} { + for i := 0; i < 3; i++ { + files := []string{cert, key, ca} + files[i] = badFile + + result, err := Server(Options{ + CertFile: files[0], + KeyFile: files[1], + CAFile: files[2], + ClientAuth: tls.VerifyClientCertIfGiven, + }) + if err == nil || result != nil { + t.Fatal("Expected a non-real file to error and return a nil TLS config") + } + } + } +} + +// If server cert and key are provided and client auth and client CA are not +// set, a tls config with only the server certs will be returned. +func TestConfigServerTLSServerCertsOnly(t *testing.T) { + key, cert := getCertAndKey() + + keypair, err := tls.LoadX509KeyPair(cert, key) + if err != nil { + t.Fatal("Unable to load the generated cert and key") + } + + tlsConfig, err := Server(Options{ + CertFile: cert, + KeyFile: key, + }) + if err != nil || tlsConfig == nil { + t.Fatal("Unable to configure server TLS", err) + } + + if len(tlsConfig.Certificates) != 1 { + t.Fatal("Unexpected server certificates") + } + if len(tlsConfig.Certificates[0].Certificate) != len(keypair.Certificate) { + t.Fatal("Unexpected server certificates") + } + for i, cert := range tlsConfig.Certificates[0].Certificate { + if !bytes.Equal(cert, keypair.Certificate[i]) { + t.Fatal("Unexpected server certificates") + } + } + + if !reflect.DeepEqual(tlsConfig.CipherSuites, DefaultServerAcceptedCiphers) { + t.Fatal("Unexpected server cipher suites") + } + if !tlsConfig.PreferServerCipherSuites { + t.Fatal("Expected server to prefer cipher suites") + } + if tlsConfig.MinVersion != tls.VersionTLS10 { + t.Fatal("Unexpected server TLS version") + } +} + +// If client CA is provided, it will only be used if the client auth is >= +// VerifyClientCertIfGiven +func TestConfigServerTLSClientCANotSetIfClientAuthTooLow(t *testing.T) { + key, cert := getCertAndKey() + ca := getMultiCert() + + tlsConfig, err := Server(Options{ + CertFile: cert, + KeyFile: key, + ClientAuth: tls.RequestClientCert, + CAFile: ca, + }) + + if err != nil || tlsConfig == nil { + t.Fatal("Unable to configure server TLS", err) + } + + if len(tlsConfig.Certificates) != 1 { + t.Fatal("Unexpected server certificates") + } + if tlsConfig.ClientAuth != tls.RequestClientCert { + t.Fatal("ClientAuth was not set to what was in the options") + } + if tlsConfig.ClientCAs != nil { + t.Fatalf("Client CAs should never have been set") + } +} + +// If client CA is provided, it will only be used if the client auth is >= +// VerifyClientCertIfGiven +func TestConfigServerTLSClientCASet(t *testing.T) { + key, cert := getCertAndKey() + ca := getMultiCert() + + tlsConfig, err := Server(Options{ + CertFile: cert, + KeyFile: key, + ClientAuth: tls.VerifyClientCertIfGiven, + CAFile: ca, + }) + + if err != nil || tlsConfig == nil { + t.Fatal("Unable to configure server TLS", err) + } + + if len(tlsConfig.Certificates) != 1 { + t.Fatal("Unexpected server certificates") + } + if tlsConfig.ClientAuth != tls.VerifyClientCertIfGiven { + t.Fatal("ClientAuth was not set to what was in the options") + } + basePool, err := SystemCertPool() + if err != nil { + basePool = x509.NewCertPool() + } + // because we are not enabling `ExclusiveRootPools`, any root pool will also contain the system roots + if tlsConfig.ClientCAs == nil || len(tlsConfig.ClientCAs.Subjects()) != len(basePool.Subjects())+2 { + t.Fatalf("Client CAs were never set correctly") + } +} + +// Exclusive root pools determines whether the CA pool will be a union of the system +// certificate pool and custom certs, or an exclusive or of the custom certs and system pool +func TestConfigServerExclusiveRootPools(t *testing.T) { + key, cert := getCertAndKey() + ca := getMultiCert() + + caBytes, err := ioutil.ReadFile(ca) + if err != nil { + t.Fatal("Unable to read CA certs", err) + } + + var testCerts []*x509.Certificate + for _, pemBytes := range [][]byte{caBytes, []byte(systemRootTrustedCert)} { + pemBlock, _ := pem.Decode(pemBytes) + if pemBlock == nil { + t.Fatal("Malformed certificate") + } + cert, err := x509.ParseCertificate(pemBlock.Bytes) + if err != nil { + t.Fatal("Unable to parse certificate") + } + testCerts = append(testCerts, cert) + } + + // ExclusiveRootPools not set, so should be able to verify both system-signed certs + // and custom CA-signed certs + tlsConfig, err := Server(Options{ + CertFile: cert, + KeyFile: key, + ClientAuth: tls.VerifyClientCertIfGiven, + CAFile: ca, + }) + + if err != nil || tlsConfig == nil { + t.Fatal("Unable to configure server TLS", err) + } + + for i, cert := range testCerts { + if _, err := cert.Verify(x509.VerifyOptions{Roots: tlsConfig.ClientCAs}); err != nil { + t.Fatalf("Unable to verify certificate %d: %v", i, err) + } + } + + // ExclusiveRootPools set and custom CA provided, so system certs should not be verifiable + // and custom CA-signed certs should be verifiable + tlsConfig, err = Server(Options{ + CertFile: cert, + KeyFile: key, + ClientAuth: tls.VerifyClientCertIfGiven, + CAFile: ca, + ExclusiveRootPools: true, + }) + + if err != nil || tlsConfig == nil { + t.Fatal("Unable to configure server TLS", err) + } + + for i, cert := range testCerts { + _, err := cert.Verify(x509.VerifyOptions{Roots: tlsConfig.ClientCAs}) + switch { + case i == 0 && err != nil: + t.Fatal("Unable to verify custom certificate, even though the root pool should have only the custom CA", err) + case i == 1 && err == nil: + t.Fatal("Successfully verified system root-signed certificate though the root pool should have only the cusotm CA", err) + } + } + + // No CA file provided, system cert should be verifiable only + tlsConfig, err = Server(Options{ + CertFile: cert, + KeyFile: key, + }) + + if err != nil || tlsConfig == nil { + t.Fatal("Unable to configure server TLS", err) + } + + for i, cert := range testCerts { + _, err := cert.Verify(x509.VerifyOptions{Roots: tlsConfig.ClientCAs}) + switch { + case i == 1 && err != nil: + t.Fatal("Unable to verify system root-signed certificate, even though the root pool should be the system pool only", err) + case i == 0 && err == nil: + t.Fatal("Successfully verified custom certificate though the root pool should be the system pool only", err) + } + } +} + +// If a valid minimum version is specified in the options, the server's +// minimum version should be set accordingly +func TestConfigServerTLSMinVersionIsSetBasedOnOptions(t *testing.T) { + versions := []uint16{ + tls.VersionTLS11, + tls.VersionTLS12, + } + key, cert := getCertAndKey() + + for _, v := range versions { + tlsConfig, err := Server(Options{ + MinVersion: v, + CertFile: cert, + KeyFile: key, + }) + + if err != nil || tlsConfig == nil { + t.Fatal("Unable to configure server TLS", err) + } + + if tlsConfig.MinVersion != v { + t.Fatal("Unexpected minimum TLS version: ", tlsConfig.MinVersion) + } + } +} + +// An error should be returned if the specified minimum version for the server +// is too low, i.e. less than VersionTLS10 +func TestConfigServerTLSMinVersionNotSetIfMinVersionIsTooLow(t *testing.T) { + key, cert := getCertAndKey() + + _, err := Server(Options{ + MinVersion: tls.VersionSSL30, + CertFile: cert, + KeyFile: key, + }) + + if err == nil { + t.Fatal("Should have returned an error for minimum version below TLS10") + } +} + +// An error should be returned if an invalid minimum version for the server is +// in the options struct +func TestConfigServerTLSMinVersionNotSetIfMinVersionIsInvalid(t *testing.T) { + key, cert := getCertAndKey() + + _, err := Server(Options{ + MinVersion: 1, + CertFile: cert, + KeyFile: key, + }) + + if err == nil { + t.Fatal("Should have returned error on invalid minimum version option") + } +} + +// The root CA is never set if InsecureSkipBoolean is set to true, but the +// default client options are set +func TestConfigClientTLSNoVerify(t *testing.T) { + ca := getMultiCert() + + tlsConfig, err := Client(Options{CAFile: ca, InsecureSkipVerify: true}) + + if err != nil || tlsConfig == nil { + t.Fatal("Unable to configure client TLS", err) + } + + if tlsConfig.RootCAs != nil { + t.Fatal("Should not have set Root CAs", err) + } + + if !reflect.DeepEqual(tlsConfig.CipherSuites, clientCipherSuites) { + t.Fatal("Unexpected client cipher suites") + } + if tlsConfig.MinVersion != tls.VersionTLS12 { + t.Fatal("Unexpected client TLS version") + } + + if tlsConfig.Certificates != nil { + t.Fatal("Somehow client certificates were set") + } +} + +// The root CA is never set if InsecureSkipBoolean is set to false and root CA +// is not provided. +func TestConfigClientTLSNoRoot(t *testing.T) { + tlsConfig, err := Client(Options{}) + + if err != nil || tlsConfig == nil { + t.Fatal("Unable to configure client TLS", err) + } + + if tlsConfig.RootCAs != nil { + t.Fatal("Should not have set Root CAs", err) + } + + if !reflect.DeepEqual(tlsConfig.CipherSuites, clientCipherSuites) { + t.Fatal("Unexpected client cipher suites") + } + if tlsConfig.MinVersion != tls.VersionTLS12 { + t.Fatal("Unexpected client TLS version") + } + + if tlsConfig.Certificates != nil { + t.Fatal("Somehow client certificates were set") + } +} + +// The RootCA is set if the file is provided and InsecureSkipVerify is false +func TestConfigClientTLSRootCAFileWithOneCert(t *testing.T) { + ca := getMultiCert() + + tlsConfig, err := Client(Options{CAFile: ca}) + + if err != nil || tlsConfig == nil { + t.Fatal("Unable to configure client TLS", err) + } + basePool, err := SystemCertPool() + if err != nil { + basePool = x509.NewCertPool() + } + // because we are not enabling `ExclusiveRootPools`, any root pool will also contain the system roots + if tlsConfig.RootCAs == nil || len(tlsConfig.RootCAs.Subjects()) != len(basePool.Subjects())+2 { + t.Fatal("Root CAs not set properly", err) + } + if tlsConfig.Certificates != nil { + t.Fatal("Somehow client certificates were set") + } +} + +// An error is returned if a root CA is provided but the file doesn't exist. +func TestConfigClientTLSNonexistentRootCAFile(t *testing.T) { + tlsConfig, err := Client(Options{CAFile: "nonexistent"}) + + if err == nil || tlsConfig != nil { + t.Fatal("Should not have been able to configure client TLS", err) + } +} + +// An error is returned if either the client cert or the key are provided +// but invalid or blank. +func TestConfigClientTLSClientCertOrKeyInvalid(t *testing.T) { + key, cert := getCertAndKey() + + tempFile, err := ioutil.TempFile("", "cert-test") + if err != nil { + t.Fatal("Unable to create temporary empty file") + } + defer os.Remove(tempFile.Name()) + tempFile.Close() + + for i := 0; i < 2; i++ { + for _, invalid := range []string{"not-a-file", "", tempFile.Name()} { + files := []string{cert, key} + files[i] = invalid + + tlsConfig, err := Client(Options{CertFile: files[0], KeyFile: files[1]}) + if err == nil || tlsConfig != nil { + t.Fatal("Should not have been able to configure client TLS", err) + } + } + } +} + +// The certificate is set if the client cert and client key are provided and +// valid. +func TestConfigClientTLSValidClientCertAndKey(t *testing.T) { + key, cert := getCertAndKey() + + keypair, err := tls.LoadX509KeyPair(cert, key) + if err != nil { + t.Fatal("Unable to load the generated cert and key") + } + + tlsConfig, err := Client(Options{CertFile: cert, KeyFile: key}) + + if err != nil || tlsConfig == nil { + t.Fatal("Unable to configure client TLS", err) + } + + if len(tlsConfig.Certificates) != 1 { + t.Fatal("Unexpected client certificates") + } + if len(tlsConfig.Certificates[0].Certificate) != len(keypair.Certificate) { + t.Fatal("Unexpected client certificates") + } + for i, cert := range tlsConfig.Certificates[0].Certificate { + if !bytes.Equal(cert, keypair.Certificate[i]) { + t.Fatal("Unexpected client certificates") + } + } + + if tlsConfig.RootCAs != nil { + t.Fatal("Root CAs should not have been set", err) + } +} + +// The certificate is set if the client cert and encrypted client key are +// provided and valid and passphrase can decrypt the key +func TestConfigClientTLSValidClientCertAndEncryptedKey(t *testing.T) { + key, cert := getCertAndEncryptedKey() + + tlsConfig, err := Client(Options{ + CertFile: cert, + KeyFile: key, + Passphrase: "FooBar123", + }) + + if err != nil || tlsConfig == nil { + t.Fatal("Unable to configure client TLS", err) + } + + if len(tlsConfig.Certificates) != 1 { + t.Fatal("Unexpected client certificates") + } +} + +// The certificate is not set if the provided passphrase cannot decrypt +// the encrypted key. +func TestConfigClientTLSNotSetWithInvalidPassphrase(t *testing.T) { + key, cert := getCertAndEncryptedKey() + + tlsConfig, err := Client(Options{ + CertFile: cert, + KeyFile: key, + Passphrase: "InvalidPassphrase", + }) + + if !IsErrEncryptedKey(err) || tlsConfig != nil { + t.Fatal("Expected failure due to incorrect passphrase.") + } +} + +// Exclusive root pools determines whether the CA pool will be a union of the system +// certificate pool and custom certs, or an exclusive or of the custom certs and system pool +func TestConfigClientExclusiveRootPools(t *testing.T) { + ca := getMultiCert() + + caBytes, err := ioutil.ReadFile(ca) + if err != nil { + t.Fatal("Unable to read CA certs", err) + } + + var testCerts []*x509.Certificate + for _, pemBytes := range [][]byte{caBytes, []byte(systemRootTrustedCert)} { + pemBlock, _ := pem.Decode(pemBytes) + if pemBlock == nil { + t.Fatal("Malformed certificate") + } + cert, err := x509.ParseCertificate(pemBlock.Bytes) + if err != nil { + t.Fatal("Unable to parse certificate") + } + testCerts = append(testCerts, cert) + } + + // ExclusiveRootPools not set, so should be able to verify both system-signed certs + // and custom CA-signed certs + tlsConfig, err := Client(Options{CAFile: ca}) + + if err != nil || tlsConfig == nil { + t.Fatal("Unable to configure client TLS", err) + } + + for i, cert := range testCerts { + if _, err := cert.Verify(x509.VerifyOptions{Roots: tlsConfig.RootCAs}); err != nil { + t.Fatalf("Unable to verify certificate %d: %v", i, err) + } + } + + // ExclusiveRootPools set and custom CA provided, so system certs should not be verifiable + // and custom CA-signed certs should be verifiable + tlsConfig, err = Client(Options{ + CAFile: ca, + ExclusiveRootPools: true, + }) + + if err != nil || tlsConfig == nil { + t.Fatal("Unable to configure client TLS", err) + } + + for i, cert := range testCerts { + _, err := cert.Verify(x509.VerifyOptions{Roots: tlsConfig.RootCAs}) + switch { + case i == 0 && err != nil: + t.Fatal("Unable to verify custom certificate, even though the root pool should have only the custom CA", err) + case i == 1 && err == nil: + t.Fatal("Successfully verified system root-signed certificate though the root pool should have only the cusotm CA", err) + } + } + + // No CA file provided, system cert should be verifiable only + tlsConfig, err = Client(Options{}) + + if err != nil || tlsConfig == nil { + t.Fatal("Unable to configure client TLS", err) + } + + for i, cert := range testCerts { + _, err := cert.Verify(x509.VerifyOptions{Roots: tlsConfig.RootCAs}) + switch { + case i == 1 && err != nil: + t.Fatal("Unable to verify system root-signed certificate, even though the root pool should be the system pool only", err) + case i == 0 && err == nil: + t.Fatal("Successfully verified custom certificate though the root pool should be the system pool only", err) + } + } +} + +// If a valid MinVersion is specified in the options, the client's +// minimum version should be set accordingly +func TestConfigClientTLSMinVersionIsSetBasedOnOptions(t *testing.T) { + key, cert := getCertAndKey() + + tlsConfig, err := Client(Options{ + MinVersion: tls.VersionTLS12, + CertFile: cert, + KeyFile: key, + }) + + if err != nil || tlsConfig == nil { + t.Fatal("Unable to configure client TLS", err) + } + + if tlsConfig.MinVersion != tls.VersionTLS12 { + t.Fatal("Unexpected minimum TLS version: ", tlsConfig.MinVersion) + } +} + +// An error should be returned if the specified minimum version for the client +// is too low, i.e. less than VersionTLS12 +func TestConfigClientTLSMinVersionNotSetIfMinVersionIsTooLow(t *testing.T) { + key, cert := getCertAndKey() + + _, err := Client(Options{ + MinVersion: tls.VersionTLS11, + CertFile: cert, + KeyFile: key, + }) + + if err == nil { + t.Fatal("Should have returned an error for minimum version below TLS12") + } +} + +// An error should be returned if an invalid minimum version for the client is +// in the options struct +func TestConfigClientTLSMinVersionNotSetIfMinVersionIsInvalid(t *testing.T) { + key, cert := getCertAndKey() + + _, err := Client(Options{ + MinVersion: 1, + CertFile: cert, + KeyFile: key, + }) + + if err == nil { + t.Fatal("Should have returned error on invalid minimum version option") + } +} diff --git a/vendor/src/github.com/docker/go-connections/tlsconfig/fixtures/cert.pem b/vendor/src/github.com/docker/go-connections/tlsconfig/fixtures/cert.pem new file mode 100755 index 000000000..09bd69ed2 --- /dev/null +++ b/vendor/src/github.com/docker/go-connections/tlsconfig/fixtures/cert.pem @@ -0,0 +1,18 @@ +-----BEGIN CERTIFICATE----- +MIIC1jCCAb6gAwIBAgIDAw0/MA0GCSqGSIb3DQEBCwUAMA8xDTALBgNVBAMTBHRl +c3QwHhcNMTYwMzI4MTg0MTQ3WhcNMjcwMzI4MTg0MTQ3WjAPMQ0wCwYDVQQDEwR0 +ZXN0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA1k1NO4wzCpxZ71Bo +SiYSWh8SE9jHtg6lz0QjMQXzFuLhpedjHJYx9fYbD+JVk5vnRbUqNUeZVKAGahfR +9vhm5I+cm359gYU0gHawLw91oh4JCiwUu77U2obHvtvcXLf6Fb/+MoSA5wH7vbL3 +T4vR1+hLt+R+kILAEHq/IlSdLD8CA0iA+ypHfCPOi5F2wVjAyMnQXgVDkAhzefpu +JkhN1yUgb5WK4qoSuOUDUYq/bRosLdHXDJiWRuqaU2zxO5cHVlrNAE5RuspfEzl4 +YP6boZTOomLEDbBTSJWgX2/ybvY7o4sCw7KrvyBIqSK9HbfaK1nFMFGoiSH6+1m4 +amWKrwIDAQABozswOTAOBgNVHQ8BAf8EBAMCBaAwGQYDVR0lBBIwEAYIKwYBBQUH +AwMGBFUdJQAwDAYDVR0TAQH/BAIwADANBgkqhkiG9w0BAQsFAAOCAQEADuXjLtGk +tU5ql+LFB32Cc2Laa0iO8aqJccOcXYKg4FD0um+1+YQO1CBZZqWjItH4CuJl5+2j +Tc9sFgrIVH5CmvUkOUFPCNDAJtxBvF6RQqRpehjheHDaNsYo9JNKHKEJB6OJrDgy +N5krM5FKyAp/EDTbIrGIZFMdxQGxK5MfpfPkKK44JgOQM3QWeR+LqIpfd34MD1jZ +jjYdl0+quIHiIdFR0a4Uam7o9GfUmcWe1VFthLb5pNhV6t+wyuLyMXVMNacKZSz/ +nOMWVQfgViZk6rHOPSMrFMc7Pp488I907MJKCryd21LcLqMuhb4BpWcJghnY8Lbs +uIPLsUHr3Pfp9Q== +-----END CERTIFICATE----- diff --git a/vendor/src/github.com/docker/go-connections/tlsconfig/fixtures/cert_of_encrypted_key.pem b/vendor/src/github.com/docker/go-connections/tlsconfig/fixtures/cert_of_encrypted_key.pem new file mode 100755 index 000000000..d0710768f --- /dev/null +++ b/vendor/src/github.com/docker/go-connections/tlsconfig/fixtures/cert_of_encrypted_key.pem @@ -0,0 +1,18 @@ +-----BEGIN CERTIFICATE----- +MIIC1jCCAb6gAwIBAgIDAw0/MA0GCSqGSIb3DQEBCwUAMA8xDTALBgNVBAMTBHRl +c3QwHhcNMTYwNDIyMDQyMjM1WhcNMTgwNDIyMDQyMjM1WjAPMQ0wCwYDVQQDEwR0 +ZXN0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4GRTos+Ik6kQG7wn +8E4HqPwgWXbY0T59UQsrbR+YbyxbUKV67Pgl4VImuUmYaism6Tm3EFYzeom5baMc +vW0hC+WbwVr1rq5ddBE8akYhlPY40SxFlh563vOi7lcFGM7xuUbTlhtAhYa5xc5U +thHYa8Mdqc2kMrmU4JBhNHoRk2mnRBo2J2/8RfOfioM6mH0t/MVtB/jSGpcwbbfj +2twKOpB9CoX57szVo7+DCFHpLxeuop+69REu5Egc2a5BtBuUf0fkUBKuF7yUy2xI +IbgjCiGb3Z+PCIC0CjNt9wExowPAGfxAJ8s1nNlpZav3707VZRtz7Js1skRjm9aU +8fhYNQIDAQABozswOTAOBgNVHQ8BAf8EBAMCBaAwGQYDVR0lBBIwEAYIKwYBBQUH +AwMGBFUdJQAwDAYDVR0TAQH/BAIwADANBgkqhkiG9w0BAQsFAAOCAQEAcKCCV5Os +O2U7Ekp0jzOusV2+ykZzUe4sEds+ikblxK9SHV/pAPIVuAevdyE1LKmJ6ZGgeU2M +4MC6jC/XTlYNhsYCfKaJn53UscKI2urXFlk1Gv5VQP5EOrMWb76A5uj1nElxKe2C +bMVoUuMwRd9jnz6594D80jGGYpHRaF7yLtGbiflDjB+yv1OU6WnuVNr0nOb9ShR6 +WPlrQj5TUSpRHF/oKy9LVWuxYA9aiY1YREDZhhauw9pGAMx1lImfJcJ077MdxN4A +DwKAx3ooajAu1n3McY1oncWW+rWs2Ptvp6lKMGoZ50ElEPCMw4/hPtPMLq/DTWNj +l342KLVWgchlIA== +-----END CERTIFICATE----- diff --git a/vendor/src/github.com/docker/go-connections/tlsconfig/fixtures/encrypted_key.pem b/vendor/src/github.com/docker/go-connections/tlsconfig/fixtures/encrypted_key.pem new file mode 100755 index 000000000..64e949dcf --- /dev/null +++ b/vendor/src/github.com/docker/go-connections/tlsconfig/fixtures/encrypted_key.pem @@ -0,0 +1,30 @@ +-----BEGIN RSA PRIVATE KEY----- +Proc-Type: 4,ENCRYPTED +DEK-Info: AES-256-CBC,68ce1d54f187b663e152d9c5dc900fd3 + +ZVBeXx7kWiF0yPOORntrN6BsyIJE7krqTVhRfk6GAllaLQv0jvb31XHB1oWOaqnx +tb7kUuoBeQdl1hs/iAnkDMc59WJfEK9A9cAD/SgxTgdENOrzFSRNEfqketLA4eHZ +2sOLkSfv58HwA0p0gzqSrLQBo/6ZtF/57HxH166PtErPNTS1Usu/f4Oj0UqxTfbZ +B5LHsepyNLt6q/15fcY0TFYJwvgEXa4SridjT+8bTz2T+bx3QFijGnl7EdkTElni +FIwnDjFZaAULqoyUIB1y8guEZVkaWKncxPdRfhId84HklWdrrLtP5D6db1xNNpsp +LzGdciD3phJp6K0hpl+WrhYxuCKURa27tXMCuYOFd1hw/kM29jFbxSIlNBGN4OLL +v4wYrJFM21iWsz9c7Cqw5Yls2Rsx0QrXRFIxwT25z+HNx1fysQxYuxf3r+e2oz8e +8Os7hvcxG2XDz01/zpx8kzxUcLuh+3o5UOYlo9z6qsjaD5NUXY+X90PUrVO9fk5y +8o8pnElPnV88Ihrog5YTYy6egiQWHhDk2I4qlYPOBQNKTLg3KulAcmC9vQ8mR5Sy +p3c3MTgh0A3Zk5Dib+sQ0tdbwDcB2JCTqGal1FNEW5Z7qTHA4Bdm2l7hGs8cRpy4 +Ehkhv3s5wWmKcbwwlPuJ0UfPeDn6v9qE2/IkOy+jWgTpaFyWtXHc1/XdqMsJ8xN0 +thJw/GMtNabB1+zuayJnvmbJd2qW1smsFTHqX3BovXIH4vx1hE2d0lJpEBynk+wr +gpPgrRoEiqsPcsRoVjvKH3qwJLRdcGYhKqhbvRdynlagCLmE8iAI99r82u6t+03h +YNpRbafY4ceAYyK0IlRiJvGkBMfH7bMXcBMmXyQSBF27ZpNidyZSCHrU5xyHqJZO +XWUhl9GHplBfueh5E831S7mDqobd8RqnUvKVygyEOol5VUFDrggTAAKKN9VzM3uT +MaVymt6fA7stzf01fT+Wi7uCm5legTXG3Ca+XxD6TdE0dNzewd5jDsuqwXnt1iC4 +slvuLRZeRZDNvBd0G7Ohhp6jb2HHwkv9kQTZ+UEDbR/Gwxty4oT1MnwSE0mi9ZFN +6PTjrSxpIKe+mAhgzrepLMfATGayYQzucEArPG7Vp+NJva+j6FKloqrzXMjlP0hN +XSBr7AL+j+OR/tzDOoUG3xdsCl/u5hFTpjsW2ti870zoRUcK0fqJ9UIYjh66L7yT +KNkXsC+OcGuGkhtQ0gxx60OI7wp4bh2pKdT6e111/WTvXxVR2C3XhFBLUfNIz/7A +Oj+s0CaV4pBmCjIobLYpxC0ofLplwBLGf9xnsBiQF5dsgKgOhACeDmDMwqAJ3U/t +54hK/8Yb8W46Tjgbm0Qsj5gFXHofnyqDeQxAjsdCXsdMaPB8nyZpEkuQSEj9HlKW +xIEErVufkvqyrzhX1pxPs+C839Ueyeob6ZWQurqCLTdZh+3bhKcvi5iP+aLLjMWK +JT9tmAuFVkbPerqObVQFbnM4/re33YYD7QXCqta5bxcVeBI8N1HdwMYrDVhXelEx +mqGleUkkDHTWzAa3u1GKOzLXAYnD0TsTwml0+k+Rf0QMBiDJiKujfy7fGqfZF2vR +-----END RSA PRIVATE KEY----- diff --git a/vendor/src/github.com/docker/go-connections/tlsconfig/fixtures/key.pem b/vendor/src/github.com/docker/go-connections/tlsconfig/fixtures/key.pem new file mode 100755 index 000000000..6a5e20a40 --- /dev/null +++ b/vendor/src/github.com/docker/go-connections/tlsconfig/fixtures/key.pem @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEpQIBAAKCAQEA1k1NO4wzCpxZ71BoSiYSWh8SE9jHtg6lz0QjMQXzFuLhpedj +HJYx9fYbD+JVk5vnRbUqNUeZVKAGahfR9vhm5I+cm359gYU0gHawLw91oh4JCiwU +u77U2obHvtvcXLf6Fb/+MoSA5wH7vbL3T4vR1+hLt+R+kILAEHq/IlSdLD8CA0iA ++ypHfCPOi5F2wVjAyMnQXgVDkAhzefpuJkhN1yUgb5WK4qoSuOUDUYq/bRosLdHX +DJiWRuqaU2zxO5cHVlrNAE5RuspfEzl4YP6boZTOomLEDbBTSJWgX2/ybvY7o4sC +w7KrvyBIqSK9HbfaK1nFMFGoiSH6+1m4amWKrwIDAQABAoIBAQC802wj9grbZJzS +A1WBUD6Hbi0tk6uVPR7YnD8t6QIivlL5LgLko2ruQKXjvxiMcai8gT7pp2bxa/d6 +7/Yv2PxAlFH3qOLJhyeVsf7X2JVb/X8VmXXDYAiJbI0AHRX0FJ+lHoDK3nn+En9Q +zSqgyqBhz+s343uptauqWZ2kkE3VNyqlPBhmKc5NcbR7Sgb4nJ3CkNAcxRkl1NeI +BRFdsTUYRNR3Vd++OvOzI4uzZfCIeUVqx+r7/SeLW0UwqeprMm7g+hFQLfH+e9SA +9lx0EIRoQFwgvKju2eogpSwvkSlObXnESu5OHYtnc+jpsOC0EbQgO0d6CqVZiqjR +2dRYsZkhAoGBAO69loXSAsyqUj0rT5iq59PuMlBEAlW6hQTfl6c8bnu1JUo2s/CH +OJfswxfHN32qmi99WbK2iLyrnznNYsyPnYKW0ObwuoqAdrlydfu7Fq9HSOACoIvK +jRMOsiJtM3JX2bHHV7yIwJ1+h++o2Ly803j7tKtYsrRQVZiWeTcR2IRZAoGBAOXL +bJFLbAhm3zRqhbiWuORqqyLxrDmIB6RY8vTdX47vwzkFGZJlCuL+vs6877I6eOc9 +wjH9qcOiJQJ4DWkAE+VS5PAPoj0UDRw7AkE9v3RwnmxvAfP5rPo5KimYxKq4yX6r ++Qc4ixwftCj0rxFoG4lnipwBFq4NXuHtIhbZXMZHAoGBAOGfatGtV9f0XyRP+jld +yxoO0p3oqAw86dlhNgFmq0NePo+UgxmdsW5i4z1lmJu6z1xyKoMq3q7vwtrtr6GD +WGhB/8tBVgnuvkUkVzw/44Bi7gxGb1OtaQXJra+7ZBN70tCgg9o5o080dWOZPruf ++Hst5eDJQpoGEd7S1lulEeqBAoGBAKAqdIak6izE/wg6wu+Q5lgW3SejCOakoKb1 +dIoljkhDZ2/j1RoLoVXsNzRDzlIMnV6X1jYf1ubLqj4ZTUeFTVjGuVl1nCA0TJsD +qiOtFTfkkxeDG/pgaSeTFocdut4/o/nNhep5h8RXeKwfN7LLPH4+FAd+Xr98BEk2 +jk8cu6RbAoGAHI9yRXKjlADBZLvxxMGHRfe7eK4PgABmluZLdsXzNmXxybrZDvdC +teipvIUSym7tvdDB6LHXKVp4mYeqHe/ktRatlhbQyPso2VPoMFQyuRBYKKFFAh0V +3d6EyTRnIxn/NW+XdcCUeufFfd+3BHyux68PyUsTtKRCJYfhExzJf70= +-----END RSA PRIVATE KEY----- diff --git a/vendor/src/github.com/docker/go-connections/tlsconfig/fixtures/multi.pem b/vendor/src/github.com/docker/go-connections/tlsconfig/fixtures/multi.pem new file mode 100755 index 000000000..9d61d2c53 --- /dev/null +++ b/vendor/src/github.com/docker/go-connections/tlsconfig/fixtures/multi.pem @@ -0,0 +1,28 @@ +-----BEGIN CERTIFICATE----- +MIIC3DCCAcSgAwIBAgIDAw0/MA0GCSqGSIb3DQEBCwUAMA8xDTALBgNVBAMTBHRl +c3QwHhcNMTYwMzI4MTg0MTQ3WhcNMjcwMzI4MTg0MTQ3WjAPMQ0wCwYDVQQDEwR0 +ZXN0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArVIJDnNnM1iX7Xj8 +bja4WsgHuENRBsBCROTDjQL1w7Ksin2jmCl/D7Gk9ifRJZ/HPE3BKo6B+3CDXygJ +Qvoe8SGWi6ae8lN4VgPoW7xDViAWhVmjIr+dNQXWD0hCq0YZuXyYSi5iXWeRaTvx +2eoG2VSkNnkc/0weEhX1nBGBscuz1UZqWp53m09eL7otngcNcdjmvLPiw4E3cric +UoLVonzf4ZE84Q7nNmfWfMKh4zJUyn8N766GAAoC6RAKsJ0xSDeRjkzSy7vGJKBv +nTBe6X1xyFZaN0mAjtRkYaxI9ZfI8K41Trhd88s4B4G61p70DY3dMLmuF8wGHVCF +lMMV6wIDAQABo0EwPzAOBgNVHQ8BAf8EBAMCAqQwGQYDVR0lBBIwEAYIKwYBBQUH +AwMGBFUdJQAwEgYDVR0TAQH/BAgwBgEB/wIBATANBgkqhkiG9w0BAQsFAAOCAQEA +LriCH0FTaOFIBl+kxAKjs7puhIZoYLwQ8IReXdEU7kYjPff3X/eiO82A0GwMM9Fp +/RdMlZGDSLyZ1a/gKCz55j9J4MW8ZH7RSEQs3dJQCvEPDO6UdgKy4Ft9yNh/ba1J +8/n0CqR+0QNov6Qp7eMDkQaDvKgCaABn8at6VLtuifJXFKDGt0LrR7wkQBJ85SZB +9GdfNSPzEZkb4FQ2gPgAk7ySoQ6Hi6mogEORbtJ7+Xiq57J+cEZQV6TOuwYgBG4e +MW3h37+7V5a/absybik1F/gcx4IbEBd/7an6a+a2l5FeTED5kpzvD4+yrQAoY8lT +gccRdP0O4CsLn7zlLRidPQ== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIBTzCB9qADAgECAgMDDT8wCgYIKoZIzj0EAwIwDzENMAsGA1UEAxMEdGVzdDAe +Fw0xNjAzMjgxODQxNDdaFw0yNzAzMjgxODQxNDdaMA8xDTALBgNVBAMTBHRlc3Qw +WTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAAQy8xfFkSiJA10EC1MMJzkLgu6csocC +UNyix7zOqijLsASE4an5LQsZ1PuhgVYnL+B9rAcnXgJaLM8YOmLRPqNdo0EwPzAO +BgNVHQ8BAf8EBAMCAqQwGQYDVR0lBBIwEAYIKwYBBQUHAwMGBFUdJQAwEgYDVR0T +AQH/BAgwBgEB/wIBATAKBggqhkjOPQQDAgNIADBFAiEAwUrZY7fHwr4FWONiBJo6 +97V9GAbj70ZJqV5M7rt+hMECIFY66kUrv0sG2vlhicSIGwSOdB3VcijdZSelzLn1 +iRk5 +-----END CERTIFICATE----- From e8426bf4a14a50186d38d16b76f3c7242bd30500 Mon Sep 17 00:00:00 2001 From: Jimmy Xu Date: Fri, 5 Jan 2018 12:23:07 +0800 Subject: [PATCH 2/2] [update-vendor] add github.com/pkg/errors to vendor --- vendor/src/github.com/pkg/errors/.gitignore | 24 + vendor/src/github.com/pkg/errors/LICENSE | 23 + vendor/src/github.com/pkg/errors/README.md | 52 ++ .../src/github.com/pkg/errors/bench_test.go | 59 ++ vendor/src/github.com/pkg/errors/errors.go | 269 +++++++++ .../src/github.com/pkg/errors/errors_test.go | 226 ++++++++ .../src/github.com/pkg/errors/example_test.go | 205 +++++++ .../src/github.com/pkg/errors/format_test.go | 535 ++++++++++++++++++ vendor/src/github.com/pkg/errors/stack.go | 178 ++++++ .../src/github.com/pkg/errors/stack_test.go | 292 ++++++++++ 10 files changed, 1863 insertions(+) create mode 100755 vendor/src/github.com/pkg/errors/.gitignore create mode 100755 vendor/src/github.com/pkg/errors/LICENSE create mode 100755 vendor/src/github.com/pkg/errors/README.md create mode 100755 vendor/src/github.com/pkg/errors/bench_test.go create mode 100755 vendor/src/github.com/pkg/errors/errors.go create mode 100755 vendor/src/github.com/pkg/errors/errors_test.go create mode 100755 vendor/src/github.com/pkg/errors/example_test.go create mode 100755 vendor/src/github.com/pkg/errors/format_test.go create mode 100755 vendor/src/github.com/pkg/errors/stack.go create mode 100755 vendor/src/github.com/pkg/errors/stack_test.go diff --git a/vendor/src/github.com/pkg/errors/.gitignore b/vendor/src/github.com/pkg/errors/.gitignore new file mode 100755 index 000000000..daf913b1b --- /dev/null +++ b/vendor/src/github.com/pkg/errors/.gitignore @@ -0,0 +1,24 @@ +# Compiled Object files, Static and Dynamic libs (Shared Objects) +*.o +*.a +*.so + +# Folders +_obj +_test + +# Architecture specific extensions/prefixes +*.[568vq] +[568vq].out + +*.cgo1.go +*.cgo2.c +_cgo_defun.c +_cgo_gotypes.go +_cgo_export.* + +_testmain.go + +*.exe +*.test +*.prof diff --git a/vendor/src/github.com/pkg/errors/LICENSE b/vendor/src/github.com/pkg/errors/LICENSE new file mode 100755 index 000000000..835ba3e75 --- /dev/null +++ b/vendor/src/github.com/pkg/errors/LICENSE @@ -0,0 +1,23 @@ +Copyright (c) 2015, Dave Cheney +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/vendor/src/github.com/pkg/errors/README.md b/vendor/src/github.com/pkg/errors/README.md new file mode 100755 index 000000000..273db3c98 --- /dev/null +++ b/vendor/src/github.com/pkg/errors/README.md @@ -0,0 +1,52 @@ +# errors [![Travis-CI](https://travis-ci.org/pkg/errors.svg)](https://travis-ci.org/pkg/errors) [![AppVeyor](https://ci.appveyor.com/api/projects/status/b98mptawhudj53ep/branch/master?svg=true)](https://ci.appveyor.com/project/davecheney/errors/branch/master) [![GoDoc](https://godoc.org/github.com/pkg/errors?status.svg)](http://godoc.org/github.com/pkg/errors) [![Report card](https://goreportcard.com/badge/github.com/pkg/errors)](https://goreportcard.com/report/github.com/pkg/errors) + +Package errors provides simple error handling primitives. + +`go get github.com/pkg/errors` + +The traditional error handling idiom in Go is roughly akin to +```go +if err != nil { + return err +} +``` +which applied recursively up the call stack results in error reports without context or debugging information. The errors package allows programmers to add context to the failure path in their code in a way that does not destroy the original value of the error. + +## Adding context to an error + +The errors.Wrap function returns a new error that adds context to the original error. For example +```go +_, err := ioutil.ReadAll(r) +if err != nil { + return errors.Wrap(err, "read failed") +} +``` +## Retrieving the cause of an error + +Using `errors.Wrap` constructs a stack of errors, adding context to the preceding error. Depending on the nature of the error it may be necessary to reverse the operation of errors.Wrap to retrieve the original error for inspection. Any error value which implements this interface can be inspected by `errors.Cause`. +```go +type causer interface { + Cause() error +} +``` +`errors.Cause` will recursively retrieve the topmost error which does not implement `causer`, which is assumed to be the original cause. For example: +```go +switch err := errors.Cause(err).(type) { +case *MyError: + // handle specifically +default: + // unknown error +} +``` + +[Read the package documentation for more information](https://godoc.org/github.com/pkg/errors). + +## Contributing + +We welcome pull requests, bug fixes and issue reports. With that said, the bar for adding new symbols to this package is intentionally set high. + +Before proposing a change, please discuss your change by raising an issue. + +## Licence + +BSD-2-Clause diff --git a/vendor/src/github.com/pkg/errors/bench_test.go b/vendor/src/github.com/pkg/errors/bench_test.go new file mode 100755 index 000000000..0416a3cbb --- /dev/null +++ b/vendor/src/github.com/pkg/errors/bench_test.go @@ -0,0 +1,59 @@ +// +build go1.7 + +package errors + +import ( + "fmt" + "testing" + + stderrors "errors" +) + +func noErrors(at, depth int) error { + if at >= depth { + return stderrors.New("no error") + } + return noErrors(at+1, depth) +} +func yesErrors(at, depth int) error { + if at >= depth { + return New("ye error") + } + return yesErrors(at+1, depth) +} + +func BenchmarkErrors(b *testing.B) { + var toperr error + type run struct { + stack int + std bool + } + runs := []run{ + {10, false}, + {10, true}, + {100, false}, + {100, true}, + {1000, false}, + {1000, true}, + } + for _, r := range runs { + part := "pkg/errors" + if r.std { + part = "errors" + } + name := fmt.Sprintf("%s-stack-%d", part, r.stack) + b.Run(name, func(b *testing.B) { + var err error + f := yesErrors + if r.std { + f = noErrors + } + b.ReportAllocs() + for i := 0; i < b.N; i++ { + err = f(0, r.stack) + } + b.StopTimer() + toperr = err + }) + } +} diff --git a/vendor/src/github.com/pkg/errors/errors.go b/vendor/src/github.com/pkg/errors/errors.go new file mode 100755 index 000000000..842ee8045 --- /dev/null +++ b/vendor/src/github.com/pkg/errors/errors.go @@ -0,0 +1,269 @@ +// Package errors provides simple error handling primitives. +// +// The traditional error handling idiom in Go is roughly akin to +// +// if err != nil { +// return err +// } +// +// which applied recursively up the call stack results in error reports +// without context or debugging information. The errors package allows +// programmers to add context to the failure path in their code in a way +// that does not destroy the original value of the error. +// +// Adding context to an error +// +// The errors.Wrap function returns a new error that adds context to the +// original error by recording a stack trace at the point Wrap is called, +// and the supplied message. For example +// +// _, err := ioutil.ReadAll(r) +// if err != nil { +// return errors.Wrap(err, "read failed") +// } +// +// If additional control is required the errors.WithStack and errors.WithMessage +// functions destructure errors.Wrap into its component operations of annotating +// an error with a stack trace and an a message, respectively. +// +// Retrieving the cause of an error +// +// Using errors.Wrap constructs a stack of errors, adding context to the +// preceding error. Depending on the nature of the error it may be necessary +// to reverse the operation of errors.Wrap to retrieve the original error +// for inspection. Any error value which implements this interface +// +// type causer interface { +// Cause() error +// } +// +// can be inspected by errors.Cause. errors.Cause will recursively retrieve +// the topmost error which does not implement causer, which is assumed to be +// the original cause. For example: +// +// switch err := errors.Cause(err).(type) { +// case *MyError: +// // handle specifically +// default: +// // unknown error +// } +// +// causer interface is not exported by this package, but is considered a part +// of stable public API. +// +// Formatted printing of errors +// +// All error values returned from this package implement fmt.Formatter and can +// be formatted by the fmt package. The following verbs are supported +// +// %s print the error. If the error has a Cause it will be +// printed recursively +// %v see %s +// %+v extended format. Each Frame of the error's StackTrace will +// be printed in detail. +// +// Retrieving the stack trace of an error or wrapper +// +// New, Errorf, Wrap, and Wrapf record a stack trace at the point they are +// invoked. This information can be retrieved with the following interface. +// +// type stackTracer interface { +// StackTrace() errors.StackTrace +// } +// +// Where errors.StackTrace is defined as +// +// type StackTrace []Frame +// +// The Frame type represents a call site in the stack trace. Frame supports +// the fmt.Formatter interface that can be used for printing information about +// the stack trace of this error. For example: +// +// if err, ok := err.(stackTracer); ok { +// for _, f := range err.StackTrace() { +// fmt.Printf("%+s:%d", f) +// } +// } +// +// stackTracer interface is not exported by this package, but is considered a part +// of stable public API. +// +// See the documentation for Frame.Format for more details. +package errors + +import ( + "fmt" + "io" +) + +// New returns an error with the supplied message. +// New also records the stack trace at the point it was called. +func New(message string) error { + return &fundamental{ + msg: message, + stack: callers(), + } +} + +// Errorf formats according to a format specifier and returns the string +// as a value that satisfies error. +// Errorf also records the stack trace at the point it was called. +func Errorf(format string, args ...interface{}) error { + return &fundamental{ + msg: fmt.Sprintf(format, args...), + stack: callers(), + } +} + +// fundamental is an error that has a message and a stack, but no caller. +type fundamental struct { + msg string + *stack +} + +func (f *fundamental) Error() string { return f.msg } + +func (f *fundamental) Format(s fmt.State, verb rune) { + switch verb { + case 'v': + if s.Flag('+') { + io.WriteString(s, f.msg) + f.stack.Format(s, verb) + return + } + fallthrough + case 's': + io.WriteString(s, f.msg) + case 'q': + fmt.Fprintf(s, "%q", f.msg) + } +} + +// WithStack annotates err with a stack trace at the point WithStack was called. +// If err is nil, WithStack returns nil. +func WithStack(err error) error { + if err == nil { + return nil + } + return &withStack{ + err, + callers(), + } +} + +type withStack struct { + error + *stack +} + +func (w *withStack) Cause() error { return w.error } + +func (w *withStack) Format(s fmt.State, verb rune) { + switch verb { + case 'v': + if s.Flag('+') { + fmt.Fprintf(s, "%+v", w.Cause()) + w.stack.Format(s, verb) + return + } + fallthrough + case 's': + io.WriteString(s, w.Error()) + case 'q': + fmt.Fprintf(s, "%q", w.Error()) + } +} + +// Wrap returns an error annotating err with a stack trace +// at the point Wrap is called, and the supplied message. +// If err is nil, Wrap returns nil. +func Wrap(err error, message string) error { + if err == nil { + return nil + } + err = &withMessage{ + cause: err, + msg: message, + } + return &withStack{ + err, + callers(), + } +} + +// Wrapf returns an error annotating err with a stack trace +// at the point Wrapf is call, and the format specifier. +// If err is nil, Wrapf returns nil. +func Wrapf(err error, format string, args ...interface{}) error { + if err == nil { + return nil + } + err = &withMessage{ + cause: err, + msg: fmt.Sprintf(format, args...), + } + return &withStack{ + err, + callers(), + } +} + +// WithMessage annotates err with a new message. +// If err is nil, WithMessage returns nil. +func WithMessage(err error, message string) error { + if err == nil { + return nil + } + return &withMessage{ + cause: err, + msg: message, + } +} + +type withMessage struct { + cause error + msg string +} + +func (w *withMessage) Error() string { return w.msg + ": " + w.cause.Error() } +func (w *withMessage) Cause() error { return w.cause } + +func (w *withMessage) Format(s fmt.State, verb rune) { + switch verb { + case 'v': + if s.Flag('+') { + fmt.Fprintf(s, "%+v\n", w.Cause()) + io.WriteString(s, w.msg) + return + } + fallthrough + case 's', 'q': + io.WriteString(s, w.Error()) + } +} + +// Cause returns the underlying cause of the error, if possible. +// An error value has a cause if it implements the following +// interface: +// +// type causer interface { +// Cause() error +// } +// +// If the error does not implement Cause, the original error will +// be returned. If the error is nil, nil will be returned without further +// investigation. +func Cause(err error) error { + type causer interface { + Cause() error + } + + for err != nil { + cause, ok := err.(causer) + if !ok { + break + } + err = cause.Cause() + } + return err +} diff --git a/vendor/src/github.com/pkg/errors/errors_test.go b/vendor/src/github.com/pkg/errors/errors_test.go new file mode 100755 index 000000000..1d8c63558 --- /dev/null +++ b/vendor/src/github.com/pkg/errors/errors_test.go @@ -0,0 +1,226 @@ +package errors + +import ( + "errors" + "fmt" + "io" + "reflect" + "testing" +) + +func TestNew(t *testing.T) { + tests := []struct { + err string + want error + }{ + {"", fmt.Errorf("")}, + {"foo", fmt.Errorf("foo")}, + {"foo", New("foo")}, + {"string with format specifiers: %v", errors.New("string with format specifiers: %v")}, + } + + for _, tt := range tests { + got := New(tt.err) + if got.Error() != tt.want.Error() { + t.Errorf("New.Error(): got: %q, want %q", got, tt.want) + } + } +} + +func TestWrapNil(t *testing.T) { + got := Wrap(nil, "no error") + if got != nil { + t.Errorf("Wrap(nil, \"no error\"): got %#v, expected nil", got) + } +} + +func TestWrap(t *testing.T) { + tests := []struct { + err error + message string + want string + }{ + {io.EOF, "read error", "read error: EOF"}, + {Wrap(io.EOF, "read error"), "client error", "client error: read error: EOF"}, + } + + for _, tt := range tests { + got := Wrap(tt.err, tt.message).Error() + if got != tt.want { + t.Errorf("Wrap(%v, %q): got: %v, want %v", tt.err, tt.message, got, tt.want) + } + } +} + +type nilError struct{} + +func (nilError) Error() string { return "nil error" } + +func TestCause(t *testing.T) { + x := New("error") + tests := []struct { + err error + want error + }{{ + // nil error is nil + err: nil, + want: nil, + }, { + // explicit nil error is nil + err: (error)(nil), + want: nil, + }, { + // typed nil is nil + err: (*nilError)(nil), + want: (*nilError)(nil), + }, { + // uncaused error is unaffected + err: io.EOF, + want: io.EOF, + }, { + // caused error returns cause + err: Wrap(io.EOF, "ignored"), + want: io.EOF, + }, { + err: x, // return from errors.New + want: x, + }, { + WithMessage(nil, "whoops"), + nil, + }, { + WithMessage(io.EOF, "whoops"), + io.EOF, + }, { + WithStack(nil), + nil, + }, { + WithStack(io.EOF), + io.EOF, + }} + + for i, tt := range tests { + got := Cause(tt.err) + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("test %d: got %#v, want %#v", i+1, got, tt.want) + } + } +} + +func TestWrapfNil(t *testing.T) { + got := Wrapf(nil, "no error") + if got != nil { + t.Errorf("Wrapf(nil, \"no error\"): got %#v, expected nil", got) + } +} + +func TestWrapf(t *testing.T) { + tests := []struct { + err error + message string + want string + }{ + {io.EOF, "read error", "read error: EOF"}, + {Wrapf(io.EOF, "read error without format specifiers"), "client error", "client error: read error without format specifiers: EOF"}, + {Wrapf(io.EOF, "read error with %d format specifier", 1), "client error", "client error: read error with 1 format specifier: EOF"}, + } + + for _, tt := range tests { + got := Wrapf(tt.err, tt.message).Error() + if got != tt.want { + t.Errorf("Wrapf(%v, %q): got: %v, want %v", tt.err, tt.message, got, tt.want) + } + } +} + +func TestErrorf(t *testing.T) { + tests := []struct { + err error + want string + }{ + {Errorf("read error without format specifiers"), "read error without format specifiers"}, + {Errorf("read error with %d format specifier", 1), "read error with 1 format specifier"}, + } + + for _, tt := range tests { + got := tt.err.Error() + if got != tt.want { + t.Errorf("Errorf(%v): got: %q, want %q", tt.err, got, tt.want) + } + } +} + +func TestWithStackNil(t *testing.T) { + got := WithStack(nil) + if got != nil { + t.Errorf("WithStack(nil): got %#v, expected nil", got) + } +} + +func TestWithStack(t *testing.T) { + tests := []struct { + err error + want string + }{ + {io.EOF, "EOF"}, + {WithStack(io.EOF), "EOF"}, + } + + for _, tt := range tests { + got := WithStack(tt.err).Error() + if got != tt.want { + t.Errorf("WithStack(%v): got: %v, want %v", tt.err, got, tt.want) + } + } +} + +func TestWithMessageNil(t *testing.T) { + got := WithMessage(nil, "no error") + if got != nil { + t.Errorf("WithMessage(nil, \"no error\"): got %#v, expected nil", got) + } +} + +func TestWithMessage(t *testing.T) { + tests := []struct { + err error + message string + want string + }{ + {io.EOF, "read error", "read error: EOF"}, + {WithMessage(io.EOF, "read error"), "client error", "client error: read error: EOF"}, + } + + for _, tt := range tests { + got := WithMessage(tt.err, tt.message).Error() + if got != tt.want { + t.Errorf("WithMessage(%v, %q): got: %q, want %q", tt.err, tt.message, got, tt.want) + } + } + +} + +// errors.New, etc values are not expected to be compared by value +// but the change in errors#27 made them incomparable. Assert that +// various kinds of errors have a functional equality operator, even +// if the result of that equality is always false. +func TestErrorEquality(t *testing.T) { + vals := []error{ + nil, + io.EOF, + errors.New("EOF"), + New("EOF"), + Errorf("EOF"), + Wrap(io.EOF, "EOF"), + Wrapf(io.EOF, "EOF%d", 2), + WithMessage(nil, "whoops"), + WithMessage(io.EOF, "whoops"), + WithStack(io.EOF), + WithStack(nil), + } + + for i := range vals { + for j := range vals { + _ = vals[i] == vals[j] // mustn't panic + } + } +} diff --git a/vendor/src/github.com/pkg/errors/example_test.go b/vendor/src/github.com/pkg/errors/example_test.go new file mode 100755 index 000000000..c1fc13e38 --- /dev/null +++ b/vendor/src/github.com/pkg/errors/example_test.go @@ -0,0 +1,205 @@ +package errors_test + +import ( + "fmt" + + "github.com/pkg/errors" +) + +func ExampleNew() { + err := errors.New("whoops") + fmt.Println(err) + + // Output: whoops +} + +func ExampleNew_printf() { + err := errors.New("whoops") + fmt.Printf("%+v", err) + + // Example output: + // whoops + // github.com/pkg/errors_test.ExampleNew_printf + // /home/dfc/src/github.com/pkg/errors/example_test.go:17 + // testing.runExample + // /home/dfc/go/src/testing/example.go:114 + // testing.RunExamples + // /home/dfc/go/src/testing/example.go:38 + // testing.(*M).Run + // /home/dfc/go/src/testing/testing.go:744 + // main.main + // /github.com/pkg/errors/_test/_testmain.go:106 + // runtime.main + // /home/dfc/go/src/runtime/proc.go:183 + // runtime.goexit + // /home/dfc/go/src/runtime/asm_amd64.s:2059 +} + +func ExampleWithMessage() { + cause := errors.New("whoops") + err := errors.WithMessage(cause, "oh noes") + fmt.Println(err) + + // Output: oh noes: whoops +} + +func ExampleWithStack() { + cause := errors.New("whoops") + err := errors.WithStack(cause) + fmt.Println(err) + + // Output: whoops +} + +func ExampleWithStack_printf() { + cause := errors.New("whoops") + err := errors.WithStack(cause) + fmt.Printf("%+v", err) + + // Example Output: + // whoops + // github.com/pkg/errors_test.ExampleWithStack_printf + // /home/fabstu/go/src/github.com/pkg/errors/example_test.go:55 + // testing.runExample + // /usr/lib/go/src/testing/example.go:114 + // testing.RunExamples + // /usr/lib/go/src/testing/example.go:38 + // testing.(*M).Run + // /usr/lib/go/src/testing/testing.go:744 + // main.main + // github.com/pkg/errors/_test/_testmain.go:106 + // runtime.main + // /usr/lib/go/src/runtime/proc.go:183 + // runtime.goexit + // /usr/lib/go/src/runtime/asm_amd64.s:2086 + // github.com/pkg/errors_test.ExampleWithStack_printf + // /home/fabstu/go/src/github.com/pkg/errors/example_test.go:56 + // testing.runExample + // /usr/lib/go/src/testing/example.go:114 + // testing.RunExamples + // /usr/lib/go/src/testing/example.go:38 + // testing.(*M).Run + // /usr/lib/go/src/testing/testing.go:744 + // main.main + // github.com/pkg/errors/_test/_testmain.go:106 + // runtime.main + // /usr/lib/go/src/runtime/proc.go:183 + // runtime.goexit + // /usr/lib/go/src/runtime/asm_amd64.s:2086 +} + +func ExampleWrap() { + cause := errors.New("whoops") + err := errors.Wrap(cause, "oh noes") + fmt.Println(err) + + // Output: oh noes: whoops +} + +func fn() error { + e1 := errors.New("error") + e2 := errors.Wrap(e1, "inner") + e3 := errors.Wrap(e2, "middle") + return errors.Wrap(e3, "outer") +} + +func ExampleCause() { + err := fn() + fmt.Println(err) + fmt.Println(errors.Cause(err)) + + // Output: outer: middle: inner: error + // error +} + +func ExampleWrap_extended() { + err := fn() + fmt.Printf("%+v\n", err) + + // Example output: + // error + // github.com/pkg/errors_test.fn + // /home/dfc/src/github.com/pkg/errors/example_test.go:47 + // github.com/pkg/errors_test.ExampleCause_printf + // /home/dfc/src/github.com/pkg/errors/example_test.go:63 + // testing.runExample + // /home/dfc/go/src/testing/example.go:114 + // testing.RunExamples + // /home/dfc/go/src/testing/example.go:38 + // testing.(*M).Run + // /home/dfc/go/src/testing/testing.go:744 + // main.main + // /github.com/pkg/errors/_test/_testmain.go:104 + // runtime.main + // /home/dfc/go/src/runtime/proc.go:183 + // runtime.goexit + // /home/dfc/go/src/runtime/asm_amd64.s:2059 + // github.com/pkg/errors_test.fn + // /home/dfc/src/github.com/pkg/errors/example_test.go:48: inner + // github.com/pkg/errors_test.fn + // /home/dfc/src/github.com/pkg/errors/example_test.go:49: middle + // github.com/pkg/errors_test.fn + // /home/dfc/src/github.com/pkg/errors/example_test.go:50: outer +} + +func ExampleWrapf() { + cause := errors.New("whoops") + err := errors.Wrapf(cause, "oh noes #%d", 2) + fmt.Println(err) + + // Output: oh noes #2: whoops +} + +func ExampleErrorf_extended() { + err := errors.Errorf("whoops: %s", "foo") + fmt.Printf("%+v", err) + + // Example output: + // whoops: foo + // github.com/pkg/errors_test.ExampleErrorf + // /home/dfc/src/github.com/pkg/errors/example_test.go:101 + // testing.runExample + // /home/dfc/go/src/testing/example.go:114 + // testing.RunExamples + // /home/dfc/go/src/testing/example.go:38 + // testing.(*M).Run + // /home/dfc/go/src/testing/testing.go:744 + // main.main + // /github.com/pkg/errors/_test/_testmain.go:102 + // runtime.main + // /home/dfc/go/src/runtime/proc.go:183 + // runtime.goexit + // /home/dfc/go/src/runtime/asm_amd64.s:2059 +} + +func Example_stackTrace() { + type stackTracer interface { + StackTrace() errors.StackTrace + } + + err, ok := errors.Cause(fn()).(stackTracer) + if !ok { + panic("oops, err does not implement stackTracer") + } + + st := err.StackTrace() + fmt.Printf("%+v", st[0:2]) // top two frames + + // Example output: + // github.com/pkg/errors_test.fn + // /home/dfc/src/github.com/pkg/errors/example_test.go:47 + // github.com/pkg/errors_test.Example_stackTrace + // /home/dfc/src/github.com/pkg/errors/example_test.go:127 +} + +func ExampleCause_printf() { + err := errors.Wrap(func() error { + return func() error { + return errors.Errorf("hello %s", fmt.Sprintf("world")) + }() + }(), "failed") + + fmt.Printf("%v", err) + + // Output: failed: hello world +} diff --git a/vendor/src/github.com/pkg/errors/format_test.go b/vendor/src/github.com/pkg/errors/format_test.go new file mode 100755 index 000000000..15fd7d89d --- /dev/null +++ b/vendor/src/github.com/pkg/errors/format_test.go @@ -0,0 +1,535 @@ +package errors + +import ( + "errors" + "fmt" + "io" + "regexp" + "strings" + "testing" +) + +func TestFormatNew(t *testing.T) { + tests := []struct { + error + format string + want string + }{{ + New("error"), + "%s", + "error", + }, { + New("error"), + "%v", + "error", + }, { + New("error"), + "%+v", + "error\n" + + "github.com/pkg/errors.TestFormatNew\n" + + "\t.+/github.com/pkg/errors/format_test.go:26", + }, { + New("error"), + "%q", + `"error"`, + }} + + for i, tt := range tests { + testFormatRegexp(t, i, tt.error, tt.format, tt.want) + } +} + +func TestFormatErrorf(t *testing.T) { + tests := []struct { + error + format string + want string + }{{ + Errorf("%s", "error"), + "%s", + "error", + }, { + Errorf("%s", "error"), + "%v", + "error", + }, { + Errorf("%s", "error"), + "%+v", + "error\n" + + "github.com/pkg/errors.TestFormatErrorf\n" + + "\t.+/github.com/pkg/errors/format_test.go:56", + }} + + for i, tt := range tests { + testFormatRegexp(t, i, tt.error, tt.format, tt.want) + } +} + +func TestFormatWrap(t *testing.T) { + tests := []struct { + error + format string + want string + }{{ + Wrap(New("error"), "error2"), + "%s", + "error2: error", + }, { + Wrap(New("error"), "error2"), + "%v", + "error2: error", + }, { + Wrap(New("error"), "error2"), + "%+v", + "error\n" + + "github.com/pkg/errors.TestFormatWrap\n" + + "\t.+/github.com/pkg/errors/format_test.go:82", + }, { + Wrap(io.EOF, "error"), + "%s", + "error: EOF", + }, { + Wrap(io.EOF, "error"), + "%v", + "error: EOF", + }, { + Wrap(io.EOF, "error"), + "%+v", + "EOF\n" + + "error\n" + + "github.com/pkg/errors.TestFormatWrap\n" + + "\t.+/github.com/pkg/errors/format_test.go:96", + }, { + Wrap(Wrap(io.EOF, "error1"), "error2"), + "%+v", + "EOF\n" + + "error1\n" + + "github.com/pkg/errors.TestFormatWrap\n" + + "\t.+/github.com/pkg/errors/format_test.go:103\n", + }, { + Wrap(New("error with space"), "context"), + "%q", + `"context: error with space"`, + }} + + for i, tt := range tests { + testFormatRegexp(t, i, tt.error, tt.format, tt.want) + } +} + +func TestFormatWrapf(t *testing.T) { + tests := []struct { + error + format string + want string + }{{ + Wrapf(io.EOF, "error%d", 2), + "%s", + "error2: EOF", + }, { + Wrapf(io.EOF, "error%d", 2), + "%v", + "error2: EOF", + }, { + Wrapf(io.EOF, "error%d", 2), + "%+v", + "EOF\n" + + "error2\n" + + "github.com/pkg/errors.TestFormatWrapf\n" + + "\t.+/github.com/pkg/errors/format_test.go:134", + }, { + Wrapf(New("error"), "error%d", 2), + "%s", + "error2: error", + }, { + Wrapf(New("error"), "error%d", 2), + "%v", + "error2: error", + }, { + Wrapf(New("error"), "error%d", 2), + "%+v", + "error\n" + + "github.com/pkg/errors.TestFormatWrapf\n" + + "\t.+/github.com/pkg/errors/format_test.go:149", + }} + + for i, tt := range tests { + testFormatRegexp(t, i, tt.error, tt.format, tt.want) + } +} + +func TestFormatWithStack(t *testing.T) { + tests := []struct { + error + format string + want []string + }{{ + WithStack(io.EOF), + "%s", + []string{"EOF"}, + }, { + WithStack(io.EOF), + "%v", + []string{"EOF"}, + }, { + WithStack(io.EOF), + "%+v", + []string{"EOF", + "github.com/pkg/errors.TestFormatWithStack\n" + + "\t.+/github.com/pkg/errors/format_test.go:175"}, + }, { + WithStack(New("error")), + "%s", + []string{"error"}, + }, { + WithStack(New("error")), + "%v", + []string{"error"}, + }, { + WithStack(New("error")), + "%+v", + []string{"error", + "github.com/pkg/errors.TestFormatWithStack\n" + + "\t.+/github.com/pkg/errors/format_test.go:189", + "github.com/pkg/errors.TestFormatWithStack\n" + + "\t.+/github.com/pkg/errors/format_test.go:189"}, + }, { + WithStack(WithStack(io.EOF)), + "%+v", + []string{"EOF", + "github.com/pkg/errors.TestFormatWithStack\n" + + "\t.+/github.com/pkg/errors/format_test.go:197", + "github.com/pkg/errors.TestFormatWithStack\n" + + "\t.+/github.com/pkg/errors/format_test.go:197"}, + }, { + WithStack(WithStack(Wrapf(io.EOF, "message"))), + "%+v", + []string{"EOF", + "message", + "github.com/pkg/errors.TestFormatWithStack\n" + + "\t.+/github.com/pkg/errors/format_test.go:205", + "github.com/pkg/errors.TestFormatWithStack\n" + + "\t.+/github.com/pkg/errors/format_test.go:205", + "github.com/pkg/errors.TestFormatWithStack\n" + + "\t.+/github.com/pkg/errors/format_test.go:205"}, + }, { + WithStack(Errorf("error%d", 1)), + "%+v", + []string{"error1", + "github.com/pkg/errors.TestFormatWithStack\n" + + "\t.+/github.com/pkg/errors/format_test.go:216", + "github.com/pkg/errors.TestFormatWithStack\n" + + "\t.+/github.com/pkg/errors/format_test.go:216"}, + }} + + for i, tt := range tests { + testFormatCompleteCompare(t, i, tt.error, tt.format, tt.want, true) + } +} + +func TestFormatWithMessage(t *testing.T) { + tests := []struct { + error + format string + want []string + }{{ + WithMessage(New("error"), "error2"), + "%s", + []string{"error2: error"}, + }, { + WithMessage(New("error"), "error2"), + "%v", + []string{"error2: error"}, + }, { + WithMessage(New("error"), "error2"), + "%+v", + []string{ + "error", + "github.com/pkg/errors.TestFormatWithMessage\n" + + "\t.+/github.com/pkg/errors/format_test.go:244", + "error2"}, + }, { + WithMessage(io.EOF, "addition1"), + "%s", + []string{"addition1: EOF"}, + }, { + WithMessage(io.EOF, "addition1"), + "%v", + []string{"addition1: EOF"}, + }, { + WithMessage(io.EOF, "addition1"), + "%+v", + []string{"EOF", "addition1"}, + }, { + WithMessage(WithMessage(io.EOF, "addition1"), "addition2"), + "%v", + []string{"addition2: addition1: EOF"}, + }, { + WithMessage(WithMessage(io.EOF, "addition1"), "addition2"), + "%+v", + []string{"EOF", "addition1", "addition2"}, + }, { + Wrap(WithMessage(io.EOF, "error1"), "error2"), + "%+v", + []string{"EOF", "error1", "error2", + "github.com/pkg/errors.TestFormatWithMessage\n" + + "\t.+/github.com/pkg/errors/format_test.go:272"}, + }, { + WithMessage(Errorf("error%d", 1), "error2"), + "%+v", + []string{"error1", + "github.com/pkg/errors.TestFormatWithMessage\n" + + "\t.+/github.com/pkg/errors/format_test.go:278", + "error2"}, + }, { + WithMessage(WithStack(io.EOF), "error"), + "%+v", + []string{ + "EOF", + "github.com/pkg/errors.TestFormatWithMessage\n" + + "\t.+/github.com/pkg/errors/format_test.go:285", + "error"}, + }, { + WithMessage(Wrap(WithStack(io.EOF), "inside-error"), "outside-error"), + "%+v", + []string{ + "EOF", + "github.com/pkg/errors.TestFormatWithMessage\n" + + "\t.+/github.com/pkg/errors/format_test.go:293", + "inside-error", + "github.com/pkg/errors.TestFormatWithMessage\n" + + "\t.+/github.com/pkg/errors/format_test.go:293", + "outside-error"}, + }} + + for i, tt := range tests { + testFormatCompleteCompare(t, i, tt.error, tt.format, tt.want, true) + } +} + +func TestFormatGeneric(t *testing.T) { + starts := []struct { + err error + want []string + }{ + {New("new-error"), []string{ + "new-error", + "github.com/pkg/errors.TestFormatGeneric\n" + + "\t.+/github.com/pkg/errors/format_test.go:315"}, + }, {Errorf("errorf-error"), []string{ + "errorf-error", + "github.com/pkg/errors.TestFormatGeneric\n" + + "\t.+/github.com/pkg/errors/format_test.go:319"}, + }, {errors.New("errors-new-error"), []string{ + "errors-new-error"}, + }, + } + + wrappers := []wrapper{ + { + func(err error) error { return WithMessage(err, "with-message") }, + []string{"with-message"}, + }, { + func(err error) error { return WithStack(err) }, + []string{ + "github.com/pkg/errors.(func·002|TestFormatGeneric.func2)\n\t" + + ".+/github.com/pkg/errors/format_test.go:333", + }, + }, { + func(err error) error { return Wrap(err, "wrap-error") }, + []string{ + "wrap-error", + "github.com/pkg/errors.(func·003|TestFormatGeneric.func3)\n\t" + + ".+/github.com/pkg/errors/format_test.go:339", + }, + }, { + func(err error) error { return Wrapf(err, "wrapf-error%d", 1) }, + []string{ + "wrapf-error1", + "github.com/pkg/errors.(func·004|TestFormatGeneric.func4)\n\t" + + ".+/github.com/pkg/errors/format_test.go:346", + }, + }, + } + + for s := range starts { + err := starts[s].err + want := starts[s].want + testFormatCompleteCompare(t, s, err, "%+v", want, false) + testGenericRecursive(t, err, want, wrappers, 3) + } +} + +func testFormatRegexp(t *testing.T, n int, arg interface{}, format, want string) { + got := fmt.Sprintf(format, arg) + gotLines := strings.SplitN(got, "\n", -1) + wantLines := strings.SplitN(want, "\n", -1) + + if len(wantLines) > len(gotLines) { + t.Errorf("test %d: wantLines(%d) > gotLines(%d):\n got: %q\nwant: %q", n+1, len(wantLines), len(gotLines), got, want) + return + } + + for i, w := range wantLines { + match, err := regexp.MatchString(w, gotLines[i]) + if err != nil { + t.Fatal(err) + } + if !match { + t.Errorf("test %d: line %d: fmt.Sprintf(%q, err):\n got: %q\nwant: %q", n+1, i+1, format, got, want) + } + } +} + +var stackLineR = regexp.MustCompile(`\.`) + +// parseBlocks parses input into a slice, where: +// - incase entry contains a newline, its a stacktrace +// - incase entry contains no newline, its a solo line. +// +// Detecting stack boundaries only works incase the WithStack-calls are +// to be found on the same line, thats why it is optionally here. +// +// Example use: +// +// for _, e := range blocks { +// if strings.ContainsAny(e, "\n") { +// // Match as stack +// } else { +// // Match as line +// } +// } +// +func parseBlocks(input string, detectStackboundaries bool) ([]string, error) { + var blocks []string + + stack := "" + wasStack := false + lines := map[string]bool{} // already found lines + + for _, l := range strings.Split(input, "\n") { + isStackLine := stackLineR.MatchString(l) + + switch { + case !isStackLine && wasStack: + blocks = append(blocks, stack, l) + stack = "" + lines = map[string]bool{} + case isStackLine: + if wasStack { + // Detecting two stacks after another, possible cause lines match in + // our tests due to WithStack(WithStack(io.EOF)) on same line. + if detectStackboundaries { + if lines[l] { + if len(stack) == 0 { + return nil, errors.New("len of block must not be zero here") + } + + blocks = append(blocks, stack) + stack = l + lines = map[string]bool{l: true} + continue + } + } + + stack = stack + "\n" + l + } else { + stack = l + } + lines[l] = true + case !isStackLine && !wasStack: + blocks = append(blocks, l) + default: + return nil, errors.New("must not happen") + } + + wasStack = isStackLine + } + + // Use up stack + if stack != "" { + blocks = append(blocks, stack) + } + return blocks, nil +} + +func testFormatCompleteCompare(t *testing.T, n int, arg interface{}, format string, want []string, detectStackBoundaries bool) { + gotStr := fmt.Sprintf(format, arg) + + got, err := parseBlocks(gotStr, detectStackBoundaries) + if err != nil { + t.Fatal(err) + } + + if len(got) != len(want) { + t.Fatalf("test %d: fmt.Sprintf(%s, err) -> wrong number of blocks: got(%d) want(%d)\n got: %s\nwant: %s\ngotStr: %q", + n+1, format, len(got), len(want), prettyBlocks(got), prettyBlocks(want), gotStr) + } + + for i := range got { + if strings.ContainsAny(want[i], "\n") { + // Match as stack + match, err := regexp.MatchString(want[i], got[i]) + if err != nil { + t.Fatal(err) + } + if !match { + t.Fatalf("test %d: block %d: fmt.Sprintf(%q, err):\ngot:\n%q\nwant:\n%q\nall-got:\n%s\nall-want:\n%s\n", + n+1, i+1, format, got[i], want[i], prettyBlocks(got), prettyBlocks(want)) + } + } else { + // Match as message + if got[i] != want[i] { + t.Fatalf("test %d: fmt.Sprintf(%s, err) at block %d got != want:\n got: %q\nwant: %q", n+1, format, i+1, got[i], want[i]) + } + } + } +} + +type wrapper struct { + wrap func(err error) error + want []string +} + +func prettyBlocks(blocks []string, prefix ...string) string { + var out []string + + for _, b := range blocks { + out = append(out, fmt.Sprintf("%v", b)) + } + + return " " + strings.Join(out, "\n ") +} + +func testGenericRecursive(t *testing.T, beforeErr error, beforeWant []string, list []wrapper, maxDepth int) { + if len(beforeWant) == 0 { + panic("beforeWant must not be empty") + } + for _, w := range list { + if len(w.want) == 0 { + panic("want must not be empty") + } + + err := w.wrap(beforeErr) + + // Copy required cause append(beforeWant, ..) modified beforeWant subtly. + beforeCopy := make([]string, len(beforeWant)) + copy(beforeCopy, beforeWant) + + beforeWant := beforeCopy + last := len(beforeWant) - 1 + var want []string + + // Merge two stacks behind each other. + if strings.ContainsAny(beforeWant[last], "\n") && strings.ContainsAny(w.want[0], "\n") { + want = append(beforeWant[:last], append([]string{beforeWant[last] + "((?s).*)" + w.want[0]}, w.want[1:]...)...) + } else { + want = append(beforeWant, w.want...) + } + + testFormatCompleteCompare(t, maxDepth, err, "%+v", want, false) + if maxDepth > 0 { + testGenericRecursive(t, err, want, list, maxDepth-1) + } + } +} diff --git a/vendor/src/github.com/pkg/errors/stack.go b/vendor/src/github.com/pkg/errors/stack.go new file mode 100755 index 000000000..6b1f2891a --- /dev/null +++ b/vendor/src/github.com/pkg/errors/stack.go @@ -0,0 +1,178 @@ +package errors + +import ( + "fmt" + "io" + "path" + "runtime" + "strings" +) + +// Frame represents a program counter inside a stack frame. +type Frame uintptr + +// pc returns the program counter for this frame; +// multiple frames may have the same PC value. +func (f Frame) pc() uintptr { return uintptr(f) - 1 } + +// file returns the full path to the file that contains the +// function for this Frame's pc. +func (f Frame) file() string { + fn := runtime.FuncForPC(f.pc()) + if fn == nil { + return "unknown" + } + file, _ := fn.FileLine(f.pc()) + return file +} + +// line returns the line number of source code of the +// function for this Frame's pc. +func (f Frame) line() int { + fn := runtime.FuncForPC(f.pc()) + if fn == nil { + return 0 + } + _, line := fn.FileLine(f.pc()) + return line +} + +// Format formats the frame according to the fmt.Formatter interface. +// +// %s source file +// %d source line +// %n function name +// %v equivalent to %s:%d +// +// Format accepts flags that alter the printing of some verbs, as follows: +// +// %+s path of source file relative to the compile time GOPATH +// %+v equivalent to %+s:%d +func (f Frame) Format(s fmt.State, verb rune) { + switch verb { + case 's': + switch { + case s.Flag('+'): + pc := f.pc() + fn := runtime.FuncForPC(pc) + if fn == nil { + io.WriteString(s, "unknown") + } else { + file, _ := fn.FileLine(pc) + fmt.Fprintf(s, "%s\n\t%s", fn.Name(), file) + } + default: + io.WriteString(s, path.Base(f.file())) + } + case 'd': + fmt.Fprintf(s, "%d", f.line()) + case 'n': + name := runtime.FuncForPC(f.pc()).Name() + io.WriteString(s, funcname(name)) + case 'v': + f.Format(s, 's') + io.WriteString(s, ":") + f.Format(s, 'd') + } +} + +// StackTrace is stack of Frames from innermost (newest) to outermost (oldest). +type StackTrace []Frame + +func (st StackTrace) Format(s fmt.State, verb rune) { + switch verb { + case 'v': + switch { + case s.Flag('+'): + for _, f := range st { + fmt.Fprintf(s, "\n%+v", f) + } + case s.Flag('#'): + fmt.Fprintf(s, "%#v", []Frame(st)) + default: + fmt.Fprintf(s, "%v", []Frame(st)) + } + case 's': + fmt.Fprintf(s, "%s", []Frame(st)) + } +} + +// stack represents a stack of program counters. +type stack []uintptr + +func (s *stack) Format(st fmt.State, verb rune) { + switch verb { + case 'v': + switch { + case st.Flag('+'): + for _, pc := range *s { + f := Frame(pc) + fmt.Fprintf(st, "\n%+v", f) + } + } + } +} + +func (s *stack) StackTrace() StackTrace { + f := make([]Frame, len(*s)) + for i := 0; i < len(f); i++ { + f[i] = Frame((*s)[i]) + } + return f +} + +func callers() *stack { + const depth = 32 + var pcs [depth]uintptr + n := runtime.Callers(3, pcs[:]) + var st stack = pcs[0:n] + return &st +} + +// funcname removes the path prefix component of a function's name reported by func.Name(). +func funcname(name string) string { + i := strings.LastIndex(name, "/") + name = name[i+1:] + i = strings.Index(name, ".") + return name[i+1:] +} + +func trimGOPATH(name, file string) string { + // Here we want to get the source file path relative to the compile time + // GOPATH. As of Go 1.6.x there is no direct way to know the compiled + // GOPATH at runtime, but we can infer the number of path segments in the + // GOPATH. We note that fn.Name() returns the function name qualified by + // the import path, which does not include the GOPATH. Thus we can trim + // segments from the beginning of the file path until the number of path + // separators remaining is one more than the number of path separators in + // the function name. For example, given: + // + // GOPATH /home/user + // file /home/user/src/pkg/sub/file.go + // fn.Name() pkg/sub.Type.Method + // + // We want to produce: + // + // pkg/sub/file.go + // + // From this we can easily see that fn.Name() has one less path separator + // than our desired output. We count separators from the end of the file + // path until it finds two more than in the function name and then move + // one character forward to preserve the initial path segment without a + // leading separator. + const sep = "/" + goal := strings.Count(name, sep) + 2 + i := len(file) + for n := 0; n < goal; n++ { + i = strings.LastIndex(file[:i], sep) + if i == -1 { + // not enough separators found, set i so that the slice expression + // below leaves file unmodified + i = -len(sep) + break + } + } + // get back to 0 or trim the leading separator + file = file[i+len(sep):] + return file +} diff --git a/vendor/src/github.com/pkg/errors/stack_test.go b/vendor/src/github.com/pkg/errors/stack_test.go new file mode 100755 index 000000000..510c27a9f --- /dev/null +++ b/vendor/src/github.com/pkg/errors/stack_test.go @@ -0,0 +1,292 @@ +package errors + +import ( + "fmt" + "runtime" + "testing" +) + +var initpc, _, _, _ = runtime.Caller(0) + +func TestFrameLine(t *testing.T) { + var tests = []struct { + Frame + want int + }{{ + Frame(initpc), + 9, + }, { + func() Frame { + var pc, _, _, _ = runtime.Caller(0) + return Frame(pc) + }(), + 20, + }, { + func() Frame { + var pc, _, _, _ = runtime.Caller(1) + return Frame(pc) + }(), + 28, + }, { + Frame(0), // invalid PC + 0, + }} + + for _, tt := range tests { + got := tt.Frame.line() + want := tt.want + if want != got { + t.Errorf("Frame(%v): want: %v, got: %v", uintptr(tt.Frame), want, got) + } + } +} + +type X struct{} + +func (x X) val() Frame { + var pc, _, _, _ = runtime.Caller(0) + return Frame(pc) +} + +func (x *X) ptr() Frame { + var pc, _, _, _ = runtime.Caller(0) + return Frame(pc) +} + +func TestFrameFormat(t *testing.T) { + var tests = []struct { + Frame + format string + want string + }{{ + Frame(initpc), + "%s", + "stack_test.go", + }, { + Frame(initpc), + "%+s", + "github.com/pkg/errors.init\n" + + "\t.+/github.com/pkg/errors/stack_test.go", + }, { + Frame(0), + "%s", + "unknown", + }, { + Frame(0), + "%+s", + "unknown", + }, { + Frame(initpc), + "%d", + "9", + }, { + Frame(0), + "%d", + "0", + }, { + Frame(initpc), + "%n", + "init", + }, { + func() Frame { + var x X + return x.ptr() + }(), + "%n", + `\(\*X\).ptr`, + }, { + func() Frame { + var x X + return x.val() + }(), + "%n", + "X.val", + }, { + Frame(0), + "%n", + "", + }, { + Frame(initpc), + "%v", + "stack_test.go:9", + }, { + Frame(initpc), + "%+v", + "github.com/pkg/errors.init\n" + + "\t.+/github.com/pkg/errors/stack_test.go:9", + }, { + Frame(0), + "%v", + "unknown:0", + }} + + for i, tt := range tests { + testFormatRegexp(t, i, tt.Frame, tt.format, tt.want) + } +} + +func TestFuncname(t *testing.T) { + tests := []struct { + name, want string + }{ + {"", ""}, + {"runtime.main", "main"}, + {"github.com/pkg/errors.funcname", "funcname"}, + {"funcname", "funcname"}, + {"io.copyBuffer", "copyBuffer"}, + {"main.(*R).Write", "(*R).Write"}, + } + + for _, tt := range tests { + got := funcname(tt.name) + want := tt.want + if got != want { + t.Errorf("funcname(%q): want: %q, got %q", tt.name, want, got) + } + } +} + +func TestTrimGOPATH(t *testing.T) { + var tests = []struct { + Frame + want string + }{{ + Frame(initpc), + "github.com/pkg/errors/stack_test.go", + }} + + for i, tt := range tests { + pc := tt.Frame.pc() + fn := runtime.FuncForPC(pc) + file, _ := fn.FileLine(pc) + got := trimGOPATH(fn.Name(), file) + testFormatRegexp(t, i, got, "%s", tt.want) + } +} + +func TestStackTrace(t *testing.T) { + tests := []struct { + err error + want []string + }{{ + New("ooh"), []string{ + "github.com/pkg/errors.TestStackTrace\n" + + "\t.+/github.com/pkg/errors/stack_test.go:172", + }, + }, { + Wrap(New("ooh"), "ahh"), []string{ + "github.com/pkg/errors.TestStackTrace\n" + + "\t.+/github.com/pkg/errors/stack_test.go:177", // this is the stack of Wrap, not New + }, + }, { + Cause(Wrap(New("ooh"), "ahh")), []string{ + "github.com/pkg/errors.TestStackTrace\n" + + "\t.+/github.com/pkg/errors/stack_test.go:182", // this is the stack of New + }, + }, { + func() error { return New("ooh") }(), []string{ + `github.com/pkg/errors.(func·009|TestStackTrace.func1)` + + "\n\t.+/github.com/pkg/errors/stack_test.go:187", // this is the stack of New + "github.com/pkg/errors.TestStackTrace\n" + + "\t.+/github.com/pkg/errors/stack_test.go:187", // this is the stack of New's caller + }, + }, { + Cause(func() error { + return func() error { + return Errorf("hello %s", fmt.Sprintf("world")) + }() + }()), []string{ + `github.com/pkg/errors.(func·010|TestStackTrace.func2.1)` + + "\n\t.+/github.com/pkg/errors/stack_test.go:196", // this is the stack of Errorf + `github.com/pkg/errors.(func·011|TestStackTrace.func2)` + + "\n\t.+/github.com/pkg/errors/stack_test.go:197", // this is the stack of Errorf's caller + "github.com/pkg/errors.TestStackTrace\n" + + "\t.+/github.com/pkg/errors/stack_test.go:198", // this is the stack of Errorf's caller's caller + }, + }} + for i, tt := range tests { + x, ok := tt.err.(interface { + StackTrace() StackTrace + }) + if !ok { + t.Errorf("expected %#v to implement StackTrace() StackTrace", tt.err) + continue + } + st := x.StackTrace() + for j, want := range tt.want { + testFormatRegexp(t, i, st[j], "%+v", want) + } + } +} + +func stackTrace() StackTrace { + const depth = 8 + var pcs [depth]uintptr + n := runtime.Callers(1, pcs[:]) + var st stack = pcs[0:n] + return st.StackTrace() +} + +func TestStackTraceFormat(t *testing.T) { + tests := []struct { + StackTrace + format string + want string + }{{ + nil, + "%s", + `\[\]`, + }, { + nil, + "%v", + `\[\]`, + }, { + nil, + "%+v", + "", + }, { + nil, + "%#v", + `\[\]errors.Frame\(nil\)`, + }, { + make(StackTrace, 0), + "%s", + `\[\]`, + }, { + make(StackTrace, 0), + "%v", + `\[\]`, + }, { + make(StackTrace, 0), + "%+v", + "", + }, { + make(StackTrace, 0), + "%#v", + `\[\]errors.Frame{}`, + }, { + stackTrace()[:2], + "%s", + `\[stack_test.go stack_test.go\]`, + }, { + stackTrace()[:2], + "%v", + `\[stack_test.go:225 stack_test.go:272\]`, + }, { + stackTrace()[:2], + "%+v", + "\n" + + "github.com/pkg/errors.stackTrace\n" + + "\t.+/github.com/pkg/errors/stack_test.go:225\n" + + "github.com/pkg/errors.TestStackTraceFormat\n" + + "\t.+/github.com/pkg/errors/stack_test.go:276", + }, { + stackTrace()[:2], + "%#v", + `\[\]errors.Frame{stack_test.go:225, stack_test.go:284}`, + }} + + for i, tt := range tests { + testFormatRegexp(t, i, tt.StackTrace, tt.format, tt.want) + } +}