First commit
This commit is contained in:
202
third_party/github.com/coreos/go-etcd/LICENSE
vendored
Normal file
202
third_party/github.com/coreos/go-etcd/LICENSE
vendored
Normal file
@@ -0,0 +1,202 @@
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
15
third_party/github.com/coreos/go-etcd/README.md
vendored
Normal file
15
third_party/github.com/coreos/go-etcd/README.md
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
# go-etcd
|
||||
|
||||
The official etcd v0.2 client library for Go.
|
||||
|
||||
For usage, please refer to [](https://godoc.org/github.com/coreos/go-etcd/etcd)
|
||||
|
||||
## Install
|
||||
|
||||
```bash
|
||||
go get github.com/coreos/go-etcd/etcd
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
See LICENSE file.
|
23
third_party/github.com/coreos/go-etcd/etcd/add_child.go
vendored
Normal file
23
third_party/github.com/coreos/go-etcd/etcd/add_child.go
vendored
Normal file
@@ -0,0 +1,23 @@
|
||||
package etcd
|
||||
|
||||
// Add a new directory with a random etcd-generated key under the given path.
|
||||
func (c *Client) AddChildDir(key string, ttl uint64) (*Response, error) {
|
||||
raw, err := c.post(key, "", ttl)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return raw.Unmarshal()
|
||||
}
|
||||
|
||||
// Add a new file with a random etcd-generated key under the given path.
|
||||
func (c *Client) AddChild(key string, value string, ttl uint64) (*Response, error) {
|
||||
raw, err := c.post(key, value, ttl)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return raw.Unmarshal()
|
||||
}
|
73
third_party/github.com/coreos/go-etcd/etcd/add_child_test.go
vendored
Normal file
73
third_party/github.com/coreos/go-etcd/etcd/add_child_test.go
vendored
Normal file
@@ -0,0 +1,73 @@
|
||||
package etcd
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestAddChild(t *testing.T) {
|
||||
c := NewClient(nil)
|
||||
defer func() {
|
||||
c.Delete("fooDir", true)
|
||||
c.Delete("nonexistentDir", true)
|
||||
}()
|
||||
|
||||
c.CreateDir("fooDir", 5)
|
||||
|
||||
_, err := c.AddChild("fooDir", "v0", 5)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
_, err = c.AddChild("fooDir", "v1", 5)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
resp, err := c.Get("fooDir", true, false)
|
||||
// The child with v0 should proceed the child with v1 because it's added
|
||||
// earlier, so it should have a lower key.
|
||||
if !(len(resp.Node.Nodes) == 2 && (resp.Node.Nodes[0].Value == "v0" && resp.Node.Nodes[1].Value == "v1")) {
|
||||
t.Fatalf("AddChild 1 failed. There should be two chlidren whose values are v0 and v1, respectively."+
|
||||
" The response was: %#v", resp)
|
||||
}
|
||||
|
||||
// Creating a child under a nonexistent directory should succeed.
|
||||
// The directory should be created.
|
||||
resp, err = c.AddChild("nonexistentDir", "foo", 5)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAddChildDir(t *testing.T) {
|
||||
c := NewClient(nil)
|
||||
defer func() {
|
||||
c.Delete("fooDir", true)
|
||||
c.Delete("nonexistentDir", true)
|
||||
}()
|
||||
|
||||
c.CreateDir("fooDir", 5)
|
||||
|
||||
_, err := c.AddChildDir("fooDir", 5)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
_, err = c.AddChildDir("fooDir", 5)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
resp, err := c.Get("fooDir", true, false)
|
||||
// The child with v0 should proceed the child with v1 because it's added
|
||||
// earlier, so it should have a lower key.
|
||||
if !(len(resp.Node.Nodes) == 2 && (len(resp.Node.Nodes[0].Nodes) == 0 && len(resp.Node.Nodes[1].Nodes) == 0)) {
|
||||
t.Fatalf("AddChildDir 1 failed. There should be two chlidren whose values are v0 and v1, respectively."+
|
||||
" The response was: %#v", resp)
|
||||
}
|
||||
|
||||
// Creating a child under a nonexistent directory should succeed.
|
||||
// The directory should be created.
|
||||
resp, err = c.AddChildDir("nonexistentDir", 5)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
430
third_party/github.com/coreos/go-etcd/etcd/client.go
vendored
Normal file
430
third_party/github.com/coreos/go-etcd/etcd/client.go
vendored
Normal file
@@ -0,0 +1,430 @@
|
||||
package etcd
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"path"
|
||||
"time"
|
||||
)
|
||||
|
||||
// See SetConsistency for how to use these constants.
|
||||
const (
|
||||
// Using strings rather than iota because the consistency level
|
||||
// could be persisted to disk, so it'd be better to use
|
||||
// human-readable values.
|
||||
STRONG_CONSISTENCY = "STRONG"
|
||||
WEAK_CONSISTENCY = "WEAK"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultBufferSize = 10
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
CertFile string `json:"certFile"`
|
||||
KeyFile string `json:"keyFile"`
|
||||
CaCertFile []string `json:"caCertFiles"`
|
||||
DialTimeout time.Duration `json:"timeout"`
|
||||
Consistency string `json:"consistency"`
|
||||
}
|
||||
|
||||
type Client struct {
|
||||
config Config `json:"config"`
|
||||
cluster *Cluster `json:"cluster"`
|
||||
httpClient *http.Client
|
||||
persistence io.Writer
|
||||
cURLch chan string
|
||||
// CheckRetry can be used to control the policy for failed requests
|
||||
// and modify the cluster if needed.
|
||||
// The client calls it before sending requests again, and
|
||||
// stops retrying if CheckRetry returns some error. The cases that
|
||||
// this function needs to handle include no response and unexpected
|
||||
// http status code of response.
|
||||
// If CheckRetry is nil, client will call the default one
|
||||
// `DefaultCheckRetry`.
|
||||
// Argument cluster is the etcd.Cluster object that these requests have been made on.
|
||||
// Argument reqs is all of the http.Requests that have been made so far.
|
||||
// Argument resps is all of the http.Responses from these requests.
|
||||
// Argument err is the reason of the failure.
|
||||
CheckRetry func(cluster *Cluster, reqs []http.Request,
|
||||
resps []http.Response, err error) error
|
||||
}
|
||||
|
||||
// NewClient create a basic client that is configured to be used
|
||||
// with the given machine list.
|
||||
func NewClient(machines []string) *Client {
|
||||
config := Config{
|
||||
// default timeout is one second
|
||||
DialTimeout: time.Second,
|
||||
// default consistency level is STRONG
|
||||
Consistency: STRONG_CONSISTENCY,
|
||||
}
|
||||
|
||||
client := &Client{
|
||||
cluster: NewCluster(machines),
|
||||
config: config,
|
||||
}
|
||||
|
||||
client.initHTTPClient()
|
||||
client.saveConfig()
|
||||
|
||||
return client
|
||||
}
|
||||
|
||||
// NewTLSClient create a basic client with TLS configuration
|
||||
func NewTLSClient(machines []string, cert, key, caCert string) (*Client, error) {
|
||||
// overwrite the default machine to use https
|
||||
if len(machines) == 0 {
|
||||
machines = []string{"https://127.0.0.1:4001"}
|
||||
}
|
||||
|
||||
config := Config{
|
||||
// default timeout is one second
|
||||
DialTimeout: time.Second,
|
||||
// default consistency level is STRONG
|
||||
Consistency: STRONG_CONSISTENCY,
|
||||
CertFile: cert,
|
||||
KeyFile: key,
|
||||
CaCertFile: make([]string, 0),
|
||||
}
|
||||
|
||||
client := &Client{
|
||||
cluster: NewCluster(machines),
|
||||
config: config,
|
||||
}
|
||||
|
||||
err := client.initHTTPSClient(cert, key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = client.AddRootCA(caCert)
|
||||
|
||||
client.saveConfig()
|
||||
|
||||
return client, nil
|
||||
}
|
||||
|
||||
// NewClientFromFile creates a client from a given file path.
|
||||
// The given file is expected to use the JSON format.
|
||||
func NewClientFromFile(fpath string) (*Client, error) {
|
||||
fi, err := os.Open(fpath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
defer func() {
|
||||
if err := fi.Close(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}()
|
||||
|
||||
return NewClientFromReader(fi)
|
||||
}
|
||||
|
||||
// NewClientFromReader creates a Client configured from a given reader.
|
||||
// The configuration is expected to use the JSON format.
|
||||
func NewClientFromReader(reader io.Reader) (*Client, error) {
|
||||
c := new(Client)
|
||||
|
||||
b, err := ioutil.ReadAll(reader)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = json.Unmarshal(b, c)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if c.config.CertFile == "" {
|
||||
c.initHTTPClient()
|
||||
} else {
|
||||
err = c.initHTTPSClient(c.config.CertFile, c.config.KeyFile)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, caCert := range c.config.CaCertFile {
|
||||
if err := c.AddRootCA(caCert); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return c, nil
|
||||
}
|
||||
|
||||
// Override the Client's HTTP Transport object
|
||||
func (c *Client) SetTransport(tr *http.Transport) {
|
||||
c.httpClient.Transport = tr
|
||||
}
|
||||
|
||||
// initHTTPClient initializes a HTTP client for etcd client
|
||||
func (c *Client) initHTTPClient() {
|
||||
tr := &http.Transport{
|
||||
Dial: c.dial,
|
||||
TLSClientConfig: &tls.Config{
|
||||
InsecureSkipVerify: true,
|
||||
},
|
||||
}
|
||||
c.httpClient = &http.Client{Transport: tr}
|
||||
}
|
||||
|
||||
// initHTTPClient initializes a HTTPS client for etcd client
|
||||
func (c *Client) initHTTPSClient(cert, key string) error {
|
||||
if cert == "" || key == "" {
|
||||
return errors.New("Require both cert and key path")
|
||||
}
|
||||
|
||||
tlsCert, err := tls.LoadX509KeyPair(cert, key)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
tlsConfig := &tls.Config{
|
||||
Certificates: []tls.Certificate{tlsCert},
|
||||
InsecureSkipVerify: true,
|
||||
}
|
||||
|
||||
tr := &http.Transport{
|
||||
TLSClientConfig: tlsConfig,
|
||||
Dial: c.dial,
|
||||
}
|
||||
|
||||
c.httpClient = &http.Client{Transport: tr}
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetPersistence sets a writer to which the config will be
|
||||
// written every time it's changed.
|
||||
func (c *Client) SetPersistence(writer io.Writer) {
|
||||
c.persistence = writer
|
||||
}
|
||||
|
||||
// SetConsistency changes the consistency level of the client.
|
||||
//
|
||||
// When consistency is set to STRONG_CONSISTENCY, all requests,
|
||||
// including GET, are sent to the leader. This means that, assuming
|
||||
// the absence of leader failures, GET requests are guaranteed to see
|
||||
// the changes made by previous requests.
|
||||
//
|
||||
// When consistency is set to WEAK_CONSISTENCY, other requests
|
||||
// are still sent to the leader, but GET requests are sent to a
|
||||
// random server from the server pool. This reduces the read
|
||||
// load on the leader, but it's not guaranteed that the GET requests
|
||||
// will see changes made by previous requests (they might have not
|
||||
// yet been committed on non-leader servers).
|
||||
func (c *Client) SetConsistency(consistency string) error {
|
||||
if !(consistency == STRONG_CONSISTENCY || consistency == WEAK_CONSISTENCY) {
|
||||
return errors.New("The argument must be either STRONG_CONSISTENCY or WEAK_CONSISTENCY.")
|
||||
}
|
||||
c.config.Consistency = consistency
|
||||
return nil
|
||||
}
|
||||
|
||||
// AddRootCA adds a root CA cert for the etcd client
|
||||
func (c *Client) AddRootCA(caCert string) error {
|
||||
if c.httpClient == nil {
|
||||
return errors.New("Client has not been initialized yet!")
|
||||
}
|
||||
|
||||
certBytes, err := ioutil.ReadFile(caCert)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
tr, ok := c.httpClient.Transport.(*http.Transport)
|
||||
|
||||
if !ok {
|
||||
panic("AddRootCA(): Transport type assert should not fail")
|
||||
}
|
||||
|
||||
if tr.TLSClientConfig.RootCAs == nil {
|
||||
caCertPool := x509.NewCertPool()
|
||||
ok = caCertPool.AppendCertsFromPEM(certBytes)
|
||||
if ok {
|
||||
tr.TLSClientConfig.RootCAs = caCertPool
|
||||
}
|
||||
tr.TLSClientConfig.InsecureSkipVerify = false
|
||||
} else {
|
||||
ok = tr.TLSClientConfig.RootCAs.AppendCertsFromPEM(certBytes)
|
||||
}
|
||||
|
||||
if !ok {
|
||||
err = errors.New("Unable to load caCert")
|
||||
}
|
||||
|
||||
c.config.CaCertFile = append(c.config.CaCertFile, caCert)
|
||||
c.saveConfig()
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// SetCluster updates cluster information using the given machine list.
|
||||
func (c *Client) SetCluster(machines []string) bool {
|
||||
success := c.internalSyncCluster(machines)
|
||||
return success
|
||||
}
|
||||
|
||||
func (c *Client) GetCluster() []string {
|
||||
return c.cluster.Machines
|
||||
}
|
||||
|
||||
// SyncCluster updates the cluster information using the internal machine list.
|
||||
func (c *Client) SyncCluster() bool {
|
||||
return c.internalSyncCluster(c.cluster.Machines)
|
||||
}
|
||||
|
||||
// internalSyncCluster syncs cluster information using the given machine list.
|
||||
func (c *Client) internalSyncCluster(machines []string) bool {
|
||||
for _, machine := range machines {
|
||||
httpPath := c.createHttpPath(machine, path.Join(version, "machines"))
|
||||
resp, err := c.httpClient.Get(httpPath)
|
||||
if err != nil {
|
||||
// try another machine in the cluster
|
||||
continue
|
||||
} else {
|
||||
b, err := ioutil.ReadAll(resp.Body)
|
||||
resp.Body.Close()
|
||||
if err != nil {
|
||||
// try another machine in the cluster
|
||||
continue
|
||||
}
|
||||
|
||||
// update Machines List
|
||||
c.cluster.updateFromStr(string(b))
|
||||
|
||||
// update leader
|
||||
// the first one in the machine list is the leader
|
||||
c.cluster.switchLeader(0)
|
||||
|
||||
logger.Debug("sync.machines ", c.cluster.Machines)
|
||||
c.saveConfig()
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// createHttpPath creates a complete HTTP URL.
|
||||
// serverName should contain both the host name and a port number, if any.
|
||||
func (c *Client) createHttpPath(serverName string, _path string) string {
|
||||
u, err := url.Parse(serverName)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
u.Path = path.Join(u.Path, _path)
|
||||
|
||||
if u.Scheme == "" {
|
||||
u.Scheme = "http"
|
||||
}
|
||||
return u.String()
|
||||
}
|
||||
|
||||
// dial attempts to open a TCP connection to the provided address, explicitly
|
||||
// enabling keep-alives with a one-second interval.
|
||||
func (c *Client) dial(network, addr string) (net.Conn, error) {
|
||||
conn, err := net.DialTimeout(network, addr, c.config.DialTimeout)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
tcpConn, ok := conn.(*net.TCPConn)
|
||||
if !ok {
|
||||
return nil, errors.New("Failed type-assertion of net.Conn as *net.TCPConn")
|
||||
}
|
||||
|
||||
// Keep TCP alive to check whether or not the remote machine is down
|
||||
if err = tcpConn.SetKeepAlive(true); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err = tcpConn.SetKeepAlivePeriod(time.Second); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return tcpConn, nil
|
||||
}
|
||||
|
||||
func (c *Client) OpenCURL() {
|
||||
c.cURLch = make(chan string, defaultBufferSize)
|
||||
}
|
||||
|
||||
func (c *Client) CloseCURL() {
|
||||
c.cURLch = nil
|
||||
}
|
||||
|
||||
func (c *Client) sendCURL(command string) {
|
||||
go func() {
|
||||
select {
|
||||
case c.cURLch <- command:
|
||||
default:
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
func (c *Client) RecvCURL() string {
|
||||
return <-c.cURLch
|
||||
}
|
||||
|
||||
// saveConfig saves the current config using c.persistence.
|
||||
func (c *Client) saveConfig() error {
|
||||
if c.persistence != nil {
|
||||
b, err := json.Marshal(c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = c.persistence.Write(b)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// MarshalJSON implements the Marshaller interface
|
||||
// as defined by the standard JSON package.
|
||||
func (c *Client) MarshalJSON() ([]byte, error) {
|
||||
b, err := json.Marshal(struct {
|
||||
Config Config `json:"config"`
|
||||
Cluster *Cluster `json:"cluster"`
|
||||
}{
|
||||
Config: c.config,
|
||||
Cluster: c.cluster,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return b, nil
|
||||
}
|
||||
|
||||
// UnmarshalJSON implements the Unmarshaller interface
|
||||
// as defined by the standard JSON package.
|
||||
func (c *Client) UnmarshalJSON(b []byte) error {
|
||||
temp := struct {
|
||||
Config Config `json:"config"`
|
||||
Cluster *Cluster `json:"cluster"`
|
||||
}{}
|
||||
err := json.Unmarshal(b, &temp)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
c.cluster = temp.Cluster
|
||||
c.config = temp.Config
|
||||
return nil
|
||||
}
|
96
third_party/github.com/coreos/go-etcd/etcd/client_test.go
vendored
Normal file
96
third_party/github.com/coreos/go-etcd/etcd/client_test.go
vendored
Normal file
@@ -0,0 +1,96 @@
|
||||
package etcd
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/url"
|
||||
"os"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// To pass this test, we need to create a cluster of 3 machines
|
||||
// The server should be listening on 127.0.0.1:4001, 4002, 4003
|
||||
func TestSync(t *testing.T) {
|
||||
fmt.Println("Make sure there are three nodes at 0.0.0.0:4001-4003")
|
||||
|
||||
// Explicit trailing slash to ensure this doesn't reproduce:
|
||||
// https://github.com/coreos/go-etcd/issues/82
|
||||
c := NewClient([]string{"http://127.0.0.1:4001/"})
|
||||
|
||||
success := c.SyncCluster()
|
||||
if !success {
|
||||
t.Fatal("cannot sync machines")
|
||||
}
|
||||
|
||||
for _, m := range c.GetCluster() {
|
||||
u, err := url.Parse(m)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if u.Scheme != "http" {
|
||||
t.Fatal("scheme must be http")
|
||||
}
|
||||
|
||||
host, _, err := net.SplitHostPort(u.Host)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if host != "127.0.0.1" {
|
||||
t.Fatal("Host must be 127.0.0.1")
|
||||
}
|
||||
}
|
||||
|
||||
badMachines := []string{"abc", "edef"}
|
||||
|
||||
success = c.SetCluster(badMachines)
|
||||
|
||||
if success {
|
||||
t.Fatal("should not sync on bad machines")
|
||||
}
|
||||
|
||||
goodMachines := []string{"127.0.0.1:4002"}
|
||||
|
||||
success = c.SetCluster(goodMachines)
|
||||
|
||||
if !success {
|
||||
t.Fatal("cannot sync machines")
|
||||
} else {
|
||||
fmt.Println(c.cluster.Machines)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestPersistence(t *testing.T) {
|
||||
c := NewClient(nil)
|
||||
c.SyncCluster()
|
||||
|
||||
fo, err := os.Create("config.json")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer func() {
|
||||
if err := fo.Close(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}()
|
||||
|
||||
c.SetPersistence(fo)
|
||||
err = c.saveConfig()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
c2, err := NewClientFromFile("config.json")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Verify that the two clients have the same config
|
||||
b1, _ := json.Marshal(c)
|
||||
b2, _ := json.Marshal(c2)
|
||||
|
||||
if string(b1) != string(b2) {
|
||||
t.Fatalf("The two configs should be equal!")
|
||||
}
|
||||
}
|
51
third_party/github.com/coreos/go-etcd/etcd/cluster.go
vendored
Normal file
51
third_party/github.com/coreos/go-etcd/etcd/cluster.go
vendored
Normal file
@@ -0,0 +1,51 @@
|
||||
package etcd
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type Cluster struct {
|
||||
Leader string `json:"leader"`
|
||||
Machines []string `json:"machines"`
|
||||
}
|
||||
|
||||
func NewCluster(machines []string) *Cluster {
|
||||
// if an empty slice was sent in then just assume HTTP 4001 on localhost
|
||||
if len(machines) == 0 {
|
||||
machines = []string{"http://127.0.0.1:4001"}
|
||||
}
|
||||
|
||||
// default leader and machines
|
||||
return &Cluster{
|
||||
Leader: machines[0],
|
||||
Machines: machines,
|
||||
}
|
||||
}
|
||||
|
||||
// switchLeader switch the current leader to machines[num]
|
||||
func (cl *Cluster) switchLeader(num int) {
|
||||
logger.Debugf("switch.leader[from %v to %v]",
|
||||
cl.Leader, cl.Machines[num])
|
||||
|
||||
cl.Leader = cl.Machines[num]
|
||||
}
|
||||
|
||||
func (cl *Cluster) updateFromStr(machines string) {
|
||||
cl.Machines = strings.Split(machines, ", ")
|
||||
}
|
||||
|
||||
func (cl *Cluster) updateLeader(leader string) {
|
||||
logger.Debugf("update.leader[%s,%s]", cl.Leader, leader)
|
||||
cl.Leader = leader
|
||||
}
|
||||
|
||||
func (cl *Cluster) updateLeaderFromURL(u *url.URL) {
|
||||
var leader string
|
||||
if u.Scheme == "" {
|
||||
leader = "http://" + u.Host
|
||||
} else {
|
||||
leader = u.Scheme + "://" + u.Host
|
||||
}
|
||||
cl.updateLeader(leader)
|
||||
}
|
34
third_party/github.com/coreos/go-etcd/etcd/compare_and_delete.go
vendored
Normal file
34
third_party/github.com/coreos/go-etcd/etcd/compare_and_delete.go
vendored
Normal file
@@ -0,0 +1,34 @@
|
||||
package etcd
|
||||
|
||||
import "fmt"
|
||||
|
||||
func (c *Client) CompareAndDelete(key string, prevValue string, prevIndex uint64) (*Response, error) {
|
||||
raw, err := c.RawCompareAndDelete(key, prevValue, prevIndex)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return raw.Unmarshal()
|
||||
}
|
||||
|
||||
func (c *Client) RawCompareAndDelete(key string, prevValue string, prevIndex uint64) (*RawResponse, error) {
|
||||
if prevValue == "" && prevIndex == 0 {
|
||||
return nil, fmt.Errorf("You must give either prevValue or prevIndex.")
|
||||
}
|
||||
|
||||
options := Options{}
|
||||
if prevValue != "" {
|
||||
options["prevValue"] = prevValue
|
||||
}
|
||||
if prevIndex != 0 {
|
||||
options["prevIndex"] = prevIndex
|
||||
}
|
||||
|
||||
raw, err := c.delete(key, options)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return raw, err
|
||||
}
|
46
third_party/github.com/coreos/go-etcd/etcd/compare_and_delete_test.go
vendored
Normal file
46
third_party/github.com/coreos/go-etcd/etcd/compare_and_delete_test.go
vendored
Normal file
@@ -0,0 +1,46 @@
|
||||
package etcd
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestCompareAndDelete(t *testing.T) {
|
||||
c := NewClient(nil)
|
||||
defer func() {
|
||||
c.Delete("foo", true)
|
||||
}()
|
||||
|
||||
c.Set("foo", "bar", 5)
|
||||
|
||||
// This should succeed an correct prevValue
|
||||
resp, err := c.CompareAndDelete("foo", "bar", 0)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !(resp.PrevNode.Value == "bar" && resp.PrevNode.Key == "/foo" && resp.PrevNode.TTL == 5) {
|
||||
t.Fatalf("CompareAndDelete 1 prevNode failed: %#v", resp)
|
||||
}
|
||||
|
||||
resp, _ = c.Set("foo", "bar", 5)
|
||||
// This should fail because it gives an incorrect prevValue
|
||||
_, err = c.CompareAndDelete("foo", "xxx", 0)
|
||||
if err == nil {
|
||||
t.Fatalf("CompareAndDelete 2 should have failed. The response is: %#v", resp)
|
||||
}
|
||||
|
||||
// This should succeed because it gives an correct prevIndex
|
||||
resp, err = c.CompareAndDelete("foo", "", resp.Node.ModifiedIndex)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !(resp.PrevNode.Value == "bar" && resp.PrevNode.Key == "/foo" && resp.PrevNode.TTL == 5) {
|
||||
t.Fatalf("CompareAndSwap 3 prevNode failed: %#v", resp)
|
||||
}
|
||||
|
||||
c.Set("foo", "bar", 5)
|
||||
// This should fail because it gives an incorrect prevIndex
|
||||
resp, err = c.CompareAndDelete("foo", "", 29817514)
|
||||
if err == nil {
|
||||
t.Fatalf("CompareAndDelete 4 should have failed. The response is: %#v", resp)
|
||||
}
|
||||
}
|
36
third_party/github.com/coreos/go-etcd/etcd/compare_and_swap.go
vendored
Normal file
36
third_party/github.com/coreos/go-etcd/etcd/compare_and_swap.go
vendored
Normal file
@@ -0,0 +1,36 @@
|
||||
package etcd
|
||||
|
||||
import "fmt"
|
||||
|
||||
func (c *Client) CompareAndSwap(key string, value string, ttl uint64,
|
||||
prevValue string, prevIndex uint64) (*Response, error) {
|
||||
raw, err := c.RawCompareAndSwap(key, value, ttl, prevValue, prevIndex)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return raw.Unmarshal()
|
||||
}
|
||||
|
||||
func (c *Client) RawCompareAndSwap(key string, value string, ttl uint64,
|
||||
prevValue string, prevIndex uint64) (*RawResponse, error) {
|
||||
if prevValue == "" && prevIndex == 0 {
|
||||
return nil, fmt.Errorf("You must give either prevValue or prevIndex.")
|
||||
}
|
||||
|
||||
options := Options{}
|
||||
if prevValue != "" {
|
||||
options["prevValue"] = prevValue
|
||||
}
|
||||
if prevIndex != 0 {
|
||||
options["prevIndex"] = prevIndex
|
||||
}
|
||||
|
||||
raw, err := c.put(key, value, ttl, options)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return raw, err
|
||||
}
|
57
third_party/github.com/coreos/go-etcd/etcd/compare_and_swap_test.go
vendored
Normal file
57
third_party/github.com/coreos/go-etcd/etcd/compare_and_swap_test.go
vendored
Normal file
@@ -0,0 +1,57 @@
|
||||
package etcd
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestCompareAndSwap(t *testing.T) {
|
||||
c := NewClient(nil)
|
||||
defer func() {
|
||||
c.Delete("foo", true)
|
||||
}()
|
||||
|
||||
c.Set("foo", "bar", 5)
|
||||
|
||||
// This should succeed
|
||||
resp, err := c.CompareAndSwap("foo", "bar2", 5, "bar", 0)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !(resp.Node.Value == "bar2" && resp.Node.Key == "/foo" && resp.Node.TTL == 5) {
|
||||
t.Fatalf("CompareAndSwap 1 failed: %#v", resp)
|
||||
}
|
||||
|
||||
if !(resp.PrevNode.Value == "bar" && resp.PrevNode.Key == "/foo" && resp.PrevNode.TTL == 5) {
|
||||
t.Fatalf("CompareAndSwap 1 prevNode failed: %#v", resp)
|
||||
}
|
||||
|
||||
// This should fail because it gives an incorrect prevValue
|
||||
resp, err = c.CompareAndSwap("foo", "bar3", 5, "xxx", 0)
|
||||
if err == nil {
|
||||
t.Fatalf("CompareAndSwap 2 should have failed. The response is: %#v", resp)
|
||||
}
|
||||
|
||||
resp, err = c.Set("foo", "bar", 5)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// This should succeed
|
||||
resp, err = c.CompareAndSwap("foo", "bar2", 5, "", resp.Node.ModifiedIndex)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !(resp.Node.Value == "bar2" && resp.Node.Key == "/foo" && resp.Node.TTL == 5) {
|
||||
t.Fatalf("CompareAndSwap 3 failed: %#v", resp)
|
||||
}
|
||||
|
||||
if !(resp.PrevNode.Value == "bar" && resp.PrevNode.Key == "/foo" && resp.PrevNode.TTL == 5) {
|
||||
t.Fatalf("CompareAndSwap 3 prevNode failed: %#v", resp)
|
||||
}
|
||||
|
||||
// This should fail because it gives an incorrect prevIndex
|
||||
resp, err = c.CompareAndSwap("foo", "bar3", 5, "", 29817514)
|
||||
if err == nil {
|
||||
t.Fatalf("CompareAndSwap 4 should have failed. The response is: %#v", resp)
|
||||
}
|
||||
}
|
54
third_party/github.com/coreos/go-etcd/etcd/debug.go
vendored
Normal file
54
third_party/github.com/coreos/go-etcd/etcd/debug.go
vendored
Normal file
@@ -0,0 +1,54 @@
|
||||
package etcd
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var logger *etcdLogger
|
||||
|
||||
func SetLogger(l *log.Logger) {
|
||||
logger = &etcdLogger{l}
|
||||
}
|
||||
|
||||
func GetLogger() *log.Logger {
|
||||
return logger.log
|
||||
}
|
||||
|
||||
type etcdLogger struct {
|
||||
log *log.Logger
|
||||
}
|
||||
|
||||
func (p *etcdLogger) Debug(args ...interface{}) {
|
||||
args[0] = "DEBUG: " + args[0].(string)
|
||||
p.log.Println(args)
|
||||
}
|
||||
|
||||
func (p *etcdLogger) Debugf(fmt string, args ...interface{}) {
|
||||
args[0] = "DEBUG: " + args[0].(string)
|
||||
// Append newline if necessary
|
||||
if !strings.HasSuffix(fmt, "\n") {
|
||||
fmt = fmt + "\n"
|
||||
}
|
||||
p.log.Printf(fmt, args)
|
||||
}
|
||||
|
||||
func (p *etcdLogger) Warning(args ...interface{}) {
|
||||
args[0] = "WARNING: " + args[0].(string)
|
||||
p.log.Println(args)
|
||||
}
|
||||
|
||||
func (p *etcdLogger) Warningf(fmt string, args ...interface{}) {
|
||||
// Append newline if necessary
|
||||
if !strings.HasSuffix(fmt, "\n") {
|
||||
fmt = fmt + "\n"
|
||||
}
|
||||
args[0] = "WARNING: " + args[0].(string)
|
||||
p.log.Printf(fmt, args)
|
||||
}
|
||||
|
||||
func init() {
|
||||
// Default logger uses the go default log.
|
||||
SetLogger(log.New(ioutil.Discard, "go-etcd", log.LstdFlags))
|
||||
}
|
40
third_party/github.com/coreos/go-etcd/etcd/delete.go
vendored
Normal file
40
third_party/github.com/coreos/go-etcd/etcd/delete.go
vendored
Normal file
@@ -0,0 +1,40 @@
|
||||
package etcd
|
||||
|
||||
// Delete deletes the given key.
|
||||
//
|
||||
// When recursive set to false, if the key points to a
|
||||
// directory the method will fail.
|
||||
//
|
||||
// When recursive set to true, if the key points to a file,
|
||||
// the file will be deleted; if the key points to a directory,
|
||||
// then everything under the directory (including all child directories)
|
||||
// will be deleted.
|
||||
func (c *Client) Delete(key string, recursive bool) (*Response, error) {
|
||||
raw, err := c.RawDelete(key, recursive, false)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return raw.Unmarshal()
|
||||
}
|
||||
|
||||
// DeleteDir deletes an empty directory or a key value pair
|
||||
func (c *Client) DeleteDir(key string) (*Response, error) {
|
||||
raw, err := c.RawDelete(key, false, true)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return raw.Unmarshal()
|
||||
}
|
||||
|
||||
func (c *Client) RawDelete(key string, recursive bool, dir bool) (*RawResponse, error) {
|
||||
ops := Options{
|
||||
"recursive": recursive,
|
||||
"dir": dir,
|
||||
}
|
||||
|
||||
return c.delete(key, ops)
|
||||
}
|
81
third_party/github.com/coreos/go-etcd/etcd/delete_test.go
vendored
Normal file
81
third_party/github.com/coreos/go-etcd/etcd/delete_test.go
vendored
Normal file
@@ -0,0 +1,81 @@
|
||||
package etcd
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestDelete(t *testing.T) {
|
||||
c := NewClient(nil)
|
||||
defer func() {
|
||||
c.Delete("foo", true)
|
||||
}()
|
||||
|
||||
c.Set("foo", "bar", 5)
|
||||
resp, err := c.Delete("foo", false)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if !(resp.Node.Value == "") {
|
||||
t.Fatalf("Delete failed with %s", resp.Node.Value)
|
||||
}
|
||||
|
||||
if !(resp.PrevNode.Value == "bar") {
|
||||
t.Fatalf("Delete PrevNode failed with %s", resp.Node.Value)
|
||||
}
|
||||
|
||||
resp, err = c.Delete("foo", false)
|
||||
if err == nil {
|
||||
t.Fatalf("Delete should have failed because the key foo did not exist. "+
|
||||
"The response was: %v", resp)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDeleteAll(t *testing.T) {
|
||||
c := NewClient(nil)
|
||||
defer func() {
|
||||
c.Delete("foo", true)
|
||||
c.Delete("fooDir", true)
|
||||
}()
|
||||
|
||||
c.SetDir("foo", 5)
|
||||
// test delete an empty dir
|
||||
resp, err := c.DeleteDir("foo")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if !(resp.Node.Value == "") {
|
||||
t.Fatalf("DeleteAll 1 failed: %#v", resp)
|
||||
}
|
||||
|
||||
if !(resp.PrevNode.Dir == true && resp.PrevNode.Value == "") {
|
||||
t.Fatalf("DeleteAll 1 PrevNode failed: %#v", resp)
|
||||
}
|
||||
|
||||
c.CreateDir("fooDir", 5)
|
||||
c.Set("fooDir/foo", "bar", 5)
|
||||
_, err = c.DeleteDir("fooDir")
|
||||
if err == nil {
|
||||
t.Fatal("should not able to delete a non-empty dir with deletedir")
|
||||
}
|
||||
|
||||
resp, err = c.Delete("fooDir", true)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if !(resp.Node.Value == "") {
|
||||
t.Fatalf("DeleteAll 2 failed: %#v", resp)
|
||||
}
|
||||
|
||||
if !(resp.PrevNode.Dir == true && resp.PrevNode.Value == "") {
|
||||
t.Fatalf("DeleteAll 2 PrevNode failed: %#v", resp)
|
||||
}
|
||||
|
||||
resp, err = c.Delete("foo", true)
|
||||
if err == nil {
|
||||
t.Fatalf("DeleteAll should have failed because the key foo did not exist. "+
|
||||
"The response was: %v", resp)
|
||||
}
|
||||
}
|
48
third_party/github.com/coreos/go-etcd/etcd/error.go
vendored
Normal file
48
third_party/github.com/coreos/go-etcd/etcd/error.go
vendored
Normal file
@@ -0,0 +1,48 @@
|
||||
package etcd
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
const (
|
||||
ErrCodeEtcdNotReachable = 501
|
||||
)
|
||||
|
||||
var (
|
||||
errorMap = map[int]string{
|
||||
ErrCodeEtcdNotReachable: "All the given peers are not reachable",
|
||||
}
|
||||
)
|
||||
|
||||
type EtcdError struct {
|
||||
ErrorCode int `json:"errorCode"`
|
||||
Message string `json:"message"`
|
||||
Cause string `json:"cause,omitempty"`
|
||||
Index uint64 `json:"index"`
|
||||
}
|
||||
|
||||
func (e EtcdError) Error() string {
|
||||
return fmt.Sprintf("%v: %v (%v) [%v]", e.ErrorCode, e.Message, e.Cause, e.Index)
|
||||
}
|
||||
|
||||
func newError(errorCode int, cause string, index uint64) *EtcdError {
|
||||
return &EtcdError{
|
||||
ErrorCode: errorCode,
|
||||
Message: errorMap[errorCode],
|
||||
Cause: cause,
|
||||
Index: index,
|
||||
}
|
||||
}
|
||||
|
||||
func handleError(b []byte) error {
|
||||
etcdErr := new(EtcdError)
|
||||
|
||||
err := json.Unmarshal(b, etcdErr)
|
||||
if err != nil {
|
||||
logger.Warningf("cannot unmarshal etcd error: %v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
return etcdErr
|
||||
}
|
27
third_party/github.com/coreos/go-etcd/etcd/get.go
vendored
Normal file
27
third_party/github.com/coreos/go-etcd/etcd/get.go
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
package etcd
|
||||
|
||||
// Get gets the file or directory associated with the given key.
|
||||
// If the key points to a directory, files and directories under
|
||||
// it will be returned in sorted or unsorted order, depending on
|
||||
// the sort flag.
|
||||
// If recursive is set to false, contents under child directories
|
||||
// will not be returned.
|
||||
// If recursive is set to true, all the contents will be returned.
|
||||
func (c *Client) Get(key string, sort, recursive bool) (*Response, error) {
|
||||
raw, err := c.RawGet(key, sort, recursive)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return raw.Unmarshal()
|
||||
}
|
||||
|
||||
func (c *Client) RawGet(key string, sort, recursive bool) (*RawResponse, error) {
|
||||
ops := Options{
|
||||
"recursive": recursive,
|
||||
"sorted": sort,
|
||||
}
|
||||
|
||||
return c.get(key, ops)
|
||||
}
|
131
third_party/github.com/coreos/go-etcd/etcd/get_test.go
vendored
Normal file
131
third_party/github.com/coreos/go-etcd/etcd/get_test.go
vendored
Normal file
@@ -0,0 +1,131 @@
|
||||
package etcd
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// cleanNode scrubs Expiration, ModifiedIndex and CreatedIndex of a node.
|
||||
func cleanNode(n *Node) {
|
||||
n.Expiration = nil
|
||||
n.ModifiedIndex = 0
|
||||
n.CreatedIndex = 0
|
||||
}
|
||||
|
||||
// cleanResult scrubs a result object two levels deep of Expiration,
|
||||
// ModifiedIndex and CreatedIndex.
|
||||
func cleanResult(result *Response) {
|
||||
// TODO(philips): make this recursive.
|
||||
cleanNode(result.Node)
|
||||
for i, _ := range result.Node.Nodes {
|
||||
cleanNode(result.Node.Nodes[i])
|
||||
for j, _ := range result.Node.Nodes[i].Nodes {
|
||||
cleanNode(result.Node.Nodes[i].Nodes[j])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestGet(t *testing.T) {
|
||||
c := NewClient(nil)
|
||||
defer func() {
|
||||
c.Delete("foo", true)
|
||||
}()
|
||||
|
||||
c.Set("foo", "bar", 5)
|
||||
|
||||
result, err := c.Get("foo", false, false)
|
||||
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if result.Node.Key != "/foo" || result.Node.Value != "bar" {
|
||||
t.Fatalf("Get failed with %s %s %v", result.Node.Key, result.Node.Value, result.Node.TTL)
|
||||
}
|
||||
|
||||
result, err = c.Get("goo", false, false)
|
||||
if err == nil {
|
||||
t.Fatalf("should not be able to get non-exist key")
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetAll(t *testing.T) {
|
||||
c := NewClient(nil)
|
||||
defer func() {
|
||||
c.Delete("fooDir", true)
|
||||
}()
|
||||
|
||||
c.CreateDir("fooDir", 5)
|
||||
c.Set("fooDir/k0", "v0", 5)
|
||||
c.Set("fooDir/k1", "v1", 5)
|
||||
|
||||
// Return kv-pairs in sorted order
|
||||
result, err := c.Get("fooDir", true, false)
|
||||
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
expected := Nodes{
|
||||
&Node{
|
||||
Key: "/fooDir/k0",
|
||||
Value: "v0",
|
||||
TTL: 5,
|
||||
},
|
||||
&Node{
|
||||
Key: "/fooDir/k1",
|
||||
Value: "v1",
|
||||
TTL: 5,
|
||||
},
|
||||
}
|
||||
|
||||
cleanResult(result)
|
||||
|
||||
if !reflect.DeepEqual(result.Node.Nodes, expected) {
|
||||
t.Fatalf("(actual) %v != (expected) %v", result.Node.Nodes, expected)
|
||||
}
|
||||
|
||||
// Test the `recursive` option
|
||||
c.CreateDir("fooDir/childDir", 5)
|
||||
c.Set("fooDir/childDir/k2", "v2", 5)
|
||||
|
||||
// Return kv-pairs in sorted order
|
||||
result, err = c.Get("fooDir", true, true)
|
||||
|
||||
cleanResult(result)
|
||||
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
expected = Nodes{
|
||||
&Node{
|
||||
Key: "/fooDir/childDir",
|
||||
Dir: true,
|
||||
Nodes: Nodes{
|
||||
&Node{
|
||||
Key: "/fooDir/childDir/k2",
|
||||
Value: "v2",
|
||||
TTL: 5,
|
||||
},
|
||||
},
|
||||
TTL: 5,
|
||||
},
|
||||
&Node{
|
||||
Key: "/fooDir/k0",
|
||||
Value: "v0",
|
||||
TTL: 5,
|
||||
},
|
||||
&Node{
|
||||
Key: "/fooDir/k1",
|
||||
Value: "v1",
|
||||
TTL: 5,
|
||||
},
|
||||
}
|
||||
|
||||
cleanResult(result)
|
||||
|
||||
if !reflect.DeepEqual(result.Node.Nodes, expected) {
|
||||
t.Fatalf("(actual) %v != (expected) %v", result.Node.Nodes, expected)
|
||||
}
|
||||
}
|
72
third_party/github.com/coreos/go-etcd/etcd/options.go
vendored
Normal file
72
third_party/github.com/coreos/go-etcd/etcd/options.go
vendored
Normal file
@@ -0,0 +1,72 @@
|
||||
package etcd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/url"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
type Options map[string]interface{}
|
||||
|
||||
// An internally-used data structure that represents a mapping
|
||||
// between valid options and their kinds
|
||||
type validOptions map[string]reflect.Kind
|
||||
|
||||
// Valid options for GET, PUT, POST, DELETE
|
||||
// Using CAPITALIZED_UNDERSCORE to emphasize that these
|
||||
// values are meant to be used as constants.
|
||||
var (
|
||||
VALID_GET_OPTIONS = validOptions{
|
||||
"recursive": reflect.Bool,
|
||||
"consistent": reflect.Bool,
|
||||
"sorted": reflect.Bool,
|
||||
"wait": reflect.Bool,
|
||||
"waitIndex": reflect.Uint64,
|
||||
}
|
||||
|
||||
VALID_PUT_OPTIONS = validOptions{
|
||||
"prevValue": reflect.String,
|
||||
"prevIndex": reflect.Uint64,
|
||||
"prevExist": reflect.Bool,
|
||||
"dir": reflect.Bool,
|
||||
}
|
||||
|
||||
VALID_POST_OPTIONS = validOptions{}
|
||||
|
||||
VALID_DELETE_OPTIONS = validOptions{
|
||||
"recursive": reflect.Bool,
|
||||
"dir": reflect.Bool,
|
||||
"prevValue": reflect.String,
|
||||
"prevIndex": reflect.Uint64,
|
||||
}
|
||||
)
|
||||
|
||||
// Convert options to a string of HTML parameters
|
||||
func (ops Options) toParameters(validOps validOptions) (string, error) {
|
||||
p := "?"
|
||||
values := url.Values{}
|
||||
|
||||
if ops == nil {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
for k, v := range ops {
|
||||
// Check if the given option is valid (that it exists)
|
||||
kind := validOps[k]
|
||||
if kind == reflect.Invalid {
|
||||
return "", fmt.Errorf("Invalid option: %v", k)
|
||||
}
|
||||
|
||||
// Check if the given option is of the valid type
|
||||
t := reflect.TypeOf(v)
|
||||
if kind != t.Kind() {
|
||||
return "", fmt.Errorf("Option %s should be of %v kind, not of %v kind.",
|
||||
k, kind, t.Kind())
|
||||
}
|
||||
|
||||
values.Set(k, fmt.Sprintf("%v", v))
|
||||
}
|
||||
|
||||
p += values.Encode()
|
||||
return p, nil
|
||||
}
|
365
third_party/github.com/coreos/go-etcd/etcd/requests.go
vendored
Normal file
365
third_party/github.com/coreos/go-etcd/etcd/requests.go
vendored
Normal file
@@ -0,0 +1,365 @@
|
||||
package etcd
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"math/rand"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"path"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Errors introduced by handling requests
|
||||
var (
|
||||
ErrRequestCancelled = errors.New("sending request is cancelled")
|
||||
)
|
||||
|
||||
type RawRequest struct {
|
||||
Method string
|
||||
RelativePath string
|
||||
Values url.Values
|
||||
Cancel <-chan bool
|
||||
}
|
||||
|
||||
// NewRawRequest returns a new RawRequest
|
||||
func NewRawRequest(method, relativePath string, values url.Values, cancel <-chan bool) *RawRequest {
|
||||
return &RawRequest{
|
||||
Method: method,
|
||||
RelativePath: relativePath,
|
||||
Values: values,
|
||||
Cancel: cancel,
|
||||
}
|
||||
}
|
||||
|
||||
// getCancelable issues a cancelable GET request
|
||||
func (c *Client) getCancelable(key string, options Options,
|
||||
cancel <-chan bool) (*RawResponse, error) {
|
||||
logger.Debugf("get %s [%s]", key, c.cluster.Leader)
|
||||
p := keyToPath(key)
|
||||
|
||||
// If consistency level is set to STRONG, append
|
||||
// the `consistent` query string.
|
||||
if c.config.Consistency == STRONG_CONSISTENCY {
|
||||
options["consistent"] = true
|
||||
}
|
||||
|
||||
str, err := options.toParameters(VALID_GET_OPTIONS)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
p += str
|
||||
|
||||
req := NewRawRequest("GET", p, nil, cancel)
|
||||
resp, err := c.SendRequest(req)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// get issues a GET request
|
||||
func (c *Client) get(key string, options Options) (*RawResponse, error) {
|
||||
return c.getCancelable(key, options, nil)
|
||||
}
|
||||
|
||||
// put issues a PUT request
|
||||
func (c *Client) put(key string, value string, ttl uint64,
|
||||
options Options) (*RawResponse, error) {
|
||||
|
||||
logger.Debugf("put %s, %s, ttl: %d, [%s]", key, value, ttl, c.cluster.Leader)
|
||||
p := keyToPath(key)
|
||||
|
||||
str, err := options.toParameters(VALID_PUT_OPTIONS)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
p += str
|
||||
|
||||
req := NewRawRequest("PUT", p, buildValues(value, ttl), nil)
|
||||
resp, err := c.SendRequest(req)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// post issues a POST request
|
||||
func (c *Client) post(key string, value string, ttl uint64) (*RawResponse, error) {
|
||||
logger.Debugf("post %s, %s, ttl: %d, [%s]", key, value, ttl, c.cluster.Leader)
|
||||
p := keyToPath(key)
|
||||
|
||||
req := NewRawRequest("POST", p, buildValues(value, ttl), nil)
|
||||
resp, err := c.SendRequest(req)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// delete issues a DELETE request
|
||||
func (c *Client) delete(key string, options Options) (*RawResponse, error) {
|
||||
logger.Debugf("delete %s [%s]", key, c.cluster.Leader)
|
||||
p := keyToPath(key)
|
||||
|
||||
str, err := options.toParameters(VALID_DELETE_OPTIONS)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
p += str
|
||||
|
||||
req := NewRawRequest("DELETE", p, nil, nil)
|
||||
resp, err := c.SendRequest(req)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// SendRequest sends a HTTP request and returns a Response as defined by etcd
|
||||
func (c *Client) SendRequest(rr *RawRequest) (*RawResponse, error) {
|
||||
|
||||
var req *http.Request
|
||||
var resp *http.Response
|
||||
var httpPath string
|
||||
var err error
|
||||
var respBody []byte
|
||||
|
||||
reqs := make([]http.Request, 0)
|
||||
resps := make([]http.Response, 0)
|
||||
|
||||
checkRetry := c.CheckRetry
|
||||
if checkRetry == nil {
|
||||
checkRetry = DefaultCheckRetry
|
||||
}
|
||||
|
||||
cancelled := make(chan bool, 1)
|
||||
reqLock := new(sync.Mutex)
|
||||
|
||||
if rr.Cancel != nil {
|
||||
cancelRoutine := make(chan bool)
|
||||
defer close(cancelRoutine)
|
||||
|
||||
go func() {
|
||||
select {
|
||||
case <-rr.Cancel:
|
||||
cancelled <- true
|
||||
logger.Debug("send.request is cancelled")
|
||||
case <-cancelRoutine:
|
||||
return
|
||||
}
|
||||
|
||||
// Repeat canceling request until this thread is stopped
|
||||
// because we have no idea about whether it succeeds.
|
||||
for {
|
||||
reqLock.Lock()
|
||||
c.httpClient.Transport.(*http.Transport).CancelRequest(req)
|
||||
reqLock.Unlock()
|
||||
|
||||
select {
|
||||
case <-time.After(100 * time.Millisecond):
|
||||
case <-cancelRoutine:
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
// if we connect to a follower, we will retry until we find a leader
|
||||
for attempt := 0; ; attempt++ {
|
||||
select {
|
||||
case <-cancelled:
|
||||
return nil, ErrRequestCancelled
|
||||
default:
|
||||
}
|
||||
|
||||
logger.Debug("begin attempt", attempt, "for", rr.RelativePath)
|
||||
|
||||
if rr.Method == "GET" && c.config.Consistency == WEAK_CONSISTENCY {
|
||||
// If it's a GET and consistency level is set to WEAK,
|
||||
// then use a random machine.
|
||||
httpPath = c.getHttpPath(true, rr.RelativePath)
|
||||
} else {
|
||||
// Else use the leader.
|
||||
httpPath = c.getHttpPath(false, rr.RelativePath)
|
||||
}
|
||||
|
||||
// Return a cURL command if curlChan is set
|
||||
if c.cURLch != nil {
|
||||
command := fmt.Sprintf("curl -X %s %s", rr.Method, httpPath)
|
||||
for key, value := range rr.Values {
|
||||
command += fmt.Sprintf(" -d %s=%s", key, value[0])
|
||||
}
|
||||
c.sendCURL(command)
|
||||
}
|
||||
|
||||
logger.Debug("send.request.to ", httpPath, " | method ", rr.Method)
|
||||
|
||||
reqLock.Lock()
|
||||
if rr.Values == nil {
|
||||
if req, err = http.NewRequest(rr.Method, httpPath, nil); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
body := strings.NewReader(rr.Values.Encode())
|
||||
if req, err = http.NewRequest(rr.Method, httpPath, body); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
req.Header.Set("Content-Type",
|
||||
"application/x-www-form-urlencoded; param=value")
|
||||
}
|
||||
reqLock.Unlock()
|
||||
|
||||
resp, err = c.httpClient.Do(req)
|
||||
// If the request was cancelled, return ErrRequestCancelled directly
|
||||
select {
|
||||
case <-cancelled:
|
||||
return nil, ErrRequestCancelled
|
||||
default:
|
||||
}
|
||||
|
||||
reqs = append(reqs, *req)
|
||||
|
||||
// network error, change a machine!
|
||||
if err != nil {
|
||||
logger.Debug("network error:", err.Error())
|
||||
resps = append(resps, http.Response{})
|
||||
if checkErr := checkRetry(c.cluster, reqs, resps, err); checkErr != nil {
|
||||
return nil, checkErr
|
||||
}
|
||||
|
||||
c.cluster.switchLeader(attempt % len(c.cluster.Machines))
|
||||
continue
|
||||
}
|
||||
|
||||
// if there is no error, it should receive response
|
||||
resps = append(resps, *resp)
|
||||
defer resp.Body.Close()
|
||||
logger.Debug("recv.response.from", httpPath)
|
||||
|
||||
if validHttpStatusCode[resp.StatusCode] {
|
||||
// try to read byte code and break the loop
|
||||
respBody, err = ioutil.ReadAll(resp.Body)
|
||||
if err == nil {
|
||||
logger.Debug("recv.success.", httpPath)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// if resp is TemporaryRedirect, set the new leader and retry
|
||||
if resp.StatusCode == http.StatusTemporaryRedirect {
|
||||
u, err := resp.Location()
|
||||
|
||||
if err != nil {
|
||||
logger.Warning(err)
|
||||
} else {
|
||||
// Update cluster leader based on redirect location
|
||||
// because it should point to the leader address
|
||||
c.cluster.updateLeaderFromURL(u)
|
||||
logger.Debug("recv.response.relocate", u.String())
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
if checkErr := checkRetry(c.cluster, reqs, resps,
|
||||
errors.New("Unexpected HTTP status code")); checkErr != nil {
|
||||
return nil, checkErr
|
||||
}
|
||||
}
|
||||
|
||||
r := &RawResponse{
|
||||
StatusCode: resp.StatusCode,
|
||||
Body: respBody,
|
||||
Header: resp.Header,
|
||||
}
|
||||
|
||||
return r, nil
|
||||
}
|
||||
|
||||
// DefaultCheckRetry checks retry cases
|
||||
// If it has retried 2 * machine number, stop to retry it anymore
|
||||
// If resp is nil, sleep for 200ms
|
||||
// If status code is InternalServerError, sleep for 200ms.
|
||||
func DefaultCheckRetry(cluster *Cluster, reqs []http.Request,
|
||||
resps []http.Response, err error) error {
|
||||
|
||||
if len(reqs) >= 2*len(cluster.Machines) {
|
||||
return newError(ErrCodeEtcdNotReachable,
|
||||
"Tried to connect to each peer twice and failed", 0)
|
||||
}
|
||||
|
||||
resp := &resps[len(resps)-1]
|
||||
|
||||
if resp == nil {
|
||||
time.Sleep(time.Millisecond * 200)
|
||||
return nil
|
||||
}
|
||||
|
||||
code := resp.StatusCode
|
||||
if code == http.StatusInternalServerError {
|
||||
time.Sleep(time.Millisecond * 200)
|
||||
|
||||
}
|
||||
|
||||
logger.Warning("bad response status code", code)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Client) getHttpPath(random bool, s ...string) string {
|
||||
var machine string
|
||||
if random {
|
||||
machine = c.cluster.Machines[rand.Intn(len(c.cluster.Machines))]
|
||||
} else {
|
||||
machine = c.cluster.Leader
|
||||
}
|
||||
|
||||
fullPath := machine + "/" + version
|
||||
for _, seg := range s {
|
||||
fullPath = fullPath + "/" + seg
|
||||
}
|
||||
|
||||
return fullPath
|
||||
}
|
||||
|
||||
// buildValues builds a url.Values map according to the given value and ttl
|
||||
func buildValues(value string, ttl uint64) url.Values {
|
||||
v := url.Values{}
|
||||
|
||||
if value != "" {
|
||||
v.Set("value", value)
|
||||
}
|
||||
|
||||
if ttl > 0 {
|
||||
v.Set("ttl", fmt.Sprintf("%v", ttl))
|
||||
}
|
||||
|
||||
return v
|
||||
}
|
||||
|
||||
// convert key string to http path exclude version
|
||||
// for example: key[foo] -> path[keys/foo]
|
||||
// key[/] -> path[keys/]
|
||||
func keyToPath(key string) string {
|
||||
p := path.Join("keys", key)
|
||||
|
||||
// corner case: if key is "/" or "//" ect
|
||||
// path join will clear the tailing "/"
|
||||
// we need to add it back
|
||||
if p == "keys" {
|
||||
p = "keys/"
|
||||
}
|
||||
|
||||
return p
|
||||
}
|
89
third_party/github.com/coreos/go-etcd/etcd/response.go
vendored
Normal file
89
third_party/github.com/coreos/go-etcd/etcd/response.go
vendored
Normal file
@@ -0,0 +1,89 @@
|
||||
package etcd
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
rawResponse = iota
|
||||
normalResponse
|
||||
)
|
||||
|
||||
type responseType int
|
||||
|
||||
type RawResponse struct {
|
||||
StatusCode int
|
||||
Body []byte
|
||||
Header http.Header
|
||||
}
|
||||
|
||||
var (
|
||||
validHttpStatusCode = map[int]bool{
|
||||
http.StatusCreated: true,
|
||||
http.StatusOK: true,
|
||||
http.StatusBadRequest: true,
|
||||
http.StatusNotFound: true,
|
||||
http.StatusPreconditionFailed: true,
|
||||
http.StatusForbidden: true,
|
||||
}
|
||||
)
|
||||
|
||||
// Unmarshal parses RawResponse and stores the result in Response
|
||||
func (rr *RawResponse) Unmarshal() (*Response, error) {
|
||||
if rr.StatusCode != http.StatusOK && rr.StatusCode != http.StatusCreated {
|
||||
return nil, handleError(rr.Body)
|
||||
}
|
||||
|
||||
resp := new(Response)
|
||||
|
||||
err := json.Unmarshal(rr.Body, resp)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// attach index and term to response
|
||||
resp.EtcdIndex, _ = strconv.ParseUint(rr.Header.Get("X-Etcd-Index"), 10, 64)
|
||||
resp.RaftIndex, _ = strconv.ParseUint(rr.Header.Get("X-Raft-Index"), 10, 64)
|
||||
resp.RaftTerm, _ = strconv.ParseUint(rr.Header.Get("X-Raft-Term"), 10, 64)
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
type Response struct {
|
||||
Action string `json:"action"`
|
||||
Node *Node `json:"node"`
|
||||
PrevNode *Node `json:"prevNode,omitempty"`
|
||||
EtcdIndex uint64 `json:"etcdIndex"`
|
||||
RaftIndex uint64 `json:"raftIndex"`
|
||||
RaftTerm uint64 `json:"raftTerm"`
|
||||
}
|
||||
|
||||
type Node struct {
|
||||
Key string `json:"key, omitempty"`
|
||||
Value string `json:"value,omitempty"`
|
||||
Dir bool `json:"dir,omitempty"`
|
||||
Expiration *time.Time `json:"expiration,omitempty"`
|
||||
TTL int64 `json:"ttl,omitempty"`
|
||||
Nodes Nodes `json:"nodes,omitempty"`
|
||||
ModifiedIndex uint64 `json:"modifiedIndex,omitempty"`
|
||||
CreatedIndex uint64 `json:"createdIndex,omitempty"`
|
||||
}
|
||||
|
||||
type Nodes []*Node
|
||||
|
||||
// interfaces for sorting
|
||||
func (ns Nodes) Len() int {
|
||||
return len(ns)
|
||||
}
|
||||
|
||||
func (ns Nodes) Less(i, j int) bool {
|
||||
return ns[i].Key < ns[j].Key
|
||||
}
|
||||
|
||||
func (ns Nodes) Swap(i, j int) {
|
||||
ns[i], ns[j] = ns[j], ns[i]
|
||||
}
|
42
third_party/github.com/coreos/go-etcd/etcd/set_curl_chan_test.go
vendored
Normal file
42
third_party/github.com/coreos/go-etcd/etcd/set_curl_chan_test.go
vendored
Normal file
@@ -0,0 +1,42 @@
|
||||
package etcd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestSetCurlChan(t *testing.T) {
|
||||
c := NewClient(nil)
|
||||
c.OpenCURL()
|
||||
|
||||
defer func() {
|
||||
c.Delete("foo", true)
|
||||
}()
|
||||
|
||||
_, err := c.Set("foo", "bar", 5)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
expected := fmt.Sprintf("curl -X PUT %s/v2/keys/foo -d value=bar -d ttl=5",
|
||||
c.cluster.Leader)
|
||||
actual := c.RecvCURL()
|
||||
if expected != actual {
|
||||
t.Fatalf(`Command "%s" is not equal to expected value "%s"`,
|
||||
actual, expected)
|
||||
}
|
||||
|
||||
c.SetConsistency(STRONG_CONSISTENCY)
|
||||
_, err = c.Get("foo", false, false)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
expected = fmt.Sprintf("curl -X GET %s/v2/keys/foo?consistent=true&recursive=false&sorted=false",
|
||||
c.cluster.Leader)
|
||||
actual = c.RecvCURL()
|
||||
if expected != actual {
|
||||
t.Fatalf(`Command "%s" is not equal to expected value "%s"`,
|
||||
actual, expected)
|
||||
}
|
||||
}
|
137
third_party/github.com/coreos/go-etcd/etcd/set_update_create.go
vendored
Normal file
137
third_party/github.com/coreos/go-etcd/etcd/set_update_create.go
vendored
Normal file
@@ -0,0 +1,137 @@
|
||||
package etcd
|
||||
|
||||
// Set sets the given key to the given value.
|
||||
// It will create a new key value pair or replace the old one.
|
||||
// It will not replace a existing directory.
|
||||
func (c *Client) Set(key string, value string, ttl uint64) (*Response, error) {
|
||||
raw, err := c.RawSet(key, value, ttl)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return raw.Unmarshal()
|
||||
}
|
||||
|
||||
// Set sets the given key to a directory.
|
||||
// It will create a new directory or replace the old key value pair by a directory.
|
||||
// It will not replace a existing directory.
|
||||
func (c *Client) SetDir(key string, ttl uint64) (*Response, error) {
|
||||
raw, err := c.RawSetDir(key, ttl)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return raw.Unmarshal()
|
||||
}
|
||||
|
||||
// CreateDir creates a directory. It succeeds only if
|
||||
// the given key does not yet exist.
|
||||
func (c *Client) CreateDir(key string, ttl uint64) (*Response, error) {
|
||||
raw, err := c.RawCreateDir(key, ttl)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return raw.Unmarshal()
|
||||
}
|
||||
|
||||
// UpdateDir updates the given directory. It succeeds only if the
|
||||
// given key already exists.
|
||||
func (c *Client) UpdateDir(key string, ttl uint64) (*Response, error) {
|
||||
raw, err := c.RawUpdateDir(key, ttl)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return raw.Unmarshal()
|
||||
}
|
||||
|
||||
// Create creates a file with the given value under the given key. It succeeds
|
||||
// only if the given key does not yet exist.
|
||||
func (c *Client) Create(key string, value string, ttl uint64) (*Response, error) {
|
||||
raw, err := c.RawCreate(key, value, ttl)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return raw.Unmarshal()
|
||||
}
|
||||
|
||||
// CreateInOrder creates a file with a key that's guaranteed to be higher than other
|
||||
// keys in the given directory. It is useful for creating queues.
|
||||
func (c *Client) CreateInOrder(dir string, value string, ttl uint64) (*Response, error) {
|
||||
raw, err := c.RawCreateInOrder(dir, value, ttl)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return raw.Unmarshal()
|
||||
}
|
||||
|
||||
// Update updates the given key to the given value. It succeeds only if the
|
||||
// given key already exists.
|
||||
func (c *Client) Update(key string, value string, ttl uint64) (*Response, error) {
|
||||
raw, err := c.RawUpdate(key, value, ttl)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return raw.Unmarshal()
|
||||
}
|
||||
|
||||
func (c *Client) RawUpdateDir(key string, ttl uint64) (*RawResponse, error) {
|
||||
ops := Options{
|
||||
"prevExist": true,
|
||||
"dir": true,
|
||||
}
|
||||
|
||||
return c.put(key, "", ttl, ops)
|
||||
}
|
||||
|
||||
func (c *Client) RawCreateDir(key string, ttl uint64) (*RawResponse, error) {
|
||||
ops := Options{
|
||||
"prevExist": false,
|
||||
"dir": true,
|
||||
}
|
||||
|
||||
return c.put(key, "", ttl, ops)
|
||||
}
|
||||
|
||||
func (c *Client) RawSet(key string, value string, ttl uint64) (*RawResponse, error) {
|
||||
return c.put(key, value, ttl, nil)
|
||||
}
|
||||
|
||||
func (c *Client) RawSetDir(key string, ttl uint64) (*RawResponse, error) {
|
||||
ops := Options{
|
||||
"dir": true,
|
||||
}
|
||||
|
||||
return c.put(key, "", ttl, ops)
|
||||
}
|
||||
|
||||
func (c *Client) RawUpdate(key string, value string, ttl uint64) (*RawResponse, error) {
|
||||
ops := Options{
|
||||
"prevExist": true,
|
||||
}
|
||||
|
||||
return c.put(key, value, ttl, ops)
|
||||
}
|
||||
|
||||
func (c *Client) RawCreate(key string, value string, ttl uint64) (*RawResponse, error) {
|
||||
ops := Options{
|
||||
"prevExist": false,
|
||||
}
|
||||
|
||||
return c.put(key, value, ttl, ops)
|
||||
}
|
||||
|
||||
func (c *Client) RawCreateInOrder(dir string, value string, ttl uint64) (*RawResponse, error) {
|
||||
return c.post(dir, value, ttl)
|
||||
}
|
241
third_party/github.com/coreos/go-etcd/etcd/set_update_create_test.go
vendored
Normal file
241
third_party/github.com/coreos/go-etcd/etcd/set_update_create_test.go
vendored
Normal file
@@ -0,0 +1,241 @@
|
||||
package etcd
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestSet(t *testing.T) {
|
||||
c := NewClient(nil)
|
||||
defer func() {
|
||||
c.Delete("foo", true)
|
||||
}()
|
||||
|
||||
resp, err := c.Set("foo", "bar", 5)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if resp.Node.Key != "/foo" || resp.Node.Value != "bar" || resp.Node.TTL != 5 {
|
||||
t.Fatalf("Set 1 failed: %#v", resp)
|
||||
}
|
||||
if resp.PrevNode != nil {
|
||||
t.Fatalf("Set 1 PrevNode failed: %#v", resp)
|
||||
}
|
||||
|
||||
resp, err = c.Set("foo", "bar2", 5)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !(resp.Node.Key == "/foo" && resp.Node.Value == "bar2" && resp.Node.TTL == 5) {
|
||||
t.Fatalf("Set 2 failed: %#v", resp)
|
||||
}
|
||||
if resp.PrevNode.Key != "/foo" || resp.PrevNode.Value != "bar" || resp.Node.TTL != 5 {
|
||||
t.Fatalf("Set 2 PrevNode failed: %#v", resp)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUpdate(t *testing.T) {
|
||||
c := NewClient(nil)
|
||||
defer func() {
|
||||
c.Delete("foo", true)
|
||||
c.Delete("nonexistent", true)
|
||||
}()
|
||||
|
||||
resp, err := c.Set("foo", "bar", 5)
|
||||
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// This should succeed.
|
||||
resp, err = c.Update("foo", "wakawaka", 5)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if !(resp.Action == "update" && resp.Node.Key == "/foo" && resp.Node.TTL == 5) {
|
||||
t.Fatalf("Update 1 failed: %#v", resp)
|
||||
}
|
||||
if !(resp.PrevNode.Key == "/foo" && resp.PrevNode.Value == "bar" && resp.Node.TTL == 5) {
|
||||
t.Fatalf("Update 1 prevValue failed: %#v", resp)
|
||||
}
|
||||
|
||||
// This should fail because the key does not exist.
|
||||
resp, err = c.Update("nonexistent", "whatever", 5)
|
||||
if err == nil {
|
||||
t.Fatalf("The key %v did not exist, so the update should have failed."+
|
||||
"The response was: %#v", resp.Node.Key, resp)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreate(t *testing.T) {
|
||||
c := NewClient(nil)
|
||||
defer func() {
|
||||
c.Delete("newKey", true)
|
||||
}()
|
||||
|
||||
newKey := "/newKey"
|
||||
newValue := "/newValue"
|
||||
|
||||
// This should succeed
|
||||
resp, err := c.Create(newKey, newValue, 5)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if !(resp.Action == "create" && resp.Node.Key == newKey &&
|
||||
resp.Node.Value == newValue && resp.Node.TTL == 5) {
|
||||
t.Fatalf("Create 1 failed: %#v", resp)
|
||||
}
|
||||
if resp.PrevNode != nil {
|
||||
t.Fatalf("Create 1 PrevNode failed: %#v", resp)
|
||||
}
|
||||
|
||||
// This should fail, because the key is already there
|
||||
resp, err = c.Create(newKey, newValue, 5)
|
||||
if err == nil {
|
||||
t.Fatalf("The key %v did exist, so the creation should have failed."+
|
||||
"The response was: %#v", resp.Node.Key, resp)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreateInOrder(t *testing.T) {
|
||||
c := NewClient(nil)
|
||||
dir := "/queue"
|
||||
defer func() {
|
||||
c.DeleteDir(dir)
|
||||
}()
|
||||
|
||||
var firstKey, secondKey string
|
||||
|
||||
resp, err := c.CreateInOrder(dir, "1", 5)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if !(resp.Action == "create" && resp.Node.Value == "1" && resp.Node.TTL == 5) {
|
||||
t.Fatalf("Create 1 failed: %#v", resp)
|
||||
}
|
||||
|
||||
firstKey = resp.Node.Key
|
||||
|
||||
resp, err = c.CreateInOrder(dir, "2", 5)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if !(resp.Action == "create" && resp.Node.Value == "2" && resp.Node.TTL == 5) {
|
||||
t.Fatalf("Create 2 failed: %#v", resp)
|
||||
}
|
||||
|
||||
secondKey = resp.Node.Key
|
||||
|
||||
if firstKey >= secondKey {
|
||||
t.Fatalf("Expected first key to be greater than second key, but %s is not greater than %s",
|
||||
firstKey, secondKey)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSetDir(t *testing.T) {
|
||||
c := NewClient(nil)
|
||||
defer func() {
|
||||
c.Delete("foo", true)
|
||||
c.Delete("fooDir", true)
|
||||
}()
|
||||
|
||||
resp, err := c.CreateDir("fooDir", 5)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !(resp.Node.Key == "/fooDir" && resp.Node.Value == "" && resp.Node.TTL == 5) {
|
||||
t.Fatalf("SetDir 1 failed: %#v", resp)
|
||||
}
|
||||
if resp.PrevNode != nil {
|
||||
t.Fatalf("SetDir 1 PrevNode failed: %#v", resp)
|
||||
}
|
||||
|
||||
// This should fail because /fooDir already points to a directory
|
||||
resp, err = c.CreateDir("/fooDir", 5)
|
||||
if err == nil {
|
||||
t.Fatalf("fooDir already points to a directory, so SetDir should have failed."+
|
||||
"The response was: %#v", resp)
|
||||
}
|
||||
|
||||
_, err = c.Set("foo", "bar", 5)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// This should succeed
|
||||
// It should replace the key
|
||||
resp, err = c.SetDir("foo", 5)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !(resp.Node.Key == "/foo" && resp.Node.Value == "" && resp.Node.TTL == 5) {
|
||||
t.Fatalf("SetDir 2 failed: %#v", resp)
|
||||
}
|
||||
if !(resp.PrevNode.Key == "/foo" && resp.PrevNode.Value == "bar" && resp.PrevNode.TTL == 5) {
|
||||
t.Fatalf("SetDir 2 failed: %#v", resp)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUpdateDir(t *testing.T) {
|
||||
c := NewClient(nil)
|
||||
defer func() {
|
||||
c.Delete("fooDir", true)
|
||||
}()
|
||||
|
||||
resp, err := c.CreateDir("fooDir", 5)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// This should succeed.
|
||||
resp, err = c.UpdateDir("fooDir", 5)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if !(resp.Action == "update" && resp.Node.Key == "/fooDir" &&
|
||||
resp.Node.Value == "" && resp.Node.TTL == 5) {
|
||||
t.Fatalf("UpdateDir 1 failed: %#v", resp)
|
||||
}
|
||||
if !(resp.PrevNode.Key == "/fooDir" && resp.PrevNode.Dir == true && resp.PrevNode.TTL == 5) {
|
||||
t.Fatalf("UpdateDir 1 PrevNode failed: %#v", resp)
|
||||
}
|
||||
|
||||
// This should fail because the key does not exist.
|
||||
resp, err = c.UpdateDir("nonexistentDir", 5)
|
||||
if err == nil {
|
||||
t.Fatalf("The key %v did not exist, so the update should have failed."+
|
||||
"The response was: %#v", resp.Node.Key, resp)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreateDir(t *testing.T) {
|
||||
c := NewClient(nil)
|
||||
defer func() {
|
||||
c.Delete("fooDir", true)
|
||||
}()
|
||||
|
||||
// This should succeed
|
||||
resp, err := c.CreateDir("fooDir", 5)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if !(resp.Action == "create" && resp.Node.Key == "/fooDir" &&
|
||||
resp.Node.Value == "" && resp.Node.TTL == 5) {
|
||||
t.Fatalf("CreateDir 1 failed: %#v", resp)
|
||||
}
|
||||
if resp.PrevNode != nil {
|
||||
t.Fatalf("CreateDir 1 PrevNode failed: %#v", resp)
|
||||
}
|
||||
|
||||
// This should fail, because the key is already there
|
||||
resp, err = c.CreateDir("fooDir", 5)
|
||||
if err == nil {
|
||||
t.Fatalf("The key %v did exist, so the creation should have failed."+
|
||||
"The response was: %#v", resp.Node.Key, resp)
|
||||
}
|
||||
}
|
3
third_party/github.com/coreos/go-etcd/etcd/version.go
vendored
Normal file
3
third_party/github.com/coreos/go-etcd/etcd/version.go
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
package etcd
|
||||
|
||||
const version = "v2"
|
103
third_party/github.com/coreos/go-etcd/etcd/watch.go
vendored
Normal file
103
third_party/github.com/coreos/go-etcd/etcd/watch.go
vendored
Normal file
@@ -0,0 +1,103 @@
|
||||
package etcd
|
||||
|
||||
import (
|
||||
"errors"
|
||||
)
|
||||
|
||||
// Errors introduced by the Watch command.
|
||||
var (
|
||||
ErrWatchStoppedByUser = errors.New("Watch stopped by the user via stop channel")
|
||||
)
|
||||
|
||||
// If recursive is set to true the watch returns the first change under the given
|
||||
// prefix since the given index.
|
||||
//
|
||||
// If recursive is set to false the watch returns the first change to the given key
|
||||
// since the given index.
|
||||
//
|
||||
// To watch for the latest change, set waitIndex = 0.
|
||||
//
|
||||
// If a receiver channel is given, it will be a long-term watch. Watch will block at the
|
||||
//channel. After someone receives the channel, it will go on to watch that
|
||||
// prefix. If a stop channel is given, the client can close long-term watch using
|
||||
// the stop channel.
|
||||
func (c *Client) Watch(prefix string, waitIndex uint64, recursive bool,
|
||||
receiver chan *Response, stop chan bool) (*Response, error) {
|
||||
logger.Debugf("watch %s [%s]", prefix, c.cluster.Leader)
|
||||
if receiver == nil {
|
||||
raw, err := c.watchOnce(prefix, waitIndex, recursive, stop)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return raw.Unmarshal()
|
||||
}
|
||||
defer close(receiver)
|
||||
|
||||
for {
|
||||
raw, err := c.watchOnce(prefix, waitIndex, recursive, stop)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resp, err := raw.Unmarshal()
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
waitIndex = resp.Node.ModifiedIndex + 1
|
||||
receiver <- resp
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Client) RawWatch(prefix string, waitIndex uint64, recursive bool,
|
||||
receiver chan *RawResponse, stop chan bool) (*RawResponse, error) {
|
||||
|
||||
logger.Debugf("rawWatch %s [%s]", prefix, c.cluster.Leader)
|
||||
if receiver == nil {
|
||||
return c.watchOnce(prefix, waitIndex, recursive, stop)
|
||||
}
|
||||
|
||||
for {
|
||||
raw, err := c.watchOnce(prefix, waitIndex, recursive, stop)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resp, err := raw.Unmarshal()
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
waitIndex = resp.Node.ModifiedIndex + 1
|
||||
receiver <- raw
|
||||
}
|
||||
}
|
||||
|
||||
// helper func
|
||||
// return when there is change under the given prefix
|
||||
func (c *Client) watchOnce(key string, waitIndex uint64, recursive bool, stop chan bool) (*RawResponse, error) {
|
||||
|
||||
options := Options{
|
||||
"wait": true,
|
||||
}
|
||||
if waitIndex > 0 {
|
||||
options["waitIndex"] = waitIndex
|
||||
}
|
||||
if recursive {
|
||||
options["recursive"] = true
|
||||
}
|
||||
|
||||
resp, err := c.getCancelable(key, options, stop)
|
||||
|
||||
if err == ErrRequestCancelled {
|
||||
return nil, ErrWatchStoppedByUser
|
||||
}
|
||||
|
||||
return resp, err
|
||||
}
|
119
third_party/github.com/coreos/go-etcd/etcd/watch_test.go
vendored
Normal file
119
third_party/github.com/coreos/go-etcd/etcd/watch_test.go
vendored
Normal file
@@ -0,0 +1,119 @@
|
||||
package etcd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"runtime"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestWatch(t *testing.T) {
|
||||
c := NewClient(nil)
|
||||
defer func() {
|
||||
c.Delete("watch_foo", true)
|
||||
}()
|
||||
|
||||
go setHelper("watch_foo", "bar", c)
|
||||
|
||||
resp, err := c.Watch("watch_foo", 0, false, nil, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !(resp.Node.Key == "/watch_foo" && resp.Node.Value == "bar") {
|
||||
t.Fatalf("Watch 1 failed: %#v", resp)
|
||||
}
|
||||
|
||||
go setHelper("watch_foo", "bar", c)
|
||||
|
||||
resp, err = c.Watch("watch_foo", resp.Node.ModifiedIndex+1, false, nil, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !(resp.Node.Key == "/watch_foo" && resp.Node.Value == "bar") {
|
||||
t.Fatalf("Watch 2 failed: %#v", resp)
|
||||
}
|
||||
|
||||
routineNum := runtime.NumGoroutine()
|
||||
|
||||
ch := make(chan *Response, 10)
|
||||
stop := make(chan bool, 1)
|
||||
|
||||
go setLoop("watch_foo", "bar", c)
|
||||
|
||||
go receiver(ch, stop)
|
||||
|
||||
_, err = c.Watch("watch_foo", 0, false, ch, stop)
|
||||
if err != ErrWatchStoppedByUser {
|
||||
t.Fatalf("Watch returned a non-user stop error")
|
||||
}
|
||||
|
||||
if newRoutineNum := runtime.NumGoroutine(); newRoutineNum != routineNum {
|
||||
t.Fatalf("Routine numbers differ after watch stop: %v, %v", routineNum, newRoutineNum)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWatchAll(t *testing.T) {
|
||||
c := NewClient(nil)
|
||||
defer func() {
|
||||
c.Delete("watch_foo", true)
|
||||
}()
|
||||
|
||||
go setHelper("watch_foo/foo", "bar", c)
|
||||
|
||||
resp, err := c.Watch("watch_foo", 0, true, nil, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !(resp.Node.Key == "/watch_foo/foo" && resp.Node.Value == "bar") {
|
||||
t.Fatalf("WatchAll 1 failed: %#v", resp)
|
||||
}
|
||||
|
||||
go setHelper("watch_foo/foo", "bar", c)
|
||||
|
||||
resp, err = c.Watch("watch_foo", resp.Node.ModifiedIndex+1, true, nil, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !(resp.Node.Key == "/watch_foo/foo" && resp.Node.Value == "bar") {
|
||||
t.Fatalf("WatchAll 2 failed: %#v", resp)
|
||||
}
|
||||
|
||||
ch := make(chan *Response, 10)
|
||||
stop := make(chan bool, 1)
|
||||
|
||||
routineNum := runtime.NumGoroutine()
|
||||
|
||||
go setLoop("watch_foo/foo", "bar", c)
|
||||
|
||||
go receiver(ch, stop)
|
||||
|
||||
_, err = c.Watch("watch_foo", 0, true, ch, stop)
|
||||
if err != ErrWatchStoppedByUser {
|
||||
t.Fatalf("Watch returned a non-user stop error")
|
||||
}
|
||||
|
||||
if newRoutineNum := runtime.NumGoroutine(); newRoutineNum != routineNum {
|
||||
t.Fatalf("Routine numbers differ after watch stop: %v, %v", routineNum, newRoutineNum)
|
||||
}
|
||||
}
|
||||
|
||||
func setHelper(key, value string, c *Client) {
|
||||
time.Sleep(time.Second)
|
||||
c.Set(key, value, 100)
|
||||
}
|
||||
|
||||
func setLoop(key, value string, c *Client) {
|
||||
time.Sleep(time.Second)
|
||||
for i := 0; i < 10; i++ {
|
||||
newValue := fmt.Sprintf("%s_%v", value, i)
|
||||
c.Set(key, newValue, 100)
|
||||
time.Sleep(time.Second / 10)
|
||||
}
|
||||
}
|
||||
|
||||
func receiver(c chan *Response, stop chan bool) {
|
||||
for i := 0; i < 10; i++ {
|
||||
<-c
|
||||
}
|
||||
stop <- true
|
||||
}
|
191
third_party/github.com/coreos/go-log/LICENSE
vendored
Normal file
191
third_party/github.com/coreos/go-log/LICENSE
vendored
Normal file
@@ -0,0 +1,191 @@
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction, and
|
||||
distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by the copyright
|
||||
owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all other entities
|
||||
that control, are controlled by, or are under common control with that entity.
|
||||
For the purposes of this definition, "control" means (i) the power, direct or
|
||||
indirect, to cause the direction or management of such entity, whether by
|
||||
contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity exercising
|
||||
permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications, including
|
||||
but not limited to software source code, documentation source, and configuration
|
||||
files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical transformation or
|
||||
translation of a Source form, including but not limited to compiled object code,
|
||||
generated documentation, and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or Object form, made
|
||||
available under the License, as indicated by a copyright notice that is included
|
||||
in or attached to the work (an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object form, that
|
||||
is based on (or derived from) the Work and for which the editorial revisions,
|
||||
annotations, elaborations, or other modifications represent, as a whole, an
|
||||
original work of authorship. For the purposes of this License, Derivative Works
|
||||
shall not include works that remain separable from, or merely link (or bind by
|
||||
name) to the interfaces of, the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including the original version
|
||||
of the Work and any modifications or additions to that Work or Derivative Works
|
||||
thereof, that is intentionally submitted to Licensor for inclusion in the Work
|
||||
by the copyright owner or by an individual or Legal Entity authorized to submit
|
||||
on behalf of the copyright owner. For the purposes of this definition,
|
||||
"submitted" means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems, and
|
||||
issue tracking systems that are managed by, or on behalf of, the Licensor for
|
||||
the purpose of discussing and improving the Work, but excluding communication
|
||||
that is conspicuously marked or otherwise designated in writing by the copyright
|
||||
owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity on behalf
|
||||
of whom a Contribution has been received by Licensor and subsequently
|
||||
incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License.
|
||||
|
||||
Subject to the terms and conditions of this License, each Contributor hereby
|
||||
grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
|
||||
irrevocable copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the Work and such
|
||||
Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License.
|
||||
|
||||
Subject to the terms and conditions of this License, each Contributor hereby
|
||||
grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
|
||||
irrevocable (except as stated in this section) patent license to make, have
|
||||
made, use, offer to sell, sell, import, and otherwise transfer the Work, where
|
||||
such license applies only to those patent claims licensable by such Contributor
|
||||
that are necessarily infringed by their Contribution(s) alone or by combination
|
||||
of their Contribution(s) with the Work to which such Contribution(s) was
|
||||
submitted. If You institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work or a
|
||||
Contribution incorporated within the Work constitutes direct or contributory
|
||||
patent infringement, then any patent licenses granted to You under this License
|
||||
for that Work shall terminate as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution.
|
||||
|
||||
You may reproduce and distribute copies of the Work or Derivative Works thereof
|
||||
in any medium, with or without modifications, and in Source or Object form,
|
||||
provided that You meet the following conditions:
|
||||
|
||||
You must give any other recipients of the Work or Derivative Works a copy of
|
||||
this License; and
|
||||
You must cause any modified files to carry prominent notices stating that You
|
||||
changed the files; and
|
||||
You must retain, in the Source form of any Derivative Works that You distribute,
|
||||
all copyright, patent, trademark, and attribution notices from the Source form
|
||||
of the Work, excluding those notices that do not pertain to any part of the
|
||||
Derivative Works; and
|
||||
If the Work includes a "NOTICE" text file as part of its distribution, then any
|
||||
Derivative Works that You distribute must include a readable copy of the
|
||||
attribution notices contained within such NOTICE file, excluding those notices
|
||||
that do not pertain to any part of the Derivative Works, in at least one of the
|
||||
following places: within a NOTICE text file distributed as part of the
|
||||
Derivative Works; within the Source form or documentation, if provided along
|
||||
with the Derivative Works; or, within a display generated by the Derivative
|
||||
Works, if and wherever such third-party notices normally appear. The contents of
|
||||
the NOTICE file are for informational purposes only and do not modify the
|
||||
License. You may add Your own attribution notices within Derivative Works that
|
||||
You distribute, alongside or as an addendum to the NOTICE text from the Work,
|
||||
provided that such additional attribution notices cannot be construed as
|
||||
modifying the License.
|
||||
You may add Your own copyright statement to Your modifications and may provide
|
||||
additional or different license terms and conditions for use, reproduction, or
|
||||
distribution of Your modifications, or for any such Derivative Works as a whole,
|
||||
provided Your use, reproduction, and distribution of the Work otherwise complies
|
||||
with the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions.
|
||||
|
||||
Unless You explicitly state otherwise, any Contribution intentionally submitted
|
||||
for inclusion in the Work by You to the Licensor shall be under the terms and
|
||||
conditions of this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify the terms of
|
||||
any separate license agreement you may have executed with Licensor regarding
|
||||
such Contributions.
|
||||
|
||||
6. Trademarks.
|
||||
|
||||
This License does not grant permission to use the trade names, trademarks,
|
||||
service marks, or product names of the Licensor, except as required for
|
||||
reasonable and customary use in describing the origin of the Work and
|
||||
reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty.
|
||||
|
||||
Unless required by applicable law or agreed to in writing, Licensor provides the
|
||||
Work (and each Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied,
|
||||
including, without limitation, any warranties or conditions of TITLE,
|
||||
NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are
|
||||
solely responsible for determining the appropriateness of using or
|
||||
redistributing the Work and assume any risks associated with Your exercise of
|
||||
permissions under this License.
|
||||
|
||||
8. Limitation of Liability.
|
||||
|
||||
In no event and under no legal theory, whether in tort (including negligence),
|
||||
contract, or otherwise, unless required by applicable law (such as deliberate
|
||||
and grossly negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special, incidental,
|
||||
or consequential damages of any character arising as a result of this License or
|
||||
out of the use or inability to use the Work (including but not limited to
|
||||
damages for loss of goodwill, work stoppage, computer failure or malfunction, or
|
||||
any and all other commercial damages or losses), even if such Contributor has
|
||||
been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability.
|
||||
|
||||
While redistributing the Work or Derivative Works thereof, You may choose to
|
||||
offer, and charge a fee for, acceptance of support, warranty, indemnity, or
|
||||
other liability obligations and/or rights consistent with this License. However,
|
||||
in accepting such obligations, You may act only on Your own behalf and on Your
|
||||
sole responsibility, not on behalf of any other Contributor, and only if You
|
||||
agree to indemnify, defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason of your
|
||||
accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work
|
||||
|
||||
To apply the Apache License to your work, attach the following boilerplate
|
||||
notice, with the fields enclosed by brackets "[]" replaced with your own
|
||||
identifying information. (Don't include the brackets!) The text should be
|
||||
enclosed in the appropriate comment syntax for the file format. We also
|
||||
recommend that a file or class name and description of purpose be included on
|
||||
the same "printed page" as the copyright notice for easier identification within
|
||||
third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
121
third_party/github.com/coreos/go-log/README.md
vendored
Normal file
121
third_party/github.com/coreos/go-log/README.md
vendored
Normal file
@@ -0,0 +1,121 @@
|
||||
go-log
|
||||
==========
|
||||
|
||||
go-log is a simple logging library for Go which supports logging to
|
||||
systemd.
|
||||
|
||||
### Examples
|
||||
#### Default
|
||||
This example uses the default log to log to standard out and (if available) to systemd:
|
||||
```go
|
||||
package main
|
||||
import (
|
||||
"github.com/coreos/go-log/log"
|
||||
)
|
||||
|
||||
func main() {
|
||||
log.Info("Hello World.")
|
||||
log.Error("There's nothing more to this program.")
|
||||
}
|
||||
```
|
||||
|
||||
#### Using Sinks and Formats
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/coreos/go-log/log"
|
||||
"os"
|
||||
)
|
||||
|
||||
func main() {
|
||||
l := log.NewSimple(
|
||||
log.WriterSink(os.Stderr,
|
||||
"%s: %s[%d] %s\n",
|
||||
[]string{"priority", "executable", "pid", "message"}))
|
||||
l.Info("Here's a differently formatted log message.")
|
||||
}
|
||||
```
|
||||
|
||||
#### Custom Sink
|
||||
This example only logs messages with priority `PriErr` and greater.
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/coreos/go-log/log"
|
||||
"os"
|
||||
)
|
||||
|
||||
func main() {
|
||||
l := log.NewSimple(
|
||||
&PriorityFilter{
|
||||
log.PriErr,
|
||||
log.WriterSink(os.Stdout, log.BasicFormat, log.BasicFields),
|
||||
})
|
||||
l.Info("This will be filtered out")
|
||||
l.Info("and not printed at all.")
|
||||
l.Error("This will be printed, though!")
|
||||
l.Critical("And so will this!")
|
||||
}
|
||||
|
||||
type PriorityFilter struct {
|
||||
priority log.Priority
|
||||
target log.Sink
|
||||
}
|
||||
|
||||
func (filter *PriorityFilter) Log(fields log.Fields) {
|
||||
// lower priority values indicate more important messages
|
||||
if fields["priority"].(log.Priority) <= filter.priority {
|
||||
filter.target.Log(fields)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Fields
|
||||
The following fields are available for use in all sinks:
|
||||
```go
|
||||
"prefix" string // static field available to all sinks
|
||||
"seq" uint64 // auto-incrementing sequence number
|
||||
"start_time" time.Time // start time of the log
|
||||
"time" string // formatted time of log entry
|
||||
"full_time" time.Time // time of log entry
|
||||
"rtime" time.Duration // relative time of log entry since started
|
||||
"pid" int // process id
|
||||
"executable" string // executable filename
|
||||
```
|
||||
In addition, if `verbose=true` is passed to `New()`, the following (somewhat expensive) runtime fields are also available:
|
||||
```go
|
||||
"funcname" string // function name where the log function was called
|
||||
"lineno" int // line number where the log function was called
|
||||
"pathname" string // full pathname of caller
|
||||
"filename" string // filename of caller
|
||||
```
|
||||
|
||||
### Logging functions
|
||||
All these functions can also be called directly to use the default log.
|
||||
```go
|
||||
func (*Logger) Log(priority Priority, v ...interface)
|
||||
func (*Logger) Logf(priority Priority, format string, v ...interface{})
|
||||
func (*Logger) Emergency(v ...interface)
|
||||
func (*Logger) Emergencyf(format string, v ...interface{})
|
||||
func (*Logger) Alert(v ...interface)
|
||||
func (*Logger) Alertf(format string, v ...interface{})
|
||||
func (*Logger) Critical(v ...interface)
|
||||
func (*Logger) Criticalf(format string, v ...interface{})
|
||||
func (*Logger) Error(v ...interface)
|
||||
func (*Logger) Errorf(format string, v ...interface{})
|
||||
func (*Logger) Warning(v ...interface)
|
||||
func (*Logger) Warningf(format string, v ...interface{})
|
||||
func (*Logger) Notice(v ...interface)
|
||||
func (*Logger) Noticef(format string, v ...interface{})
|
||||
func (*Logger) Info(v ...interface)
|
||||
func (*Logger) Infof(format string, v ...interface{})
|
||||
func (*Logger) Debug(v ...interface)
|
||||
func (*Logger) Debugf(format string, v ...interface{})
|
||||
```
|
||||
|
||||
### Acknowledgements
|
||||
This package is a mostly-from-scratch rewrite of
|
||||
[ccding/go-logging](https://github.com/ccding/go-logging) with some features
|
||||
removed and systemd support added.
|
214
third_party/github.com/coreos/go-log/log/commands.go
vendored
Normal file
214
third_party/github.com/coreos/go-log/log/commands.go
vendored
Normal file
@@ -0,0 +1,214 @@
|
||||
package log
|
||||
// Copyright 2013, CoreOS, Inc. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
// author: David Fisher <ddf1991@gmail.com>
|
||||
// based on previous package by: Cong Ding <dinggnu@gmail.com>
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
)
|
||||
|
||||
var BasicFormat = "%s [%9s] %s- %s\n"
|
||||
var BasicFields = []string{"time", "priority", "prefix", "message"}
|
||||
var RichFormat = "%s [%9s] %d %s - %s:%s:%d - %s\n"
|
||||
var RichFields = []string{"full_time", "priority", "seq", "prefix", "filename", "funcname", "lineno", "message"}
|
||||
|
||||
// This function has an unusual name to aid in finding it while walking the
|
||||
// stack. We need to do some dead reckoning from this function to access the
|
||||
// caller's stack, so there is a consistent call depth above this function.
|
||||
func (logger *Logger) Log(priority Priority, v ...interface{}) {
|
||||
fields := logger.fieldValues()
|
||||
fields["priority"] = priority
|
||||
fields["message"] = fmt.Sprint(v...)
|
||||
for _, sink := range logger.sinks {
|
||||
sink.Log(fields)
|
||||
}
|
||||
}
|
||||
|
||||
func (logger *Logger) Logf(priority Priority, format string, v ...interface{}) {
|
||||
logger.Log(priority, fmt.Sprintf(format, v...))
|
||||
}
|
||||
|
||||
|
||||
func (logger *Logger) Emergency(v ...interface{}) {
|
||||
logger.Log(PriEmerg, v...)
|
||||
}
|
||||
func (logger *Logger) Emergencyf(format string, v ...interface{}) {
|
||||
logger.Log(PriEmerg, fmt.Sprintf(format, v...))
|
||||
}
|
||||
|
||||
func (logger *Logger) Alert(v ...interface{}) {
|
||||
logger.Log(PriAlert, v...)
|
||||
}
|
||||
func (logger *Logger) Alertf(format string, v ...interface{}) {
|
||||
logger.Log(PriAlert, fmt.Sprintf(format, v...))
|
||||
}
|
||||
|
||||
func (logger *Logger) Critical(v ...interface{}) {
|
||||
logger.Log(PriCrit, v...)
|
||||
}
|
||||
func (logger *Logger) Criticalf(format string, v ...interface{}) {
|
||||
logger.Log(PriCrit, fmt.Sprintf(format, v...))
|
||||
}
|
||||
|
||||
func (logger *Logger) Error(v ...interface{}) {
|
||||
logger.Log(PriErr, v...)
|
||||
}
|
||||
func (logger *Logger) Errorf(format string, v ...interface{}) {
|
||||
logger.Log(PriErr, fmt.Sprintf(format, v...))
|
||||
}
|
||||
|
||||
func (logger *Logger) Warning(v ...interface{}) {
|
||||
logger.Log(PriWarning, v...)
|
||||
}
|
||||
func (logger *Logger) Warningf(format string, v ...interface{}) {
|
||||
logger.Log(PriWarning, fmt.Sprintf(format, v...))
|
||||
}
|
||||
|
||||
func (logger *Logger) Notice(v ...interface{}) {
|
||||
logger.Log(PriNotice, v...)
|
||||
}
|
||||
func (logger *Logger) Noticef(format string, v ...interface{}) {
|
||||
logger.Log(PriNotice, fmt.Sprintf(format, v...))
|
||||
}
|
||||
|
||||
func (logger *Logger) Info(v ...interface{}) {
|
||||
logger.Log(PriInfo, v...)
|
||||
}
|
||||
func (logger *Logger) Infof(format string, v ...interface{}) {
|
||||
logger.Log(PriInfo, fmt.Sprintf(format, v...))
|
||||
}
|
||||
|
||||
func (logger *Logger) Debug(v ...interface{}) {
|
||||
logger.Log(PriDebug, v...)
|
||||
}
|
||||
func (logger *Logger) Debugf(format string, v ...interface{}) {
|
||||
logger.Log(PriDebug, fmt.Sprintf(format, v...))
|
||||
}
|
||||
|
||||
|
||||
func Emergency(v ...interface{}) {
|
||||
defaultLogger.Log(PriEmerg, v...)
|
||||
}
|
||||
func Emergencyf(format string, v ...interface{}) {
|
||||
defaultLogger.Log(PriEmerg, fmt.Sprintf(format, v...))
|
||||
}
|
||||
|
||||
func Alert(v ...interface{}) {
|
||||
defaultLogger.Log(PriAlert, v...)
|
||||
}
|
||||
func Alertf(format string, v ...interface{}) {
|
||||
defaultLogger.Log(PriAlert, fmt.Sprintf(format, v...))
|
||||
}
|
||||
|
||||
func Critical(v ...interface{}) {
|
||||
defaultLogger.Log(PriCrit, v...)
|
||||
}
|
||||
func Criticalf(format string, v ...interface{}) {
|
||||
defaultLogger.Log(PriCrit, fmt.Sprintf(format, v...))
|
||||
}
|
||||
|
||||
func Error(v ...interface{}) {
|
||||
defaultLogger.Log(PriErr, v...)
|
||||
}
|
||||
func Errorf(format string, v ...interface{}) {
|
||||
defaultLogger.Log(PriErr, fmt.Sprintf(format, v...))
|
||||
}
|
||||
|
||||
func Warning(v ...interface{}) {
|
||||
defaultLogger.Log(PriWarning, v...)
|
||||
}
|
||||
func Warningf(format string, v ...interface{}) {
|
||||
defaultLogger.Log(PriWarning, fmt.Sprintf(format, v...))
|
||||
}
|
||||
|
||||
func Notice(v ...interface{}) {
|
||||
defaultLogger.Log(PriNotice, v...)
|
||||
}
|
||||
func Noticef(format string, v ...interface{}) {
|
||||
defaultLogger.Log(PriNotice, fmt.Sprintf(format, v...))
|
||||
}
|
||||
|
||||
func Info(v ...interface{}) {
|
||||
defaultLogger.Log(PriInfo, v...)
|
||||
}
|
||||
func Infof(format string, v ...interface{}) {
|
||||
defaultLogger.Log(PriInfo, fmt.Sprintf(format, v...))
|
||||
}
|
||||
|
||||
func Debug(v ...interface{}) {
|
||||
defaultLogger.Log(PriDebug, v...)
|
||||
}
|
||||
func Debugf(format string, v ...interface{}) {
|
||||
defaultLogger.Log(PriDebug, fmt.Sprintf(format, v...))
|
||||
}
|
||||
|
||||
// Standard library log functions
|
||||
|
||||
func (logger *Logger)Fatalln (v ...interface{}) {
|
||||
logger.Log(PriCrit, v...)
|
||||
os.Exit(1)
|
||||
}
|
||||
func (logger *Logger)Fatalf (format string, v ...interface{}) {
|
||||
logger.Logf(PriCrit, format, v...)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
func (logger *Logger)Panicln (v ...interface{}) {
|
||||
s := fmt.Sprint(v...)
|
||||
logger.Log(PriErr, s)
|
||||
panic(s)
|
||||
}
|
||||
func (logger *Logger)Panicf (format string, v ...interface{}) {
|
||||
s := fmt.Sprintf(format, v...)
|
||||
logger.Log(PriErr, s)
|
||||
panic(s)
|
||||
}
|
||||
|
||||
func (logger *Logger)Println (v ...interface{}) {
|
||||
logger.Log(PriInfo, v...)
|
||||
}
|
||||
func (logger *Logger)Printf (format string, v ...interface{}) {
|
||||
logger.Logf(PriInfo, format, v...)
|
||||
}
|
||||
|
||||
|
||||
func Fatalln (v ...interface{}) {
|
||||
defaultLogger.Log(PriCrit, v...)
|
||||
os.Exit(1)
|
||||
}
|
||||
func Fatalf (format string, v ...interface{}) {
|
||||
defaultLogger.Logf(PriCrit, format, v...)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
func Panicln (v ...interface{}) {
|
||||
s := fmt.Sprint(v...)
|
||||
defaultLogger.Log(PriErr, s)
|
||||
panic(s)
|
||||
}
|
||||
func Panicf (format string, v ...interface{}) {
|
||||
s := fmt.Sprintf(format, v...)
|
||||
defaultLogger.Log(PriErr, s)
|
||||
panic(s)
|
||||
}
|
||||
|
||||
func Println (v ...interface{}) {
|
||||
defaultLogger.Log(PriInfo, v...)
|
||||
}
|
||||
func Printf (format string, v ...interface{}) {
|
||||
defaultLogger.Logf(PriInfo, format, v...)
|
||||
}
|
69
third_party/github.com/coreos/go-log/log/fields.go
vendored
Normal file
69
third_party/github.com/coreos/go-log/log/fields.go
vendored
Normal file
@@ -0,0 +1,69 @@
|
||||
package log
|
||||
// Copyright 2013, CoreOS, Inc. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
// author: David Fisher <ddf1991@gmail.com>
|
||||
// based on previous package by: Cong Ding <dinggnu@gmail.com>
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path"
|
||||
"runtime"
|
||||
"strings"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Fields map[string]interface{}
|
||||
|
||||
func (logger *Logger) fieldValues() Fields {
|
||||
now := time.Now()
|
||||
fields := Fields{
|
||||
"prefix": logger.prefix, // static field available to all sinks
|
||||
"seq": logger.nextSeq(), // auto-incrementing sequence number
|
||||
"start_time": logger.created, // start time of the logger
|
||||
"time": now.Format(time.StampMilli), // formatted time of log entry
|
||||
"full_time": now, // time of log entry
|
||||
"rtime": time.Since(logger.created), // relative time of log entry since started
|
||||
"pid": os.Getpid(), // process id
|
||||
"executable": logger.executable, // executable filename
|
||||
}
|
||||
|
||||
if logger.verbose {
|
||||
setVerboseFields(fields)
|
||||
}
|
||||
return fields
|
||||
}
|
||||
|
||||
func (logger *Logger) nextSeq() uint64 {
|
||||
return atomic.AddUint64(&logger.seq, 1)
|
||||
}
|
||||
|
||||
func setVerboseFields(fields Fields) {
|
||||
callers := make([]uintptr, 10)
|
||||
n := runtime.Callers(3, callers) // starts in (*Logger).Log or similar
|
||||
callers = callers[:n]
|
||||
|
||||
for _, pc := range callers {
|
||||
f := runtime.FuncForPC(pc)
|
||||
if !strings.Contains(f.Name(), "logger.(*Logger)") {
|
||||
fields["funcname"] = f.Name()
|
||||
pathname, lineno := f.FileLine(pc)
|
||||
fields["lineno"] = lineno
|
||||
fields["pathname"] = pathname
|
||||
fields["filename"] = path.Base(pathname)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
72
third_party/github.com/coreos/go-log/log/logger.go
vendored
Normal file
72
third_party/github.com/coreos/go-log/log/logger.go
vendored
Normal file
@@ -0,0 +1,72 @@
|
||||
package log
|
||||
// Copyright 2013, CoreOS, Inc. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
// author: David Fisher <ddf1991@gmail.com>
|
||||
// based on previous package by: Cong Ding <dinggnu@gmail.com>
|
||||
|
||||
import (
|
||||
"bitbucket.org/kardianos/osext"
|
||||
"os"
|
||||
"path"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Logger is user-immutable immutable struct which can log to several outputs
|
||||
type Logger struct {
|
||||
sinks []Sink // the sinks this logger will log to
|
||||
verbose bool // gather expensive logging data?
|
||||
prefix string // static field available to all log sinks under this logger
|
||||
|
||||
created time.Time // time when this logger was created
|
||||
seq uint64 // sequential number of log message, starting at 1
|
||||
executable string // executable name
|
||||
}
|
||||
|
||||
// New creates a new Logger which logs to all the supplied sinks. The prefix
|
||||
// argument is passed to all loggers under the field "prefix" with every log
|
||||
// message. If verbose is true, more expensive runtime fields will be computed
|
||||
// and passed to loggers. These fields are funcname, lineno, pathname, and
|
||||
// filename.
|
||||
func New(prefix string, verbose bool, sinks ...Sink) *Logger {
|
||||
return &Logger{
|
||||
sinks: sinks,
|
||||
verbose: verbose,
|
||||
prefix: prefix,
|
||||
|
||||
created: time.Now(),
|
||||
seq: 0,
|
||||
executable: getExecutableName(),
|
||||
}
|
||||
}
|
||||
|
||||
func getExecutableName() string {
|
||||
executablePath, err := osext.Executable()
|
||||
if err != nil {
|
||||
return "(UNKNOWN)"
|
||||
} else {
|
||||
return path.Base(executablePath)
|
||||
}
|
||||
}
|
||||
|
||||
// NewSimple(sinks...) is equivalent to New("", false, sinks...)
|
||||
func NewSimple(sinks ...Sink) *Logger {
|
||||
return New("", false, sinks...)
|
||||
}
|
||||
|
||||
var defaultLogger *Logger
|
||||
|
||||
func init() {
|
||||
defaultLogger = NewSimple(CombinedSink(os.Stdout, BasicFormat, BasicFields))
|
||||
}
|
54
third_party/github.com/coreos/go-log/log/priority.go
vendored
Normal file
54
third_party/github.com/coreos/go-log/log/priority.go
vendored
Normal file
@@ -0,0 +1,54 @@
|
||||
package log
|
||||
// Copyright 2013, CoreOS, Inc. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
// author: David Fisher <ddf1991@gmail.com>
|
||||
// based on previous package by: Cong Ding <dinggnu@gmail.com>
|
||||
|
||||
type Priority int
|
||||
|
||||
const (
|
||||
PriEmerg Priority = iota
|
||||
PriAlert
|
||||
PriCrit
|
||||
PriErr
|
||||
PriWarning
|
||||
PriNotice
|
||||
PriInfo
|
||||
PriDebug
|
||||
)
|
||||
|
||||
func (priority Priority) String() string {
|
||||
switch priority {
|
||||
case PriEmerg:
|
||||
return "EMERGENCY"
|
||||
case PriAlert:
|
||||
return "ALERT"
|
||||
case PriCrit:
|
||||
return "CRITICAL"
|
||||
case PriErr:
|
||||
return "ERROR"
|
||||
case PriWarning:
|
||||
return "WARNING"
|
||||
case PriNotice:
|
||||
return "NOTICE"
|
||||
case PriInfo:
|
||||
return "INFO"
|
||||
case PriDebug:
|
||||
return "DEBUG"
|
||||
|
||||
default:
|
||||
return "UNKNOWN"
|
||||
}
|
||||
}
|
97
third_party/github.com/coreos/go-log/log/sinks.go
vendored
Normal file
97
third_party/github.com/coreos/go-log/log/sinks.go
vendored
Normal file
@@ -0,0 +1,97 @@
|
||||
package log
|
||||
|
||||
// Copyright 2013, CoreOS, Inc. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
// author: David Fisher <ddf1991@gmail.com>
|
||||
// based on previous package by: Cong Ding <dinggnu@gmail.com>
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"sync"
|
||||
)
|
||||
|
||||
const AsyncBuffer = 100
|
||||
|
||||
type Sink interface {
|
||||
Log(Fields)
|
||||
}
|
||||
|
||||
type nullSink struct{}
|
||||
|
||||
func (sink *nullSink) Log(fields Fields) {}
|
||||
|
||||
func NullSink() Sink {
|
||||
return &nullSink{}
|
||||
}
|
||||
|
||||
type writerSink struct {
|
||||
lock sync.Mutex
|
||||
out io.Writer
|
||||
format string
|
||||
fields []string
|
||||
}
|
||||
|
||||
func (sink *writerSink) Log(fields Fields) {
|
||||
vals := make([]interface{}, len(sink.fields))
|
||||
for i, field := range sink.fields {
|
||||
var ok bool
|
||||
vals[i], ok = fields[field]
|
||||
if !ok {
|
||||
vals[i] = "???"
|
||||
}
|
||||
}
|
||||
|
||||
sink.lock.Lock()
|
||||
defer sink.lock.Unlock()
|
||||
fmt.Fprintf(sink.out, sink.format, vals...)
|
||||
}
|
||||
|
||||
func WriterSink(out io.Writer, format string, fields []string) Sink {
|
||||
return &writerSink{
|
||||
out: out,
|
||||
format: format,
|
||||
fields: fields,
|
||||
}
|
||||
}
|
||||
|
||||
type combinedSink struct {
|
||||
sinks []Sink
|
||||
}
|
||||
|
||||
func (sink *combinedSink) Log(fields Fields) {
|
||||
for _, s := range sink.sinks {
|
||||
s.Log(fields)
|
||||
}
|
||||
}
|
||||
|
||||
type priorityFilter struct {
|
||||
priority Priority
|
||||
target Sink
|
||||
}
|
||||
|
||||
func (filter *priorityFilter) Log(fields Fields) {
|
||||
// lower priority values indicate more important messages
|
||||
if fields["priority"].(Priority) <= filter.priority {
|
||||
filter.target.Log(fields)
|
||||
}
|
||||
}
|
||||
|
||||
func PriorityFilter(priority Priority, target Sink) Sink {
|
||||
return &priorityFilter{
|
||||
priority: priority,
|
||||
target: target,
|
||||
}
|
||||
}
|
82
third_party/github.com/coreos/go-log/log/sinks_unix.go
vendored
Normal file
82
third_party/github.com/coreos/go-log/log/sinks_unix.go
vendored
Normal file
@@ -0,0 +1,82 @@
|
||||
// +build !windows
|
||||
|
||||
package log
|
||||
|
||||
// Copyright 2013, CoreOS, Inc. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
// author: David Fisher <ddf1991@gmail.com>
|
||||
// based on previous package by: Cong Ding <dinggnu@gmail.com>
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/coreos/go-systemd/journal"
|
||||
"io"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type journalSink struct{}
|
||||
|
||||
func (sink *journalSink) Log(fields Fields) {
|
||||
message := fields["message"].(string)
|
||||
priority := toJournalPriority(fields["priority"].(Priority))
|
||||
journalFields := make(map[string]string)
|
||||
for k, v := range fields {
|
||||
if k == "message" || k == "priority" {
|
||||
continue
|
||||
}
|
||||
journalFields[strings.ToUpper(k)] = fmt.Sprint(v)
|
||||
}
|
||||
journal.Send(message, priority, journalFields)
|
||||
}
|
||||
|
||||
func toJournalPriority(priority Priority) journal.Priority {
|
||||
switch priority {
|
||||
case PriEmerg:
|
||||
return journal.PriEmerg
|
||||
case PriAlert:
|
||||
return journal.PriAlert
|
||||
case PriCrit:
|
||||
return journal.PriCrit
|
||||
case PriErr:
|
||||
return journal.PriErr
|
||||
case PriWarning:
|
||||
return journal.PriWarning
|
||||
case PriNotice:
|
||||
return journal.PriNotice
|
||||
case PriInfo:
|
||||
return journal.PriInfo
|
||||
case PriDebug:
|
||||
return journal.PriDebug
|
||||
|
||||
default:
|
||||
return journal.PriErr
|
||||
}
|
||||
}
|
||||
|
||||
func JournalSink() Sink {
|
||||
return &journalSink{}
|
||||
}
|
||||
|
||||
func CombinedSink(writer io.Writer, format string, fields []string) Sink {
|
||||
sinks := make([]Sink, 0)
|
||||
sinks = append(sinks, WriterSink(writer, format, fields))
|
||||
if journal.Enabled() {
|
||||
sinks = append(sinks, JournalSink())
|
||||
}
|
||||
|
||||
return &combinedSink{
|
||||
sinks: sinks,
|
||||
}
|
||||
}
|
33
third_party/github.com/coreos/go-log/log/sinks_windows.go
vendored
Normal file
33
third_party/github.com/coreos/go-log/log/sinks_windows.go
vendored
Normal file
@@ -0,0 +1,33 @@
|
||||
// +build windows
|
||||
|
||||
package log
|
||||
|
||||
// Copyright 2013, CoreOS, Inc. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
// author: David Fisher <ddf1991@gmail.com>
|
||||
// based on previous package by: Cong Ding <dinggnu@gmail.com>
|
||||
|
||||
import (
|
||||
"io"
|
||||
)
|
||||
|
||||
func CombinedSink(writer io.Writer, format string, fields []string) Sink {
|
||||
sinks := make([]Sink, 0)
|
||||
sinks = append(sinks, WriterSink(writer, format, fields))
|
||||
|
||||
return &combinedSink{
|
||||
sinks: sinks,
|
||||
}
|
||||
}
|
168
third_party/github.com/coreos/go-systemd/journal/send.go
vendored
Normal file
168
third_party/github.com/coreos/go-systemd/journal/send.go
vendored
Normal file
@@ -0,0 +1,168 @@
|
||||
/*
|
||||
Copyright 2013 CoreOS Inc.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
// Package journal provides write bindings to the systemd journal
|
||||
package journal
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
// Priority of a journal message
|
||||
type Priority int
|
||||
|
||||
const (
|
||||
PriEmerg Priority = iota
|
||||
PriAlert
|
||||
PriCrit
|
||||
PriErr
|
||||
PriWarning
|
||||
PriNotice
|
||||
PriInfo
|
||||
PriDebug
|
||||
)
|
||||
|
||||
var conn net.Conn
|
||||
|
||||
func init() {
|
||||
var err error
|
||||
conn, err = net.Dial("unixgram", "/run/systemd/journal/socket")
|
||||
if err != nil {
|
||||
conn = nil
|
||||
}
|
||||
}
|
||||
|
||||
// Enabled returns true iff the systemd journal is available for logging
|
||||
func Enabled() bool {
|
||||
return conn != nil
|
||||
}
|
||||
|
||||
// Send a message to the systemd journal. vars is a map of journald fields to
|
||||
// values. Fields must be composed of uppercase letters, numbers, and
|
||||
// underscores, but must not start with an underscore. Within these
|
||||
// restrictions, any arbitrary field name may be used. Some names have special
|
||||
// significance: see the journalctl documentation
|
||||
// (http://www.freedesktop.org/software/systemd/man/systemd.journal-fields.html)
|
||||
// for more details. vars may be nil.
|
||||
func Send(message string, priority Priority, vars map[string]string) error {
|
||||
if conn == nil {
|
||||
return journalError("could not connect to journald socket")
|
||||
}
|
||||
|
||||
data := new(bytes.Buffer)
|
||||
appendVariable(data, "PRIORITY", strconv.Itoa(int(priority)))
|
||||
appendVariable(data, "MESSAGE", message)
|
||||
for k, v := range vars {
|
||||
appendVariable(data, k, v)
|
||||
}
|
||||
|
||||
_, err := io.Copy(conn, data)
|
||||
if err != nil && isSocketSpaceError(err) {
|
||||
file, err := tempFd()
|
||||
if err != nil {
|
||||
return journalError(err.Error())
|
||||
}
|
||||
_, err = io.Copy(file, data)
|
||||
if err != nil {
|
||||
return journalError(err.Error())
|
||||
}
|
||||
|
||||
rights := syscall.UnixRights(int(file.Fd()))
|
||||
|
||||
/* this connection should always be a UnixConn, but better safe than sorry */
|
||||
unixConn, ok := conn.(*net.UnixConn)
|
||||
if !ok {
|
||||
return journalError("can't send file through non-Unix connection")
|
||||
}
|
||||
unixConn.WriteMsgUnix([]byte{}, rights, nil)
|
||||
} else if err != nil {
|
||||
return journalError(err.Error())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func appendVariable(w io.Writer, name, value string) {
|
||||
if !validVarName(name) {
|
||||
journalError("variable name contains invalid character, ignoring")
|
||||
}
|
||||
if strings.ContainsRune(value, '\n') {
|
||||
/* When the value contains a newline, we write:
|
||||
* - the variable name, followed by a newline
|
||||
* - the size (in 64bit little endian format)
|
||||
* - the data, followed by a newline
|
||||
*/
|
||||
fmt.Fprintln(w, name)
|
||||
binary.Write(w, binary.LittleEndian, uint64(len(value)))
|
||||
fmt.Fprintln(w, value)
|
||||
} else {
|
||||
/* just write the variable and value all on one line */
|
||||
fmt.Fprintf(w, "%s=%s\n", name, value)
|
||||
}
|
||||
}
|
||||
|
||||
func validVarName(name string) bool {
|
||||
/* The variable name must be in uppercase and consist only of characters,
|
||||
* numbers and underscores, and may not begin with an underscore. (from the docs)
|
||||
*/
|
||||
|
||||
valid := name[0] != '_'
|
||||
for _, c := range name {
|
||||
valid = valid && ('A' <= c && c <= 'Z') || ('0' <= c && c <= '9') || c == '_'
|
||||
}
|
||||
return valid
|
||||
}
|
||||
|
||||
func isSocketSpaceError(err error) bool {
|
||||
opErr, ok := err.(*net.OpError)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
|
||||
sysErr, ok := opErr.Err.(syscall.Errno)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
|
||||
return sysErr == syscall.EMSGSIZE || sysErr == syscall.ENOBUFS
|
||||
}
|
||||
|
||||
func tempFd() (*os.File, error) {
|
||||
file, err := ioutil.TempFile("/dev/shm/", "journal.XXXXX")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
syscall.Unlink(file.Name())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return file, nil
|
||||
}
|
||||
|
||||
func journalError(s string) error {
|
||||
s = "journal error: " + s
|
||||
fmt.Fprintln(os.Stderr, s)
|
||||
return errors.New(s)
|
||||
}
|
12
third_party/github.com/fsouza/go-dockerclient/.travis.yml
vendored
Normal file
12
third_party/github.com/fsouza/go-dockerclient/.travis.yml
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
language: go
|
||||
go:
|
||||
- 1.1.2
|
||||
- 1.2
|
||||
- tip
|
||||
env:
|
||||
- GOARCH=amd64
|
||||
- GOARCH=386
|
||||
install:
|
||||
- go get -d ./...
|
||||
script:
|
||||
- go test ./...
|
29
third_party/github.com/fsouza/go-dockerclient/AUTHORS
vendored
Normal file
29
third_party/github.com/fsouza/go-dockerclient/AUTHORS
vendored
Normal file
@@ -0,0 +1,29 @@
|
||||
# This is the official list of go-dockerclient authors for copyright purposes.
|
||||
|
||||
Andrews Medina <andrewsmedina@gmail.com>
|
||||
Andy Goldstein <andy.goldstein@redhat.com>
|
||||
Ben McCann <benmccann.com>
|
||||
Cezar Sa Espinola <cezar.sa@corp.globo.com>
|
||||
cheneydeng <cheneydeng@qq.com>
|
||||
Ed <edrocksit@gmail.com>
|
||||
Eric Anderson <anderson@copperegg.com>
|
||||
Flavia Missi <flaviamissi@gmail.com>
|
||||
Francisco Souza <f@souza.cc>
|
||||
Jason Wilder <jwilder@litl.com>
|
||||
Jean-Baptiste Dalido <jeanbaptiste@appgratis.com>
|
||||
Jeff Mitchell <jeffrey.mitchell@gmail.com>
|
||||
Jeffrey Hulten <jhulten@gmail.com>
|
||||
Lucas Clemente <lucas@clemente.io>
|
||||
Paul Morie <pmorie@gmail.com>
|
||||
Peter Jihoon Kim <raingrove@gmail.com>
|
||||
Philippe Lafoucrière <philippe.lafoucriere@tech-angels.com>
|
||||
Salvador Gironès <salvadorgirones@gmail.com>
|
||||
Simon Eskildsen <sirup@sirupsen.com>
|
||||
Simon Menke <simon.menke@gmail.com>
|
||||
Skolos <skolos@gopherlab.com>
|
||||
Soulou <leo@unbekandt.eu>
|
||||
Sridhar Ratnakumar <sridharr@activestate.com>
|
||||
Summer Mousa <smousa@zenoss.com>
|
||||
Tarsis Azevedo <tarsis@corp.globo.com>
|
||||
Tim Schindler <tim@catalyst-zero.com>
|
||||
Wiliam Souza <wiliamsouza83@gmail.com>
|
6
third_party/github.com/fsouza/go-dockerclient/DOCKER-LICENSE
vendored
Normal file
6
third_party/github.com/fsouza/go-dockerclient/DOCKER-LICENSE
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
You can find the Docker license int the following link:
|
||||
https://raw2.github.com/dotcloud/docker/master/LICENSE
|
22
third_party/github.com/fsouza/go-dockerclient/LICENSE
vendored
Normal file
22
third_party/github.com/fsouza/go-dockerclient/LICENSE
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
Copyright (c) 2014, go-dockerclient authors
|
||||
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.
|
47
third_party/github.com/fsouza/go-dockerclient/README.markdown
vendored
Normal file
47
third_party/github.com/fsouza/go-dockerclient/README.markdown
vendored
Normal file
@@ -0,0 +1,47 @@
|
||||
#go-dockerclient
|
||||
|
||||
[](https://drone.io/github.com/fsouza/go-dockerclient/latest)
|
||||
[](https://travis-ci.org/fsouza/go-dockerclient)
|
||||
|
||||
[](http://godoc.org/github.com/fsouza/go-dockerclient)
|
||||
|
||||
This package presents a client for the Docker remote API.
|
||||
|
||||
For more details, check the [remote API documentation](http://docs.docker.io/en/latest/reference/api/docker_remote_api/).
|
||||
|
||||
##Versioning
|
||||
|
||||
* Version 0.1 is compatible with Docker v0.7.1
|
||||
* The master is compatible with Docker's master
|
||||
|
||||
|
||||
## Example
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/fsouza/go-dockerclient"
|
||||
)
|
||||
|
||||
func main() {
|
||||
endpoint := "unix:///var/run/docker.sock"
|
||||
client, _ := docker.NewClient(endpoint)
|
||||
imgs, _ := client.ListImages(true)
|
||||
for _, img := range imgs {
|
||||
fmt.Println("ID: ", img.ID)
|
||||
fmt.Println("RepoTags: ", img.RepoTags)
|
||||
fmt.Println("Created: ", img.Created)
|
||||
fmt.Println("Size: ", img.Size)
|
||||
fmt.Println("VirtualSize: ", img.VirtualSize)
|
||||
fmt.Println("ParentId: ", img.ParentId)
|
||||
fmt.Println("Repository: ", img.Repository)
|
||||
}
|
||||
}
|
||||
|
||||
## Developing
|
||||
|
||||
You can run the tests with:
|
||||
|
||||
go get -d ./...
|
||||
go test ./...
|
36
third_party/github.com/fsouza/go-dockerclient/change.go
vendored
Normal file
36
third_party/github.com/fsouza/go-dockerclient/change.go
vendored
Normal file
@@ -0,0 +1,36 @@
|
||||
// Copyright 2014 go-dockerclient authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package docker
|
||||
|
||||
import "fmt"
|
||||
|
||||
type ChangeType int
|
||||
|
||||
const (
|
||||
ChangeModify ChangeType = iota
|
||||
ChangeAdd
|
||||
ChangeDelete
|
||||
)
|
||||
|
||||
// Change represents a change in a container.
|
||||
//
|
||||
// See http://goo.gl/DpGyzK for more details.
|
||||
type Change struct {
|
||||
Path string
|
||||
Kind ChangeType
|
||||
}
|
||||
|
||||
func (change *Change) String() string {
|
||||
var kind string
|
||||
switch change.Kind {
|
||||
case ChangeModify:
|
||||
kind = "C"
|
||||
case ChangeAdd:
|
||||
kind = "A"
|
||||
case ChangeDelete:
|
||||
kind = "D"
|
||||
}
|
||||
return fmt.Sprintf("%s %s", kind, change.Path)
|
||||
}
|
26
third_party/github.com/fsouza/go-dockerclient/change_test.go
vendored
Normal file
26
third_party/github.com/fsouza/go-dockerclient/change_test.go
vendored
Normal file
@@ -0,0 +1,26 @@
|
||||
// Copyright 2014 go-dockerclient authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package docker
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestChangeString(t *testing.T) {
|
||||
var tests = []struct {
|
||||
change Change
|
||||
expected string
|
||||
}{
|
||||
{Change{"/etc/passwd", ChangeModify}, "C /etc/passwd"},
|
||||
{Change{"/etc/passwd", ChangeAdd}, "A /etc/passwd"},
|
||||
{Change{"/etc/passwd", ChangeDelete}, "D /etc/passwd"},
|
||||
{Change{"/etc/passwd", 33}, " /etc/passwd"},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
if got := tt.change.String(); got != tt.expected {
|
||||
t.Errorf("Change.String(): want %q. Got %q.", tt.expected, got)
|
||||
}
|
||||
}
|
||||
}
|
352
third_party/github.com/fsouza/go-dockerclient/client.go
vendored
Normal file
352
third_party/github.com/fsouza/go-dockerclient/client.go
vendored
Normal file
@@ -0,0 +1,352 @@
|
||||
// Copyright 2014 go-dockerclient authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package docker provides a client for the Docker remote API.
|
||||
//
|
||||
// See http://goo.gl/mxyql for more details on the remote API.
|
||||
package docker
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/fsouza/go-dockerclient/utils"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/http/httputil"
|
||||
"net/url"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
|
||||
const userAgent = "go-dockerclient"
|
||||
|
||||
var (
|
||||
// ErrInvalidEndpoint is returned when the endpoint is not a valid HTTP URL.
|
||||
ErrInvalidEndpoint = errors.New("invalid endpoint")
|
||||
|
||||
// ErrConnectionRefused is returned when the client cannot connect to the given endpoint.
|
||||
ErrConnectionRefused = errors.New("cannot connect to Docker endpoint")
|
||||
)
|
||||
|
||||
// Client is the basic type of this package. It provides methods for
|
||||
// interaction with the API.
|
||||
type Client struct {
|
||||
endpoint string
|
||||
endpointURL *url.URL
|
||||
eventMonitor *eventMonitoringState
|
||||
client *http.Client
|
||||
}
|
||||
|
||||
// NewClient returns a Client instance ready for communication with the
|
||||
// given server endpoint.
|
||||
func NewClient(endpoint string) (*Client, error) {
|
||||
u, err := parseEndpoint(endpoint)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &Client{
|
||||
endpoint: endpoint,
|
||||
endpointURL: u,
|
||||
client: http.DefaultClient,
|
||||
eventMonitor: new(eventMonitoringState),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (c *Client) do(method, path string, data interface{}) ([]byte, int, error) {
|
||||
var params io.Reader
|
||||
if data != nil {
|
||||
buf, err := json.Marshal(data)
|
||||
if err != nil {
|
||||
return nil, -1, err
|
||||
}
|
||||
params = bytes.NewBuffer(buf)
|
||||
}
|
||||
req, err := http.NewRequest(method, c.getURL(path), params)
|
||||
if err != nil {
|
||||
return nil, -1, err
|
||||
}
|
||||
req.Header.Set("User-Agent", userAgent)
|
||||
if data != nil {
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
} else if method == "POST" {
|
||||
req.Header.Set("Content-Type", "plain/text")
|
||||
}
|
||||
var resp *http.Response
|
||||
protocol := c.endpointURL.Scheme
|
||||
address := c.endpointURL.Path
|
||||
if protocol == "unix" {
|
||||
dial, err := net.Dial(protocol, address)
|
||||
if err != nil {
|
||||
return nil, -1, err
|
||||
}
|
||||
clientconn := httputil.NewClientConn(dial, nil)
|
||||
resp, err = clientconn.Do(req)
|
||||
defer clientconn.Close()
|
||||
} else {
|
||||
resp, err = c.client.Do(req)
|
||||
}
|
||||
if err != nil {
|
||||
if strings.Contains(err.Error(), "connection refused") {
|
||||
return nil, -1, ErrConnectionRefused
|
||||
}
|
||||
return nil, -1, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, -1, err
|
||||
}
|
||||
if resp.StatusCode < 200 || resp.StatusCode >= 400 {
|
||||
return nil, resp.StatusCode, newError(resp.StatusCode, body)
|
||||
}
|
||||
return body, resp.StatusCode, nil
|
||||
}
|
||||
|
||||
func (c *Client) stream(method, path string, headers map[string]string, in io.Reader, out io.Writer) error {
|
||||
if (method == "POST" || method == "PUT") && in == nil {
|
||||
in = bytes.NewReader(nil)
|
||||
}
|
||||
req, err := http.NewRequest(method, c.getURL(path), in)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
req.Header.Set("User-Agent", userAgent)
|
||||
if method == "POST" {
|
||||
req.Header.Set("Content-Type", "plain/text")
|
||||
}
|
||||
for key, val := range headers {
|
||||
req.Header.Set(key, val)
|
||||
}
|
||||
var resp *http.Response
|
||||
protocol := c.endpointURL.Scheme
|
||||
address := c.endpointURL.Path
|
||||
if out == nil {
|
||||
out = ioutil.Discard
|
||||
}
|
||||
if protocol == "unix" {
|
||||
dial, err := net.Dial(protocol, address)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
clientconn := httputil.NewClientConn(dial, nil)
|
||||
resp, err = clientconn.Do(req)
|
||||
defer clientconn.Close()
|
||||
} else {
|
||||
resp, err = c.client.Do(req)
|
||||
}
|
||||
if err != nil {
|
||||
if strings.Contains(err.Error(), "connection refused") {
|
||||
return ErrConnectionRefused
|
||||
}
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
if resp.StatusCode < 200 || resp.StatusCode >= 400 {
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return newError(resp.StatusCode, body)
|
||||
}
|
||||
if resp.Header.Get("Content-Type") == "application/json" {
|
||||
dec := json.NewDecoder(resp.Body)
|
||||
for {
|
||||
var m jsonMessage
|
||||
if err := dec.Decode(&m); err == io.EOF {
|
||||
break
|
||||
} else if err != nil {
|
||||
return err
|
||||
}
|
||||
if m.Stream != "" {
|
||||
fmt.Fprint(out, m.Stream)
|
||||
} else if m.Progress != "" {
|
||||
fmt.Fprintf(out, "%s %s\r", m.Status, m.Progress)
|
||||
} else if m.Error != "" {
|
||||
return errors.New(m.Error)
|
||||
}
|
||||
if m.Status != "" {
|
||||
fmt.Fprintln(out, m.Status)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if _, err := io.Copy(out, resp.Body); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Client) hijack(method, path string, success chan struct{}, in io.Reader, errStream io.Writer, out io.Writer) error {
|
||||
req, err := http.NewRequest(method, c.getURL(path), nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
req.Header.Set("Content-Type", "plain/text")
|
||||
protocol := c.endpointURL.Scheme
|
||||
address := c.endpointURL.Path
|
||||
if protocol != "unix" {
|
||||
protocol = "tcp"
|
||||
address = c.endpointURL.Host
|
||||
}
|
||||
dial, err := net.Dial(protocol, address)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer dial.Close()
|
||||
clientconn := httputil.NewClientConn(dial, nil)
|
||||
clientconn.Do(req)
|
||||
if success != nil {
|
||||
success <- struct{}{}
|
||||
<-success
|
||||
}
|
||||
rwc, br := clientconn.Hijack()
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(2)
|
||||
errs := make(chan error, 2)
|
||||
go func() {
|
||||
var err error
|
||||
if in != nil {
|
||||
_, err = io.Copy(out, br)
|
||||
} else {
|
||||
_, err = utils.StdCopy(out, errStream, br)
|
||||
}
|
||||
errs <- err
|
||||
wg.Done()
|
||||
}()
|
||||
go func() {
|
||||
var err error
|
||||
if in != nil {
|
||||
_, err = io.Copy(rwc, in)
|
||||
}
|
||||
rwc.(interface {
|
||||
CloseWrite() error
|
||||
}).CloseWrite()
|
||||
errs <- err
|
||||
wg.Done()
|
||||
}()
|
||||
wg.Wait()
|
||||
close(errs)
|
||||
if err := <-errs; err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Client) getURL(path string) string {
|
||||
urlStr := strings.TrimRight(c.endpointURL.String(), "/")
|
||||
if c.endpointURL.Scheme == "unix" {
|
||||
urlStr = ""
|
||||
}
|
||||
return fmt.Sprintf("%s%s", urlStr, path)
|
||||
}
|
||||
|
||||
type jsonMessage struct {
|
||||
Status string `json:"status,omitempty"`
|
||||
Progress string `json:"progress,omitempty"`
|
||||
Error string `json:"error,omitempty"`
|
||||
Stream string `json:"stream,omitempty"`
|
||||
}
|
||||
|
||||
func queryString(opts interface{}) string {
|
||||
if opts == nil {
|
||||
return ""
|
||||
}
|
||||
value := reflect.ValueOf(opts)
|
||||
if value.Kind() == reflect.Ptr {
|
||||
value = value.Elem()
|
||||
}
|
||||
if value.Kind() != reflect.Struct {
|
||||
return ""
|
||||
}
|
||||
items := url.Values(map[string][]string{})
|
||||
for i := 0; i < value.NumField(); i++ {
|
||||
field := value.Type().Field(i)
|
||||
if field.PkgPath != "" {
|
||||
continue
|
||||
}
|
||||
key := field.Tag.Get("qs")
|
||||
if key == "" {
|
||||
key = strings.ToLower(field.Name)
|
||||
} else if key == "-" {
|
||||
continue
|
||||
}
|
||||
v := value.Field(i)
|
||||
switch v.Kind() {
|
||||
case reflect.Bool:
|
||||
if v.Bool() {
|
||||
items.Add(key, "1")
|
||||
}
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
if v.Int() > 0 {
|
||||
items.Add(key, strconv.FormatInt(v.Int(), 10))
|
||||
}
|
||||
case reflect.Float32, reflect.Float64:
|
||||
if v.Float() > 0 {
|
||||
items.Add(key, strconv.FormatFloat(v.Float(), 'f', -1, 64))
|
||||
}
|
||||
case reflect.String:
|
||||
if v.String() != "" {
|
||||
items.Add(key, v.String())
|
||||
}
|
||||
case reflect.Ptr:
|
||||
if !v.IsNil() {
|
||||
if b, err := json.Marshal(v.Interface()); err == nil {
|
||||
items.Add(key, string(b))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return items.Encode()
|
||||
}
|
||||
|
||||
// Error represents failures in the API. It represents a failure from the API.
|
||||
type Error struct {
|
||||
Status int
|
||||
Message string
|
||||
}
|
||||
|
||||
func newError(status int, body []byte) *Error {
|
||||
return &Error{Status: status, Message: string(body)}
|
||||
}
|
||||
|
||||
func (e *Error) Error() string {
|
||||
return fmt.Sprintf("API error (%d): %s", e.Status, e.Message)
|
||||
}
|
||||
|
||||
func parseEndpoint(endpoint string) (*url.URL, error) {
|
||||
u, err := url.Parse(endpoint)
|
||||
if err != nil {
|
||||
return nil, ErrInvalidEndpoint
|
||||
}
|
||||
if u.Scheme == "tcp" {
|
||||
u.Scheme = "http"
|
||||
}
|
||||
if u.Scheme != "http" && u.Scheme != "https" && u.Scheme != "unix" {
|
||||
return nil, ErrInvalidEndpoint
|
||||
}
|
||||
if u.Scheme != "unix" {
|
||||
_, port, err := net.SplitHostPort(u.Host)
|
||||
if err != nil {
|
||||
if e, ok := err.(*net.AddrError); ok {
|
||||
if e.Err == "missing port in address" {
|
||||
return u, nil
|
||||
}
|
||||
}
|
||||
return nil, ErrInvalidEndpoint
|
||||
}
|
||||
number, err := strconv.ParseInt(port, 10, 64)
|
||||
if err == nil && number > 0 && number < 65536 {
|
||||
return u, nil
|
||||
}
|
||||
} else {
|
||||
return u, nil // we don't need port when using a unix socket
|
||||
}
|
||||
return nil, ErrInvalidEndpoint
|
||||
}
|
161
third_party/github.com/fsouza/go-dockerclient/client_test.go
vendored
Normal file
161
third_party/github.com/fsouza/go-dockerclient/client_test.go
vendored
Normal file
@@ -0,0 +1,161 @@
|
||||
// Copyright 2014 go-dockerclient authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package docker
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestNewAPIClient(t *testing.T) {
|
||||
endpoint := "http://localhost:4243"
|
||||
client, err := NewClient(endpoint)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if client.endpoint != endpoint {
|
||||
t.Errorf("Expected endpoint %s. Got %s.", endpoint, client.endpoint)
|
||||
}
|
||||
if client.client != http.DefaultClient {
|
||||
t.Errorf("Expected http.Client %#v. Got %#v.", http.DefaultClient, client.client)
|
||||
}
|
||||
|
||||
// test unix socket endpoints
|
||||
endpoint = "unix:///var/run/docker.sock"
|
||||
client, err = NewClient(endpoint)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if client.endpoint != endpoint {
|
||||
t.Errorf("Expected endpoint %s. Got %s.", endpoint, client.endpoint)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestNewClientInvalidEndpoint(t *testing.T) {
|
||||
cases := []string{
|
||||
"htp://localhost:3243", "http://localhost:a", "localhost:8080",
|
||||
"", "localhost", "http://localhost:8080:8383", "http://localhost:65536",
|
||||
"https://localhost:-20",
|
||||
}
|
||||
for _, c := range cases {
|
||||
client, err := NewClient(c)
|
||||
if client != nil {
|
||||
t.Errorf("Want <nil> client for invalid endpoint, got %#v.", client)
|
||||
}
|
||||
if !reflect.DeepEqual(err, ErrInvalidEndpoint) {
|
||||
t.Errorf("NewClient(%q): Got invalid error for invalid endpoint. Want %#v. Got %#v.", c, ErrInvalidEndpoint, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetURL(t *testing.T) {
|
||||
var tests = []struct {
|
||||
endpoint string
|
||||
path string
|
||||
expected string
|
||||
}{
|
||||
{"http://localhost:4243/", "/", "http://localhost:4243/"},
|
||||
{"http://localhost:4243", "/", "http://localhost:4243/"},
|
||||
{"http://localhost:4243", "/containers/ps", "http://localhost:4243/containers/ps"},
|
||||
{"tcp://localhost:4243", "/containers/ps", "http://localhost:4243/containers/ps"},
|
||||
{"http://localhost:4243/////", "/", "http://localhost:4243/"},
|
||||
{"unix:///var/run/docker.socket", "/containers", "/containers"},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
client, _ := NewClient(tt.endpoint)
|
||||
client.endpoint = tt.endpoint
|
||||
got := client.getURL(tt.path)
|
||||
if got != tt.expected {
|
||||
t.Errorf("getURL(%q): Got %s. Want %s.", tt.path, got, tt.expected)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestError(t *testing.T) {
|
||||
err := newError(400, []byte("bad parameter"))
|
||||
expected := Error{Status: 400, Message: "bad parameter"}
|
||||
if !reflect.DeepEqual(expected, *err) {
|
||||
t.Errorf("Wrong error type. Want %#v. Got %#v.", expected, *err)
|
||||
}
|
||||
message := "API error (400): bad parameter"
|
||||
if err.Error() != message {
|
||||
t.Errorf("Wrong error message. Want %q. Got %q.", message, err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func TestQueryString(t *testing.T) {
|
||||
v := float32(2.4)
|
||||
f32QueryString := fmt.Sprintf("w=%s&x=10&y=10.35", strconv.FormatFloat(float64(v), 'f', -1, 64))
|
||||
jsonPerson := url.QueryEscape(`{"Name":"gopher","age":4}`)
|
||||
var tests = []struct {
|
||||
input interface{}
|
||||
want string
|
||||
}{
|
||||
{&ListContainersOptions{All: true}, "all=1"},
|
||||
{ListContainersOptions{All: true}, "all=1"},
|
||||
{ListContainersOptions{Before: "something"}, "before=something"},
|
||||
{ListContainersOptions{Before: "something", Since: "other"}, "before=something&since=other"},
|
||||
{dumb{X: 10, Y: 10.35000}, "x=10&y=10.35"},
|
||||
{dumb{W: v, X: 10, Y: 10.35000}, f32QueryString},
|
||||
{dumb{X: 10, Y: 10.35000, Z: 10}, "x=10&y=10.35&zee=10"},
|
||||
{dumb{v: 4, X: 10, Y: 10.35000}, "x=10&y=10.35"},
|
||||
{dumb{T: 10, Y: 10.35000}, "y=10.35"},
|
||||
{dumb{Person: &person{Name: "gopher", Age: 4}}, "p=" + jsonPerson},
|
||||
{nil, ""},
|
||||
{10, ""},
|
||||
{"not_a_struct", ""},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
got := queryString(tt.input)
|
||||
if got != tt.want {
|
||||
t.Errorf("queryString(%v). Want %q. Got %q.", tt.input, tt.want, got)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type FakeRoundTripper struct {
|
||||
message string
|
||||
status int
|
||||
requests []*http.Request
|
||||
}
|
||||
|
||||
func (rt *FakeRoundTripper) RoundTrip(r *http.Request) (*http.Response, error) {
|
||||
body := strings.NewReader(rt.message)
|
||||
rt.requests = append(rt.requests, r)
|
||||
return &http.Response{
|
||||
StatusCode: rt.status,
|
||||
Body: ioutil.NopCloser(body),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (rt *FakeRoundTripper) Reset() {
|
||||
rt.requests = nil
|
||||
}
|
||||
|
||||
type person struct {
|
||||
Name string
|
||||
Age int `json:"age"`
|
||||
}
|
||||
|
||||
type dumb struct {
|
||||
T int `qs:"-"`
|
||||
v int
|
||||
W float32
|
||||
X int
|
||||
Y float64
|
||||
Z int `qs:"zee"`
|
||||
Person *person `qs:"p"`
|
||||
}
|
||||
|
||||
type fakeEndpointURL struct {
|
||||
Scheme string
|
||||
}
|
583
third_party/github.com/fsouza/go-dockerclient/container.go
vendored
Normal file
583
third_party/github.com/fsouza/go-dockerclient/container.go
vendored
Normal file
@@ -0,0 +1,583 @@
|
||||
// Copyright 2014 go-dockerclient authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package docker
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// ListContainersOptions specify parameters to the ListContainers function.
|
||||
//
|
||||
// See http://goo.gl/QpCnDN for more details.
|
||||
type ListContainersOptions struct {
|
||||
All bool
|
||||
Size bool
|
||||
Limit int
|
||||
Since string
|
||||
Before string
|
||||
}
|
||||
|
||||
type APIPort struct {
|
||||
PrivatePort int64
|
||||
PublicPort int64
|
||||
Type string
|
||||
IP string
|
||||
}
|
||||
|
||||
// APIContainers represents a container.
|
||||
//
|
||||
// See http://goo.gl/QeFH7U for more details.
|
||||
type APIContainers struct {
|
||||
ID string `json:"Id"`
|
||||
Image string
|
||||
Command string
|
||||
Created int64
|
||||
Status string
|
||||
Ports []APIPort
|
||||
SizeRw int64
|
||||
SizeRootFs int64
|
||||
Names []string
|
||||
}
|
||||
|
||||
// ListContainers returns a slice of containers matching the given criteria.
|
||||
//
|
||||
// See http://goo.gl/QpCnDN for more details.
|
||||
func (c *Client) ListContainers(opts ListContainersOptions) ([]APIContainers, error) {
|
||||
path := "/containers/json?" + queryString(opts)
|
||||
body, _, err := c.do("GET", path, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var containers []APIContainers
|
||||
err = json.Unmarshal(body, &containers)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return containers, nil
|
||||
}
|
||||
|
||||
// Port represents the port number and the protocol, in the form
|
||||
// <number>/<protocol>. For example: 80/tcp.
|
||||
type Port string
|
||||
|
||||
// Port returns the number of the port.
|
||||
func (p Port) Port() string {
|
||||
return strings.Split(string(p), "/")[0]
|
||||
}
|
||||
|
||||
// Proto returns the name of the protocol.
|
||||
func (p Port) Proto() string {
|
||||
parts := strings.Split(string(p), "/")
|
||||
if len(parts) == 1 {
|
||||
return "tcp"
|
||||
}
|
||||
return parts[1]
|
||||
}
|
||||
|
||||
// State represents the state of a container.
|
||||
type State struct {
|
||||
sync.RWMutex
|
||||
Running bool
|
||||
Pid int
|
||||
ExitCode int
|
||||
StartedAt time.Time
|
||||
FinishedAt time.Time
|
||||
Ghost bool
|
||||
}
|
||||
|
||||
// String returns the string representation of a state.
|
||||
func (s *State) String() string {
|
||||
s.RLock()
|
||||
defer s.RUnlock()
|
||||
if s.Running {
|
||||
if s.Ghost {
|
||||
return "Ghost"
|
||||
}
|
||||
return fmt.Sprintf("Up %s", time.Now().UTC().Sub(s.StartedAt))
|
||||
}
|
||||
return fmt.Sprintf("Exit %d", s.ExitCode)
|
||||
}
|
||||
|
||||
type PortBinding struct {
|
||||
HostIp string
|
||||
HostPort string
|
||||
}
|
||||
|
||||
type PortMapping map[string]string
|
||||
|
||||
type NetworkSettings struct {
|
||||
IPAddress string
|
||||
IPPrefixLen int
|
||||
Gateway string
|
||||
Bridge string
|
||||
PortMapping map[string]PortMapping
|
||||
Ports map[Port][]PortBinding
|
||||
}
|
||||
|
||||
func (settings *NetworkSettings) PortMappingAPI() []APIPort {
|
||||
var mapping []APIPort
|
||||
for port, bindings := range settings.Ports {
|
||||
p, _ := parsePort(port.Port())
|
||||
if len(bindings) == 0 {
|
||||
mapping = append(mapping, APIPort{
|
||||
PublicPort: int64(p),
|
||||
Type: port.Proto(),
|
||||
})
|
||||
continue
|
||||
}
|
||||
for _, binding := range bindings {
|
||||
p, _ := parsePort(port.Port())
|
||||
h, _ := parsePort(binding.HostPort)
|
||||
mapping = append(mapping, APIPort{
|
||||
PrivatePort: int64(p),
|
||||
PublicPort: int64(h),
|
||||
Type: port.Proto(),
|
||||
IP: binding.HostIp,
|
||||
})
|
||||
}
|
||||
}
|
||||
return mapping
|
||||
}
|
||||
|
||||
func parsePort(rawPort string) (int, error) {
|
||||
port, err := strconv.ParseUint(rawPort, 10, 16)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return int(port), nil
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
Hostname string
|
||||
Domainname string
|
||||
User string
|
||||
Memory int64
|
||||
MemorySwap int64
|
||||
CpuShares int64
|
||||
AttachStdin bool
|
||||
AttachStdout bool
|
||||
AttachStderr bool
|
||||
PortSpecs []string
|
||||
ExposedPorts map[Port]struct{}
|
||||
Tty bool
|
||||
OpenStdin bool
|
||||
StdinOnce bool
|
||||
Env []string
|
||||
Cmd []string
|
||||
Dns []string // For Docker API v1.9 and below only
|
||||
Image string
|
||||
Volumes map[string]struct{}
|
||||
VolumesFrom string
|
||||
WorkingDir string
|
||||
Entrypoint []string
|
||||
NetworkDisabled bool
|
||||
}
|
||||
|
||||
type Container struct {
|
||||
ID string
|
||||
|
||||
Created time.Time
|
||||
|
||||
Path string
|
||||
Args []string
|
||||
|
||||
Config *Config
|
||||
State State
|
||||
Image string
|
||||
|
||||
NetworkSettings *NetworkSettings
|
||||
|
||||
SysInitPath string
|
||||
ResolvConfPath string
|
||||
HostnamePath string
|
||||
HostsPath string
|
||||
Name string
|
||||
Driver string
|
||||
|
||||
Volumes map[string]string
|
||||
VolumesRW map[string]bool
|
||||
HostConfig *HostConfig
|
||||
}
|
||||
|
||||
// InspectContainer returns information about a container by its ID.
|
||||
//
|
||||
// See http://goo.gl/2o52Sx for more details.
|
||||
func (c *Client) InspectContainer(id string) (*Container, error) {
|
||||
path := "/containers/" + id + "/json"
|
||||
body, status, err := c.do("GET", path, nil)
|
||||
if status == http.StatusNotFound {
|
||||
return nil, &NoSuchContainer{ID: id}
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var container Container
|
||||
err = json.Unmarshal(body, &container)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &container, nil
|
||||
}
|
||||
|
||||
// ContainerChanges returns changes in the filesystem of the given container.
|
||||
//
|
||||
// See http://goo.gl/DpGyzK for more details.
|
||||
func (c *Client) ContainerChanges(id string) ([]Change, error) {
|
||||
path := "/containers/" + id + "/changes"
|
||||
body, status, err := c.do("GET", path, nil)
|
||||
if status == http.StatusNotFound {
|
||||
return nil, &NoSuchContainer{ID: id}
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var changes []Change
|
||||
err = json.Unmarshal(body, &changes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return changes, nil
|
||||
}
|
||||
|
||||
// CreateContainerOptions specify parameters to the CreateContainer function.
|
||||
//
|
||||
// See http://goo.gl/WPPYtB for more details.
|
||||
type CreateContainerOptions struct {
|
||||
Name string
|
||||
Config *Config `qs:"-"`
|
||||
}
|
||||
|
||||
// CreateContainer creates a new container, returning the container instance,
|
||||
// or an error in case of failure.
|
||||
//
|
||||
// See http://goo.gl/tjihUc for more details.
|
||||
func (c *Client) CreateContainer(opts CreateContainerOptions) (*Container, error) {
|
||||
path := "/containers/create?" + queryString(opts)
|
||||
body, status, err := c.do("POST", path, opts.Config)
|
||||
if status == http.StatusNotFound {
|
||||
return nil, ErrNoSuchImage
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var container Container
|
||||
err = json.Unmarshal(body, &container)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
container.Name = opts.Name
|
||||
|
||||
return &container, nil
|
||||
}
|
||||
|
||||
type KeyValuePair struct {
|
||||
Key string
|
||||
Value string
|
||||
}
|
||||
|
||||
type HostConfig struct {
|
||||
Binds []string
|
||||
ContainerIDFile string
|
||||
LxcConf []KeyValuePair
|
||||
Privileged bool
|
||||
PortBindings map[Port][]PortBinding
|
||||
Links []string
|
||||
PublishAllPorts bool
|
||||
Dns []string // For Docker API v1.10 and above only
|
||||
}
|
||||
|
||||
// StartContainer starts a container, returning an errror in case of failure.
|
||||
//
|
||||
// See http://goo.gl/y5GZlE for more details.
|
||||
func (c *Client) StartContainer(id string, hostConfig *HostConfig) error {
|
||||
if hostConfig == nil {
|
||||
hostConfig = &HostConfig{}
|
||||
}
|
||||
path := "/containers/" + id + "/start"
|
||||
_, status, err := c.do("POST", path, hostConfig)
|
||||
if status == http.StatusNotFound {
|
||||
return &NoSuchContainer{ID: id}
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// StopContainer stops a container, killing it after the given timeout (in
|
||||
// seconds).
|
||||
//
|
||||
// See http://goo.gl/X2mj8t for more details.
|
||||
func (c *Client) StopContainer(id string, timeout uint) error {
|
||||
path := fmt.Sprintf("/containers/%s/stop?t=%d", id, timeout)
|
||||
_, status, err := c.do("POST", path, nil)
|
||||
if status == http.StatusNotFound {
|
||||
return &NoSuchContainer{ID: id}
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// RestartContainer stops a container, killing it after the given timeout (in
|
||||
// seconds), during the stop process.
|
||||
//
|
||||
// See http://goo.gl/zms73Z for more details.
|
||||
func (c *Client) RestartContainer(id string, timeout uint) error {
|
||||
path := fmt.Sprintf("/containers/%s/restart?t=%d", id, timeout)
|
||||
_, status, err := c.do("POST", path, nil)
|
||||
if status == http.StatusNotFound {
|
||||
return &NoSuchContainer{ID: id}
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// KillContainerOptions represents the set of options that can be used in a
|
||||
// call to KillContainer.
|
||||
type KillContainerOptions struct {
|
||||
// The ID of the container.
|
||||
ID string `qs:"-"`
|
||||
|
||||
// The signal to send to the container. When omitted, Docker server
|
||||
// will assume SIGKILL.
|
||||
Signal Signal
|
||||
}
|
||||
|
||||
// KillContainer kills a container, returning an error in case of failure.
|
||||
//
|
||||
// See http://goo.gl/DPbbBy for more details.
|
||||
func (c *Client) KillContainer(opts KillContainerOptions) error {
|
||||
path := "/containers/" + opts.ID + "/kill" + "?" + queryString(opts)
|
||||
_, status, err := c.do("POST", path, nil)
|
||||
if status == http.StatusNotFound {
|
||||
return &NoSuchContainer{ID: opts.ID}
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// RemoveContainerOptions encapsulates options to remove a container.
|
||||
type RemoveContainerOptions struct {
|
||||
// The ID of the container.
|
||||
ID string `qs:"-"`
|
||||
|
||||
// A flag that indicates whether Docker should remove the volumes
|
||||
// associated to the container.
|
||||
RemoveVolumes bool `qs:"v"`
|
||||
|
||||
// A flag that indicates whether Docker should remove the container
|
||||
// even if it is currently running.
|
||||
Force bool
|
||||
}
|
||||
|
||||
// RemoveContainer removes a container, returning an error in case of failure.
|
||||
//
|
||||
// See http://goo.gl/PBvGdU for more details.
|
||||
func (c *Client) RemoveContainer(opts RemoveContainerOptions) error {
|
||||
path := "/containers/" + opts.ID + "?" + queryString(opts)
|
||||
_, status, err := c.do("DELETE", path, nil)
|
||||
if status == http.StatusNotFound {
|
||||
return &NoSuchContainer{ID: opts.ID}
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// CopyFromContainerOptions is the set of options that can be used when copying
|
||||
// files or folders from a container.
|
||||
//
|
||||
// See http://goo.gl/mnxRMl for more details.
|
||||
type CopyFromContainerOptions struct {
|
||||
OutputStream io.Writer `json:"-"`
|
||||
Container string `json:"-"`
|
||||
Resource string
|
||||
}
|
||||
|
||||
// CopyFromContainer copy files or folders from a container, using a given
|
||||
// resource.
|
||||
//
|
||||
// See http://goo.gl/mnxRMl for more details.
|
||||
func (c *Client) CopyFromContainer(opts CopyFromContainerOptions) error {
|
||||
if opts.Container == "" {
|
||||
return &NoSuchContainer{ID: opts.Container}
|
||||
}
|
||||
url := fmt.Sprintf("/containers/%s/copy", opts.Container)
|
||||
body, status, err := c.do("POST", url, opts)
|
||||
if status == http.StatusNotFound {
|
||||
return &NoSuchContainer{ID: opts.Container}
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
io.Copy(opts.OutputStream, bytes.NewBuffer(body))
|
||||
return nil
|
||||
}
|
||||
|
||||
// WaitContainer blocks until the given container stops, return the exit code
|
||||
// of the container status.
|
||||
//
|
||||
// See http://goo.gl/gnHJL2 for more details.
|
||||
func (c *Client) WaitContainer(id string) (int, error) {
|
||||
body, status, err := c.do("POST", "/containers/"+id+"/wait", nil)
|
||||
if status == http.StatusNotFound {
|
||||
return 0, &NoSuchContainer{ID: id}
|
||||
}
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
var r struct{ StatusCode int }
|
||||
err = json.Unmarshal(body, &r)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return r.StatusCode, nil
|
||||
}
|
||||
|
||||
// CommitContainerOptions aggregates parameters to the CommitContainer method.
|
||||
//
|
||||
// See http://goo.gl/628gxm for more details.
|
||||
type CommitContainerOptions struct {
|
||||
Container string
|
||||
Repository string `qs:"repo"`
|
||||
Tag string
|
||||
Message string `qs:"m"`
|
||||
Author string
|
||||
Run *Config `qs:"-"`
|
||||
}
|
||||
|
||||
type Image struct {
|
||||
ID string `json:"id"`
|
||||
Parent string `json:"parent,omitempty"`
|
||||
Comment string `json:"comment,omitempty"`
|
||||
Created time.Time `json:"created"`
|
||||
Container string `json:"container,omitempty"`
|
||||
ContainerConfig Config `json:"container_config,omitempty"`
|
||||
DockerVersion string `json:"docker_version,omitempty"`
|
||||
Author string `json:"author,omitempty"`
|
||||
Config *Config `json:"config,omitempty"`
|
||||
Architecture string `json:"architecture,omitempty"`
|
||||
Size int64
|
||||
}
|
||||
|
||||
// CommitContainer creates a new image from a container's changes.
|
||||
//
|
||||
// See http://goo.gl/628gxm for more details.
|
||||
func (c *Client) CommitContainer(opts CommitContainerOptions) (*Image, error) {
|
||||
path := "/commit?" + queryString(opts)
|
||||
body, status, err := c.do("POST", path, opts.Run)
|
||||
if status == http.StatusNotFound {
|
||||
return nil, &NoSuchContainer{ID: opts.Container}
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var image Image
|
||||
err = json.Unmarshal(body, &image)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &image, nil
|
||||
}
|
||||
|
||||
// AttachToContainerOptions is the set of options that can be used when
|
||||
// attaching to a container.
|
||||
//
|
||||
// See http://goo.gl/oPzcqH for more details.
|
||||
type AttachToContainerOptions struct {
|
||||
Container string `qs:"-"`
|
||||
InputStream io.Reader `qs:"-"`
|
||||
OutputStream io.Writer `qs:"-"`
|
||||
ErrorStream io.Writer `qs:"-"`
|
||||
|
||||
// Get container logs, sending it to OutputStream.
|
||||
Logs bool
|
||||
|
||||
// Stream the response?
|
||||
Stream bool
|
||||
|
||||
// Attach to stdin, and use InputFile.
|
||||
Stdin bool
|
||||
|
||||
// Attach to stdout, and use OutputStream.
|
||||
Stdout bool
|
||||
|
||||
// Attach to stderr, and use ErrorStream.
|
||||
Stderr bool
|
||||
|
||||
// If set, after a successful connect, a sentinel will be sent and then the
|
||||
// client will block on receive before continuing.
|
||||
//
|
||||
// It must be an unbuffered channel. Using a buffered channel can lead
|
||||
// to unexpected behavior.
|
||||
Success chan struct{}
|
||||
}
|
||||
|
||||
// AttachToContainer attaches to a container, using the given options.
|
||||
//
|
||||
// See http://goo.gl/oPzcqH for more details.
|
||||
func (c *Client) AttachToContainer(opts AttachToContainerOptions) error {
|
||||
if opts.Container == "" {
|
||||
return &NoSuchContainer{ID: opts.Container}
|
||||
}
|
||||
path := "/containers/" + opts.Container + "/attach?" + queryString(opts)
|
||||
return c.hijack("POST", path, opts.Success, opts.InputStream, opts.ErrorStream, opts.OutputStream)
|
||||
}
|
||||
|
||||
// ResizeContainerTTY resizes the terminal to the given height and width.
|
||||
func (c *Client) ResizeContainerTTY(id string, height, width int) error {
|
||||
params := make(url.Values)
|
||||
params.Set("h", strconv.Itoa(height))
|
||||
params.Set("w", strconv.Itoa(width))
|
||||
_, _, err := c.do("POST", "/containers/"+id+"/resize?"+params.Encode(), nil)
|
||||
return err
|
||||
}
|
||||
|
||||
// ExportContainerOptions is the set of parameters to the ExportContainer
|
||||
// method.
|
||||
//
|
||||
// See http://goo.gl/Lqk0FZ for more details.
|
||||
type ExportContainerOptions struct {
|
||||
ID string
|
||||
OutputStream io.Writer
|
||||
}
|
||||
|
||||
// ExportContainer export the contents of container id as tar archive
|
||||
// and prints the exported contents to stdout.
|
||||
//
|
||||
// See http://goo.gl/Lqk0FZ for more details.
|
||||
func (c *Client) ExportContainer(opts ExportContainerOptions) error {
|
||||
if opts.ID == "" {
|
||||
return NoSuchContainer{ID: opts.ID}
|
||||
}
|
||||
url := fmt.Sprintf("/containers/%s/export", opts.ID)
|
||||
return c.stream("GET", url, nil, nil, opts.OutputStream)
|
||||
}
|
||||
|
||||
// NoSuchContainer is the error returned when a given container does not exist.
|
||||
type NoSuchContainer struct {
|
||||
ID string
|
||||
}
|
||||
|
||||
func (err NoSuchContainer) Error() string {
|
||||
return "No such container: " + err.ID
|
||||
}
|
888
third_party/github.com/fsouza/go-dockerclient/container_test.go
vendored
Normal file
888
third_party/github.com/fsouza/go-dockerclient/container_test.go
vendored
Normal file
@@ -0,0 +1,888 @@
|
||||
// Copyright 2014 go-dockerclient authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package docker
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"net/url"
|
||||
"os"
|
||||
"reflect"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestListContainers(t *testing.T) {
|
||||
jsonContainers := `[
|
||||
{
|
||||
"Id": "8dfafdbc3a40",
|
||||
"Image": "base:latest",
|
||||
"Command": "echo 1",
|
||||
"Created": 1367854155,
|
||||
"Ports":[{"PrivatePort": 2222, "PublicPort": 3333, "Type": "tcp"}],
|
||||
"Status": "Exit 0"
|
||||
},
|
||||
{
|
||||
"Id": "9cd87474be90",
|
||||
"Image": "base:latest",
|
||||
"Command": "echo 222222",
|
||||
"Created": 1367854155,
|
||||
"Ports":[{"PrivatePort": 2222, "PublicPort": 3333, "Type": "tcp"}],
|
||||
"Status": "Exit 0"
|
||||
},
|
||||
{
|
||||
"Id": "3176a2479c92",
|
||||
"Image": "base:latest",
|
||||
"Command": "echo 3333333333333333",
|
||||
"Created": 1367854154,
|
||||
"Ports":[{"PrivatePort": 2221, "PublicPort": 3331, "Type": "tcp"}],
|
||||
"Status": "Exit 0"
|
||||
},
|
||||
{
|
||||
"Id": "4cb07b47f9fb",
|
||||
"Image": "base:latest",
|
||||
"Command": "echo 444444444444444444444444444444444",
|
||||
"Ports":[{"PrivatePort": 2223, "PublicPort": 3332, "Type": "tcp"}],
|
||||
"Created": 1367854152,
|
||||
"Status": "Exit 0"
|
||||
}
|
||||
]`
|
||||
var expected []APIContainers
|
||||
err := json.Unmarshal([]byte(jsonContainers), &expected)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
client := newTestClient(&FakeRoundTripper{message: jsonContainers, status: http.StatusOK})
|
||||
containers, err := client.ListContainers(ListContainersOptions{})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !reflect.DeepEqual(containers, expected) {
|
||||
t.Errorf("ListContainers: Expected %#v. Got %#v.", expected, containers)
|
||||
}
|
||||
}
|
||||
|
||||
func TestListContainersParams(t *testing.T) {
|
||||
var tests = []struct {
|
||||
input ListContainersOptions
|
||||
params map[string][]string
|
||||
}{
|
||||
{ListContainersOptions{}, map[string][]string{}},
|
||||
{ListContainersOptions{All: true}, map[string][]string{"all": {"1"}}},
|
||||
{ListContainersOptions{All: true, Limit: 10}, map[string][]string{"all": {"1"}, "limit": {"10"}}},
|
||||
{
|
||||
ListContainersOptions{All: true, Limit: 10, Since: "adf9983", Before: "abdeef"},
|
||||
map[string][]string{"all": {"1"}, "limit": {"10"}, "since": {"adf9983"}, "before": {"abdeef"}},
|
||||
},
|
||||
}
|
||||
fakeRT := &FakeRoundTripper{message: "[]", status: http.StatusOK}
|
||||
client := newTestClient(fakeRT)
|
||||
u, _ := url.Parse(client.getURL("/containers/json"))
|
||||
for _, tt := range tests {
|
||||
client.ListContainers(tt.input)
|
||||
got := map[string][]string(fakeRT.requests[0].URL.Query())
|
||||
if !reflect.DeepEqual(got, tt.params) {
|
||||
t.Errorf("Expected %#v, got %#v.", tt.params, got)
|
||||
}
|
||||
if path := fakeRT.requests[0].URL.Path; path != u.Path {
|
||||
t.Errorf("Wrong path on request. Want %q. Got %q.", u.Path, path)
|
||||
}
|
||||
if meth := fakeRT.requests[0].Method; meth != "GET" {
|
||||
t.Errorf("Wrong HTTP method. Want GET. Got %s.", meth)
|
||||
}
|
||||
fakeRT.Reset()
|
||||
}
|
||||
}
|
||||
|
||||
func TestListContainersFailure(t *testing.T) {
|
||||
var tests = []struct {
|
||||
status int
|
||||
message string
|
||||
}{
|
||||
{400, "bad parameter"},
|
||||
{500, "internal server error"},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
client := newTestClient(&FakeRoundTripper{message: tt.message, status: tt.status})
|
||||
expected := Error{Status: tt.status, Message: tt.message}
|
||||
containers, err := client.ListContainers(ListContainersOptions{})
|
||||
if !reflect.DeepEqual(expected, *err.(*Error)) {
|
||||
t.Errorf("Wrong error in ListContainers. Want %#v. Got %#v.", expected, err)
|
||||
}
|
||||
if len(containers) > 0 {
|
||||
t.Errorf("ListContainers failure. Expected empty list. Got %#v.", containers)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestInspectContainer(t *testing.T) {
|
||||
jsonContainer := `{
|
||||
"Id": "4fa6e0f0c6786287e131c3852c58a2e01cc697a68231826813597e4994f1d6e2",
|
||||
"Created": "2013-05-07T14:51:42.087658+02:00",
|
||||
"Path": "date",
|
||||
"Args": [],
|
||||
"Config": {
|
||||
"Hostname": "4fa6e0f0c678",
|
||||
"User": "",
|
||||
"Memory": 0,
|
||||
"MemorySwap": 0,
|
||||
"AttachStdin": false,
|
||||
"AttachStdout": true,
|
||||
"AttachStderr": true,
|
||||
"PortSpecs": null,
|
||||
"Tty": false,
|
||||
"OpenStdin": false,
|
||||
"StdinOnce": false,
|
||||
"Env": null,
|
||||
"Cmd": [
|
||||
"date"
|
||||
],
|
||||
"Image": "base",
|
||||
"Volumes": {},
|
||||
"VolumesFrom": ""
|
||||
},
|
||||
"State": {
|
||||
"Running": false,
|
||||
"Pid": 0,
|
||||
"ExitCode": 0,
|
||||
"StartedAt": "2013-05-07T14:51:42.087658+02:00",
|
||||
"Ghost": false
|
||||
},
|
||||
"Image": "b750fe79269d2ec9a3c593ef05b4332b1d1a02a62b4accb2c21d589ff2f5f2dc",
|
||||
"NetworkSettings": {
|
||||
"IpAddress": "",
|
||||
"IpPrefixLen": 0,
|
||||
"Gateway": "",
|
||||
"Bridge": "",
|
||||
"PortMapping": null
|
||||
},
|
||||
"SysInitPath": "/home/kitty/go/src/github.com/dotcloud/docker/bin/docker",
|
||||
"ResolvConfPath": "/etc/resolv.conf",
|
||||
"Volumes": {},
|
||||
"HostConfig": {
|
||||
"Binds": null,
|
||||
"ContainerIDFile": "",
|
||||
"LxcConf": [],
|
||||
"Privileged": false,
|
||||
"PortBindings": {
|
||||
"80/tcp": [
|
||||
{
|
||||
"HostIp": "0.0.0.0",
|
||||
"HostPort": "49153"
|
||||
}
|
||||
]
|
||||
},
|
||||
"Links": null,
|
||||
"PublishAllPorts": false
|
||||
}
|
||||
}`
|
||||
var expected Container
|
||||
err := json.Unmarshal([]byte(jsonContainer), &expected)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
fakeRT := &FakeRoundTripper{message: jsonContainer, status: http.StatusOK}
|
||||
client := newTestClient(fakeRT)
|
||||
id := "4fa6e0f0c678"
|
||||
container, err := client.InspectContainer(id)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !reflect.DeepEqual(*container, expected) {
|
||||
t.Errorf("InspectContainer(%q): Expected %#v. Got %#v.", id, expected, container)
|
||||
}
|
||||
expectedURL, _ := url.Parse(client.getURL("/containers/4fa6e0f0c678/json"))
|
||||
if gotPath := fakeRT.requests[0].URL.Path; gotPath != expectedURL.Path {
|
||||
t.Errorf("InspectContainer(%q): Wrong path in request. Want %q. Got %q.", id, expectedURL.Path, gotPath)
|
||||
}
|
||||
}
|
||||
|
||||
func TestInspectContainerFailure(t *testing.T) {
|
||||
client := newTestClient(&FakeRoundTripper{message: "server error", status: 500})
|
||||
expected := Error{Status: 500, Message: "server error"}
|
||||
container, err := client.InspectContainer("abe033")
|
||||
if container != nil {
|
||||
t.Errorf("InspectContainer: Expected <nil> container, got %#v", container)
|
||||
}
|
||||
if !reflect.DeepEqual(expected, *err.(*Error)) {
|
||||
t.Errorf("InspectContainer: Wrong error information. Want %#v. Got %#v.", expected, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestInspectContainerNotFound(t *testing.T) {
|
||||
client := newTestClient(&FakeRoundTripper{message: "no such container", status: 404})
|
||||
container, err := client.InspectContainer("abe033")
|
||||
if container != nil {
|
||||
t.Errorf("InspectContainer: Expected <nil> container, got %#v", container)
|
||||
}
|
||||
expected := &NoSuchContainer{ID: "abe033"}
|
||||
if !reflect.DeepEqual(err, expected) {
|
||||
t.Errorf("InspectContainer: Wrong error information. Want %#v. Got %#v.", expected, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestContainerChanges(t *testing.T) {
|
||||
jsonChanges := `[
|
||||
{
|
||||
"Path":"/dev",
|
||||
"Kind":0
|
||||
},
|
||||
{
|
||||
"Path":"/dev/kmsg",
|
||||
"Kind":1
|
||||
},
|
||||
{
|
||||
"Path":"/test",
|
||||
"Kind":1
|
||||
}
|
||||
]`
|
||||
var expected []Change
|
||||
err := json.Unmarshal([]byte(jsonChanges), &expected)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
fakeRT := &FakeRoundTripper{message: jsonChanges, status: http.StatusOK}
|
||||
client := newTestClient(fakeRT)
|
||||
id := "4fa6e0f0c678"
|
||||
changes, err := client.ContainerChanges(id)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !reflect.DeepEqual(changes, expected) {
|
||||
t.Errorf("ContainerChanges(%q): Expected %#v. Got %#v.", id, expected, changes)
|
||||
}
|
||||
expectedURL, _ := url.Parse(client.getURL("/containers/4fa6e0f0c678/changes"))
|
||||
if gotPath := fakeRT.requests[0].URL.Path; gotPath != expectedURL.Path {
|
||||
t.Errorf("ContainerChanges(%q): Wrong path in request. Want %q. Got %q.", id, expectedURL.Path, gotPath)
|
||||
}
|
||||
}
|
||||
|
||||
func TestContainerChangesFailure(t *testing.T) {
|
||||
client := newTestClient(&FakeRoundTripper{message: "server error", status: 500})
|
||||
expected := Error{Status: 500, Message: "server error"}
|
||||
changes, err := client.ContainerChanges("abe033")
|
||||
if changes != nil {
|
||||
t.Errorf("ContainerChanges: Expected <nil> changes, got %#v", changes)
|
||||
}
|
||||
if !reflect.DeepEqual(expected, *err.(*Error)) {
|
||||
t.Errorf("ContainerChanges: Wrong error information. Want %#v. Got %#v.", expected, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestContainerChangesNotFound(t *testing.T) {
|
||||
client := newTestClient(&FakeRoundTripper{message: "no such container", status: 404})
|
||||
changes, err := client.ContainerChanges("abe033")
|
||||
if changes != nil {
|
||||
t.Errorf("ContainerChanges: Expected <nil> changes, got %#v", changes)
|
||||
}
|
||||
expected := &NoSuchContainer{ID: "abe033"}
|
||||
if !reflect.DeepEqual(err, expected) {
|
||||
t.Errorf("ContainerChanges: Wrong error information. Want %#v. Got %#v.", expected, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreateContainer(t *testing.T) {
|
||||
jsonContainer := `{
|
||||
"Id": "4fa6e0f0c6786287e131c3852c58a2e01cc697a68231826813597e4994f1d6e2",
|
||||
"Warnings": []
|
||||
}`
|
||||
var expected Container
|
||||
err := json.Unmarshal([]byte(jsonContainer), &expected)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
fakeRT := &FakeRoundTripper{message: jsonContainer, status: http.StatusOK}
|
||||
client := newTestClient(fakeRT)
|
||||
config := Config{AttachStdout: true, AttachStdin: true}
|
||||
opts := CreateContainerOptions{Name: "TestCreateContainer", Config: &config}
|
||||
container, err := client.CreateContainer(opts)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
id := "4fa6e0f0c6786287e131c3852c58a2e01cc697a68231826813597e4994f1d6e2"
|
||||
if container.ID != id {
|
||||
t.Errorf("CreateContainer: wrong ID. Want %q. Got %q.", id, container.ID)
|
||||
}
|
||||
req := fakeRT.requests[0]
|
||||
if req.Method != "POST" {
|
||||
t.Errorf("CreateContainer: wrong HTTP method. Want %q. Got %q.", "POST", req.Method)
|
||||
}
|
||||
expectedURL, _ := url.Parse(client.getURL("/containers/create"))
|
||||
if gotPath := req.URL.Path; gotPath != expectedURL.Path {
|
||||
t.Errorf("CreateContainer: Wrong path in request. Want %q. Got %q.", expectedURL.Path, gotPath)
|
||||
}
|
||||
var gotBody Config
|
||||
err = json.NewDecoder(req.Body).Decode(&gotBody)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreateContainerImageNotFound(t *testing.T) {
|
||||
client := newTestClient(&FakeRoundTripper{message: "No such image", status: http.StatusNotFound})
|
||||
config := Config{AttachStdout: true, AttachStdin: true}
|
||||
container, err := client.CreateContainer(CreateContainerOptions{Config: &config})
|
||||
if container != nil {
|
||||
t.Errorf("CreateContainer: expected <nil> container, got %#v.", container)
|
||||
}
|
||||
if !reflect.DeepEqual(err, ErrNoSuchImage) {
|
||||
t.Errorf("CreateContainer: Wrong error type. Want %#v. Got %#v.", ErrNoSuchImage, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStartContainer(t *testing.T) {
|
||||
fakeRT := &FakeRoundTripper{message: "", status: http.StatusOK}
|
||||
client := newTestClient(fakeRT)
|
||||
id := "4fa6e0f0c6786287e131c3852c58a2e01cc697a68231826813597e4994f1d6e2"
|
||||
err := client.StartContainer(id, &HostConfig{})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
req := fakeRT.requests[0]
|
||||
if req.Method != "POST" {
|
||||
t.Errorf("StartContainer(%q): wrong HTTP method. Want %q. Got %q.", id, "POST", req.Method)
|
||||
}
|
||||
expectedURL, _ := url.Parse(client.getURL("/containers/" + id + "/start"))
|
||||
if gotPath := req.URL.Path; gotPath != expectedURL.Path {
|
||||
t.Errorf("StartContainer(%q): Wrong path in request. Want %q. Got %q.", id, expectedURL.Path, gotPath)
|
||||
}
|
||||
expectedContentType := "application/json"
|
||||
if contentType := req.Header.Get("Content-Type"); contentType != expectedContentType {
|
||||
t.Errorf("StartContainer(%q): Wrong content-type in request. Want %q. Got %q.", id, expectedContentType, contentType)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStartContainerNilHostConfig(t *testing.T) {
|
||||
fakeRT := &FakeRoundTripper{message: "", status: http.StatusOK}
|
||||
client := newTestClient(fakeRT)
|
||||
id := "4fa6e0f0c6786287e131c3852c58a2e01cc697a68231826813597e4994f1d6e2"
|
||||
err := client.StartContainer(id, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
req := fakeRT.requests[0]
|
||||
if req.Method != "POST" {
|
||||
t.Errorf("StartContainer(%q): wrong HTTP method. Want %q. Got %q.", id, "POST", req.Method)
|
||||
}
|
||||
expectedURL, _ := url.Parse(client.getURL("/containers/" + id + "/start"))
|
||||
if gotPath := req.URL.Path; gotPath != expectedURL.Path {
|
||||
t.Errorf("StartContainer(%q): Wrong path in request. Want %q. Got %q.", id, expectedURL.Path, gotPath)
|
||||
}
|
||||
expectedContentType := "application/json"
|
||||
if contentType := req.Header.Get("Content-Type"); contentType != expectedContentType {
|
||||
t.Errorf("StartContainer(%q): Wrong content-type in request. Want %q. Got %q.", id, expectedContentType, contentType)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStartContainerNotFound(t *testing.T) {
|
||||
client := newTestClient(&FakeRoundTripper{message: "no such container", status: http.StatusNotFound})
|
||||
err := client.StartContainer("a2344", &HostConfig{})
|
||||
expected := &NoSuchContainer{ID: "a2344"}
|
||||
if !reflect.DeepEqual(err, expected) {
|
||||
t.Errorf("StartContainer: Wrong error returned. Want %#v. Got %#v.", expected, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStopContainer(t *testing.T) {
|
||||
fakeRT := &FakeRoundTripper{message: "", status: http.StatusNoContent}
|
||||
client := newTestClient(fakeRT)
|
||||
id := "4fa6e0f0c6786287e131c3852c58a2e01cc697a68231826813597e4994f1d6e2"
|
||||
err := client.StopContainer(id, 10)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
req := fakeRT.requests[0]
|
||||
if req.Method != "POST" {
|
||||
t.Errorf("StopContainer(%q, 10): wrong HTTP method. Want %q. Got %q.", id, "POST", req.Method)
|
||||
}
|
||||
expectedURL, _ := url.Parse(client.getURL("/containers/" + id + "/stop"))
|
||||
if gotPath := req.URL.Path; gotPath != expectedURL.Path {
|
||||
t.Errorf("StopContainer(%q, 10): Wrong path in request. Want %q. Got %q.", id, expectedURL.Path, gotPath)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStopContainerNotFound(t *testing.T) {
|
||||
client := newTestClient(&FakeRoundTripper{message: "no such container", status: http.StatusNotFound})
|
||||
err := client.StopContainer("a2334", 10)
|
||||
expected := &NoSuchContainer{ID: "a2334"}
|
||||
if !reflect.DeepEqual(err, expected) {
|
||||
t.Errorf("StopContainer: Wrong error returned. Want %#v. Got %#v.", expected, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRestartContainer(t *testing.T) {
|
||||
fakeRT := &FakeRoundTripper{message: "", status: http.StatusNoContent}
|
||||
client := newTestClient(fakeRT)
|
||||
id := "4fa6e0f0c6786287e131c3852c58a2e01cc697a68231826813597e4994f1d6e2"
|
||||
err := client.RestartContainer(id, 10)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
req := fakeRT.requests[0]
|
||||
if req.Method != "POST" {
|
||||
t.Errorf("RestartContainer(%q, 10): wrong HTTP method. Want %q. Got %q.", id, "POST", req.Method)
|
||||
}
|
||||
expectedURL, _ := url.Parse(client.getURL("/containers/" + id + "/restart"))
|
||||
if gotPath := req.URL.Path; gotPath != expectedURL.Path {
|
||||
t.Errorf("RestartContainer(%q, 10): Wrong path in request. Want %q. Got %q.", id, expectedURL.Path, gotPath)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRestartContainerNotFound(t *testing.T) {
|
||||
client := newTestClient(&FakeRoundTripper{message: "no such container", status: http.StatusNotFound})
|
||||
err := client.RestartContainer("a2334", 10)
|
||||
expected := &NoSuchContainer{ID: "a2334"}
|
||||
if !reflect.DeepEqual(err, expected) {
|
||||
t.Errorf("RestartContainer: Wrong error returned. Want %#v. Got %#v.", expected, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestKillContainer(t *testing.T) {
|
||||
fakeRT := &FakeRoundTripper{message: "", status: http.StatusNoContent}
|
||||
client := newTestClient(fakeRT)
|
||||
id := "4fa6e0f0c6786287e131c3852c58a2e01cc697a68231826813597e4994f1d6e2"
|
||||
err := client.KillContainer(KillContainerOptions{ID: id})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
req := fakeRT.requests[0]
|
||||
if req.Method != "POST" {
|
||||
t.Errorf("KillContainer(%q): wrong HTTP method. Want %q. Got %q.", id, "POST", req.Method)
|
||||
}
|
||||
expectedURL, _ := url.Parse(client.getURL("/containers/" + id + "/kill"))
|
||||
if gotPath := req.URL.Path; gotPath != expectedURL.Path {
|
||||
t.Errorf("KillContainer(%q): Wrong path in request. Want %q. Got %q.", id, expectedURL.Path, gotPath)
|
||||
}
|
||||
}
|
||||
|
||||
func TestKillContainerSignal(t *testing.T) {
|
||||
fakeRT := &FakeRoundTripper{message: "", status: http.StatusNoContent}
|
||||
client := newTestClient(fakeRT)
|
||||
id := "4fa6e0f0c6786287e131c3852c58a2e01cc697a68231826813597e4994f1d6e2"
|
||||
err := client.KillContainer(KillContainerOptions{ID: id, Signal: SIGTERM})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
req := fakeRT.requests[0]
|
||||
if req.Method != "POST" {
|
||||
t.Errorf("KillContainer(%q): wrong HTTP method. Want %q. Got %q.", id, "POST", req.Method)
|
||||
}
|
||||
if signal := req.URL.Query().Get("signal"); signal != "15" {
|
||||
t.Errorf("KillContainer(%q): Wrong query string in request. Want %q. Got %q.", id, "15", signal)
|
||||
}
|
||||
}
|
||||
|
||||
func TestKillContainerNotFound(t *testing.T) {
|
||||
client := newTestClient(&FakeRoundTripper{message: "no such container", status: http.StatusNotFound})
|
||||
err := client.KillContainer(KillContainerOptions{ID: "a2334"})
|
||||
expected := &NoSuchContainer{ID: "a2334"}
|
||||
if !reflect.DeepEqual(err, expected) {
|
||||
t.Errorf("KillContainer: Wrong error returned. Want %#v. Got %#v.", expected, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRemoveContainer(t *testing.T) {
|
||||
fakeRT := &FakeRoundTripper{message: "", status: http.StatusOK}
|
||||
client := newTestClient(fakeRT)
|
||||
id := "4fa6e0f0c6786287e131c3852c58a2e01cc697a68231826813597e4994f1d6e2"
|
||||
opts := RemoveContainerOptions{ID: id}
|
||||
err := client.RemoveContainer(opts)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
req := fakeRT.requests[0]
|
||||
if req.Method != "DELETE" {
|
||||
t.Errorf("RemoveContainer(%q): wrong HTTP method. Want %q. Got %q.", id, "DELETE", req.Method)
|
||||
}
|
||||
expectedURL, _ := url.Parse(client.getURL("/containers/" + id))
|
||||
if gotPath := req.URL.Path; gotPath != expectedURL.Path {
|
||||
t.Errorf("RemoveContainer(%q): Wrong path in request. Want %q. Got %q.", id, expectedURL.Path, gotPath)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRemoveContainerRemoveVolumes(t *testing.T) {
|
||||
fakeRT := &FakeRoundTripper{message: "", status: http.StatusOK}
|
||||
client := newTestClient(fakeRT)
|
||||
id := "4fa6e0f0c6786287e131c3852c58a2e01cc697a68231826813597e4994f1d6e2"
|
||||
opts := RemoveContainerOptions{ID: id, RemoveVolumes: true}
|
||||
err := client.RemoveContainer(opts)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
req := fakeRT.requests[0]
|
||||
params := map[string][]string(req.URL.Query())
|
||||
expected := map[string][]string{"v": {"1"}}
|
||||
if !reflect.DeepEqual(params, expected) {
|
||||
t.Errorf("RemoveContainer(%q): wrong parameters. Want %#v. Got %#v.", id, expected, params)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRemoveContainerNotFound(t *testing.T) {
|
||||
client := newTestClient(&FakeRoundTripper{message: "no such container", status: http.StatusNotFound})
|
||||
err := client.RemoveContainer(RemoveContainerOptions{ID: "a2334"})
|
||||
expected := &NoSuchContainer{ID: "a2334"}
|
||||
if !reflect.DeepEqual(err, expected) {
|
||||
t.Errorf("RemoveContainer: Wrong error returned. Want %#v. Got %#v.", expected, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestResizeContainerTTY(t *testing.T) {
|
||||
fakeRT := &FakeRoundTripper{message: "", status: http.StatusOK}
|
||||
client := newTestClient(fakeRT)
|
||||
id := "4fa6e0f0c6786287e131c3852c58a2e01cc697a68231826813597e4994f1d6e2"
|
||||
err := client.ResizeContainerTTY(id, 40, 80)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
req := fakeRT.requests[0]
|
||||
if req.Method != "POST" {
|
||||
t.Errorf("ResizeContainerTTY(%q): wrong HTTP method. Want %q. Got %q.", id, "POST", req.Method)
|
||||
}
|
||||
expectedURL, _ := url.Parse(client.getURL("/containers/" + id + "/resize"))
|
||||
if gotPath := req.URL.Path; gotPath != expectedURL.Path {
|
||||
t.Errorf("ResizeContainerTTY(%q): Wrong path in request. Want %q. Got %q.", id, expectedURL.Path, gotPath)
|
||||
}
|
||||
got := map[string][]string(req.URL.Query())
|
||||
expectedParams := map[string][]string{
|
||||
"w": {"80"},
|
||||
"h": {"40"},
|
||||
}
|
||||
if !reflect.DeepEqual(got, expectedParams) {
|
||||
t.Errorf("Expected %#v, got %#v.", expectedParams, got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWaitContainer(t *testing.T) {
|
||||
fakeRT := &FakeRoundTripper{message: `{"StatusCode": 56}`, status: http.StatusOK}
|
||||
client := newTestClient(fakeRT)
|
||||
id := "4fa6e0f0c6786287e131c3852c58a2e01cc697a68231826813597e4994f1d6e2"
|
||||
status, err := client.WaitContainer(id)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if status != 56 {
|
||||
t.Errorf("WaitContainer(%q): wrong return. Want 56. Got %d.", id, status)
|
||||
}
|
||||
req := fakeRT.requests[0]
|
||||
if req.Method != "POST" {
|
||||
t.Errorf("WaitContainer(%q): wrong HTTP method. Want %q. Got %q.", id, "POST", req.Method)
|
||||
}
|
||||
expectedURL, _ := url.Parse(client.getURL("/containers/" + id + "/wait"))
|
||||
if gotPath := req.URL.Path; gotPath != expectedURL.Path {
|
||||
t.Errorf("WaitContainer(%q): Wrong path in request. Want %q. Got %q.", id, expectedURL.Path, gotPath)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWaitContainerNotFound(t *testing.T) {
|
||||
client := newTestClient(&FakeRoundTripper{message: "no such container", status: http.StatusNotFound})
|
||||
_, err := client.WaitContainer("a2334")
|
||||
expected := &NoSuchContainer{ID: "a2334"}
|
||||
if !reflect.DeepEqual(err, expected) {
|
||||
t.Errorf("WaitContainer: Wrong error returned. Want %#v. Got %#v.", expected, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCommitContainer(t *testing.T) {
|
||||
response := `{"Id":"596069db4bf5"}`
|
||||
client := newTestClient(&FakeRoundTripper{message: response, status: http.StatusOK})
|
||||
id := "596069db4bf5"
|
||||
image, err := client.CommitContainer(CommitContainerOptions{})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if image.ID != id {
|
||||
t.Errorf("CommitContainer: Wrong image id. Want %q. Got %q.", id, image.ID)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCommitContainerParams(t *testing.T) {
|
||||
cfg := Config{Memory: 67108864}
|
||||
json, _ := json.Marshal(&cfg)
|
||||
var tests = []struct {
|
||||
input CommitContainerOptions
|
||||
params map[string][]string
|
||||
body []byte
|
||||
}{
|
||||
{CommitContainerOptions{}, map[string][]string{}, nil},
|
||||
{CommitContainerOptions{Container: "44c004db4b17"}, map[string][]string{"container": {"44c004db4b17"}}, nil},
|
||||
{
|
||||
CommitContainerOptions{Container: "44c004db4b17", Repository: "tsuru/python", Message: "something"},
|
||||
map[string][]string{"container": {"44c004db4b17"}, "repo": {"tsuru/python"}, "m": {"something"}},
|
||||
nil,
|
||||
},
|
||||
{
|
||||
CommitContainerOptions{Container: "44c004db4b17", Run: &cfg},
|
||||
map[string][]string{"container": {"44c004db4b17"}},
|
||||
json,
|
||||
},
|
||||
}
|
||||
fakeRT := &FakeRoundTripper{message: "[]", status: http.StatusOK}
|
||||
client := newTestClient(fakeRT)
|
||||
u, _ := url.Parse(client.getURL("/commit"))
|
||||
for _, tt := range tests {
|
||||
client.CommitContainer(tt.input)
|
||||
got := map[string][]string(fakeRT.requests[0].URL.Query())
|
||||
if !reflect.DeepEqual(got, tt.params) {
|
||||
t.Errorf("Expected %#v, got %#v.", tt.params, got)
|
||||
}
|
||||
if path := fakeRT.requests[0].URL.Path; path != u.Path {
|
||||
t.Errorf("Wrong path on request. Want %q. Got %q.", u.Path, path)
|
||||
}
|
||||
if meth := fakeRT.requests[0].Method; meth != "POST" {
|
||||
t.Errorf("Wrong HTTP method. Want POST. Got %s.", meth)
|
||||
}
|
||||
if tt.body != nil {
|
||||
if requestBody, err := ioutil.ReadAll(fakeRT.requests[0].Body); err == nil {
|
||||
if bytes.Compare(requestBody, tt.body) != 0 {
|
||||
t.Errorf("Expected body %#v, got %#v", tt.body, requestBody)
|
||||
}
|
||||
} else {
|
||||
t.Errorf("Error reading request body: %#v", err)
|
||||
}
|
||||
}
|
||||
fakeRT.Reset()
|
||||
}
|
||||
}
|
||||
|
||||
func TestCommitContainerFailure(t *testing.T) {
|
||||
client := newTestClient(&FakeRoundTripper{message: "no such container", status: http.StatusInternalServerError})
|
||||
_, err := client.CommitContainer(CommitContainerOptions{})
|
||||
if err == nil {
|
||||
t.Error("Expected non-nil error, got <nil>.")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCommitContainerNotFound(t *testing.T) {
|
||||
client := newTestClient(&FakeRoundTripper{message: "no such container", status: http.StatusNotFound})
|
||||
_, err := client.CommitContainer(CommitContainerOptions{})
|
||||
expected := &NoSuchContainer{ID: ""}
|
||||
if !reflect.DeepEqual(err, expected) {
|
||||
t.Errorf("CommitContainer: Wrong error returned. Want %#v. Got %#v.", expected, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAttachToContainerLogs(t *testing.T) {
|
||||
var req http.Request
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Write([]byte{1, 0, 0, 0, 0, 0, 0, 19})
|
||||
w.Write([]byte("something happened!"))
|
||||
req = *r
|
||||
}))
|
||||
defer server.Close()
|
||||
client, _ := NewClient(server.URL)
|
||||
var buf bytes.Buffer
|
||||
opts := AttachToContainerOptions{
|
||||
Container: "a123456",
|
||||
OutputStream: &buf,
|
||||
Stdout: true,
|
||||
Stderr: true,
|
||||
Logs: true,
|
||||
}
|
||||
err := client.AttachToContainer(opts)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
expected := "something happened!"
|
||||
if buf.String() != expected {
|
||||
t.Errorf("AttachToContainer for logs: wrong output. Want %q. Got %q.", expected, buf.String())
|
||||
}
|
||||
if req.Method != "POST" {
|
||||
t.Errorf("AttachToContainer: wrong HTTP method. Want POST. Got %s.", req.Method)
|
||||
}
|
||||
u, _ := url.Parse(client.getURL("/containers/a123456/attach"))
|
||||
if req.URL.Path != u.Path {
|
||||
t.Errorf("AttachToContainer for logs: wrong HTTP path. Want %q. Got %q.", u.Path, req.URL.Path)
|
||||
}
|
||||
expectedQs := map[string][]string{
|
||||
"logs": {"1"},
|
||||
"stdout": {"1"},
|
||||
"stderr": {"1"},
|
||||
}
|
||||
got := map[string][]string(req.URL.Query())
|
||||
if !reflect.DeepEqual(got, expectedQs) {
|
||||
t.Errorf("AttachToContainer: wrong query string. Want %#v. Got %#v.", expectedQs, got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAttachToContainer(t *testing.T) {
|
||||
var reader = strings.NewReader("send value")
|
||||
var req http.Request
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Write([]byte{1, 0, 0, 0, 0, 0, 0, 5})
|
||||
w.Write([]byte("hello"))
|
||||
req = *r
|
||||
}))
|
||||
defer server.Close()
|
||||
client, _ := NewClient(server.URL)
|
||||
var stdout, stderr bytes.Buffer
|
||||
opts := AttachToContainerOptions{
|
||||
Container: "a123456",
|
||||
OutputStream: &stdout,
|
||||
ErrorStream: &stderr,
|
||||
InputStream: reader,
|
||||
Stdin: true,
|
||||
Stdout: true,
|
||||
Stderr: true,
|
||||
Stream: true,
|
||||
}
|
||||
var err = client.AttachToContainer(opts)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
expected := map[string][]string{
|
||||
"stdin": {"1"},
|
||||
"stdout": {"1"},
|
||||
"stderr": {"1"},
|
||||
"stream": {"1"},
|
||||
}
|
||||
got := map[string][]string(req.URL.Query())
|
||||
if !reflect.DeepEqual(got, expected) {
|
||||
t.Errorf("AttachToContainer: wrong query string. Want %#v. Got %#v.", expected, got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAttachToContainerWithoutContainer(t *testing.T) {
|
||||
var client Client
|
||||
err := client.AttachToContainer(AttachToContainerOptions{})
|
||||
expected := &NoSuchContainer{ID: ""}
|
||||
if !reflect.DeepEqual(err, expected) {
|
||||
t.Errorf("AttachToContainer: wrong error. Want %#v. Got %#v.", expected, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNoSuchContainerError(t *testing.T) {
|
||||
var err error = &NoSuchContainer{ID: "i345"}
|
||||
expected := "No such container: i345"
|
||||
if got := err.Error(); got != expected {
|
||||
t.Errorf("NoSuchContainer: wrong message. Want %q. Got %q.", expected, got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestExportContainer(t *testing.T) {
|
||||
content := "exported container tar content"
|
||||
out := stdoutMock{bytes.NewBufferString(content)}
|
||||
client := newTestClient(&FakeRoundTripper{status: http.StatusOK})
|
||||
opts := ExportContainerOptions{ID: "4fa6e0f0c678", OutputStream: out}
|
||||
err := client.ExportContainer(opts)
|
||||
if err != nil {
|
||||
t.Errorf("ExportContainer: caugh error %#v while exporting container, expected nil", err.Error())
|
||||
}
|
||||
if out.String() != content {
|
||||
t.Errorf("ExportContainer: wrong stdout. Want %#v. Got %#v.", content, out.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestExportContainerViaUnixSocket(t *testing.T) {
|
||||
if runtime.GOOS != "darwin" {
|
||||
t.Skip("skipping test on %q", runtime.GOOS)
|
||||
}
|
||||
content := "exported container tar content"
|
||||
var buf []byte
|
||||
out := bytes.NewBuffer(buf)
|
||||
tempSocket := tempfile("export_socket")
|
||||
defer os.Remove(tempSocket)
|
||||
endpoint := "unix://" + tempSocket
|
||||
u, _ := parseEndpoint(endpoint)
|
||||
client := Client{
|
||||
endpoint: endpoint,
|
||||
endpointURL: u,
|
||||
client: http.DefaultClient,
|
||||
}
|
||||
listening := make(chan string)
|
||||
done := make(chan int)
|
||||
go runStreamConnServer(t, "unix", tempSocket, listening, done)
|
||||
<-listening // wait for server to start
|
||||
opts := ExportContainerOptions{ID: "4fa6e0f0c678", OutputStream: out}
|
||||
err := client.ExportContainer(opts)
|
||||
<-done // make sure server stopped
|
||||
if err != nil {
|
||||
t.Errorf("ExportContainer: caugh error %#v while exporting container, expected nil", err.Error())
|
||||
}
|
||||
if out.String() != content {
|
||||
t.Errorf("ExportContainer: wrong stdout. Want %#v. Got %#v.", content, out.String())
|
||||
}
|
||||
}
|
||||
|
||||
func runStreamConnServer(t *testing.T, network, laddr string, listening chan<- string, done chan<- int) {
|
||||
defer close(done)
|
||||
l, err := net.Listen(network, laddr)
|
||||
if err != nil {
|
||||
t.Errorf("Listen(%q, %q) failed: %v", network, laddr, err)
|
||||
listening <- "<nil>"
|
||||
return
|
||||
}
|
||||
defer l.Close()
|
||||
listening <- l.Addr().String()
|
||||
c, err := l.Accept()
|
||||
if err != nil {
|
||||
t.Logf("Accept failed: %v", err)
|
||||
return
|
||||
}
|
||||
c.Write([]byte("HTTP/1.1 200 OK\n\nexported container tar content"))
|
||||
c.Close()
|
||||
}
|
||||
|
||||
func tempfile(filename string) string {
|
||||
return os.TempDir() + "/" + filename + "." + strconv.Itoa(os.Getpid())
|
||||
}
|
||||
|
||||
func TestExportContainerNoId(t *testing.T) {
|
||||
client := Client{}
|
||||
out := stdoutMock{bytes.NewBufferString("")}
|
||||
err := client.ExportContainer(ExportContainerOptions{OutputStream: out})
|
||||
if err != (NoSuchContainer{}) {
|
||||
t.Errorf("ExportContainer: wrong error. Want %#v. Got %#v.", NoSuchContainer{}, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCopyFromContainer(t *testing.T) {
|
||||
content := "File content"
|
||||
out := stdoutMock{bytes.NewBufferString(content)}
|
||||
client := newTestClient(&FakeRoundTripper{status: http.StatusOK})
|
||||
opts := CopyFromContainerOptions{
|
||||
Container: "a123456",
|
||||
OutputStream: out,
|
||||
}
|
||||
err := client.CopyFromContainer(opts)
|
||||
if err != nil {
|
||||
t.Errorf("CopyFromContainer: caugh error %#v while copying from container, expected nil", err.Error())
|
||||
}
|
||||
if out.String() != content {
|
||||
t.Errorf("CopyFromContainer: wrong stdout. Want %#v. Got %#v.", content, out.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestCopyFromContainerEmptyContainer(t *testing.T) {
|
||||
client := newTestClient(&FakeRoundTripper{status: http.StatusOK})
|
||||
err := client.CopyFromContainer(CopyFromContainerOptions{})
|
||||
_, ok := err.(*NoSuchContainer)
|
||||
if !ok {
|
||||
t.Errorf("CopyFromContainer: invalid error returned. Want NoSuchContainer, got %#v.", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPassingNameOptToCreateContainerReturnsItInContainer(t *testing.T) {
|
||||
jsonContainer := `{
|
||||
"Id": "4fa6e0f0c6786287e131c3852c58a2e01cc697a68231826813597e4994f1d6e2",
|
||||
"Warnings": []
|
||||
}`
|
||||
fakeRT := &FakeRoundTripper{message: jsonContainer, status: http.StatusOK}
|
||||
client := newTestClient(fakeRT)
|
||||
config := Config{AttachStdout: true, AttachStdin: true}
|
||||
opts := CreateContainerOptions{Name: "TestCreateContainer", Config: &config}
|
||||
container, err := client.CreateContainer(opts)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if container.Name != "TestCreateContainer" {
|
||||
t.Errorf("Container name expected to be TestCreateContainer, was %s", container.Name)
|
||||
}
|
||||
}
|
147
third_party/github.com/fsouza/go-dockerclient/engine/engine.go
vendored
Normal file
147
third_party/github.com/fsouza/go-dockerclient/engine/engine.go
vendored
Normal file
@@ -0,0 +1,147 @@
|
||||
// Copyright 2014 Docker authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the DOCKER-LICENSE file.
|
||||
|
||||
package engine
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/fsouza/go-dockerclient/utils"
|
||||
"io"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type Handler func(*Job) Status
|
||||
|
||||
var globalHandlers map[string]Handler
|
||||
|
||||
func init() {
|
||||
globalHandlers = make(map[string]Handler)
|
||||
}
|
||||
|
||||
func Register(name string, handler Handler) error {
|
||||
_, exists := globalHandlers[name]
|
||||
if exists {
|
||||
return fmt.Errorf("Can't overwrite global handler for command %s", name)
|
||||
}
|
||||
globalHandlers[name] = handler
|
||||
return nil
|
||||
}
|
||||
|
||||
// The Engine is the core of Docker.
|
||||
// It acts as a store for *containers*, and allows manipulation of these
|
||||
// containers by executing *jobs*.
|
||||
type Engine struct {
|
||||
root string
|
||||
handlers map[string]Handler
|
||||
hack Hack // data for temporary hackery (see hack.go)
|
||||
id string
|
||||
Stdout io.Writer
|
||||
Stderr io.Writer
|
||||
Stdin io.Reader
|
||||
}
|
||||
|
||||
func (eng *Engine) Root() string {
|
||||
return eng.root
|
||||
}
|
||||
|
||||
func (eng *Engine) Register(name string, handler Handler) error {
|
||||
eng.Logf("Register(%s) (handlers=%v)", name, eng.handlers)
|
||||
_, exists := eng.handlers[name]
|
||||
if exists {
|
||||
return fmt.Errorf("Can't overwrite handler for command %s", name)
|
||||
}
|
||||
eng.handlers[name] = handler
|
||||
return nil
|
||||
}
|
||||
|
||||
// New initializes a new engine managing the directory specified at `root`.
|
||||
// `root` is used to store containers and any other state private to the engine.
|
||||
// Changing the contents of the root without executing a job will cause unspecified
|
||||
// behavior.
|
||||
func New(root string) (*Engine, error) {
|
||||
// Check for unsupported architectures
|
||||
if runtime.GOARCH != "amd64" {
|
||||
return nil, fmt.Errorf("The docker runtime currently only supports amd64 (not %s). This will change in the future. Aborting.", runtime.GOARCH)
|
||||
}
|
||||
// Check for unsupported kernel versions
|
||||
// FIXME: it would be cleaner to not test for specific versions, but rather
|
||||
// test for specific functionalities.
|
||||
// Unfortunately we can't test for the feature "does not cause a kernel panic"
|
||||
// without actually causing a kernel panic, so we need this workaround until
|
||||
// the circumstances of pre-3.8 crashes are clearer.
|
||||
// For details see http://github.com/dotcloud/docker/issues/407
|
||||
if k, err := utils.GetKernelVersion(); err != nil {
|
||||
log.Printf("WARNING: %s\n", err)
|
||||
} else {
|
||||
if utils.CompareKernelVersion(k, &utils.KernelVersionInfo{Kernel: 3, Major: 8, Minor: 0}) < 0 {
|
||||
if os.Getenv("DOCKER_NOWARN_KERNEL_VERSION") == "" {
|
||||
log.Printf("WARNING: You are running linux kernel version %s, which might be unstable running docker. Please upgrade your kernel to 3.8.0.", k.String())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if err := os.MkdirAll(root, 0700); err != nil && !os.IsExist(err) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Docker makes some assumptions about the "absoluteness" of root
|
||||
// ... so let's make sure it has no symlinks
|
||||
if p, err := filepath.Abs(root); err != nil {
|
||||
log.Fatalf("Unable to get absolute root (%s): %s", root, err)
|
||||
} else {
|
||||
root = p
|
||||
}
|
||||
if p, err := filepath.EvalSymlinks(root); err != nil {
|
||||
log.Fatalf("Unable to canonicalize root (%s): %s", root, err)
|
||||
} else {
|
||||
root = p
|
||||
}
|
||||
|
||||
eng := &Engine{
|
||||
root: root,
|
||||
handlers: make(map[string]Handler),
|
||||
id: utils.RandomString(),
|
||||
Stdout: os.Stdout,
|
||||
Stderr: os.Stderr,
|
||||
Stdin: os.Stdin,
|
||||
}
|
||||
// Copy existing global handlers
|
||||
for k, v := range globalHandlers {
|
||||
eng.handlers[k] = v
|
||||
}
|
||||
return eng, nil
|
||||
}
|
||||
|
||||
func (eng *Engine) String() string {
|
||||
return fmt.Sprintf("%s|%s", eng.Root(), eng.id[:8])
|
||||
}
|
||||
|
||||
// Job creates a new job which can later be executed.
|
||||
// This function mimics `Command` from the standard os/exec package.
|
||||
func (eng *Engine) Job(name string, args ...string) *Job {
|
||||
job := &Job{
|
||||
Eng: eng,
|
||||
Name: name,
|
||||
Args: args,
|
||||
Stdin: NewInput(),
|
||||
Stdout: NewOutput(),
|
||||
Stderr: NewOutput(),
|
||||
env: &Env{},
|
||||
}
|
||||
job.Stderr.Add(utils.NopWriteCloser(eng.Stderr))
|
||||
handler, exists := eng.handlers[name]
|
||||
if exists {
|
||||
job.handler = handler
|
||||
}
|
||||
return job
|
||||
}
|
||||
|
||||
func (eng *Engine) Logf(format string, args ...interface{}) (n int, err error) {
|
||||
prefixedFormat := fmt.Sprintf("[%s] %s\n", eng, strings.TrimRight(format, "\n"))
|
||||
return fmt.Fprintf(eng.Stderr, prefixedFormat, args...)
|
||||
}
|
111
third_party/github.com/fsouza/go-dockerclient/engine/engine_test.go
vendored
Normal file
111
third_party/github.com/fsouza/go-dockerclient/engine/engine_test.go
vendored
Normal file
@@ -0,0 +1,111 @@
|
||||
// Copyright 2014 Docker authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the DOCKER-LICENSE file.
|
||||
|
||||
package engine
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestRegister(t *testing.T) {
|
||||
if err := Register("dummy1", nil); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if err := Register("dummy1", nil); err == nil {
|
||||
t.Fatalf("Expecting error, got none")
|
||||
}
|
||||
|
||||
eng := newTestEngine(t)
|
||||
|
||||
//Should fail because global handlers are copied
|
||||
//at the engine creation
|
||||
if err := eng.Register("dummy1", nil); err == nil {
|
||||
t.Fatalf("Expecting error, got none")
|
||||
}
|
||||
|
||||
if err := eng.Register("dummy2", nil); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if err := eng.Register("dummy2", nil); err == nil {
|
||||
t.Fatalf("Expecting error, got none")
|
||||
}
|
||||
}
|
||||
|
||||
func TestJob(t *testing.T) {
|
||||
eng := newTestEngine(t)
|
||||
job1 := eng.Job("dummy1", "--level=awesome")
|
||||
|
||||
if job1.handler != nil {
|
||||
t.Fatalf("job1.handler should be empty")
|
||||
}
|
||||
|
||||
h := func(j *Job) Status {
|
||||
j.Printf("%s\n", j.Name)
|
||||
return 42
|
||||
}
|
||||
|
||||
eng.Register("dummy2", h)
|
||||
job2 := eng.Job("dummy2", "--level=awesome")
|
||||
|
||||
if job2.handler == nil {
|
||||
t.Fatalf("job2.handler shouldn't be nil")
|
||||
}
|
||||
|
||||
if job2.handler(job2) != 42 {
|
||||
t.Fatalf("handler dummy2 was not found in job2")
|
||||
}
|
||||
}
|
||||
|
||||
func TestEngineRoot(t *testing.T) {
|
||||
tmp, err := ioutil.TempDir("", "docker-test-TestEngineCreateDir")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer os.RemoveAll(tmp)
|
||||
dir := path.Join(tmp, "dir")
|
||||
eng, err := New(dir)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if st, err := os.Stat(dir); err != nil {
|
||||
t.Fatal(err)
|
||||
} else if !st.IsDir() {
|
||||
t.Fatalf("engine.New() created something other than a directory at %s", dir)
|
||||
}
|
||||
r := eng.Root()
|
||||
r, _ = filepath.EvalSymlinks(r)
|
||||
dir, _ = filepath.EvalSymlinks(dir)
|
||||
if r != dir {
|
||||
t.Fatalf("Expected: %v\nReceived: %v", dir, r)
|
||||
}
|
||||
}
|
||||
|
||||
func TestEngineString(t *testing.T) {
|
||||
eng1 := newTestEngine(t)
|
||||
defer os.RemoveAll(eng1.Root())
|
||||
eng2 := newTestEngine(t)
|
||||
defer os.RemoveAll(eng2.Root())
|
||||
s1 := eng1.String()
|
||||
s2 := eng2.String()
|
||||
if eng1 == eng2 {
|
||||
t.Fatalf("Different engines should have different names (%v == %v)", s1, s2)
|
||||
}
|
||||
}
|
||||
|
||||
func TestEngineLogf(t *testing.T) {
|
||||
eng := newTestEngine(t)
|
||||
defer os.RemoveAll(eng.Root())
|
||||
input := "Test log line"
|
||||
if n, err := eng.Logf("%s\n", input); err != nil {
|
||||
t.Fatal(err)
|
||||
} else if n < len(input) {
|
||||
t.Fatalf("Test: Logf() should print at least as much as the input\ninput=%d\nprinted=%d", len(input), n)
|
||||
}
|
||||
}
|
238
third_party/github.com/fsouza/go-dockerclient/engine/env.go
vendored
Normal file
238
third_party/github.com/fsouza/go-dockerclient/engine/env.go
vendored
Normal file
@@ -0,0 +1,238 @@
|
||||
// Copyright 2014 Docker authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the DOCKER-LICENSE file.
|
||||
|
||||
package engine
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type Env []string
|
||||
|
||||
func (env *Env) Get(key string) (value string) {
|
||||
// FIXME: use Map()
|
||||
for _, kv := range *env {
|
||||
if strings.Index(kv, "=") == -1 {
|
||||
continue
|
||||
}
|
||||
parts := strings.SplitN(kv, "=", 2)
|
||||
if parts[0] != key {
|
||||
continue
|
||||
}
|
||||
if len(parts) < 2 {
|
||||
value = ""
|
||||
} else {
|
||||
value = parts[1]
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (env *Env) Exists(key string) bool {
|
||||
_, exists := env.Map()[key]
|
||||
return exists
|
||||
}
|
||||
|
||||
func (env *Env) GetBool(key string) (value bool) {
|
||||
s := strings.ToLower(strings.Trim(env.Get(key), " \t"))
|
||||
if s == "" || s == "0" || s == "no" || s == "false" || s == "none" {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (env *Env) SetBool(key string, value bool) {
|
||||
if value {
|
||||
env.Set(key, "1")
|
||||
} else {
|
||||
env.Set(key, "0")
|
||||
}
|
||||
}
|
||||
|
||||
func (env *Env) GetInt(key string) int {
|
||||
return int(env.GetInt64(key))
|
||||
}
|
||||
|
||||
func (env *Env) GetInt64(key string) int64 {
|
||||
s := strings.Trim(env.Get(key), " \t")
|
||||
val, err := strconv.ParseInt(s, 10, 64)
|
||||
if err != nil {
|
||||
return -1
|
||||
}
|
||||
return val
|
||||
}
|
||||
|
||||
func (env *Env) SetInt(key string, value int) {
|
||||
env.Set(key, fmt.Sprintf("%d", value))
|
||||
}
|
||||
|
||||
func (env *Env) SetInt64(key string, value int64) {
|
||||
env.Set(key, fmt.Sprintf("%d", value))
|
||||
}
|
||||
|
||||
// Returns nil if key not found
|
||||
func (env *Env) GetList(key string) []string {
|
||||
sval := env.Get(key)
|
||||
if sval == "" {
|
||||
return nil
|
||||
}
|
||||
l := make([]string, 0, 1)
|
||||
if err := json.Unmarshal([]byte(sval), &l); err != nil {
|
||||
l = append(l, sval)
|
||||
}
|
||||
return l
|
||||
}
|
||||
|
||||
func (env *Env) GetJson(key string, iface interface{}) error {
|
||||
sval := env.Get(key)
|
||||
if sval == "" {
|
||||
return nil
|
||||
}
|
||||
return json.Unmarshal([]byte(sval), iface)
|
||||
}
|
||||
|
||||
func (env *Env) SetJson(key string, value interface{}) error {
|
||||
sval, err := json.Marshal(value)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
env.Set(key, string(sval))
|
||||
return nil
|
||||
}
|
||||
|
||||
func (env *Env) SetList(key string, value []string) error {
|
||||
return env.SetJson(key, value)
|
||||
}
|
||||
|
||||
func (env *Env) Set(key, value string) {
|
||||
*env = append(*env, key+"="+value)
|
||||
}
|
||||
|
||||
func NewDecoder(src io.Reader) *Decoder {
|
||||
return &Decoder{
|
||||
json.NewDecoder(src),
|
||||
}
|
||||
}
|
||||
|
||||
type Decoder struct {
|
||||
*json.Decoder
|
||||
}
|
||||
|
||||
func (decoder *Decoder) Decode() (*Env, error) {
|
||||
m := make(map[string]interface{})
|
||||
if err := decoder.Decoder.Decode(&m); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
env := &Env{}
|
||||
for key, value := range m {
|
||||
env.SetAuto(key, value)
|
||||
}
|
||||
return env, nil
|
||||
}
|
||||
|
||||
// DecodeEnv decodes `src` as a json dictionary, and adds
|
||||
// each decoded key-value pair to the environment.
|
||||
//
|
||||
// If `src` cannot be decoded as a json dictionary, an error
|
||||
// is returned.
|
||||
func (env *Env) Decode(src io.Reader) error {
|
||||
m := make(map[string]interface{})
|
||||
if err := json.NewDecoder(src).Decode(&m); err != nil {
|
||||
return err
|
||||
}
|
||||
for k, v := range m {
|
||||
env.SetAuto(k, v)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (env *Env) SetAuto(k string, v interface{}) {
|
||||
// FIXME: we fix-convert float values to int, because
|
||||
// encoding/json decodes integers to float64, but cannot encode them back.
|
||||
// (See http://golang.org/src/pkg/encoding/json/decode.go#L46)
|
||||
if fval, ok := v.(float64); ok {
|
||||
env.SetInt64(k, int64(fval))
|
||||
} else if sval, ok := v.(string); ok {
|
||||
env.Set(k, sval)
|
||||
} else if val, err := json.Marshal(v); err == nil {
|
||||
env.Set(k, string(val))
|
||||
} else {
|
||||
env.Set(k, fmt.Sprintf("%v", v))
|
||||
}
|
||||
}
|
||||
|
||||
func (env *Env) Encode(dst io.Writer) error {
|
||||
m := make(map[string]interface{})
|
||||
for k, v := range env.Map() {
|
||||
var val interface{}
|
||||
if err := json.Unmarshal([]byte(v), &val); err == nil {
|
||||
// FIXME: we fix-convert float values to int, because
|
||||
// encoding/json decodes integers to float64, but cannot encode them back.
|
||||
// (See http://golang.org/src/pkg/encoding/json/decode.go#L46)
|
||||
if fval, isFloat := val.(float64); isFloat {
|
||||
val = int(fval)
|
||||
}
|
||||
m[k] = val
|
||||
} else {
|
||||
m[k] = v
|
||||
}
|
||||
}
|
||||
if err := json.NewEncoder(dst).Encode(&m); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (env *Env) WriteTo(dst io.Writer) (n int64, err error) {
|
||||
// FIXME: return the number of bytes written to respect io.WriterTo
|
||||
return 0, env.Encode(dst)
|
||||
}
|
||||
|
||||
func (env *Env) Export(dst interface{}) (err error) {
|
||||
defer func() {
|
||||
if err != nil {
|
||||
err = fmt.Errorf("ExportEnv %s", err)
|
||||
}
|
||||
}()
|
||||
var buf bytes.Buffer
|
||||
// step 1: encode/marshal the env to an intermediary json representation
|
||||
if err := env.Encode(&buf); err != nil {
|
||||
return err
|
||||
}
|
||||
// step 2: decode/unmarshal the intermediary json into the destination object
|
||||
if err := json.NewDecoder(&buf).Decode(dst); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (env *Env) Import(src interface{}) (err error) {
|
||||
defer func() {
|
||||
if err != nil {
|
||||
err = fmt.Errorf("ImportEnv: %s", err)
|
||||
}
|
||||
}()
|
||||
var buf bytes.Buffer
|
||||
if err := json.NewEncoder(&buf).Encode(src); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := env.Decode(&buf); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (env *Env) Map() map[string]string {
|
||||
m := make(map[string]string)
|
||||
for _, kv := range *env {
|
||||
parts := strings.SplitN(kv, "=", 2)
|
||||
m[parts[0]] = parts[1]
|
||||
}
|
||||
return m
|
||||
}
|
127
third_party/github.com/fsouza/go-dockerclient/engine/env_test.go
vendored
Normal file
127
third_party/github.com/fsouza/go-dockerclient/engine/env_test.go
vendored
Normal file
@@ -0,0 +1,127 @@
|
||||
// Copyright 2014 Docker authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the DOCKER-LICENSE file.
|
||||
|
||||
package engine
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestNewJob(t *testing.T) {
|
||||
job := mkJob(t, "dummy", "--level=awesome")
|
||||
if job.Name != "dummy" {
|
||||
t.Fatalf("Wrong job name: %s", job.Name)
|
||||
}
|
||||
if len(job.Args) != 1 {
|
||||
t.Fatalf("Wrong number of job arguments: %d", len(job.Args))
|
||||
}
|
||||
if job.Args[0] != "--level=awesome" {
|
||||
t.Fatalf("Wrong job arguments: %s", job.Args[0])
|
||||
}
|
||||
}
|
||||
|
||||
func TestSetenv(t *testing.T) {
|
||||
job := mkJob(t, "dummy")
|
||||
job.Setenv("foo", "bar")
|
||||
if val := job.Getenv("foo"); val != "bar" {
|
||||
t.Fatalf("Getenv returns incorrect value: %s", val)
|
||||
}
|
||||
|
||||
job.Setenv("bar", "")
|
||||
if val := job.Getenv("bar"); val != "" {
|
||||
t.Fatalf("Getenv returns incorrect value: %s", val)
|
||||
}
|
||||
if val := job.Getenv("nonexistent"); val != "" {
|
||||
t.Fatalf("Getenv returns incorrect value: %s", val)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSetenvBool(t *testing.T) {
|
||||
job := mkJob(t, "dummy")
|
||||
job.SetenvBool("foo", true)
|
||||
if val := job.GetenvBool("foo"); !val {
|
||||
t.Fatalf("GetenvBool returns incorrect value: %t", val)
|
||||
}
|
||||
|
||||
job.SetenvBool("bar", false)
|
||||
if val := job.GetenvBool("bar"); val {
|
||||
t.Fatalf("GetenvBool returns incorrect value: %t", val)
|
||||
}
|
||||
|
||||
if val := job.GetenvBool("nonexistent"); val {
|
||||
t.Fatalf("GetenvBool returns incorrect value: %t", val)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSetenvInt(t *testing.T) {
|
||||
job := mkJob(t, "dummy")
|
||||
|
||||
job.SetenvInt("foo", -42)
|
||||
if val := job.GetenvInt("foo"); val != -42 {
|
||||
t.Fatalf("GetenvInt returns incorrect value: %d", val)
|
||||
}
|
||||
|
||||
job.SetenvInt("bar", 42)
|
||||
if val := job.GetenvInt("bar"); val != 42 {
|
||||
t.Fatalf("GetenvInt returns incorrect value: %d", val)
|
||||
}
|
||||
if val := job.GetenvInt("nonexistent"); val != -1 {
|
||||
t.Fatalf("GetenvInt returns incorrect value: %d", val)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSetenvList(t *testing.T) {
|
||||
job := mkJob(t, "dummy")
|
||||
|
||||
job.SetenvList("foo", []string{"bar"})
|
||||
if val := job.GetenvList("foo"); len(val) != 1 || val[0] != "bar" {
|
||||
t.Fatalf("GetenvList returns incorrect value: %v", val)
|
||||
}
|
||||
|
||||
job.SetenvList("bar", nil)
|
||||
if val := job.GetenvList("bar"); val != nil {
|
||||
t.Fatalf("GetenvList returns incorrect value: %v", val)
|
||||
}
|
||||
if val := job.GetenvList("nonexistent"); val != nil {
|
||||
t.Fatalf("GetenvList returns incorrect value: %v", val)
|
||||
}
|
||||
}
|
||||
|
||||
func TestImportEnv(t *testing.T) {
|
||||
type dummy struct {
|
||||
DummyInt int
|
||||
DummyStringArray []string
|
||||
}
|
||||
|
||||
job := mkJob(t, "dummy")
|
||||
if err := job.ImportEnv(&dummy{42, []string{"foo", "bar"}}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
dmy := dummy{}
|
||||
if err := job.ExportEnv(&dmy); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if dmy.DummyInt != 42 {
|
||||
t.Fatalf("Expected 42, got %d", dmy.DummyInt)
|
||||
}
|
||||
|
||||
if len(dmy.DummyStringArray) != 2 || dmy.DummyStringArray[0] != "foo" || dmy.DummyStringArray[1] != "bar" {
|
||||
t.Fatalf("Expected {foo, bar}, got %v", dmy.DummyStringArray)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestEnviron(t *testing.T) {
|
||||
job := mkJob(t, "dummy")
|
||||
job.Setenv("foo", "bar")
|
||||
val, exists := job.Environ()["foo"]
|
||||
if !exists {
|
||||
t.Fatalf("foo not found in the environ")
|
||||
}
|
||||
if val != "bar" {
|
||||
t.Fatalf("bar not found in the environ")
|
||||
}
|
||||
}
|
25
third_party/github.com/fsouza/go-dockerclient/engine/hack.go
vendored
Normal file
25
third_party/github.com/fsouza/go-dockerclient/engine/hack.go
vendored
Normal file
@@ -0,0 +1,25 @@
|
||||
// Copyright 2014 Docker authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the DOCKER-LICENSE file.
|
||||
|
||||
package engine
|
||||
|
||||
type Hack map[string]interface{}
|
||||
|
||||
func (eng *Engine) Hack_GetGlobalVar(key string) interface{} {
|
||||
if eng.hack == nil {
|
||||
return nil
|
||||
}
|
||||
val, exists := eng.hack[key]
|
||||
if !exists {
|
||||
return nil
|
||||
}
|
||||
return val
|
||||
}
|
||||
|
||||
func (eng *Engine) Hack_SetGlobalVar(key string, val interface{}) {
|
||||
if eng.hack == nil {
|
||||
eng.hack = make(Hack)
|
||||
}
|
||||
eng.hack[key] = val
|
||||
}
|
28
third_party/github.com/fsouza/go-dockerclient/engine/helpers_test.go
vendored
Normal file
28
third_party/github.com/fsouza/go-dockerclient/engine/helpers_test.go
vendored
Normal file
@@ -0,0 +1,28 @@
|
||||
// Copyright 2014 Docker authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the DOCKER-LICENSE file.
|
||||
|
||||
package engine
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"testing"
|
||||
)
|
||||
|
||||
var globalTestID string
|
||||
|
||||
func newTestEngine(t *testing.T) *Engine {
|
||||
tmp, err := ioutil.TempDir("", "asd")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
eng, err := New(tmp)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return eng
|
||||
}
|
||||
|
||||
func mkJob(t *testing.T, name string, args ...string) *Job {
|
||||
return newTestEngine(t).Job(name, args...)
|
||||
}
|
44
third_party/github.com/fsouza/go-dockerclient/engine/http.go
vendored
Normal file
44
third_party/github.com/fsouza/go-dockerclient/engine/http.go
vendored
Normal file
@@ -0,0 +1,44 @@
|
||||
// Copyright 2014 Docker authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the DOCKER-LICENSE file.
|
||||
|
||||
package engine
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"path"
|
||||
)
|
||||
|
||||
// ServeHTTP executes a job as specified by the http request `r`, and sends the
|
||||
// result as an http response.
|
||||
// This method allows an Engine instance to be passed as a standard http.Handler interface.
|
||||
//
|
||||
// Note that the protocol used in this methid is a convenience wrapper and is not the canonical
|
||||
// implementation of remote job execution. This is because HTTP/1 does not handle stream multiplexing,
|
||||
// and so cannot differentiate stdout from stderr. Additionally, headers cannot be added to a response
|
||||
// once data has been written to the body, which makes it inconvenient to return metadata such
|
||||
// as the exit status.
|
||||
//
|
||||
func (eng *Engine) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
jobName := path.Base(r.URL.Path)
|
||||
jobArgs, exists := r.URL.Query()["a"]
|
||||
if !exists {
|
||||
jobArgs = []string{}
|
||||
}
|
||||
w.Header().Set("Job-Name", jobName)
|
||||
for _, arg := range jobArgs {
|
||||
w.Header().Add("Job-Args", arg)
|
||||
}
|
||||
job := eng.Job(jobName, jobArgs...)
|
||||
job.Stdout.Add(w)
|
||||
job.Stderr.Add(w)
|
||||
// FIXME: distinguish job status from engine error in Run()
|
||||
// The former should be passed as a special header, the former
|
||||
// should cause a 500 status
|
||||
w.WriteHeader(http.StatusOK)
|
||||
// The exit status cannot be sent reliably with HTTP1, because headers
|
||||
// can only be sent before the body.
|
||||
// (we could possibly use http footers via chunked encoding, but I couldn't find
|
||||
// how to use them in net/http)
|
||||
job.Run()
|
||||
}
|
197
third_party/github.com/fsouza/go-dockerclient/engine/job.go
vendored
Normal file
197
third_party/github.com/fsouza/go-dockerclient/engine/job.go
vendored
Normal file
@@ -0,0 +1,197 @@
|
||||
// Copyright 2014 Docker authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the DOCKER-LICENSE file.
|
||||
|
||||
package engine
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// A job is the fundamental unit of work in the docker engine.
|
||||
// Everything docker can do should eventually be exposed as a job.
|
||||
// For example: execute a process in a container, create a new container,
|
||||
// download an archive from the internet, serve the http api, etc.
|
||||
//
|
||||
// The job API is designed after unix processes: a job has a name, arguments,
|
||||
// environment variables, standard streams for input, output and error, and
|
||||
// an exit status which can indicate success (0) or error (anything else).
|
||||
//
|
||||
// One slight variation is that jobs report their status as a string. The
|
||||
// string "0" indicates success, and any other strings indicates an error.
|
||||
// This allows for richer error reporting.
|
||||
//
|
||||
type Job struct {
|
||||
Eng *Engine
|
||||
Name string
|
||||
Args []string
|
||||
env *Env
|
||||
Stdout *Output
|
||||
Stderr *Output
|
||||
Stdin *Input
|
||||
handler Handler
|
||||
status Status
|
||||
end time.Time
|
||||
onExit []func()
|
||||
}
|
||||
|
||||
type Status int
|
||||
|
||||
const (
|
||||
StatusOK Status = 0
|
||||
StatusErr Status = 1
|
||||
StatusNotFound Status = 127
|
||||
)
|
||||
|
||||
// Run executes the job and blocks until the job completes.
|
||||
// If the job returns a failure status, an error is returned
|
||||
// which includes the status.
|
||||
func (job *Job) Run() error {
|
||||
// FIXME: make this thread-safe
|
||||
// FIXME: implement wait
|
||||
if !job.end.IsZero() {
|
||||
return fmt.Errorf("%s: job has already completed", job.Name)
|
||||
}
|
||||
// Log beginning and end of the job
|
||||
job.Eng.Logf("+job %s", job.CallString())
|
||||
defer func() {
|
||||
job.Eng.Logf("-job %s%s", job.CallString(), job.StatusString())
|
||||
}()
|
||||
var errorMessage string
|
||||
job.Stderr.AddString(&errorMessage)
|
||||
if job.handler == nil {
|
||||
job.Errorf("%s: command not found", job.Name)
|
||||
job.status = 127
|
||||
} else {
|
||||
job.status = job.handler(job)
|
||||
job.end = time.Now()
|
||||
}
|
||||
// Wait for all background tasks to complete
|
||||
if err := job.Stdout.Close(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := job.Stderr.Close(); err != nil {
|
||||
return err
|
||||
}
|
||||
if job.status != 0 {
|
||||
return fmt.Errorf("%s: %s", job.Name, errorMessage)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (job *Job) CallString() string {
|
||||
return fmt.Sprintf("%s(%s)", job.Name, strings.Join(job.Args, ", "))
|
||||
}
|
||||
|
||||
func (job *Job) StatusString() string {
|
||||
// If the job hasn't completed, status string is empty
|
||||
if job.end.IsZero() {
|
||||
return ""
|
||||
}
|
||||
var okerr string
|
||||
if job.status == StatusOK {
|
||||
okerr = "OK"
|
||||
} else {
|
||||
okerr = "ERR"
|
||||
}
|
||||
return fmt.Sprintf(" = %s (%d)", okerr, job.status)
|
||||
}
|
||||
|
||||
// String returns a human-readable description of `job`
|
||||
func (job *Job) String() string {
|
||||
return fmt.Sprintf("%s.%s%s", job.Eng, job.CallString(), job.StatusString())
|
||||
}
|
||||
|
||||
func (job *Job) Getenv(key string) (value string) {
|
||||
return job.env.Get(key)
|
||||
}
|
||||
|
||||
func (job *Job) GetenvBool(key string) (value bool) {
|
||||
return job.env.GetBool(key)
|
||||
}
|
||||
|
||||
func (job *Job) SetenvBool(key string, value bool) {
|
||||
job.env.SetBool(key, value)
|
||||
}
|
||||
|
||||
func (job *Job) GetenvInt64(key string) int64 {
|
||||
return job.env.GetInt64(key)
|
||||
}
|
||||
|
||||
func (job *Job) GetenvInt(key string) int {
|
||||
return job.env.GetInt(key)
|
||||
}
|
||||
|
||||
func (job *Job) SetenvInt64(key string, value int64) {
|
||||
job.env.SetInt64(key, value)
|
||||
}
|
||||
|
||||
func (job *Job) SetenvInt(key string, value int) {
|
||||
job.env.SetInt(key, value)
|
||||
}
|
||||
|
||||
// Returns nil if key not found
|
||||
func (job *Job) GetenvList(key string) []string {
|
||||
return job.env.GetList(key)
|
||||
}
|
||||
|
||||
func (job *Job) GetenvJson(key string, iface interface{}) error {
|
||||
return job.env.GetJson(key, iface)
|
||||
}
|
||||
|
||||
func (job *Job) SetenvJson(key string, value interface{}) error {
|
||||
return job.env.SetJson(key, value)
|
||||
}
|
||||
|
||||
func (job *Job) SetenvList(key string, value []string) error {
|
||||
return job.env.SetJson(key, value)
|
||||
}
|
||||
|
||||
func (job *Job) Setenv(key, value string) {
|
||||
job.env.Set(key, value)
|
||||
}
|
||||
|
||||
// DecodeEnv decodes `src` as a json dictionary, and adds
|
||||
// each decoded key-value pair to the environment.
|
||||
//
|
||||
// If `src` cannot be decoded as a json dictionary, an error
|
||||
// is returned.
|
||||
func (job *Job) DecodeEnv(src io.Reader) error {
|
||||
return job.env.Decode(src)
|
||||
}
|
||||
|
||||
func (job *Job) EncodeEnv(dst io.Writer) error {
|
||||
return job.env.Encode(dst)
|
||||
}
|
||||
|
||||
func (job *Job) ExportEnv(dst interface{}) (err error) {
|
||||
return job.env.Export(dst)
|
||||
}
|
||||
|
||||
func (job *Job) ImportEnv(src interface{}) (err error) {
|
||||
return job.env.Import(src)
|
||||
}
|
||||
|
||||
func (job *Job) Environ() map[string]string {
|
||||
return job.env.Map()
|
||||
}
|
||||
|
||||
func (job *Job) Logf(format string, args ...interface{}) (n int, err error) {
|
||||
prefixedFormat := fmt.Sprintf("[%s] %s\n", job, strings.TrimRight(format, "\n"))
|
||||
return fmt.Fprintf(job.Stderr, prefixedFormat, args...)
|
||||
}
|
||||
|
||||
func (job *Job) Printf(format string, args ...interface{}) (n int, err error) {
|
||||
return fmt.Fprintf(job.Stdout, format, args...)
|
||||
}
|
||||
|
||||
func (job *Job) Errorf(format string, args ...interface{}) (n int, err error) {
|
||||
return fmt.Fprintf(job.Stderr, format, args...)
|
||||
}
|
||||
|
||||
func (job *Job) Error(err error) (int, error) {
|
||||
return fmt.Fprintf(job.Stderr, "%s", err)
|
||||
}
|
84
third_party/github.com/fsouza/go-dockerclient/engine/job_test.go
vendored
Normal file
84
third_party/github.com/fsouza/go-dockerclient/engine/job_test.go
vendored
Normal file
@@ -0,0 +1,84 @@
|
||||
// Copyright 2014 Docker authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the DOCKER-LICENSE file.
|
||||
|
||||
package engine
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestJobStatusOK(t *testing.T) {
|
||||
eng := newTestEngine(t)
|
||||
defer os.RemoveAll(eng.Root())
|
||||
eng.Register("return_ok", func(job *Job) Status { return StatusOK })
|
||||
err := eng.Job("return_ok").Run()
|
||||
if err != nil {
|
||||
t.Fatalf("Expected: err=%v\nReceived: err=%v", nil, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestJobStatusErr(t *testing.T) {
|
||||
eng := newTestEngine(t)
|
||||
defer os.RemoveAll(eng.Root())
|
||||
eng.Register("return_err", func(job *Job) Status { return StatusErr })
|
||||
err := eng.Job("return_err").Run()
|
||||
if err == nil {
|
||||
t.Fatalf("When a job returns StatusErr, Run() should return an error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestJobStatusNotFound(t *testing.T) {
|
||||
eng := newTestEngine(t)
|
||||
defer os.RemoveAll(eng.Root())
|
||||
eng.Register("return_not_found", func(job *Job) Status { return StatusNotFound })
|
||||
err := eng.Job("return_not_found").Run()
|
||||
if err == nil {
|
||||
t.Fatalf("When a job returns StatusNotFound, Run() should return an error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestJobStdoutString(t *testing.T) {
|
||||
eng := newTestEngine(t)
|
||||
defer os.RemoveAll(eng.Root())
|
||||
// FIXME: test multiple combinations of output and status
|
||||
eng.Register("say_something_in_stdout", func(job *Job) Status {
|
||||
job.Printf("Hello world\n")
|
||||
return StatusOK
|
||||
})
|
||||
|
||||
job := eng.Job("say_something_in_stdout")
|
||||
var output string
|
||||
if err := job.Stdout.AddString(&output); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := job.Run(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if expectedOutput := "Hello world"; output != expectedOutput {
|
||||
t.Fatalf("Stdout last line:\nExpected: %v\nReceived: %v", expectedOutput, output)
|
||||
}
|
||||
}
|
||||
|
||||
func TestJobStderrString(t *testing.T) {
|
||||
eng := newTestEngine(t)
|
||||
defer os.RemoveAll(eng.Root())
|
||||
// FIXME: test multiple combinations of output and status
|
||||
eng.Register("say_something_in_stderr", func(job *Job) Status {
|
||||
job.Errorf("Warning, something might happen\nHere it comes!\nOh no...\nSomething happened\n")
|
||||
return StatusOK
|
||||
})
|
||||
|
||||
job := eng.Job("say_something_in_stderr")
|
||||
var output string
|
||||
if err := job.Stderr.AddString(&output); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := job.Run(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if expectedOutput := "Something happened"; output != expectedOutput {
|
||||
t.Fatalf("Stderr last line:\nExpected: %v\nReceived: %v", expectedOutput, output)
|
||||
}
|
||||
}
|
196
third_party/github.com/fsouza/go-dockerclient/engine/streams.go
vendored
Normal file
196
third_party/github.com/fsouza/go-dockerclient/engine/streams.go
vendored
Normal file
@@ -0,0 +1,196 @@
|
||||
// Copyright 2014 Docker authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the DOCKER-LICENSE file.
|
||||
|
||||
package engine
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"container/ring"
|
||||
"fmt"
|
||||
"io"
|
||||
"sync"
|
||||
)
|
||||
|
||||
type Output struct {
|
||||
sync.Mutex
|
||||
dests []io.Writer
|
||||
tasks sync.WaitGroup
|
||||
}
|
||||
|
||||
// NewOutput returns a new Output object with no destinations attached.
|
||||
// Writing to an empty Output will cause the written data to be discarded.
|
||||
func NewOutput() *Output {
|
||||
return &Output{}
|
||||
}
|
||||
|
||||
// Add attaches a new destination to the Output. Any data subsequently written
|
||||
// to the output will be written to the new destination in addition to all the others.
|
||||
// This method is thread-safe.
|
||||
// FIXME: Add cannot fail
|
||||
func (o *Output) Add(dst io.Writer) error {
|
||||
o.Mutex.Lock()
|
||||
defer o.Mutex.Unlock()
|
||||
o.dests = append(o.dests, dst)
|
||||
return nil
|
||||
}
|
||||
|
||||
// AddPipe creates an in-memory pipe with io.Pipe(), adds its writing end as a destination,
|
||||
// and returns its reading end for consumption by the caller.
|
||||
// This is a rough equivalent similar to Cmd.StdoutPipe() in the standard os/exec package.
|
||||
// This method is thread-safe.
|
||||
func (o *Output) AddPipe() (io.Reader, error) {
|
||||
r, w := io.Pipe()
|
||||
o.Add(w)
|
||||
return r, nil
|
||||
}
|
||||
|
||||
// AddTail starts a new goroutine which will read all subsequent data written to the output,
|
||||
// line by line, and append the last `n` lines to `dst`.
|
||||
func (o *Output) AddTail(dst *[]string, n int) error {
|
||||
src, err := o.AddPipe()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
o.tasks.Add(1)
|
||||
go func() {
|
||||
defer o.tasks.Done()
|
||||
Tail(src, n, dst)
|
||||
}()
|
||||
return nil
|
||||
}
|
||||
|
||||
// AddString starts a new goroutine which will read all subsequent data written to the output,
|
||||
// line by line, and store the last line into `dst`.
|
||||
func (o *Output) AddString(dst *string) error {
|
||||
src, err := o.AddPipe()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
o.tasks.Add(1)
|
||||
go func() {
|
||||
defer o.tasks.Done()
|
||||
lines := make([]string, 0, 1)
|
||||
Tail(src, 1, &lines)
|
||||
if len(lines) == 0 {
|
||||
*dst = ""
|
||||
} else {
|
||||
*dst = lines[0]
|
||||
}
|
||||
}()
|
||||
return nil
|
||||
}
|
||||
|
||||
// Write writes the same data to all registered destinations.
|
||||
// This method is thread-safe.
|
||||
func (o *Output) Write(p []byte) (n int, err error) {
|
||||
o.Mutex.Lock()
|
||||
defer o.Mutex.Unlock()
|
||||
var firstErr error
|
||||
for _, dst := range o.dests {
|
||||
_, err := dst.Write(p)
|
||||
if err != nil && firstErr == nil {
|
||||
firstErr = err
|
||||
}
|
||||
}
|
||||
return len(p), firstErr
|
||||
}
|
||||
|
||||
// Close unregisters all destinations and waits for all background
|
||||
// AddTail and AddString tasks to complete.
|
||||
// The Close method of each destination is called if it exists.
|
||||
func (o *Output) Close() error {
|
||||
o.Mutex.Lock()
|
||||
defer o.Mutex.Unlock()
|
||||
var firstErr error
|
||||
for _, dst := range o.dests {
|
||||
if closer, ok := dst.(io.WriteCloser); ok {
|
||||
err := closer.Close()
|
||||
if err != nil && firstErr == nil {
|
||||
firstErr = err
|
||||
}
|
||||
}
|
||||
}
|
||||
o.tasks.Wait()
|
||||
return firstErr
|
||||
}
|
||||
|
||||
type Input struct {
|
||||
src io.Reader
|
||||
sync.Mutex
|
||||
}
|
||||
|
||||
// NewInput returns a new Input object with no source attached.
|
||||
// Reading to an empty Input will return io.EOF.
|
||||
func NewInput() *Input {
|
||||
return &Input{}
|
||||
}
|
||||
|
||||
// Read reads from the input in a thread-safe way.
|
||||
func (i *Input) Read(p []byte) (n int, err error) {
|
||||
i.Mutex.Lock()
|
||||
defer i.Mutex.Unlock()
|
||||
if i.src == nil {
|
||||
return 0, io.EOF
|
||||
}
|
||||
return i.src.Read(p)
|
||||
}
|
||||
|
||||
// Add attaches a new source to the input.
|
||||
// Add can only be called once per input. Subsequent calls will
|
||||
// return an error.
|
||||
func (i *Input) Add(src io.Reader) error {
|
||||
i.Mutex.Lock()
|
||||
defer i.Mutex.Unlock()
|
||||
if i.src != nil {
|
||||
return fmt.Errorf("Maximum number of sources reached: 1")
|
||||
}
|
||||
i.src = src
|
||||
return nil
|
||||
}
|
||||
|
||||
// Tail reads from `src` line per line, and returns the last `n` lines as an array.
|
||||
// A ring buffer is used to only store `n` lines at any time.
|
||||
func Tail(src io.Reader, n int, dst *[]string) {
|
||||
scanner := bufio.NewScanner(src)
|
||||
r := ring.New(n)
|
||||
for scanner.Scan() {
|
||||
if n == 0 {
|
||||
continue
|
||||
}
|
||||
r.Value = scanner.Text()
|
||||
r = r.Next()
|
||||
}
|
||||
r.Do(func(v interface{}) {
|
||||
if v == nil {
|
||||
return
|
||||
}
|
||||
*dst = append(*dst, v.(string))
|
||||
})
|
||||
}
|
||||
|
||||
// AddEnv starts a new goroutine which will decode all subsequent data
|
||||
// as a stream of json-encoded objects, and point `dst` to the last
|
||||
// decoded object.
|
||||
// The result `env` can be queried using the type-neutral Env interface.
|
||||
// It is not safe to query `env` until the Output is closed.
|
||||
func (o *Output) AddEnv() (dst *Env, err error) {
|
||||
src, err := o.AddPipe()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
dst = &Env{}
|
||||
o.tasks.Add(1)
|
||||
go func() {
|
||||
defer o.tasks.Done()
|
||||
decoder := NewDecoder(src)
|
||||
for {
|
||||
env, err := decoder.Decode()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
*dst = *env
|
||||
}
|
||||
}()
|
||||
return dst, nil
|
||||
}
|
298
third_party/github.com/fsouza/go-dockerclient/engine/streams_test.go
vendored
Normal file
298
third_party/github.com/fsouza/go-dockerclient/engine/streams_test.go
vendored
Normal file
@@ -0,0 +1,298 @@
|
||||
// Copyright 2014 Docker authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the DOCKER-LICENSE file.
|
||||
|
||||
package engine
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestOutputAddString(t *testing.T) {
|
||||
var testInputs = [][2]string{
|
||||
{
|
||||
"hello, world!",
|
||||
"hello, world!",
|
||||
},
|
||||
|
||||
{
|
||||
"One\nTwo\nThree",
|
||||
"Three",
|
||||
},
|
||||
|
||||
{
|
||||
"",
|
||||
"",
|
||||
},
|
||||
|
||||
{
|
||||
"A line\nThen another nl-terminated line\n",
|
||||
"Then another nl-terminated line",
|
||||
},
|
||||
|
||||
{
|
||||
"A line followed by an empty line\n\n",
|
||||
"",
|
||||
},
|
||||
}
|
||||
for _, testData := range testInputs {
|
||||
input := testData[0]
|
||||
expectedOutput := testData[1]
|
||||
o := NewOutput()
|
||||
var output string
|
||||
if err := o.AddString(&output); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if n, err := o.Write([]byte(input)); err != nil {
|
||||
t.Error(err)
|
||||
} else if n != len(input) {
|
||||
t.Errorf("Expected %d, got %d", len(input), n)
|
||||
}
|
||||
o.Close()
|
||||
if output != expectedOutput {
|
||||
t.Errorf("Last line is not stored as return string.\nInput: '%s'\nExpected: '%s'\nGot: '%s'", input, expectedOutput, output)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type sentinelWriteCloser struct {
|
||||
calledWrite bool
|
||||
calledClose bool
|
||||
}
|
||||
|
||||
func (w *sentinelWriteCloser) Write(p []byte) (int, error) {
|
||||
w.calledWrite = true
|
||||
return len(p), nil
|
||||
}
|
||||
|
||||
func (w *sentinelWriteCloser) Close() error {
|
||||
w.calledClose = true
|
||||
return nil
|
||||
}
|
||||
|
||||
func TestOutputAddEnv(t *testing.T) {
|
||||
input := "{\"foo\": \"bar\", \"answer_to_life_the_universe_and_everything\": 42}"
|
||||
o := NewOutput()
|
||||
result, err := o.AddEnv()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
o.Write([]byte(input))
|
||||
o.Close()
|
||||
if v := result.Get("foo"); v != "bar" {
|
||||
t.Errorf("Expected %v, got %v", "bar", v)
|
||||
}
|
||||
if v := result.GetInt("answer_to_life_the_universe_and_everything"); v != 42 {
|
||||
t.Errorf("Expected %v, got %v", 42, v)
|
||||
}
|
||||
if v := result.Get("this-value-doesnt-exist"); v != "" {
|
||||
t.Errorf("Expected %v, got %v", "", v)
|
||||
}
|
||||
}
|
||||
|
||||
func TestOutputAddClose(t *testing.T) {
|
||||
o := NewOutput()
|
||||
var s sentinelWriteCloser
|
||||
if err := o.Add(&s); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := o.Close(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
// Write data after the output is closed.
|
||||
// Write should succeed, but no destination should receive it.
|
||||
if _, err := o.Write([]byte("foo bar")); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !s.calledClose {
|
||||
t.Fatal("Output.Close() didn't close the destination")
|
||||
}
|
||||
}
|
||||
|
||||
func TestOutputAddPipe(t *testing.T) {
|
||||
var testInputs = []string{
|
||||
"hello, world!",
|
||||
"One\nTwo\nThree",
|
||||
"",
|
||||
"A line\nThen another nl-terminated line\n",
|
||||
"A line followed by an empty line\n\n",
|
||||
}
|
||||
for _, input := range testInputs {
|
||||
expectedOutput := input
|
||||
o := NewOutput()
|
||||
r, err := o.AddPipe()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
go func(o *Output) {
|
||||
if n, err := o.Write([]byte(input)); err != nil {
|
||||
t.Error(err)
|
||||
} else if n != len(input) {
|
||||
t.Errorf("Expected %d, got %d", len(input), n)
|
||||
}
|
||||
if err := o.Close(); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}(o)
|
||||
output, err := ioutil.ReadAll(r)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if string(output) != expectedOutput {
|
||||
t.Errorf("Last line is not stored as return string.\nExpected: '%s'\nGot: '%s'", expectedOutput, output)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestTail(t *testing.T) {
|
||||
var tests = make(map[string][][]string)
|
||||
tests["hello, world!"] = [][]string{
|
||||
{},
|
||||
{"hello, world!"},
|
||||
{"hello, world!"},
|
||||
{"hello, world!"},
|
||||
}
|
||||
tests["One\nTwo\nThree"] = [][]string{
|
||||
{},
|
||||
{"Three"},
|
||||
{"Two", "Three"},
|
||||
{"One", "Two", "Three"},
|
||||
}
|
||||
for input, outputs := range tests {
|
||||
for n, expectedOutput := range outputs {
|
||||
var output []string
|
||||
Tail(strings.NewReader(input), n, &output)
|
||||
if fmt.Sprintf("%v", output) != fmt.Sprintf("%v", expectedOutput) {
|
||||
t.Errorf("Tail n=%d returned wrong result.\nExpected: '%s'\nGot : '%s'", n, expectedOutput, output)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestOutputAddTail(t *testing.T) {
|
||||
var tests = make(map[string][][]string)
|
||||
tests["hello, world!"] = [][]string{
|
||||
{},
|
||||
{"hello, world!"},
|
||||
{"hello, world!"},
|
||||
{"hello, world!"},
|
||||
}
|
||||
tests["One\nTwo\nThree"] = [][]string{
|
||||
{},
|
||||
{"Three"},
|
||||
{"Two", "Three"},
|
||||
{"One", "Two", "Three"},
|
||||
}
|
||||
for input, outputs := range tests {
|
||||
for n, expectedOutput := range outputs {
|
||||
o := NewOutput()
|
||||
var output []string
|
||||
if err := o.AddTail(&output, n); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if n, err := o.Write([]byte(input)); err != nil {
|
||||
t.Error(err)
|
||||
} else if n != len(input) {
|
||||
t.Errorf("Expected %d, got %d", len(input), n)
|
||||
}
|
||||
o.Close()
|
||||
if fmt.Sprintf("%v", output) != fmt.Sprintf("%v", expectedOutput) {
|
||||
t.Errorf("Tail(%d) returned wrong result.\nExpected: %v\nGot: %v", n, expectedOutput, output)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func lastLine(txt string) string {
|
||||
scanner := bufio.NewScanner(strings.NewReader(txt))
|
||||
var lastLine string
|
||||
for scanner.Scan() {
|
||||
lastLine = scanner.Text()
|
||||
}
|
||||
return lastLine
|
||||
}
|
||||
|
||||
func TestOutputAdd(t *testing.T) {
|
||||
o := NewOutput()
|
||||
b := &bytes.Buffer{}
|
||||
o.Add(b)
|
||||
input := "hello, world!"
|
||||
if n, err := o.Write([]byte(input)); err != nil {
|
||||
t.Fatal(err)
|
||||
} else if n != len(input) {
|
||||
t.Fatalf("Expected %d, got %d", len(input), n)
|
||||
}
|
||||
if output := b.String(); output != input {
|
||||
t.Fatalf("Received wrong data from Add.\nExpected: '%s'\nGot: '%s'", input, output)
|
||||
}
|
||||
}
|
||||
|
||||
func TestOutputWriteError(t *testing.T) {
|
||||
o := NewOutput()
|
||||
buf := &bytes.Buffer{}
|
||||
o.Add(buf)
|
||||
r, w := io.Pipe()
|
||||
input := "Hello there"
|
||||
expectedErr := fmt.Errorf("This is an error")
|
||||
r.CloseWithError(expectedErr)
|
||||
o.Add(w)
|
||||
n, err := o.Write([]byte(input))
|
||||
if err != expectedErr {
|
||||
t.Fatalf("Output.Write() should return the first error encountered, if any")
|
||||
}
|
||||
if buf.String() != input {
|
||||
t.Fatalf("Output.Write() should attempt write on all destinations, even after encountering an error")
|
||||
}
|
||||
if n != len(input) {
|
||||
t.Fatalf("Output.Write() should return the size of the input if it successfully writes to at least one destination")
|
||||
}
|
||||
}
|
||||
|
||||
func TestInputAddEmpty(t *testing.T) {
|
||||
i := NewInput()
|
||||
var b bytes.Buffer
|
||||
if err := i.Add(&b); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
data, err := ioutil.ReadAll(i)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(data) > 0 {
|
||||
t.Fatalf("Read from empty input shoul yield no data")
|
||||
}
|
||||
}
|
||||
|
||||
func TestInputAddTwo(t *testing.T) {
|
||||
i := NewInput()
|
||||
var b1 bytes.Buffer
|
||||
// First add should succeed
|
||||
if err := i.Add(&b1); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
var b2 bytes.Buffer
|
||||
// Second add should fail
|
||||
if err := i.Add(&b2); err == nil {
|
||||
t.Fatalf("Adding a second source should return an error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestInputAddNotEmpty(t *testing.T) {
|
||||
i := NewInput()
|
||||
b := bytes.NewBufferString("hello world\nabc")
|
||||
expectedResult := b.String()
|
||||
i.Add(b)
|
||||
result, err := ioutil.ReadAll(i)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if string(result) != expectedResult {
|
||||
t.Fatalf("Expected: %v\nReceived: %v", expectedResult, result)
|
||||
}
|
||||
}
|
279
third_party/github.com/fsouza/go-dockerclient/event.go
vendored
Normal file
279
third_party/github.com/fsouza/go-dockerclient/event.go
vendored
Normal file
@@ -0,0 +1,279 @@
|
||||
// Copyright 2014 go-dockerclient authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package docker
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"math"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/http/httputil"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
)
|
||||
|
||||
// APIEvents represents an event returned by the API.
|
||||
type APIEvents struct {
|
||||
Status string
|
||||
ID string
|
||||
From string
|
||||
Time int64
|
||||
}
|
||||
|
||||
type eventMonitoringState struct {
|
||||
sync.RWMutex
|
||||
sync.WaitGroup
|
||||
enabled bool
|
||||
lastSeen *int64
|
||||
C chan *APIEvents
|
||||
errC chan error
|
||||
listeners []chan<- *APIEvents
|
||||
}
|
||||
|
||||
const (
|
||||
maxMonitorConnRetries = 5
|
||||
retryInitialWaitTime = 10.
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrNoListeners is the error returned when no listeners are available
|
||||
// to receive an event.
|
||||
ErrNoListeners = errors.New("no listeners present to receive event")
|
||||
|
||||
// ErrListenerAlreadyExists is the error returned when the listerner already
|
||||
// exists.
|
||||
ErrListenerAlreadyExists = errors.New("listener already exists for docker events")
|
||||
)
|
||||
|
||||
// AddEventListener adds a new listener to container events in the Docker API.
|
||||
//
|
||||
// The parameter is a channel through which events will be sent.
|
||||
func (c *Client) AddEventListener(listener chan<- *APIEvents) error {
|
||||
var err error
|
||||
if !c.eventMonitor.isEnabled() {
|
||||
err = c.eventMonitor.enableEventMonitoring(c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
err = c.eventMonitor.addListener(listener)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// RemoveEventListener removes a listener from the monitor.
|
||||
func (c *Client) RemoveEventListener(listener chan *APIEvents) error {
|
||||
err := c.eventMonitor.removeListener(listener)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(c.eventMonitor.listeners) == 0 {
|
||||
err = c.eventMonitor.disableEventMonitoring()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (eventState *eventMonitoringState) addListener(listener chan<- *APIEvents) error {
|
||||
eventState.Lock()
|
||||
defer eventState.Unlock()
|
||||
if listenerExists(listener, &eventState.listeners) {
|
||||
return ErrListenerAlreadyExists
|
||||
}
|
||||
eventState.Add(1)
|
||||
eventState.listeners = append(eventState.listeners, listener)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (eventState *eventMonitoringState) removeListener(listener chan<- *APIEvents) error {
|
||||
eventState.Lock()
|
||||
defer eventState.Unlock()
|
||||
if listenerExists(listener, &eventState.listeners) {
|
||||
var newListeners []chan<- *APIEvents
|
||||
for _, l := range eventState.listeners {
|
||||
if l != listener {
|
||||
newListeners = append(newListeners, l)
|
||||
}
|
||||
}
|
||||
eventState.listeners = newListeners
|
||||
eventState.Add(-1)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func listenerExists(a chan<- *APIEvents, list *[]chan<- *APIEvents) bool {
|
||||
for _, b := range *list {
|
||||
if b == a {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (eventState *eventMonitoringState) enableEventMonitoring(c *Client) error {
|
||||
eventState.Lock()
|
||||
defer eventState.Unlock()
|
||||
if !eventState.enabled {
|
||||
eventState.enabled = true
|
||||
var lastSeenDefault = int64(0)
|
||||
eventState.lastSeen = &lastSeenDefault
|
||||
eventState.C = make(chan *APIEvents, 100)
|
||||
eventState.errC = make(chan error, 1)
|
||||
go eventState.monitorEvents(c)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (eventState *eventMonitoringState) disableEventMonitoring() error {
|
||||
eventState.Wait()
|
||||
eventState.Lock()
|
||||
defer eventState.Unlock()
|
||||
if eventState.enabled {
|
||||
eventState.enabled = false
|
||||
close(eventState.C)
|
||||
close(eventState.errC)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (eventState *eventMonitoringState) monitorEvents(c *Client) {
|
||||
var err error
|
||||
for eventState.noListeners() {
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
}
|
||||
if err = eventState.connectWithRetry(c); err != nil {
|
||||
eventState.terminate(err)
|
||||
}
|
||||
for eventState.isEnabled() {
|
||||
timeout := time.After(100 * time.Millisecond)
|
||||
select {
|
||||
case ev, ok := <-eventState.C:
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
go eventState.sendEvent(ev)
|
||||
go eventState.updateLastSeen(ev)
|
||||
case err = <-eventState.errC:
|
||||
if err == ErrNoListeners {
|
||||
eventState.terminate(nil)
|
||||
return
|
||||
} else if err != nil {
|
||||
defer func() { go eventState.monitorEvents(c) }()
|
||||
return
|
||||
}
|
||||
case <-timeout:
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (eventState *eventMonitoringState) connectWithRetry(c *Client) error {
|
||||
var retries int
|
||||
var err error
|
||||
for err = c.eventHijack(atomic.LoadInt64(eventState.lastSeen), eventState.C, eventState.errC); err != nil && retries < maxMonitorConnRetries; retries++ {
|
||||
waitTime := int64(retryInitialWaitTime * math.Pow(2, float64(retries)))
|
||||
time.Sleep(time.Duration(waitTime) * time.Millisecond)
|
||||
err = c.eventHijack(atomic.LoadInt64(eventState.lastSeen), eventState.C, eventState.errC)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (eventState *eventMonitoringState) noListeners() bool {
|
||||
eventState.RLock()
|
||||
defer eventState.RUnlock()
|
||||
return len(eventState.listeners) == 0
|
||||
}
|
||||
|
||||
func (eventState *eventMonitoringState) isEnabled() bool {
|
||||
eventState.RLock()
|
||||
defer eventState.RUnlock()
|
||||
return eventState.enabled
|
||||
}
|
||||
|
||||
func (eventState *eventMonitoringState) sendEvent(event *APIEvents) {
|
||||
eventState.RLock()
|
||||
defer eventState.RUnlock()
|
||||
eventState.Add(1)
|
||||
defer eventState.Done()
|
||||
if eventState.isEnabled() {
|
||||
if eventState.noListeners() {
|
||||
eventState.errC <- ErrNoListeners
|
||||
return
|
||||
}
|
||||
|
||||
for _, listener := range eventState.listeners {
|
||||
listener <- event
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (eventState *eventMonitoringState) updateLastSeen(e *APIEvents) {
|
||||
eventState.Lock()
|
||||
defer eventState.Unlock()
|
||||
if atomic.LoadInt64(eventState.lastSeen) < e.Time {
|
||||
atomic.StoreInt64(eventState.lastSeen, e.Time)
|
||||
}
|
||||
}
|
||||
|
||||
func (eventState *eventMonitoringState) terminate(err error) {
|
||||
eventState.disableEventMonitoring()
|
||||
}
|
||||
|
||||
func (c *Client) eventHijack(startTime int64, eventChan chan *APIEvents, errChan chan error) error {
|
||||
uri := "/events"
|
||||
if startTime != 0 {
|
||||
uri += fmt.Sprintf("?since=%d", startTime)
|
||||
}
|
||||
protocol := c.endpointURL.Scheme
|
||||
address := c.endpointURL.Path
|
||||
if protocol != "unix" {
|
||||
protocol = "tcp"
|
||||
address = c.endpointURL.Host
|
||||
}
|
||||
dial, err := net.Dial(protocol, address)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
conn := httputil.NewClientConn(dial, nil)
|
||||
req, err := http.NewRequest("GET", uri, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
res, err := conn.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
go func(res *http.Response, conn *httputil.ClientConn) {
|
||||
defer conn.Close()
|
||||
defer res.Body.Close()
|
||||
decoder := json.NewDecoder(res.Body)
|
||||
for {
|
||||
var event APIEvents
|
||||
if err = decoder.Decode(&event); err != nil {
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
errChan <- err
|
||||
}
|
||||
if event.Time == 0 {
|
||||
continue
|
||||
}
|
||||
if !c.eventMonitor.isEnabled() {
|
||||
return
|
||||
} else {
|
||||
c.eventMonitor.C <- &event
|
||||
}
|
||||
}
|
||||
}(res, conn)
|
||||
return nil
|
||||
}
|
92
third_party/github.com/fsouza/go-dockerclient/event_test.go
vendored
Normal file
92
third_party/github.com/fsouza/go-dockerclient/event_test.go
vendored
Normal file
@@ -0,0 +1,92 @@
|
||||
// Copyright 2014 go-dockerclient authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package docker
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestEventListeners(t *testing.T) {
|
||||
response := `{"status":"create","id":"dfdf82bd3881","from":"base:latest","time":1374067924}
|
||||
{"status":"start","id":"dfdf82bd3881","from":"base:latest","time":1374067924}
|
||||
{"status":"stop","id":"dfdf82bd3881","from":"base:latest","time":1374067966}
|
||||
{"status":"destroy","id":"dfdf82bd3881","from":"base:latest","time":1374067970}
|
||||
`
|
||||
|
||||
var req http.Request
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
rsc := bufio.NewScanner(strings.NewReader(response))
|
||||
for rsc.Scan() {
|
||||
w.Write([]byte(rsc.Text()))
|
||||
w.(http.Flusher).Flush()
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
}
|
||||
req = *r
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
client, err := NewClient(server.URL)
|
||||
if err != nil {
|
||||
t.Errorf("Failed to create client: %s", err)
|
||||
}
|
||||
|
||||
listener := make(chan *APIEvents, 10)
|
||||
defer func() { time.Sleep(10 * time.Millisecond); client.RemoveEventListener(listener) }()
|
||||
|
||||
err = client.AddEventListener(listener)
|
||||
if err != nil {
|
||||
t.Errorf("Failed to add event listener: %s", err)
|
||||
}
|
||||
|
||||
timeout := time.After(1 * time.Second)
|
||||
var count int
|
||||
|
||||
for {
|
||||
select {
|
||||
case msg := <-listener:
|
||||
t.Logf("Recieved: %s", *msg)
|
||||
count++
|
||||
err = checkEvent(count, msg)
|
||||
if err != nil {
|
||||
t.Fatalf("Check event failed: %s", err)
|
||||
}
|
||||
if count == 4 {
|
||||
return
|
||||
}
|
||||
case <-timeout:
|
||||
t.Fatal("TestAddEventListener timed out waiting on events")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func checkEvent(index int, event *APIEvents) error {
|
||||
if event.ID != "dfdf82bd3881" {
|
||||
return fmt.Errorf("event ID did not match. Expected dfdf82bd3881 got %s", event.ID)
|
||||
}
|
||||
if event.From != "base:latest" {
|
||||
return fmt.Errorf("event from did not match. Expected base:latest got %s", event.From)
|
||||
}
|
||||
var status string
|
||||
switch index {
|
||||
case 1:
|
||||
status = "create"
|
||||
case 2:
|
||||
status = "start"
|
||||
case 3:
|
||||
status = "stop"
|
||||
case 4:
|
||||
status = "destroy"
|
||||
}
|
||||
if event.Status != status {
|
||||
return fmt.Errorf("event status did not match. Expected %s got %s", status, event.Status)
|
||||
}
|
||||
return nil
|
||||
}
|
133
third_party/github.com/fsouza/go-dockerclient/example_test.go
vendored
Normal file
133
third_party/github.com/fsouza/go-dockerclient/example_test.go
vendored
Normal file
@@ -0,0 +1,133 @@
|
||||
// Copyright 2014 go-dockerclient authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package docker_test
|
||||
|
||||
import (
|
||||
"archive/tar"
|
||||
"bytes"
|
||||
"io"
|
||||
"log"
|
||||
"time"
|
||||
|
||||
"github.com/fsouza/go-dockerclient"
|
||||
)
|
||||
|
||||
func ExampleClient_AttachToContainer() {
|
||||
client, err := docker.NewClient("http://localhost:4243")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
// Reading logs from container a84849 and sending them to buf.
|
||||
var buf bytes.Buffer
|
||||
err = client.AttachToContainer(docker.AttachToContainerOptions{
|
||||
Container: "a84849",
|
||||
OutputStream: &buf,
|
||||
Logs: true,
|
||||
Stdout: true,
|
||||
Stderr: true,
|
||||
})
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
log.Println(buf.String())
|
||||
// Attaching to stdout and streaming.
|
||||
buf.Reset()
|
||||
err = client.AttachToContainer(docker.AttachToContainerOptions{
|
||||
Container: "a84849",
|
||||
OutputStream: &buf,
|
||||
Stdout: true,
|
||||
Stream: true,
|
||||
})
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
log.Println(buf.String())
|
||||
}
|
||||
|
||||
func ExampleClient_CopyFromContainer() {
|
||||
client, err := docker.NewClient("http://localhost:4243")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
cid := "a84849"
|
||||
// Copy resulting file
|
||||
var buf bytes.Buffer
|
||||
filename := "/tmp/output.txt"
|
||||
err = client.CopyFromContainer(docker.CopyFromContainerOptions{
|
||||
Container: cid,
|
||||
Resource: filename,
|
||||
OutputStream: &buf,
|
||||
})
|
||||
if err != nil {
|
||||
log.Fatalf("Error while copying from %s: %s\n", cid, err)
|
||||
}
|
||||
content := new(bytes.Buffer)
|
||||
r := bytes.NewReader(buf.Bytes())
|
||||
tr := tar.NewReader(r)
|
||||
tr.Next()
|
||||
if err != nil && err != io.EOF {
|
||||
log.Fatal(err)
|
||||
}
|
||||
if _, err := io.Copy(content, tr); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
log.Println(buf.String())
|
||||
}
|
||||
|
||||
func ExampleClient_BuildImage() {
|
||||
client, err := docker.NewClient("http://localhost:4243")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
t := time.Now()
|
||||
inputbuf, outputbuf := bytes.NewBuffer(nil), bytes.NewBuffer(nil)
|
||||
tr := tar.NewWriter(inputbuf)
|
||||
tr.WriteHeader(&tar.Header{Name: "Dockerfile", Size: 10, ModTime: t, AccessTime: t, ChangeTime: t})
|
||||
tr.Write([]byte("FROM base\n"))
|
||||
tr.Close()
|
||||
opts := docker.BuildImageOptions{
|
||||
Name: "test",
|
||||
InputStream: inputbuf,
|
||||
OutputStream: outputbuf,
|
||||
}
|
||||
if err := client.BuildImage(opts); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func ExampleClient_ListenEvents() {
|
||||
client, err := docker.NewClient("http://localhost:4243")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
listener := make(chan *docker.APIEvents)
|
||||
err = client.AddEventListener(listener)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
defer func() {
|
||||
|
||||
err = client.RemoveEventListener(listener)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
}()
|
||||
|
||||
timeout := time.After(1 * time.Second)
|
||||
|
||||
for {
|
||||
select {
|
||||
case msg := <-listener:
|
||||
log.Println(msg)
|
||||
case <-timeout:
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
}
|
265
third_party/github.com/fsouza/go-dockerclient/image.go
vendored
Normal file
265
third_party/github.com/fsouza/go-dockerclient/image.go
vendored
Normal file
@@ -0,0 +1,265 @@
|
||||
// Copyright 2014 go-dockerclient authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package docker
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
)
|
||||
|
||||
// APIImages represent an image returned in the ListImages call.
|
||||
type APIImages struct {
|
||||
ID string `json:"Id"`
|
||||
RepoTags []string `json:",omitempty"`
|
||||
Created int64
|
||||
Size int64
|
||||
VirtualSize int64
|
||||
ParentId string `json:",omitempty"`
|
||||
Repository string `json:",omitempty"`
|
||||
Tag string `json:",omitempty"`
|
||||
}
|
||||
|
||||
var (
|
||||
// ErrNoSuchImage is the error returned when the image does not exist.
|
||||
ErrNoSuchImage = errors.New("no such image")
|
||||
|
||||
// ErrMissingRepo is the error returned when the remote repository is
|
||||
// missing.
|
||||
ErrMissingRepo = errors.New("missing remote repository e.g. 'github.com/user/repo'")
|
||||
|
||||
// ErrMissingOutputStream is the error returned when no output stream
|
||||
// is provided to some calls, like BuildImage.
|
||||
ErrMissingOutputStream = errors.New("missing output stream")
|
||||
)
|
||||
|
||||
// ListImages returns the list of available images in the server.
|
||||
//
|
||||
// See http://goo.gl/dkMrwP for more details.
|
||||
func (c *Client) ListImages(all bool) ([]APIImages, error) {
|
||||
path := "/images/json?all="
|
||||
if all {
|
||||
path += "1"
|
||||
} else {
|
||||
path += "0"
|
||||
}
|
||||
body, _, err := c.do("GET", path, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var images []APIImages
|
||||
err = json.Unmarshal(body, &images)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return images, nil
|
||||
}
|
||||
|
||||
// RemoveImage removes an image by its name or ID.
|
||||
//
|
||||
// See http://goo.gl/7hjHHy for more details.
|
||||
func (c *Client) RemoveImage(name string) error {
|
||||
_, status, err := c.do("DELETE", "/images/"+name, nil)
|
||||
if status == http.StatusNotFound {
|
||||
return ErrNoSuchImage
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// InspectImage returns an image by its name or ID.
|
||||
//
|
||||
// See http://goo.gl/pHEbma for more details.
|
||||
func (c *Client) InspectImage(name string) (*Image, error) {
|
||||
body, status, err := c.do("GET", "/images/"+name+"/json", nil)
|
||||
if status == http.StatusNotFound {
|
||||
return nil, ErrNoSuchImage
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var image Image
|
||||
err = json.Unmarshal(body, &image)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &image, nil
|
||||
}
|
||||
|
||||
// PushImageOptions represents options to use in the PushImage method.
|
||||
//
|
||||
// See http://goo.gl/GBmyhc for more details.
|
||||
type PushImageOptions struct {
|
||||
// Name of the image
|
||||
Name string
|
||||
|
||||
// Registry server to push the image
|
||||
Registry string
|
||||
|
||||
OutputStream io.Writer `qs:"-"`
|
||||
}
|
||||
|
||||
// AuthConfiguration represents authentication options to use in the PushImage
|
||||
// method. It represents the authencation in the Docker index server.
|
||||
type AuthConfiguration struct {
|
||||
Username string `json:"username,omitempty"`
|
||||
Password string `json:"password,omitempty"`
|
||||
Email string `json:"email,omitempty"`
|
||||
}
|
||||
|
||||
// PushImage pushes an image to a remote registry, logging progress to w.
|
||||
//
|
||||
// An empty instance of AuthConfiguration may be used for unauthenticated
|
||||
// pushes.
|
||||
//
|
||||
// See http://goo.gl/GBmyhc for more details.
|
||||
func (c *Client) PushImage(opts PushImageOptions, auth AuthConfiguration) error {
|
||||
if opts.Name == "" {
|
||||
return ErrNoSuchImage
|
||||
}
|
||||
name := opts.Name
|
||||
opts.Name = ""
|
||||
path := "/images/" + name + "/push?" + queryString(&opts)
|
||||
var headers = make(map[string]string)
|
||||
var buf bytes.Buffer
|
||||
json.NewEncoder(&buf).Encode(auth)
|
||||
|
||||
headers["X-Registry-Auth"] = base64.URLEncoding.EncodeToString(buf.Bytes())
|
||||
|
||||
return c.stream("POST", path, headers, nil, opts.OutputStream)
|
||||
}
|
||||
|
||||
// PullImageOptions present the set of options available for pulling an image
|
||||
// from a registry.
|
||||
//
|
||||
// See http://goo.gl/PhBKnS for more details.
|
||||
type PullImageOptions struct {
|
||||
Repository string `qs:"fromImage"`
|
||||
Registry string
|
||||
Tag string
|
||||
OutputStream io.Writer `qs:"-"`
|
||||
}
|
||||
|
||||
// PullImage pulls an image from a remote registry, logging progress to w.
|
||||
//
|
||||
// See http://goo.gl/PhBKnS for more details.
|
||||
func (c *Client) PullImage(opts PullImageOptions, auth AuthConfiguration) error {
|
||||
if opts.Repository == "" {
|
||||
return ErrNoSuchImage
|
||||
}
|
||||
|
||||
var headers = make(map[string]string)
|
||||
var buf bytes.Buffer
|
||||
json.NewEncoder(&buf).Encode(auth)
|
||||
headers["X-Registry-Auth"] = base64.URLEncoding.EncodeToString(buf.Bytes())
|
||||
|
||||
return c.createImage(queryString(&opts), headers, nil, opts.OutputStream)
|
||||
}
|
||||
|
||||
func (c *Client) createImage(qs string, headers map[string]string, in io.Reader, w io.Writer) error {
|
||||
path := "/images/create?" + qs
|
||||
return c.stream("POST", path, headers, in, w)
|
||||
}
|
||||
|
||||
// ImportImageOptions present the set of informations available for importing
|
||||
// an image from a source file or the stdin.
|
||||
//
|
||||
// See http://goo.gl/PhBKnS for more details.
|
||||
type ImportImageOptions struct {
|
||||
Repository string `qs:"repo"`
|
||||
Source string `qs:"fromSrc"`
|
||||
Tag string `qs:"tag"`
|
||||
|
||||
InputStream io.Reader `qs:"-"`
|
||||
OutputStream io.Writer `qs:"-"`
|
||||
}
|
||||
|
||||
// ImportImage imports an image from a url, a file or stdin
|
||||
//
|
||||
// See http://goo.gl/PhBKnS for more details.
|
||||
func (c *Client) ImportImage(opts ImportImageOptions) error {
|
||||
if opts.Repository == "" {
|
||||
return ErrNoSuchImage
|
||||
}
|
||||
if opts.Source != "-" {
|
||||
opts.InputStream = nil
|
||||
}
|
||||
if opts.Source != "-" && !isURL(opts.Source) {
|
||||
f, err := os.Open(opts.Source)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
b, err := ioutil.ReadAll(f)
|
||||
opts.InputStream = bytes.NewBuffer(b)
|
||||
opts.Source = "-"
|
||||
}
|
||||
return c.createImage(queryString(&opts), nil, opts.InputStream, opts.OutputStream)
|
||||
}
|
||||
|
||||
// BuildImageOptions present the set of informations available for building
|
||||
// an image from a tarfile with a Dockerfile in it,the details about Dockerfile
|
||||
// see http://docs.docker.io/en/latest/reference/builder/
|
||||
type BuildImageOptions struct {
|
||||
Name string `qs:"t"`
|
||||
NoCache bool `qs:"nocache"`
|
||||
SuppressOutput bool `qs:"q"`
|
||||
RmTmpContainer bool `qs:"rm"`
|
||||
InputStream io.Reader `qs:"-"`
|
||||
OutputStream io.Writer `qs:"-"`
|
||||
Remote string `qs:"remote"`
|
||||
}
|
||||
|
||||
// BuildImage builds an image from a tarball's url or a Dockerfile in the input
|
||||
// stream.
|
||||
func (c *Client) BuildImage(opts BuildImageOptions) error {
|
||||
if opts.OutputStream == nil {
|
||||
return ErrMissingOutputStream
|
||||
}
|
||||
var headers map[string]string
|
||||
if opts.Remote != "" && opts.Name == "" {
|
||||
opts.Name = opts.Remote
|
||||
}
|
||||
if opts.InputStream != nil {
|
||||
headers = map[string]string{"Content-Type": "application/tar"}
|
||||
} else if opts.Remote == "" {
|
||||
return ErrMissingRepo
|
||||
}
|
||||
return c.stream("POST", fmt.Sprintf("/build?%s",
|
||||
queryString(&opts)), headers, opts.InputStream, opts.OutputStream)
|
||||
}
|
||||
|
||||
// TagImageOptions present the set of options to tag an image
|
||||
type TagImageOptions struct {
|
||||
Repo string `qs:"repo"`
|
||||
Force bool `qs:"force"`
|
||||
}
|
||||
|
||||
// TagImage adds a tag to the image 'name'
|
||||
func (c *Client) TagImage(name string, opts TagImageOptions) error {
|
||||
if name == "" {
|
||||
return ErrNoSuchImage
|
||||
}
|
||||
_, status, err := c.do("POST", fmt.Sprintf("/images/"+name+"/tag?%s",
|
||||
queryString(&opts)), nil)
|
||||
if status == http.StatusNotFound {
|
||||
return ErrNoSuchImage
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func isURL(u string) bool {
|
||||
p, err := url.Parse(u)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return p.Scheme == "http" || p.Scheme == "https"
|
||||
}
|
641
third_party/github.com/fsouza/go-dockerclient/image_test.go
vendored
Normal file
641
third_party/github.com/fsouza/go-dockerclient/image_test.go
vendored
Normal file
@@ -0,0 +1,641 @@
|
||||
// Copyright 2014 go-dockerclient authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package docker
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func newTestClient(rt *FakeRoundTripper) Client {
|
||||
endpoint := "http://localhost:4243"
|
||||
u, _ := parseEndpoint("http://localhost:4243")
|
||||
client := Client{
|
||||
endpoint: endpoint,
|
||||
endpointURL: u,
|
||||
client: &http.Client{Transport: rt},
|
||||
}
|
||||
return client
|
||||
}
|
||||
|
||||
type stdoutMock struct {
|
||||
*bytes.Buffer
|
||||
}
|
||||
|
||||
func (m stdoutMock) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
type stdinMock struct {
|
||||
*bytes.Buffer
|
||||
}
|
||||
|
||||
func (m stdinMock) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func TestListImages(t *testing.T) {
|
||||
body := `[
|
||||
{
|
||||
"Repository":"base",
|
||||
"Tag":"ubuntu-12.10",
|
||||
"Id":"b750fe79269d",
|
||||
"Created":1364102658
|
||||
},
|
||||
{
|
||||
"Repository":"base",
|
||||
"Tag":"ubuntu-quantal",
|
||||
"Id":"b750fe79269d",
|
||||
"Created":1364102658
|
||||
},
|
||||
{
|
||||
"RepoTag": [
|
||||
"ubuntu:12.04",
|
||||
"ubuntu:precise",
|
||||
"ubuntu:latest"
|
||||
],
|
||||
"Id": "8dbd9e392a964c",
|
||||
"Created": 1365714795,
|
||||
"Size": 131506275,
|
||||
"VirtualSize": 131506275
|
||||
},
|
||||
{
|
||||
"RepoTag": [
|
||||
"ubuntu:12.10",
|
||||
"ubuntu:quantal"
|
||||
],
|
||||
"ParentId": "27cf784147099545",
|
||||
"Id": "b750fe79269d2e",
|
||||
"Created": 1364102658,
|
||||
"Size": 24653,
|
||||
"VirtualSize": 180116135
|
||||
}
|
||||
]`
|
||||
var expected []APIImages
|
||||
err := json.Unmarshal([]byte(body), &expected)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
client := newTestClient(&FakeRoundTripper{message: body, status: http.StatusOK})
|
||||
images, err := client.ListImages(false)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if !reflect.DeepEqual(images, expected) {
|
||||
t.Errorf("ListImages: Wrong return value. Want %#v. Got %#v.", expected, images)
|
||||
}
|
||||
}
|
||||
|
||||
func TestListImagesParameters(t *testing.T) {
|
||||
fakeRT := &FakeRoundTripper{message: "null", status: http.StatusOK}
|
||||
client := newTestClient(fakeRT)
|
||||
_, err := client.ListImages(false)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
req := fakeRT.requests[0]
|
||||
if req.Method != "GET" {
|
||||
t.Errorf("ListImages(false: Wrong HTTP method. Want GET. Got %s.", req.Method)
|
||||
}
|
||||
if all := req.URL.Query().Get("all"); all != "0" {
|
||||
t.Errorf("ListImages(false): Wrong parameter. Want all=0. Got all=%s", all)
|
||||
}
|
||||
fakeRT.Reset()
|
||||
_, err = client.ListImages(true)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
req = fakeRT.requests[0]
|
||||
if all := req.URL.Query().Get("all"); all != "1" {
|
||||
t.Errorf("ListImages(true): Wrong parameter. Want all=1. Got all=%s", all)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRemoveImage(t *testing.T) {
|
||||
name := "test"
|
||||
fakeRT := &FakeRoundTripper{message: "", status: http.StatusNoContent}
|
||||
client := newTestClient(fakeRT)
|
||||
err := client.RemoveImage(name)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
req := fakeRT.requests[0]
|
||||
expectedMethod := "DELETE"
|
||||
if req.Method != expectedMethod {
|
||||
t.Errorf("RemoveImage(%q): Wrong HTTP method. Want %s. Got %s.", name, expectedMethod, req.Method)
|
||||
}
|
||||
u, _ := url.Parse(client.getURL("/images/" + name))
|
||||
if req.URL.Path != u.Path {
|
||||
t.Errorf("RemoveImage(%q): Wrong request path. Want %q. Got %q.", name, u.Path, req.URL.Path)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRemoveImageNotFound(t *testing.T) {
|
||||
client := newTestClient(&FakeRoundTripper{message: "no such image", status: http.StatusNotFound})
|
||||
err := client.RemoveImage("test:")
|
||||
if err != ErrNoSuchImage {
|
||||
t.Errorf("RemoveImage: wrong error. Want %#v. Got %#v.", ErrNoSuchImage, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestInspectImage(t *testing.T) {
|
||||
body := `{
|
||||
"id":"b750fe79269d2ec9a3c593ef05b4332b1d1a02a62b4accb2c21d589ff2f5f2dc",
|
||||
"parent":"27cf784147099545",
|
||||
"created":"2013-03-23T22:24:18.818426-07:00",
|
||||
"container":"3d67245a8d72ecf13f33dffac9f79dcdf70f75acb84d308770391510e0c23ad0",
|
||||
"container_config":{"Memory":0}
|
||||
}`
|
||||
var expected Image
|
||||
json.Unmarshal([]byte(body), &expected)
|
||||
fakeRT := &FakeRoundTripper{message: body, status: http.StatusOK}
|
||||
client := newTestClient(fakeRT)
|
||||
image, err := client.InspectImage(expected.ID)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !reflect.DeepEqual(*image, expected) {
|
||||
t.Errorf("InspectImage(%q): Wrong image returned. Want %#v. Got %#v.", expected.ID, expected, *image)
|
||||
}
|
||||
req := fakeRT.requests[0]
|
||||
if req.Method != "GET" {
|
||||
t.Errorf("InspectImage(%q): Wrong HTTP method. Want GET. Got %s.", expected.ID, req.Method)
|
||||
}
|
||||
u, _ := url.Parse(client.getURL("/images/" + expected.ID + "/json"))
|
||||
if req.URL.Path != u.Path {
|
||||
t.Errorf("InspectImage(%q): Wrong request URL. Want %q. Got %q.", expected.ID, u.Path, req.URL.Path)
|
||||
}
|
||||
}
|
||||
|
||||
func TestInspectImageNotFound(t *testing.T) {
|
||||
client := newTestClient(&FakeRoundTripper{message: "no such image", status: http.StatusNotFound})
|
||||
name := "test"
|
||||
image, err := client.InspectImage(name)
|
||||
if image != nil {
|
||||
t.Errorf("InspectImage(%q): expected <nil> image, got %#v.", name, image)
|
||||
}
|
||||
if err != ErrNoSuchImage {
|
||||
t.Errorf("InspectImage(%q): wrong error. Want %#v. Got %#v.", name, ErrNoSuchImage, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPushImage(t *testing.T) {
|
||||
fakeRT := &FakeRoundTripper{message: "Pushing 1/100", status: http.StatusOK}
|
||||
client := newTestClient(fakeRT)
|
||||
var buf bytes.Buffer
|
||||
err := client.PushImage(PushImageOptions{Name: "test", OutputStream: &buf}, AuthConfiguration{})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
expected := "Pushing 1/100"
|
||||
if buf.String() != expected {
|
||||
t.Errorf("PushImage: Wrong output. Want %q. Got %q.", expected, buf.String())
|
||||
}
|
||||
req := fakeRT.requests[0]
|
||||
if req.Method != "POST" {
|
||||
t.Errorf("PushImage: Wrong HTTP method. Want POST. Got %s.", req.Method)
|
||||
}
|
||||
u, _ := url.Parse(client.getURL("/images/test/push"))
|
||||
if req.URL.Path != u.Path {
|
||||
t.Errorf("PushImage: Wrong request path. Want %q. Got %q.", u.Path, req.URL.Path)
|
||||
}
|
||||
if query := req.URL.Query().Encode(); query != "" {
|
||||
t.Errorf("PushImage: Wrong query string. Want no parameters, got %q.", query)
|
||||
}
|
||||
|
||||
auth, err := base64.URLEncoding.DecodeString(req.Header.Get("X-Registry-Auth"))
|
||||
if err != nil {
|
||||
t.Errorf("PushImage: caught error decoding auth. %#v", err.Error())
|
||||
}
|
||||
if strings.TrimSpace(string(auth)) != "{}" {
|
||||
t.Errorf("PushImage: wrong body. Want %q. Got %q.",
|
||||
base64.URLEncoding.EncodeToString([]byte("{}")), req.Header.Get("X-Registry-Auth"))
|
||||
}
|
||||
}
|
||||
|
||||
func TestPushImageWithAuthentication(t *testing.T) {
|
||||
fakeRT := &FakeRoundTripper{message: "Pushing 1/100", status: http.StatusOK}
|
||||
client := newTestClient(fakeRT)
|
||||
var buf bytes.Buffer
|
||||
inputAuth := AuthConfiguration{
|
||||
Username: "gopher",
|
||||
Password: "gopher123",
|
||||
Email: "gopher@tsuru.io",
|
||||
}
|
||||
err := client.PushImage(PushImageOptions{Name: "test", OutputStream: &buf}, inputAuth)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
req := fakeRT.requests[0]
|
||||
var gotAuth AuthConfiguration
|
||||
|
||||
auth, err := base64.URLEncoding.DecodeString(req.Header.Get("X-Registry-Auth"))
|
||||
if err != nil {
|
||||
t.Errorf("PushImage: caught error decoding auth. %#v", err.Error())
|
||||
}
|
||||
|
||||
err = json.Unmarshal(auth, &gotAuth)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !reflect.DeepEqual(gotAuth, inputAuth) {
|
||||
t.Errorf("PushImage: wrong auth configuration. Want %#v. Got %#v.", inputAuth, gotAuth)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPushImageCustomRegistry(t *testing.T) {
|
||||
fakeRT := &FakeRoundTripper{message: "Pushing 1/100", status: http.StatusOK}
|
||||
client := newTestClient(fakeRT)
|
||||
var authConfig AuthConfiguration
|
||||
var buf bytes.Buffer
|
||||
opts := PushImageOptions{
|
||||
Name: "test", Registry: "docker.tsuru.io",
|
||||
OutputStream: &buf,
|
||||
}
|
||||
err := client.PushImage(opts, authConfig)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
req := fakeRT.requests[0]
|
||||
expectedQuery := "registry=docker.tsuru.io"
|
||||
if query := req.URL.Query().Encode(); query != expectedQuery {
|
||||
t.Errorf("PushImage: Wrong query string. Want %q. Got %q.", expectedQuery, query)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPushImageNoName(t *testing.T) {
|
||||
client := Client{}
|
||||
err := client.PushImage(PushImageOptions{}, AuthConfiguration{})
|
||||
if err != ErrNoSuchImage {
|
||||
t.Errorf("PushImage: got wrong error. Want %#v. Got %#v.", ErrNoSuchImage, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPullImage(t *testing.T) {
|
||||
fakeRT := &FakeRoundTripper{message: "Pulling 1/100", status: http.StatusOK}
|
||||
client := newTestClient(fakeRT)
|
||||
var buf bytes.Buffer
|
||||
err := client.PullImage(PullImageOptions{Repository: "base", OutputStream: &buf},
|
||||
AuthConfiguration{})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
expected := "Pulling 1/100"
|
||||
if buf.String() != expected {
|
||||
t.Errorf("PullImage: Wrong output. Want %q. Got %q.", expected, buf.String())
|
||||
}
|
||||
req := fakeRT.requests[0]
|
||||
if req.Method != "POST" {
|
||||
t.Errorf("PullImage: Wrong HTTP method. Want POST. Got %s.", req.Method)
|
||||
}
|
||||
u, _ := url.Parse(client.getURL("/images/create"))
|
||||
if req.URL.Path != u.Path {
|
||||
t.Errorf("PullImage: Wrong request path. Want %q. Got %q.", u.Path, req.URL.Path)
|
||||
}
|
||||
expectedQuery := "fromImage=base"
|
||||
if query := req.URL.Query().Encode(); query != expectedQuery {
|
||||
t.Errorf("PullImage: Wrong query strin. Want %q. Got %q.", expectedQuery, query)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPullImageWithoutOutputStream(t *testing.T) {
|
||||
fakeRT := &FakeRoundTripper{message: "Pulling 1/100", status: http.StatusOK}
|
||||
client := newTestClient(fakeRT)
|
||||
opts := PullImageOptions{
|
||||
Repository: "base",
|
||||
Registry: "docker.tsuru.io",
|
||||
}
|
||||
err := client.PullImage(opts, AuthConfiguration{})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
req := fakeRT.requests[0]
|
||||
expected := map[string][]string{"fromImage": {"base"}, "registry": {"docker.tsuru.io"}}
|
||||
got := map[string][]string(req.URL.Query())
|
||||
if !reflect.DeepEqual(got, expected) {
|
||||
t.Errorf("PullImage: wrong query string. Want %#v. Got %#v.", expected, got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPullImageCustomRegistry(t *testing.T) {
|
||||
fakeRT := &FakeRoundTripper{message: "Pulling 1/100", status: http.StatusOK}
|
||||
client := newTestClient(fakeRT)
|
||||
var buf bytes.Buffer
|
||||
opts := PullImageOptions{
|
||||
Repository: "base",
|
||||
Registry: "docker.tsuru.io",
|
||||
OutputStream: &buf,
|
||||
}
|
||||
err := client.PullImage(opts, AuthConfiguration{})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
req := fakeRT.requests[0]
|
||||
expected := map[string][]string{"fromImage": {"base"}, "registry": {"docker.tsuru.io"}}
|
||||
got := map[string][]string(req.URL.Query())
|
||||
if !reflect.DeepEqual(got, expected) {
|
||||
t.Errorf("PullImage: wrong query string. Want %#v. Got %#v.", expected, got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPullImageTag(t *testing.T) {
|
||||
fakeRT := &FakeRoundTripper{message: "Pulling 1/100", status: http.StatusOK}
|
||||
client := newTestClient(fakeRT)
|
||||
var buf bytes.Buffer
|
||||
opts := PullImageOptions{
|
||||
Repository: "base",
|
||||
Registry: "docker.tsuru.io",
|
||||
Tag: "latest",
|
||||
OutputStream: &buf,
|
||||
}
|
||||
err := client.PullImage(opts, AuthConfiguration{})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
req := fakeRT.requests[0]
|
||||
expected := map[string][]string{"fromImage": {"base"}, "registry": {"docker.tsuru.io"}, "tag": {"latest"}}
|
||||
got := map[string][]string(req.URL.Query())
|
||||
if !reflect.DeepEqual(got, expected) {
|
||||
t.Errorf("PullImage: wrong query string. Want %#v. Got %#v.", expected, got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPullImageNoRepository(t *testing.T) {
|
||||
var opts PullImageOptions
|
||||
client := Client{}
|
||||
err := client.PullImage(opts, AuthConfiguration{})
|
||||
if err != ErrNoSuchImage {
|
||||
t.Errorf("PullImage: got wrong error. Want %#v. Got %#v.", ErrNoSuchImage, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestImportImageFromUrl(t *testing.T) {
|
||||
fakeRT := &FakeRoundTripper{message: "", status: http.StatusOK}
|
||||
client := newTestClient(fakeRT)
|
||||
var buf bytes.Buffer
|
||||
opts := ImportImageOptions{
|
||||
Source: "http://mycompany.com/file.tar",
|
||||
Repository: "testimage",
|
||||
Tag: "tag",
|
||||
OutputStream: &buf,
|
||||
}
|
||||
err := client.ImportImage(opts)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
req := fakeRT.requests[0]
|
||||
expected := map[string][]string{"fromSrc": {opts.Source}, "repo": {opts.Repository}, "tag": {opts.Tag}}
|
||||
got := map[string][]string(req.URL.Query())
|
||||
if !reflect.DeepEqual(got, expected) {
|
||||
t.Errorf("ImportImage: wrong query string. Want %#v. Got %#v.", expected, got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestImportImageFromInput(t *testing.T) {
|
||||
fakeRT := &FakeRoundTripper{message: "", status: http.StatusOK}
|
||||
client := newTestClient(fakeRT)
|
||||
in := bytes.NewBufferString("tar content")
|
||||
var buf bytes.Buffer
|
||||
opts := ImportImageOptions{
|
||||
Source: "-", Repository: "testimage",
|
||||
InputStream: in, OutputStream: &buf,
|
||||
Tag: "tag",
|
||||
}
|
||||
err := client.ImportImage(opts)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
req := fakeRT.requests[0]
|
||||
expected := map[string][]string{"fromSrc": {opts.Source}, "repo": {opts.Repository}, "tag": {opts.Tag}}
|
||||
got := map[string][]string(req.URL.Query())
|
||||
if !reflect.DeepEqual(got, expected) {
|
||||
t.Errorf("ImportImage: wrong query string. Want %#v. Got %#v.", expected, got)
|
||||
}
|
||||
body, err := ioutil.ReadAll(req.Body)
|
||||
if err != nil {
|
||||
t.Errorf("ImportImage: caugth error while reading body %#v", err.Error())
|
||||
}
|
||||
e := "tar content"
|
||||
if string(body) != e {
|
||||
t.Errorf("ImportImage: wrong body. Want %#v. Got %#v.", e, string(body))
|
||||
}
|
||||
}
|
||||
|
||||
func TestImportImageDoesNotPassesInputIfSourceIsNotDash(t *testing.T) {
|
||||
fakeRT := &FakeRoundTripper{message: "", status: http.StatusOK}
|
||||
client := newTestClient(fakeRT)
|
||||
var buf bytes.Buffer
|
||||
in := bytes.NewBufferString("foo")
|
||||
opts := ImportImageOptions{
|
||||
Source: "http://test.com/container.tar", Repository: "testimage",
|
||||
InputStream: in, OutputStream: &buf,
|
||||
}
|
||||
err := client.ImportImage(opts)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
req := fakeRT.requests[0]
|
||||
expected := map[string][]string{"fromSrc": {opts.Source}, "repo": {opts.Repository}}
|
||||
got := map[string][]string(req.URL.Query())
|
||||
if !reflect.DeepEqual(got, expected) {
|
||||
t.Errorf("ImportImage: wrong query string. Want %#v. Got %#v.", expected, got)
|
||||
}
|
||||
body, err := ioutil.ReadAll(req.Body)
|
||||
if err != nil {
|
||||
t.Errorf("ImportImage: caugth error while reading body %#v", err.Error())
|
||||
}
|
||||
if string(body) != "" {
|
||||
t.Errorf("ImportImage: wrong body. Want nothing. Got %#v.", string(body))
|
||||
}
|
||||
}
|
||||
|
||||
func TestImportImageShouldPassTarContentToBodyWhenSourceIsFilePath(t *testing.T) {
|
||||
fakeRT := &FakeRoundTripper{message: "", status: http.StatusOK}
|
||||
client := newTestClient(fakeRT)
|
||||
var buf bytes.Buffer
|
||||
tarPath := "testing/data/container.tar"
|
||||
opts := ImportImageOptions{
|
||||
Source: tarPath, Repository: "testimage",
|
||||
OutputStream: &buf,
|
||||
}
|
||||
err := client.ImportImage(opts)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
tar, err := os.Open(tarPath)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
req := fakeRT.requests[0]
|
||||
tarContent, err := ioutil.ReadAll(tar)
|
||||
body, err := ioutil.ReadAll(req.Body)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !reflect.DeepEqual(tarContent, body) {
|
||||
t.Errorf("ImportImage: wrong body. Want %#v content. Got %#v.", tarPath, body)
|
||||
}
|
||||
}
|
||||
|
||||
func TestImportImageShouldChangeSourceToDashWhenItsAFilePath(t *testing.T) {
|
||||
fakeRT := &FakeRoundTripper{message: "", status: http.StatusOK}
|
||||
client := newTestClient(fakeRT)
|
||||
var buf bytes.Buffer
|
||||
tarPath := "testing/data/container.tar"
|
||||
opts := ImportImageOptions{
|
||||
Source: tarPath, Repository: "testimage",
|
||||
OutputStream: &buf,
|
||||
}
|
||||
err := client.ImportImage(opts)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
req := fakeRT.requests[0]
|
||||
expected := map[string][]string{"fromSrc": {"-"}, "repo": {opts.Repository}}
|
||||
got := map[string][]string(req.URL.Query())
|
||||
if !reflect.DeepEqual(got, expected) {
|
||||
t.Errorf("ImportImage: wrong query string. Want %#v. Got %#v.", expected, got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildImageParameters(t *testing.T) {
|
||||
fakeRT := &FakeRoundTripper{message: "", status: http.StatusOK}
|
||||
client := newTestClient(fakeRT)
|
||||
var buf bytes.Buffer
|
||||
opts := BuildImageOptions{
|
||||
Name: "testImage",
|
||||
NoCache: true,
|
||||
SuppressOutput: true,
|
||||
RmTmpContainer: true,
|
||||
InputStream: &buf,
|
||||
OutputStream: &buf,
|
||||
}
|
||||
err := client.BuildImage(opts)
|
||||
if err != nil && strings.Index(err.Error(), "build image fail") == -1 {
|
||||
t.Fatal(err)
|
||||
}
|
||||
req := fakeRT.requests[0]
|
||||
expected := map[string][]string{"t": {opts.Name}, "nocache": {"1"}, "q": {"1"}, "rm": {"1"}}
|
||||
got := map[string][]string(req.URL.Query())
|
||||
if !reflect.DeepEqual(got, expected) {
|
||||
t.Errorf("BuildImage: wrong query string. Want %#v. Got %#v.", expected, got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildImageParametersForRemoteBuild(t *testing.T) {
|
||||
fakeRT := &FakeRoundTripper{message: "", status: http.StatusOK}
|
||||
client := newTestClient(fakeRT)
|
||||
var buf bytes.Buffer
|
||||
opts := BuildImageOptions{
|
||||
Name: "testImage",
|
||||
Remote: "testing/data/container.tar",
|
||||
SuppressOutput: true,
|
||||
OutputStream: &buf,
|
||||
}
|
||||
err := client.BuildImage(opts)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
req := fakeRT.requests[0]
|
||||
expected := map[string][]string{"t": {opts.Name}, "remote": {opts.Remote}, "q": {"1"}}
|
||||
got := map[string][]string(req.URL.Query())
|
||||
if !reflect.DeepEqual(got, expected) {
|
||||
t.Errorf("ImportImage: wrong query string. Want %#v. Got %#v.", expected, got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildImageMissingRepoAndNilInput(t *testing.T) {
|
||||
fakeRT := &FakeRoundTripper{message: "", status: http.StatusOK}
|
||||
client := newTestClient(fakeRT)
|
||||
var buf bytes.Buffer
|
||||
opts := BuildImageOptions{
|
||||
Name: "testImage",
|
||||
SuppressOutput: true,
|
||||
OutputStream: &buf,
|
||||
}
|
||||
err := client.BuildImage(opts)
|
||||
if err != ErrMissingRepo {
|
||||
t.Errorf("BuildImage: wrong error returned. Want %#v. Got %#v.", ErrMissingRepo, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildImageMissingOutputStream(t *testing.T) {
|
||||
fakeRT := &FakeRoundTripper{message: "", status: http.StatusOK}
|
||||
client := newTestClient(fakeRT)
|
||||
opts := BuildImageOptions{Name: "testImage"}
|
||||
err := client.BuildImage(opts)
|
||||
if err != ErrMissingOutputStream {
|
||||
t.Errorf("BuildImage: wrong error returned. Want %#v. Got %#v.", ErrMissingOutputStream, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildImageRemoteWithoutName(t *testing.T) {
|
||||
fakeRT := &FakeRoundTripper{message: "", status: http.StatusOK}
|
||||
client := newTestClient(fakeRT)
|
||||
var buf bytes.Buffer
|
||||
opts := BuildImageOptions{
|
||||
Remote: "testing/data/container.tar",
|
||||
SuppressOutput: true,
|
||||
OutputStream: &buf,
|
||||
}
|
||||
err := client.BuildImage(opts)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
req := fakeRT.requests[0]
|
||||
expected := map[string][]string{"t": {opts.Remote}, "remote": {opts.Remote}, "q": {"1"}}
|
||||
got := map[string][]string(req.URL.Query())
|
||||
if !reflect.DeepEqual(got, expected) {
|
||||
t.Errorf("BuildImage: wrong query string. Want %#v. Got %#v.", expected, got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTagImageParameters(t *testing.T) {
|
||||
fakeRT := &FakeRoundTripper{message: "", status: http.StatusOK}
|
||||
client := newTestClient(fakeRT)
|
||||
opts := TagImageOptions{Repo: "testImage"}
|
||||
err := client.TagImage("base", opts)
|
||||
if err != nil && strings.Index(err.Error(), "tag image fail") == -1 {
|
||||
t.Fatal(err)
|
||||
}
|
||||
req := fakeRT.requests[0]
|
||||
expected := "http://localhost:4243/images/base/tag?repo=testImage"
|
||||
got := req.URL.String()
|
||||
if !reflect.DeepEqual(got, expected) {
|
||||
t.Errorf("TagImage: wrong query string. Want %#v. Got %#v.", expected, got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTagImageMissingRepo(t *testing.T) {
|
||||
fakeRT := &FakeRoundTripper{message: "", status: http.StatusOK}
|
||||
client := newTestClient(fakeRT)
|
||||
opts := TagImageOptions{Repo: "testImage"}
|
||||
err := client.TagImage("", opts)
|
||||
if err != ErrNoSuchImage {
|
||||
t.Errorf("TestTag: wrong error returned. Want %#v. Got %#v.",
|
||||
ErrNoSuchImage, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsUrl(t *testing.T) {
|
||||
url := "http://foo.bar/"
|
||||
result := isURL(url)
|
||||
if !result {
|
||||
t.Errorf("isURL: wrong match. Expected %#v to be a url. Got %#v.", url, result)
|
||||
}
|
||||
url = "/foo/bar.tar"
|
||||
result = isURL(url)
|
||||
if result {
|
||||
t.Errorf("isURL: wrong match. Expected %#v to not be a url. Got %#v", url, result)
|
||||
}
|
||||
}
|
46
third_party/github.com/fsouza/go-dockerclient/misc.go
vendored
Normal file
46
third_party/github.com/fsouza/go-dockerclient/misc.go
vendored
Normal file
@@ -0,0 +1,46 @@
|
||||
// Copyright 2014 go-dockerclient authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package docker
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"github.com/fsouza/go-dockerclient/engine"
|
||||
"io"
|
||||
)
|
||||
|
||||
// Version returns version information about the docker server.
|
||||
//
|
||||
// See http://goo.gl/IqKNRE for more details.
|
||||
func (c *Client) Version() (*engine.Env, error) {
|
||||
body, _, err := c.do("GET", "/version", nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out := engine.NewOutput()
|
||||
remoteVersion, err := out.AddEnv()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if _, err := io.Copy(out, bytes.NewReader(body)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return remoteVersion, nil
|
||||
}
|
||||
|
||||
// Info returns system-wide information, like the number of running containers.
|
||||
//
|
||||
// See http://goo.gl/LOmySw for more details.
|
||||
func (c *Client) Info() (*engine.Env, error) {
|
||||
body, _, err := c.do("GET", "/info", nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var info engine.Env
|
||||
err = info.Decode(bytes.NewReader(body))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &info, nil
|
||||
}
|
122
third_party/github.com/fsouza/go-dockerclient/misc_test.go
vendored
Normal file
122
third_party/github.com/fsouza/go-dockerclient/misc_test.go
vendored
Normal file
@@ -0,0 +1,122 @@
|
||||
// Copyright 2014 go-dockerclient authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package docker
|
||||
|
||||
import (
|
||||
"github.com/fsouza/go-dockerclient/engine"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"reflect"
|
||||
"sort"
|
||||
"testing"
|
||||
)
|
||||
|
||||
type DockerVersion struct {
|
||||
Version string
|
||||
GitCommit string
|
||||
GoVersion string
|
||||
}
|
||||
|
||||
func TestVersion(t *testing.T) {
|
||||
body := `{
|
||||
"Version":"0.2.2",
|
||||
"GitCommit":"5a2a5cc+CHANGES",
|
||||
"GoVersion":"go1.0.3"
|
||||
}`
|
||||
fakeRT := FakeRoundTripper{message: body, status: http.StatusOK}
|
||||
client := newTestClient(&fakeRT)
|
||||
expected := DockerVersion{
|
||||
Version: "0.2.2",
|
||||
GitCommit: "5a2a5cc+CHANGES",
|
||||
GoVersion: "go1.0.3",
|
||||
}
|
||||
version, err := client.Version()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if result := version.Get("Version"); result != expected.Version {
|
||||
t.Errorf("Version(): Wrong result. Want %#v. Got %#v.", expected.Version, version.Get("Version"))
|
||||
}
|
||||
if result := version.Get("GitCommit"); result != expected.GitCommit {
|
||||
t.Errorf("GitCommit(): Wrong result. Want %#v. Got %#v.", expected.GitCommit, version.Get("GitCommit"))
|
||||
}
|
||||
if result := version.Get("GoVersion"); result != expected.GoVersion {
|
||||
t.Errorf("GoVersion(): Wrong result. Want %#v. Got %#v.", expected.GoVersion, version.Get("GoVersion"))
|
||||
}
|
||||
req := fakeRT.requests[0]
|
||||
if req.Method != "GET" {
|
||||
t.Errorf("Version(): wrong request method. Want GET. Got %s.", req.Method)
|
||||
}
|
||||
u, _ := url.Parse(client.getURL("/version"))
|
||||
if req.URL.Path != u.Path {
|
||||
t.Errorf("Version(): wrong request path. Want %q. Got %q.", u.Path, req.URL.Path)
|
||||
}
|
||||
}
|
||||
|
||||
func TestVersionError(t *testing.T) {
|
||||
fakeRT := &FakeRoundTripper{message: "internal error", status: http.StatusInternalServerError}
|
||||
client := newTestClient(fakeRT)
|
||||
version, err := client.Version()
|
||||
if version != nil {
|
||||
t.Errorf("Version(): expected <nil> value, got %#v.", version)
|
||||
}
|
||||
if err == nil {
|
||||
t.Error("Version(): unexpected <nil> error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestInfo(t *testing.T) {
|
||||
body := `{
|
||||
"Containers":11,
|
||||
"Images":16,
|
||||
"Debug":0,
|
||||
"NFd":11,
|
||||
"NGoroutines":21,
|
||||
"MemoryLimit":1,
|
||||
"SwapLimit":0
|
||||
}`
|
||||
fakeRT := FakeRoundTripper{message: body, status: http.StatusOK}
|
||||
client := newTestClient(&fakeRT)
|
||||
expected := engine.Env{}
|
||||
expected.SetInt("Containers", 11)
|
||||
expected.SetInt("Images", 16)
|
||||
expected.SetBool("Debug", false)
|
||||
expected.SetInt("NFd", 11)
|
||||
expected.SetInt("NGoroutines", 21)
|
||||
expected.SetBool("MemoryLimit", true)
|
||||
expected.SetBool("SwapLimit", false)
|
||||
info, err := client.Info()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
infoSlice := []string(*info)
|
||||
expectedSlice := []string(expected)
|
||||
sort.Strings(infoSlice)
|
||||
sort.Strings(expectedSlice)
|
||||
if !reflect.DeepEqual(expectedSlice, infoSlice) {
|
||||
t.Errorf("Info(): Wrong result.\nWant %#v.\nGot %#v.", expected, *info)
|
||||
}
|
||||
req := fakeRT.requests[0]
|
||||
if req.Method != "GET" {
|
||||
t.Errorf("Info(): Wrong HTTP method. Want GET. Got %s.", req.Method)
|
||||
}
|
||||
u, _ := url.Parse(client.getURL("/info"))
|
||||
if req.URL.Path != u.Path {
|
||||
t.Errorf("Info(): Wrong request path. Want %q. Got %q.", u.Path, req.URL.Path)
|
||||
}
|
||||
}
|
||||
|
||||
func TestInfoError(t *testing.T) {
|
||||
fakeRT := &FakeRoundTripper{message: "internal error", status: http.StatusInternalServerError}
|
||||
client := newTestClient(fakeRT)
|
||||
version, err := client.Info()
|
||||
if version != nil {
|
||||
t.Errorf("Info(): expected <nil> value, got %#v.", version)
|
||||
}
|
||||
if err == nil {
|
||||
t.Error("Info(): unexpected <nil> error")
|
||||
}
|
||||
}
|
49
third_party/github.com/fsouza/go-dockerclient/signal.go
vendored
Normal file
49
third_party/github.com/fsouza/go-dockerclient/signal.go
vendored
Normal file
@@ -0,0 +1,49 @@
|
||||
// Copyright 2014 go-dockerclient authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package docker
|
||||
|
||||
// Signal represents a signal that can be send to the container on
|
||||
// KillContainer call.
|
||||
type Signal int
|
||||
|
||||
// These values represent all signals available on Linux, where containers will
|
||||
// be running.
|
||||
const (
|
||||
SIGABRT = Signal(0x6)
|
||||
SIGALRM = Signal(0xe)
|
||||
SIGBUS = Signal(0x7)
|
||||
SIGCHLD = Signal(0x11)
|
||||
SIGCLD = Signal(0x11)
|
||||
SIGCONT = Signal(0x12)
|
||||
SIGFPE = Signal(0x8)
|
||||
SIGHUP = Signal(0x1)
|
||||
SIGILL = Signal(0x4)
|
||||
SIGINT = Signal(0x2)
|
||||
SIGIO = Signal(0x1d)
|
||||
SIGIOT = Signal(0x6)
|
||||
SIGKILL = Signal(0x9)
|
||||
SIGPIPE = Signal(0xd)
|
||||
SIGPOLL = Signal(0x1d)
|
||||
SIGPROF = Signal(0x1b)
|
||||
SIGPWR = Signal(0x1e)
|
||||
SIGQUIT = Signal(0x3)
|
||||
SIGSEGV = Signal(0xb)
|
||||
SIGSTKFLT = Signal(0x10)
|
||||
SIGSTOP = Signal(0x13)
|
||||
SIGSYS = Signal(0x1f)
|
||||
SIGTERM = Signal(0xf)
|
||||
SIGTRAP = Signal(0x5)
|
||||
SIGTSTP = Signal(0x14)
|
||||
SIGTTIN = Signal(0x15)
|
||||
SIGTTOU = Signal(0x16)
|
||||
SIGUNUSED = Signal(0x1f)
|
||||
SIGURG = Signal(0x17)
|
||||
SIGUSR1 = Signal(0xa)
|
||||
SIGUSR2 = Signal(0xc)
|
||||
SIGVTALRM = Signal(0x1a)
|
||||
SIGWINCH = Signal(0x1c)
|
||||
SIGXCPU = Signal(0x18)
|
||||
SIGXFSZ = Signal(0x19)
|
||||
)
|
15
third_party/github.com/fsouza/go-dockerclient/testing/data/Dockerfile
vendored
Normal file
15
third_party/github.com/fsouza/go-dockerclient/testing/data/Dockerfile
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
# this file describes how to build tsuru python image
|
||||
# to run it:
|
||||
# 1- install docker
|
||||
# 2- run: $ docker build -t tsuru/python https://raw.github.com/tsuru/basebuilder/master/python/Dockerfile
|
||||
|
||||
from base:ubuntu-quantal
|
||||
run apt-get install wget -y --force-yes
|
||||
run wget http://github.com/tsuru/basebuilder/tarball/master -O basebuilder.tar.gz --no-check-certificate
|
||||
run mkdir /var/lib/tsuru
|
||||
run tar -xvf basebuilder.tar.gz -C /var/lib/tsuru --strip 1
|
||||
run cp /var/lib/tsuru/python/deploy /var/lib/tsuru
|
||||
run cp /var/lib/tsuru/base/restart /var/lib/tsuru
|
||||
run cp /var/lib/tsuru/base/start /var/lib/tsuru
|
||||
run /var/lib/tsuru/base/install
|
||||
run /var/lib/tsuru/base/setup
|
BIN
third_party/github.com/fsouza/go-dockerclient/testing/data/container.tar
vendored
Normal file
BIN
third_party/github.com/fsouza/go-dockerclient/testing/data/container.tar
vendored
Normal file
Binary file not shown.
BIN
third_party/github.com/fsouza/go-dockerclient/testing/data/dockerfile.tar
vendored
Normal file
BIN
third_party/github.com/fsouza/go-dockerclient/testing/data/dockerfile.tar
vendored
Normal file
Binary file not shown.
568
third_party/github.com/fsouza/go-dockerclient/testing/server.go
vendored
Normal file
568
third_party/github.com/fsouza/go-dockerclient/testing/server.go
vendored
Normal file
@@ -0,0 +1,568 @@
|
||||
// Copyright 2014 go-dockerclient authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package testing provides a fake implementation of the Docker API, useful for
|
||||
// testing purpose.
|
||||
package testing
|
||||
|
||||
import (
|
||||
"archive/tar"
|
||||
"crypto/rand"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/fsouza/go-dockerclient"
|
||||
"github.com/fsouza/go-dockerclient/utils"
|
||||
"github.com/gorilla/mux"
|
||||
mathrand "math/rand"
|
||||
"net"
|
||||
"net/http"
|
||||
"reflect"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// DockerServer represents a programmable, concurrent (not much), HTTP server
|
||||
// implementing a fake version of the Docker remote API.
|
||||
//
|
||||
// It can used in standalone mode, listening for connections or as an arbitrary
|
||||
// HTTP handler.
|
||||
//
|
||||
// For more details on the remote API, check http://goo.gl/yMI1S.
|
||||
type DockerServer struct {
|
||||
containers []*docker.Container
|
||||
cMut sync.RWMutex
|
||||
images []docker.Image
|
||||
iMut sync.RWMutex
|
||||
imgIDs map[string]string
|
||||
listener net.Listener
|
||||
mux *mux.Router
|
||||
hook func(*http.Request)
|
||||
failures map[string]FailureSpec
|
||||
}
|
||||
|
||||
// FailureSpec is used with PrepareFailure and describes in which situations
|
||||
// the request should fail. UrlRegex is mandatory, if a container id is sent
|
||||
// on the request you can also specify the other properties.
|
||||
type FailureSpec struct {
|
||||
UrlRegex string
|
||||
ContainerPath string
|
||||
ContainerArgs []string
|
||||
}
|
||||
|
||||
// NewServer returns a new instance of the fake server, in standalone mode. Use
|
||||
// the method URL to get the URL of the server.
|
||||
//
|
||||
// It receives the bind address (use 127.0.0.1:0 for getting an available port
|
||||
// on the host) and a hook function, that will be called on every request.
|
||||
func NewServer(bind string, hook func(*http.Request)) (*DockerServer, error) {
|
||||
listener, err := net.Listen("tcp", bind)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
server := DockerServer{listener: listener, imgIDs: make(map[string]string), hook: hook,
|
||||
failures: make(map[string]FailureSpec)}
|
||||
server.buildMuxer()
|
||||
go http.Serve(listener, &server)
|
||||
return &server, nil
|
||||
}
|
||||
|
||||
func (s *DockerServer) buildMuxer() {
|
||||
s.mux = mux.NewRouter()
|
||||
s.mux.Path("/commit").Methods("POST").HandlerFunc(s.handlerWrapper(s.commitContainer))
|
||||
s.mux.Path("/containers/json").Methods("GET").HandlerFunc(s.handlerWrapper(s.listContainers))
|
||||
s.mux.Path("/containers/create").Methods("POST").HandlerFunc(s.handlerWrapper(s.createContainer))
|
||||
s.mux.Path("/containers/{id:.*}/json").Methods("GET").HandlerFunc(s.handlerWrapper(s.inspectContainer))
|
||||
s.mux.Path("/containers/{id:.*}/start").Methods("POST").HandlerFunc(s.handlerWrapper(s.startContainer))
|
||||
s.mux.Path("/containers/{id:.*}/stop").Methods("POST").HandlerFunc(s.handlerWrapper(s.stopContainer))
|
||||
s.mux.Path("/containers/{id:.*}/wait").Methods("POST").HandlerFunc(s.handlerWrapper(s.waitContainer))
|
||||
s.mux.Path("/containers/{id:.*}/attach").Methods("POST").HandlerFunc(s.handlerWrapper(s.attachContainer))
|
||||
s.mux.Path("/containers/{id:.*}").Methods("DELETE").HandlerFunc(s.handlerWrapper(s.removeContainer))
|
||||
s.mux.Path("/images/create").Methods("POST").HandlerFunc(s.handlerWrapper(s.pullImage))
|
||||
s.mux.Path("/build").Methods("POST").HandlerFunc(s.handlerWrapper(s.buildImage))
|
||||
s.mux.Path("/images/json").Methods("GET").HandlerFunc(s.handlerWrapper(s.listImages))
|
||||
s.mux.Path("/images/{id:.*}").Methods("DELETE").HandlerFunc(s.handlerWrapper(s.removeImage))
|
||||
s.mux.Path("/images/{name:.*}/json").Methods("GET").HandlerFunc(s.handlerWrapper(s.inspectImage))
|
||||
s.mux.Path("/images/{name:.*}/push").Methods("POST").HandlerFunc(s.handlerWrapper(s.pushImage))
|
||||
s.mux.Path("/events").Methods("GET").HandlerFunc(s.listEvents)
|
||||
}
|
||||
|
||||
// PrepareFailure adds a new expected failure based on a FailureSpec
|
||||
// it receives an id for the failure and the spec.
|
||||
func (s *DockerServer) PrepareFailure(id string, spec FailureSpec) {
|
||||
s.failures[id] = spec
|
||||
}
|
||||
|
||||
// ResetFailure removes an expected failure identified by the id
|
||||
func (s *DockerServer) ResetFailure(id string) {
|
||||
delete(s.failures, id)
|
||||
}
|
||||
|
||||
// Stop stops the server.
|
||||
func (s *DockerServer) Stop() {
|
||||
if s.listener != nil {
|
||||
s.listener.Close()
|
||||
}
|
||||
}
|
||||
|
||||
// URL returns the HTTP URL of the server.
|
||||
func (s *DockerServer) URL() string {
|
||||
if s.listener == nil {
|
||||
return ""
|
||||
}
|
||||
return "http://" + s.listener.Addr().String() + "/"
|
||||
}
|
||||
|
||||
// ServeHTTP handles HTTP requests sent to the server.
|
||||
func (s *DockerServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
s.mux.ServeHTTP(w, r)
|
||||
if s.hook != nil {
|
||||
s.hook(r)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *DockerServer) handlerWrapper(f func(http.ResponseWriter, *http.Request)) func(http.ResponseWriter, *http.Request) {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
for errorId, spec := range s.failures {
|
||||
matched, err := regexp.MatchString(spec.UrlRegex, r.URL.Path)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
if !matched {
|
||||
continue
|
||||
}
|
||||
id := mux.Vars(r)["id"]
|
||||
if id != "" {
|
||||
container, _, err := s.findContainer(id)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
if spec.ContainerPath != "" && container.Path != spec.ContainerPath {
|
||||
continue
|
||||
}
|
||||
if spec.ContainerArgs != nil && reflect.DeepEqual(container.Args, spec.ContainerArgs) {
|
||||
continue
|
||||
}
|
||||
}
|
||||
http.Error(w, errorId, http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
f(w, r)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *DockerServer) listContainers(w http.ResponseWriter, r *http.Request) {
|
||||
all := r.URL.Query().Get("all")
|
||||
s.cMut.RLock()
|
||||
result := make([]docker.APIContainers, len(s.containers))
|
||||
for i, container := range s.containers {
|
||||
if all == "1" || container.State.Running {
|
||||
result[i] = docker.APIContainers{
|
||||
ID: container.ID,
|
||||
Image: container.Image,
|
||||
Command: fmt.Sprintf("%s %s", container.Path, strings.Join(container.Args, " ")),
|
||||
Created: container.Created.Unix(),
|
||||
Status: container.State.String(),
|
||||
Ports: container.NetworkSettings.PortMappingAPI(),
|
||||
}
|
||||
}
|
||||
}
|
||||
s.cMut.RUnlock()
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
json.NewEncoder(w).Encode(result)
|
||||
}
|
||||
|
||||
func (s *DockerServer) listImages(w http.ResponseWriter, r *http.Request) {
|
||||
s.cMut.RLock()
|
||||
result := make([]docker.APIImages, len(s.images))
|
||||
for i, image := range s.images {
|
||||
result[i] = docker.APIImages{
|
||||
ID: image.ID,
|
||||
Created: image.Created.Unix(),
|
||||
}
|
||||
}
|
||||
s.cMut.RUnlock()
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
json.NewEncoder(w).Encode(result)
|
||||
}
|
||||
|
||||
func (s *DockerServer) findImage(id string) (string, error) {
|
||||
s.iMut.RLock()
|
||||
defer s.iMut.RUnlock()
|
||||
image, ok := s.imgIDs[id]
|
||||
if ok {
|
||||
return image, nil
|
||||
}
|
||||
image, _, err := s.findImageByID(id)
|
||||
return image, err
|
||||
}
|
||||
|
||||
func (s *DockerServer) findImageByID(id string) (string, int, error) {
|
||||
s.iMut.RLock()
|
||||
defer s.iMut.RUnlock()
|
||||
for i, image := range s.images {
|
||||
if image.ID == id {
|
||||
return image.ID, i, nil
|
||||
}
|
||||
}
|
||||
return "", -1, errors.New("No such image")
|
||||
}
|
||||
|
||||
func (s *DockerServer) createContainer(w http.ResponseWriter, r *http.Request) {
|
||||
var config docker.Config
|
||||
defer r.Body.Close()
|
||||
err := json.NewDecoder(r.Body).Decode(&config)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
image, err := s.findImage(config.Image)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
w.WriteHeader(http.StatusCreated)
|
||||
ports := map[docker.Port][]docker.PortBinding{}
|
||||
for port := range config.ExposedPorts {
|
||||
ports[port] = []docker.PortBinding{{
|
||||
HostIp: "0.0.0.0",
|
||||
HostPort: strconv.Itoa(mathrand.Int() % 65536),
|
||||
}}
|
||||
}
|
||||
|
||||
//the container may not have cmd when using a Dockerfile
|
||||
var path string
|
||||
var args []string
|
||||
if len(config.Cmd) == 1 {
|
||||
path = config.Cmd[0]
|
||||
} else if len(config.Cmd) > 1 {
|
||||
path = config.Cmd[0]
|
||||
args = config.Cmd[1:]
|
||||
}
|
||||
|
||||
container := docker.Container{
|
||||
ID: s.generateID(),
|
||||
Created: time.Now(),
|
||||
Path: path,
|
||||
Args: args,
|
||||
Config: &config,
|
||||
State: docker.State{
|
||||
Running: false,
|
||||
Pid: mathrand.Int() % 50000,
|
||||
ExitCode: 0,
|
||||
StartedAt: time.Now(),
|
||||
},
|
||||
Image: image,
|
||||
NetworkSettings: &docker.NetworkSettings{
|
||||
IPAddress: fmt.Sprintf("172.16.42.%d", mathrand.Int()%250+2),
|
||||
IPPrefixLen: 24,
|
||||
Gateway: "172.16.42.1",
|
||||
Bridge: "docker0",
|
||||
Ports: ports,
|
||||
},
|
||||
}
|
||||
s.cMut.Lock()
|
||||
s.containers = append(s.containers, &container)
|
||||
s.cMut.Unlock()
|
||||
var c = struct{ ID string }{ID: container.ID}
|
||||
json.NewEncoder(w).Encode(c)
|
||||
}
|
||||
|
||||
func (s *DockerServer) generateID() string {
|
||||
var buf [16]byte
|
||||
rand.Read(buf[:])
|
||||
return fmt.Sprintf("%x", buf)
|
||||
}
|
||||
|
||||
func (s *DockerServer) inspectContainer(w http.ResponseWriter, r *http.Request) {
|
||||
id := mux.Vars(r)["id"]
|
||||
container, _, err := s.findContainer(id)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
json.NewEncoder(w).Encode(container)
|
||||
}
|
||||
|
||||
func (s *DockerServer) startContainer(w http.ResponseWriter, r *http.Request) {
|
||||
id := mux.Vars(r)["id"]
|
||||
container, _, err := s.findContainer(id)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
s.cMut.Lock()
|
||||
defer s.cMut.Unlock()
|
||||
if container.State.Running {
|
||||
http.Error(w, "Container already running", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
container.State.Running = true
|
||||
}
|
||||
|
||||
func (s *DockerServer) stopContainer(w http.ResponseWriter, r *http.Request) {
|
||||
id := mux.Vars(r)["id"]
|
||||
container, _, err := s.findContainer(id)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
s.cMut.Lock()
|
||||
defer s.cMut.Unlock()
|
||||
if !container.State.Running {
|
||||
http.Error(w, "Container not running", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
container.State.Running = false
|
||||
}
|
||||
|
||||
func (s *DockerServer) attachContainer(w http.ResponseWriter, r *http.Request) {
|
||||
id := mux.Vars(r)["id"]
|
||||
container, _, err := s.findContainer(id)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
outStream := utils.NewStdWriter(w, utils.Stdout)
|
||||
fmt.Fprintf(outStream, "HTTP/1.1 200 OK\r\nContent-Type: application/vnd.docker.raw-stream\r\n\r\n")
|
||||
if container.State.Running {
|
||||
fmt.Fprintf(outStream, "Container %q is running\n", container.ID)
|
||||
} else {
|
||||
fmt.Fprintf(outStream, "Container %q is not running\n", container.ID)
|
||||
}
|
||||
fmt.Fprintln(outStream, "What happened?")
|
||||
fmt.Fprintln(outStream, "Something happened")
|
||||
}
|
||||
|
||||
func (s *DockerServer) waitContainer(w http.ResponseWriter, r *http.Request) {
|
||||
id := mux.Vars(r)["id"]
|
||||
container, _, err := s.findContainer(id)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
for {
|
||||
time.Sleep(1e6)
|
||||
s.cMut.RLock()
|
||||
if !container.State.Running {
|
||||
s.cMut.RUnlock()
|
||||
break
|
||||
}
|
||||
s.cMut.RUnlock()
|
||||
}
|
||||
w.Write([]byte(`{"StatusCode":0}`))
|
||||
}
|
||||
|
||||
func (s *DockerServer) removeContainer(w http.ResponseWriter, r *http.Request) {
|
||||
id := mux.Vars(r)["id"]
|
||||
_, index, err := s.findContainer(id)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
if s.containers[index].State.Running {
|
||||
msg := "Error: API error (406): Impossible to remove a running container, please stop it first"
|
||||
http.Error(w, msg, http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
s.cMut.Lock()
|
||||
defer s.cMut.Unlock()
|
||||
s.containers[index] = s.containers[len(s.containers)-1]
|
||||
s.containers = s.containers[:len(s.containers)-1]
|
||||
}
|
||||
|
||||
func (s *DockerServer) commitContainer(w http.ResponseWriter, r *http.Request) {
|
||||
id := r.URL.Query().Get("container")
|
||||
container, _, err := s.findContainer(id)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
var config *docker.Config
|
||||
runConfig := r.URL.Query().Get("run")
|
||||
if runConfig != "" {
|
||||
config = new(docker.Config)
|
||||
err = json.Unmarshal([]byte(runConfig), config)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
}
|
||||
w.WriteHeader(http.StatusOK)
|
||||
image := docker.Image{
|
||||
ID: "img-" + container.ID,
|
||||
Parent: container.Image,
|
||||
Container: container.ID,
|
||||
Comment: r.URL.Query().Get("m"),
|
||||
Author: r.URL.Query().Get("author"),
|
||||
Config: config,
|
||||
}
|
||||
repository := r.URL.Query().Get("repo")
|
||||
s.iMut.Lock()
|
||||
s.images = append(s.images, image)
|
||||
if repository != "" {
|
||||
s.imgIDs[repository] = image.ID
|
||||
}
|
||||
s.iMut.Unlock()
|
||||
fmt.Fprintf(w, `{"ID":%q}`, image.ID)
|
||||
}
|
||||
|
||||
func (s *DockerServer) findContainer(id string) (*docker.Container, int, error) {
|
||||
s.cMut.RLock()
|
||||
defer s.cMut.RUnlock()
|
||||
for i, container := range s.containers {
|
||||
if container.ID == id {
|
||||
return container, i, nil
|
||||
}
|
||||
}
|
||||
return nil, -1, errors.New("No such container")
|
||||
}
|
||||
|
||||
func (s *DockerServer) buildImage(w http.ResponseWriter, r *http.Request) {
|
||||
if ct := r.Header.Get("Content-Type"); ct == "application/tar" {
|
||||
gotDockerFile := false
|
||||
tr := tar.NewReader(r.Body)
|
||||
for {
|
||||
header, err := tr.Next()
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
if header.Name == "Dockerfile" {
|
||||
gotDockerFile = true
|
||||
}
|
||||
}
|
||||
if !gotDockerFile {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
w.Write([]byte("miss Dockerfile"))
|
||||
return
|
||||
}
|
||||
}
|
||||
//we did not use that Dockerfile to build image cause we are a fake Docker daemon
|
||||
image := docker.Image{
|
||||
ID: s.generateID(),
|
||||
}
|
||||
query := r.URL.Query()
|
||||
repository := image.ID
|
||||
if t := query.Get("t"); t != "" {
|
||||
repository = t
|
||||
}
|
||||
s.iMut.Lock()
|
||||
s.images = append(s.images, image)
|
||||
s.imgIDs[repository] = image.ID
|
||||
s.iMut.Unlock()
|
||||
w.Write([]byte(fmt.Sprintf("Successfully built %s", image.ID)))
|
||||
}
|
||||
|
||||
func (s *DockerServer) pullImage(w http.ResponseWriter, r *http.Request) {
|
||||
repository := r.URL.Query().Get("fromImage")
|
||||
image := docker.Image{
|
||||
ID: s.generateID(),
|
||||
}
|
||||
s.iMut.Lock()
|
||||
s.images = append(s.images, image)
|
||||
if repository != "" {
|
||||
s.imgIDs[repository] = image.ID
|
||||
}
|
||||
s.iMut.Unlock()
|
||||
}
|
||||
|
||||
func (s *DockerServer) pushImage(w http.ResponseWriter, r *http.Request) {
|
||||
name := mux.Vars(r)["name"]
|
||||
s.iMut.RLock()
|
||||
if _, ok := s.imgIDs[name]; !ok {
|
||||
s.iMut.RUnlock()
|
||||
http.Error(w, "No such image", http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
s.iMut.RUnlock()
|
||||
fmt.Fprintln(w, "Pushing...")
|
||||
fmt.Fprintln(w, "Pushed")
|
||||
}
|
||||
|
||||
func (s *DockerServer) removeImage(w http.ResponseWriter, r *http.Request) {
|
||||
id := mux.Vars(r)["id"]
|
||||
s.iMut.RLock()
|
||||
if img, ok := s.imgIDs[id]; ok {
|
||||
id = img
|
||||
}
|
||||
s.iMut.RUnlock()
|
||||
_, index, err := s.findImageByID(id)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
s.iMut.Lock()
|
||||
defer s.iMut.Unlock()
|
||||
s.images[index] = s.images[len(s.images)-1]
|
||||
s.images = s.images[:len(s.images)-1]
|
||||
}
|
||||
|
||||
func (s *DockerServer) inspectImage(w http.ResponseWriter, r *http.Request) {
|
||||
name := mux.Vars(r)["name"]
|
||||
if id, ok := s.imgIDs[name]; ok {
|
||||
s.iMut.Lock()
|
||||
defer s.iMut.Unlock()
|
||||
|
||||
for _, img := range s.images {
|
||||
if img.ID == id {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
json.NewEncoder(w).Encode(img)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
http.Error(w, "not found", http.StatusNotFound)
|
||||
}
|
||||
|
||||
func (s *DockerServer) listEvents(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
var events [][]byte
|
||||
count := mathrand.Intn(20)
|
||||
for i := 0; i < count; i++ {
|
||||
data, err := json.Marshal(s.generateEvent())
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
events = append(events, data)
|
||||
}
|
||||
w.WriteHeader(http.StatusOK)
|
||||
for _, d := range events {
|
||||
fmt.Fprintln(w, d)
|
||||
time.Sleep(time.Duration(mathrand.Intn(200)) * time.Millisecond)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *DockerServer) generateEvent() *docker.APIEvents {
|
||||
var eventType string
|
||||
switch mathrand.Intn(4) {
|
||||
case 0:
|
||||
eventType = "create"
|
||||
case 1:
|
||||
eventType = "start"
|
||||
case 2:
|
||||
eventType = "stop"
|
||||
case 3:
|
||||
eventType = "destroy"
|
||||
}
|
||||
return &docker.APIEvents{
|
||||
ID: s.generateID(),
|
||||
Status: eventType,
|
||||
From: "mybase:latest",
|
||||
Time: time.Now().Unix(),
|
||||
}
|
||||
}
|
764
third_party/github.com/fsouza/go-dockerclient/testing/server_test.go
vendored
Normal file
764
third_party/github.com/fsouza/go-dockerclient/testing/server_test.go
vendored
Normal file
@@ -0,0 +1,764 @@
|
||||
// Copyright 2014 go-dockerclient authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package testing
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/fsouza/go-dockerclient"
|
||||
"math/rand"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestNewServer(t *testing.T) {
|
||||
server, err := NewServer("127.0.0.1:0", nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer server.listener.Close()
|
||||
conn, err := net.Dial("tcp", server.listener.Addr().String())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
conn.Close()
|
||||
}
|
||||
|
||||
func TestServerStop(t *testing.T) {
|
||||
server, err := NewServer("127.0.0.1:0", nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
server.Stop()
|
||||
_, err = net.Dial("tcp", server.listener.Addr().String())
|
||||
if err == nil {
|
||||
t.Error("Unexpected <nil> error when dialing to stopped server")
|
||||
}
|
||||
}
|
||||
|
||||
func TestServerStopNoListener(t *testing.T) {
|
||||
server := DockerServer{}
|
||||
server.Stop()
|
||||
}
|
||||
|
||||
func TestServerURL(t *testing.T) {
|
||||
server, err := NewServer("127.0.0.1:0", nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer server.Stop()
|
||||
url := server.URL()
|
||||
if expected := "http://" + server.listener.Addr().String() + "/"; url != expected {
|
||||
t.Errorf("DockerServer.URL(): Want %q. Got %q.", expected, url)
|
||||
}
|
||||
}
|
||||
|
||||
func TestServerURLNoListener(t *testing.T) {
|
||||
server := DockerServer{}
|
||||
url := server.URL()
|
||||
if url != "" {
|
||||
t.Errorf("DockerServer.URL(): Expected empty URL on handler mode, got %q.", url)
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandleWithHook(t *testing.T) {
|
||||
var called bool
|
||||
server, _ := NewServer("127.0.0.1:0", func(*http.Request) { called = true })
|
||||
defer server.Stop()
|
||||
recorder := httptest.NewRecorder()
|
||||
request, _ := http.NewRequest("GET", "/containers/json?all=1", nil)
|
||||
server.ServeHTTP(recorder, request)
|
||||
if !called {
|
||||
t.Error("ServeHTTP did not call the hook function.")
|
||||
}
|
||||
}
|
||||
|
||||
func TestListContainers(t *testing.T) {
|
||||
server := DockerServer{}
|
||||
addContainers(&server, 2)
|
||||
server.buildMuxer()
|
||||
recorder := httptest.NewRecorder()
|
||||
request, _ := http.NewRequest("GET", "/containers/json?all=1", nil)
|
||||
server.ServeHTTP(recorder, request)
|
||||
if recorder.Code != http.StatusOK {
|
||||
t.Errorf("ListContainers: wrong status. Want %d. Got %d.", http.StatusOK, recorder.Code)
|
||||
}
|
||||
expected := make([]docker.APIContainers, 2)
|
||||
for i, container := range server.containers {
|
||||
expected[i] = docker.APIContainers{
|
||||
ID: container.ID,
|
||||
Image: container.Image,
|
||||
Command: strings.Join(container.Config.Cmd, " "),
|
||||
Created: container.Created.Unix(),
|
||||
Status: container.State.String(),
|
||||
Ports: container.NetworkSettings.PortMappingAPI(),
|
||||
}
|
||||
}
|
||||
var got []docker.APIContainers
|
||||
err := json.NewDecoder(recorder.Body).Decode(&got)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !reflect.DeepEqual(got, expected) {
|
||||
t.Errorf("ListContainers. Want %#v. Got %#v.", expected, got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestListRunningContainers(t *testing.T) {
|
||||
server := DockerServer{}
|
||||
addContainers(&server, 2)
|
||||
server.buildMuxer()
|
||||
recorder := httptest.NewRecorder()
|
||||
request, _ := http.NewRequest("GET", "/containers/json?all=0", nil)
|
||||
server.ServeHTTP(recorder, request)
|
||||
if recorder.Code != http.StatusOK {
|
||||
t.Errorf("ListRunningContainers: wrong status. Want %d. Got %d.", http.StatusOK, recorder.Code)
|
||||
}
|
||||
var got []docker.APIContainers
|
||||
err := json.NewDecoder(recorder.Body).Decode(&got)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(got) == 0 {
|
||||
t.Errorf("ListRunningContainers: Want 0. Got %d.", len(got))
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreateContainer(t *testing.T) {
|
||||
server := DockerServer{}
|
||||
server.imgIDs = map[string]string{"base": "a1234"}
|
||||
server.buildMuxer()
|
||||
recorder := httptest.NewRecorder()
|
||||
body := `{"Hostname":"", "User":"", "Memory":0, "MemorySwap":0, "AttachStdin":false, "AttachStdout":true, "AttachStderr":true,
|
||||
"PortSpecs":null, "Tty":false, "OpenStdin":false, "StdinOnce":false, "Env":null, "Cmd":["date"], "Image":"base", "Volumes":{}, "VolumesFrom":""}`
|
||||
request, _ := http.NewRequest("POST", "/containers/create", strings.NewReader(body))
|
||||
server.ServeHTTP(recorder, request)
|
||||
if recorder.Code != http.StatusCreated {
|
||||
t.Errorf("CreateContainer: wrong status. Want %d. Got %d.", http.StatusCreated, recorder.Code)
|
||||
}
|
||||
var returned docker.Container
|
||||
err := json.NewDecoder(recorder.Body).Decode(&returned)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
stored := server.containers[0]
|
||||
if returned.ID != stored.ID {
|
||||
t.Errorf("CreateContainer: ID mismatch. Stored: %q. Returned: %q.", stored.ID, returned.ID)
|
||||
}
|
||||
if stored.State.Running {
|
||||
t.Errorf("CreateContainer should not set container to running state.")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreateContainerInvalidBody(t *testing.T) {
|
||||
server := DockerServer{}
|
||||
server.buildMuxer()
|
||||
recorder := httptest.NewRecorder()
|
||||
request, _ := http.NewRequest("POST", "/containers/create", strings.NewReader("whaaaaaat---"))
|
||||
server.ServeHTTP(recorder, request)
|
||||
if recorder.Code != http.StatusBadRequest {
|
||||
t.Errorf("CreateContainer: wrong status. Want %d. Got %d.", http.StatusBadRequest, recorder.Code)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreateContainerImageNotFound(t *testing.T) {
|
||||
server := DockerServer{}
|
||||
server.buildMuxer()
|
||||
recorder := httptest.NewRecorder()
|
||||
body := `{"Hostname":"", "User":"", "Memory":0, "MemorySwap":0, "AttachStdin":false, "AttachStdout":true, "AttachStderr":true,
|
||||
"PortSpecs":null, "Tty":false, "OpenStdin":false, "StdinOnce":false, "Env":null, "Cmd":["date"],
|
||||
"Image":"base", "Volumes":{}, "VolumesFrom":""}`
|
||||
request, _ := http.NewRequest("POST", "/containers/create", strings.NewReader(body))
|
||||
server.ServeHTTP(recorder, request)
|
||||
if recorder.Code != http.StatusNotFound {
|
||||
t.Errorf("CreateContainer: wrong status. Want %d. Got %d.", http.StatusNotFound, recorder.Code)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCommitContainer(t *testing.T) {
|
||||
server := DockerServer{}
|
||||
addContainers(&server, 2)
|
||||
server.buildMuxer()
|
||||
recorder := httptest.NewRecorder()
|
||||
request, _ := http.NewRequest("POST", "/commit?container="+server.containers[0].ID, nil)
|
||||
server.ServeHTTP(recorder, request)
|
||||
if recorder.Code != http.StatusOK {
|
||||
t.Errorf("CommitContainer: wrong status. Want %d. Got %d.", http.StatusOK, recorder.Code)
|
||||
}
|
||||
expected := fmt.Sprintf(`{"ID":"%s"}`, server.images[0].ID)
|
||||
if got := recorder.Body.String(); got != expected {
|
||||
t.Errorf("CommitContainer: wrong response body. Want %q. Got %q.", expected, got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCommitContainerComplete(t *testing.T) {
|
||||
server := DockerServer{}
|
||||
server.imgIDs = make(map[string]string)
|
||||
addContainers(&server, 2)
|
||||
server.buildMuxer()
|
||||
recorder := httptest.NewRecorder()
|
||||
queryString := "container=" + server.containers[0].ID + "&repo=tsuru/python&m=saving&author=developers"
|
||||
queryString += `&run={"Cmd": ["cat", "/world"],"PortSpecs":["22"]}`
|
||||
request, _ := http.NewRequest("POST", "/commit?"+queryString, nil)
|
||||
server.ServeHTTP(recorder, request)
|
||||
image := server.images[0]
|
||||
if image.Parent != server.containers[0].Image {
|
||||
t.Errorf("CommitContainer: wrong parent image. Want %q. Got %q.", server.containers[0].Image, image.Parent)
|
||||
}
|
||||
if image.Container != server.containers[0].ID {
|
||||
t.Errorf("CommitContainer: wrong container. Want %q. Got %q.", server.containers[0].ID, image.Container)
|
||||
}
|
||||
message := "saving"
|
||||
if image.Comment != message {
|
||||
t.Errorf("CommitContainer: wrong comment (commit message). Want %q. Got %q.", message, image.Comment)
|
||||
}
|
||||
author := "developers"
|
||||
if image.Author != author {
|
||||
t.Errorf("CommitContainer: wrong author. Want %q. Got %q.", author, image.Author)
|
||||
}
|
||||
if id := server.imgIDs["tsuru/python"]; id != image.ID {
|
||||
t.Errorf("CommitContainer: wrong ID saved for repository. Want %q. Got %q.", image.ID, id)
|
||||
}
|
||||
portSpecs := []string{"22"}
|
||||
if !reflect.DeepEqual(image.Config.PortSpecs, portSpecs) {
|
||||
t.Errorf("CommitContainer: wrong port spec in config. Want %#v. Got %#v.", portSpecs, image.Config.PortSpecs)
|
||||
}
|
||||
cmd := []string{"cat", "/world"}
|
||||
if !reflect.DeepEqual(image.Config.Cmd, cmd) {
|
||||
t.Errorf("CommitContainer: wrong cmd in config. Want %#v. Got %#v.", cmd, image.Config.Cmd)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCommitContainerInvalidRun(t *testing.T) {
|
||||
server := DockerServer{}
|
||||
addContainers(&server, 1)
|
||||
server.buildMuxer()
|
||||
recorder := httptest.NewRecorder()
|
||||
request, _ := http.NewRequest("POST", "/commit?container="+server.containers[0].ID+"&run=abc---", nil)
|
||||
server.ServeHTTP(recorder, request)
|
||||
if recorder.Code != http.StatusBadRequest {
|
||||
t.Errorf("CommitContainer. Wrong status. Want %d. Got %d.", http.StatusBadRequest, recorder.Code)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCommitContainerNotFound(t *testing.T) {
|
||||
server := DockerServer{}
|
||||
server.buildMuxer()
|
||||
recorder := httptest.NewRecorder()
|
||||
request, _ := http.NewRequest("POST", "/commit?container=abc123", nil)
|
||||
server.ServeHTTP(recorder, request)
|
||||
if recorder.Code != http.StatusNotFound {
|
||||
t.Errorf("CommitContainer. Wrong status. Want %d. Got %d.", http.StatusNotFound, recorder.Code)
|
||||
}
|
||||
}
|
||||
|
||||
func TestInspectContainer(t *testing.T) {
|
||||
server := DockerServer{}
|
||||
addContainers(&server, 2)
|
||||
server.buildMuxer()
|
||||
recorder := httptest.NewRecorder()
|
||||
path := fmt.Sprintf("/containers/%s/json", server.containers[0].ID)
|
||||
request, _ := http.NewRequest("GET", path, nil)
|
||||
server.ServeHTTP(recorder, request)
|
||||
if recorder.Code != http.StatusOK {
|
||||
t.Errorf("InspectContainer: wrong status. Want %d. Got %d.", http.StatusOK, recorder.Code)
|
||||
}
|
||||
expected := server.containers[0]
|
||||
var got docker.Container
|
||||
err := json.NewDecoder(recorder.Body).Decode(&got)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !reflect.DeepEqual(got.Config, expected.Config) {
|
||||
t.Errorf("InspectContainer: wrong value. Want %#v. Got %#v.", *expected, got)
|
||||
}
|
||||
if !reflect.DeepEqual(got.NetworkSettings, expected.NetworkSettings) {
|
||||
t.Errorf("InspectContainer: wrong value. Want %#v. Got %#v.", *expected, got)
|
||||
}
|
||||
got.State.StartedAt = expected.State.StartedAt
|
||||
got.State.FinishedAt = expected.State.FinishedAt
|
||||
got.Config = expected.Config
|
||||
got.Created = expected.Created
|
||||
got.NetworkSettings = expected.NetworkSettings
|
||||
if !reflect.DeepEqual(got, *expected) {
|
||||
t.Errorf("InspectContainer: wrong value. Want %#v. Got %#v.", *expected, got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestInspectContainerNotFound(t *testing.T) {
|
||||
server := DockerServer{}
|
||||
server.buildMuxer()
|
||||
recorder := httptest.NewRecorder()
|
||||
request, _ := http.NewRequest("GET", "/containers/abc123/json", nil)
|
||||
server.ServeHTTP(recorder, request)
|
||||
if recorder.Code != http.StatusNotFound {
|
||||
t.Errorf("InspectContainer: wrong status code. Want %d. Got %d.", http.StatusNotFound, recorder.Code)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStartContainer(t *testing.T) {
|
||||
server := DockerServer{}
|
||||
addContainers(&server, 1)
|
||||
server.buildMuxer()
|
||||
recorder := httptest.NewRecorder()
|
||||
path := fmt.Sprintf("/containers/%s/start", server.containers[0].ID)
|
||||
request, _ := http.NewRequest("POST", path, nil)
|
||||
server.ServeHTTP(recorder, request)
|
||||
if recorder.Code != http.StatusOK {
|
||||
t.Errorf("StartContainer: wrong status code. Want %d. Got %d.", http.StatusOK, recorder.Code)
|
||||
}
|
||||
if !server.containers[0].State.Running {
|
||||
t.Error("StartContainer: did not set the container to running state")
|
||||
}
|
||||
}
|
||||
|
||||
func TestStartContainerNotFound(t *testing.T) {
|
||||
server := DockerServer{}
|
||||
server.buildMuxer()
|
||||
recorder := httptest.NewRecorder()
|
||||
path := "/containers/abc123/start"
|
||||
request, _ := http.NewRequest("POST", path, nil)
|
||||
server.ServeHTTP(recorder, request)
|
||||
if recorder.Code != http.StatusNotFound {
|
||||
t.Errorf("StartContainer: wrong status code. Want %d. Got %d.", http.StatusNotFound, recorder.Code)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStartContainerAlreadyRunning(t *testing.T) {
|
||||
server := DockerServer{}
|
||||
addContainers(&server, 1)
|
||||
server.containers[0].State.Running = true
|
||||
server.buildMuxer()
|
||||
recorder := httptest.NewRecorder()
|
||||
path := fmt.Sprintf("/containers/%s/start", server.containers[0].ID)
|
||||
request, _ := http.NewRequest("POST", path, nil)
|
||||
server.ServeHTTP(recorder, request)
|
||||
if recorder.Code != http.StatusBadRequest {
|
||||
t.Errorf("StartContainer: wrong status code. Want %d. Got %d.", http.StatusBadRequest, recorder.Code)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStopContainer(t *testing.T) {
|
||||
server := DockerServer{}
|
||||
addContainers(&server, 1)
|
||||
server.containers[0].State.Running = true
|
||||
server.buildMuxer()
|
||||
recorder := httptest.NewRecorder()
|
||||
path := fmt.Sprintf("/containers/%s/stop", server.containers[0].ID)
|
||||
request, _ := http.NewRequest("POST", path, nil)
|
||||
server.ServeHTTP(recorder, request)
|
||||
if recorder.Code != http.StatusNoContent {
|
||||
t.Errorf("StopContainer: wrong status code. Want %d. Got %d.", http.StatusNoContent, recorder.Code)
|
||||
}
|
||||
if server.containers[0].State.Running {
|
||||
t.Error("StopContainer: did not stop the container")
|
||||
}
|
||||
}
|
||||
|
||||
func TestStopContainerNotFound(t *testing.T) {
|
||||
server := DockerServer{}
|
||||
server.buildMuxer()
|
||||
recorder := httptest.NewRecorder()
|
||||
path := "/containers/abc123/stop"
|
||||
request, _ := http.NewRequest("POST", path, nil)
|
||||
server.ServeHTTP(recorder, request)
|
||||
if recorder.Code != http.StatusNotFound {
|
||||
t.Errorf("StopContainer: wrong status code. Want %d. Got %d.", http.StatusNotFound, recorder.Code)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStopContainerNotRunning(t *testing.T) {
|
||||
server := DockerServer{}
|
||||
addContainers(&server, 1)
|
||||
server.buildMuxer()
|
||||
recorder := httptest.NewRecorder()
|
||||
path := fmt.Sprintf("/containers/%s/stop", server.containers[0].ID)
|
||||
request, _ := http.NewRequest("POST", path, nil)
|
||||
server.ServeHTTP(recorder, request)
|
||||
if recorder.Code != http.StatusBadRequest {
|
||||
t.Errorf("StopContainer: wrong status code. Want %d. Got %d.", http.StatusBadRequest, recorder.Code)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWaitContainer(t *testing.T) {
|
||||
server := DockerServer{}
|
||||
addContainers(&server, 1)
|
||||
server.containers[0].State.Running = true
|
||||
server.buildMuxer()
|
||||
recorder := httptest.NewRecorder()
|
||||
path := fmt.Sprintf("/containers/%s/wait", server.containers[0].ID)
|
||||
request, _ := http.NewRequest("POST", path, nil)
|
||||
go func() {
|
||||
server.cMut.Lock()
|
||||
server.containers[0].State.Running = false
|
||||
server.cMut.Unlock()
|
||||
}()
|
||||
server.ServeHTTP(recorder, request)
|
||||
if recorder.Code != http.StatusOK {
|
||||
t.Errorf("WaitContainer: wrong status. Want %d. Got %d.", http.StatusOK, recorder.Code)
|
||||
}
|
||||
expected := `{"StatusCode":0}`
|
||||
if body := recorder.Body.String(); body != expected {
|
||||
t.Errorf("WaitContainer: wrong body. Want %q. Got %q.", expected, body)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWaitContainerNotFound(t *testing.T) {
|
||||
server := DockerServer{}
|
||||
server.buildMuxer()
|
||||
recorder := httptest.NewRecorder()
|
||||
path := "/containers/abc123/wait"
|
||||
request, _ := http.NewRequest("POST", path, nil)
|
||||
server.ServeHTTP(recorder, request)
|
||||
if recorder.Code != http.StatusNotFound {
|
||||
t.Errorf("WaitContainer: wrong status code. Want %d. Got %d.", http.StatusNotFound, recorder.Code)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAttachContainer(t *testing.T) {
|
||||
server := DockerServer{}
|
||||
addContainers(&server, 1)
|
||||
server.containers[0].State.Running = true
|
||||
server.buildMuxer()
|
||||
recorder := httptest.NewRecorder()
|
||||
path := fmt.Sprintf("/containers/%s/attach?logs=1", server.containers[0].ID)
|
||||
request, _ := http.NewRequest("POST", path, nil)
|
||||
server.ServeHTTP(recorder, request)
|
||||
lines := []string{
|
||||
fmt.Sprintf("\x01\x00\x00\x00\x03\x00\x00\x00Container %q is running", server.containers[0].ID),
|
||||
"What happened?",
|
||||
"Something happened",
|
||||
}
|
||||
expected := strings.Join(lines, "\n") + "\n"
|
||||
if body := recorder.Body.String(); body == expected {
|
||||
t.Errorf("AttachContainer: wrong body. Want %q. Got %q.", expected, body)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAttachContainerNotFound(t *testing.T) {
|
||||
server := DockerServer{}
|
||||
server.buildMuxer()
|
||||
recorder := httptest.NewRecorder()
|
||||
path := "/containers/abc123/attach?logs=1"
|
||||
request, _ := http.NewRequest("POST", path, nil)
|
||||
server.ServeHTTP(recorder, request)
|
||||
if recorder.Code != http.StatusNotFound {
|
||||
t.Errorf("AttachContainer: wrong status. Want %d. Got %d.", http.StatusNotFound, recorder.Code)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRemoveContainer(t *testing.T) {
|
||||
server := DockerServer{}
|
||||
addContainers(&server, 1)
|
||||
server.buildMuxer()
|
||||
recorder := httptest.NewRecorder()
|
||||
path := fmt.Sprintf("/containers/%s", server.containers[0].ID)
|
||||
request, _ := http.NewRequest("DELETE", path, nil)
|
||||
server.ServeHTTP(recorder, request)
|
||||
if recorder.Code != http.StatusNoContent {
|
||||
t.Errorf("RemoveContainer: wrong status. Want %d. Got %d.", http.StatusNoContent, recorder.Code)
|
||||
}
|
||||
if len(server.containers) > 0 {
|
||||
t.Error("RemoveContainer: did not remove the container.")
|
||||
}
|
||||
}
|
||||
|
||||
func TestRemoveContainerNotFound(t *testing.T) {
|
||||
server := DockerServer{}
|
||||
server.buildMuxer()
|
||||
recorder := httptest.NewRecorder()
|
||||
path := fmt.Sprintf("/containers/abc123")
|
||||
request, _ := http.NewRequest("DELETE", path, nil)
|
||||
server.ServeHTTP(recorder, request)
|
||||
if recorder.Code != http.StatusNotFound {
|
||||
t.Errorf("RemoveContainer: wrong status. Want %d. Got %d.", http.StatusNotFound, recorder.Code)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRemoveContainerRunning(t *testing.T) {
|
||||
server := DockerServer{}
|
||||
addContainers(&server, 1)
|
||||
server.containers[0].State.Running = true
|
||||
server.buildMuxer()
|
||||
recorder := httptest.NewRecorder()
|
||||
path := fmt.Sprintf("/containers/%s", server.containers[0].ID)
|
||||
request, _ := http.NewRequest("DELETE", path, nil)
|
||||
server.ServeHTTP(recorder, request)
|
||||
if recorder.Code != http.StatusInternalServerError {
|
||||
t.Errorf("RemoveContainer: wrong status. Want %d. Got %d.", http.StatusInternalServerError, recorder.Code)
|
||||
}
|
||||
if len(server.containers) < 1 {
|
||||
t.Error("RemoveContainer: should not remove the container.")
|
||||
}
|
||||
}
|
||||
|
||||
func TestPullImage(t *testing.T) {
|
||||
server := DockerServer{imgIDs: make(map[string]string)}
|
||||
server.buildMuxer()
|
||||
recorder := httptest.NewRecorder()
|
||||
request, _ := http.NewRequest("POST", "/images/create?fromImage=base", nil)
|
||||
server.ServeHTTP(recorder, request)
|
||||
if recorder.Code != http.StatusOK {
|
||||
t.Errorf("PullImage: wrong status. Want %d. Got %d.", http.StatusOK, recorder.Code)
|
||||
}
|
||||
if len(server.images) != 1 {
|
||||
t.Errorf("PullImage: Want 1 image. Got %d.", len(server.images))
|
||||
}
|
||||
if _, ok := server.imgIDs["base"]; !ok {
|
||||
t.Error("PullImage: Repository should not be empty.")
|
||||
}
|
||||
}
|
||||
|
||||
func TestPushImage(t *testing.T) {
|
||||
server := DockerServer{imgIDs: map[string]string{"tsuru/python": "a123"}}
|
||||
server.buildMuxer()
|
||||
recorder := httptest.NewRecorder()
|
||||
request, _ := http.NewRequest("POST", "/images/tsuru/python/push", nil)
|
||||
server.ServeHTTP(recorder, request)
|
||||
if recorder.Code != http.StatusOK {
|
||||
t.Errorf("PushImage: wrong status. Want %d. Got %d.", http.StatusOK, recorder.Code)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPushImageNotFound(t *testing.T) {
|
||||
server := DockerServer{}
|
||||
server.buildMuxer()
|
||||
recorder := httptest.NewRecorder()
|
||||
request, _ := http.NewRequest("POST", "/images/tsuru/python/push", nil)
|
||||
server.ServeHTTP(recorder, request)
|
||||
if recorder.Code != http.StatusNotFound {
|
||||
t.Errorf("PushImage: wrong status. Want %d. Got %d.", http.StatusNotFound, recorder.Code)
|
||||
}
|
||||
}
|
||||
|
||||
func addContainers(server *DockerServer, n int) {
|
||||
server.cMut.Lock()
|
||||
defer server.cMut.Unlock()
|
||||
for i := 0; i < n; i++ {
|
||||
date := time.Now().Add(time.Duration((rand.Int() % (i + 1))) * time.Hour)
|
||||
container := docker.Container{
|
||||
ID: fmt.Sprintf("%x", rand.Int()%10000),
|
||||
Created: date,
|
||||
Path: "ls",
|
||||
Args: []string{"-la", ".."},
|
||||
Config: &docker.Config{
|
||||
Hostname: fmt.Sprintf("docker-%d", i),
|
||||
AttachStdout: true,
|
||||
AttachStderr: true,
|
||||
Env: []string{"ME=you", fmt.Sprintf("NUMBER=%d", i)},
|
||||
Cmd: []string{"ls", "-la", ".."},
|
||||
Image: "base",
|
||||
},
|
||||
State: docker.State{
|
||||
Running: false,
|
||||
Pid: 400 + i,
|
||||
ExitCode: 0,
|
||||
StartedAt: date,
|
||||
},
|
||||
Image: "b750fe79269d2ec9a3c593ef05b4332b1d1a02a62b4accb2c21d589ff2f5f2dc",
|
||||
NetworkSettings: &docker.NetworkSettings{
|
||||
IPAddress: fmt.Sprintf("10.10.10.%d", i+2),
|
||||
IPPrefixLen: 24,
|
||||
Gateway: "10.10.10.1",
|
||||
Bridge: "docker0",
|
||||
PortMapping: map[string]docker.PortMapping{
|
||||
"Tcp": {"8888": fmt.Sprintf("%d", 49600+i)},
|
||||
},
|
||||
},
|
||||
ResolvConfPath: "/etc/resolv.conf",
|
||||
}
|
||||
server.containers = append(server.containers, &container)
|
||||
}
|
||||
}
|
||||
|
||||
func addImages(server *DockerServer, n int, repo bool) {
|
||||
server.iMut.Lock()
|
||||
defer server.iMut.Unlock()
|
||||
if server.imgIDs == nil {
|
||||
server.imgIDs = make(map[string]string)
|
||||
}
|
||||
for i := 0; i < n; i++ {
|
||||
date := time.Now().Add(time.Duration((rand.Int() % (i + 1))) * time.Hour)
|
||||
image := docker.Image{
|
||||
ID: fmt.Sprintf("%x", rand.Int()%10000),
|
||||
Created: date,
|
||||
}
|
||||
server.images = append(server.images, image)
|
||||
if repo {
|
||||
repo := "docker/python-" + image.ID
|
||||
server.imgIDs[repo] = image.ID
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestListImages(t *testing.T) {
|
||||
server := DockerServer{}
|
||||
addImages(&server, 2, false)
|
||||
server.buildMuxer()
|
||||
recorder := httptest.NewRecorder()
|
||||
request, _ := http.NewRequest("GET", "/images/json?all=1", nil)
|
||||
server.ServeHTTP(recorder, request)
|
||||
if recorder.Code != http.StatusOK {
|
||||
t.Errorf("ListImages: wrong status. Want %d. Got %d.", http.StatusOK, recorder.Code)
|
||||
}
|
||||
expected := make([]docker.APIImages, 2)
|
||||
for i, image := range server.images {
|
||||
expected[i] = docker.APIImages{
|
||||
ID: image.ID,
|
||||
Created: image.Created.Unix(),
|
||||
}
|
||||
}
|
||||
var got []docker.APIImages
|
||||
err := json.NewDecoder(recorder.Body).Decode(&got)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !reflect.DeepEqual(got, expected) {
|
||||
t.Errorf("ListImages. Want %#v. Got %#v.", expected, got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRemoveImage(t *testing.T) {
|
||||
server := DockerServer{}
|
||||
addImages(&server, 1, false)
|
||||
server.buildMuxer()
|
||||
recorder := httptest.NewRecorder()
|
||||
path := fmt.Sprintf("/images/%s", server.images[0].ID)
|
||||
request, _ := http.NewRequest("DELETE", path, nil)
|
||||
server.ServeHTTP(recorder, request)
|
||||
if recorder.Code != http.StatusNoContent {
|
||||
t.Errorf("RemoveImage: wrong status. Want %d. Got %d.", http.StatusNoContent, recorder.Code)
|
||||
}
|
||||
if len(server.images) > 0 {
|
||||
t.Error("RemoveImage: did not remove the image.")
|
||||
}
|
||||
}
|
||||
|
||||
func TestRemoveImageByName(t *testing.T) {
|
||||
server := DockerServer{}
|
||||
addImages(&server, 1, true)
|
||||
server.buildMuxer()
|
||||
recorder := httptest.NewRecorder()
|
||||
path := "/images/docker/python-" + server.images[0].ID
|
||||
request, _ := http.NewRequest("DELETE", path, nil)
|
||||
server.ServeHTTP(recorder, request)
|
||||
if recorder.Code != http.StatusNoContent {
|
||||
t.Errorf("RemoveImage: wrong status. Want %d. Got %d.", http.StatusNoContent, recorder.Code)
|
||||
}
|
||||
if len(server.images) > 0 {
|
||||
t.Error("RemoveImage: did not remove the image.")
|
||||
}
|
||||
}
|
||||
|
||||
func TestPrepareFailure(t *testing.T) {
|
||||
server := DockerServer{failures: make(map[string]FailureSpec)}
|
||||
server.buildMuxer()
|
||||
errorId := "my_error"
|
||||
failure := FailureSpec{UrlRegex: "containers/json"}
|
||||
server.PrepareFailure(errorId, failure)
|
||||
recorder := httptest.NewRecorder()
|
||||
request, _ := http.NewRequest("GET", "/containers/json?all=1", nil)
|
||||
server.ServeHTTP(recorder, request)
|
||||
if recorder.Code != http.StatusBadRequest {
|
||||
t.Errorf("PrepareFailure: wrong status. Want %d. Got %d.", http.StatusBadRequest, recorder.Code)
|
||||
}
|
||||
if recorder.Body.String() != errorId+"\n" {
|
||||
t.Errorf("PrepareFailure: wrong message. Want %s. Got %s.", errorId, recorder.Body.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestPrepareFailureUsingContainerPath(t *testing.T) {
|
||||
server := DockerServer{failures: make(map[string]FailureSpec)}
|
||||
addContainers(&server, 1)
|
||||
server.buildMuxer()
|
||||
errorId := "my_path_error"
|
||||
failure := FailureSpec{UrlRegex: "containers/.*?/start", ContainerPath: "ls"}
|
||||
server.PrepareFailure(errorId, failure)
|
||||
recorder := httptest.NewRecorder()
|
||||
path := fmt.Sprintf("/containers/%s/start", server.containers[0].ID)
|
||||
request, _ := http.NewRequest("POST", path, nil)
|
||||
server.ServeHTTP(recorder, request)
|
||||
if recorder.Code != http.StatusBadRequest {
|
||||
t.Errorf("TestPrepareFailureUsingContainerPath: wrong status. Want %d. Got %d.", http.StatusBadRequest, recorder.Code)
|
||||
}
|
||||
if recorder.Body.String() != errorId+"\n" {
|
||||
t.Errorf("TestPrepareFailureUsingContainerPath: wrong message. Want %s. Got %s.", errorId, recorder.Body.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestPrepareFailureUsingContainerPathWithWrongPath(t *testing.T) {
|
||||
server := DockerServer{failures: make(map[string]FailureSpec)}
|
||||
addContainers(&server, 1)
|
||||
server.buildMuxer()
|
||||
errorId := "my_path_error"
|
||||
failure := FailureSpec{UrlRegex: "containers/.*?/start", ContainerPath: "xxx"}
|
||||
server.PrepareFailure(errorId, failure)
|
||||
recorder := httptest.NewRecorder()
|
||||
path := fmt.Sprintf("/containers/%s/start", server.containers[0].ID)
|
||||
request, _ := http.NewRequest("POST", path, nil)
|
||||
server.ServeHTTP(recorder, request)
|
||||
if recorder.Code != http.StatusOK {
|
||||
t.Errorf("StartContainer: wrong status code. Want %d. Got %d.", http.StatusOK, recorder.Code)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRemoveFailure(t *testing.T) {
|
||||
server := DockerServer{failures: make(map[string]FailureSpec)}
|
||||
server.buildMuxer()
|
||||
errorId := "my_error"
|
||||
failure := FailureSpec{UrlRegex: "containers/json"}
|
||||
server.PrepareFailure(errorId, failure)
|
||||
recorder := httptest.NewRecorder()
|
||||
request, _ := http.NewRequest("GET", "/containers/json?all=1", nil)
|
||||
server.ServeHTTP(recorder, request)
|
||||
if recorder.Code != http.StatusBadRequest {
|
||||
t.Errorf("PrepareFailure: wrong status. Want %d. Got %d.", http.StatusBadRequest, recorder.Code)
|
||||
}
|
||||
server.ResetFailure(errorId)
|
||||
recorder = httptest.NewRecorder()
|
||||
request, _ = http.NewRequest("GET", "/containers/json?all=1", nil)
|
||||
server.ServeHTTP(recorder, request)
|
||||
if recorder.Code != http.StatusOK {
|
||||
t.Errorf("RemoveFailure: wrong status. Want %d. Got %d.", http.StatusOK, recorder.Code)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildImageWithContentTypeTar(t *testing.T) {
|
||||
server := DockerServer{imgIDs: make(map[string]string)}
|
||||
imageName := "teste"
|
||||
recorder := httptest.NewRecorder()
|
||||
tarFile, err := os.Open("data/dockerfile.tar")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer tarFile.Close()
|
||||
request, _ := http.NewRequest("POST", "/build?t=teste", tarFile)
|
||||
request.Header.Add("Content-Type", "application/tar")
|
||||
server.buildImage(recorder, request)
|
||||
if recorder.Body.String() == "miss Dockerfile" {
|
||||
t.Errorf("BuildImage: miss Dockerfile")
|
||||
return
|
||||
}
|
||||
if _, ok := server.imgIDs[imageName]; ok == false {
|
||||
t.Errorf("BuildImage: image %s not builded", imageName)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildImageWithRemoteDockerfile(t *testing.T) {
|
||||
server := DockerServer{imgIDs: make(map[string]string)}
|
||||
imageName := "teste"
|
||||
recorder := httptest.NewRecorder()
|
||||
request, _ := http.NewRequest("POST", "/build?t=teste&remote=http://localhost/Dockerfile", nil)
|
||||
server.buildImage(recorder, request)
|
||||
if _, ok := server.imgIDs[imageName]; ok == false {
|
||||
t.Errorf("BuildImage: image %s not builded", imageName)
|
||||
}
|
||||
}
|
20
third_party/github.com/fsouza/go-dockerclient/utils/random.go
vendored
Normal file
20
third_party/github.com/fsouza/go-dockerclient/utils/random.go
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
// Copyright 2014 Docker authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the DOCKER-LICENSE file.
|
||||
|
||||
package utils
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"encoding/hex"
|
||||
"io"
|
||||
)
|
||||
|
||||
func RandomString() string {
|
||||
id := make([]byte, 32)
|
||||
_, err := io.ReadFull(rand.Reader, id)
|
||||
if err != nil {
|
||||
panic(err) // This shouldn't happen
|
||||
}
|
||||
return hex.EncodeToString(id)
|
||||
}
|
158
third_party/github.com/fsouza/go-dockerclient/utils/stdcopy.go
vendored
Normal file
158
third_party/github.com/fsouza/go-dockerclient/utils/stdcopy.go
vendored
Normal file
@@ -0,0 +1,158 @@
|
||||
// Copyright 2014 Docker authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the DOCKER-LICENSE file.
|
||||
|
||||
package utils
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"io"
|
||||
)
|
||||
|
||||
const (
|
||||
StdWriterPrefixLen = 8
|
||||
StdWriterFdIndex = 0
|
||||
StdWriterSizeIndex = 4
|
||||
)
|
||||
|
||||
type StdType [StdWriterPrefixLen]byte
|
||||
|
||||
var (
|
||||
Stdin StdType = StdType{0: 0}
|
||||
Stdout StdType = StdType{0: 1}
|
||||
Stderr StdType = StdType{0: 2}
|
||||
)
|
||||
|
||||
type StdWriter struct {
|
||||
io.Writer
|
||||
prefix StdType
|
||||
sizeBuf []byte
|
||||
}
|
||||
|
||||
func (w *StdWriter) Write(buf []byte) (n int, err error) {
|
||||
if w == nil || w.Writer == nil {
|
||||
return 0, errors.New("Writer not instanciated")
|
||||
}
|
||||
binary.BigEndian.PutUint32(w.prefix[4:], uint32(len(buf)))
|
||||
buf = append(w.prefix[:], buf...)
|
||||
|
||||
n, err = w.Writer.Write(buf)
|
||||
return n - StdWriterPrefixLen, err
|
||||
}
|
||||
|
||||
// NewStdWriter instanciates a new Writer.
|
||||
// Everything written to it will be encapsulated using a custom format,
|
||||
// and written to the underlying `w` stream.
|
||||
// This allows multiple write streams (e.g. stdout and stderr) to be muxed into a single connection.
|
||||
// `t` indicates the id of the stream to encapsulate.
|
||||
// It can be utils.Stdin, utils.Stdout, utils.Stderr.
|
||||
func NewStdWriter(w io.Writer, t StdType) *StdWriter {
|
||||
if len(t) != StdWriterPrefixLen {
|
||||
return nil
|
||||
}
|
||||
|
||||
return &StdWriter{
|
||||
Writer: w,
|
||||
prefix: t,
|
||||
sizeBuf: make([]byte, 4),
|
||||
}
|
||||
}
|
||||
|
||||
var ErrInvalidStdHeader = errors.New("Unrecognized input header")
|
||||
|
||||
// StdCopy is a modified version of io.Copy.
|
||||
//
|
||||
// StdCopy will demultiplex `src`, assuming that it contains two streams,
|
||||
// previously multiplexed together using a StdWriter instance.
|
||||
// As it reads from `src`, StdCopy will write to `dstout` and `dsterr`.
|
||||
//
|
||||
// StdCopy will read until it hits EOF on `src`. It will then return a nil error.
|
||||
// In other words: if `err` is non nil, it indicates a real underlying error.
|
||||
//
|
||||
// `written` will hold the total number of bytes written to `dstout` and `dsterr`.
|
||||
func StdCopy(dstout, dsterr io.Writer, src io.Reader) (written int64, err error) {
|
||||
var (
|
||||
buf = make([]byte, 32*1024+StdWriterPrefixLen+1)
|
||||
bufLen = len(buf)
|
||||
nr, nw int
|
||||
er, ew error
|
||||
out io.Writer
|
||||
frameSize int
|
||||
)
|
||||
|
||||
for {
|
||||
// Make sure we have at least a full header
|
||||
for nr < StdWriterPrefixLen {
|
||||
var nr2 int
|
||||
nr2, er = src.Read(buf[nr:])
|
||||
if er == io.EOF {
|
||||
return written, nil
|
||||
}
|
||||
if er != nil {
|
||||
return 0, er
|
||||
}
|
||||
nr += nr2
|
||||
}
|
||||
|
||||
// Check the first byte to know where to write
|
||||
switch buf[StdWriterFdIndex] {
|
||||
case 0:
|
||||
fallthrough
|
||||
case 1:
|
||||
// Write on stdout
|
||||
out = dstout
|
||||
case 2:
|
||||
// Write on stderr
|
||||
out = dsterr
|
||||
default:
|
||||
Debugf("Error selecting output fd: (%d)", buf[StdWriterFdIndex])
|
||||
return 0, ErrInvalidStdHeader
|
||||
}
|
||||
|
||||
// Retrieve the size of the frame
|
||||
frameSize = int(binary.BigEndian.Uint32(buf[StdWriterSizeIndex : StdWriterSizeIndex+4]))
|
||||
|
||||
// Check if the buffer is big enough to read the frame.
|
||||
// Extend it if necessary.
|
||||
if frameSize+StdWriterPrefixLen > bufLen {
|
||||
Debugf("Extending buffer cap.")
|
||||
buf = append(buf, make([]byte, frameSize-len(buf)+1)...)
|
||||
bufLen = len(buf)
|
||||
}
|
||||
|
||||
// While the amount of bytes read is less than the size of the frame + header, we keep reading
|
||||
for nr < frameSize+StdWriterPrefixLen {
|
||||
var nr2 int
|
||||
nr2, er = src.Read(buf[nr:])
|
||||
if er == io.EOF {
|
||||
return written, nil
|
||||
}
|
||||
if er != nil {
|
||||
Debugf("Error reading frame: %s", er)
|
||||
return 0, er
|
||||
}
|
||||
nr += nr2
|
||||
}
|
||||
|
||||
// Write the retrieved frame (without header)
|
||||
nw, ew = out.Write(buf[StdWriterPrefixLen : frameSize+StdWriterPrefixLen])
|
||||
if nw > 0 {
|
||||
written += int64(nw)
|
||||
}
|
||||
if ew != nil {
|
||||
Debugf("Error writing frame: %s", ew)
|
||||
return 0, ew
|
||||
}
|
||||
// If the frame has not been fully written: error
|
||||
if nw != frameSize {
|
||||
Debugf("Error Short Write: (%d on %d)", nw, frameSize)
|
||||
return 0, io.ErrShortWrite
|
||||
}
|
||||
|
||||
// Move the rest of the buffer to the beginning
|
||||
copy(buf, buf[frameSize+StdWriterPrefixLen:])
|
||||
// Move the index
|
||||
nr -= frameSize + StdWriterPrefixLen
|
||||
}
|
||||
}
|
17
third_party/github.com/fsouza/go-dockerclient/utils/uname_darwin.go
vendored
Normal file
17
third_party/github.com/fsouza/go-dockerclient/utils/uname_darwin.go
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
// Copyright 2014 Docker authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the DOCKER-LICENSE file.
|
||||
|
||||
package utils
|
||||
|
||||
import (
|
||||
"errors"
|
||||
)
|
||||
|
||||
type Utsname struct {
|
||||
Release [65]byte
|
||||
}
|
||||
|
||||
func uname() (*Utsname, error) {
|
||||
return nil, errors.New("Kernel version detection is not available on darwin")
|
||||
}
|
20
third_party/github.com/fsouza/go-dockerclient/utils/uname_linux.go
vendored
Normal file
20
third_party/github.com/fsouza/go-dockerclient/utils/uname_linux.go
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
// Copyright 2014 Docker authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the DOCKER-LICENSE file.
|
||||
|
||||
package utils
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
)
|
||||
|
||||
type Utsname syscall.Utsname
|
||||
|
||||
func uname() (*syscall.Utsname, error) {
|
||||
uts := &syscall.Utsname{}
|
||||
|
||||
if err := syscall.Uname(uts); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return uts, nil
|
||||
}
|
1114
third_party/github.com/fsouza/go-dockerclient/utils/utils.go
vendored
Normal file
1114
third_party/github.com/fsouza/go-dockerclient/utils/utils.go
vendored
Normal file
File diff suppressed because it is too large
Load Diff
535
third_party/github.com/fsouza/go-dockerclient/utils/utils_test.go
vendored
Normal file
535
third_party/github.com/fsouza/go-dockerclient/utils/utils_test.go
vendored
Normal file
@@ -0,0 +1,535 @@
|
||||
// Copyright 2014 Docker authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the DOCKER-LICENSE file.
|
||||
|
||||
package utils
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestBufReader(t *testing.T) {
|
||||
reader, writer := io.Pipe()
|
||||
bufreader := NewBufReader(reader)
|
||||
|
||||
// Write everything down to a Pipe
|
||||
// Usually, a pipe should block but because of the buffered reader,
|
||||
// the writes will go through
|
||||
done := make(chan bool)
|
||||
go func() {
|
||||
writer.Write([]byte("hello world"))
|
||||
writer.Close()
|
||||
done <- true
|
||||
}()
|
||||
|
||||
// Drain the reader *after* everything has been written, just to verify
|
||||
// it is indeed buffering
|
||||
<-done
|
||||
output, err := ioutil.ReadAll(bufreader)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !bytes.Equal(output, []byte("hello world")) {
|
||||
t.Error(string(output))
|
||||
}
|
||||
}
|
||||
|
||||
type dummyWriter struct {
|
||||
buffer bytes.Buffer
|
||||
failOnWrite bool
|
||||
}
|
||||
|
||||
func (dw *dummyWriter) Write(p []byte) (n int, err error) {
|
||||
if dw.failOnWrite {
|
||||
return 0, errors.New("Fake fail")
|
||||
}
|
||||
return dw.buffer.Write(p)
|
||||
}
|
||||
|
||||
func (dw *dummyWriter) String() string {
|
||||
return dw.buffer.String()
|
||||
}
|
||||
|
||||
func (dw *dummyWriter) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func TestWriteBroadcaster(t *testing.T) {
|
||||
writer := NewWriteBroadcaster()
|
||||
|
||||
// Test 1: Both bufferA and bufferB should contain "foo"
|
||||
bufferA := &dummyWriter{}
|
||||
writer.AddWriter(bufferA, "")
|
||||
bufferB := &dummyWriter{}
|
||||
writer.AddWriter(bufferB, "")
|
||||
writer.Write([]byte("foo"))
|
||||
|
||||
if bufferA.String() != "foo" {
|
||||
t.Errorf("Buffer contains %v", bufferA.String())
|
||||
}
|
||||
|
||||
if bufferB.String() != "foo" {
|
||||
t.Errorf("Buffer contains %v", bufferB.String())
|
||||
}
|
||||
|
||||
// Test2: bufferA and bufferB should contain "foobar",
|
||||
// while bufferC should only contain "bar"
|
||||
bufferC := &dummyWriter{}
|
||||
writer.AddWriter(bufferC, "")
|
||||
writer.Write([]byte("bar"))
|
||||
|
||||
if bufferA.String() != "foobar" {
|
||||
t.Errorf("Buffer contains %v", bufferA.String())
|
||||
}
|
||||
|
||||
if bufferB.String() != "foobar" {
|
||||
t.Errorf("Buffer contains %v", bufferB.String())
|
||||
}
|
||||
|
||||
if bufferC.String() != "bar" {
|
||||
t.Errorf("Buffer contains %v", bufferC.String())
|
||||
}
|
||||
|
||||
// Test3: Test eviction on failure
|
||||
bufferA.failOnWrite = true
|
||||
writer.Write([]byte("fail"))
|
||||
if bufferA.String() != "foobar" {
|
||||
t.Errorf("Buffer contains %v", bufferA.String())
|
||||
}
|
||||
if bufferC.String() != "barfail" {
|
||||
t.Errorf("Buffer contains %v", bufferC.String())
|
||||
}
|
||||
// Even though we reset the flag, no more writes should go in there
|
||||
bufferA.failOnWrite = false
|
||||
writer.Write([]byte("test"))
|
||||
if bufferA.String() != "foobar" {
|
||||
t.Errorf("Buffer contains %v", bufferA.String())
|
||||
}
|
||||
if bufferC.String() != "barfailtest" {
|
||||
t.Errorf("Buffer contains %v", bufferC.String())
|
||||
}
|
||||
|
||||
writer.CloseWriters()
|
||||
}
|
||||
|
||||
type devNullCloser int
|
||||
|
||||
func (d devNullCloser) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d devNullCloser) Write(buf []byte) (int, error) {
|
||||
return len(buf), nil
|
||||
}
|
||||
|
||||
// This test checks for races. It is only useful when run with the race detector.
|
||||
func TestRaceWriteBroadcaster(t *testing.T) {
|
||||
writer := NewWriteBroadcaster()
|
||||
c := make(chan bool)
|
||||
go func() {
|
||||
writer.AddWriter(devNullCloser(0), "")
|
||||
c <- true
|
||||
}()
|
||||
writer.Write([]byte("hello"))
|
||||
<-c
|
||||
}
|
||||
|
||||
// Test the behavior of TruncIndex, an index for querying IDs from a non-conflicting prefix.
|
||||
func TestTruncIndex(t *testing.T) {
|
||||
index := NewTruncIndex()
|
||||
// Get on an empty index
|
||||
if _, err := index.Get("foobar"); err == nil {
|
||||
t.Fatal("Get on an empty index should return an error")
|
||||
}
|
||||
|
||||
// Spaces should be illegal in an id
|
||||
if err := index.Add("I have a space"); err == nil {
|
||||
t.Fatalf("Adding an id with ' ' should return an error")
|
||||
}
|
||||
|
||||
id := "99b36c2c326ccc11e726eee6ee78a0baf166ef96"
|
||||
// Add an id
|
||||
if err := index.Add(id); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
// Get a non-existing id
|
||||
assertIndexGet(t, index, "abracadabra", "", true)
|
||||
// Get the exact id
|
||||
assertIndexGet(t, index, id, id, false)
|
||||
// The first letter should match
|
||||
assertIndexGet(t, index, id[:1], id, false)
|
||||
// The first half should match
|
||||
assertIndexGet(t, index, id[:len(id)/2], id, false)
|
||||
// The second half should NOT match
|
||||
assertIndexGet(t, index, id[len(id)/2:], "", true)
|
||||
|
||||
id2 := id[:6] + "blabla"
|
||||
// Add an id
|
||||
if err := index.Add(id2); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
// Both exact IDs should work
|
||||
assertIndexGet(t, index, id, id, false)
|
||||
assertIndexGet(t, index, id2, id2, false)
|
||||
|
||||
// 6 characters or less should conflict
|
||||
assertIndexGet(t, index, id[:6], "", true)
|
||||
assertIndexGet(t, index, id[:4], "", true)
|
||||
assertIndexGet(t, index, id[:1], "", true)
|
||||
|
||||
// 7 characters should NOT conflict
|
||||
assertIndexGet(t, index, id[:7], id, false)
|
||||
assertIndexGet(t, index, id2[:7], id2, false)
|
||||
|
||||
// Deleting a non-existing id should return an error
|
||||
if err := index.Delete("non-existing"); err == nil {
|
||||
t.Fatalf("Deleting a non-existing id should return an error")
|
||||
}
|
||||
|
||||
// Deleting id2 should remove conflicts
|
||||
if err := index.Delete(id2); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
// id2 should no longer work
|
||||
assertIndexGet(t, index, id2, "", true)
|
||||
assertIndexGet(t, index, id2[:7], "", true)
|
||||
assertIndexGet(t, index, id2[:11], "", true)
|
||||
|
||||
// conflicts between id and id2 should be gone
|
||||
assertIndexGet(t, index, id[:6], id, false)
|
||||
assertIndexGet(t, index, id[:4], id, false)
|
||||
assertIndexGet(t, index, id[:1], id, false)
|
||||
|
||||
// non-conflicting substrings should still not conflict
|
||||
assertIndexGet(t, index, id[:7], id, false)
|
||||
assertIndexGet(t, index, id[:15], id, false)
|
||||
assertIndexGet(t, index, id, id, false)
|
||||
}
|
||||
|
||||
func assertIndexGet(t *testing.T, index *TruncIndex, input, expectedResult string, expectError bool) {
|
||||
if result, err := index.Get(input); err != nil && !expectError {
|
||||
t.Fatalf("Unexpected error getting '%s': %s", input, err)
|
||||
} else if err == nil && expectError {
|
||||
t.Fatalf("Getting '%s' should return an error", input)
|
||||
} else if result != expectedResult {
|
||||
t.Fatalf("Getting '%s' returned '%s' instead of '%s'", input, result, expectedResult)
|
||||
}
|
||||
}
|
||||
|
||||
func assertKernelVersion(t *testing.T, a, b *KernelVersionInfo, result int) {
|
||||
if r := CompareKernelVersion(a, b); r != result {
|
||||
t.Fatalf("Unexpected kernel version comparison result. Found %d, expected %d", r, result)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCompareKernelVersion(t *testing.T) {
|
||||
assertKernelVersion(t,
|
||||
&KernelVersionInfo{Kernel: 3, Major: 8, Minor: 0},
|
||||
&KernelVersionInfo{Kernel: 3, Major: 8, Minor: 0},
|
||||
0)
|
||||
assertKernelVersion(t,
|
||||
&KernelVersionInfo{Kernel: 2, Major: 6, Minor: 0},
|
||||
&KernelVersionInfo{Kernel: 3, Major: 8, Minor: 0},
|
||||
-1)
|
||||
assertKernelVersion(t,
|
||||
&KernelVersionInfo{Kernel: 3, Major: 8, Minor: 0},
|
||||
&KernelVersionInfo{Kernel: 2, Major: 6, Minor: 0},
|
||||
1)
|
||||
assertKernelVersion(t,
|
||||
&KernelVersionInfo{Kernel: 3, Major: 8, Minor: 0, Flavor: "0"},
|
||||
&KernelVersionInfo{Kernel: 3, Major: 8, Minor: 0, Flavor: "16"},
|
||||
0)
|
||||
assertKernelVersion(t,
|
||||
&KernelVersionInfo{Kernel: 3, Major: 8, Minor: 5},
|
||||
&KernelVersionInfo{Kernel: 3, Major: 8, Minor: 0},
|
||||
1)
|
||||
assertKernelVersion(t,
|
||||
&KernelVersionInfo{Kernel: 3, Major: 0, Minor: 20, Flavor: "25"},
|
||||
&KernelVersionInfo{Kernel: 3, Major: 8, Minor: 0, Flavor: "0"},
|
||||
-1)
|
||||
}
|
||||
|
||||
func TestHumanSize(t *testing.T) {
|
||||
|
||||
size := strings.Trim(HumanSize(1000), " \t")
|
||||
expect := "1 kB"
|
||||
if size != expect {
|
||||
t.Errorf("1000 -> expected '%s', got '%s'", expect, size)
|
||||
}
|
||||
|
||||
size = strings.Trim(HumanSize(1024), " \t")
|
||||
expect = "1.024 kB"
|
||||
if size != expect {
|
||||
t.Errorf("1024 -> expected '%s', got '%s'", expect, size)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRAMInBytes(t *testing.T) {
|
||||
assertRAMInBytes(t, "32", false, 32)
|
||||
assertRAMInBytes(t, "32b", false, 32)
|
||||
assertRAMInBytes(t, "32B", false, 32)
|
||||
assertRAMInBytes(t, "32k", false, 32*1024)
|
||||
assertRAMInBytes(t, "32K", false, 32*1024)
|
||||
assertRAMInBytes(t, "32kb", false, 32*1024)
|
||||
assertRAMInBytes(t, "32Kb", false, 32*1024)
|
||||
assertRAMInBytes(t, "32Mb", false, 32*1024*1024)
|
||||
assertRAMInBytes(t, "32Gb", false, 32*1024*1024*1024)
|
||||
|
||||
assertRAMInBytes(t, "", true, -1)
|
||||
assertRAMInBytes(t, "hello", true, -1)
|
||||
assertRAMInBytes(t, "-32", true, -1)
|
||||
assertRAMInBytes(t, " 32 ", true, -1)
|
||||
assertRAMInBytes(t, "32 mb", true, -1)
|
||||
assertRAMInBytes(t, "32m b", true, -1)
|
||||
assertRAMInBytes(t, "32bm", true, -1)
|
||||
}
|
||||
|
||||
func assertRAMInBytes(t *testing.T, size string, expectError bool, expectedBytes int64) {
|
||||
actualBytes, err := RAMInBytes(size)
|
||||
if (err != nil) && !expectError {
|
||||
t.Errorf("Unexpected error parsing '%s': %s", size, err)
|
||||
}
|
||||
if (err == nil) && expectError {
|
||||
t.Errorf("Expected to get an error parsing '%s', but got none (bytes=%d)", size, actualBytes)
|
||||
}
|
||||
if actualBytes != expectedBytes {
|
||||
t.Errorf("Expected '%s' to parse as %d bytes, got %d", size, expectedBytes, actualBytes)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseHost(t *testing.T) {
|
||||
var (
|
||||
defaultHttpHost = "127.0.0.1"
|
||||
defaultHttpPort = 4243
|
||||
defaultUnix = "/var/run/docker.sock"
|
||||
)
|
||||
if addr, err := ParseHost(defaultHttpHost, defaultHttpPort, defaultUnix, "0.0.0.0"); err != nil || addr != "tcp://0.0.0.0:4243" {
|
||||
t.Errorf("0.0.0.0 -> expected tcp://0.0.0.0:4243, got %s", addr)
|
||||
}
|
||||
if addr, err := ParseHost(defaultHttpHost, defaultHttpPort, defaultUnix, "0.0.0.1:5555"); err != nil || addr != "tcp://0.0.0.1:5555" {
|
||||
t.Errorf("0.0.0.1:5555 -> expected tcp://0.0.0.1:5555, got %s", addr)
|
||||
}
|
||||
if addr, err := ParseHost(defaultHttpHost, defaultHttpPort, defaultUnix, ":6666"); err != nil || addr != "tcp://127.0.0.1:6666" {
|
||||
t.Errorf(":6666 -> expected tcp://127.0.0.1:6666, got %s", addr)
|
||||
}
|
||||
if addr, err := ParseHost(defaultHttpHost, defaultHttpPort, defaultUnix, "tcp://:7777"); err != nil || addr != "tcp://127.0.0.1:7777" {
|
||||
t.Errorf("tcp://:7777 -> expected tcp://127.0.0.1:7777, got %s", addr)
|
||||
}
|
||||
if addr, err := ParseHost(defaultHttpHost, defaultHttpPort, defaultUnix, ""); err != nil || addr != "unix:///var/run/docker.sock" {
|
||||
t.Errorf("empty argument -> expected unix:///var/run/docker.sock, got %s", addr)
|
||||
}
|
||||
if addr, err := ParseHost(defaultHttpHost, defaultHttpPort, defaultUnix, "unix:///var/run/docker.sock"); err != nil || addr != "unix:///var/run/docker.sock" {
|
||||
t.Errorf("unix:///var/run/docker.sock -> expected unix:///var/run/docker.sock, got %s", addr)
|
||||
}
|
||||
if addr, err := ParseHost(defaultHttpHost, defaultHttpPort, defaultUnix, "unix://"); err != nil || addr != "unix:///var/run/docker.sock" {
|
||||
t.Errorf("unix:///var/run/docker.sock -> expected unix:///var/run/docker.sock, got %s", addr)
|
||||
}
|
||||
if addr, err := ParseHost(defaultHttpHost, defaultHttpPort, defaultUnix, "udp://127.0.0.1"); err == nil {
|
||||
t.Errorf("udp protocol address expected error return, but err == nil. Got %s", addr)
|
||||
}
|
||||
if addr, err := ParseHost(defaultHttpHost, defaultHttpPort, defaultUnix, "udp://127.0.0.1:4243"); err == nil {
|
||||
t.Errorf("udp protocol address expected error return, but err == nil. Got %s", addr)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseRepositoryTag(t *testing.T) {
|
||||
if repo, tag := ParseRepositoryTag("root"); repo != "root" || tag != "" {
|
||||
t.Errorf("Expected repo: '%s' and tag: '%s', got '%s' and '%s'", "root", "", repo, tag)
|
||||
}
|
||||
if repo, tag := ParseRepositoryTag("root:tag"); repo != "root" || tag != "tag" {
|
||||
t.Errorf("Expected repo: '%s' and tag: '%s', got '%s' and '%s'", "root", "tag", repo, tag)
|
||||
}
|
||||
if repo, tag := ParseRepositoryTag("user/repo"); repo != "user/repo" || tag != "" {
|
||||
t.Errorf("Expected repo: '%s' and tag: '%s', got '%s' and '%s'", "user/repo", "", repo, tag)
|
||||
}
|
||||
if repo, tag := ParseRepositoryTag("user/repo:tag"); repo != "user/repo" || tag != "tag" {
|
||||
t.Errorf("Expected repo: '%s' and tag: '%s', got '%s' and '%s'", "user/repo", "tag", repo, tag)
|
||||
}
|
||||
if repo, tag := ParseRepositoryTag("url:5000/repo"); repo != "url:5000/repo" || tag != "" {
|
||||
t.Errorf("Expected repo: '%s' and tag: '%s', got '%s' and '%s'", "url:5000/repo", "", repo, tag)
|
||||
}
|
||||
if repo, tag := ParseRepositoryTag("url:5000/repo:tag"); repo != "url:5000/repo" || tag != "tag" {
|
||||
t.Errorf("Expected repo: '%s' and tag: '%s', got '%s' and '%s'", "url:5000/repo", "tag", repo, tag)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetResolvConf(t *testing.T) {
|
||||
resolvConfUtils, err := GetResolvConf()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
resolvConfSystem, err := ioutil.ReadFile("/etc/resolv.conf")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if string(resolvConfUtils) != string(resolvConfSystem) {
|
||||
t.Fatalf("/etc/resolv.conf and GetResolvConf have different content.")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCheckLocalDns(t *testing.T) {
|
||||
for resolv, result := range map[string]bool{`# Dynamic
|
||||
nameserver 10.0.2.3
|
||||
search dotcloud.net`: false,
|
||||
`# Dynamic
|
||||
#nameserver 127.0.0.1
|
||||
nameserver 10.0.2.3
|
||||
search dotcloud.net`: false,
|
||||
`# Dynamic
|
||||
nameserver 10.0.2.3 #not used 127.0.1.1
|
||||
search dotcloud.net`: false,
|
||||
`# Dynamic
|
||||
#nameserver 10.0.2.3
|
||||
#search dotcloud.net`: true,
|
||||
`# Dynamic
|
||||
nameserver 127.0.0.1
|
||||
search dotcloud.net`: true,
|
||||
`# Dynamic
|
||||
nameserver 127.0.1.1
|
||||
search dotcloud.net`: true,
|
||||
`# Dynamic
|
||||
`: true,
|
||||
``: true,
|
||||
} {
|
||||
if CheckLocalDns([]byte(resolv)) != result {
|
||||
t.Fatalf("Wrong local dns detection: {%s} should be %v", resolv, result)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func assertParseRelease(t *testing.T, release string, b *KernelVersionInfo, result int) {
|
||||
var (
|
||||
a *KernelVersionInfo
|
||||
)
|
||||
a, _ = ParseRelease(release)
|
||||
|
||||
if r := CompareKernelVersion(a, b); r != result {
|
||||
t.Fatalf("Unexpected kernel version comparison result. Found %d, expected %d", r, result)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseRelease(t *testing.T) {
|
||||
assertParseRelease(t, "3.8.0", &KernelVersionInfo{Kernel: 3, Major: 8, Minor: 0}, 0)
|
||||
assertParseRelease(t, "3.4.54.longterm-1", &KernelVersionInfo{Kernel: 3, Major: 4, Minor: 54}, 0)
|
||||
assertParseRelease(t, "3.4.54.longterm-1", &KernelVersionInfo{Kernel: 3, Major: 4, Minor: 54, Flavor: "1"}, 0)
|
||||
assertParseRelease(t, "3.8.0-19-generic", &KernelVersionInfo{Kernel: 3, Major: 8, Minor: 0, Flavor: "19-generic"}, 0)
|
||||
}
|
||||
|
||||
func TestDependencyGraphCircular(t *testing.T) {
|
||||
g1 := NewDependencyGraph()
|
||||
a := g1.NewNode("a")
|
||||
b := g1.NewNode("b")
|
||||
g1.AddDependency(a, b)
|
||||
g1.AddDependency(b, a)
|
||||
res, err := g1.GenerateTraversalMap()
|
||||
if res != nil {
|
||||
t.Fatalf("Expected nil result")
|
||||
}
|
||||
if err == nil {
|
||||
t.Fatalf("Expected error (circular graph can not be resolved)")
|
||||
}
|
||||
}
|
||||
|
||||
func TestDependencyGraph(t *testing.T) {
|
||||
g1 := NewDependencyGraph()
|
||||
a := g1.NewNode("a")
|
||||
b := g1.NewNode("b")
|
||||
c := g1.NewNode("c")
|
||||
d := g1.NewNode("d")
|
||||
g1.AddDependency(b, a)
|
||||
g1.AddDependency(c, a)
|
||||
g1.AddDependency(d, c)
|
||||
g1.AddDependency(d, b)
|
||||
res, err := g1.GenerateTraversalMap()
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("%s", err)
|
||||
}
|
||||
|
||||
if res == nil {
|
||||
t.Fatalf("Unexpected nil result")
|
||||
}
|
||||
|
||||
if len(res) != 3 {
|
||||
t.Fatalf("Expected map of length 3, found %d instead", len(res))
|
||||
}
|
||||
|
||||
if len(res[0]) != 1 || res[0][0] != "a" {
|
||||
t.Fatalf("Expected [a], found %v instead", res[0])
|
||||
}
|
||||
|
||||
if len(res[1]) != 2 {
|
||||
t.Fatalf("Expected 2 nodes for step 2, found %d", len(res[1]))
|
||||
}
|
||||
|
||||
if (res[1][0] != "b" && res[1][1] != "b") || (res[1][0] != "c" && res[1][1] != "c") {
|
||||
t.Fatalf("Expected [b, c], found %v instead", res[1])
|
||||
}
|
||||
|
||||
if len(res[2]) != 1 || res[2][0] != "d" {
|
||||
t.Fatalf("Expected [d], found %v instead", res[2])
|
||||
}
|
||||
}
|
||||
|
||||
func TestParsePortMapping(t *testing.T) {
|
||||
data, err := PartParser("ip:public:private", "192.168.1.1:80:8080")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if len(data) != 3 {
|
||||
t.FailNow()
|
||||
}
|
||||
if data["ip"] != "192.168.1.1" {
|
||||
t.Fail()
|
||||
}
|
||||
if data["public"] != "80" {
|
||||
t.Fail()
|
||||
}
|
||||
if data["private"] != "8080" {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetNameserversAsCIDR(t *testing.T) {
|
||||
for resolv, result := range map[string][]string{`
|
||||
nameserver 1.2.3.4
|
||||
nameserver 40.3.200.10
|
||||
search example.com`: {"1.2.3.4/32", "40.3.200.10/32"},
|
||||
`search example.com`: {},
|
||||
`nameserver 1.2.3.4
|
||||
search example.com
|
||||
nameserver 4.30.20.100`: {"1.2.3.4/32", "4.30.20.100/32"},
|
||||
``: {},
|
||||
` nameserver 1.2.3.4 `: {"1.2.3.4/32"},
|
||||
`search example.com
|
||||
nameserver 1.2.3.4
|
||||
#nameserver 4.3.2.1`: {"1.2.3.4/32"},
|
||||
`search example.com
|
||||
nameserver 1.2.3.4 # not 4.3.2.1`: {"1.2.3.4/32"},
|
||||
} {
|
||||
test := GetNameserversAsCIDR([]byte(resolv))
|
||||
if !StrSlicesEqual(test, result) {
|
||||
t.Fatalf("Wrong nameserver string {%s} should be %v. Input: %s", test, result, resolv)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func StrSlicesEqual(a, b []string) bool {
|
||||
if len(a) != len(b) {
|
||||
return false
|
||||
}
|
||||
|
||||
for i, v := range a {
|
||||
if v != b[i] {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
17
third_party/github.com/fsouza/go-dockerclient/utils/utils_windows.go
vendored
Normal file
17
third_party/github.com/fsouza/go-dockerclient/utils/utils_windows.go
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
// Copyright 2014 Docker authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the DOCKER-LICENSE file.
|
||||
|
||||
package utils
|
||||
|
||||
import (
|
||||
"errors"
|
||||
)
|
||||
|
||||
type Utsname struct {
|
||||
Release [65]byte
|
||||
}
|
||||
|
||||
func uname() (*Utsname, error) {
|
||||
return nil, errors.New("Kernel version detection is not available on windows")
|
||||
}
|
Reference in New Issue
Block a user