Add glide.yaml and vendor deps

This commit is contained in:
Dalton Hubble 2016-12-03 22:43:32 -08:00
parent db918f12ad
commit 5b3d5e81bd
18880 changed files with 5166045 additions and 1 deletions

9
vendor/github.com/docker/engine-api/.travis.yml generated vendored Normal file
View file

@ -0,0 +1,9 @@
---
language: go
sudo: false
notifications:
email: false
go:
- 1.6
install: make deps
script: make validate && make test

141
vendor/github.com/docker/engine-api/CHANGELOG.md generated vendored Normal file
View file

@ -0,0 +1,141 @@
# Changelog
Items starting with DEPRECATE are important deprecation notices. For more information on the list of deprecated APIs please have a look at https://docs.docker.com/misc/deprecated/ where target removal dates can also be found.
## 0.3.2 (2016-03-30)
## Client
- Revert setting the ServerName in the TLS configuration at client init. See https://github.com/docker/swarm/issues/2027.
## 0.3.1 (2016-03-23)
### Client
- Ensure that API paths are properly escaped.
## 0.3.0 (2016-03-22)
### Client
- Add context to every function.
- Fix issue loading a default TLS CA.
- Allow to configure the client with a given http.Client.
- Add support for Windows named pipes.
- Set default host for Solaris.
- Add quiet flag for image load.
- Add ability to hijack connections through a proxy.
- Correctly set content type for image load.
- Add support for getting token for login.
### Types
- Add struct for update restart policy.
- Add human friendly State for container.
- Use OS specific host when DOCKER_HOST is not set.
- Rename Status in info to SystemStatus.
- Add internal flag to network inspect.
- Add disk quota field to container.
- Add EnableIPv6 fields.
- Add Mounts to container.
- Add cgroup driver to info.
- Add userns to host config.
- Remove email from AuthConfig.
- Make AuthConfig fields optional.
- Add IO resource settings for Windows.
- Add storage driver to host config.
- Update NetworkName to return proper user defined network names.
- Support joining cgroups by container id.
- Add KernelMemory to info.
- Add UsernsMode to container config.
- Add CPU resource control for Windows.
- Add AutoRemove to host config.
- Add Status field to Volume.
- Add Label to Image, Network and Volume.
- Add RootFS to container.
## 0.2.3 (2016-02-02)
### Types
- Add missing status field.
## 0.2.2 (2016-01-13)
### Client
- Fix issue configuring response hijacking with TLS enabled.
## 0.2.1 (2016-01-12)
### Client
- Fix issue detecting missing images on container creation.
### Types
- Remove invalid json tag in endpoint configuration.
- Add missing fields in info structure.
## 0.2.0 (2016-01-11)
### Client
- Allow to force network disconnection. (docker 1.10)
### Types
- Add global and local alias configuration to network endpoint.
- Add network ID to network endpoint.
- Add IPAM options.
- Add Seccomp options.
- Fix issue referencing OOMKillDisable.
## 0.1.3 (2016-01-07)
### Client
- Fix issue sending all network configurations for a per network request.
## 0.1.2 (2016-01-07)
### Client
- Add interface to represent the API client.
- Restrict the fields send to the update endpoint to only those that are used.
- Send network settings as part of the container create request. (docker 1.10)
- Send network settings as part of the network connect request. (docker 1.10)
### Types
- Add PidsLimit as part of the host configuration.
- Add PidsStats to show PID stats.
- Add graph storage options to host configuration.
- Add NetworkConfig and EndpointIPAMConfig structs. (docker 1.10)
## 0.1.1 (2016-01-06)
### Client
- Delegate shmSize units conversion to the consumer.
### Types
- Add warnings to the volume list response.
- Fix image build options:
* use 0 as default value for shmSize.
## 0.1.0 (2016-01-04)
### Client
- Initial API client implementation.
### Types
- Initial API types implementation.

55
vendor/github.com/docker/engine-api/CONTRIBUTING.md generated vendored Normal file
View file

@ -0,0 +1,55 @@
# Contributing to Docker
### Sign your work
The sign-off is a simple line at the end of the explanation for the patch. Your
signature certifies that you wrote the patch or otherwise have the right to pass
it on as an open-source patch. The rules are pretty simple: if you can certify
the below (from [developercertificate.org](http://developercertificate.org/)):
```
Developer Certificate of Origin
Version 1.1
Copyright (C) 2004, 2006 The Linux Foundation and its contributors.
660 York Street, Suite 102,
San Francisco, CA 94110 USA
Everyone is permitted to copy and distribute verbatim copies of this
license document, but changing it is not allowed.
Developer's Certificate of Origin 1.1
By making a contribution to this project, I certify that:
(a) The contribution was created in whole or in part by me and I
have the right to submit it under the open source license
indicated in the file; or
(b) The contribution is based upon previous work that, to the best
of my knowledge, is covered under an appropriate open source
license and I have the right under that license to submit that
work with modifications, whether created in whole or in part
by me, under the same open source license (unless I am
permitted to submit under a different license), as indicated
in the file; or
(c) The contribution was provided directly to me by some other
person who certified (a), (b) or (c) and I have not modified
it.
(d) I understand and agree that this project and the contribution
are public and that a record of the contribution (including all
personal information I submit with it, including my sign-off) is
maintained indefinitely and may be redistributed consistent with
this project or the open source license(s) involved.
```
Then you just add a line to every git commit message:
Signed-off-by: Joe Smith <joe.smith@email.com>
Use your real name (sorry, no pseudonyms or anonymous contributions.)
If you set your `user.name` and `user.email` git configs, you can sign your
commit automatically with `git commit -s`.

191
vendor/github.com/docker/engine-api/LICENSE generated vendored Normal file
View file

@ -0,0 +1,191 @@
Apache License
Version 2.0, January 2004
https://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
Copyright 2015-2016 Docker, 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
https://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.

160
vendor/github.com/docker/engine-api/MAINTAINERS generated vendored Normal file
View file

@ -0,0 +1,160 @@
# engine-api maintainers file
#
# This file describes who runs the docker/engine-api project and how.
# This is a living document - if you see something out of date or missing, speak up!
#
# It is structured to be consumable by both humans and programs.
# To extract its contents programmatically, use any TOML-compliant parser.
#
# This file is compiled into the MAINTAINERS file in docker/opensource.
#
[Org]
[Org."Core maintainers"]
people = [
"aaronlehmann",
"calavera",
"coolljt0725",
"cpuguy83",
"crosbymichael",
"dnephin",
"dongluochen",
"duglin",
"estesp",
"icecrime",
"jhowardmsft",
"lk4d4",
"mavenugo",
"mhbauer",
"runcom",
"stevvooe",
"thajeztah",
"tianon",
"tibor",
"tonistiigi",
"unclejack",
"vdemeester",
"vieux"
]
[people]
# A reference list of all people associated with the project.
# All other sections should refer to people by their canonical key
# in the people section.
# ADD YOURSELF HERE IN ALPHABETICAL ORDER
[people.aaronlehmann]
Name = "Aaron Lehmann"
Email = "aaron.lehmann@docker.com"
GitHub = "aaronlehmann"
[people.calavera]
Name = "David Calavera"
Email = "david.calavera@gmail.com"
GitHub = "calavera"
[people.coolljt0725]
Name = "Lei Jitang"
Email = "leijitang@huawei.com"
GitHub = "coolljt0725"
[people.cpuguy83]
Name = "Brian Goff"
Email = "cpuguy83@gmail.com"
Github = "cpuguy83"
[people.crosbymichael]
Name = "Michael Crosby"
Email = "crosbymichael@gmail.com"
GitHub = "crosbymichael"
[people.dnephin]
Name = "Daniel Nephin"
Email = "dnephin@gmail.com"
GitHub = "dnephin"
[people.dongluochen]
Name = "Dongluo Chen"
Email = "dongluo.chen@docker.com"
GitHub = "dongluochen"
[people.duglin]
Name = "Doug Davis"
Email = "dug@us.ibm.com"
GitHub = "duglin"
[people.estesp]
Name = "Phil Estes"
Email = "estesp@linux.vnet.ibm.com"
GitHub = "estesp"
[people.icecrime]
Name = "Arnaud Porterie"
Email = "arnaud@docker.com"
GitHub = "icecrime"
[people.jhowardmsft]
Name = "John Howard"
Email = "jhoward@microsoft.com"
GitHub = "jhowardmsft"
[people.lk4d4]
Name = "Alexander Morozov"
Email = "lk4d4@docker.com"
GitHub = "lk4d4"
[people.mavenugo]
Name = "Madhu Venugopal"
Email = "madhu@docker.com"
GitHub = "mavenugo"
[people.mhbauer]
Name = "Morgan Bauer"
Email = "mbauer@us.ibm.com"
GitHub = "mhbauer"
[people.runcom]
Name = "Antonio Murdaca"
Email = "runcom@redhat.com"
GitHub = "runcom"
[people.stevvooe]
Name = "Stephen Day"
Email = "stephen.day@docker.com"
GitHub = "stevvooe"
[people.thajeztah]
Name = "Sebastiaan van Stijn"
Email = "github@gone.nl"
GitHub = "thaJeztah"
[people.tianon]
Name = "Tianon Gravi"
Email = "admwiggin@gmail.com"
GitHub = "tianon"
[people.tibor]
Name = "Tibor Vass"
Email = "tibor@docker.com"
GitHub = "tiborvass"
[people.tonistiigi]
Name = "Tõnis Tiigi"
Email = "tonis@docker.com"
GitHub = "tonistiigi"
[people.unclejack]
Name = "Cristian Staretu"
Email = "cristian.staretu@gmail.com"
GitHub = "unclejack"
[people.vdemeester]
Name = "Vincent Demeester"
Email = "vincent@sbr.pm"
GitHub = "vdemeester"
[people.vieux]
Name = "Victor Vieux"
Email = "vieux@docker.com"
GitHub = "vieux"

21
vendor/github.com/docker/engine-api/Makefile generated vendored Normal file
View file

@ -0,0 +1,21 @@
.PHONY: all deps test validate lint
all: deps test validate
deps:
go get -t ./...
go get -u github.com/golang/lint/golint
test:
go test -tags experimental -race -cover ./...
validate: lint
go vet ./...
test -z "$(gofmt -s -l . | tee /dev/stderr)"
lint:
out="$$(golint ./...)"; \
if [ -n "$$(golint ./...)" ]; then \
echo "$$out"; \
exit 1; \
fi

68
vendor/github.com/docker/engine-api/README.md generated vendored Normal file
View file

@ -0,0 +1,68 @@
[![GoDoc](https://godoc.org/github.com/docker/engine-api?status.svg)](https://godoc.org/github.com/docker/engine-api)
# Introduction
Engine-api is a set of Go libraries to implement client and server components compatible with the Docker engine.
The code was extracted from the [Docker engine](https://github.com/docker/docker) and contributed back as an external library.
## Components
### Client
The client package implements a fully featured http client to interact with the Docker engine. It's modeled after the requirements of the Docker engine CLI, but it can also serve other purposes.
#### Usage
You can use this client package in your applications by creating a new client object. Then use that object to execute operations against the remote server. Follow the example below to see how to list all the containers running in a Docker engine host:
```go
package main
import (
"fmt"
"github.com/docker/engine-api/client"
"github.com/docker/engine-api/types"
"golang.org/x/net/context"
)
func main() {
defaultHeaders := map[string]string{"User-Agent": "engine-api-cli-1.0"}
cli, err := client.NewClient("unix:///var/run/docker.sock", "v1.22", nil, defaultHeaders)
if err != nil {
panic(err)
}
options := types.ContainerListOptions{All: true}
containers, err := cli.ContainerList(context.Background(), options)
if err != nil {
panic(err)
}
for _, c := range containers {
fmt.Println(c.ID)
}
}
```
### Types
The types package includes all typed structures that client and server serialize to execute operations.
### Server
The server package includes API endpoints that applications compatible with the Docker engine API can reuse. It also provides useful middlewares and helpers to handle http requests.
This package is still pending to be extracted from the Docker engine.
## Developing
engine-api requires some minimal libraries that you can download running `make deps`.
To run tests, use the command `make test`. We use build tags to isolate functions and structures that are only available for testing.
To validate the sources, use the command `make validate`.
## License
engine-api is licensed under the Apache License, Version 2.0. See [LICENSE](LICENSE) for the full license text.

37
vendor/github.com/docker/engine-api/appveyor.yml generated vendored Normal file
View file

@ -0,0 +1,37 @@
version: "{build}"
# Source Config
clone_folder: c:\gopath\src\github.com\docker\engine-api
# Build host
environment:
GOPATH: c:\gopath
GOVERSION: 1.6
init:
- git config --global core.autocrlf input
# Build
install:
# Install Go 1.6.
- rmdir c:\go /s /q
- appveyor DownloadFile https://storage.googleapis.com/golang/go%GOVERSION%.windows-amd64.msi
- msiexec /i go%GOVERSION%.windows-amd64.msi /q
- set Path=c:\go\bin;c:\gopath\bin;%Path%
- go version
- go env
build: false
deploy: false
before_test:
- go get -t ./...
- go get github.com/golang/lint/golint
test_script:
- go vet ./...
- golint ./...
- gofmt -s -l .
- go test -race -cover -v -tags=test ./...

View file

@ -0,0 +1,13 @@
package client
import (
"github.com/docker/engine-api/types"
"golang.org/x/net/context"
)
// CheckpointCreate creates a checkpoint from the given container with the given name
func (cli *Client) CheckpointCreate(ctx context.Context, container string, options types.CheckpointCreateOptions) error {
resp, err := cli.post(ctx, "/containers/"+container+"/checkpoints", nil, options, nil)
ensureReaderClosed(resp)
return err
}

View file

@ -0,0 +1,73 @@
package client
import (
"bytes"
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"strings"
"testing"
"github.com/docker/engine-api/types"
"golang.org/x/net/context"
)
func TestCheckpointCreateError(t *testing.T) {
client := &Client{
transport: newMockClient(nil, errorMock(http.StatusInternalServerError, "Server error")),
}
err := client.CheckpointCreate(context.Background(), "nothing", types.CheckpointCreateOptions{
CheckpointID: "noting",
Exit: true,
})
if err == nil || err.Error() != "Error response from daemon: Server error" {
t.Fatalf("expected a Server Error, got %v", err)
}
}
func TestCheckpointCreate(t *testing.T) {
expectedContainerID := "container_id"
expectedCheckpointID := "checkpoint_id"
expectedURL := "/containers/container_id/checkpoints"
client := &Client{
transport: newMockClient(nil, func(req *http.Request) (*http.Response, error) {
if !strings.HasPrefix(req.URL.Path, expectedURL) {
return nil, fmt.Errorf("Expected URL '%s', got '%s'", expectedURL, req.URL)
}
if req.Method != "POST" {
return nil, fmt.Errorf("expected POST method, got %s", req.Method)
}
createOptions := &types.CheckpointCreateOptions{}
if err := json.NewDecoder(req.Body).Decode(createOptions); err != nil {
return nil, err
}
if createOptions.CheckpointID != expectedCheckpointID {
return nil, fmt.Errorf("expected CheckpointID to be 'checkpoint_id', got %v", createOptions.CheckpointID)
}
if !createOptions.Exit {
return nil, fmt.Errorf("expected Exit to be true")
}
return &http.Response{
StatusCode: http.StatusOK,
Body: ioutil.NopCloser(bytes.NewReader([]byte(""))),
}, nil
}),
}
err := client.CheckpointCreate(context.Background(), expectedContainerID, types.CheckpointCreateOptions{
CheckpointID: expectedCheckpointID,
Exit: true,
})
if err != nil {
t.Fatal(err)
}
}

View file

@ -0,0 +1,12 @@
package client
import (
"golang.org/x/net/context"
)
// CheckpointDelete deletes the checkpoint with the given name from the given container
func (cli *Client) CheckpointDelete(ctx context.Context, containerID string, checkpointID string) error {
resp, err := cli.delete(ctx, "/containers/"+containerID+"/checkpoints/"+checkpointID, nil, nil)
ensureReaderClosed(resp)
return err
}

View file

@ -0,0 +1,47 @@
package client
import (
"bytes"
"fmt"
"io/ioutil"
"net/http"
"strings"
"testing"
"golang.org/x/net/context"
)
func TestCheckpointDeleteError(t *testing.T) {
client := &Client{
transport: newMockClient(nil, errorMock(http.StatusInternalServerError, "Server error")),
}
err := client.CheckpointDelete(context.Background(), "container_id", "checkpoint_id")
if err == nil || err.Error() != "Error response from daemon: Server error" {
t.Fatalf("expected a Server Error, got %v", err)
}
}
func TestCheckpointDelete(t *testing.T) {
expectedURL := "/containers/container_id/checkpoints/checkpoint_id"
client := &Client{
transport: newMockClient(nil, func(req *http.Request) (*http.Response, error) {
if !strings.HasPrefix(req.URL.Path, expectedURL) {
return nil, fmt.Errorf("Expected URL '%s', got '%s'", expectedURL, req.URL)
}
if req.Method != "DELETE" {
return nil, fmt.Errorf("expected DELETE method, got %s", req.Method)
}
return &http.Response{
StatusCode: http.StatusOK,
Body: ioutil.NopCloser(bytes.NewReader([]byte(""))),
}, nil
}),
}
err := client.CheckpointDelete(context.Background(), "container_id", "checkpoint_id")
if err != nil {
t.Fatal(err)
}
}

View file

@ -0,0 +1,22 @@
package client
import (
"encoding/json"
"github.com/docker/engine-api/types"
"golang.org/x/net/context"
)
// CheckpointList returns the volumes configured in the docker host.
func (cli *Client) CheckpointList(ctx context.Context, container string) ([]types.Checkpoint, error) {
var checkpoints []types.Checkpoint
resp, err := cli.get(ctx, "/containers/"+container+"/checkpoints", nil, nil)
if err != nil {
return checkpoints, err
}
err = json.NewDecoder(resp.body).Decode(&checkpoints)
ensureReaderClosed(resp)
return checkpoints, err
}

View file

@ -0,0 +1,57 @@
package client
import (
"bytes"
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"strings"
"testing"
"github.com/docker/engine-api/types"
"golang.org/x/net/context"
)
func TestCheckpointListError(t *testing.T) {
client := &Client{
transport: newMockClient(nil, errorMock(http.StatusInternalServerError, "Server error")),
}
_, err := client.CheckpointList(context.Background(), "container_id")
if err == nil || err.Error() != "Error response from daemon: Server error" {
t.Fatalf("expected a Server Error, got %v", err)
}
}
func TestCheckpointList(t *testing.T) {
expectedURL := "/containers/container_id/checkpoints"
client := &Client{
transport: newMockClient(nil, func(req *http.Request) (*http.Response, error) {
if !strings.HasPrefix(req.URL.Path, expectedURL) {
return nil, fmt.Errorf("Expected URL '%s', got '%s'", expectedURL, req.URL)
}
content, err := json.Marshal([]types.Checkpoint{
{
Name: "checkpoint",
},
})
if err != nil {
return nil, err
}
return &http.Response{
StatusCode: http.StatusOK,
Body: ioutil.NopCloser(bytes.NewReader(content)),
}, nil
}),
}
checkpoints, err := client.CheckpointList(context.Background(), "container_id")
if err != nil {
t.Fatal(err)
}
if len(checkpoints) != 1 {
t.Fatalf("expected 1 checkpoint, got %v", checkpoints)
}
}

156
vendor/github.com/docker/engine-api/client/client.go generated vendored Normal file
View file

@ -0,0 +1,156 @@
package client
import (
"fmt"
"net/http"
"net/url"
"os"
"path/filepath"
"strings"
"github.com/docker/engine-api/client/transport"
"github.com/docker/go-connections/tlsconfig"
)
// DefaultVersion is the version of the current stable API
const DefaultVersion string = "1.23"
// Client is the API client that performs all operations
// against a docker server.
type Client struct {
// host holds the server address to connect to
host string
// proto holds the client protocol i.e. unix.
proto string
// addr holds the client address.
addr string
// basePath holds the path to prepend to the requests.
basePath string
// transport is the interface to send request with, it implements transport.Client.
transport transport.Client
// version of the server to talk to.
version string
// custom http headers configured by users.
customHTTPHeaders map[string]string
}
// NewEnvClient initializes a new API client based on environment variables.
// Use DOCKER_HOST to set the url to the docker server.
// Use DOCKER_API_VERSION to set the version of the API to reach, leave empty for latest.
// Use DOCKER_CERT_PATH to load the tls certificates from.
// Use DOCKER_TLS_VERIFY to enable or disable TLS verification, off by default.
func NewEnvClient() (*Client, error) {
var client *http.Client
if dockerCertPath := os.Getenv("DOCKER_CERT_PATH"); dockerCertPath != "" {
options := tlsconfig.Options{
CAFile: filepath.Join(dockerCertPath, "ca.pem"),
CertFile: filepath.Join(dockerCertPath, "cert.pem"),
KeyFile: filepath.Join(dockerCertPath, "key.pem"),
InsecureSkipVerify: os.Getenv("DOCKER_TLS_VERIFY") == "",
}
tlsc, err := tlsconfig.Client(options)
if err != nil {
return nil, err
}
client = &http.Client{
Transport: &http.Transport{
TLSClientConfig: tlsc,
},
}
}
host := os.Getenv("DOCKER_HOST")
if host == "" {
host = DefaultDockerHost
}
version := os.Getenv("DOCKER_API_VERSION")
if version == "" {
version = DefaultVersion
}
return NewClient(host, version, client, nil)
}
// NewClient initializes a new API client for the given host and API version.
// It uses the given http client as transport.
// It also initializes the custom http headers to add to each request.
//
// It won't send any version information if the version number is empty. It is
// highly recommended that you set a version or your client may break if the
// server is upgraded.
func NewClient(host string, version string, client *http.Client, httpHeaders map[string]string) (*Client, error) {
proto, addr, basePath, err := ParseHost(host)
if err != nil {
return nil, err
}
transport, err := transport.NewTransportWithHTTP(proto, addr, client)
if err != nil {
return nil, err
}
return &Client{
host: host,
proto: proto,
addr: addr,
basePath: basePath,
transport: transport,
version: version,
customHTTPHeaders: httpHeaders,
}, nil
}
// getAPIPath returns the versioned request path to call the api.
// It appends the query parameters to the path if they are not empty.
func (cli *Client) getAPIPath(p string, query url.Values) string {
var apiPath string
if cli.version != "" {
v := strings.TrimPrefix(cli.version, "v")
apiPath = fmt.Sprintf("%s/v%s%s", cli.basePath, v, p)
} else {
apiPath = fmt.Sprintf("%s%s", cli.basePath, p)
}
u := &url.URL{
Path: apiPath,
}
if len(query) > 0 {
u.RawQuery = query.Encode()
}
return u.String()
}
// ClientVersion returns the version string associated with this
// instance of the Client. Note that this value can be changed
// via the DOCKER_API_VERSION env var.
func (cli *Client) ClientVersion() string {
return cli.version
}
// UpdateClientVersion updates the version string associated with this
// instance of the Client.
func (cli *Client) UpdateClientVersion(v string) {
cli.version = v
}
// ParseHost verifies that the given host strings is valid.
func ParseHost(host string) (string, string, string, error) {
protoAddrParts := strings.SplitN(host, "://", 2)
if len(protoAddrParts) == 1 {
return "", "", "", fmt.Errorf("unable to parse docker host `%s`", host)
}
var basePath string
proto, addr := protoAddrParts[0], protoAddrParts[1]
if proto == "tcp" {
parsed, err := url.Parse("tcp://" + addr)
if err != nil {
return "", "", "", err
}
addr = parsed.Host
basePath = parsed.Path
}
return proto, addr, basePath, nil
}

View file

@ -0,0 +1,76 @@
package client
import (
"bytes"
"crypto/tls"
"encoding/json"
"io/ioutil"
"net/http"
"github.com/docker/engine-api/client/transport"
"github.com/docker/engine-api/types"
)
type mockClient struct {
do func(*http.Request) (*http.Response, error)
}
// TLSConfig returns the TLS configuration.
func (m *mockClient) TLSConfig() *tls.Config {
return &tls.Config{}
}
// Scheme returns protocol scheme to use.
func (m *mockClient) Scheme() string {
return "http"
}
// Secure returns true if there is a TLS configuration.
func (m *mockClient) Secure() bool {
return false
}
// NewMockClient returns a mocked client that runs the function supplied as `client.Do` call
func newMockClient(tlsConfig *tls.Config, doer func(*http.Request) (*http.Response, error)) transport.Client {
if tlsConfig != nil {
panic("this actually gets set!")
}
return &mockClient{
do: doer,
}
}
// Do executes the supplied function for the mock.
func (m mockClient) Do(req *http.Request) (*http.Response, error) {
return m.do(req)
}
func errorMock(statusCode int, message string) func(req *http.Request) (*http.Response, error) {
return func(req *http.Request) (*http.Response, error) {
header := http.Header{}
header.Set("Content-Type", "application/json")
body, err := json.Marshal(&types.ErrorResponse{
Message: message,
})
if err != nil {
return nil, err
}
return &http.Response{
StatusCode: statusCode,
Body: ioutil.NopCloser(bytes.NewReader(body)),
Header: header,
}, nil
}
}
func plainTextErrorMock(statusCode int, message string) func(req *http.Request) (*http.Response, error) {
return func(req *http.Request) (*http.Response, error) {
return &http.Response{
StatusCode: statusCode,
Body: ioutil.NopCloser(bytes.NewReader([]byte(message))),
}, nil
}
}

View file

@ -0,0 +1,245 @@
package client
import (
"bytes"
"encoding/json"
"io/ioutil"
"net/http"
"net/url"
"os"
"strings"
"testing"
"github.com/docker/engine-api/types"
"golang.org/x/net/context"
)
func TestNewEnvClient(t *testing.T) {
cases := []struct {
envs map[string]string
expectedError string
expectedVersion string
}{
{
envs: map[string]string{},
expectedVersion: DefaultVersion,
},
{
envs: map[string]string{
"DOCKER_CERT_PATH": "invalid/path",
},
expectedError: "Could not load X509 key pair: open invalid/path/cert.pem: no such file or directory. Make sure the key is not encrypted",
},
{
envs: map[string]string{
"DOCKER_CERT_PATH": "testdata/",
},
expectedVersion: DefaultVersion,
},
{
envs: map[string]string{
"DOCKER_HOST": "host",
},
expectedError: "unable to parse docker host `host`",
},
{
envs: map[string]string{
"DOCKER_HOST": "invalid://url",
},
expectedVersion: DefaultVersion,
},
{
envs: map[string]string{
"DOCKER_API_VERSION": "anything",
},
expectedVersion: "anything",
},
{
envs: map[string]string{
"DOCKER_API_VERSION": "1.22",
},
expectedVersion: "1.22",
},
}
for _, c := range cases {
recoverEnvs := setupEnvs(t, c.envs)
apiclient, err := NewEnvClient()
if c.expectedError != "" {
if err == nil || err.Error() != c.expectedError {
t.Errorf("expected an error %s, got %s, for %v", c.expectedError, err.Error(), c)
}
} else {
if err != nil {
t.Error(err)
}
version := apiclient.ClientVersion()
if version != c.expectedVersion {
t.Errorf("expected %s, got %s, for %v", c.expectedVersion, version, c)
}
}
recoverEnvs(t)
}
}
func setupEnvs(t *testing.T, envs map[string]string) func(*testing.T) {
oldEnvs := map[string]string{}
for key, value := range envs {
oldEnv := os.Getenv(key)
oldEnvs[key] = oldEnv
err := os.Setenv(key, value)
if err != nil {
t.Error(err)
}
}
return func(t *testing.T) {
for key, value := range oldEnvs {
err := os.Setenv(key, value)
if err != nil {
t.Error(err)
}
}
}
}
func TestGetAPIPath(t *testing.T) {
cases := []struct {
v string
p string
q url.Values
e string
}{
{"", "/containers/json", nil, "/containers/json"},
{"", "/containers/json", url.Values{}, "/containers/json"},
{"", "/containers/json", url.Values{"s": []string{"c"}}, "/containers/json?s=c"},
{"1.22", "/containers/json", nil, "/v1.22/containers/json"},
{"1.22", "/containers/json", url.Values{}, "/v1.22/containers/json"},
{"1.22", "/containers/json", url.Values{"s": []string{"c"}}, "/v1.22/containers/json?s=c"},
{"v1.22", "/containers/json", nil, "/v1.22/containers/json"},
{"v1.22", "/containers/json", url.Values{}, "/v1.22/containers/json"},
{"v1.22", "/containers/json", url.Values{"s": []string{"c"}}, "/v1.22/containers/json?s=c"},
{"v1.22", "/networks/kiwl$%^", nil, "/v1.22/networks/kiwl$%25%5E"},
}
for _, cs := range cases {
c, err := NewClient("unix:///var/run/docker.sock", cs.v, nil, nil)
if err != nil {
t.Fatal(err)
}
g := c.getAPIPath(cs.p, cs.q)
if g != cs.e {
t.Fatalf("Expected %s, got %s", cs.e, g)
}
}
}
func TestParseHost(t *testing.T) {
cases := []struct {
host string
proto string
addr string
base string
err bool
}{
{"", "", "", "", true},
{"foobar", "", "", "", true},
{"foo://bar", "foo", "bar", "", false},
{"tcp://localhost:2476", "tcp", "localhost:2476", "", false},
{"tcp://localhost:2476/path", "tcp", "localhost:2476", "/path", false},
}
for _, cs := range cases {
p, a, b, e := ParseHost(cs.host)
if cs.err && e == nil {
t.Fatalf("expected error, got nil")
}
if !cs.err && e != nil {
t.Fatal(e)
}
if cs.proto != p {
t.Fatalf("expected proto %s, got %s", cs.proto, p)
}
if cs.addr != a {
t.Fatalf("expected addr %s, got %s", cs.addr, a)
}
if cs.base != b {
t.Fatalf("expected base %s, got %s", cs.base, b)
}
}
}
func TestUpdateClientVersion(t *testing.T) {
client := &Client{
transport: newMockClient(nil, func(req *http.Request) (*http.Response, error) {
splitQuery := strings.Split(req.URL.Path, "/")
queryVersion := splitQuery[1]
b, err := json.Marshal(types.Version{
APIVersion: queryVersion,
})
if err != nil {
return nil, err
}
return &http.Response{
StatusCode: http.StatusOK,
Body: ioutil.NopCloser(bytes.NewReader(b)),
}, nil
}),
}
cases := []struct {
v string
}{
{"1.20"},
{"v1.21"},
{"1.22"},
{"v1.22"},
}
for _, cs := range cases {
client.UpdateClientVersion(cs.v)
r, err := client.ServerVersion(context.Background())
if err != nil {
t.Fatal(err)
}
if strings.TrimPrefix(r.APIVersion, "v") != strings.TrimPrefix(cs.v, "v") {
t.Fatalf("Expected %s, got %s", cs.v, r.APIVersion)
}
}
}
func TestNewEnvClientSetsDefaultVersion(t *testing.T) {
// Unset environment variables
envVarKeys := []string{
"DOCKER_HOST",
"DOCKER_API_VERSION",
"DOCKER_TLS_VERIFY",
"DOCKER_CERT_PATH",
}
envVarValues := make(map[string]string)
for _, key := range envVarKeys {
envVarValues[key] = os.Getenv(key)
os.Setenv(key, "")
}
client, err := NewEnvClient()
if err != nil {
t.Fatal(err)
}
if client.version != DefaultVersion {
t.Fatalf("Expected %s, got %s", DefaultVersion, client.version)
}
expected := "1.22"
os.Setenv("DOCKER_API_VERSION", expected)
client, err = NewEnvClient()
if err != nil {
t.Fatal(err)
}
if client.version != expected {
t.Fatalf("Expected %s, got %s", expected, client.version)
}
// Restore environment variables
for _, key := range envVarKeys {
os.Setenv(key, envVarValues[key])
}
}

View file

@ -0,0 +1,6 @@
// +build linux freebsd solaris openbsd darwin
package client
// DefaultDockerHost defines os specific default if DOCKER_HOST is unset
const DefaultDockerHost = "unix:///var/run/docker.sock"

View file

@ -0,0 +1,4 @@
package client
// DefaultDockerHost defines os specific default if DOCKER_HOST is unset
const DefaultDockerHost = "npipe:////./pipe/docker_engine"

View file

@ -0,0 +1,34 @@
package client
import (
"net/url"
"github.com/docker/engine-api/types"
"golang.org/x/net/context"
)
// ContainerAttach attaches a connection to a container in the server.
// It returns a types.HijackedConnection with the hijacked connection
// and the a reader to get output. It's up to the called to close
// the hijacked connection by calling types.HijackedResponse.Close.
func (cli *Client) ContainerAttach(ctx context.Context, container string, options types.ContainerAttachOptions) (types.HijackedResponse, error) {
query := url.Values{}
if options.Stream {
query.Set("stream", "1")
}
if options.Stdin {
query.Set("stdin", "1")
}
if options.Stdout {
query.Set("stdout", "1")
}
if options.Stderr {
query.Set("stderr", "1")
}
if options.DetachKeys != "" {
query.Set("detachKeys", options.DetachKeys)
}
headers := map[string][]string{"Content-Type": {"text/plain"}}
return cli.postHijacked(ctx, "/containers/"+container+"/attach", query, nil, headers)
}

View file

@ -0,0 +1,53 @@
package client
import (
"encoding/json"
"errors"
"net/url"
distreference "github.com/docker/distribution/reference"
"github.com/docker/engine-api/types"
"github.com/docker/engine-api/types/reference"
"golang.org/x/net/context"
)
// ContainerCommit applies changes into a container and creates a new tagged image.
func (cli *Client) ContainerCommit(ctx context.Context, container string, options types.ContainerCommitOptions) (types.ContainerCommitResponse, error) {
var repository, tag string
if options.Reference != "" {
distributionRef, err := distreference.ParseNamed(options.Reference)
if err != nil {
return types.ContainerCommitResponse{}, err
}
if _, isCanonical := distributionRef.(distreference.Canonical); isCanonical {
return types.ContainerCommitResponse{}, errors.New("refusing to create a tag with a digest reference")
}
tag = reference.GetTagFromNamedRef(distributionRef)
repository = distributionRef.Name()
}
query := url.Values{}
query.Set("container", container)
query.Set("repo", repository)
query.Set("tag", tag)
query.Set("comment", options.Comment)
query.Set("author", options.Author)
for _, change := range options.Changes {
query.Add("changes", change)
}
if options.Pause != true {
query.Set("pause", "0")
}
var response types.ContainerCommitResponse
resp, err := cli.post(ctx, "/commit", query, options.Config, nil)
if err != nil {
return response, err
}
err = json.NewDecoder(resp.body).Decode(&response)
ensureReaderClosed(resp)
return response, err
}

View file

@ -0,0 +1,96 @@
package client
import (
"bytes"
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"strings"
"testing"
"github.com/docker/engine-api/types"
"golang.org/x/net/context"
)
func TestContainerCommitError(t *testing.T) {
client := &Client{
transport: newMockClient(nil, errorMock(http.StatusInternalServerError, "Server error")),
}
_, err := client.ContainerCommit(context.Background(), "nothing", types.ContainerCommitOptions{})
if err == nil || err.Error() != "Error response from daemon: Server error" {
t.Fatalf("expected a Server Error, got %v", err)
}
}
func TestContainerCommit(t *testing.T) {
expectedURL := "/commit"
expectedContainerID := "container_id"
specifiedReference := "repository_name:tag"
expectedRepositoryName := "repository_name"
expectedTag := "tag"
expectedComment := "comment"
expectedAuthor := "author"
expectedChanges := []string{"change1", "change2"}
client := &Client{
transport: newMockClient(nil, func(req *http.Request) (*http.Response, error) {
if !strings.HasPrefix(req.URL.Path, expectedURL) {
return nil, fmt.Errorf("Expected URL '%s', got '%s'", expectedURL, req.URL)
}
query := req.URL.Query()
containerID := query.Get("container")
if containerID != expectedContainerID {
return nil, fmt.Errorf("container id not set in URL query properly. Expected '%s', got %s", expectedContainerID, containerID)
}
repo := query.Get("repo")
if repo != expectedRepositoryName {
return nil, fmt.Errorf("container repo not set in URL query properly. Expected '%s', got %s", expectedRepositoryName, repo)
}
tag := query.Get("tag")
if tag != expectedTag {
return nil, fmt.Errorf("container tag not set in URL query properly. Expected '%s', got %s'", expectedTag, tag)
}
comment := query.Get("comment")
if comment != expectedComment {
return nil, fmt.Errorf("container comment not set in URL query properly. Expected '%s', got %s'", expectedComment, comment)
}
author := query.Get("author")
if author != expectedAuthor {
return nil, fmt.Errorf("container author not set in URL query properly. Expected '%s', got %s'", expectedAuthor, author)
}
pause := query.Get("pause")
if pause != "0" {
return nil, fmt.Errorf("container pause not set in URL query properly. Expected 'true', got %v'", pause)
}
changes := query["changes"]
if len(changes) != len(expectedChanges) {
return nil, fmt.Errorf("expected container changes size to be '%d', got %d", len(expectedChanges), len(changes))
}
b, err := json.Marshal(types.ContainerCommitResponse{
ID: "new_container_id",
})
if err != nil {
return nil, err
}
return &http.Response{
StatusCode: http.StatusOK,
Body: ioutil.NopCloser(bytes.NewReader(b)),
}, nil
}),
}
r, err := client.ContainerCommit(context.Background(), expectedContainerID, types.ContainerCommitOptions{
Reference: specifiedReference,
Comment: expectedComment,
Author: expectedAuthor,
Changes: expectedChanges,
Pause: false,
})
if err != nil {
t.Fatal(err)
}
if r.ID != "new_container_id" {
t.Fatalf("expected `container_id`, got %s", r.ID)
}
}

View file

@ -0,0 +1,97 @@
package client
import (
"encoding/base64"
"encoding/json"
"fmt"
"io"
"net/http"
"net/url"
"path/filepath"
"strings"
"golang.org/x/net/context"
"github.com/docker/engine-api/types"
)
// ContainerStatPath returns Stat information about a path inside the container filesystem.
func (cli *Client) ContainerStatPath(ctx context.Context, containerID, path string) (types.ContainerPathStat, error) {
query := url.Values{}
query.Set("path", filepath.ToSlash(path)) // Normalize the paths used in the API.
urlStr := fmt.Sprintf("/containers/%s/archive", containerID)
response, err := cli.head(ctx, urlStr, query, nil)
if err != nil {
return types.ContainerPathStat{}, err
}
defer ensureReaderClosed(response)
return getContainerPathStatFromHeader(response.header)
}
// CopyToContainer copies content into the container filesystem.
func (cli *Client) CopyToContainer(ctx context.Context, container, path string, content io.Reader, options types.CopyToContainerOptions) error {
query := url.Values{}
query.Set("path", filepath.ToSlash(path)) // Normalize the paths used in the API.
// Do not allow for an existing directory to be overwritten by a non-directory and vice versa.
if !options.AllowOverwriteDirWithFile {
query.Set("noOverwriteDirNonDir", "true")
}
apiPath := fmt.Sprintf("/containers/%s/archive", container)
response, err := cli.putRaw(ctx, apiPath, query, content, nil)
if err != nil {
return err
}
defer ensureReaderClosed(response)
if response.statusCode != http.StatusOK {
return fmt.Errorf("unexpected status code from daemon: %d", response.statusCode)
}
return nil
}
// CopyFromContainer gets the content from the container and returns it as a Reader
// to manipulate it in the host. It's up to the caller to close the reader.
func (cli *Client) CopyFromContainer(ctx context.Context, container, srcPath string) (io.ReadCloser, types.ContainerPathStat, error) {
query := make(url.Values, 1)
query.Set("path", filepath.ToSlash(srcPath)) // Normalize the paths used in the API.
apiPath := fmt.Sprintf("/containers/%s/archive", container)
response, err := cli.get(ctx, apiPath, query, nil)
if err != nil {
return nil, types.ContainerPathStat{}, err
}
if response.statusCode != http.StatusOK {
return nil, types.ContainerPathStat{}, fmt.Errorf("unexpected status code from daemon: %d", response.statusCode)
}
// In order to get the copy behavior right, we need to know information
// about both the source and the destination. The response headers include
// stat info about the source that we can use in deciding exactly how to
// copy it locally. Along with the stat info about the local destination,
// we have everything we need to handle the multiple possibilities there
// can be when copying a file/dir from one location to another file/dir.
stat, err := getContainerPathStatFromHeader(response.header)
if err != nil {
return nil, stat, fmt.Errorf("unable to get resource stat from response: %s", err)
}
return response.body, stat, err
}
func getContainerPathStatFromHeader(header http.Header) (types.ContainerPathStat, error) {
var stat types.ContainerPathStat
encodedStat := header.Get("X-Docker-Container-Path-Stat")
statDecoder := base64.NewDecoder(base64.StdEncoding, strings.NewReader(encodedStat))
err := json.NewDecoder(statDecoder).Decode(&stat)
if err != nil {
err = fmt.Errorf("unable to decode container path stat header: %s", err)
}
return stat, err
}

View file

@ -0,0 +1,244 @@
package client
import (
"bytes"
"encoding/base64"
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"strings"
"testing"
"golang.org/x/net/context"
"github.com/docker/engine-api/types"
)
func TestContainerStatPathError(t *testing.T) {
client := &Client{
transport: newMockClient(nil, errorMock(http.StatusInternalServerError, "Server error")),
}
_, err := client.ContainerStatPath(context.Background(), "container_id", "path")
if err == nil || err.Error() != "Error response from daemon: Server error" {
t.Fatalf("expected a Server error, got %v", err)
}
}
func TestContainerStatPathNoHeaderError(t *testing.T) {
client := &Client{
transport: newMockClient(nil, func(req *http.Request) (*http.Response, error) {
return &http.Response{
StatusCode: http.StatusOK,
Body: ioutil.NopCloser(bytes.NewReader([]byte(""))),
}, nil
}),
}
_, err := client.ContainerStatPath(context.Background(), "container_id", "path/to/file")
if err == nil {
t.Fatalf("expected an error, got nothing")
}
}
func TestContainerStatPath(t *testing.T) {
expectedURL := "/containers/container_id/archive"
expectedPath := "path/to/file"
client := &Client{
transport: newMockClient(nil, func(req *http.Request) (*http.Response, error) {
if !strings.HasPrefix(req.URL.Path, expectedURL) {
return nil, fmt.Errorf("Expected URL '%s', got '%s'", expectedURL, req.URL)
}
if req.Method != "HEAD" {
return nil, fmt.Errorf("expected HEAD method, got %s", req.Method)
}
query := req.URL.Query()
path := query.Get("path")
if path != expectedPath {
return nil, fmt.Errorf("path not set in URL query properly")
}
content, err := json.Marshal(types.ContainerPathStat{
Name: "name",
Mode: 0700,
})
if err != nil {
return nil, err
}
base64PathStat := base64.StdEncoding.EncodeToString(content)
return &http.Response{
StatusCode: http.StatusOK,
Body: ioutil.NopCloser(bytes.NewReader([]byte(""))),
Header: http.Header{
"X-Docker-Container-Path-Stat": []string{base64PathStat},
},
}, nil
}),
}
stat, err := client.ContainerStatPath(context.Background(), "container_id", expectedPath)
if err != nil {
t.Fatal(err)
}
if stat.Name != "name" {
t.Fatalf("expected container path stat name to be 'name', was '%s'", stat.Name)
}
if stat.Mode != 0700 {
t.Fatalf("expected container path stat mode to be 0700, was '%v'", stat.Mode)
}
}
func TestCopyToContainerError(t *testing.T) {
client := &Client{
transport: newMockClient(nil, errorMock(http.StatusInternalServerError, "Server error")),
}
err := client.CopyToContainer(context.Background(), "container_id", "path/to/file", bytes.NewReader([]byte("")), types.CopyToContainerOptions{})
if err == nil || err.Error() != "Error response from daemon: Server error" {
t.Fatalf("expected a Server error, got %v", err)
}
}
func TestCopyToContainerNotStatusOKError(t *testing.T) {
client := &Client{
transport: newMockClient(nil, errorMock(http.StatusNoContent, "No content")),
}
err := client.CopyToContainer(context.Background(), "container_id", "path/to/file", bytes.NewReader([]byte("")), types.CopyToContainerOptions{})
if err == nil || err.Error() != "unexpected status code from daemon: 204" {
t.Fatalf("expected an unexpected status code error, got %v", err)
}
}
func TestCopyToContainer(t *testing.T) {
expectedURL := "/containers/container_id/archive"
expectedPath := "path/to/file"
client := &Client{
transport: newMockClient(nil, func(req *http.Request) (*http.Response, error) {
if !strings.HasPrefix(req.URL.Path, expectedURL) {
return nil, fmt.Errorf("Expected URL '%s', got '%s'", expectedURL, req.URL)
}
if req.Method != "PUT" {
return nil, fmt.Errorf("expected PUT method, got %s", req.Method)
}
query := req.URL.Query()
path := query.Get("path")
if path != expectedPath {
return nil, fmt.Errorf("path not set in URL query properly, expected '%s', got %s", expectedPath, path)
}
noOverwriteDirNonDir := query.Get("noOverwriteDirNonDir")
if noOverwriteDirNonDir != "true" {
return nil, fmt.Errorf("noOverwriteDirNonDir not set in URL query properly, expected true, got %s", noOverwriteDirNonDir)
}
content, err := ioutil.ReadAll(req.Body)
if err != nil {
return nil, err
}
if err := req.Body.Close(); err != nil {
return nil, err
}
if string(content) != "content" {
return nil, fmt.Errorf("expected content to be 'content', got %s", string(content))
}
return &http.Response{
StatusCode: http.StatusOK,
Body: ioutil.NopCloser(bytes.NewReader([]byte(""))),
}, nil
}),
}
err := client.CopyToContainer(context.Background(), "container_id", expectedPath, bytes.NewReader([]byte("content")), types.CopyToContainerOptions{
AllowOverwriteDirWithFile: false,
})
if err != nil {
t.Fatal(err)
}
}
func TestCopyFromContainerError(t *testing.T) {
client := &Client{
transport: newMockClient(nil, errorMock(http.StatusInternalServerError, "Server error")),
}
_, _, err := client.CopyFromContainer(context.Background(), "container_id", "path/to/file")
if err == nil || err.Error() != "Error response from daemon: Server error" {
t.Fatalf("expected a Server error, got %v", err)
}
}
func TestCopyFromContainerNotStatusOKError(t *testing.T) {
client := &Client{
transport: newMockClient(nil, errorMock(http.StatusNoContent, "No content")),
}
_, _, err := client.CopyFromContainer(context.Background(), "container_id", "path/to/file")
if err == nil || err.Error() != "unexpected status code from daemon: 204" {
t.Fatalf("expected an unexpected status code error, got %v", err)
}
}
func TestCopyFromContainerNoHeaderError(t *testing.T) {
client := &Client{
transport: newMockClient(nil, func(req *http.Request) (*http.Response, error) {
return &http.Response{
StatusCode: http.StatusOK,
Body: ioutil.NopCloser(bytes.NewReader([]byte(""))),
}, nil
}),
}
_, _, err := client.CopyFromContainer(context.Background(), "container_id", "path/to/file")
if err == nil {
t.Fatalf("expected an error, got nothing")
}
}
func TestCopyFromContainer(t *testing.T) {
expectedURL := "/containers/container_id/archive"
expectedPath := "path/to/file"
client := &Client{
transport: newMockClient(nil, func(req *http.Request) (*http.Response, error) {
if !strings.HasPrefix(req.URL.Path, expectedURL) {
return nil, fmt.Errorf("Expected URL '%s', got '%s'", expectedURL, req.URL)
}
if req.Method != "GET" {
return nil, fmt.Errorf("expected PUT method, got %s", req.Method)
}
query := req.URL.Query()
path := query.Get("path")
if path != expectedPath {
return nil, fmt.Errorf("path not set in URL query properly, expected '%s', got %s", expectedPath, path)
}
headercontent, err := json.Marshal(types.ContainerPathStat{
Name: "name",
Mode: 0700,
})
if err != nil {
return nil, err
}
base64PathStat := base64.StdEncoding.EncodeToString(headercontent)
return &http.Response{
StatusCode: http.StatusOK,
Body: ioutil.NopCloser(bytes.NewReader([]byte("content"))),
Header: http.Header{
"X-Docker-Container-Path-Stat": []string{base64PathStat},
},
}, nil
}),
}
r, stat, err := client.CopyFromContainer(context.Background(), "container_id", expectedPath)
if err != nil {
t.Fatal(err)
}
if stat.Name != "name" {
t.Fatalf("expected container path stat name to be 'name', was '%s'", stat.Name)
}
if stat.Mode != 0700 {
t.Fatalf("expected container path stat mode to be 0700, was '%v'", stat.Mode)
}
content, err := ioutil.ReadAll(r)
if err != nil {
t.Fatal(err)
}
if err := r.Close(); err != nil {
t.Fatal(err)
}
if string(content) != "content" {
t.Fatalf("expected content to be 'content', got %s", string(content))
}
}

View file

@ -0,0 +1,46 @@
package client
import (
"encoding/json"
"net/url"
"strings"
"github.com/docker/engine-api/types"
"github.com/docker/engine-api/types/container"
"github.com/docker/engine-api/types/network"
"golang.org/x/net/context"
)
type configWrapper struct {
*container.Config
HostConfig *container.HostConfig
NetworkingConfig *network.NetworkingConfig
}
// ContainerCreate creates a new container based in the given configuration.
// It can be associated with a name, but it's not mandatory.
func (cli *Client) ContainerCreate(ctx context.Context, config *container.Config, hostConfig *container.HostConfig, networkingConfig *network.NetworkingConfig, containerName string) (types.ContainerCreateResponse, error) {
var response types.ContainerCreateResponse
query := url.Values{}
if containerName != "" {
query.Set("name", containerName)
}
body := configWrapper{
Config: config,
HostConfig: hostConfig,
NetworkingConfig: networkingConfig,
}
serverResp, err := cli.post(ctx, "/containers/create", query, body, nil)
if err != nil {
if serverResp.statusCode == 404 && strings.Contains(err.Error(), "No such image") {
return response, imageNotFoundError{config.Image}
}
return response, err
}
err = json.NewDecoder(serverResp.body).Decode(&response)
ensureReaderClosed(serverResp)
return response, err
}

View file

@ -0,0 +1,77 @@
package client
import (
"bytes"
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"strings"
"testing"
"github.com/docker/engine-api/types"
"github.com/docker/engine-api/types/container"
"golang.org/x/net/context"
)
func TestContainerCreateError(t *testing.T) {
client := &Client{
transport: newMockClient(nil, errorMock(http.StatusInternalServerError, "Server error")),
}
_, err := client.ContainerCreate(context.Background(), nil, nil, nil, "nothing")
if err == nil || err.Error() != "Error response from daemon: Server error" {
t.Fatalf("expected a Server Error, got %v", err)
}
// 404 doesn't automagitally means an unknown image
client = &Client{
transport: newMockClient(nil, errorMock(http.StatusNotFound, "Server error")),
}
_, err = client.ContainerCreate(context.Background(), nil, nil, nil, "nothing")
if err == nil || err.Error() != "Error response from daemon: Server error" {
t.Fatalf("expected a Server Error, got %v", err)
}
}
func TestContainerCreateImageNotFound(t *testing.T) {
client := &Client{
transport: newMockClient(nil, errorMock(http.StatusNotFound, "No such image")),
}
_, err := client.ContainerCreate(context.Background(), &container.Config{Image: "unknown_image"}, nil, nil, "unknown")
if err == nil || !IsErrImageNotFound(err) {
t.Fatalf("expected an imageNotFound error, got %v", err)
}
}
func TestContainerCreateWithName(t *testing.T) {
expectedURL := "/containers/create"
client := &Client{
transport: newMockClient(nil, func(req *http.Request) (*http.Response, error) {
if !strings.HasPrefix(req.URL.Path, expectedURL) {
return nil, fmt.Errorf("Expected URL '%s', got '%s'", expectedURL, req.URL)
}
name := req.URL.Query().Get("name")
if name != "container_name" {
return nil, fmt.Errorf("container name not set in URL query properly. Expected `container_name`, got %s", name)
}
b, err := json.Marshal(types.ContainerCreateResponse{
ID: "container_id",
})
if err != nil {
return nil, err
}
return &http.Response{
StatusCode: http.StatusOK,
Body: ioutil.NopCloser(bytes.NewReader(b)),
}, nil
}),
}
r, err := client.ContainerCreate(context.Background(), nil, nil, nil, "container_name")
if err != nil {
t.Fatal(err)
}
if r.ID != "container_id" {
t.Fatalf("expected `container_id`, got %s", r.ID)
}
}

View file

@ -0,0 +1,23 @@
package client
import (
"encoding/json"
"net/url"
"github.com/docker/engine-api/types"
"golang.org/x/net/context"
)
// ContainerDiff shows differences in a container filesystem since it was started.
func (cli *Client) ContainerDiff(ctx context.Context, containerID string) ([]types.ContainerChange, error) {
var changes []types.ContainerChange
serverResp, err := cli.get(ctx, "/containers/"+containerID+"/changes", url.Values{}, nil)
if err != nil {
return changes, err
}
err = json.NewDecoder(serverResp.body).Decode(&changes)
ensureReaderClosed(serverResp)
return changes, err
}

View file

@ -0,0 +1,61 @@
package client
import (
"bytes"
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"strings"
"testing"
"github.com/docker/engine-api/types"
"golang.org/x/net/context"
)
func TestContainerDiffError(t *testing.T) {
client := &Client{
transport: newMockClient(nil, errorMock(http.StatusInternalServerError, "Server error")),
}
_, err := client.ContainerDiff(context.Background(), "nothing")
if err == nil || err.Error() != "Error response from daemon: Server error" {
t.Fatalf("expected a Server Error, got %v", err)
}
}
func TestContainerDiff(t *testing.T) {
expectedURL := "/containers/container_id/changes"
client := &Client{
transport: newMockClient(nil, func(req *http.Request) (*http.Response, error) {
if !strings.HasPrefix(req.URL.Path, expectedURL) {
return nil, fmt.Errorf("Expected URL '%s', got '%s'", expectedURL, req.URL)
}
b, err := json.Marshal([]types.ContainerChange{
{
Kind: 0,
Path: "/path/1",
},
{
Kind: 1,
Path: "/path/2",
},
})
if err != nil {
return nil, err
}
return &http.Response{
StatusCode: http.StatusOK,
Body: ioutil.NopCloser(bytes.NewReader(b)),
}, nil
}),
}
changes, err := client.ContainerDiff(context.Background(), "container_id")
if err != nil {
t.Fatal(err)
}
if len(changes) != 2 {
t.Fatalf("expected an array of 2 changes, got %v", changes)
}
}

View file

@ -0,0 +1,49 @@
package client
import (
"encoding/json"
"github.com/docker/engine-api/types"
"golang.org/x/net/context"
)
// ContainerExecCreate creates a new exec configuration to run an exec process.
func (cli *Client) ContainerExecCreate(ctx context.Context, container string, config types.ExecConfig) (types.ContainerExecCreateResponse, error) {
var response types.ContainerExecCreateResponse
resp, err := cli.post(ctx, "/containers/"+container+"/exec", nil, config, nil)
if err != nil {
return response, err
}
err = json.NewDecoder(resp.body).Decode(&response)
ensureReaderClosed(resp)
return response, err
}
// ContainerExecStart starts an exec process already created in the docker host.
func (cli *Client) ContainerExecStart(ctx context.Context, execID string, config types.ExecStartCheck) error {
resp, err := cli.post(ctx, "/exec/"+execID+"/start", nil, config, nil)
ensureReaderClosed(resp)
return err
}
// ContainerExecAttach attaches a connection to an exec process in the server.
// It returns a types.HijackedConnection with the hijacked connection
// and the a reader to get output. It's up to the called to close
// the hijacked connection by calling types.HijackedResponse.Close.
func (cli *Client) ContainerExecAttach(ctx context.Context, execID string, config types.ExecConfig) (types.HijackedResponse, error) {
headers := map[string][]string{"Content-Type": {"application/json"}}
return cli.postHijacked(ctx, "/exec/"+execID+"/start", nil, config, headers)
}
// ContainerExecInspect returns information about a specific exec process on the docker host.
func (cli *Client) ContainerExecInspect(ctx context.Context, execID string) (types.ContainerExecInspect, error) {
var response types.ContainerExecInspect
resp, err := cli.get(ctx, "/exec/"+execID+"/json", nil, nil)
if err != nil {
return response, err
}
err = json.NewDecoder(resp.body).Decode(&response)
ensureReaderClosed(resp)
return response, err
}

View file

@ -0,0 +1,157 @@
package client
import (
"bytes"
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"strings"
"testing"
"golang.org/x/net/context"
"github.com/docker/engine-api/types"
)
func TestContainerExecCreateError(t *testing.T) {
client := &Client{
transport: newMockClient(nil, errorMock(http.StatusInternalServerError, "Server error")),
}
_, err := client.ContainerExecCreate(context.Background(), "container_id", types.ExecConfig{})
if err == nil || err.Error() != "Error response from daemon: Server error" {
t.Fatalf("expected a Server Error, got %v", err)
}
}
func TestContainerExecCreate(t *testing.T) {
expectedURL := "/containers/container_id/exec"
client := &Client{
transport: newMockClient(nil, func(req *http.Request) (*http.Response, error) {
if !strings.HasPrefix(req.URL.Path, expectedURL) {
return nil, fmt.Errorf("expected URL '%s', got '%s'", expectedURL, req.URL)
}
if req.Method != "POST" {
return nil, fmt.Errorf("expected POST method, got %s", req.Method)
}
// FIXME validate the content is the given ExecConfig ?
if err := req.ParseForm(); err != nil {
return nil, err
}
execConfig := &types.ExecConfig{}
if err := json.NewDecoder(req.Body).Decode(execConfig); err != nil {
return nil, err
}
if execConfig.User != "user" {
return nil, fmt.Errorf("expected an execConfig with User == 'user', got %v", execConfig)
}
b, err := json.Marshal(types.ContainerExecCreateResponse{
ID: "exec_id",
})
if err != nil {
return nil, err
}
return &http.Response{
StatusCode: http.StatusOK,
Body: ioutil.NopCloser(bytes.NewReader(b)),
}, nil
}),
}
r, err := client.ContainerExecCreate(context.Background(), "container_id", types.ExecConfig{
User: "user",
})
if err != nil {
t.Fatal(err)
}
if r.ID != "exec_id" {
t.Fatalf("expected `exec_id`, got %s", r.ID)
}
}
func TestContainerExecStartError(t *testing.T) {
client := &Client{
transport: newMockClient(nil, errorMock(http.StatusInternalServerError, "Server error")),
}
err := client.ContainerExecStart(context.Background(), "nothing", types.ExecStartCheck{})
if err == nil || err.Error() != "Error response from daemon: Server error" {
t.Fatalf("expected a Server Error, got %v", err)
}
}
func TestContainerExecStart(t *testing.T) {
expectedURL := "/exec/exec_id/start"
client := &Client{
transport: newMockClient(nil, func(req *http.Request) (*http.Response, error) {
if !strings.HasPrefix(req.URL.Path, expectedURL) {
return nil, fmt.Errorf("Expected URL '%s', got '%s'", expectedURL, req.URL)
}
if err := req.ParseForm(); err != nil {
return nil, err
}
execStartCheck := &types.ExecStartCheck{}
if err := json.NewDecoder(req.Body).Decode(execStartCheck); err != nil {
return nil, err
}
if execStartCheck.Tty || !execStartCheck.Detach {
return nil, fmt.Errorf("expected execStartCheck{Detach:true,Tty:false}, got %v", execStartCheck)
}
return &http.Response{
StatusCode: http.StatusOK,
Body: ioutil.NopCloser(bytes.NewReader([]byte(""))),
}, nil
}),
}
err := client.ContainerExecStart(context.Background(), "exec_id", types.ExecStartCheck{
Detach: true,
Tty: false,
})
if err != nil {
t.Fatal(err)
}
}
func TestContainerExecInspectError(t *testing.T) {
client := &Client{
transport: newMockClient(nil, errorMock(http.StatusInternalServerError, "Server error")),
}
_, err := client.ContainerExecInspect(context.Background(), "nothing")
if err == nil || err.Error() != "Error response from daemon: Server error" {
t.Fatalf("expected a Server Error, got %v", err)
}
}
func TestContainerExecInspect(t *testing.T) {
expectedURL := "/exec/exec_id/json"
client := &Client{
transport: newMockClient(nil, func(req *http.Request) (*http.Response, error) {
if !strings.HasPrefix(req.URL.Path, expectedURL) {
return nil, fmt.Errorf("Expected URL '%s', got '%s'", expectedURL, req.URL)
}
b, err := json.Marshal(types.ContainerExecInspect{
ExecID: "exec_id",
ContainerID: "container_id",
})
if err != nil {
return nil, err
}
return &http.Response{
StatusCode: http.StatusOK,
Body: ioutil.NopCloser(bytes.NewReader(b)),
}, nil
}),
}
inspect, err := client.ContainerExecInspect(context.Background(), "exec_id")
if err != nil {
t.Fatal(err)
}
if inspect.ExecID != "exec_id" {
t.Fatalf("expected ExecID to be `exec_id`, got %s", inspect.ExecID)
}
if inspect.ContainerID != "container_id" {
t.Fatalf("expected ContainerID `container_id`, got %s", inspect.ContainerID)
}
}

View file

@ -0,0 +1,20 @@
package client
import (
"io"
"net/url"
"golang.org/x/net/context"
)
// ContainerExport retrieves the raw contents of a container
// and returns them as an io.ReadCloser. It's up to the caller
// to close the stream.
func (cli *Client) ContainerExport(ctx context.Context, containerID string) (io.ReadCloser, error) {
serverResp, err := cli.get(ctx, "/containers/"+containerID+"/export", url.Values{}, nil)
if err != nil {
return nil, err
}
return serverResp.body, nil
}

View file

@ -0,0 +1,50 @@
package client
import (
"bytes"
"fmt"
"io/ioutil"
"net/http"
"strings"
"testing"
"golang.org/x/net/context"
)
func TestContainerExportError(t *testing.T) {
client := &Client{
transport: newMockClient(nil, errorMock(http.StatusInternalServerError, "Server error")),
}
_, err := client.ContainerExport(context.Background(), "nothing")
if err == nil || err.Error() != "Error response from daemon: Server error" {
t.Fatalf("expected a Server Error, got %v", err)
}
}
func TestContainerExport(t *testing.T) {
expectedURL := "/containers/container_id/export"
client := &Client{
transport: newMockClient(nil, func(r *http.Request) (*http.Response, error) {
if !strings.HasPrefix(r.URL.Path, expectedURL) {
return nil, fmt.Errorf("Expected URL '%s', got '%s'", expectedURL, r.URL)
}
return &http.Response{
StatusCode: http.StatusOK,
Body: ioutil.NopCloser(bytes.NewReader([]byte("response"))),
}, nil
}),
}
body, err := client.ContainerExport(context.Background(), "container_id")
if err != nil {
t.Fatal(err)
}
defer body.Close()
content, err := ioutil.ReadAll(body)
if err != nil {
t.Fatal(err)
}
if string(content) != "response" {
t.Fatalf("expected response to contain 'response', got %s", string(content))
}
}

View file

@ -0,0 +1,54 @@
package client
import (
"bytes"
"encoding/json"
"io/ioutil"
"net/http"
"net/url"
"github.com/docker/engine-api/types"
"golang.org/x/net/context"
)
// ContainerInspect returns the container information.
func (cli *Client) ContainerInspect(ctx context.Context, containerID string) (types.ContainerJSON, error) {
serverResp, err := cli.get(ctx, "/containers/"+containerID+"/json", nil, nil)
if err != nil {
if serverResp.statusCode == http.StatusNotFound {
return types.ContainerJSON{}, containerNotFoundError{containerID}
}
return types.ContainerJSON{}, err
}
var response types.ContainerJSON
err = json.NewDecoder(serverResp.body).Decode(&response)
ensureReaderClosed(serverResp)
return response, err
}
// ContainerInspectWithRaw returns the container information and its raw representation.
func (cli *Client) ContainerInspectWithRaw(ctx context.Context, containerID string, getSize bool) (types.ContainerJSON, []byte, error) {
query := url.Values{}
if getSize {
query.Set("size", "1")
}
serverResp, err := cli.get(ctx, "/containers/"+containerID+"/json", query, nil)
if err != nil {
if serverResp.statusCode == http.StatusNotFound {
return types.ContainerJSON{}, nil, containerNotFoundError{containerID}
}
return types.ContainerJSON{}, nil, err
}
defer ensureReaderClosed(serverResp)
body, err := ioutil.ReadAll(serverResp.body)
if err != nil {
return types.ContainerJSON{}, nil, err
}
var response types.ContainerJSON
rdr := bytes.NewReader(body)
err = json.NewDecoder(rdr).Decode(&response)
return response, body, err
}

View file

@ -0,0 +1,125 @@
package client
import (
"bytes"
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"strings"
"testing"
"github.com/docker/engine-api/types"
"golang.org/x/net/context"
)
func TestContainerInspectError(t *testing.T) {
client := &Client{
transport: newMockClient(nil, errorMock(http.StatusInternalServerError, "Server error")),
}
_, err := client.ContainerInspect(context.Background(), "nothing")
if err == nil || err.Error() != "Error response from daemon: Server error" {
t.Fatalf("expected a Server Error, got %v", err)
}
}
func TestContainerInspectContainerNotFound(t *testing.T) {
client := &Client{
transport: newMockClient(nil, errorMock(http.StatusNotFound, "Server error")),
}
_, err := client.ContainerInspect(context.Background(), "unknown")
if err == nil || !IsErrContainerNotFound(err) {
t.Fatalf("expected a containerNotFound error, got %v", err)
}
}
func TestContainerInspect(t *testing.T) {
expectedURL := "/containers/container_id/json"
client := &Client{
transport: newMockClient(nil, func(req *http.Request) (*http.Response, error) {
if !strings.HasPrefix(req.URL.Path, expectedURL) {
return nil, fmt.Errorf("Expected URL '%s', got '%s'", expectedURL, req.URL)
}
content, err := json.Marshal(types.ContainerJSON{
ContainerJSONBase: &types.ContainerJSONBase{
ID: "container_id",
Image: "image",
Name: "name",
},
})
if err != nil {
return nil, err
}
return &http.Response{
StatusCode: http.StatusOK,
Body: ioutil.NopCloser(bytes.NewReader(content)),
}, nil
}),
}
r, err := client.ContainerInspect(context.Background(), "container_id")
if err != nil {
t.Fatal(err)
}
if r.ID != "container_id" {
t.Fatalf("expected `container_id`, got %s", r.ID)
}
if r.Image != "image" {
t.Fatalf("expected `image`, got %s", r.ID)
}
if r.Name != "name" {
t.Fatalf("expected `name`, got %s", r.ID)
}
}
func TestContainerInspectNode(t *testing.T) {
client := &Client{
transport: newMockClient(nil, func(req *http.Request) (*http.Response, error) {
content, err := json.Marshal(types.ContainerJSON{
ContainerJSONBase: &types.ContainerJSONBase{
ID: "container_id",
Image: "image",
Name: "name",
Node: &types.ContainerNode{
ID: "container_node_id",
Addr: "container_node",
Labels: map[string]string{"foo": "bar"},
},
},
})
if err != nil {
return nil, err
}
return &http.Response{
StatusCode: http.StatusOK,
Body: ioutil.NopCloser(bytes.NewReader(content)),
}, nil
}),
}
r, err := client.ContainerInspect(context.Background(), "container_id")
if err != nil {
t.Fatal(err)
}
if r.ID != "container_id" {
t.Fatalf("expected `container_id`, got %s", r.ID)
}
if r.Image != "image" {
t.Fatalf("expected `image`, got %s", r.ID)
}
if r.Name != "name" {
t.Fatalf("expected `name`, got %s", r.ID)
}
if r.Node.ID != "container_node_id" {
t.Fatalf("expected `container_node_id`, got %s", r.Node.ID)
}
if r.Node.Addr != "container_node" {
t.Fatalf("expected `container_node`, got %s", r.Node.Addr)
}
foo, ok := r.Node.Labels["foo"]
if foo != "bar" || !ok {
t.Fatalf("expected `bar` for label `foo`")
}
}

View file

@ -0,0 +1,17 @@
package client
import (
"net/url"
"golang.org/x/net/context"
)
// ContainerKill terminates the container process but does not remove the container from the docker host.
func (cli *Client) ContainerKill(ctx context.Context, containerID, signal string) error {
query := url.Values{}
query.Set("signal", signal)
resp, err := cli.post(ctx, "/containers/"+containerID+"/kill", query, nil, nil)
ensureReaderClosed(resp)
return err
}

View file

@ -0,0 +1,46 @@
package client
import (
"bytes"
"fmt"
"io/ioutil"
"net/http"
"strings"
"testing"
"golang.org/x/net/context"
)
func TestContainerKillError(t *testing.T) {
client := &Client{
transport: newMockClient(nil, errorMock(http.StatusInternalServerError, "Server error")),
}
err := client.ContainerKill(context.Background(), "nothing", "SIGKILL")
if err == nil || err.Error() != "Error response from daemon: Server error" {
t.Fatalf("expected a Server Error, got %v", err)
}
}
func TestContainerKill(t *testing.T) {
expectedURL := "/containers/container_id/kill"
client := &Client{
transport: newMockClient(nil, func(req *http.Request) (*http.Response, error) {
if !strings.HasPrefix(req.URL.Path, expectedURL) {
return nil, fmt.Errorf("Expected URL '%s', got '%s'", expectedURL, req.URL)
}
signal := req.URL.Query().Get("signal")
if signal != "SIGKILL" {
return nil, fmt.Errorf("signal not set in URL query properly. Expected 'SIGKILL', got %s", signal)
}
return &http.Response{
StatusCode: http.StatusOK,
Body: ioutil.NopCloser(bytes.NewReader([]byte(""))),
}, nil
}),
}
err := client.ContainerKill(context.Background(), "container_id", "SIGKILL")
if err != nil {
t.Fatal(err)
}
}

View file

@ -0,0 +1,56 @@
package client
import (
"encoding/json"
"net/url"
"strconv"
"github.com/docker/engine-api/types"
"github.com/docker/engine-api/types/filters"
"golang.org/x/net/context"
)
// ContainerList returns the list of containers in the docker host.
func (cli *Client) ContainerList(ctx context.Context, options types.ContainerListOptions) ([]types.Container, error) {
query := url.Values{}
if options.All {
query.Set("all", "1")
}
if options.Limit != -1 {
query.Set("limit", strconv.Itoa(options.Limit))
}
if options.Since != "" {
query.Set("since", options.Since)
}
if options.Before != "" {
query.Set("before", options.Before)
}
if options.Size {
query.Set("size", "1")
}
if options.Filter.Len() > 0 {
filterJSON, err := filters.ToParamWithVersion(cli.version, options.Filter)
if err != nil {
return nil, err
}
query.Set("filters", filterJSON)
}
resp, err := cli.get(ctx, "/containers/json", query, nil)
if err != nil {
return nil, err
}
var containers []types.Container
err = json.NewDecoder(resp.body).Decode(&containers)
ensureReaderClosed(resp)
return containers, err
}

View file

@ -0,0 +1,96 @@
package client
import (
"bytes"
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"strings"
"testing"
"github.com/docker/engine-api/types"
"github.com/docker/engine-api/types/filters"
"golang.org/x/net/context"
)
func TestContainerListError(t *testing.T) {
client := &Client{
transport: newMockClient(nil, errorMock(http.StatusInternalServerError, "Server error")),
}
_, err := client.ContainerList(context.Background(), types.ContainerListOptions{})
if err == nil || err.Error() != "Error response from daemon: Server error" {
t.Fatalf("expected a Server Error, got %v", err)
}
}
func TestContainerList(t *testing.T) {
expectedURL := "/containers/json"
expectedFilters := `{"before":{"container":true},"label":{"label1":true,"label2":true}}`
client := &Client{
transport: newMockClient(nil, func(req *http.Request) (*http.Response, error) {
if !strings.HasPrefix(req.URL.Path, expectedURL) {
return nil, fmt.Errorf("Expected URL '%s', got '%s'", expectedURL, req.URL)
}
query := req.URL.Query()
all := query.Get("all")
if all != "1" {
return nil, fmt.Errorf("all not set in URL query properly. Expected '1', got %s", all)
}
limit := query.Get("limit")
if limit != "0" {
return nil, fmt.Errorf("limit should have not be present in query. Expected '0', got %s", limit)
}
since := query.Get("since")
if since != "container" {
return nil, fmt.Errorf("since not set in URL query properly. Expected 'container', got %s", since)
}
before := query.Get("before")
if before != "" {
return nil, fmt.Errorf("before should have not be present in query, go %s", before)
}
size := query.Get("size")
if size != "1" {
return nil, fmt.Errorf("size not set in URL query properly. Expected '1', got %s", size)
}
filters := query.Get("filters")
if filters != expectedFilters {
return nil, fmt.Errorf("expected filters incoherent '%v' with actual filters %v", expectedFilters, filters)
}
b, err := json.Marshal([]types.Container{
{
ID: "container_id1",
},
{
ID: "container_id2",
},
})
if err != nil {
return nil, err
}
return &http.Response{
StatusCode: http.StatusOK,
Body: ioutil.NopCloser(bytes.NewReader(b)),
}, nil
}),
}
filters := filters.NewArgs()
filters.Add("label", "label1")
filters.Add("label", "label2")
filters.Add("before", "container")
containers, err := client.ContainerList(context.Background(), types.ContainerListOptions{
Size: true,
All: true,
Since: "container",
Filter: filters,
})
if err != nil {
t.Fatal(err)
}
if len(containers) != 2 {
t.Fatalf("expected 2 containers, got %v", containers)
}
}

View file

@ -0,0 +1,52 @@
package client
import (
"io"
"net/url"
"time"
"golang.org/x/net/context"
"github.com/docker/engine-api/types"
timetypes "github.com/docker/engine-api/types/time"
)
// ContainerLogs returns the logs generated by a container in an io.ReadCloser.
// It's up to the caller to close the stream.
func (cli *Client) ContainerLogs(ctx context.Context, container string, options types.ContainerLogsOptions) (io.ReadCloser, error) {
query := url.Values{}
if options.ShowStdout {
query.Set("stdout", "1")
}
if options.ShowStderr {
query.Set("stderr", "1")
}
if options.Since != "" {
ts, err := timetypes.GetTimestamp(options.Since, time.Now())
if err != nil {
return nil, err
}
query.Set("since", ts)
}
if options.Timestamps {
query.Set("timestamps", "1")
}
if options.Details {
query.Set("details", "1")
}
if options.Follow {
query.Set("follow", "1")
}
query.Set("tail", options.Tail)
resp, err := cli.get(ctx, "/containers/"+container+"/logs", query, nil)
if err != nil {
return nil, err
}
return resp.body, nil
}

View file

@ -0,0 +1,133 @@
package client
import (
"bytes"
"fmt"
"io"
"io/ioutil"
"log"
"net/http"
"os"
"strings"
"testing"
"time"
"github.com/docker/engine-api/types"
"golang.org/x/net/context"
)
func TestContainerLogsError(t *testing.T) {
client := &Client{
transport: newMockClient(nil, errorMock(http.StatusInternalServerError, "Server error")),
}
_, err := client.ContainerLogs(context.Background(), "container_id", types.ContainerLogsOptions{})
if err == nil || err.Error() != "Error response from daemon: Server error" {
t.Fatalf("expected a Server Error, got %v", err)
}
_, err = client.ContainerLogs(context.Background(), "container_id", types.ContainerLogsOptions{
Since: "2006-01-02TZ",
})
if err == nil || !strings.Contains(err.Error(), `parsing time "2006-01-02TZ"`) {
t.Fatalf("expected a 'parsing time' error, got %v", err)
}
}
func TestContainerLogs(t *testing.T) {
expectedURL := "/containers/container_id/logs"
cases := []struct {
options types.ContainerLogsOptions
expectedQueryParams map[string]string
}{
{
expectedQueryParams: map[string]string{
"tail": "",
},
},
{
options: types.ContainerLogsOptions{
Tail: "any",
},
expectedQueryParams: map[string]string{
"tail": "any",
},
},
{
options: types.ContainerLogsOptions{
ShowStdout: true,
ShowStderr: true,
Timestamps: true,
Details: true,
Follow: true,
},
expectedQueryParams: map[string]string{
"tail": "",
"stdout": "1",
"stderr": "1",
"timestamps": "1",
"details": "1",
"follow": "1",
},
},
{
options: types.ContainerLogsOptions{
// An complete invalid date, timestamp or go duration will be
// passed as is
Since: "invalid but valid",
},
expectedQueryParams: map[string]string{
"tail": "",
"since": "invalid but valid",
},
},
}
for _, logCase := range cases {
client := &Client{
transport: newMockClient(nil, func(r *http.Request) (*http.Response, error) {
if !strings.HasPrefix(r.URL.Path, expectedURL) {
return nil, fmt.Errorf("Expected URL '%s', got '%s'", expectedURL, r.URL)
}
// Check query parameters
query := r.URL.Query()
for key, expected := range logCase.expectedQueryParams {
actual := query.Get(key)
if actual != expected {
return nil, fmt.Errorf("%s not set in URL query properly. Expected '%s', got %s", key, expected, actual)
}
}
return &http.Response{
StatusCode: http.StatusOK,
Body: ioutil.NopCloser(bytes.NewReader([]byte("response"))),
}, nil
}),
}
body, err := client.ContainerLogs(context.Background(), "container_id", logCase.options)
if err != nil {
t.Fatal(err)
}
defer body.Close()
content, err := ioutil.ReadAll(body)
if err != nil {
t.Fatal(err)
}
if string(content) != "response" {
t.Fatalf("expected response to contain 'response', got %s", string(content))
}
}
}
func ExampleClient_ContainerLogs_withTimeout() {
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
client, _ := NewEnvClient()
reader, err := client.ContainerLogs(ctx, "container_id", types.ContainerLogsOptions{})
if err != nil {
log.Fatal(err)
}
_, err = io.Copy(os.Stdout, reader)
if err != nil && err != io.EOF {
log.Fatal(err)
}
}

View file

@ -0,0 +1,10 @@
package client
import "golang.org/x/net/context"
// ContainerPause pauses the main process of a given container without terminating it.
func (cli *Client) ContainerPause(ctx context.Context, containerID string) error {
resp, err := cli.post(ctx, "/containers/"+containerID+"/pause", nil, nil, nil)
ensureReaderClosed(resp)
return err
}

View file

@ -0,0 +1,41 @@
package client
import (
"bytes"
"fmt"
"io/ioutil"
"net/http"
"strings"
"testing"
"golang.org/x/net/context"
)
func TestContainerPauseError(t *testing.T) {
client := &Client{
transport: newMockClient(nil, errorMock(http.StatusInternalServerError, "Server error")),
}
err := client.ContainerPause(context.Background(), "nothing")
if err == nil || err.Error() != "Error response from daemon: Server error" {
t.Fatalf("expected a Server Error, got %v", err)
}
}
func TestContainerPause(t *testing.T) {
expectedURL := "/containers/container_id/pause"
client := &Client{
transport: newMockClient(nil, func(req *http.Request) (*http.Response, error) {
if !strings.HasPrefix(req.URL.Path, expectedURL) {
return nil, fmt.Errorf("Expected URL '%s', got '%s'", expectedURL, req.URL)
}
return &http.Response{
StatusCode: http.StatusOK,
Body: ioutil.NopCloser(bytes.NewReader([]byte(""))),
}, nil
}),
}
err := client.ContainerPause(context.Background(), "container_id")
if err != nil {
t.Fatal(err)
}
}

View file

@ -0,0 +1,27 @@
package client
import (
"net/url"
"github.com/docker/engine-api/types"
"golang.org/x/net/context"
)
// ContainerRemove kills and removes a container from the docker host.
func (cli *Client) ContainerRemove(ctx context.Context, containerID string, options types.ContainerRemoveOptions) error {
query := url.Values{}
if options.RemoveVolumes {
query.Set("v", "1")
}
if options.RemoveLinks {
query.Set("link", "1")
}
if options.Force {
query.Set("force", "1")
}
resp, err := cli.delete(ctx, "/containers/"+containerID, query, nil)
ensureReaderClosed(resp)
return err
}

View file

@ -0,0 +1,59 @@
package client
import (
"bytes"
"fmt"
"io/ioutil"
"net/http"
"strings"
"testing"
"github.com/docker/engine-api/types"
"golang.org/x/net/context"
)
func TestContainerRemoveError(t *testing.T) {
client := &Client{
transport: newMockClient(nil, errorMock(http.StatusInternalServerError, "Server error")),
}
err := client.ContainerRemove(context.Background(), "container_id", types.ContainerRemoveOptions{})
if err == nil || err.Error() != "Error response from daemon: Server error" {
t.Fatalf("expected a Server Error, got %v", err)
}
}
func TestContainerRemove(t *testing.T) {
expectedURL := "/containers/container_id"
client := &Client{
transport: newMockClient(nil, func(req *http.Request) (*http.Response, error) {
if !strings.HasPrefix(req.URL.Path, expectedURL) {
return nil, fmt.Errorf("Expected URL '%s', got '%s'", expectedURL, req.URL)
}
query := req.URL.Query()
volume := query.Get("v")
if volume != "1" {
return nil, fmt.Errorf("v (volume) not set in URL query properly. Expected '1', got %s", volume)
}
force := query.Get("force")
if force != "1" {
return nil, fmt.Errorf("force not set in URL query properly. Expected '1', got %s", force)
}
link := query.Get("link")
if link != "" {
return nil, fmt.Errorf("link should have not be present in query, go %s", link)
}
return &http.Response{
StatusCode: http.StatusOK,
Body: ioutil.NopCloser(bytes.NewReader([]byte(""))),
}, nil
}),
}
err := client.ContainerRemove(context.Background(), "container_id", types.ContainerRemoveOptions{
RemoveVolumes: true,
Force: true,
})
if err != nil {
t.Fatal(err)
}
}

View file

@ -0,0 +1,16 @@
package client
import (
"net/url"
"golang.org/x/net/context"
)
// ContainerRename changes the name of a given container.
func (cli *Client) ContainerRename(ctx context.Context, containerID, newContainerName string) error {
query := url.Values{}
query.Set("name", newContainerName)
resp, err := cli.post(ctx, "/containers/"+containerID+"/rename", query, nil, nil)
ensureReaderClosed(resp)
return err
}

View file

@ -0,0 +1,46 @@
package client
import (
"bytes"
"fmt"
"io/ioutil"
"net/http"
"strings"
"testing"
"golang.org/x/net/context"
)
func TestContainerRenameError(t *testing.T) {
client := &Client{
transport: newMockClient(nil, errorMock(http.StatusInternalServerError, "Server error")),
}
err := client.ContainerRename(context.Background(), "nothing", "newNothing")
if err == nil || err.Error() != "Error response from daemon: Server error" {
t.Fatalf("expected a Server Error, got %v", err)
}
}
func TestContainerRename(t *testing.T) {
expectedURL := "/containers/container_id/rename"
client := &Client{
transport: newMockClient(nil, func(req *http.Request) (*http.Response, error) {
if !strings.HasPrefix(req.URL.Path, expectedURL) {
return nil, fmt.Errorf("Expected URL '%s', got '%s'", expectedURL, req.URL)
}
name := req.URL.Query().Get("name")
if name != "newName" {
return nil, fmt.Errorf("name not set in URL query properly. Expected 'newName', got %s", name)
}
return &http.Response{
StatusCode: http.StatusOK,
Body: ioutil.NopCloser(bytes.NewReader([]byte(""))),
}, nil
}),
}
err := client.ContainerRename(context.Background(), "container_id", "newName")
if err != nil {
t.Fatal(err)
}
}

View file

@ -0,0 +1,29 @@
package client
import (
"net/url"
"strconv"
"github.com/docker/engine-api/types"
"golang.org/x/net/context"
)
// ContainerResize changes the size of the tty for a container.
func (cli *Client) ContainerResize(ctx context.Context, containerID string, options types.ResizeOptions) error {
return cli.resize(ctx, "/containers/"+containerID, options.Height, options.Width)
}
// ContainerExecResize changes the size of the tty for an exec process running inside a container.
func (cli *Client) ContainerExecResize(ctx context.Context, execID string, options types.ResizeOptions) error {
return cli.resize(ctx, "/exec/"+execID, options.Height, options.Width)
}
func (cli *Client) resize(ctx context.Context, basePath string, height, width int) error {
query := url.Values{}
query.Set("h", strconv.Itoa(height))
query.Set("w", strconv.Itoa(width))
resp, err := cli.post(ctx, basePath+"/resize", query, nil, nil)
ensureReaderClosed(resp)
return err
}

View file

@ -0,0 +1,82 @@
package client
import (
"bytes"
"fmt"
"io/ioutil"
"net/http"
"strings"
"testing"
"github.com/docker/engine-api/types"
"golang.org/x/net/context"
)
func TestContainerResizeError(t *testing.T) {
client := &Client{
transport: newMockClient(nil, errorMock(http.StatusInternalServerError, "Server error")),
}
err := client.ContainerResize(context.Background(), "container_id", types.ResizeOptions{})
if err == nil || err.Error() != "Error response from daemon: Server error" {
t.Fatalf("expected a Server Error, got %v", err)
}
}
func TestContainerExecResizeError(t *testing.T) {
client := &Client{
transport: newMockClient(nil, errorMock(http.StatusInternalServerError, "Server error")),
}
err := client.ContainerExecResize(context.Background(), "exec_id", types.ResizeOptions{})
if err == nil || err.Error() != "Error response from daemon: Server error" {
t.Fatalf("expected a Server Error, got %v", err)
}
}
func TestContainerResize(t *testing.T) {
client := &Client{
transport: newMockClient(nil, resizeTransport("/containers/container_id/resize")),
}
err := client.ContainerResize(context.Background(), "container_id", types.ResizeOptions{
Height: 500,
Width: 600,
})
if err != nil {
t.Fatal(err)
}
}
func TestContainerExecResize(t *testing.T) {
client := &Client{
transport: newMockClient(nil, resizeTransport("/exec/exec_id/resize")),
}
err := client.ContainerExecResize(context.Background(), "exec_id", types.ResizeOptions{
Height: 500,
Width: 600,
})
if err != nil {
t.Fatal(err)
}
}
func resizeTransport(expectedURL string) func(req *http.Request) (*http.Response, error) {
return func(req *http.Request) (*http.Response, error) {
if !strings.HasPrefix(req.URL.Path, expectedURL) {
return nil, fmt.Errorf("Expected URL '%s', got '%s'", expectedURL, req.URL)
}
query := req.URL.Query()
h := query.Get("h")
if h != "500" {
return nil, fmt.Errorf("h not set in URL query properly. Expected '500', got %s", h)
}
w := query.Get("w")
if w != "600" {
return nil, fmt.Errorf("w not set in URL query properly. Expected '600', got %s", w)
}
return &http.Response{
StatusCode: http.StatusOK,
Body: ioutil.NopCloser(bytes.NewReader([]byte(""))),
}, nil
}
}

View file

@ -0,0 +1,22 @@
package client
import (
"net/url"
"time"
timetypes "github.com/docker/engine-api/types/time"
"golang.org/x/net/context"
)
// ContainerRestart stops and starts a container again.
// It makes the daemon to wait for the container to be up again for
// a specific amount of time, given the timeout.
func (cli *Client) ContainerRestart(ctx context.Context, containerID string, timeout *time.Duration) error {
query := url.Values{}
if timeout != nil {
query.Set("t", timetypes.DurationToSecondsString(*timeout))
}
resp, err := cli.post(ctx, "/containers/"+containerID+"/restart", query, nil, nil)
ensureReaderClosed(resp)
return err
}

View file

@ -0,0 +1,48 @@
package client
import (
"bytes"
"fmt"
"io/ioutil"
"net/http"
"strings"
"testing"
"time"
"golang.org/x/net/context"
)
func TestContainerRestartError(t *testing.T) {
client := &Client{
transport: newMockClient(nil, errorMock(http.StatusInternalServerError, "Server error")),
}
timeout := 0*time.Second
err := client.ContainerRestart(context.Background(), "nothing", &timeout)
if err == nil || err.Error() != "Error response from daemon: Server error" {
t.Fatalf("expected a Server Error, got %v", err)
}
}
func TestContainerRestart(t *testing.T) {
expectedURL := "/containers/container_id/restart"
client := &Client{
transport: newMockClient(nil, func(req *http.Request) (*http.Response, error) {
if !strings.HasPrefix(req.URL.Path, expectedURL) {
return nil, fmt.Errorf("Expected URL '%s', got '%s'", expectedURL, req.URL)
}
t := req.URL.Query().Get("t")
if t != "100" {
return nil, fmt.Errorf("t (timeout) not set in URL query properly. Expected '100', got %s", t)
}
return &http.Response{
StatusCode: http.StatusOK,
Body: ioutil.NopCloser(bytes.NewReader([]byte(""))),
}, nil
}),
}
timeout := 100*time.Second
err := client.ContainerRestart(context.Background(), "container_id", &timeout)
if err != nil {
t.Fatal(err)
}
}

View file

@ -0,0 +1,21 @@
package client
import (
"net/url"
"golang.org/x/net/context"
"github.com/docker/engine-api/types"
)
// ContainerStart sends a request to the docker daemon to start a container.
func (cli *Client) ContainerStart(ctx context.Context, containerID string, options types.ContainerStartOptions) error {
query := url.Values{}
if len(options.CheckpointID) != 0 {
query.Set("checkpoint", options.CheckpointID)
}
resp, err := cli.post(ctx, "/containers/"+containerID+"/start", query, nil, nil)
ensureReaderClosed(resp)
return err
}

View file

@ -0,0 +1,58 @@
package client
import (
"bytes"
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"strings"
"testing"
"golang.org/x/net/context"
"github.com/docker/engine-api/types"
)
func TestContainerStartError(t *testing.T) {
client := &Client{
transport: newMockClient(nil, errorMock(http.StatusInternalServerError, "Server error")),
}
err := client.ContainerStart(context.Background(), "nothing", types.ContainerStartOptions{})
if err == nil || err.Error() != "Error response from daemon: Server error" {
t.Fatalf("expected a Server Error, got %v", err)
}
}
func TestContainerStart(t *testing.T) {
expectedURL := "/containers/container_id/start"
client := &Client{
transport: newMockClient(nil, func(req *http.Request) (*http.Response, error) {
if !strings.HasPrefix(req.URL.Path, expectedURL) {
return nil, fmt.Errorf("Expected URL '%s', got '%s'", expectedURL, req.URL)
}
// we're not expecting any payload, but if one is supplied, check it is valid.
if req.Header.Get("Content-Type") == "application/json" {
var startConfig interface{}
if err := json.NewDecoder(req.Body).Decode(&startConfig); err != nil {
return nil, fmt.Errorf("Unable to parse json: %s", err)
}
}
checkpoint := req.URL.Query().Get("checkpoint")
if checkpoint != "checkpoint_id" {
return nil, fmt.Errorf("checkpoint not set in URL query properly. Expected 'checkpoint_id', got %s", checkpoint)
}
return &http.Response{
StatusCode: http.StatusOK,
Body: ioutil.NopCloser(bytes.NewReader([]byte(""))),
}, nil
}),
}
err := client.ContainerStart(context.Background(), "container_id", types.ContainerStartOptions{CheckpointID: "checkpoint_id"})
if err != nil {
t.Fatal(err)
}
}

View file

@ -0,0 +1,24 @@
package client
import (
"io"
"net/url"
"golang.org/x/net/context"
)
// ContainerStats returns near realtime stats for a given container.
// It's up to the caller to close the io.ReadCloser returned.
func (cli *Client) ContainerStats(ctx context.Context, containerID string, stream bool) (io.ReadCloser, error) {
query := url.Values{}
query.Set("stream", "0")
if stream {
query.Set("stream", "1")
}
resp, err := cli.get(ctx, "/containers/"+containerID+"/stats", query, nil)
if err != nil {
return nil, err
}
return resp.body, err
}

View file

@ -0,0 +1,70 @@
package client
import (
"bytes"
"fmt"
"io/ioutil"
"net/http"
"strings"
"testing"
"golang.org/x/net/context"
)
func TestContainerStatsError(t *testing.T) {
client := &Client{
transport: newMockClient(nil, errorMock(http.StatusInternalServerError, "Server error")),
}
_, err := client.ContainerStats(context.Background(), "nothing", false)
if err == nil || err.Error() != "Error response from daemon: Server error" {
t.Fatalf("expected a Server Error, got %v", err)
}
}
func TestContainerStats(t *testing.T) {
expectedURL := "/containers/container_id/stats"
cases := []struct {
stream bool
expectedStream string
}{
{
expectedStream: "0",
},
{
stream: true,
expectedStream: "1",
},
}
for _, c := range cases {
client := &Client{
transport: newMockClient(nil, func(r *http.Request) (*http.Response, error) {
if !strings.HasPrefix(r.URL.Path, expectedURL) {
return nil, fmt.Errorf("Expected URL '%s', got '%s'", expectedURL, r.URL)
}
query := r.URL.Query()
stream := query.Get("stream")
if stream != c.expectedStream {
return nil, fmt.Errorf("stream not set in URL query properly. Expected '%s', got %s", c.expectedStream, stream)
}
return &http.Response{
StatusCode: http.StatusOK,
Body: ioutil.NopCloser(bytes.NewReader([]byte("response"))),
}, nil
}),
}
body, err := client.ContainerStats(context.Background(), "container_id", c.stream)
if err != nil {
t.Fatal(err)
}
defer body.Close()
content, err := ioutil.ReadAll(body)
if err != nil {
t.Fatal(err)
}
if string(content) != "response" {
t.Fatalf("expected response to contain 'response', got %s", string(content))
}
}
}

View file

@ -0,0 +1,21 @@
package client
import (
"net/url"
"time"
timetypes "github.com/docker/engine-api/types/time"
"golang.org/x/net/context"
)
// ContainerStop stops a container without terminating the process.
// The process is blocked until the container stops or the timeout expires.
func (cli *Client) ContainerStop(ctx context.Context, containerID string, timeout *time.Duration) error {
query := url.Values{}
if timeout != nil {
query.Set("t", timetypes.DurationToSecondsString(*timeout))
}
resp, err := cli.post(ctx, "/containers/"+containerID+"/stop", query, nil, nil)
ensureReaderClosed(resp)
return err
}

View file

@ -0,0 +1,48 @@
package client
import (
"bytes"
"fmt"
"io/ioutil"
"net/http"
"strings"
"testing"
"time"
"golang.org/x/net/context"
)
func TestContainerStopError(t *testing.T) {
client := &Client{
transport: newMockClient(nil, errorMock(http.StatusInternalServerError, "Server error")),
}
timeout := 0*time.Second
err := client.ContainerStop(context.Background(), "nothing", &timeout)
if err == nil || err.Error() != "Error response from daemon: Server error" {
t.Fatalf("expected a Server Error, got %v", err)
}
}
func TestContainerStop(t *testing.T) {
expectedURL := "/containers/container_id/stop"
client := &Client{
transport: newMockClient(nil, func(req *http.Request) (*http.Response, error) {
if !strings.HasPrefix(req.URL.Path, expectedURL) {
return nil, fmt.Errorf("Expected URL '%s', got '%s'", expectedURL, req.URL)
}
t := req.URL.Query().Get("t")
if t != "100" {
return nil, fmt.Errorf("t (timeout) not set in URL query properly. Expected '100', got %s", t)
}
return &http.Response{
StatusCode: http.StatusOK,
Body: ioutil.NopCloser(bytes.NewReader([]byte(""))),
}, nil
}),
}
timeout := 100*time.Second
err := client.ContainerStop(context.Background(), "container_id", &timeout)
if err != nil {
t.Fatal(err)
}
}

View file

@ -0,0 +1,28 @@
package client
import (
"encoding/json"
"net/url"
"strings"
"github.com/docker/engine-api/types"
"golang.org/x/net/context"
)
// ContainerTop shows process information from within a container.
func (cli *Client) ContainerTop(ctx context.Context, containerID string, arguments []string) (types.ContainerProcessList, error) {
var response types.ContainerProcessList
query := url.Values{}
if len(arguments) > 0 {
query.Set("ps_args", strings.Join(arguments, " "))
}
resp, err := cli.get(ctx, "/containers/"+containerID+"/top", query, nil)
if err != nil {
return response, err
}
err = json.NewDecoder(resp.body).Decode(&response)
ensureReaderClosed(resp)
return response, err
}

View file

@ -0,0 +1,74 @@
package client
import (
"bytes"
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"reflect"
"strings"
"testing"
"github.com/docker/engine-api/types"
"golang.org/x/net/context"
)
func TestContainerTopError(t *testing.T) {
client := &Client{
transport: newMockClient(nil, errorMock(http.StatusInternalServerError, "Server error")),
}
_, err := client.ContainerTop(context.Background(), "nothing", []string{})
if err == nil || err.Error() != "Error response from daemon: Server error" {
t.Fatalf("expected a Server Error, got %v", err)
}
}
func TestContainerTop(t *testing.T) {
expectedURL := "/containers/container_id/top"
expectedProcesses := [][]string{
{"p1", "p2"},
{"p3"},
}
expectedTitles := []string{"title1", "title2"}
client := &Client{
transport: newMockClient(nil, func(req *http.Request) (*http.Response, error) {
if !strings.HasPrefix(req.URL.Path, expectedURL) {
return nil, fmt.Errorf("Expected URL '%s', got '%s'", expectedURL, req.URL)
}
query := req.URL.Query()
args := query.Get("ps_args")
if args != "arg1 arg2" {
return nil, fmt.Errorf("args not set in URL query properly. Expected 'arg1 arg2', got %v", args)
}
b, err := json.Marshal(types.ContainerProcessList{
Processes: [][]string{
{"p1", "p2"},
{"p3"},
},
Titles: []string{"title1", "title2"},
})
if err != nil {
return nil, err
}
return &http.Response{
StatusCode: http.StatusOK,
Body: ioutil.NopCloser(bytes.NewReader(b)),
}, nil
}),
}
processList, err := client.ContainerTop(context.Background(), "container_id", []string{"arg1", "arg2"})
if err != nil {
t.Fatal(err)
}
if !reflect.DeepEqual(expectedProcesses, processList.Processes) {
t.Fatalf("Processes: expected %v, got %v", expectedProcesses, processList.Processes)
}
if !reflect.DeepEqual(expectedTitles, processList.Titles) {
t.Fatalf("Titles: expected %v, got %v", expectedTitles, processList.Titles)
}
}

View file

@ -0,0 +1,10 @@
package client
import "golang.org/x/net/context"
// ContainerUnpause resumes the process execution within a container
func (cli *Client) ContainerUnpause(ctx context.Context, containerID string) error {
resp, err := cli.post(ctx, "/containers/"+containerID+"/unpause", nil, nil, nil)
ensureReaderClosed(resp)
return err
}

View file

@ -0,0 +1,41 @@
package client
import (
"bytes"
"fmt"
"io/ioutil"
"net/http"
"strings"
"testing"
"golang.org/x/net/context"
)
func TestContainerUnpauseError(t *testing.T) {
client := &Client{
transport: newMockClient(nil, errorMock(http.StatusInternalServerError, "Server error")),
}
err := client.ContainerUnpause(context.Background(), "nothing")
if err == nil || err.Error() != "Error response from daemon: Server error" {
t.Fatalf("expected a Server Error, got %v", err)
}
}
func TestContainerUnpause(t *testing.T) {
expectedURL := "/containers/container_id/unpause"
client := &Client{
transport: newMockClient(nil, func(req *http.Request) (*http.Response, error) {
if !strings.HasPrefix(req.URL.Path, expectedURL) {
return nil, fmt.Errorf("Expected URL '%s', got '%s'", expectedURL, req.URL)
}
return &http.Response{
StatusCode: http.StatusOK,
Body: ioutil.NopCloser(bytes.NewReader([]byte(""))),
}, nil
}),
}
err := client.ContainerUnpause(context.Background(), "container_id")
if err != nil {
t.Fatal(err)
}
}

View file

@ -0,0 +1,23 @@
package client
import (
"encoding/json"
"github.com/docker/engine-api/types"
"github.com/docker/engine-api/types/container"
"golang.org/x/net/context"
)
// ContainerUpdate updates resources of a container
func (cli *Client) ContainerUpdate(ctx context.Context, containerID string, updateConfig container.UpdateConfig) (types.ContainerUpdateResponse, error) {
var response types.ContainerUpdateResponse
serverResp, err := cli.post(ctx, "/containers/"+containerID+"/update", nil, updateConfig, nil)
if err != nil {
return response, err
}
err = json.NewDecoder(serverResp.body).Decode(&response)
ensureReaderClosed(serverResp)
return response, err
}

View file

@ -0,0 +1,59 @@
package client
import (
"bytes"
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"strings"
"testing"
"github.com/docker/engine-api/types"
"github.com/docker/engine-api/types/container"
"golang.org/x/net/context"
)
func TestContainerUpdateError(t *testing.T) {
client := &Client{
transport: newMockClient(nil, errorMock(http.StatusInternalServerError, "Server error")),
}
_, err := client.ContainerUpdate(context.Background(), "nothing", container.UpdateConfig{})
if err == nil || err.Error() != "Error response from daemon: Server error" {
t.Fatalf("expected a Server Error, got %v", err)
}
}
func TestContainerUpdate(t *testing.T) {
expectedURL := "/containers/container_id/update"
client := &Client{
transport: newMockClient(nil, func(req *http.Request) (*http.Response, error) {
if !strings.HasPrefix(req.URL.Path, expectedURL) {
return nil, fmt.Errorf("Expected URL '%s', got '%s'", expectedURL, req.URL)
}
b, err := json.Marshal(types.ContainerUpdateResponse{})
if err != nil {
return nil, err
}
return &http.Response{
StatusCode: http.StatusOK,
Body: ioutil.NopCloser(bytes.NewReader(b)),
}, nil
}),
}
_, err := client.ContainerUpdate(context.Background(), "container_id", container.UpdateConfig{
Resources: container.Resources{
CPUPeriod: 1,
},
RestartPolicy: container.RestartPolicy{
Name: "always",
},
})
if err != nil {
t.Fatal(err)
}
}

View file

@ -0,0 +1,26 @@
package client
import (
"encoding/json"
"golang.org/x/net/context"
"github.com/docker/engine-api/types"
)
// ContainerWait pauses execution until a container exits.
// It returns the API status code as response of its readiness.
func (cli *Client) ContainerWait(ctx context.Context, containerID string) (int, error) {
resp, err := cli.post(ctx, "/containers/"+containerID+"/wait", nil, nil, nil)
if err != nil {
return -1, err
}
defer ensureReaderClosed(resp)
var res types.ContainerWaitResponse
if err := json.NewDecoder(resp.body).Decode(&res); err != nil {
return -1, err
}
return res.StatusCode, nil
}

View file

@ -0,0 +1,70 @@
package client
import (
"bytes"
"encoding/json"
"fmt"
"io/ioutil"
"log"
"net/http"
"strings"
"testing"
"time"
"github.com/docker/engine-api/types"
"golang.org/x/net/context"
)
func TestContainerWaitError(t *testing.T) {
client := &Client{
transport: newMockClient(nil, errorMock(http.StatusInternalServerError, "Server error")),
}
code, err := client.ContainerWait(context.Background(), "nothing")
if err == nil || err.Error() != "Error response from daemon: Server error" {
t.Fatalf("expected a Server Error, got %v", err)
}
if code != -1 {
t.Fatalf("expected a status code equal to '-1', got %d", code)
}
}
func TestContainerWait(t *testing.T) {
expectedURL := "/containers/container_id/wait"
client := &Client{
transport: newMockClient(nil, func(req *http.Request) (*http.Response, error) {
if !strings.HasPrefix(req.URL.Path, expectedURL) {
return nil, fmt.Errorf("Expected URL '%s', got '%s'", expectedURL, req.URL)
}
b, err := json.Marshal(types.ContainerWaitResponse{
StatusCode: 15,
})
if err != nil {
return nil, err
}
return &http.Response{
StatusCode: http.StatusOK,
Body: ioutil.NopCloser(bytes.NewReader(b)),
}, nil
}),
}
code, err := client.ContainerWait(context.Background(), "container_id")
if err != nil {
t.Fatal(err)
}
if code != 15 {
t.Fatalf("expected a status code equal to '15', got %d", code)
}
}
func ExampleClient_ContainerWait_withTimeout() {
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
client, _ := NewEnvClient()
_, err := client.ContainerWait(ctx, "container_id")
if err != nil {
log.Fatal(err)
}
}

208
vendor/github.com/docker/engine-api/client/errors.go generated vendored Normal file
View file

@ -0,0 +1,208 @@
package client
import (
"errors"
"fmt"
)
// ErrConnectionFailed is an error raised when the connection between the client and the server failed.
var ErrConnectionFailed = errors.New("Cannot connect to the Docker daemon. Is the docker daemon running on this host?")
// ErrorConnectionFailed returns an error with host in the error message when connection to docker daemon failed.
func ErrorConnectionFailed(host string) error {
return fmt.Errorf("Cannot connect to the Docker daemon at %s. Is the docker daemon running?", host)
}
type notFound interface {
error
NotFound() bool // Is the error a NotFound error
}
// IsErrNotFound returns true if the error is caused with an
// object (image, container, network, volume, …) is not found in the docker host.
func IsErrNotFound(err error) bool {
te, ok := err.(notFound)
return ok && te.NotFound()
}
// imageNotFoundError implements an error returned when an image is not in the docker host.
type imageNotFoundError struct {
imageID string
}
// NoFound indicates that this error type is of NotFound
func (e imageNotFoundError) NotFound() bool {
return true
}
// Error returns a string representation of an imageNotFoundError
func (e imageNotFoundError) Error() string {
return fmt.Sprintf("Error: No such image: %s", e.imageID)
}
// IsErrImageNotFound returns true if the error is caused
// when an image is not found in the docker host.
func IsErrImageNotFound(err error) bool {
return IsErrNotFound(err)
}
// containerNotFoundError implements an error returned when a container is not in the docker host.
type containerNotFoundError struct {
containerID string
}
// NoFound indicates that this error type is of NotFound
func (e containerNotFoundError) NotFound() bool {
return true
}
// Error returns a string representation of a containerNotFoundError
func (e containerNotFoundError) Error() string {
return fmt.Sprintf("Error: No such container: %s", e.containerID)
}
// IsErrContainerNotFound returns true if the error is caused
// when a container is not found in the docker host.
func IsErrContainerNotFound(err error) bool {
return IsErrNotFound(err)
}
// networkNotFoundError implements an error returned when a network is not in the docker host.
type networkNotFoundError struct {
networkID string
}
// NoFound indicates that this error type is of NotFound
func (e networkNotFoundError) NotFound() bool {
return true
}
// Error returns a string representation of a networkNotFoundError
func (e networkNotFoundError) Error() string {
return fmt.Sprintf("Error: No such network: %s", e.networkID)
}
// IsErrNetworkNotFound returns true if the error is caused
// when a network is not found in the docker host.
func IsErrNetworkNotFound(err error) bool {
return IsErrNotFound(err)
}
// volumeNotFoundError implements an error returned when a volume is not in the docker host.
type volumeNotFoundError struct {
volumeID string
}
// NoFound indicates that this error type is of NotFound
func (e volumeNotFoundError) NotFound() bool {
return true
}
// Error returns a string representation of a networkNotFoundError
func (e volumeNotFoundError) Error() string {
return fmt.Sprintf("Error: No such volume: %s", e.volumeID)
}
// IsErrVolumeNotFound returns true if the error is caused
// when a volume is not found in the docker host.
func IsErrVolumeNotFound(err error) bool {
return IsErrNotFound(err)
}
// unauthorizedError represents an authorization error in a remote registry.
type unauthorizedError struct {
cause error
}
// Error returns a string representation of an unauthorizedError
func (u unauthorizedError) Error() string {
return u.cause.Error()
}
// IsErrUnauthorized returns true if the error is caused
// when a remote registry authentication fails
func IsErrUnauthorized(err error) bool {
_, ok := err.(unauthorizedError)
return ok
}
// nodeNotFoundError implements an error returned when a node is not found.
type nodeNotFoundError struct {
nodeID string
}
// Error returns a string representation of a nodeNotFoundError
func (e nodeNotFoundError) Error() string {
return fmt.Sprintf("Error: No such node: %s", e.nodeID)
}
// NoFound indicates that this error type is of NotFound
func (e nodeNotFoundError) NotFound() bool {
return true
}
// IsErrNodeNotFound returns true if the error is caused
// when a node is not found.
func IsErrNodeNotFound(err error) bool {
_, ok := err.(nodeNotFoundError)
return ok
}
// serviceNotFoundError implements an error returned when a service is not found.
type serviceNotFoundError struct {
serviceID string
}
// Error returns a string representation of a serviceNotFoundError
func (e serviceNotFoundError) Error() string {
return fmt.Sprintf("Error: No such service: %s", e.serviceID)
}
// NoFound indicates that this error type is of NotFound
func (e serviceNotFoundError) NotFound() bool {
return true
}
// IsErrServiceNotFound returns true if the error is caused
// when a service is not found.
func IsErrServiceNotFound(err error) bool {
_, ok := err.(serviceNotFoundError)
return ok
}
// taskNotFoundError implements an error returned when a task is not found.
type taskNotFoundError struct {
taskID string
}
// Error returns a string representation of a taskNotFoundError
func (e taskNotFoundError) Error() string {
return fmt.Sprintf("Error: No such task: %s", e.taskID)
}
// NoFound indicates that this error type is of NotFound
func (e taskNotFoundError) NotFound() bool {
return true
}
// IsErrTaskNotFound returns true if the error is caused
// when a task is not found.
func IsErrTaskNotFound(err error) bool {
_, ok := err.(taskNotFoundError)
return ok
}
type pluginPermissionDenied struct {
name string
}
func (e pluginPermissionDenied) Error() string {
return "Permission denied while installing plugin " + e.name
}
// IsErrPluginPermissionDenied returns true if the error is caused
// when a user denies a plugin's permissions
func IsErrPluginPermissionDenied(err error) bool {
_, ok := err.(pluginPermissionDenied)
return ok
}

48
vendor/github.com/docker/engine-api/client/events.go generated vendored Normal file
View file

@ -0,0 +1,48 @@
package client
import (
"io"
"net/url"
"time"
"golang.org/x/net/context"
"github.com/docker/engine-api/types"
"github.com/docker/engine-api/types/filters"
timetypes "github.com/docker/engine-api/types/time"
)
// Events returns a stream of events in the daemon in a ReadCloser.
// It's up to the caller to close the stream.
func (cli *Client) Events(ctx context.Context, options types.EventsOptions) (io.ReadCloser, error) {
query := url.Values{}
ref := time.Now()
if options.Since != "" {
ts, err := timetypes.GetTimestamp(options.Since, ref)
if err != nil {
return nil, err
}
query.Set("since", ts)
}
if options.Until != "" {
ts, err := timetypes.GetTimestamp(options.Until, ref)
if err != nil {
return nil, err
}
query.Set("until", ts)
}
if options.Filters.Len() > 0 {
filterJSON, err := filters.ToParamWithVersion(cli.version, options.Filters)
if err != nil {
return nil, err
}
query.Set("filters", filterJSON)
}
serverResponse, err := cli.get(ctx, "/events", query, nil)
if err != nil {
return nil, err
}
return serverResponse.body, nil
}

View file

@ -0,0 +1,126 @@
package client
import (
"bytes"
"fmt"
"io/ioutil"
"net/http"
"strings"
"testing"
"golang.org/x/net/context"
"github.com/docker/engine-api/types"
"github.com/docker/engine-api/types/filters"
)
func TestEventsErrorInOptions(t *testing.T) {
errorCases := []struct {
options types.EventsOptions
expectedError string
}{
{
options: types.EventsOptions{
Since: "2006-01-02TZ",
},
expectedError: `parsing time "2006-01-02TZ"`,
},
{
options: types.EventsOptions{
Until: "2006-01-02TZ",
},
expectedError: `parsing time "2006-01-02TZ"`,
},
}
for _, e := range errorCases {
client := &Client{
transport: newMockClient(nil, errorMock(http.StatusInternalServerError, "Server error")),
}
_, err := client.Events(context.Background(), e.options)
if err == nil || !strings.Contains(err.Error(), e.expectedError) {
t.Fatalf("expected a error %q, got %v", e.expectedError, err)
}
}
}
func TestEventsErrorFromServer(t *testing.T) {
client := &Client{
transport: newMockClient(nil, errorMock(http.StatusInternalServerError, "Server error")),
}
_, err := client.Events(context.Background(), types.EventsOptions{})
if err == nil || err.Error() != "Error response from daemon: Server error" {
t.Fatalf("expected a Server Error, got %v", err)
}
}
func TestEvents(t *testing.T) {
expectedURL := "/events"
filters := filters.NewArgs()
filters.Add("label", "label1")
filters.Add("label", "label2")
expectedFiltersJSON := `{"label":{"label1":true,"label2":true}}`
eventsCases := []struct {
options types.EventsOptions
expectedQueryParams map[string]string
}{
{
options: types.EventsOptions{
Since: "invalid but valid",
},
expectedQueryParams: map[string]string{
"since": "invalid but valid",
},
},
{
options: types.EventsOptions{
Until: "invalid but valid",
},
expectedQueryParams: map[string]string{
"until": "invalid but valid",
},
},
{
options: types.EventsOptions{
Filters: filters,
},
expectedQueryParams: map[string]string{
"filters": expectedFiltersJSON,
},
},
}
for _, eventsCase := range eventsCases {
client := &Client{
transport: newMockClient(nil, func(req *http.Request) (*http.Response, error) {
if !strings.HasPrefix(req.URL.Path, expectedURL) {
return nil, fmt.Errorf("Expected URL '%s', got '%s'", expectedURL, req.URL)
}
query := req.URL.Query()
for key, expected := range eventsCase.expectedQueryParams {
actual := query.Get(key)
if actual != expected {
return nil, fmt.Errorf("%s not set in URL query properly. Expected '%s', got %s", key, expected, actual)
}
}
return &http.Response{
StatusCode: http.StatusOK,
Body: ioutil.NopCloser(bytes.NewReader([]byte("response"))),
}, nil
}),
}
body, err := client.Events(context.Background(), eventsCase.options)
if err != nil {
t.Fatal(err)
}
defer body.Close()
content, err := ioutil.ReadAll(body)
if err != nil {
t.Fatal(err)
}
if string(content) != "response" {
t.Fatalf("expected response to contain 'response', got %s", string(content))
}
}
}

174
vendor/github.com/docker/engine-api/client/hijack.go generated vendored Normal file
View file

@ -0,0 +1,174 @@
package client
import (
"crypto/tls"
"errors"
"fmt"
"net"
"net/http/httputil"
"net/url"
"strings"
"time"
"github.com/docker/engine-api/types"
"github.com/docker/go-connections/sockets"
"golang.org/x/net/context"
)
// tlsClientCon holds tls information and a dialed connection.
type tlsClientCon struct {
*tls.Conn
rawConn net.Conn
}
func (c *tlsClientCon) CloseWrite() error {
// Go standard tls.Conn doesn't provide the CloseWrite() method so we do it
// on its underlying connection.
if conn, ok := c.rawConn.(types.CloseWriter); ok {
return conn.CloseWrite()
}
return nil
}
// postHijacked sends a POST request and hijacks the connection.
func (cli *Client) postHijacked(ctx context.Context, path string, query url.Values, body interface{}, headers map[string][]string) (types.HijackedResponse, error) {
bodyEncoded, err := encodeData(body)
if err != nil {
return types.HijackedResponse{}, err
}
req, err := cli.newRequest("POST", path, query, bodyEncoded, headers)
if err != nil {
return types.HijackedResponse{}, err
}
req.Host = cli.addr
req.Header.Set("Connection", "Upgrade")
req.Header.Set("Upgrade", "tcp")
conn, err := dial(cli.proto, cli.addr, cli.transport.TLSConfig())
if err != nil {
if strings.Contains(err.Error(), "connection refused") {
return types.HijackedResponse{}, fmt.Errorf("Cannot connect to the Docker daemon. Is 'docker daemon' running on this host?")
}
return types.HijackedResponse{}, err
}
// When we set up a TCP connection for hijack, there could be long periods
// of inactivity (a long running command with no output) that in certain
// network setups may cause ECONNTIMEOUT, leaving the client in an unknown
// state. Setting TCP KeepAlive on the socket connection will prohibit
// ECONNTIMEOUT unless the socket connection truly is broken
if tcpConn, ok := conn.(*net.TCPConn); ok {
tcpConn.SetKeepAlive(true)
tcpConn.SetKeepAlivePeriod(30 * time.Second)
}
clientconn := httputil.NewClientConn(conn, nil)
defer clientconn.Close()
// Server hijacks the connection, error 'connection closed' expected
_, err = clientconn.Do(req)
rwc, br := clientconn.Hijack()
return types.HijackedResponse{Conn: rwc, Reader: br}, err
}
func tlsDial(network, addr string, config *tls.Config) (net.Conn, error) {
return tlsDialWithDialer(new(net.Dialer), network, addr, config)
}
// We need to copy Go's implementation of tls.Dial (pkg/cryptor/tls/tls.go) in
// order to return our custom tlsClientCon struct which holds both the tls.Conn
// object _and_ its underlying raw connection. The rationale for this is that
// we need to be able to close the write end of the connection when attaching,
// which tls.Conn does not provide.
func tlsDialWithDialer(dialer *net.Dialer, network, addr string, config *tls.Config) (net.Conn, error) {
// We want the Timeout and Deadline values from dialer to cover the
// whole process: TCP connection and TLS handshake. This means that we
// also need to start our own timers now.
timeout := dialer.Timeout
if !dialer.Deadline.IsZero() {
deadlineTimeout := dialer.Deadline.Sub(time.Now())
if timeout == 0 || deadlineTimeout < timeout {
timeout = deadlineTimeout
}
}
var errChannel chan error
if timeout != 0 {
errChannel = make(chan error, 2)
time.AfterFunc(timeout, func() {
errChannel <- errors.New("")
})
}
proxyDialer, err := sockets.DialerFromEnvironment(dialer)
if err != nil {
return nil, err
}
rawConn, err := proxyDialer.Dial(network, addr)
if err != nil {
return nil, err
}
// When we set up a TCP connection for hijack, there could be long periods
// of inactivity (a long running command with no output) that in certain
// network setups may cause ECONNTIMEOUT, leaving the client in an unknown
// state. Setting TCP KeepAlive on the socket connection will prohibit
// ECONNTIMEOUT unless the socket connection truly is broken
if tcpConn, ok := rawConn.(*net.TCPConn); ok {
tcpConn.SetKeepAlive(true)
tcpConn.SetKeepAlivePeriod(30 * time.Second)
}
colonPos := strings.LastIndex(addr, ":")
if colonPos == -1 {
colonPos = len(addr)
}
hostname := addr[:colonPos]
// If no ServerName is set, infer the ServerName
// from the hostname we're connecting to.
if config.ServerName == "" {
// Make a copy to avoid polluting argument or default.
c := *config
c.ServerName = hostname
config = &c
}
conn := tls.Client(rawConn, config)
if timeout == 0 {
err = conn.Handshake()
} else {
go func() {
errChannel <- conn.Handshake()
}()
err = <-errChannel
}
if err != nil {
rawConn.Close()
return nil, err
}
// This is Docker difference with standard's crypto/tls package: returned a
// wrapper which holds both the TLS and raw connections.
return &tlsClientCon{conn, rawConn}, nil
}
func dial(proto, addr string, tlsConfig *tls.Config) (net.Conn, error) {
if tlsConfig != nil && proto != "unix" && proto != "npipe" {
// Notice this isn't Go standard's tls.Dial function
return tlsDial(proto, addr, tlsConfig)
}
if proto == "npipe" {
return sockets.DialPipe(addr, 32*time.Second)
}
return net.Dial(proto, addr)
}

View file

@ -0,0 +1,123 @@
package client
import (
"encoding/base64"
"encoding/json"
"io"
"net/http"
"net/url"
"regexp"
"strconv"
"golang.org/x/net/context"
"github.com/docker/engine-api/types"
"github.com/docker/engine-api/types/container"
)
var headerRegexp = regexp.MustCompile(`\ADocker/.+\s\((.+)\)\z`)
// ImageBuild sends request to the daemon to build images.
// The Body in the response implement an io.ReadCloser and it's up to the caller to
// close it.
func (cli *Client) ImageBuild(ctx context.Context, buildContext io.Reader, options types.ImageBuildOptions) (types.ImageBuildResponse, error) {
query, err := imageBuildOptionsToQuery(options)
if err != nil {
return types.ImageBuildResponse{}, err
}
headers := http.Header(make(map[string][]string))
buf, err := json.Marshal(options.AuthConfigs)
if err != nil {
return types.ImageBuildResponse{}, err
}
headers.Add("X-Registry-Config", base64.URLEncoding.EncodeToString(buf))
headers.Set("Content-Type", "application/tar")
serverResp, err := cli.postRaw(ctx, "/build", query, buildContext, headers)
if err != nil {
return types.ImageBuildResponse{}, err
}
osType := getDockerOS(serverResp.header.Get("Server"))
return types.ImageBuildResponse{
Body: serverResp.body,
OSType: osType,
}, nil
}
func imageBuildOptionsToQuery(options types.ImageBuildOptions) (url.Values, error) {
query := url.Values{
"t": options.Tags,
}
if options.SuppressOutput {
query.Set("q", "1")
}
if options.RemoteContext != "" {
query.Set("remote", options.RemoteContext)
}
if options.NoCache {
query.Set("nocache", "1")
}
if options.Remove {
query.Set("rm", "1")
} else {
query.Set("rm", "0")
}
if options.ForceRemove {
query.Set("forcerm", "1")
}
if options.PullParent {
query.Set("pull", "1")
}
if options.Squash {
query.Set("squash", "1")
}
if !container.Isolation.IsDefault(options.Isolation) {
query.Set("isolation", string(options.Isolation))
}
query.Set("cpusetcpus", options.CPUSetCPUs)
query.Set("cpusetmems", options.CPUSetMems)
query.Set("cpushares", strconv.FormatInt(options.CPUShares, 10))
query.Set("cpuquota", strconv.FormatInt(options.CPUQuota, 10))
query.Set("cpuperiod", strconv.FormatInt(options.CPUPeriod, 10))
query.Set("memory", strconv.FormatInt(options.Memory, 10))
query.Set("memswap", strconv.FormatInt(options.MemorySwap, 10))
query.Set("cgroupparent", options.CgroupParent)
query.Set("shmsize", strconv.FormatInt(options.ShmSize, 10))
query.Set("dockerfile", options.Dockerfile)
ulimitsJSON, err := json.Marshal(options.Ulimits)
if err != nil {
return query, err
}
query.Set("ulimits", string(ulimitsJSON))
buildArgsJSON, err := json.Marshal(options.BuildArgs)
if err != nil {
return query, err
}
query.Set("buildargs", string(buildArgsJSON))
labelsJSON, err := json.Marshal(options.Labels)
if err != nil {
return query, err
}
query.Set("labels", string(labelsJSON))
return query, nil
}
func getDockerOS(serverHeader string) string {
var osType string
matches := headerRegexp.FindStringSubmatch(serverHeader)
if len(matches) > 0 {
osType = matches[1]
}
return osType
}

View file

@ -0,0 +1,230 @@
package client
import (
"bytes"
"fmt"
"io/ioutil"
"net/http"
"reflect"
"strings"
"testing"
"golang.org/x/net/context"
"github.com/docker/engine-api/types"
"github.com/docker/engine-api/types/container"
"github.com/docker/go-units"
)
func TestImageBuildError(t *testing.T) {
client := &Client{
transport: newMockClient(nil, errorMock(http.StatusInternalServerError, "Server error")),
}
_, err := client.ImageBuild(context.Background(), nil, types.ImageBuildOptions{})
if err == nil || err.Error() != "Error response from daemon: Server error" {
t.Fatalf("expected a Server Error, got %v", err)
}
}
func TestImageBuild(t *testing.T) {
emptyRegistryConfig := "bnVsbA=="
buildCases := []struct {
buildOptions types.ImageBuildOptions
expectedQueryParams map[string]string
expectedTags []string
expectedRegistryConfig string
}{
{
buildOptions: types.ImageBuildOptions{
SuppressOutput: true,
NoCache: true,
Remove: true,
ForceRemove: true,
PullParent: true,
},
expectedQueryParams: map[string]string{
"q": "1",
"nocache": "1",
"rm": "1",
"forcerm": "1",
"pull": "1",
},
expectedTags: []string{},
expectedRegistryConfig: emptyRegistryConfig,
},
{
buildOptions: types.ImageBuildOptions{
SuppressOutput: false,
NoCache: false,
Remove: false,
ForceRemove: false,
PullParent: false,
},
expectedQueryParams: map[string]string{
"q": "",
"nocache": "",
"rm": "0",
"forcerm": "",
"pull": "",
},
expectedTags: []string{},
expectedRegistryConfig: emptyRegistryConfig,
},
{
buildOptions: types.ImageBuildOptions{
RemoteContext: "remoteContext",
Isolation: container.Isolation("isolation"),
CPUSetCPUs: "2",
CPUSetMems: "12",
CPUShares: 20,
CPUQuota: 10,
CPUPeriod: 30,
Memory: 256,
MemorySwap: 512,
ShmSize: 10,
CgroupParent: "cgroup_parent",
Dockerfile: "Dockerfile",
},
expectedQueryParams: map[string]string{
"remote": "remoteContext",
"isolation": "isolation",
"cpusetcpus": "2",
"cpusetmems": "12",
"cpushares": "20",
"cpuquota": "10",
"cpuperiod": "30",
"memory": "256",
"memswap": "512",
"shmsize": "10",
"cgroupparent": "cgroup_parent",
"dockerfile": "Dockerfile",
"rm": "0",
},
expectedTags: []string{},
expectedRegistryConfig: emptyRegistryConfig,
},
{
buildOptions: types.ImageBuildOptions{
BuildArgs: map[string]string{
"ARG1": "value1",
"ARG2": "value2",
},
},
expectedQueryParams: map[string]string{
"buildargs": `{"ARG1":"value1","ARG2":"value2"}`,
"rm": "0",
},
expectedTags: []string{},
expectedRegistryConfig: emptyRegistryConfig,
},
{
buildOptions: types.ImageBuildOptions{
Ulimits: []*units.Ulimit{
{
Name: "nproc",
Hard: 65557,
Soft: 65557,
},
{
Name: "nofile",
Hard: 20000,
Soft: 40000,
},
},
},
expectedQueryParams: map[string]string{
"ulimits": `[{"Name":"nproc","Hard":65557,"Soft":65557},{"Name":"nofile","Hard":20000,"Soft":40000}]`,
"rm": "0",
},
expectedTags: []string{},
expectedRegistryConfig: emptyRegistryConfig,
},
{
buildOptions: types.ImageBuildOptions{
AuthConfigs: map[string]types.AuthConfig{
"https://index.docker.io/v1/": {
Auth: "dG90bwo=",
},
},
},
expectedQueryParams: map[string]string{
"rm": "0",
},
expectedTags: []string{},
expectedRegistryConfig: "eyJodHRwczovL2luZGV4LmRvY2tlci5pby92MS8iOnsiYXV0aCI6ImRHOTBid289In19",
},
}
for _, buildCase := range buildCases {
expectedURL := "/build"
client := &Client{
transport: newMockClient(nil, func(r *http.Request) (*http.Response, error) {
if !strings.HasPrefix(r.URL.Path, expectedURL) {
return nil, fmt.Errorf("Expected URL '%s', got '%s'", expectedURL, r.URL)
}
// Check request headers
registryConfig := r.Header.Get("X-Registry-Config")
if registryConfig != buildCase.expectedRegistryConfig {
return nil, fmt.Errorf("X-Registry-Config header not properly set in the request. Expected '%s', got %s", buildCase.expectedRegistryConfig, registryConfig)
}
contentType := r.Header.Get("Content-Type")
if contentType != "application/tar" {
return nil, fmt.Errorf("Content-type header not properly set in the request. Expected 'application/tar', got %s", contentType)
}
// Check query parameters
query := r.URL.Query()
for key, expected := range buildCase.expectedQueryParams {
actual := query.Get(key)
if actual != expected {
return nil, fmt.Errorf("%s not set in URL query properly. Expected '%s', got %s", key, expected, actual)
}
}
// Check tags
if len(buildCase.expectedTags) > 0 {
tags := query["t"]
if !reflect.DeepEqual(tags, buildCase.expectedTags) {
return nil, fmt.Errorf("t (tags) not set in URL query properly. Expected '%s', got %s", buildCase.expectedTags, tags)
}
}
headers := http.Header{}
headers.Add("Server", "Docker/v1.23 (MyOS)")
return &http.Response{
StatusCode: http.StatusOK,
Body: ioutil.NopCloser(bytes.NewReader([]byte("body"))),
Header: headers,
}, nil
}),
}
buildResponse, err := client.ImageBuild(context.Background(), nil, buildCase.buildOptions)
if err != nil {
t.Fatal(err)
}
if buildResponse.OSType != "MyOS" {
t.Fatalf("expected OSType to be 'MyOS', got %s", buildResponse.OSType)
}
response, err := ioutil.ReadAll(buildResponse.Body)
if err != nil {
t.Fatal(err)
}
buildResponse.Body.Close()
if string(response) != "body" {
t.Fatalf("expected Body to contain 'body' string, got %s", response)
}
}
}
func TestGetDockerOS(t *testing.T) {
cases := map[string]string{
"Docker/v1.22 (linux)": "linux",
"Docker/v1.22 (windows)": "windows",
"Foo/v1.22 (bar)": "",
}
for header, os := range cases {
g := getDockerOS(header)
if g != os {
t.Fatalf("Expected %s, got %s", os, g)
}
}
}

View file

@ -0,0 +1,34 @@
package client
import (
"io"
"net/url"
"golang.org/x/net/context"
"github.com/docker/engine-api/types"
"github.com/docker/engine-api/types/reference"
)
// ImageCreate creates a new image based in the parent options.
// It returns the JSON content in the response body.
func (cli *Client) ImageCreate(ctx context.Context, parentReference string, options types.ImageCreateOptions) (io.ReadCloser, error) {
repository, tag, err := reference.Parse(parentReference)
if err != nil {
return nil, err
}
query := url.Values{}
query.Set("fromImage", repository)
query.Set("tag", tag)
resp, err := cli.tryImageCreate(ctx, query, options.RegistryAuth)
if err != nil {
return nil, err
}
return resp.body, nil
}
func (cli *Client) tryImageCreate(ctx context.Context, query url.Values, registryAuth string) (serverResponse, error) {
headers := map[string][]string{"X-Registry-Auth": {registryAuth}}
return cli.post(ctx, "/images/create", query, nil, headers)
}

View file

@ -0,0 +1,76 @@
package client
import (
"bytes"
"fmt"
"io/ioutil"
"net/http"
"strings"
"testing"
"golang.org/x/net/context"
"github.com/docker/engine-api/types"
)
func TestImageCreateError(t *testing.T) {
client := &Client{
transport: newMockClient(nil, errorMock(http.StatusInternalServerError, "Server error")),
}
_, err := client.ImageCreate(context.Background(), "reference", types.ImageCreateOptions{})
if err == nil || err.Error() != "Error response from daemon: Server error" {
t.Fatalf("expected a Server error, got %v", err)
}
}
func TestImageCreate(t *testing.T) {
expectedURL := "/images/create"
expectedImage := "test:5000/my_image"
expectedTag := "sha256:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"
expectedReference := fmt.Sprintf("%s@%s", expectedImage, expectedTag)
expectedRegistryAuth := "eyJodHRwczovL2luZGV4LmRvY2tlci5pby92MS8iOnsiYXV0aCI6ImRHOTBid289IiwiZW1haWwiOiJqb2huQGRvZS5jb20ifX0="
client := &Client{
transport: newMockClient(nil, func(r *http.Request) (*http.Response, error) {
if !strings.HasPrefix(r.URL.Path, expectedURL) {
return nil, fmt.Errorf("Expected URL '%s', got '%s'", expectedURL, r.URL)
}
registryAuth := r.Header.Get("X-Registry-Auth")
if registryAuth != expectedRegistryAuth {
return nil, fmt.Errorf("X-Registry-Auth header not properly set in the request. Expected '%s', got %s", expectedRegistryAuth, registryAuth)
}
query := r.URL.Query()
fromImage := query.Get("fromImage")
if fromImage != expectedImage {
return nil, fmt.Errorf("fromImage not set in URL query properly. Expected '%s', got %s", expectedImage, fromImage)
}
tag := query.Get("tag")
if tag != expectedTag {
return nil, fmt.Errorf("tag not set in URL query properly. Expected '%s', got %s", expectedTag, tag)
}
return &http.Response{
StatusCode: http.StatusOK,
Body: ioutil.NopCloser(bytes.NewReader([]byte("body"))),
}, nil
}),
}
createResponse, err := client.ImageCreate(context.Background(), expectedReference, types.ImageCreateOptions{
RegistryAuth: expectedRegistryAuth,
})
if err != nil {
t.Fatal(err)
}
response, err := ioutil.ReadAll(createResponse)
if err != nil {
t.Fatal(err)
}
if err = createResponse.Close(); err != nil {
t.Fatal(err)
}
if string(response) != "body" {
t.Fatalf("expected Body to contain 'body' string, got %s", response)
}
}

View file

@ -0,0 +1,22 @@
package client
import (
"encoding/json"
"net/url"
"github.com/docker/engine-api/types"
"golang.org/x/net/context"
)
// ImageHistory returns the changes in an image in history format.
func (cli *Client) ImageHistory(ctx context.Context, imageID string) ([]types.ImageHistory, error) {
var history []types.ImageHistory
serverResp, err := cli.get(ctx, "/images/"+imageID+"/history", url.Values{}, nil)
if err != nil {
return history, err
}
err = json.NewDecoder(serverResp.body).Decode(&history)
ensureReaderClosed(serverResp)
return history, err
}

View file

@ -0,0 +1,60 @@
package client
import (
"bytes"
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"strings"
"testing"
"github.com/docker/engine-api/types"
"golang.org/x/net/context"
)
func TestImageHistoryError(t *testing.T) {
client := &Client{
transport: newMockClient(nil, errorMock(http.StatusInternalServerError, "Server error")),
}
_, err := client.ImageHistory(context.Background(), "nothing")
if err == nil || err.Error() != "Error response from daemon: Server error" {
t.Fatalf("expected a Server error, got %v", err)
}
}
func TestImageHistory(t *testing.T) {
expectedURL := "/images/image_id/history"
client := &Client{
transport: newMockClient(nil, func(r *http.Request) (*http.Response, error) {
if !strings.HasPrefix(r.URL.Path, expectedURL) {
return nil, fmt.Errorf("Expected URL '%s', got '%s'", expectedURL, r.URL)
}
b, err := json.Marshal([]types.ImageHistory{
{
ID: "image_id1",
Tags: []string{"tag1", "tag2"},
},
{
ID: "image_id2",
Tags: []string{"tag1", "tag2"},
},
})
if err != nil {
return nil, err
}
return &http.Response{
StatusCode: http.StatusOK,
Body: ioutil.NopCloser(bytes.NewReader(b)),
}, nil
}),
}
imageHistories, err := client.ImageHistory(context.Background(), "image_id")
if err != nil {
t.Fatal(err)
}
if len(imageHistories) != 2 {
t.Fatalf("expected 2 containers, got %v", imageHistories)
}
}

View file

@ -0,0 +1,37 @@
package client
import (
"io"
"net/url"
"golang.org/x/net/context"
"github.com/docker/distribution/reference"
"github.com/docker/engine-api/types"
)
// ImageImport creates a new image based in the source options.
// It returns the JSON content in the response body.
func (cli *Client) ImageImport(ctx context.Context, source types.ImageImportSource, ref string, options types.ImageImportOptions) (io.ReadCloser, error) {
if ref != "" {
//Check if the given image name can be resolved
if _, err := reference.ParseNamed(ref); err != nil {
return nil, err
}
}
query := url.Values{}
query.Set("fromSrc", source.SourceName)
query.Set("repo", ref)
query.Set("tag", options.Tag)
query.Set("message", options.Message)
for _, change := range options.Changes {
query.Add("changes", change)
}
resp, err := cli.postRaw(ctx, "/images/create", query, source.Source, nil)
if err != nil {
return nil, err
}
return resp.body, nil
}

View file

@ -0,0 +1,81 @@
package client
import (
"bytes"
"fmt"
"io/ioutil"
"net/http"
"reflect"
"strings"
"testing"
"github.com/docker/engine-api/types"
"golang.org/x/net/context"
)
func TestImageImportError(t *testing.T) {
client := &Client{
transport: newMockClient(nil, errorMock(http.StatusInternalServerError, "Server error")),
}
_, err := client.ImageImport(context.Background(), types.ImageImportSource{}, "image:tag", types.ImageImportOptions{})
if err == nil || err.Error() != "Error response from daemon: Server error" {
t.Fatalf("expected a Server error, got %v", err)
}
}
func TestImageImport(t *testing.T) {
expectedURL := "/images/create"
client := &Client{
transport: newMockClient(nil, func(r *http.Request) (*http.Response, error) {
if !strings.HasPrefix(r.URL.Path, expectedURL) {
return nil, fmt.Errorf("Expected URL '%s', got '%s'", expectedURL, r.URL)
}
query := r.URL.Query()
fromSrc := query.Get("fromSrc")
if fromSrc != "image_source" {
return nil, fmt.Errorf("fromSrc not set in URL query properly. Expected 'image_source', got %s", fromSrc)
}
repo := query.Get("repo")
if repo != "repository_name:imported" {
return nil, fmt.Errorf("repo not set in URL query properly. Expected 'repository_name', got %s", repo)
}
tag := query.Get("tag")
if tag != "imported" {
return nil, fmt.Errorf("tag not set in URL query properly. Expected 'imported', got %s", tag)
}
message := query.Get("message")
if message != "A message" {
return nil, fmt.Errorf("message not set in URL query properly. Expected 'A message', got %s", message)
}
changes := query["changes"]
expectedChanges := []string{"change1", "change2"}
if !reflect.DeepEqual(expectedChanges, changes) {
return nil, fmt.Errorf("changes not set in URL query properly. Expected %v, got %v", expectedChanges, changes)
}
return &http.Response{
StatusCode: http.StatusOK,
Body: ioutil.NopCloser(bytes.NewReader([]byte("response"))),
}, nil
}),
}
importResponse, err := client.ImageImport(context.Background(), types.ImageImportSource{
Source: strings.NewReader("source"),
SourceName: "image_source",
}, "repository_name:imported", types.ImageImportOptions{
Tag: "imported",
Message: "A message",
Changes: []string{"change1", "change2"},
})
if err != nil {
t.Fatal(err)
}
response, err := ioutil.ReadAll(importResponse)
if err != nil {
t.Fatal(err)
}
importResponse.Close()
if string(response) != "response" {
t.Fatalf("expected response to contain 'response', got %s", string(response))
}
}

View file

@ -0,0 +1,33 @@
package client
import (
"bytes"
"encoding/json"
"io/ioutil"
"net/http"
"github.com/docker/engine-api/types"
"golang.org/x/net/context"
)
// ImageInspectWithRaw returns the image information and its raw representation.
func (cli *Client) ImageInspectWithRaw(ctx context.Context, imageID string) (types.ImageInspect, []byte, error) {
serverResp, err := cli.get(ctx, "/images/"+imageID+"/json", nil, nil)
if err != nil {
if serverResp.statusCode == http.StatusNotFound {
return types.ImageInspect{}, nil, imageNotFoundError{imageID}
}
return types.ImageInspect{}, nil, err
}
defer ensureReaderClosed(serverResp)
body, err := ioutil.ReadAll(serverResp.body)
if err != nil {
return types.ImageInspect{}, nil, err
}
var response types.ImageInspect
rdr := bytes.NewReader(body)
err = json.NewDecoder(rdr).Decode(&response)
return response, body, err
}

View file

@ -0,0 +1,71 @@
package client
import (
"bytes"
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"reflect"
"strings"
"testing"
"github.com/docker/engine-api/types"
"golang.org/x/net/context"
)
func TestImageInspectError(t *testing.T) {
client := &Client{
transport: newMockClient(nil, errorMock(http.StatusInternalServerError, "Server error")),
}
_, _, err := client.ImageInspectWithRaw(context.Background(), "nothing")
if err == nil || err.Error() != "Error response from daemon: Server error" {
t.Fatalf("expected a Server Error, got %v", err)
}
}
func TestImageInspectImageNotFound(t *testing.T) {
client := &Client{
transport: newMockClient(nil, errorMock(http.StatusNotFound, "Server error")),
}
_, _, err := client.ImageInspectWithRaw(context.Background(), "unknown")
if err == nil || !IsErrImageNotFound(err) {
t.Fatalf("expected an imageNotFound error, got %v", err)
}
}
func TestImageInspect(t *testing.T) {
expectedURL := "/images/image_id/json"
expectedTags := []string{"tag1", "tag2"}
client := &Client{
transport: newMockClient(nil, func(req *http.Request) (*http.Response, error) {
if !strings.HasPrefix(req.URL.Path, expectedURL) {
return nil, fmt.Errorf("Expected URL '%s', got '%s'", expectedURL, req.URL)
}
content, err := json.Marshal(types.ImageInspect{
ID: "image_id",
RepoTags: expectedTags,
})
if err != nil {
return nil, err
}
return &http.Response{
StatusCode: http.StatusOK,
Body: ioutil.NopCloser(bytes.NewReader(content)),
}, nil
}),
}
imageInspect, _, err := client.ImageInspectWithRaw(context.Background(), "image_id")
if err != nil {
t.Fatal(err)
}
if imageInspect.ID != "image_id" {
t.Fatalf("expected `image_id`, got %s", imageInspect.ID)
}
if !reflect.DeepEqual(imageInspect.RepoTags, expectedTags) {
t.Fatalf("expected `%v`, got %v", expectedTags, imageInspect.RepoTags)
}
}

View file

@ -0,0 +1,40 @@
package client
import (
"encoding/json"
"net/url"
"github.com/docker/engine-api/types"
"github.com/docker/engine-api/types/filters"
"golang.org/x/net/context"
)
// ImageList returns a list of images in the docker host.
func (cli *Client) ImageList(ctx context.Context, options types.ImageListOptions) ([]types.Image, error) {
var images []types.Image
query := url.Values{}
if options.Filters.Len() > 0 {
filterJSON, err := filters.ToParamWithVersion(cli.version, options.Filters)
if err != nil {
return images, err
}
query.Set("filters", filterJSON)
}
if options.MatchName != "" {
// FIXME rename this parameter, to not be confused with the filters flag
query.Set("filter", options.MatchName)
}
if options.All {
query.Set("all", "1")
}
serverResp, err := cli.get(ctx, "/images/json", query, nil)
if err != nil {
return images, err
}
err = json.NewDecoder(serverResp.body).Decode(&images)
ensureReaderClosed(serverResp)
return images, err
}

View file

@ -0,0 +1,122 @@
package client
import (
"bytes"
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"strings"
"testing"
"github.com/docker/engine-api/types"
"github.com/docker/engine-api/types/filters"
"golang.org/x/net/context"
)
func TestImageListError(t *testing.T) {
client := &Client{
transport: newMockClient(nil, errorMock(http.StatusInternalServerError, "Server error")),
}
_, err := client.ImageList(context.Background(), types.ImageListOptions{})
if err == nil || err.Error() != "Error response from daemon: Server error" {
t.Fatalf("expected a Server Error, got %v", err)
}
}
func TestImageList(t *testing.T) {
expectedURL := "/images/json"
noDanglingfilters := filters.NewArgs()
noDanglingfilters.Add("dangling", "false")
filters := filters.NewArgs()
filters.Add("label", "label1")
filters.Add("label", "label2")
filters.Add("dangling", "true")
listCases := []struct {
options types.ImageListOptions
expectedQueryParams map[string]string
}{
{
options: types.ImageListOptions{},
expectedQueryParams: map[string]string{
"all": "",
"filter": "",
"filters": "",
},
},
{
options: types.ImageListOptions{
All: true,
MatchName: "image_name",
},
expectedQueryParams: map[string]string{
"all": "1",
"filter": "image_name",
"filters": "",
},
},
{
options: types.ImageListOptions{
Filters: filters,
},
expectedQueryParams: map[string]string{
"all": "",
"filter": "",
"filters": `{"dangling":{"true":true},"label":{"label1":true,"label2":true}}`,
},
},
{
options: types.ImageListOptions{
Filters: noDanglingfilters,
},
expectedQueryParams: map[string]string{
"all": "",
"filter": "",
"filters": `{"dangling":{"false":true}}`,
},
},
}
for _, listCase := range listCases {
client := &Client{
transport: newMockClient(nil, func(req *http.Request) (*http.Response, error) {
if !strings.HasPrefix(req.URL.Path, expectedURL) {
return nil, fmt.Errorf("Expected URL '%s', got '%s'", expectedURL, req.URL)
}
query := req.URL.Query()
for key, expected := range listCase.expectedQueryParams {
actual := query.Get(key)
if actual != expected {
return nil, fmt.Errorf("%s not set in URL query properly. Expected '%s', got %s", key, expected, actual)
}
}
content, err := json.Marshal([]types.Image{
{
ID: "image_id2",
},
{
ID: "image_id2",
},
})
if err != nil {
return nil, err
}
return &http.Response{
StatusCode: http.StatusOK,
Body: ioutil.NopCloser(bytes.NewReader(content)),
}, nil
}),
}
images, err := client.ImageList(context.Background(), listCase.options)
if err != nil {
t.Fatal(err)
}
if len(images) != 2 {
t.Fatalf("expected 2 images, got %v", images)
}
}
}

View file

@ -0,0 +1,30 @@
package client
import (
"io"
"net/url"
"golang.org/x/net/context"
"github.com/docker/engine-api/types"
)
// ImageLoad loads an image in the docker host from the client host.
// It's up to the caller to close the io.ReadCloser in the
// ImageLoadResponse returned by this function.
func (cli *Client) ImageLoad(ctx context.Context, input io.Reader, quiet bool) (types.ImageLoadResponse, error) {
v := url.Values{}
v.Set("quiet", "0")
if quiet {
v.Set("quiet", "1")
}
headers := map[string][]string{"Content-Type": {"application/x-tar"}}
resp, err := cli.postRaw(ctx, "/images/load", v, input, headers)
if err != nil {
return types.ImageLoadResponse{}, err
}
return types.ImageLoadResponse{
Body: resp.body,
JSON: resp.header.Get("Content-Type") == "application/json",
}, nil
}

View file

@ -0,0 +1,95 @@
package client
import (
"bytes"
"fmt"
"io/ioutil"
"net/http"
"strings"
"testing"
"golang.org/x/net/context"
)
func TestImageLoadError(t *testing.T) {
client := &Client{
transport: newMockClient(nil, errorMock(http.StatusInternalServerError, "Server error")),
}
_, err := client.ImageLoad(context.Background(), nil, true)
if err == nil || err.Error() != "Error response from daemon: Server error" {
t.Fatalf("expected a Server Error, got %v", err)
}
}
func TestImageLoad(t *testing.T) {
expectedURL := "/images/load"
expectedInput := "inputBody"
expectedOutput := "outputBody"
loadCases := []struct {
quiet bool
responseContentType string
expectedResponseJSON bool
expectedQueryParams map[string]string
}{
{
quiet: false,
responseContentType: "text/plain",
expectedResponseJSON: false,
expectedQueryParams: map[string]string{
"quiet": "0",
},
},
{
quiet: true,
responseContentType: "application/json",
expectedResponseJSON: true,
expectedQueryParams: map[string]string{
"quiet": "1",
},
},
}
for _, loadCase := range loadCases {
client := &Client{
transport: newMockClient(nil, func(req *http.Request) (*http.Response, error) {
if !strings.HasPrefix(req.URL.Path, expectedURL) {
return nil, fmt.Errorf("Expected URL '%s', got '%s'", expectedURL, req.URL)
}
contentType := req.Header.Get("Content-Type")
if contentType != "application/x-tar" {
return nil, fmt.Errorf("content-type not set in URL headers properly. Expected 'application/x-tar', got %s", contentType)
}
query := req.URL.Query()
for key, expected := range loadCase.expectedQueryParams {
actual := query.Get(key)
if actual != expected {
return nil, fmt.Errorf("%s not set in URL query properly. Expected '%s', got %s", key, expected, actual)
}
}
headers := http.Header{}
headers.Add("Content-Type", loadCase.responseContentType)
return &http.Response{
StatusCode: http.StatusOK,
Body: ioutil.NopCloser(bytes.NewReader([]byte(expectedOutput))),
Header: headers,
}, nil
}),
}
input := bytes.NewReader([]byte(expectedInput))
imageLoadResponse, err := client.ImageLoad(context.Background(), input, loadCase.quiet)
if err != nil {
t.Fatal(err)
}
if imageLoadResponse.JSON != loadCase.expectedResponseJSON {
t.Fatalf("expected a JSON response, was not.")
}
body, err := ioutil.ReadAll(imageLoadResponse.Body)
if err != nil {
t.Fatal(err)
}
if string(body) != expectedOutput {
t.Fatalf("expected %s, got %s", expectedOutput, string(body))
}
}
}

View file

@ -0,0 +1,46 @@
package client
import (
"io"
"net/http"
"net/url"
"golang.org/x/net/context"
"github.com/docker/engine-api/types"
"github.com/docker/engine-api/types/reference"
)
// ImagePull requests the docker host to pull an image from a remote registry.
// It executes the privileged function if the operation is unauthorized
// and it tries one more time.
// It's up to the caller to handle the io.ReadCloser and close it properly.
//
// FIXME(vdemeester): there is currently used in a few way in docker/docker
// - if not in trusted content, ref is used to pass the whole reference, and tag is empty
// - if in trusted content, ref is used to pass the reference name, and tag for the digest
func (cli *Client) ImagePull(ctx context.Context, ref string, options types.ImagePullOptions) (io.ReadCloser, error) {
repository, tag, err := reference.Parse(ref)
if err != nil {
return nil, err
}
query := url.Values{}
query.Set("fromImage", repository)
if tag != "" && !options.All {
query.Set("tag", tag)
}
resp, err := cli.tryImageCreate(ctx, query, options.RegistryAuth)
if resp.statusCode == http.StatusUnauthorized && options.PrivilegeFunc != nil {
newAuthHeader, privilegeErr := options.PrivilegeFunc()
if privilegeErr != nil {
return nil, privilegeErr
}
resp, err = cli.tryImageCreate(ctx, query, newAuthHeader)
}
if err != nil {
return nil, err
}
return resp.body, nil
}

View file

@ -0,0 +1,199 @@
package client
import (
"bytes"
"fmt"
"io/ioutil"
"net/http"
"strings"
"testing"
"golang.org/x/net/context"
"github.com/docker/engine-api/types"
)
func TestImagePullReferenceParseError(t *testing.T) {
client := &Client{
transport: newMockClient(nil, func(req *http.Request) (*http.Response, error) {
return nil, nil
}),
}
// An empty reference is an invalid reference
_, err := client.ImagePull(context.Background(), "", types.ImagePullOptions{})
if err == nil || err.Error() != "repository name must have at least one component" {
t.Fatalf("expected an error, got %v", err)
}
}
func TestImagePullAnyError(t *testing.T) {
client := &Client{
transport: newMockClient(nil, errorMock(http.StatusInternalServerError, "Server error")),
}
_, err := client.ImagePull(context.Background(), "myimage", types.ImagePullOptions{})
if err == nil || err.Error() != "Error response from daemon: Server error" {
t.Fatalf("expected a Server Error, got %v", err)
}
}
func TestImagePullStatusUnauthorizedError(t *testing.T) {
client := &Client{
transport: newMockClient(nil, errorMock(http.StatusUnauthorized, "Unauthorized error")),
}
_, err := client.ImagePull(context.Background(), "myimage", types.ImagePullOptions{})
if err == nil || err.Error() != "Error response from daemon: Unauthorized error" {
t.Fatalf("expected an Unauthorized Error, got %v", err)
}
}
func TestImagePullWithUnauthorizedErrorAndPrivilegeFuncError(t *testing.T) {
client := &Client{
transport: newMockClient(nil, errorMock(http.StatusUnauthorized, "Unauthorized error")),
}
privilegeFunc := func() (string, error) {
return "", fmt.Errorf("Error requesting privilege")
}
_, err := client.ImagePull(context.Background(), "myimage", types.ImagePullOptions{
PrivilegeFunc: privilegeFunc,
})
if err == nil || err.Error() != "Error requesting privilege" {
t.Fatalf("expected an error requesting privilege, got %v", err)
}
}
func TestImagePullWithUnauthorizedErrorAndAnotherUnauthorizedError(t *testing.T) {
client := &Client{
transport: newMockClient(nil, errorMock(http.StatusUnauthorized, "Unauthorized error")),
}
privilegeFunc := func() (string, error) {
return "a-auth-header", nil
}
_, err := client.ImagePull(context.Background(), "myimage", types.ImagePullOptions{
PrivilegeFunc: privilegeFunc,
})
if err == nil || err.Error() != "Error response from daemon: Unauthorized error" {
t.Fatalf("expected an Unauthorized Error, got %v", err)
}
}
func TestImagePullWithPrivilegedFuncNoError(t *testing.T) {
expectedURL := "/images/create"
client := &Client{
transport: newMockClient(nil, func(req *http.Request) (*http.Response, error) {
if !strings.HasPrefix(req.URL.Path, expectedURL) {
return nil, fmt.Errorf("Expected URL '%s', got '%s'", expectedURL, req.URL)
}
auth := req.Header.Get("X-Registry-Auth")
if auth == "NotValid" {
return &http.Response{
StatusCode: http.StatusUnauthorized,
Body: ioutil.NopCloser(bytes.NewReader([]byte("Invalid credentials"))),
}, nil
}
if auth != "IAmValid" {
return nil, fmt.Errorf("Invalid auth header : expected %s, got %s", "IAmValid", auth)
}
query := req.URL.Query()
fromImage := query.Get("fromImage")
if fromImage != "myimage" {
return nil, fmt.Errorf("fromimage not set in URL query properly. Expected '%s', got %s", "myimage", fromImage)
}
tag := query.Get("tag")
if tag != "latest" {
return nil, fmt.Errorf("tag not set in URL query properly. Expected '%s', got %s", "latest", tag)
}
return &http.Response{
StatusCode: http.StatusOK,
Body: ioutil.NopCloser(bytes.NewReader([]byte("hello world"))),
}, nil
}),
}
privilegeFunc := func() (string, error) {
return "IAmValid", nil
}
resp, err := client.ImagePull(context.Background(), "myimage", types.ImagePullOptions{
RegistryAuth: "NotValid",
PrivilegeFunc: privilegeFunc,
})
if err != nil {
t.Fatal(err)
}
body, err := ioutil.ReadAll(resp)
if err != nil {
t.Fatal(err)
}
if string(body) != "hello world" {
t.Fatalf("expected 'hello world', got %s", string(body))
}
}
func TestImagePullWithoutErrors(t *testing.T) {
expectedURL := "/images/create"
expectedOutput := "hello world"
pullCases := []struct {
all bool
reference string
expectedImage string
expectedTag string
}{
{
all: false,
reference: "myimage",
expectedImage: "myimage",
expectedTag: "latest",
},
{
all: false,
reference: "myimage:tag",
expectedImage: "myimage",
expectedTag: "tag",
},
{
all: true,
reference: "myimage",
expectedImage: "myimage",
expectedTag: "",
},
{
all: true,
reference: "myimage:anything",
expectedImage: "myimage",
expectedTag: "",
},
}
for _, pullCase := range pullCases {
client := &Client{
transport: newMockClient(nil, func(req *http.Request) (*http.Response, error) {
if !strings.HasPrefix(req.URL.Path, expectedURL) {
return nil, fmt.Errorf("Expected URL '%s', got '%s'", expectedURL, req.URL)
}
query := req.URL.Query()
fromImage := query.Get("fromImage")
if fromImage != pullCase.expectedImage {
return nil, fmt.Errorf("fromimage not set in URL query properly. Expected '%s', got %s", pullCase.expectedImage, fromImage)
}
tag := query.Get("tag")
if tag != pullCase.expectedTag {
return nil, fmt.Errorf("tag not set in URL query properly. Expected '%s', got %s", pullCase.expectedTag, tag)
}
return &http.Response{
StatusCode: http.StatusOK,
Body: ioutil.NopCloser(bytes.NewReader([]byte(expectedOutput))),
}, nil
}),
}
resp, err := client.ImagePull(context.Background(), pullCase.reference, types.ImagePullOptions{
All: pullCase.all,
})
if err != nil {
t.Fatal(err)
}
body, err := ioutil.ReadAll(resp)
if err != nil {
t.Fatal(err)
}
if string(body) != expectedOutput {
t.Fatalf("expected '%s', got %s", expectedOutput, string(body))
}
}
}

View file

@ -0,0 +1,54 @@
package client
import (
"errors"
"io"
"net/http"
"net/url"
"golang.org/x/net/context"
distreference "github.com/docker/distribution/reference"
"github.com/docker/engine-api/types"
)
// ImagePush requests the docker host to push an image to a remote registry.
// It executes the privileged function if the operation is unauthorized
// and it tries one more time.
// It's up to the caller to handle the io.ReadCloser and close it properly.
func (cli *Client) ImagePush(ctx context.Context, ref string, options types.ImagePushOptions) (io.ReadCloser, error) {
distributionRef, err := distreference.ParseNamed(ref)
if err != nil {
return nil, err
}
if _, isCanonical := distributionRef.(distreference.Canonical); isCanonical {
return nil, errors.New("cannot push a digest reference")
}
var tag = ""
if nameTaggedRef, isNamedTagged := distributionRef.(distreference.NamedTagged); isNamedTagged {
tag = nameTaggedRef.Tag()
}
query := url.Values{}
query.Set("tag", tag)
resp, err := cli.tryImagePush(ctx, distributionRef.Name(), query, options.RegistryAuth)
if resp.statusCode == http.StatusUnauthorized && options.PrivilegeFunc != nil {
newAuthHeader, privilegeErr := options.PrivilegeFunc()
if privilegeErr != nil {
return nil, privilegeErr
}
resp, err = cli.tryImagePush(ctx, distributionRef.Name(), query, newAuthHeader)
}
if err != nil {
return nil, err
}
return resp.body, nil
}
func (cli *Client) tryImagePush(ctx context.Context, imageID string, query url.Values, registryAuth string) (serverResponse, error) {
headers := map[string][]string{"X-Registry-Auth": {registryAuth}}
return cli.post(ctx, "/images/"+imageID+"/push", query, nil, headers)
}

View file

@ -0,0 +1,180 @@
package client
import (
"bytes"
"fmt"
"io/ioutil"
"net/http"
"strings"
"testing"
"golang.org/x/net/context"
"github.com/docker/engine-api/types"
)
func TestImagePushReferenceError(t *testing.T) {
client := &Client{
transport: newMockClient(nil, func(req *http.Request) (*http.Response, error) {
return nil, nil
}),
}
// An empty reference is an invalid reference
_, err := client.ImagePush(context.Background(), "", types.ImagePushOptions{})
if err == nil || err.Error() != "repository name must have at least one component" {
t.Fatalf("expected an error, got %v", err)
}
// An canonical reference cannot be pushed
_, err = client.ImagePush(context.Background(), "repo@sha256:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", types.ImagePushOptions{})
if err == nil || err.Error() != "cannot push a digest reference" {
t.Fatalf("expected an error, got %v", err)
}
}
func TestImagePushAnyError(t *testing.T) {
client := &Client{
transport: newMockClient(nil, errorMock(http.StatusInternalServerError, "Server error")),
}
_, err := client.ImagePush(context.Background(), "myimage", types.ImagePushOptions{})
if err == nil || err.Error() != "Error response from daemon: Server error" {
t.Fatalf("expected a Server Error, got %v", err)
}
}
func TestImagePushStatusUnauthorizedError(t *testing.T) {
client := &Client{
transport: newMockClient(nil, errorMock(http.StatusUnauthorized, "Unauthorized error")),
}
_, err := client.ImagePush(context.Background(), "myimage", types.ImagePushOptions{})
if err == nil || err.Error() != "Error response from daemon: Unauthorized error" {
t.Fatalf("expected an Unauthorized Error, got %v", err)
}
}
func TestImagePushWithUnauthorizedErrorAndPrivilegeFuncError(t *testing.T) {
client := &Client{
transport: newMockClient(nil, errorMock(http.StatusUnauthorized, "Unauthorized error")),
}
privilegeFunc := func() (string, error) {
return "", fmt.Errorf("Error requesting privilege")
}
_, err := client.ImagePush(context.Background(), "myimage", types.ImagePushOptions{
PrivilegeFunc: privilegeFunc,
})
if err == nil || err.Error() != "Error requesting privilege" {
t.Fatalf("expected an error requesting privilege, got %v", err)
}
}
func TestImagePushWithUnauthorizedErrorAndAnotherUnauthorizedError(t *testing.T) {
client := &Client{
transport: newMockClient(nil, errorMock(http.StatusUnauthorized, "Unauthorized error")),
}
privilegeFunc := func() (string, error) {
return "a-auth-header", nil
}
_, err := client.ImagePush(context.Background(), "myimage", types.ImagePushOptions{
PrivilegeFunc: privilegeFunc,
})
if err == nil || err.Error() != "Error response from daemon: Unauthorized error" {
t.Fatalf("expected an Unauthorized Error, got %v", err)
}
}
func TestImagePushWithPrivilegedFuncNoError(t *testing.T) {
expectedURL := "/images/myimage/push"
client := &Client{
transport: newMockClient(nil, func(req *http.Request) (*http.Response, error) {
if !strings.HasPrefix(req.URL.Path, expectedURL) {
return nil, fmt.Errorf("Expected URL '%s', got '%s'", expectedURL, req.URL)
}
auth := req.Header.Get("X-Registry-Auth")
if auth == "NotValid" {
return &http.Response{
StatusCode: http.StatusUnauthorized,
Body: ioutil.NopCloser(bytes.NewReader([]byte("Invalid credentials"))),
}, nil
}
if auth != "IAmValid" {
return nil, fmt.Errorf("Invalid auth header : expected %s, got %s", "IAmValid", auth)
}
query := req.URL.Query()
tag := query.Get("tag")
if tag != "tag" {
return nil, fmt.Errorf("tag not set in URL query properly. Expected '%s', got %s", "tag", tag)
}
return &http.Response{
StatusCode: http.StatusOK,
Body: ioutil.NopCloser(bytes.NewReader([]byte("hello world"))),
}, nil
}),
}
privilegeFunc := func() (string, error) {
return "IAmValid", nil
}
resp, err := client.ImagePush(context.Background(), "myimage:tag", types.ImagePushOptions{
RegistryAuth: "NotValid",
PrivilegeFunc: privilegeFunc,
})
if err != nil {
t.Fatal(err)
}
body, err := ioutil.ReadAll(resp)
if err != nil {
t.Fatal(err)
}
if string(body) != "hello world" {
t.Fatalf("expected 'hello world', got %s", string(body))
}
}
func TestImagePushWithoutErrors(t *testing.T) {
expectedOutput := "hello world"
expectedURLFormat := "/images/%s/push"
pullCases := []struct {
reference string
expectedImage string
expectedTag string
}{
{
reference: "myimage",
expectedImage: "myimage",
expectedTag: "",
},
{
reference: "myimage:tag",
expectedImage: "myimage",
expectedTag: "tag",
},
}
for _, pullCase := range pullCases {
client := &Client{
transport: newMockClient(nil, func(req *http.Request) (*http.Response, error) {
expectedURL := fmt.Sprintf(expectedURLFormat, pullCase.expectedImage)
if !strings.HasPrefix(req.URL.Path, expectedURL) {
return nil, fmt.Errorf("Expected URL '%s', got '%s'", expectedURL, req.URL)
}
query := req.URL.Query()
tag := query.Get("tag")
if tag != pullCase.expectedTag {
return nil, fmt.Errorf("tag not set in URL query properly. Expected '%s', got %s", pullCase.expectedTag, tag)
}
return &http.Response{
StatusCode: http.StatusOK,
Body: ioutil.NopCloser(bytes.NewReader([]byte(expectedOutput))),
}, nil
}),
}
resp, err := client.ImagePush(context.Background(), pullCase.reference, types.ImagePushOptions{})
if err != nil {
t.Fatal(err)
}
body, err := ioutil.ReadAll(resp)
if err != nil {
t.Fatal(err)
}
if string(body) != expectedOutput {
t.Fatalf("expected '%s', got %s", expectedOutput, string(body))
}
}
}

View file

@ -0,0 +1,31 @@
package client
import (
"encoding/json"
"net/url"
"github.com/docker/engine-api/types"
"golang.org/x/net/context"
)
// ImageRemove removes an image from the docker host.
func (cli *Client) ImageRemove(ctx context.Context, imageID string, options types.ImageRemoveOptions) ([]types.ImageDelete, error) {
query := url.Values{}
if options.Force {
query.Set("force", "1")
}
if !options.PruneChildren {
query.Set("noprune", "1")
}
resp, err := cli.delete(ctx, "/images/"+imageID, query, nil)
if err != nil {
return nil, err
}
var dels []types.ImageDelete
err = json.NewDecoder(resp.body).Decode(&dels)
ensureReaderClosed(resp)
return dels, err
}

View file

@ -0,0 +1,95 @@
package client
import (
"bytes"
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"strings"
"testing"
"github.com/docker/engine-api/types"
"golang.org/x/net/context"
)
func TestImageRemoveError(t *testing.T) {
client := &Client{
transport: newMockClient(nil, errorMock(http.StatusInternalServerError, "Server error")),
}
_, err := client.ImageRemove(context.Background(), "image_id", types.ImageRemoveOptions{})
if err == nil || err.Error() != "Error response from daemon: Server error" {
t.Fatalf("expected a Server Error, got %v", err)
}
}
func TestImageRemove(t *testing.T) {
expectedURL := "/images/image_id"
removeCases := []struct {
force bool
pruneChildren bool
expectedQueryParams map[string]string
}{
{
force: false,
pruneChildren: false,
expectedQueryParams: map[string]string{
"force": "",
"noprune": "1",
},
}, {
force: true,
pruneChildren: true,
expectedQueryParams: map[string]string{
"force": "1",
"noprune": "",
},
},
}
for _, removeCase := range removeCases {
client := &Client{
transport: newMockClient(nil, func(req *http.Request) (*http.Response, error) {
if !strings.HasPrefix(req.URL.Path, expectedURL) {
return nil, fmt.Errorf("expected URL '%s', got '%s'", expectedURL, req.URL)
}
if req.Method != "DELETE" {
return nil, fmt.Errorf("expected DELETE method, got %s", req.Method)
}
query := req.URL.Query()
for key, expected := range removeCase.expectedQueryParams {
actual := query.Get(key)
if actual != expected {
return nil, fmt.Errorf("%s not set in URL query properly. Expected '%s', got %s", key, expected, actual)
}
}
b, err := json.Marshal([]types.ImageDelete{
{
Untagged: "image_id1",
},
{
Deleted: "image_id",
},
})
if err != nil {
return nil, err
}
return &http.Response{
StatusCode: http.StatusOK,
Body: ioutil.NopCloser(bytes.NewReader(b)),
}, nil
}),
}
imageDeletes, err := client.ImageRemove(context.Background(), "image_id", types.ImageRemoveOptions{
Force: removeCase.force,
PruneChildren: removeCase.pruneChildren,
})
if err != nil {
t.Fatal(err)
}
if len(imageDeletes) != 2 {
t.Fatalf("expected 2 deleted images, got %v", imageDeletes)
}
}
}

View file

@ -0,0 +1,22 @@
package client
import (
"io"
"net/url"
"golang.org/x/net/context"
)
// ImageSave retrieves one or more images from the docker host as an io.ReadCloser.
// It's up to the caller to store the images and close the stream.
func (cli *Client) ImageSave(ctx context.Context, imageIDs []string) (io.ReadCloser, error) {
query := url.Values{
"names": imageIDs,
}
resp, err := cli.get(ctx, "/images/get", query, nil)
if err != nil {
return nil, err
}
return resp.body, nil
}

View file

@ -0,0 +1,58 @@
package client
import (
"bytes"
"fmt"
"io/ioutil"
"net/http"
"reflect"
"testing"
"golang.org/x/net/context"
"strings"
)
func TestImageSaveError(t *testing.T) {
client := &Client{
transport: newMockClient(nil, errorMock(http.StatusInternalServerError, "Server error")),
}
_, err := client.ImageSave(context.Background(), []string{"nothing"})
if err == nil || err.Error() != "Error response from daemon: Server error" {
t.Fatalf("expected a Server error, got %v", err)
}
}
func TestImageSave(t *testing.T) {
expectedURL := "/images/get"
client := &Client{
transport: newMockClient(nil, func(r *http.Request) (*http.Response, error) {
if !strings.HasPrefix(r.URL.Path, expectedURL) {
return nil, fmt.Errorf("Expected URL '%s', got '%s'", expectedURL, r.URL)
}
query := r.URL.Query()
names := query["names"]
expectedNames := []string{"image_id1", "image_id2"}
if !reflect.DeepEqual(names, expectedNames) {
return nil, fmt.Errorf("names not set in URL query properly. Expected %v, got %v", names, expectedNames)
}
return &http.Response{
StatusCode: http.StatusOK,
Body: ioutil.NopCloser(bytes.NewReader([]byte("response"))),
}, nil
}),
}
saveResponse, err := client.ImageSave(context.Background(), []string{"image_id1", "image_id2"})
if err != nil {
t.Fatal(err)
}
response, err := ioutil.ReadAll(saveResponse)
if err != nil {
t.Fatal(err)
}
saveResponse.Close()
if string(response) != "response" {
t.Fatalf("expected response to contain 'response', got %s", string(response))
}
}

View file

@ -0,0 +1,51 @@
package client
import (
"encoding/json"
"fmt"
"net/http"
"net/url"
"github.com/docker/engine-api/types"
"github.com/docker/engine-api/types/filters"
"github.com/docker/engine-api/types/registry"
"golang.org/x/net/context"
)
// ImageSearch makes the docker host to search by a term in a remote registry.
// The list of results is not sorted in any fashion.
func (cli *Client) ImageSearch(ctx context.Context, term string, options types.ImageSearchOptions) ([]registry.SearchResult, error) {
var results []registry.SearchResult
query := url.Values{}
query.Set("term", term)
query.Set("limit", fmt.Sprintf("%d", options.Limit))
if options.Filters.Len() > 0 {
filterJSON, err := filters.ToParam(options.Filters)
if err != nil {
return results, err
}
query.Set("filters", filterJSON)
}
resp, err := cli.tryImageSearch(ctx, query, options.RegistryAuth)
if resp.statusCode == http.StatusUnauthorized && options.PrivilegeFunc != nil {
newAuthHeader, privilegeErr := options.PrivilegeFunc()
if privilegeErr != nil {
return results, privilegeErr
}
resp, err = cli.tryImageSearch(ctx, query, newAuthHeader)
}
if err != nil {
return results, err
}
err = json.NewDecoder(resp.body).Decode(&results)
ensureReaderClosed(resp)
return results, err
}
func (cli *Client) tryImageSearch(ctx context.Context, query url.Values, registryAuth string) (serverResponse, error) {
headers := map[string][]string{"X-Registry-Auth": {registryAuth}}
return cli.get(ctx, "/images/search", query, headers)
}

View file

@ -0,0 +1,165 @@
package client
import (
"bytes"
"fmt"
"io/ioutil"
"net/http"
"strings"
"testing"
"golang.org/x/net/context"
"encoding/json"
"github.com/docker/engine-api/types"
"github.com/docker/engine-api/types/filters"
"github.com/docker/engine-api/types/registry"
)
func TestImageSearchAnyError(t *testing.T) {
client := &Client{
transport: newMockClient(nil, errorMock(http.StatusInternalServerError, "Server error")),
}
_, err := client.ImageSearch(context.Background(), "some-image", types.ImageSearchOptions{})
if err == nil || err.Error() != "Error response from daemon: Server error" {
t.Fatalf("expected a Server Error, got %v", err)
}
}
func TestImageSearchStatusUnauthorizedError(t *testing.T) {
client := &Client{
transport: newMockClient(nil, errorMock(http.StatusUnauthorized, "Unauthorized error")),
}
_, err := client.ImageSearch(context.Background(), "some-image", types.ImageSearchOptions{})
if err == nil || err.Error() != "Error response from daemon: Unauthorized error" {
t.Fatalf("expected an Unauthorized Error, got %v", err)
}
}
func TestImageSearchWithUnauthorizedErrorAndPrivilegeFuncError(t *testing.T) {
client := &Client{
transport: newMockClient(nil, errorMock(http.StatusUnauthorized, "Unauthorized error")),
}
privilegeFunc := func() (string, error) {
return "", fmt.Errorf("Error requesting privilege")
}
_, err := client.ImageSearch(context.Background(), "some-image", types.ImageSearchOptions{
PrivilegeFunc: privilegeFunc,
})
if err == nil || err.Error() != "Error requesting privilege" {
t.Fatalf("expected an error requesting privilege, got %v", err)
}
}
func TestImageSearchWithUnauthorizedErrorAndAnotherUnauthorizedError(t *testing.T) {
client := &Client{
transport: newMockClient(nil, errorMock(http.StatusUnauthorized, "Unauthorized error")),
}
privilegeFunc := func() (string, error) {
return "a-auth-header", nil
}
_, err := client.ImageSearch(context.Background(), "some-image", types.ImageSearchOptions{
PrivilegeFunc: privilegeFunc,
})
if err == nil || err.Error() != "Error response from daemon: Unauthorized error" {
t.Fatalf("expected an Unauthorized Error, got %v", err)
}
}
func TestImageSearchWithPrivilegedFuncNoError(t *testing.T) {
expectedURL := "/images/search"
client := &Client{
transport: newMockClient(nil, func(req *http.Request) (*http.Response, error) {
if !strings.HasPrefix(req.URL.Path, expectedURL) {
return nil, fmt.Errorf("Expected URL '%s', got '%s'", expectedURL, req.URL)
}
auth := req.Header.Get("X-Registry-Auth")
if auth == "NotValid" {
return &http.Response{
StatusCode: http.StatusUnauthorized,
Body: ioutil.NopCloser(bytes.NewReader([]byte("Invalid credentials"))),
}, nil
}
if auth != "IAmValid" {
return nil, fmt.Errorf("Invalid auth header : expected %s, got %s", "IAmValid", auth)
}
query := req.URL.Query()
term := query.Get("term")
if term != "some-image" {
return nil, fmt.Errorf("tag not set in URL query properly. Expected '%s', got %s", "some-image", term)
}
content, err := json.Marshal([]registry.SearchResult{
{
Name: "anything",
},
})
if err != nil {
return nil, err
}
return &http.Response{
StatusCode: http.StatusOK,
Body: ioutil.NopCloser(bytes.NewReader(content)),
}, nil
}),
}
privilegeFunc := func() (string, error) {
return "IAmValid", nil
}
results, err := client.ImageSearch(context.Background(), "some-image", types.ImageSearchOptions{
RegistryAuth: "NotValid",
PrivilegeFunc: privilegeFunc,
})
if err != nil {
t.Fatal(err)
}
if len(results) != 1 {
t.Fatalf("expected a result, got %v", results)
}
}
func TestImageSearchWithoutErrors(t *testing.T) {
expectedURL := "/images/search"
filterArgs := filters.NewArgs()
filterArgs.Add("is-automated", "true")
filterArgs.Add("stars", "3")
expectedFilters := `{"is-automated":{"true":true},"stars":{"3":true}}`
client := &Client{
transport: newMockClient(nil, func(req *http.Request) (*http.Response, error) {
if !strings.HasPrefix(req.URL.Path, expectedURL) {
return nil, fmt.Errorf("Expected URL '%s', got '%s'", expectedURL, req.URL)
}
query := req.URL.Query()
term := query.Get("term")
if term != "some-image" {
return nil, fmt.Errorf("tag not set in URL query properly. Expected '%s', got %s", "some-image", term)
}
filters := query.Get("filters")
if filters != expectedFilters {
return nil, fmt.Errorf("filters not set in URL query properly. Expected '%s', got %s", expectedFilters, filters)
}
content, err := json.Marshal([]registry.SearchResult{
{
Name: "anything",
},
})
if err != nil {
return nil, err
}
return &http.Response{
StatusCode: http.StatusOK,
Body: ioutil.NopCloser(bytes.NewReader(content)),
}, nil
}),
}
results, err := client.ImageSearch(context.Background(), "some-image", types.ImageSearchOptions{
Filters: filterArgs,
})
if err != nil {
t.Fatal(err)
}
if len(results) != 1 {
t.Fatalf("expected a result, got %v", results)
}
}

View file

@ -0,0 +1,34 @@
package client
import (
"errors"
"fmt"
"net/url"
"golang.org/x/net/context"
distreference "github.com/docker/distribution/reference"
"github.com/docker/engine-api/types/reference"
)
// ImageTag tags an image in the docker host
func (cli *Client) ImageTag(ctx context.Context, imageID, ref string) error {
distributionRef, err := distreference.ParseNamed(ref)
if err != nil {
return fmt.Errorf("Error parsing reference: %q is not a valid repository/tag", ref)
}
if _, isCanonical := distributionRef.(distreference.Canonical); isCanonical {
return errors.New("refusing to create a tag with a digest reference")
}
tag := reference.GetTagFromNamedRef(distributionRef)
query := url.Values{}
query.Set("repo", distributionRef.Name())
query.Set("tag", tag)
resp, err := cli.post(ctx, "/images/"+imageID+"/tag", query, nil, nil)
ensureReaderClosed(resp)
return err
}

View file

@ -0,0 +1,121 @@
package client
import (
"bytes"
"fmt"
"io/ioutil"
"net/http"
"strings"
"testing"
"golang.org/x/net/context"
)
func TestImageTagError(t *testing.T) {
client := &Client{
transport: newMockClient(nil, errorMock(http.StatusInternalServerError, "Server error")),
}
err := client.ImageTag(context.Background(), "image_id", "repo:tag")
if err == nil || err.Error() != "Error response from daemon: Server error" {
t.Fatalf("expected a Server Error, got %v", err)
}
}
// Note: this is not testing all the InvalidReference as it's the reponsability
// of distribution/reference package.
func TestImageTagInvalidReference(t *testing.T) {
client := &Client{
transport: newMockClient(nil, errorMock(http.StatusInternalServerError, "Server error")),
}
err := client.ImageTag(context.Background(), "image_id", "aa/asdf$$^/aa")
if err == nil || err.Error() != `Error parsing reference: "aa/asdf$$^/aa" is not a valid repository/tag` {
t.Fatalf("expected ErrReferenceInvalidFormat, got %v", err)
}
}
func TestImageTag(t *testing.T) {
expectedURL := "/images/image_id/tag"
tagCases := []struct {
reference string
expectedQueryParams map[string]string
}{
{
reference: "repository:tag1",
expectedQueryParams: map[string]string{
"repo": "repository",
"tag": "tag1",
},
}, {
reference: "another_repository:latest",
expectedQueryParams: map[string]string{
"repo": "another_repository",
"tag": "latest",
},
}, {
reference: "another_repository",
expectedQueryParams: map[string]string{
"repo": "another_repository",
"tag": "latest",
},
}, {
reference: "test/another_repository",
expectedQueryParams: map[string]string{
"repo": "test/another_repository",
"tag": "latest",
},
}, {
reference: "test/another_repository:tag1",
expectedQueryParams: map[string]string{
"repo": "test/another_repository",
"tag": "tag1",
},
}, {
reference: "test/test/another_repository:tag1",
expectedQueryParams: map[string]string{
"repo": "test/test/another_repository",
"tag": "tag1",
},
}, {
reference: "test:5000/test/another_repository:tag1",
expectedQueryParams: map[string]string{
"repo": "test:5000/test/another_repository",
"tag": "tag1",
},
}, {
reference: "test:5000/test/another_repository",
expectedQueryParams: map[string]string{
"repo": "test:5000/test/another_repository",
"tag": "latest",
},
},
}
for _, tagCase := range tagCases {
client := &Client{
transport: newMockClient(nil, func(req *http.Request) (*http.Response, error) {
if !strings.HasPrefix(req.URL.Path, expectedURL) {
return nil, fmt.Errorf("expected URL '%s', got '%s'", expectedURL, req.URL)
}
if req.Method != "POST" {
return nil, fmt.Errorf("expected POST method, got %s", req.Method)
}
query := req.URL.Query()
for key, expected := range tagCase.expectedQueryParams {
actual := query.Get(key)
if actual != expected {
return nil, fmt.Errorf("%s not set in URL query properly. Expected '%s', got %s", key, expected, actual)
}
}
return &http.Response{
StatusCode: http.StatusOK,
Body: ioutil.NopCloser(bytes.NewReader([]byte(""))),
}, nil
}),
}
err := client.ImageTag(context.Background(), "image_id", tagCase.reference)
if err != nil {
t.Fatal(err)
}
}
}

26
vendor/github.com/docker/engine-api/client/info.go generated vendored Normal file
View file

@ -0,0 +1,26 @@
package client
import (
"encoding/json"
"fmt"
"net/url"
"github.com/docker/engine-api/types"
"golang.org/x/net/context"
)
// Info returns information about the docker server.
func (cli *Client) Info(ctx context.Context) (types.Info, error) {
var info types.Info
serverResp, err := cli.get(ctx, "/info", url.Values{}, nil)
if err != nil {
return info, err
}
defer ensureReaderClosed(serverResp)
if err := json.NewDecoder(serverResp.body).Decode(&info); err != nil {
return info, fmt.Errorf("Error reading remote info: %v", err)
}
return info, nil
}

View file

@ -0,0 +1,76 @@
package client
import (
"bytes"
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"strings"
"testing"
"github.com/docker/engine-api/types"
"golang.org/x/net/context"
)
func TestInfoServerError(t *testing.T) {
client := &Client{
transport: newMockClient(nil, errorMock(http.StatusInternalServerError, "Server error")),
}
_, err := client.Info(context.Background())
if err == nil || err.Error() != "Error response from daemon: Server error" {
t.Fatalf("expected a Server Error, got %v", err)
}
}
func TestInfoInvalidResponseJSONError(t *testing.T) {
client := &Client{
transport: newMockClient(nil, func(req *http.Request) (*http.Response, error) {
return &http.Response{
StatusCode: http.StatusOK,
Body: ioutil.NopCloser(bytes.NewReader([]byte("invalid json"))),
}, nil
}),
}
_, err := client.Info(context.Background())
if err == nil || !strings.Contains(err.Error(), "invalid character") {
t.Fatalf("expected a 'invalid character' error, got %v", err)
}
}
func TestInfo(t *testing.T) {
expectedURL := "/info"
client := &Client{
transport: newMockClient(nil, func(req *http.Request) (*http.Response, error) {
if !strings.HasPrefix(req.URL.Path, expectedURL) {
return nil, fmt.Errorf("Expected URL '%s', got '%s'", expectedURL, req.URL)
}
info := &types.Info{
ID: "daemonID",
Containers: 3,
}
b, err := json.Marshal(info)
if err != nil {
return nil, err
}
return &http.Response{
StatusCode: http.StatusOK,
Body: ioutil.NopCloser(bytes.NewReader(b)),
}, nil
}),
}
info, err := client.Info(context.Background())
if err != nil {
t.Fatal(err)
}
if info.ID != "daemonID" {
t.Fatalf("expected daemonID, got %s", info.ID)
}
if info.Containers != 3 {
t.Fatalf("expected 3 containers, got %d", info.Containers)
}
}

135
vendor/github.com/docker/engine-api/client/interface.go generated vendored Normal file
View file

@ -0,0 +1,135 @@
package client
import (
"io"
"time"
"github.com/docker/engine-api/types"
"github.com/docker/engine-api/types/container"
"github.com/docker/engine-api/types/filters"
"github.com/docker/engine-api/types/network"
"github.com/docker/engine-api/types/registry"
"github.com/docker/engine-api/types/swarm"
"golang.org/x/net/context"
)
// CommonAPIClient is the common methods between stable and experimental versions of APIClient.
type CommonAPIClient interface {
ContainerAPIClient
ImageAPIClient
NodeAPIClient
NetworkAPIClient
ServiceAPIClient
SwarmAPIClient
SystemAPIClient
VolumeAPIClient
ClientVersion() string
ServerVersion(ctx context.Context) (types.Version, error)
UpdateClientVersion(v string)
}
// ContainerAPIClient defines API client methods for the containers
type ContainerAPIClient interface {
ContainerAttach(ctx context.Context, container string, options types.ContainerAttachOptions) (types.HijackedResponse, error)
ContainerCommit(ctx context.Context, container string, options types.ContainerCommitOptions) (types.ContainerCommitResponse, error)
ContainerCreate(ctx context.Context, config *container.Config, hostConfig *container.HostConfig, networkingConfig *network.NetworkingConfig, containerName string) (types.ContainerCreateResponse, error)
ContainerDiff(ctx context.Context, container string) ([]types.ContainerChange, error)
ContainerExecAttach(ctx context.Context, execID string, config types.ExecConfig) (types.HijackedResponse, error)
ContainerExecCreate(ctx context.Context, container string, config types.ExecConfig) (types.ContainerExecCreateResponse, error)
ContainerExecInspect(ctx context.Context, execID string) (types.ContainerExecInspect, error)
ContainerExecResize(ctx context.Context, execID string, options types.ResizeOptions) error
ContainerExecStart(ctx context.Context, execID string, config types.ExecStartCheck) error
ContainerExport(ctx context.Context, container string) (io.ReadCloser, error)
ContainerInspect(ctx context.Context, container string) (types.ContainerJSON, error)
ContainerInspectWithRaw(ctx context.Context, container string, getSize bool) (types.ContainerJSON, []byte, error)
ContainerKill(ctx context.Context, container, signal string) error
ContainerList(ctx context.Context, options types.ContainerListOptions) ([]types.Container, error)
ContainerLogs(ctx context.Context, container string, options types.ContainerLogsOptions) (io.ReadCloser, error)
ContainerPause(ctx context.Context, container string) error
ContainerRemove(ctx context.Context, container string, options types.ContainerRemoveOptions) error
ContainerRename(ctx context.Context, container, newContainerName string) error
ContainerResize(ctx context.Context, container string, options types.ResizeOptions) error
ContainerRestart(ctx context.Context, container string, timeout *time.Duration) error
ContainerStatPath(ctx context.Context, container, path string) (types.ContainerPathStat, error)
ContainerStats(ctx context.Context, container string, stream bool) (io.ReadCloser, error)
ContainerStart(ctx context.Context, container string, options types.ContainerStartOptions) error
ContainerStop(ctx context.Context, container string, timeout *time.Duration) error
ContainerTop(ctx context.Context, container string, arguments []string) (types.ContainerProcessList, error)
ContainerUnpause(ctx context.Context, container string) error
ContainerUpdate(ctx context.Context, container string, updateConfig container.UpdateConfig) (types.ContainerUpdateResponse, error)
ContainerWait(ctx context.Context, container string) (int, error)
CopyFromContainer(ctx context.Context, container, srcPath string) (io.ReadCloser, types.ContainerPathStat, error)
CopyToContainer(ctx context.Context, container, path string, content io.Reader, options types.CopyToContainerOptions) error
}
// ImageAPIClient defines API client methods for the images
type ImageAPIClient interface {
ImageBuild(ctx context.Context, context io.Reader, options types.ImageBuildOptions) (types.ImageBuildResponse, error)
ImageCreate(ctx context.Context, parentReference string, options types.ImageCreateOptions) (io.ReadCloser, error)
ImageHistory(ctx context.Context, image string) ([]types.ImageHistory, error)
ImageImport(ctx context.Context, source types.ImageImportSource, ref string, options types.ImageImportOptions) (io.ReadCloser, error)
ImageInspectWithRaw(ctx context.Context, image string) (types.ImageInspect, []byte, error)
ImageList(ctx context.Context, options types.ImageListOptions) ([]types.Image, error)
ImageLoad(ctx context.Context, input io.Reader, quiet bool) (types.ImageLoadResponse, error)
ImagePull(ctx context.Context, ref string, options types.ImagePullOptions) (io.ReadCloser, error)
ImagePush(ctx context.Context, ref string, options types.ImagePushOptions) (io.ReadCloser, error)
ImageRemove(ctx context.Context, image string, options types.ImageRemoveOptions) ([]types.ImageDelete, error)
ImageSearch(ctx context.Context, term string, options types.ImageSearchOptions) ([]registry.SearchResult, error)
ImageSave(ctx context.Context, images []string) (io.ReadCloser, error)
ImageTag(ctx context.Context, image, ref string) error
}
// NetworkAPIClient defines API client methods for the networks
type NetworkAPIClient interface {
NetworkConnect(ctx context.Context, networkID, container string, config *network.EndpointSettings) error
NetworkCreate(ctx context.Context, name string, options types.NetworkCreate) (types.NetworkCreateResponse, error)
NetworkDisconnect(ctx context.Context, networkID, container string, force bool) error
NetworkInspect(ctx context.Context, networkID string) (types.NetworkResource, error)
NetworkInspectWithRaw(ctx context.Context, networkID string) (types.NetworkResource, []byte, error)
NetworkList(ctx context.Context, options types.NetworkListOptions) ([]types.NetworkResource, error)
NetworkRemove(ctx context.Context, networkID string) error
}
// NodeAPIClient defines API client methods for the nodes
type NodeAPIClient interface {
NodeInspectWithRaw(ctx context.Context, nodeID string) (swarm.Node, []byte, error)
NodeList(ctx context.Context, options types.NodeListOptions) ([]swarm.Node, error)
NodeRemove(ctx context.Context, nodeID string, options types.NodeRemoveOptions) error
NodeUpdate(ctx context.Context, nodeID string, version swarm.Version, node swarm.NodeSpec) error
}
// ServiceAPIClient defines API client methods for the services
type ServiceAPIClient interface {
ServiceCreate(ctx context.Context, service swarm.ServiceSpec, options types.ServiceCreateOptions) (types.ServiceCreateResponse, error)
ServiceInspectWithRaw(ctx context.Context, serviceID string) (swarm.Service, []byte, error)
ServiceList(ctx context.Context, options types.ServiceListOptions) ([]swarm.Service, error)
ServiceRemove(ctx context.Context, serviceID string) error
ServiceUpdate(ctx context.Context, serviceID string, version swarm.Version, service swarm.ServiceSpec, options types.ServiceUpdateOptions) error
TaskInspectWithRaw(ctx context.Context, taskID string) (swarm.Task, []byte, error)
TaskList(ctx context.Context, options types.TaskListOptions) ([]swarm.Task, error)
}
// SwarmAPIClient defines API client methods for the swarm
type SwarmAPIClient interface {
SwarmInit(ctx context.Context, req swarm.InitRequest) (string, error)
SwarmJoin(ctx context.Context, req swarm.JoinRequest) error
SwarmLeave(ctx context.Context, force bool) error
SwarmInspect(ctx context.Context) (swarm.Swarm, error)
SwarmUpdate(ctx context.Context, version swarm.Version, swarm swarm.Spec, flags swarm.UpdateFlags) error
}
// SystemAPIClient defines API client methods for the system
type SystemAPIClient interface {
Events(ctx context.Context, options types.EventsOptions) (io.ReadCloser, error)
Info(ctx context.Context) (types.Info, error)
RegistryLogin(ctx context.Context, auth types.AuthConfig) (types.AuthResponse, error)
}
// VolumeAPIClient defines API client methods for the volumes
type VolumeAPIClient interface {
VolumeCreate(ctx context.Context, options types.VolumeCreateRequest) (types.Volume, error)
VolumeInspect(ctx context.Context, volumeID string) (types.Volume, error)
VolumeInspectWithRaw(ctx context.Context, volumeID string) (types.Volume, []byte, error)
VolumeList(ctx context.Context, filter filters.Args) (types.VolumesListResponse, error)
VolumeRemove(ctx context.Context, volumeID string, force bool) error
}

View file

@ -0,0 +1,37 @@
// +build experimental
package client
import (
"github.com/docker/engine-api/types"
"golang.org/x/net/context"
)
// APIClient is an interface that clients that talk with a docker server must implement.
type APIClient interface {
CommonAPIClient
CheckpointAPIClient
PluginAPIClient
}
// CheckpointAPIClient defines API client methods for the checkpoints
type CheckpointAPIClient interface {
CheckpointCreate(ctx context.Context, container string, options types.CheckpointCreateOptions) error
CheckpointDelete(ctx context.Context, container string, checkpointID string) error
CheckpointList(ctx context.Context, container string) ([]types.Checkpoint, error)
}
// PluginAPIClient defines API client methods for the plugins
type PluginAPIClient interface {
PluginList(ctx context.Context) (types.PluginsListResponse, error)
PluginRemove(ctx context.Context, name string, options types.PluginRemoveOptions) error
PluginEnable(ctx context.Context, name string) error
PluginDisable(ctx context.Context, name string) error
PluginInstall(ctx context.Context, name string, options types.PluginInstallOptions) error
PluginPush(ctx context.Context, name string, registryAuth string) error
PluginSet(ctx context.Context, name string, args []string) error
PluginInspectWithRaw(ctx context.Context, name string) (*types.Plugin, []byte, error)
}
// Ensure that Client always implements APIClient.
var _ APIClient = &Client{}

View file

@ -0,0 +1,11 @@
// +build !experimental
package client
// APIClient is an interface that clients that talk with a docker server must implement.
type APIClient interface {
CommonAPIClient
}
// Ensure that Client always implements APIClient.
var _ APIClient = &Client{}

28
vendor/github.com/docker/engine-api/client/login.go generated vendored Normal file
View file

@ -0,0 +1,28 @@
package client
import (
"encoding/json"
"net/http"
"net/url"
"github.com/docker/engine-api/types"
"golang.org/x/net/context"
)
// RegistryLogin authenticates the docker server with a given docker registry.
// It returns UnauthorizerError when the authentication fails.
func (cli *Client) RegistryLogin(ctx context.Context, auth types.AuthConfig) (types.AuthResponse, error) {
resp, err := cli.post(ctx, "/auth", url.Values{}, auth, nil)
if resp.statusCode == http.StatusUnauthorized {
return types.AuthResponse{}, unauthorizedError{err}
}
if err != nil {
return types.AuthResponse{}, err
}
var response types.AuthResponse
err = json.NewDecoder(resp.body).Decode(&response)
ensureReaderClosed(resp)
return response, err
}

Some files were not shown because too many files have changed in this diff Show more