pax_global_header00006660000000000000000000000064127462021050014511gustar00rootroot0000000000000052 comment=3d1601b9d2436a70b0dfc045a23f6503d19195df engine-api-0.4.0/000077500000000000000000000000001274620210500135265ustar00rootroot00000000000000engine-api-0.4.0/.travis.yml000066400000000000000000000002151274620210500156350ustar00rootroot00000000000000--- language: go sudo: false notifications: email: false go: - 1.6 install: make deps script: make validate && make test engine-api-0.4.0/CHANGELOG.md000066400000000000000000000064641274620210500153510ustar00rootroot00000000000000# 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. engine-api-0.4.0/CONTRIBUTING.md000066400000000000000000000040551274620210500157630ustar00rootroot00000000000000# Contributing to Docker ### Sign your work The sign-off is a simple line at the end of the explanation for the patch. Your signature certifies that you wrote the patch or otherwise have the right to pass it on as an open-source patch. The rules are pretty simple: if you can certify the below (from [developercertificate.org](http://developercertificate.org/)): ``` Developer Certificate of Origin Version 1.1 Copyright (C) 2004, 2006 The Linux Foundation and its contributors. 660 York Street, Suite 102, San Francisco, CA 94110 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Developer's Certificate of Origin 1.1 By making a contribution to this project, I certify that: (a) The contribution was created in whole or in part by me and I have the right to submit it under the open source license indicated in the file; or (b) The contribution is based upon previous work that, to the best of my knowledge, is covered under an appropriate open source license and I have the right under that license to submit that work with modifications, whether created in whole or in part by me, under the same open source license (unless I am permitted to submit under a different license), as indicated in the file; or (c) The contribution was provided directly to me by some other person who certified (a), (b) or (c) and I have not modified it. (d) I understand and agree that this project and the contribution are public and that a record of the contribution (including all personal information I submit with it, including my sign-off) is maintained indefinitely and may be redistributed consistent with this project or the open source license(s) involved. ``` Then you just add a line to every git commit message: Signed-off-by: Joe Smith Use your real name (sorry, no pseudonyms or anonymous contributions.) If you set your `user.name` and `user.email` git configs, you can sign your commit automatically with `git commit -s`. engine-api-0.4.0/LICENSE000066400000000000000000000250151274620210500145360ustar00rootroot00000000000000 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. engine-api-0.4.0/MAINTAINERS000066400000000000000000000062431274620210500152300ustar00rootroot00000000000000# 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" engine-api-0.4.0/Makefile000066400000000000000000000005521274620210500151700ustar00rootroot00000000000000.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 engine-api-0.4.0/README.md000066400000000000000000000042761274620210500150160ustar00rootroot00000000000000[![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. engine-api-0.4.0/appveyor.yml000066400000000000000000000012741274620210500161220ustar00rootroot00000000000000version: "{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 ./... engine-api-0.4.0/client/000077500000000000000000000000001274620210500150045ustar00rootroot00000000000000engine-api-0.4.0/client/checkpoint_create.go000066400000000000000000000006561274620210500210140ustar00rootroot00000000000000package 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 } engine-api-0.4.0/client/checkpoint_create_test.go000066400000000000000000000035711274620210500220520ustar00rootroot00000000000000package 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) } } engine-api-0.4.0/client/checkpoint_delete.go000066400000000000000000000006031274620210500210030ustar00rootroot00000000000000package 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 } engine-api-0.4.0/client/checkpoint_delete_test.go000066400000000000000000000023171274620210500220460ustar00rootroot00000000000000package 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) } } engine-api-0.4.0/client/checkpoint_list.go000066400000000000000000000010521274620210500205130ustar00rootroot00000000000000package 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 } engine-api-0.4.0/client/checkpoint_list_test.go000066400000000000000000000025221274620210500215550ustar00rootroot00000000000000package 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) } } engine-api-0.4.0/client/client.go000066400000000000000000000104631274620210500166150ustar00rootroot00000000000000package 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 { // 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{ 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 } engine-api-0.4.0/client/client_darwin.go000066400000000000000000000002121274620210500201500ustar00rootroot00000000000000package client // DefaultDockerHost defines os specific default if DOCKER_HOST is unset const DefaultDockerHost = "tcp://127.0.0.1:2375" engine-api-0.4.0/client/client_mock_test.go000066400000000000000000000034331274620210500206640ustar00rootroot00000000000000package 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 } } engine-api-0.4.0/client/client_test.go000066400000000000000000000131371274620210500176550ustar00rootroot00000000000000package 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]) } } engine-api-0.4.0/client/client_unix.go000066400000000000000000000002721274620210500176550ustar00rootroot00000000000000// +build linux freebsd solaris openbsd package client // DefaultDockerHost defines os specific default if DOCKER_HOST is unset const DefaultDockerHost = "unix:///var/run/docker.sock" engine-api-0.4.0/client/client_windows.go000066400000000000000000000002241274620210500203610ustar00rootroot00000000000000package client // DefaultDockerHost defines os specific default if DOCKER_HOST is unset const DefaultDockerHost = "npipe:////./pipe/docker_engine" engine-api-0.4.0/client/container_attach.go000066400000000000000000000017321274620210500206440ustar00rootroot00000000000000package 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) } engine-api-0.4.0/client/container_commit.go000066400000000000000000000030041274620210500206620ustar00rootroot00000000000000package 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 } engine-api-0.4.0/client/container_commit_test.go000066400000000000000000000061411274620210500217260ustar00rootroot00000000000000package 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) } } engine-api-0.4.0/client/container_copy.go000066400000000000000000000066121274620210500203540ustar00rootroot00000000000000package 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 } engine-api-0.4.0/client/container_copy_test.go000066400000000000000000000172661274620210500214220ustar00rootroot00000000000000package 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)) } } engine-api-0.4.0/client/container_create.go000066400000000000000000000025411274620210500206420ustar00rootroot00000000000000package 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 != nil && 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 } engine-api-0.4.0/client/container_create_test.go000066400000000000000000000044601274620210500217030ustar00rootroot00000000000000package 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) } } engine-api-0.4.0/client/container_diff.go000066400000000000000000000011271274620210500203060ustar00rootroot00000000000000package 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 } engine-api-0.4.0/client/container_diff_test.go000066400000000000000000000025661274620210500213550ustar00rootroot00000000000000package 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) } } engine-api-0.4.0/client/container_exec.go000066400000000000000000000036301274620210500203230ustar00rootroot00000000000000package 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 } engine-api-0.4.0/client/container_exec_test.go000066400000000000000000000110171274620210500213600ustar00rootroot00000000000000package 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) } } engine-api-0.4.0/client/container_export.go000066400000000000000000000007431274620210500207220ustar00rootroot00000000000000package 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 } engine-api-0.4.0/client/container_export_test.go000066400000000000000000000024001274620210500217510ustar00rootroot00000000000000package 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)) } } engine-api-0.4.0/client/container_inspect.go000066400000000000000000000030541274620210500210440ustar00rootroot00000000000000package 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 } engine-api-0.4.0/client/container_inspect_test.go000066400000000000000000000062521274620210500221060ustar00rootroot00000000000000package 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`") } } engine-api-0.4.0/client/container_kill.go000066400000000000000000000006661274620210500203400ustar00rootroot00000000000000package 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 } engine-api-0.4.0/client/container_kill_test.go000066400000000000000000000023411274620210500213670ustar00rootroot00000000000000package 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) } } engine-api-0.4.0/client/container_list.go000066400000000000000000000021501274620210500203460ustar00rootroot00000000000000package 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 } engine-api-0.4.0/client/container_list_test.go000066400000000000000000000052251274620210500214130ustar00rootroot00000000000000package 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) } } engine-api-0.4.0/client/container_logs.go000066400000000000000000000020461274620210500203430ustar00rootroot00000000000000package 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 } engine-api-0.4.0/client/container_logs_test.go000066400000000000000000000064401274620210500214040ustar00rootroot00000000000000package 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) } } engine-api-0.4.0/client/container_pause.go000066400000000000000000000005251274620210500205140ustar00rootroot00000000000000package 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 } engine-api-0.4.0/client/container_pause_test.go000066400000000000000000000020341274620210500215500ustar00rootroot00000000000000package 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) } } engine-api-0.4.0/client/container_remove.go000066400000000000000000000011161274620210500206710ustar00rootroot00000000000000package 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 } engine-api-0.4.0/client/container_remove_test.go000066400000000000000000000032171274620210500217340ustar00rootroot00000000000000package 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) } } engine-api-0.4.0/client/container_rename.go000066400000000000000000000006331274620210500206460ustar00rootroot00000000000000package 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 } engine-api-0.4.0/client/container_rename_test.go000066400000000000000000000023441274620210500217060ustar00rootroot00000000000000package 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) } } engine-api-0.4.0/client/container_resize.go000066400000000000000000000017011274620210500206750ustar00rootroot00000000000000package 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 } engine-api-0.4.0/client/container_resize_test.go000066400000000000000000000043241274620210500217400ustar00rootroot00000000000000package 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 } } engine-api-0.4.0/client/container_restart.go000066400000000000000000000012041274620210500210560ustar00rootroot00000000000000package 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 } engine-api-0.4.0/client/container_restart_test.go000066400000000000000000000024241274620210500221220ustar00rootroot00000000000000package 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) } } engine-api-0.4.0/client/container_start.go000066400000000000000000000010321274620210500205260ustar00rootroot00000000000000package 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 } engine-api-0.4.0/client/container_start_test.go000066400000000000000000000033071274620210500215740ustar00rootroot00000000000000package 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) } } engine-api-0.4.0/client/container_stats.go000066400000000000000000000010461274620210500205340ustar00rootroot00000000000000package 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 } engine-api-0.4.0/client/container_stats_test.go000066400000000000000000000033031274620210500215710ustar00rootroot00000000000000package 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)) } } } engine-api-0.4.0/client/container_stop.go000066400000000000000000000011411274620210500203570ustar00rootroot00000000000000package 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 } engine-api-0.4.0/client/container_stop_test.go000066400000000000000000000024051274620210500214220ustar00rootroot00000000000000package 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) } } engine-api-0.4.0/client/container_top.go000066400000000000000000000012721274620210500202010ustar00rootroot00000000000000package 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 } engine-api-0.4.0/client/container_top_test.go000066400000000000000000000036651274620210500212500ustar00rootroot00000000000000package 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) } } engine-api-0.4.0/client/container_unpause.go000066400000000000000000000005071274620210500210570ustar00rootroot00000000000000package 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 } engine-api-0.4.0/client/container_unpause_test.go000066400000000000000000000020461274620210500221160ustar00rootroot00000000000000package 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) } } engine-api-0.4.0/client/container_update.go000066400000000000000000000006271274620210500206640ustar00rootroot00000000000000package client import ( "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) error { resp, err := cli.post(ctx, "/containers/"+containerID+"/update", nil, updateConfig, nil) ensureReaderClosed(resp) return err } engine-api-0.4.0/client/container_update_test.go000066400000000000000000000024021274620210500217140ustar00rootroot00000000000000package client import ( "bytes" "fmt" "io/ioutil" "net/http" "strings" "testing" "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) } return &http.Response{ StatusCode: http.StatusOK, Body: ioutil.NopCloser(bytes.NewReader([]byte(""))), }, 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) } } engine-api-0.4.0/client/container_wait.go000066400000000000000000000011631274620210500203420ustar00rootroot00000000000000package 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 } engine-api-0.4.0/client/container_wait_test.go000066400000000000000000000032161274620210500214020ustar00rootroot00000000000000package 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) } } engine-api-0.4.0/client/errors.go000066400000000000000000000132131274620210500166470ustar00rootroot00000000000000package 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?") 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 } engine-api-0.4.0/client/events.go000066400000000000000000000021171274620210500166400ustar00rootroot00000000000000package 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 } engine-api-0.4.0/client/events_test.go000066400000000000000000000061531274620210500177030ustar00rootroot00000000000000package 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)) } } } engine-api-0.4.0/client/hijack.go000066400000000000000000000120421274620210500165630ustar00rootroot00000000000000package 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) } engine-api-0.4.0/client/image_build.go000066400000000000000000000060171274620210500176000ustar00rootroot00000000000000package 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 !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 } engine-api-0.4.0/client/image_build_test.go000066400000000000000000000143311274620210500206350ustar00rootroot00000000000000package 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) } } } engine-api-0.4.0/client/image_create.go000066400000000000000000000017161274620210500177450ustar00rootroot00000000000000package 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) } engine-api-0.4.0/client/image_create_test.go000066400000000000000000000045421274620210500210040ustar00rootroot00000000000000package 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) } } engine-api-0.4.0/client/image_history.go000066400000000000000000000010631274620210500201760ustar00rootroot00000000000000package 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 } engine-api-0.4.0/client/image_history_test.go000066400000000000000000000026321274620210500212400ustar00rootroot00000000000000package 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) } } engine-api-0.4.0/client/image_import.go000066400000000000000000000016671274620210500200210ustar00rootroot00000000000000package 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 } engine-api-0.4.0/client/image_import_test.go000066400000000000000000000050471274620210500210540ustar00rootroot00000000000000package 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)) } } engine-api-0.4.0/client/image_inspect.go000066400000000000000000000017121274620210500201430ustar00rootroot00000000000000package client import ( "bytes" "encoding/json" "io/ioutil" "net/http" "net/url" "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, getSize bool) (types.ImageInspect, []byte, error) { query := url.Values{} if getSize { query.Set("size", "1") } serverResp, err := cli.get(ctx, "/images/"+imageID+"/json", query, 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 } engine-api-0.4.0/client/image_inspect_test.go000066400000000000000000000050131274620210500212000ustar00rootroot00000000000000package 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", true) 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", true) 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"} inspectCases := []struct { size bool expectedQueryParams map[string]string }{ { size: true, expectedQueryParams: map[string]string{ "size": "1", }, }, { size: false, expectedQueryParams: map[string]string{ "size": "", }, }, } for _, inspectCase := range inspectCases { 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 inspectCase.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.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", inspectCase.size) 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) } } } engine-api-0.4.0/client/image_list.go000066400000000000000000000017301274620210500174510ustar00rootroot00000000000000package 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 } engine-api-0.4.0/client/image_list_test.go000066400000000000000000000055101274620210500205100ustar00rootroot00000000000000package 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) } } } engine-api-0.4.0/client/image_load.go000066400000000000000000000014421274620210500174150ustar00rootroot00000000000000package 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 } engine-api-0.4.0/client/image_load_test.go000066400000000000000000000051701274620210500204560ustar00rootroot00000000000000package 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)) } } } engine-api-0.4.0/client/image_pull.go000066400000000000000000000025741274620210500174610ustar00rootroot00000000000000package 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 } engine-api-0.4.0/client/image_pull_test.go000066400000000000000000000136731274620210500205220ustar00rootroot00000000000000package 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)) } } } engine-api-0.4.0/client/image_push.go000066400000000000000000000032501274620210500174540ustar00rootroot00000000000000package 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) } engine-api-0.4.0/client/image_push_test.go000066400000000000000000000131331274620210500205140ustar00rootroot00000000000000package 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)) } } } engine-api-0.4.0/client/image_remove.go000066400000000000000000000012351274620210500177730ustar00rootroot00000000000000package 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 } engine-api-0.4.0/client/image_remove_test.go000066400000000000000000000045121274620210500210330ustar00rootroot00000000000000package 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) } } } engine-api-0.4.0/client/image_save.go000066400000000000000000000007431274620210500174370ustar00rootroot00000000000000package 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 } engine-api-0.4.0/client/image_save_test.go000066400000000000000000000030421274620210500204710ustar00rootroot00000000000000package 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)) } } engine-api-0.4.0/client/image_search.go000066400000000000000000000027731274620210500177530ustar00rootroot00000000000000package 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) } engine-api-0.4.0/client/image_search_test.go000066400000000000000000000117711274620210500210100ustar00rootroot00000000000000package 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) } } engine-api-0.4.0/client/image_tag.go000066400000000000000000000015711274620210500172540ustar00rootroot00000000000000package 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 } engine-api-0.4.0/client/image_tag_test.go000066400000000000000000000065161274620210500203170ustar00rootroot00000000000000package 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) } } } engine-api-0.4.0/client/info.go000066400000000000000000000010721274620210500162660ustar00rootroot00000000000000package 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 } engine-api-0.4.0/client/info_test.go000066400000000000000000000034621274620210500173320ustar00rootroot00000000000000package 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) } } engine-api-0.4.0/client/interface.go000066400000000000000000000203501274620210500172730ustar00rootroot00000000000000package 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) 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, getSize bool) (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) 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) error } engine-api-0.4.0/client/interface_experimental.go000066400000000000000000000025451274620210500220560ustar00rootroot00000000000000// +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) 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 PluginInspect(ctx context.Context, name string) (*types.Plugin, error) } // Ensure that Client always implements APIClient. var _ APIClient = &Client{} engine-api-0.4.0/client/interface_stable.go000066400000000000000000000004001274620210500206170ustar00rootroot00000000000000// +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{} engine-api-0.4.0/client/login.go000066400000000000000000000013671274620210500164520ustar00rootroot00000000000000package 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 != nil && 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 } engine-api-0.4.0/client/network_connect.go000066400000000000000000000010541274620210500205350ustar00rootroot00000000000000package client import ( "github.com/docker/engine-api/types" "github.com/docker/engine-api/types/network" "golang.org/x/net/context" ) // NetworkConnect connects a container to an existent network in the docker host. func (cli *Client) NetworkConnect(ctx context.Context, networkID, containerID string, config *network.EndpointSettings) error { nc := types.NetworkConnect{ Container: containerID, EndpointConfig: config, } resp, err := cli.post(ctx, "/networks/"+networkID+"/connect", nil, nc, nil) ensureReaderClosed(resp) return err } engine-api-0.4.0/client/network_connect_test.go000066400000000000000000000055371274620210500216060ustar00rootroot00000000000000package client import ( "bytes" "encoding/json" "fmt" "io/ioutil" "net/http" "strings" "testing" "golang.org/x/net/context" "github.com/docker/engine-api/types" "github.com/docker/engine-api/types/network" ) func TestNetworkConnectError(t *testing.T) { client := &Client{ transport: newMockClient(nil, errorMock(http.StatusInternalServerError, "Server error")), } err := client.NetworkConnect(context.Background(), "network_id", "container_id", nil) if err == nil || err.Error() != "Error response from daemon: Server error" { t.Fatalf("expected a Server Error, got %v", err) } } func TestNetworkConnectEmptyNilEndpointSettings(t *testing.T) { expectedURL := "/networks/network_id/connect" 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) } var connect types.NetworkConnect if err := json.NewDecoder(req.Body).Decode(&connect); err != nil { return nil, err } if connect.Container != "container_id" { return nil, fmt.Errorf("expected 'container_id', got %s", connect.Container) } if connect.EndpointConfig != nil { return nil, fmt.Errorf("expected connect.EndpointConfig to be nil, got %v", connect.EndpointConfig) } return &http.Response{ StatusCode: http.StatusOK, Body: ioutil.NopCloser(bytes.NewReader([]byte(""))), }, nil }), } err := client.NetworkConnect(context.Background(), "network_id", "container_id", nil) if err != nil { t.Fatal(err) } } func TestNetworkConnect(t *testing.T) { expectedURL := "/networks/network_id/connect" 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) } var connect types.NetworkConnect if err := json.NewDecoder(req.Body).Decode(&connect); err != nil { return nil, err } if connect.Container != "container_id" { return nil, fmt.Errorf("expected 'container_id', got %s", connect.Container) } if connect.EndpointConfig.NetworkID != "NetworkID" { return nil, fmt.Errorf("expected 'NetworkID', got %s", connect.EndpointConfig.NetworkID) } return &http.Response{ StatusCode: http.StatusOK, Body: ioutil.NopCloser(bytes.NewReader([]byte(""))), }, nil }), } err := client.NetworkConnect(context.Background(), "network_id", "container_id", &network.EndpointSettings{ NetworkID: "NetworkID", }) if err != nil { t.Fatal(err) } } engine-api-0.4.0/client/network_create.go000066400000000000000000000012631274620210500203510ustar00rootroot00000000000000package client import ( "encoding/json" "github.com/docker/engine-api/types" "golang.org/x/net/context" ) // NetworkCreate creates a new network in the docker host. func (cli *Client) NetworkCreate(ctx context.Context, name string, options types.NetworkCreate) (types.NetworkCreateResponse, error) { networkCreateRequest := types.NetworkCreateRequest{ NetworkCreate: options, Name: name, } var response types.NetworkCreateResponse serverResp, err := cli.post(ctx, "/networks/create", nil, networkCreateRequest, nil) if err != nil { return response, err } json.NewDecoder(serverResp.body).Decode(&response) ensureReaderClosed(serverResp) return response, err } engine-api-0.4.0/client/network_create_test.go000066400000000000000000000035101274620210500214050ustar00rootroot00000000000000package client import ( "bytes" "encoding/json" "fmt" "io/ioutil" "net/http" "strings" "testing" "github.com/docker/engine-api/types" "golang.org/x/net/context" ) func TestNetworkCreateError(t *testing.T) { client := &Client{ transport: newMockClient(nil, errorMock(http.StatusInternalServerError, "Server error")), } _, err := client.NetworkCreate(context.Background(), "mynetwork", types.NetworkCreate{}) if err == nil || err.Error() != "Error response from daemon: Server error" { t.Fatalf("expected a Server Error, got %v", err) } } func TestNetworkCreate(t *testing.T) { expectedURL := "/networks/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) } if req.Method != "POST" { return nil, fmt.Errorf("expected POST method, got %s", req.Method) } content, err := json.Marshal(types.NetworkCreateResponse{ ID: "network_id", Warning: "warning", }) if err != nil { return nil, err } return &http.Response{ StatusCode: http.StatusOK, Body: ioutil.NopCloser(bytes.NewReader(content)), }, nil }), } networkResponse, err := client.NetworkCreate(context.Background(), "mynetwork", types.NetworkCreate{ CheckDuplicate: true, Driver: "mydriver", EnableIPv6: true, Internal: true, Options: map[string]string{ "opt-key": "opt-value", }, }) if err != nil { t.Fatal(err) } if networkResponse.ID != "network_id" { t.Fatalf("expected networkResponse.ID to be 'network_id', got %s", networkResponse.ID) } if networkResponse.Warning != "warning" { t.Fatalf("expected networkResponse.Warning to be 'warning', got %s", networkResponse.Warning) } } engine-api-0.4.0/client/network_disconnect.go000066400000000000000000000007421274620210500212400ustar00rootroot00000000000000package client import ( "github.com/docker/engine-api/types" "golang.org/x/net/context" ) // NetworkDisconnect disconnects a container from an existent network in the docker host. func (cli *Client) NetworkDisconnect(ctx context.Context, networkID, containerID string, force bool) error { nd := types.NetworkDisconnect{Container: containerID, Force: force} resp, err := cli.post(ctx, "/networks/"+networkID+"/disconnect", nil, nd, nil) ensureReaderClosed(resp) return err } engine-api-0.4.0/client/network_disconnect_test.go000066400000000000000000000032041274620210500222730ustar00rootroot00000000000000package client import ( "bytes" "encoding/json" "fmt" "io/ioutil" "net/http" "strings" "testing" "github.com/docker/engine-api/types" "golang.org/x/net/context" ) func TestNetworkDisconnectError(t *testing.T) { client := &Client{ transport: newMockClient(nil, errorMock(http.StatusInternalServerError, "Server error")), } err := client.NetworkDisconnect(context.Background(), "network_id", "container_id", false) if err == nil || err.Error() != "Error response from daemon: Server error" { t.Fatalf("expected a Server Error, got %v", err) } } func TestNetworkDisconnect(t *testing.T) { expectedURL := "/networks/network_id/disconnect" 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) } var disconnect types.NetworkDisconnect if err := json.NewDecoder(req.Body).Decode(&disconnect); err != nil { return nil, err } if disconnect.Container != "container_id" { return nil, fmt.Errorf("expected 'container_id', got %s", disconnect.Container) } if !disconnect.Force { return nil, fmt.Errorf("expected Force to be true, got %v", disconnect.Force) } return &http.Response{ StatusCode: http.StatusOK, Body: ioutil.NopCloser(bytes.NewReader([]byte(""))), }, nil }), } err := client.NetworkDisconnect(context.Background(), "network_id", "container_id", true) if err != nil { t.Fatal(err) } } engine-api-0.4.0/client/network_inspect.go000066400000000000000000000023061274620210500205520ustar00rootroot00000000000000package client import ( "bytes" "encoding/json" "io/ioutil" "net/http" "github.com/docker/engine-api/types" "golang.org/x/net/context" ) // NetworkInspect returns the information for a specific network configured in the docker host. func (cli *Client) NetworkInspect(ctx context.Context, networkID string) (types.NetworkResource, error) { networkResource, _, err := cli.NetworkInspectWithRaw(ctx, networkID) return networkResource, err } // NetworkInspectWithRaw returns the information for a specific network configured in the docker host and its raw representation. func (cli *Client) NetworkInspectWithRaw(ctx context.Context, networkID string) (types.NetworkResource, []byte, error) { var networkResource types.NetworkResource resp, err := cli.get(ctx, "/networks/"+networkID, nil, nil) if err != nil { if resp.statusCode == http.StatusNotFound { return networkResource, nil, networkNotFoundError{networkID} } return networkResource, nil, err } defer ensureReaderClosed(resp) body, err := ioutil.ReadAll(resp.body) if err != nil { return networkResource, nil, err } rdr := bytes.NewReader(body) err = json.NewDecoder(rdr).Decode(&networkResource) return networkResource, body, err } engine-api-0.4.0/client/network_inspect_test.go000066400000000000000000000033351274620210500216140ustar00rootroot00000000000000package client import ( "bytes" "encoding/json" "fmt" "io/ioutil" "net/http" "strings" "testing" "github.com/docker/engine-api/types" "golang.org/x/net/context" ) func TestNetworkInspectError(t *testing.T) { client := &Client{ transport: newMockClient(nil, errorMock(http.StatusInternalServerError, "Server error")), } _, err := client.NetworkInspect(context.Background(), "nothing") if err == nil || err.Error() != "Error response from daemon: Server error" { t.Fatalf("expected a Server Error, got %v", err) } } func TestNetworkInspectContainerNotFound(t *testing.T) { client := &Client{ transport: newMockClient(nil, errorMock(http.StatusNotFound, "Server error")), } _, err := client.NetworkInspect(context.Background(), "unknown") if err == nil || !IsErrNetworkNotFound(err) { t.Fatalf("expected a containerNotFound error, got %v", err) } } func TestNetworkInspect(t *testing.T) { expectedURL := "/networks/network_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 != "GET" { return nil, fmt.Errorf("expected GET method, got %s", req.Method) } content, err := json.Marshal(types.NetworkResource{ Name: "mynetwork", }) if err != nil { return nil, err } return &http.Response{ StatusCode: http.StatusOK, Body: ioutil.NopCloser(bytes.NewReader(content)), }, nil }), } r, err := client.NetworkInspect(context.Background(), "network_id") if err != nil { t.Fatal(err) } if r.Name != "mynetwork" { t.Fatalf("expected `mynetwork`, got %s", r.Name) } } engine-api-0.4.0/client/network_list.go000066400000000000000000000015151274620210500200610ustar00rootroot00000000000000package client import ( "encoding/json" "net/url" "github.com/docker/engine-api/types" "github.com/docker/engine-api/types/filters" "golang.org/x/net/context" ) // NetworkList returns the list of networks configured in the docker host. func (cli *Client) NetworkList(ctx context.Context, options types.NetworkListOptions) ([]types.NetworkResource, error) { query := url.Values{} if options.Filters.Len() > 0 { filterJSON, err := filters.ToParamWithVersion(cli.version, options.Filters) if err != nil { return nil, err } query.Set("filters", filterJSON) } var networkResources []types.NetworkResource resp, err := cli.get(ctx, "/networks", query, nil) if err != nil { return networkResources, err } err = json.NewDecoder(resp.body).Decode(&networkResources) ensureReaderClosed(resp) return networkResources, err } engine-api-0.4.0/client/network_list_test.go000066400000000000000000000053451274620210500211250ustar00rootroot00000000000000package 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 TestNetworkListError(t *testing.T) { client := &Client{ transport: newMockClient(nil, errorMock(http.StatusInternalServerError, "Server error")), } _, err := client.NetworkList(context.Background(), types.NetworkListOptions{ Filters: filters.NewArgs(), }) if err == nil || err.Error() != "Error response from daemon: Server error" { t.Fatalf("expected a Server Error, got %v", err) } } func TestNetworkList(t *testing.T) { expectedURL := "/networks" noDanglingFilters := filters.NewArgs() noDanglingFilters.Add("dangling", "false") danglingFilters := filters.NewArgs() danglingFilters.Add("dangling", "true") labelFilters := filters.NewArgs() labelFilters.Add("label", "label1") labelFilters.Add("label", "label2") listCases := []struct { options types.NetworkListOptions expectedFilters string }{ { options: types.NetworkListOptions{ Filters: filters.NewArgs(), }, expectedFilters: "", }, { options: types.NetworkListOptions{ Filters: noDanglingFilters, }, expectedFilters: `{"dangling":{"false":true}}`, }, { options: types.NetworkListOptions{ Filters: danglingFilters, }, expectedFilters: `{"dangling":{"true":true}}`, }, { options: types.NetworkListOptions{ Filters: labelFilters, }, expectedFilters: `{"label":{"label1":true,"label2":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) } if req.Method != "GET" { return nil, fmt.Errorf("expected GET method, got %s", req.Method) } query := req.URL.Query() actualFilters := query.Get("filters") if actualFilters != listCase.expectedFilters { return nil, fmt.Errorf("filters not set in URL query properly. Expected '%s', got %s", listCase.expectedFilters, actualFilters) } content, err := json.Marshal([]types.NetworkResource{ { Name: "network", Driver: "bridge", }, }) if err != nil { return nil, err } return &http.Response{ StatusCode: http.StatusOK, Body: ioutil.NopCloser(bytes.NewReader(content)), }, nil }), } networkResources, err := client.NetworkList(context.Background(), listCase.options) if err != nil { t.Fatal(err) } if len(networkResources) != 1 { t.Fatalf("expected 1 network resource, got %v", networkResources) } } } engine-api-0.4.0/client/network_remove.go000066400000000000000000000004561274620210500204060ustar00rootroot00000000000000package client import "golang.org/x/net/context" // NetworkRemove removes an existent network from the docker host. func (cli *Client) NetworkRemove(ctx context.Context, networkID string) error { resp, err := cli.delete(ctx, "/networks/"+networkID, nil, nil) ensureReaderClosed(resp) return err } engine-api-0.4.0/client/network_remove_test.go000066400000000000000000000022031274620210500214350ustar00rootroot00000000000000package client import ( "bytes" "fmt" "io/ioutil" "net/http" "strings" "testing" "golang.org/x/net/context" ) func TestNetworkRemoveError(t *testing.T) { client := &Client{ transport: newMockClient(nil, errorMock(http.StatusInternalServerError, "Server error")), } err := client.NetworkRemove(context.Background(), "network_id") if err == nil || err.Error() != "Error response from daemon: Server error" { t.Fatalf("expected a Server Error, got %v", err) } } func TestNetworkRemove(t *testing.T) { expectedURL := "/networks/network_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("body"))), }, nil }), } err := client.NetworkRemove(context.Background(), "network_id") if err != nil { t.Fatal(err) } } engine-api-0.4.0/client/node_inspect.go000066400000000000000000000014411274620210500200050ustar00rootroot00000000000000package client import ( "bytes" "encoding/json" "io/ioutil" "net/http" "github.com/docker/engine-api/types/swarm" "golang.org/x/net/context" ) // NodeInspectWithRaw returns the node information. func (cli *Client) NodeInspectWithRaw(ctx context.Context, nodeID string) (swarm.Node, []byte, error) { serverResp, err := cli.get(ctx, "/nodes/"+nodeID, nil, nil) if err != nil { if serverResp.statusCode == http.StatusNotFound { return swarm.Node{}, nil, nodeNotFoundError{nodeID} } return swarm.Node{}, nil, err } defer ensureReaderClosed(serverResp) body, err := ioutil.ReadAll(serverResp.body) if err != nil { return swarm.Node{}, nil, err } var response swarm.Node rdr := bytes.NewReader(body) err = json.NewDecoder(rdr).Decode(&response) return response, body, err } engine-api-0.4.0/client/node_inspect_test.go000066400000000000000000000031761274620210500210530ustar00rootroot00000000000000package client import ( "bytes" "encoding/json" "fmt" "io/ioutil" "net/http" "strings" "testing" "github.com/docker/engine-api/types/swarm" "golang.org/x/net/context" ) func TestNodeInspectError(t *testing.T) { client := &Client{ transport: newMockClient(nil, errorMock(http.StatusInternalServerError, "Server error")), } _, _, err := client.NodeInspectWithRaw(context.Background(), "nothing") if err == nil || err.Error() != "Error response from daemon: Server error" { t.Fatalf("expected a Server Error, got %v", err) } } func TestNodeInspectNodeNotFound(t *testing.T) { client := &Client{ transport: newMockClient(nil, errorMock(http.StatusNotFound, "Server error")), } _, _, err := client.NodeInspectWithRaw(context.Background(), "unknown") if err == nil || !IsErrNodeNotFound(err) { t.Fatalf("expected an nodeNotFoundError error, got %v", err) } } func TestNodeInspect(t *testing.T) { expectedURL := "/nodes/node_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) } content, err := json.Marshal(swarm.Node{ ID: "node_id", }) if err != nil { return nil, err } return &http.Response{ StatusCode: http.StatusOK, Body: ioutil.NopCloser(bytes.NewReader(content)), }, nil }), } nodeInspect, _, err := client.NodeInspectWithRaw(context.Background(), "node_id") if err != nil { t.Fatal(err) } if nodeInspect.ID != "node_id" { t.Fatalf("expected `node_id`, got %s", nodeInspect.ID) } } engine-api-0.4.0/client/node_list.go000066400000000000000000000013621274620210500173150ustar00rootroot00000000000000package client import ( "encoding/json" "net/url" "github.com/docker/engine-api/types" "github.com/docker/engine-api/types/filters" "github.com/docker/engine-api/types/swarm" "golang.org/x/net/context" ) // NodeList returns the list of nodes. func (cli *Client) NodeList(ctx context.Context, options types.NodeListOptions) ([]swarm.Node, error) { query := url.Values{} if options.Filter.Len() > 0 { filterJSON, err := filters.ToParam(options.Filter) if err != nil { return nil, err } query.Set("filters", filterJSON) } resp, err := cli.get(ctx, "/nodes", query, nil) if err != nil { return nil, err } var nodes []swarm.Node err = json.NewDecoder(resp.body).Decode(&nodes) ensureReaderClosed(resp) return nodes, err } engine-api-0.4.0/client/node_list_test.go000066400000000000000000000043211274620210500203520ustar00rootroot00000000000000package 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" "github.com/docker/engine-api/types/swarm" "golang.org/x/net/context" ) func TestNodeListError(t *testing.T) { client := &Client{ transport: newMockClient(nil, errorMock(http.StatusInternalServerError, "Server error")), } _, err := client.NodeList(context.Background(), types.NodeListOptions{}) if err == nil || err.Error() != "Error response from daemon: Server error" { t.Fatalf("expected a Server Error, got %v", err) } } func TestNodeList(t *testing.T) { expectedURL := "/nodes" filters := filters.NewArgs() filters.Add("label", "label1") filters.Add("label", "label2") listCases := []struct { options types.NodeListOptions expectedQueryParams map[string]string }{ { options: types.NodeListOptions{}, expectedQueryParams: map[string]string{ "filters": "", }, }, { options: types.NodeListOptions{ Filter: filters, }, expectedQueryParams: map[string]string{ "filters": `{"label":{"label1":true,"label2":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([]swarm.Node{ { ID: "node_id1", }, { ID: "node_id2", }, }) if err != nil { return nil, err } return &http.Response{ StatusCode: http.StatusOK, Body: ioutil.NopCloser(bytes.NewReader(content)), }, nil }), } nodes, err := client.NodeList(context.Background(), listCase.options) if err != nil { t.Fatal(err) } if len(nodes) != 2 { t.Fatalf("expected 2 nodes, got %v", nodes) } } } engine-api-0.4.0/client/node_remove.go000066400000000000000000000003751274620210500176420ustar00rootroot00000000000000package client import "golang.org/x/net/context" // NodeRemove removes a Node. func (cli *Client) NodeRemove(ctx context.Context, nodeID string) error { resp, err := cli.delete(ctx, "/nodes/"+nodeID, nil, nil) ensureReaderClosed(resp) return err } engine-api-0.4.0/client/node_remove_test.go000066400000000000000000000021531274620210500206750ustar00rootroot00000000000000package client import ( "bytes" "fmt" "io/ioutil" "net/http" "strings" "testing" "golang.org/x/net/context" ) func TestNodeRemoveError(t *testing.T) { client := &Client{ transport: newMockClient(nil, errorMock(http.StatusInternalServerError, "Server error")), } err := client.NodeRemove(context.Background(), "node_id") if err == nil || err.Error() != "Error response from daemon: Server error" { t.Fatalf("expected a Server Error, got %v", err) } } func TestNodeRemove(t *testing.T) { expectedURL := "/nodes/node_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("body"))), }, nil }), } err := client.NodeRemove(context.Background(), "node_id") if err != nil { t.Fatal(err) } } engine-api-0.4.0/client/node_update.go000066400000000000000000000007251274620210500176260ustar00rootroot00000000000000package client import ( "net/url" "strconv" "github.com/docker/engine-api/types/swarm" "golang.org/x/net/context" ) // NodeUpdate updates a Node. func (cli *Client) NodeUpdate(ctx context.Context, nodeID string, version swarm.Version, node swarm.NodeSpec) error { query := url.Values{} query.Set("version", strconv.FormatUint(version.Index, 10)) resp, err := cli.post(ctx, "/nodes/"+nodeID+"/update", query, node, nil) ensureReaderClosed(resp) return err } engine-api-0.4.0/client/node_update_test.go000066400000000000000000000023411274620210500206610ustar00rootroot00000000000000package client import ( "bytes" "fmt" "io/ioutil" "net/http" "strings" "testing" "golang.org/x/net/context" "github.com/docker/engine-api/types/swarm" ) func TestNodeUpdateError(t *testing.T) { client := &Client{ transport: newMockClient(nil, errorMock(http.StatusInternalServerError, "Server error")), } err := client.NodeUpdate(context.Background(), "node_id", swarm.Version{}, swarm.NodeSpec{}) if err == nil || err.Error() != "Error response from daemon: Server error" { t.Fatalf("expected a Server Error, got %v", err) } } func TestNodeUpdate(t *testing.T) { expectedURL := "/nodes/node_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) } if req.Method != "POST" { return nil, fmt.Errorf("expected POST method, got %s", req.Method) } return &http.Response{ StatusCode: http.StatusOK, Body: ioutil.NopCloser(bytes.NewReader([]byte("body"))), }, nil }), } err := client.NodeUpdate(context.Background(), "node_id", swarm.Version{}, swarm.NodeSpec{}) if err != nil { t.Fatal(err) } } engine-api-0.4.0/client/plugin_disable.go000066400000000000000000000004561274620210500203210ustar00rootroot00000000000000// +build experimental package client import ( "golang.org/x/net/context" ) // PluginDisable disables a plugin func (cli *Client) PluginDisable(ctx context.Context, name string) error { resp, err := cli.post(ctx, "/plugins/"+name+"/disable", nil, nil, nil) ensureReaderClosed(resp) return err } engine-api-0.4.0/client/plugin_disable_test.go000066400000000000000000000022351274620210500213550ustar00rootroot00000000000000// +build experimental package client import ( "bytes" "fmt" "io/ioutil" "net/http" "strings" "testing" "golang.org/x/net/context" ) func TestPluginDisableError(t *testing.T) { client := &Client{ transport: newMockClient(nil, errorMock(http.StatusInternalServerError, "Server error")), } err := client.PluginDisable(context.Background(), "plugin_name") if err == nil || err.Error() != "Error response from daemon: Server error" { t.Fatalf("expected a Server Error, got %v", err) } } func TestPluginDisable(t *testing.T) { expectedURL := "/plugins/plugin_name/disable" 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) } return &http.Response{ StatusCode: http.StatusOK, Body: ioutil.NopCloser(bytes.NewReader([]byte(""))), }, nil }), } err := client.PluginDisable(context.Background(), "plugin_name") if err != nil { t.Fatal(err) } } engine-api-0.4.0/client/plugin_enable.go000066400000000000000000000004521274620210500201400ustar00rootroot00000000000000// +build experimental package client import ( "golang.org/x/net/context" ) // PluginEnable enables a plugin func (cli *Client) PluginEnable(ctx context.Context, name string) error { resp, err := cli.post(ctx, "/plugins/"+name+"/enable", nil, nil, nil) ensureReaderClosed(resp) return err } engine-api-0.4.0/client/plugin_enable_test.go000066400000000000000000000022301274620210500211730ustar00rootroot00000000000000// +build experimental package client import ( "bytes" "fmt" "io/ioutil" "net/http" "strings" "testing" "golang.org/x/net/context" ) func TestPluginEnableError(t *testing.T) { client := &Client{ transport: newMockClient(nil, errorMock(http.StatusInternalServerError, "Server error")), } err := client.PluginEnable(context.Background(), "plugin_name") if err == nil || err.Error() != "Error response from daemon: Server error" { t.Fatalf("expected a Server Error, got %v", err) } } func TestPluginEnable(t *testing.T) { expectedURL := "/plugins/plugin_name/enable" 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) } return &http.Response{ StatusCode: http.StatusOK, Body: ioutil.NopCloser(bytes.NewReader([]byte(""))), }, nil }), } err := client.PluginEnable(context.Background(), "plugin_name") if err != nil { t.Fatal(err) } } engine-api-0.4.0/client/plugin_inspect.go000066400000000000000000000007331274620210500203610ustar00rootroot00000000000000// +build experimental package client import ( "encoding/json" "github.com/docker/engine-api/types" "golang.org/x/net/context" ) // PluginInspect inspects an existing plugin func (cli *Client) PluginInspect(ctx context.Context, name string) (*types.Plugin, error) { var p types.Plugin resp, err := cli.get(ctx, "/plugins/"+name, nil, nil) if err != nil { return nil, err } err = json.NewDecoder(resp.body).Decode(&p) ensureReaderClosed(resp) return &p, err } engine-api-0.4.0/client/plugin_inspect_test.go000066400000000000000000000025101274620210500214130ustar00rootroot00000000000000// +build experimental 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 TestPluginInspectError(t *testing.T) { client := &Client{ transport: newMockClient(nil, errorMock(http.StatusInternalServerError, "Server error")), } _, err := client.PluginInspect(context.Background(), "nothing") if err == nil || err.Error() != "Error response from daemon: Server error" { t.Fatalf("expected a Server Error, got %v", err) } } func TestPluginInspect(t *testing.T) { expectedURL := "/plugins/plugin_name" 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.Plugin{ ID: "plugin_id", }) if err != nil { return nil, err } return &http.Response{ StatusCode: http.StatusOK, Body: ioutil.NopCloser(bytes.NewReader(content)), }, nil }), } pluginInspect, err := client.PluginInspect(context.Background(), "plugin_name") if err != nil { t.Fatal(err) } if pluginInspect.ID != "plugin_id" { t.Fatalf("expected `plugin_id`, got %s", pluginInspect.ID) } } engine-api-0.4.0/client/plugin_install.go000066400000000000000000000032301274620210500203550ustar00rootroot00000000000000// +build experimental package client import ( "encoding/json" "net/http" "net/url" "github.com/docker/engine-api/types" "golang.org/x/net/context" ) // PluginInstall installs a plugin func (cli *Client) PluginInstall(ctx context.Context, name string, options types.PluginInstallOptions) error { // FIXME(vdemeester) name is a ref, we might want to parse/validate it here. query := url.Values{} query.Set("name", name) resp, err := cli.tryPluginPull(ctx, query, options.RegistryAuth) if resp.statusCode == http.StatusUnauthorized && options.PrivilegeFunc != nil { newAuthHeader, privilegeErr := options.PrivilegeFunc() if privilegeErr != nil { ensureReaderClosed(resp) return privilegeErr } resp, err = cli.tryPluginPull(ctx, query, newAuthHeader) } if err != nil { ensureReaderClosed(resp) return err } var privileges types.PluginPrivileges if err := json.NewDecoder(resp.body).Decode(&privileges); err != nil { ensureReaderClosed(resp) return err } ensureReaderClosed(resp) if !options.AcceptAllPermissions && options.AcceptPermissionsFunc != nil && len(privileges) > 0 { accept, err := options.AcceptPermissionsFunc(privileges) if err != nil { return err } if !accept { resp, _ := cli.delete(ctx, "/plugins/"+name, nil, nil) ensureReaderClosed(resp) return pluginPermissionDenied{name} } } if options.Disabled { return nil } return cli.PluginEnable(ctx, name) } func (cli *Client) tryPluginPull(ctx context.Context, query url.Values, registryAuth string) (*serverResponse, error) { headers := map[string][]string{"X-Registry-Auth": {registryAuth}} return cli.post(ctx, "/plugins/pull", query, nil, headers) } engine-api-0.4.0/client/plugin_list.go000066400000000000000000000007631274620210500176720ustar00rootroot00000000000000// +build experimental package client import ( "encoding/json" "github.com/docker/engine-api/types" "golang.org/x/net/context" ) // PluginList returns the installed plugins func (cli *Client) PluginList(ctx context.Context) (types.PluginsListResponse, error) { var plugins types.PluginsListResponse resp, err := cli.get(ctx, "/plugins", nil, nil) if err != nil { return plugins, err } err = json.NewDecoder(resp.body).Decode(&plugins) ensureReaderClosed(resp) return plugins, err } engine-api-0.4.0/client/plugin_list_test.go000066400000000000000000000024551274620210500207310ustar00rootroot00000000000000// +build experimental 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 TestPluginListError(t *testing.T) { client := &Client{ transport: newMockClient(nil, errorMock(http.StatusInternalServerError, "Server error")), } _, err := client.PluginList(context.Background()) if err == nil || err.Error() != "Error response from daemon: Server error" { t.Fatalf("expected a Server Error, got %v", err) } } func TestPluginList(t *testing.T) { expectedURL := "/plugins" 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.Plugin{ { ID: "plugin_id1", }, { ID: "plugin_id2", }, }) if err != nil { return nil, err } return &http.Response{ StatusCode: http.StatusOK, Body: ioutil.NopCloser(bytes.NewReader(content)), }, nil }), } plugins, err := client.PluginList(context.Background()) if err != nil { t.Fatal(err) } if len(plugins) != 2 { t.Fatalf("expected 2 plugins, got %v", plugins) } } engine-api-0.4.0/client/plugin_push.go000066400000000000000000000006151274620210500176720ustar00rootroot00000000000000// +build experimental package client import ( "golang.org/x/net/context" ) // PluginPush pushes a plugin to a registry func (cli *Client) PluginPush(ctx context.Context, name string, registryAuth string) error { headers := map[string][]string{"X-Registry-Auth": {registryAuth}} resp, err := cli.post(ctx, "/plugins/"+name+"/push", nil, nil, headers) ensureReaderClosed(resp) return err } engine-api-0.4.0/client/plugin_push_test.go000066400000000000000000000025031274620210500207270ustar00rootroot00000000000000// +build experimental package client import ( "bytes" "fmt" "io/ioutil" "net/http" "strings" "testing" "golang.org/x/net/context" ) func TestPluginPushError(t *testing.T) { client := &Client{ transport: newMockClient(nil, errorMock(http.StatusInternalServerError, "Server error")), } err := client.PluginPush(context.Background(), "plugin_name", "") if err == nil || err.Error() != "Error response from daemon: Server error" { t.Fatalf("expected a Server Error, got %v", err) } } func TestPluginPush(t *testing.T) { expectedURL := "/plugins/plugin_name" 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) } auth := req.Header.Get("X-Registry-Auth") if auth != "authtoken" { return nil, fmt.Errorf("Invalid auth header : expected %s, got %s", "authtoken", auth) } return &http.Response{ StatusCode: http.StatusOK, Body: ioutil.NopCloser(bytes.NewReader([]byte(""))), }, nil }), } err := client.PluginPush(context.Background(), "plugin_name", "authtoken") if err != nil { t.Fatal(err) } } engine-api-0.4.0/client/plugin_remove.go000066400000000000000000000004351274620210500202100ustar00rootroot00000000000000// +build experimental package client import ( "golang.org/x/net/context" ) // PluginRemove removes a plugin func (cli *Client) PluginRemove(ctx context.Context, name string) error { resp, err := cli.delete(ctx, "/plugins/"+name, nil, nil) ensureReaderClosed(resp) return err } engine-api-0.4.0/client/plugin_remove_test.go000066400000000000000000000022231274620210500212440ustar00rootroot00000000000000// +build experimental package client import ( "bytes" "fmt" "io/ioutil" "net/http" "strings" "testing" "golang.org/x/net/context" ) func TestPluginRemoveError(t *testing.T) { client := &Client{ transport: newMockClient(nil, errorMock(http.StatusInternalServerError, "Server error")), } err := client.PluginRemove(context.Background(), "plugin_name") if err == nil || err.Error() != "Error response from daemon: Server error" { t.Fatalf("expected a Server Error, got %v", err) } } func TestPluginRemove(t *testing.T) { expectedURL := "/plugins/plugin_name" 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 POST method, got %s", req.Method) } return &http.Response{ StatusCode: http.StatusOK, Body: ioutil.NopCloser(bytes.NewReader([]byte(""))), }, nil }), } err := client.PluginRemove(context.Background(), "plugin_name") if err != nil { t.Fatal(err) } } engine-api-0.4.0/client/plugin_set.go000066400000000000000000000005111274620210500175010ustar00rootroot00000000000000// +build experimental package client import ( "golang.org/x/net/context" ) // PluginSet modifies settings for an existing plugin func (cli *Client) PluginSet(ctx context.Context, name string, args []string) error { resp, err := cli.post(ctx, "/plugins/"+name+"/set", nil, args, nil) ensureReaderClosed(resp) return err } engine-api-0.4.0/client/plugin_set_test.go000066400000000000000000000022471274620210500205500ustar00rootroot00000000000000// +build experimental package client import ( "bytes" "fmt" "io/ioutil" "net/http" "strings" "testing" "golang.org/x/net/context" ) func TestPluginSetError(t *testing.T) { client := &Client{ transport: newMockClient(nil, errorMock(http.StatusInternalServerError, "Server error")), } err := client.PluginSet(context.Background(), "plugin_name", []string{}) if err == nil || err.Error() != "Error response from daemon: Server error" { t.Fatalf("expected a Server Error, got %v", err) } } func TestPluginSet(t *testing.T) { expectedURL := "/plugins/plugin_name/set" 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) } return &http.Response{ StatusCode: http.StatusOK, Body: ioutil.NopCloser(bytes.NewReader([]byte(""))), }, nil }), } err := client.PluginSet(context.Background(), "plugin_name", []string{"arg1"}) if err != nil { t.Fatal(err) } } engine-api-0.4.0/client/request.go000066400000000000000000000153151274620210500170300ustar00rootroot00000000000000package client import ( "bytes" "encoding/json" "fmt" "io" "io/ioutil" "net/http" "net/url" "strings" "github.com/docker/engine-api/client/transport/cancellable" "github.com/docker/engine-api/types" "github.com/docker/engine-api/types/versions" "golang.org/x/net/context" ) // serverResponse is a wrapper for http API responses. type serverResponse struct { body io.ReadCloser header http.Header statusCode int } // head sends an http request to the docker API using the method HEAD. func (cli *Client) head(ctx context.Context, path string, query url.Values, headers map[string][]string) (*serverResponse, error) { return cli.sendRequest(ctx, "HEAD", path, query, nil, headers) } // getWithContext sends an http request to the docker API using the method GET with a specific go context. func (cli *Client) get(ctx context.Context, path string, query url.Values, headers map[string][]string) (*serverResponse, error) { return cli.sendRequest(ctx, "GET", path, query, nil, headers) } // postWithContext sends an http request to the docker API using the method POST with a specific go context. func (cli *Client) post(ctx context.Context, path string, query url.Values, obj interface{}, headers map[string][]string) (*serverResponse, error) { return cli.sendRequest(ctx, "POST", path, query, obj, headers) } func (cli *Client) postRaw(ctx context.Context, path string, query url.Values, body io.Reader, headers map[string][]string) (*serverResponse, error) { return cli.sendClientRequest(ctx, "POST", path, query, body, headers) } // put sends an http request to the docker API using the method PUT. func (cli *Client) put(ctx context.Context, path string, query url.Values, obj interface{}, headers map[string][]string) (*serverResponse, error) { return cli.sendRequest(ctx, "PUT", path, query, obj, headers) } // put sends an http request to the docker API using the method PUT. func (cli *Client) putRaw(ctx context.Context, path string, query url.Values, body io.Reader, headers map[string][]string) (*serverResponse, error) { return cli.sendClientRequest(ctx, "PUT", path, query, body, headers) } // delete sends an http request to the docker API using the method DELETE. func (cli *Client) delete(ctx context.Context, path string, query url.Values, headers map[string][]string) (*serverResponse, error) { return cli.sendRequest(ctx, "DELETE", path, query, nil, headers) } func (cli *Client) sendRequest(ctx context.Context, method, path string, query url.Values, obj interface{}, headers map[string][]string) (*serverResponse, error) { var body io.Reader if obj != nil { var err error body, err = encodeData(obj) if err != nil { return nil, err } if headers == nil { headers = make(map[string][]string) } headers["Content-Type"] = []string{"application/json"} } return cli.sendClientRequest(ctx, method, path, query, body, headers) } func (cli *Client) sendClientRequest(ctx context.Context, method, path string, query url.Values, body io.Reader, headers map[string][]string) (*serverResponse, error) { serverResp := &serverResponse{ body: nil, statusCode: -1, } expectedPayload := (method == "POST" || method == "PUT") if expectedPayload && body == nil { body = bytes.NewReader([]byte{}) } req, err := cli.newRequest(method, path, query, body, headers) if err != nil { return serverResp, err } if cli.proto == "unix" || cli.proto == "npipe" { // For local communications, it doesn't matter what the host is. We just // need a valid and meaningful host name. (See #189) req.Host = "docker" } req.URL.Host = cli.addr req.URL.Scheme = cli.transport.Scheme() if expectedPayload && req.Header.Get("Content-Type") == "" { req.Header.Set("Content-Type", "text/plain") } resp, err := cancellable.Do(ctx, cli.transport, req) if err != nil { if isTimeout(err) || strings.Contains(err.Error(), "connection refused") || strings.Contains(err.Error(), "dial unix") { return serverResp, ErrConnectionFailed } if !cli.transport.Secure() && strings.Contains(err.Error(), "malformed HTTP response") { return serverResp, fmt.Errorf("%v.\n* Are you trying to connect to a TLS-enabled daemon without TLS?", err) } if cli.transport.Secure() && strings.Contains(err.Error(), "bad certificate") { return serverResp, fmt.Errorf("The server probably has client authentication (--tlsverify) enabled. Please check your TLS client certification settings: %v", err) } return serverResp, fmt.Errorf("An error occurred trying to connect: %v", err) } if resp != nil { serverResp.statusCode = resp.StatusCode } if serverResp.statusCode < 200 || serverResp.statusCode >= 400 { body, err := ioutil.ReadAll(resp.Body) if err != nil { return serverResp, err } if len(body) == 0 { return serverResp, fmt.Errorf("Error: request returned %s for API route and version %s, check if the server supports the requested API version", http.StatusText(serverResp.statusCode), req.URL) } var errorMessage string if (cli.version == "" || versions.GreaterThan(cli.version, "1.23")) && resp.Header.Get("Content-Type") == "application/json" { var errorResponse types.ErrorResponse if err := json.Unmarshal(body, &errorResponse); err != nil { return serverResp, fmt.Errorf("Error reading JSON: %v", err) } errorMessage = errorResponse.Message } else { errorMessage = string(body) } return serverResp, fmt.Errorf("Error response from daemon: %s", strings.TrimSpace(errorMessage)) } serverResp.body = resp.Body serverResp.header = resp.Header return serverResp, nil } func (cli *Client) newRequest(method, path string, query url.Values, body io.Reader, headers map[string][]string) (*http.Request, error) { apiPath := cli.getAPIPath(path, query) req, err := http.NewRequest(method, apiPath, body) if err != nil { return nil, err } // Add CLI Config's HTTP Headers BEFORE we set the Docker headers // then the user can't change OUR headers for k, v := range cli.customHTTPHeaders { req.Header.Set(k, v) } if headers != nil { for k, v := range headers { req.Header[k] = v } } return req, nil } func encodeData(data interface{}) (*bytes.Buffer, error) { params := bytes.NewBuffer(nil) if data != nil { if err := json.NewEncoder(params).Encode(data); err != nil { return nil, err } } return params, nil } func ensureReaderClosed(response *serverResponse) { if response != nil && response.body != nil { // Drain up to 512 bytes and close the body to let the Transport reuse the connection io.CopyN(ioutil.Discard, response.body, 512) response.body.Close() } } func isTimeout(err error) bool { type timeout interface { Timeout() bool } e := err switch urlErr := err.(type) { case *url.Error: e = urlErr.Err } t, ok := e.(timeout) return ok && t.Timeout() } engine-api-0.4.0/client/request_test.go000066400000000000000000000044161274620210500200670ustar00rootroot00000000000000package client import ( "bytes" "fmt" "io/ioutil" "net/http" "strings" "testing" "github.com/docker/engine-api/types" "golang.org/x/net/context" ) // TestSetHostHeader should set fake host for local communications, set real host // for normal communications. func TestSetHostHeader(t *testing.T) { testURL := "/test" testCases := []struct { host string expectedHost string expectedURLHost string }{ { "unix:///var/run/docker.sock", "docker", "/var/run/docker.sock", }, { "npipe:////./pipe/docker_engine", "docker", "//./pipe/docker_engine", }, { "tcp://0.0.0.0:4243", "", "0.0.0.0:4243", }, { "tcp://localhost:4243", "", "localhost:4243", }, } for c, test := range testCases { proto, addr, basePath, err := ParseHost(test.host) if err != nil { t.Fatal(err) } client := &Client{ transport: newMockClient(nil, func(req *http.Request) (*http.Response, error) { if !strings.HasPrefix(req.URL.Path, testURL) { return nil, fmt.Errorf("Test Case #%d: Expected URL %q, got %q", c, testURL, req.URL) } if req.Host != test.expectedHost { return nil, fmt.Errorf("Test Case #%d: Expected host %q, got %q", c, test.expectedHost, req.Host) } if req.URL.Host != test.expectedURLHost { return nil, fmt.Errorf("Test Case #%d: Expected URL host %q, got %q", c, test.expectedURLHost, req.URL.Host) } return &http.Response{ StatusCode: http.StatusOK, Body: ioutil.NopCloser(bytes.NewReader(([]byte("")))), }, nil }), proto: proto, addr: addr, basePath: basePath, } _, err = client.sendRequest(context.Background(), "GET", testURL, nil, nil, nil) if err != nil { t.Fatal(err) } } } // TestPlainTextError tests the server returning an error in plain text for // backwards compatibility with API versions <1.24. All other tests use // errors returned as JSON func TestPlainTextError(t *testing.T) { client := &Client{ transport: newMockClient(nil, plainTextErrorMock(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) } } engine-api-0.4.0/client/service_create.go000066400000000000000000000014231274620210500203160ustar00rootroot00000000000000package client import ( "encoding/json" "github.com/docker/engine-api/types" "github.com/docker/engine-api/types/swarm" "golang.org/x/net/context" ) // ServiceCreate creates a new Service. func (cli *Client) ServiceCreate(ctx context.Context, service swarm.ServiceSpec, options types.ServiceCreateOptions) (types.ServiceCreateResponse, error) { var headers map[string][]string if options.EncodedRegistryAuth != "" { headers = map[string][]string{ "X-Registry-Auth": []string{options.EncodedRegistryAuth}, } } var response types.ServiceCreateResponse resp, err := cli.post(ctx, "/services/create", nil, service, headers) if err != nil { return response, err } err = json.NewDecoder(resp.body).Decode(&response) ensureReaderClosed(resp) return response, err } engine-api-0.4.0/client/service_create_test.go000066400000000000000000000027561274620210500213670ustar00rootroot00000000000000package client import ( "bytes" "encoding/json" "fmt" "io/ioutil" "net/http" "strings" "testing" "github.com/docker/engine-api/types" "github.com/docker/engine-api/types/swarm" "golang.org/x/net/context" ) func TestServiceCreateError(t *testing.T) { client := &Client{ transport: newMockClient(nil, errorMock(http.StatusInternalServerError, "Server error")), } _, err := client.ServiceCreate(context.Background(), swarm.ServiceSpec{}, types.ServiceCreateOptions{}) if err == nil || err.Error() != "Error response from daemon: Server error" { t.Fatalf("expected a Server Error, got %v", err) } } func TestServiceCreate(t *testing.T) { expectedURL := "/services/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) } if req.Method != "POST" { return nil, fmt.Errorf("expected POST method, got %s", req.Method) } b, err := json.Marshal(types.ServiceCreateResponse{ ID: "service_id", }) if err != nil { return nil, err } return &http.Response{ StatusCode: http.StatusOK, Body: ioutil.NopCloser(bytes.NewReader(b)), }, nil }), } r, err := client.ServiceCreate(context.Background(), swarm.ServiceSpec{}, types.ServiceCreateOptions{}) if err != nil { t.Fatal(err) } if r.ID != "service_id" { t.Fatalf("expected `service_id`, got %s", r.ID) } } engine-api-0.4.0/client/service_inspect.go000066400000000000000000000015311274620210500205200ustar00rootroot00000000000000package client import ( "bytes" "encoding/json" "io/ioutil" "net/http" "github.com/docker/engine-api/types/swarm" "golang.org/x/net/context" ) // ServiceInspectWithRaw returns the service information and the raw data. func (cli *Client) ServiceInspectWithRaw(ctx context.Context, serviceID string) (swarm.Service, []byte, error) { serverResp, err := cli.get(ctx, "/services/"+serviceID, nil, nil) if err != nil { if serverResp.statusCode == http.StatusNotFound { return swarm.Service{}, nil, serviceNotFoundError{serviceID} } return swarm.Service{}, nil, err } defer ensureReaderClosed(serverResp) body, err := ioutil.ReadAll(serverResp.body) if err != nil { return swarm.Service{}, nil, err } var response swarm.Service rdr := bytes.NewReader(body) err = json.NewDecoder(rdr).Decode(&response) return response, body, err } engine-api-0.4.0/client/service_inspect_test.go000066400000000000000000000032671274620210500215670ustar00rootroot00000000000000package client import ( "bytes" "encoding/json" "fmt" "io/ioutil" "net/http" "strings" "testing" "github.com/docker/engine-api/types/swarm" "golang.org/x/net/context" ) func TestServiceInspectError(t *testing.T) { client := &Client{ transport: newMockClient(nil, errorMock(http.StatusInternalServerError, "Server error")), } _, _, err := client.ServiceInspectWithRaw(context.Background(), "nothing") if err == nil || err.Error() != "Error response from daemon: Server error" { t.Fatalf("expected a Server Error, got %v", err) } } func TestServiceInspectServiceNotFound(t *testing.T) { client := &Client{ transport: newMockClient(nil, errorMock(http.StatusNotFound, "Server error")), } _, _, err := client.ServiceInspectWithRaw(context.Background(), "unknown") if err == nil || !IsErrServiceNotFound(err) { t.Fatalf("expected an serviceNotFoundError error, got %v", err) } } func TestServiceInspect(t *testing.T) { expectedURL := "/services/service_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) } content, err := json.Marshal(swarm.Service{ ID: "service_id", }) if err != nil { return nil, err } return &http.Response{ StatusCode: http.StatusOK, Body: ioutil.NopCloser(bytes.NewReader(content)), }, nil }), } serviceInspect, _, err := client.ServiceInspectWithRaw(context.Background(), "service_id") if err != nil { t.Fatal(err) } if serviceInspect.ID != "service_id" { t.Fatalf("expected `service_id`, got %s", serviceInspect.ID) } } engine-api-0.4.0/client/service_list.go000066400000000000000000000014171274620210500200310ustar00rootroot00000000000000package client import ( "encoding/json" "net/url" "github.com/docker/engine-api/types" "github.com/docker/engine-api/types/filters" "github.com/docker/engine-api/types/swarm" "golang.org/x/net/context" ) // ServiceList returns the list of services. func (cli *Client) ServiceList(ctx context.Context, options types.ServiceListOptions) ([]swarm.Service, error) { query := url.Values{} if options.Filter.Len() > 0 { filterJSON, err := filters.ToParam(options.Filter) if err != nil { return nil, err } query.Set("filters", filterJSON) } resp, err := cli.get(ctx, "/services", query, nil) if err != nil { return nil, err } var services []swarm.Service err = json.NewDecoder(resp.body).Decode(&services) ensureReaderClosed(resp) return services, err } engine-api-0.4.0/client/service_list_test.go000066400000000000000000000044011274620210500210640ustar00rootroot00000000000000package 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" "github.com/docker/engine-api/types/swarm" "golang.org/x/net/context" ) func TestServiceListError(t *testing.T) { client := &Client{ transport: newMockClient(nil, errorMock(http.StatusInternalServerError, "Server error")), } _, err := client.ServiceList(context.Background(), types.ServiceListOptions{}) if err == nil || err.Error() != "Error response from daemon: Server error" { t.Fatalf("expected a Server Error, got %v", err) } } func TestServiceList(t *testing.T) { expectedURL := "/services" filters := filters.NewArgs() filters.Add("label", "label1") filters.Add("label", "label2") listCases := []struct { options types.ServiceListOptions expectedQueryParams map[string]string }{ { options: types.ServiceListOptions{}, expectedQueryParams: map[string]string{ "filters": "", }, }, { options: types.ServiceListOptions{ Filter: filters, }, expectedQueryParams: map[string]string{ "filters": `{"label":{"label1":true,"label2":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([]swarm.Service{ { ID: "service_id1", }, { ID: "service_id2", }, }) if err != nil { return nil, err } return &http.Response{ StatusCode: http.StatusOK, Body: ioutil.NopCloser(bytes.NewReader(content)), }, nil }), } services, err := client.ServiceList(context.Background(), listCase.options) if err != nil { t.Fatal(err) } if len(services) != 2 { t.Fatalf("expected 2 services, got %v", services) } } } engine-api-0.4.0/client/service_remove.go000066400000000000000000000004311274620210500203460ustar00rootroot00000000000000package client import "golang.org/x/net/context" // ServiceRemove kills and removes a service. func (cli *Client) ServiceRemove(ctx context.Context, serviceID string) error { resp, err := cli.delete(ctx, "/services/"+serviceID, nil, nil) ensureReaderClosed(resp) return err } engine-api-0.4.0/client/service_remove_test.go000066400000000000000000000022031274620210500214040ustar00rootroot00000000000000package client import ( "bytes" "fmt" "io/ioutil" "net/http" "strings" "testing" "golang.org/x/net/context" ) func TestServiceRemoveError(t *testing.T) { client := &Client{ transport: newMockClient(nil, errorMock(http.StatusInternalServerError, "Server error")), } err := client.ServiceRemove(context.Background(), "service_id") if err == nil || err.Error() != "Error response from daemon: Server error" { t.Fatalf("expected a Server Error, got %v", err) } } func TestServiceRemove(t *testing.T) { expectedURL := "/services/service_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("body"))), }, nil }), } err := client.ServiceRemove(context.Background(), "service_id") if err != nil { t.Fatal(err) } } engine-api-0.4.0/client/service_update.go000066400000000000000000000013701274620210500203360ustar00rootroot00000000000000package client import ( "net/url" "strconv" "github.com/docker/engine-api/types" "github.com/docker/engine-api/types/swarm" "golang.org/x/net/context" ) // ServiceUpdate updates a Service. func (cli *Client) ServiceUpdate(ctx context.Context, serviceID string, version swarm.Version, service swarm.ServiceSpec, options types.ServiceUpdateOptions) error { var ( headers map[string][]string query = url.Values{} ) if options.EncodedRegistryAuth != "" { headers = map[string][]string{ "X-Registry-Auth": []string{options.EncodedRegistryAuth}, } } query.Set("version", strconv.FormatUint(version.Index, 10)) resp, err := cli.post(ctx, "/services/"+serviceID+"/update", query, service, headers) ensureReaderClosed(resp) return err } engine-api-0.4.0/client/service_update_test.go000066400000000000000000000036701274620210500214020ustar00rootroot00000000000000package 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/swarm" ) func TestServiceUpdateError(t *testing.T) { client := &Client{ transport: newMockClient(nil, errorMock(http.StatusInternalServerError, "Server error")), } err := client.ServiceUpdate(context.Background(), "service_id", swarm.Version{}, swarm.ServiceSpec{}, types.ServiceUpdateOptions{}) if err == nil || err.Error() != "Error response from daemon: Server error" { t.Fatalf("expected a Server Error, got %v", err) } } func TestServiceUpdate(t *testing.T) { expectedURL := "/services/service_id/update" updateCases := []struct { swarmVersion swarm.Version expectedVersion string }{ { expectedVersion: "0", }, { swarmVersion: swarm.Version{ Index: 0, }, expectedVersion: "0", }, { swarmVersion: swarm.Version{ Index: 10, }, expectedVersion: "10", }, } for _, updateCase := range updateCases { 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) } version := req.URL.Query().Get("version") if version != updateCase.expectedVersion { return nil, fmt.Errorf("version not set in URL query properly, expected '%s', got %s", updateCase.expectedVersion, version) } return &http.Response{ StatusCode: http.StatusOK, Body: ioutil.NopCloser(bytes.NewReader([]byte("body"))), }, nil }), } err := client.ServiceUpdate(context.Background(), "service_id", updateCase.swarmVersion, swarm.ServiceSpec{}, types.ServiceUpdateOptions{}) if err != nil { t.Fatal(err) } } } engine-api-0.4.0/client/swarm_init.go000066400000000000000000000007421274620210500175120ustar00rootroot00000000000000package client import ( "encoding/json" "github.com/docker/engine-api/types/swarm" "golang.org/x/net/context" ) // SwarmInit initializes the Swarm. func (cli *Client) SwarmInit(ctx context.Context, req swarm.InitRequest) (string, error) { serverResp, err := cli.post(ctx, "/swarm/init", nil, req, nil) if err != nil { return "", err } var response string err = json.NewDecoder(serverResp.body).Decode(&response) ensureReaderClosed(serverResp) return response, err } engine-api-0.4.0/client/swarm_init_test.go000066400000000000000000000024201274620210500205440ustar00rootroot00000000000000package client import ( "bytes" "fmt" "io/ioutil" "net/http" "strings" "testing" "golang.org/x/net/context" "github.com/docker/engine-api/types/swarm" ) func TestSwarmInitError(t *testing.T) { client := &Client{ transport: newMockClient(nil, errorMock(http.StatusInternalServerError, "Server error")), } _, err := client.SwarmInit(context.Background(), swarm.InitRequest{}) if err == nil || err.Error() != "Error response from daemon: Server error" { t.Fatalf("expected a Server Error, got %v", err) } } func TestSwarmInit(t *testing.T) { expectedURL := "/swarm/init" 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) } return &http.Response{ StatusCode: http.StatusOK, Body: ioutil.NopCloser(bytes.NewReader([]byte(`"body"`))), }, nil }), } resp, err := client.SwarmInit(context.Background(), swarm.InitRequest{ ListenAddr: "0.0.0.0:2377", }) if err != nil { t.Fatal(err) } if resp != "body" { t.Fatalf("Expected 'body', got %s", resp) } } engine-api-0.4.0/client/swarm_inspect.go000066400000000000000000000007301274620210500202110ustar00rootroot00000000000000package client import ( "encoding/json" "github.com/docker/engine-api/types/swarm" "golang.org/x/net/context" ) // SwarmInspect inspects the Swarm. func (cli *Client) SwarmInspect(ctx context.Context) (swarm.Swarm, error) { serverResp, err := cli.get(ctx, "/swarm", nil, nil) if err != nil { return swarm.Swarm{}, err } var response swarm.Swarm err = json.NewDecoder(serverResp.body).Decode(&response) ensureReaderClosed(serverResp) return response, err } engine-api-0.4.0/client/swarm_inspect_test.go000066400000000000000000000024571274620210500212600ustar00rootroot00000000000000package client import ( "bytes" "encoding/json" "fmt" "io/ioutil" "net/http" "strings" "testing" "github.com/docker/engine-api/types/swarm" "golang.org/x/net/context" ) func TestSwarmInspectError(t *testing.T) { client := &Client{ transport: newMockClient(nil, errorMock(http.StatusInternalServerError, "Server error")), } _, err := client.SwarmInspect(context.Background()) if err == nil || err.Error() != "Error response from daemon: Server error" { t.Fatalf("expected a Server Error, got %v", err) } } func TestSwarmInspect(t *testing.T) { expectedURL := "/swarm" 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(swarm.Swarm{ ClusterInfo: swarm.ClusterInfo{ ID: "swarm_id", }, }) if err != nil { return nil, err } return &http.Response{ StatusCode: http.StatusOK, Body: ioutil.NopCloser(bytes.NewReader(content)), }, nil }), } swarmInspect, err := client.SwarmInspect(context.Background()) if err != nil { t.Fatal(err) } if swarmInspect.ID != "swarm_id" { t.Fatalf("expected `swarm_id`, got %s", swarmInspect.ID) } } engine-api-0.4.0/client/swarm_join.go000066400000000000000000000004651274620210500175100ustar00rootroot00000000000000package client import ( "github.com/docker/engine-api/types/swarm" "golang.org/x/net/context" ) // SwarmJoin joins the Swarm. func (cli *Client) SwarmJoin(ctx context.Context, req swarm.JoinRequest) error { resp, err := cli.post(ctx, "/swarm/join", nil, req, nil) ensureReaderClosed(resp) return err } engine-api-0.4.0/client/swarm_join_test.go000066400000000000000000000022751274620210500205500ustar00rootroot00000000000000package client import ( "bytes" "fmt" "io/ioutil" "net/http" "strings" "testing" "golang.org/x/net/context" "github.com/docker/engine-api/types/swarm" ) func TestSwarmJoinError(t *testing.T) { client := &Client{ transport: newMockClient(nil, errorMock(http.StatusInternalServerError, "Server error")), } err := client.SwarmJoin(context.Background(), swarm.JoinRequest{}) if err == nil || err.Error() != "Error response from daemon: Server error" { t.Fatalf("expected a Server Error, got %v", err) } } func TestSwarmJoin(t *testing.T) { expectedURL := "/swarm/join" 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) } return &http.Response{ StatusCode: http.StatusOK, Body: ioutil.NopCloser(bytes.NewReader([]byte(""))), }, nil }), } err := client.SwarmJoin(context.Background(), swarm.JoinRequest{ ListenAddr: "0.0.0.0:2377", }) if err != nil { t.Fatal(err) } } engine-api-0.4.0/client/swarm_leave.go000066400000000000000000000005201274620210500176350ustar00rootroot00000000000000package client import ( "net/url" "golang.org/x/net/context" ) // SwarmLeave leaves the Swarm. func (cli *Client) SwarmLeave(ctx context.Context, force bool) error { query := url.Values{} if force { query.Set("force", "1") } resp, err := cli.post(ctx, "/swarm/leave", query, nil, nil) ensureReaderClosed(resp) return err } engine-api-0.4.0/client/swarm_leave_test.go000066400000000000000000000030311274620210500206740ustar00rootroot00000000000000package client import ( "bytes" "fmt" "io/ioutil" "net/http" "strings" "testing" "golang.org/x/net/context" ) func TestSwarmLeaveError(t *testing.T) { client := &Client{ transport: newMockClient(nil, errorMock(http.StatusInternalServerError, "Server error")), } err := client.SwarmLeave(context.Background(), false) if err == nil || err.Error() != "Error response from daemon: Server error" { t.Fatalf("expected a Server Error, got %v", err) } } func TestSwarmLeave(t *testing.T) { expectedURL := "/swarm/leave" leaveCases := []struct { force bool expectedForce string }{ { expectedForce: "", }, { force: true, expectedForce: "1", }, } for _, leaveCase := range leaveCases { 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) } force := req.URL.Query().Get("force") if force != leaveCase.expectedForce { return nil, fmt.Errorf("force not set in URL query properly. expected '%s', got %s", leaveCase.expectedForce, force) } return &http.Response{ StatusCode: http.StatusOK, Body: ioutil.NopCloser(bytes.NewReader([]byte(""))), }, nil }), } err := client.SwarmLeave(context.Background(), leaveCase.force) if err != nil { t.Fatal(err) } } } engine-api-0.4.0/client/swarm_update.go000066400000000000000000000011701274620210500200250ustar00rootroot00000000000000package client import ( "fmt" "net/url" "strconv" "github.com/docker/engine-api/types/swarm" "golang.org/x/net/context" ) // SwarmUpdate updates the Swarm. func (cli *Client) SwarmUpdate(ctx context.Context, version swarm.Version, swarm swarm.Spec, flags swarm.UpdateFlags) error { query := url.Values{} query.Set("version", strconv.FormatUint(version.Index, 10)) query.Set("rotateWorkerToken", fmt.Sprintf("%v", flags.RotateWorkerToken)) query.Set("rotateManagerToken", fmt.Sprintf("%v", flags.RotateManagerToken)) resp, err := cli.post(ctx, "/swarm/update", query, swarm, nil) ensureReaderClosed(resp) return err } engine-api-0.4.0/client/swarm_update_test.go000066400000000000000000000023451274620210500210710ustar00rootroot00000000000000package client import ( "bytes" "fmt" "io/ioutil" "net/http" "strings" "testing" "golang.org/x/net/context" "github.com/docker/engine-api/types/swarm" ) func TestSwarmUpdateError(t *testing.T) { client := &Client{ transport: newMockClient(nil, errorMock(http.StatusInternalServerError, "Server error")), } err := client.SwarmUpdate(context.Background(), swarm.Version{}, swarm.Spec{}, swarm.UpdateFlags{}) if err == nil || err.Error() != "Error response from daemon: Server error" { t.Fatalf("expected a Server Error, got %v", err) } } func TestSwarmUpdate(t *testing.T) { expectedURL := "/swarm/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) } if req.Method != "POST" { return nil, fmt.Errorf("expected POST method, got %s", req.Method) } return &http.Response{ StatusCode: http.StatusOK, Body: ioutil.NopCloser(bytes.NewReader([]byte(""))), }, nil }), } err := client.SwarmUpdate(context.Background(), swarm.Version{}, swarm.Spec{}, swarm.UpdateFlags{}) if err != nil { t.Fatal(err) } } engine-api-0.4.0/client/task_inspect.go000066400000000000000000000014761274620210500200320ustar00rootroot00000000000000package client import ( "bytes" "encoding/json" "io/ioutil" "net/http" "github.com/docker/engine-api/types/swarm" "golang.org/x/net/context" ) // TaskInspectWithRaw returns the task information and its raw representation.. func (cli *Client) TaskInspectWithRaw(ctx context.Context, taskID string) (swarm.Task, []byte, error) { serverResp, err := cli.get(ctx, "/tasks/"+taskID, nil, nil) if err != nil { if serverResp.statusCode == http.StatusNotFound { return swarm.Task{}, nil, taskNotFoundError{taskID} } return swarm.Task{}, nil, err } defer ensureReaderClosed(serverResp) body, err := ioutil.ReadAll(serverResp.body) if err != nil { return swarm.Task{}, nil, err } var response swarm.Task rdr := bytes.NewReader(body) err = json.NewDecoder(rdr).Decode(&response) return response, body, err } engine-api-0.4.0/client/task_inspect_test.go000066400000000000000000000024521274620210500210640ustar00rootroot00000000000000package client import ( "bytes" "encoding/json" "fmt" "io/ioutil" "net/http" "strings" "testing" "github.com/docker/engine-api/types/swarm" "golang.org/x/net/context" ) func TestTaskInspectError(t *testing.T) { client := &Client{ transport: newMockClient(nil, errorMock(http.StatusInternalServerError, "Server error")), } _, _, err := client.TaskInspectWithRaw(context.Background(), "nothing") if err == nil || err.Error() != "Error response from daemon: Server error" { t.Fatalf("expected a Server Error, got %v", err) } } func TestTaskInspect(t *testing.T) { expectedURL := "/tasks/task_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) } content, err := json.Marshal(swarm.Task{ ID: "task_id", }) if err != nil { return nil, err } return &http.Response{ StatusCode: http.StatusOK, Body: ioutil.NopCloser(bytes.NewReader(content)), }, nil }), } taskInspect, _, err := client.TaskInspectWithRaw(context.Background(), "task_id") if err != nil { t.Fatal(err) } if taskInspect.ID != "task_id" { t.Fatalf("expected `task_id`, got %s", taskInspect.ID) } } engine-api-0.4.0/client/task_list.go000066400000000000000000000013611274620210500173310ustar00rootroot00000000000000package client import ( "encoding/json" "net/url" "github.com/docker/engine-api/types" "github.com/docker/engine-api/types/filters" "github.com/docker/engine-api/types/swarm" "golang.org/x/net/context" ) // TaskList returns the list of tasks. func (cli *Client) TaskList(ctx context.Context, options types.TaskListOptions) ([]swarm.Task, error) { query := url.Values{} if options.Filter.Len() > 0 { filterJSON, err := filters.ToParam(options.Filter) if err != nil { return nil, err } query.Set("filters", filterJSON) } resp, err := cli.get(ctx, "/tasks", query, nil) if err != nil { return nil, err } var tasks []swarm.Task err = json.NewDecoder(resp.body).Decode(&tasks) ensureReaderClosed(resp) return tasks, err } engine-api-0.4.0/client/task_list_test.go000066400000000000000000000043211274620210500203670ustar00rootroot00000000000000package 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" "github.com/docker/engine-api/types/swarm" "golang.org/x/net/context" ) func TestTaskListError(t *testing.T) { client := &Client{ transport: newMockClient(nil, errorMock(http.StatusInternalServerError, "Server error")), } _, err := client.TaskList(context.Background(), types.TaskListOptions{}) if err == nil || err.Error() != "Error response from daemon: Server error" { t.Fatalf("expected a Server Error, got %v", err) } } func TestTaskList(t *testing.T) { expectedURL := "/tasks" filters := filters.NewArgs() filters.Add("label", "label1") filters.Add("label", "label2") listCases := []struct { options types.TaskListOptions expectedQueryParams map[string]string }{ { options: types.TaskListOptions{}, expectedQueryParams: map[string]string{ "filters": "", }, }, { options: types.TaskListOptions{ Filter: filters, }, expectedQueryParams: map[string]string{ "filters": `{"label":{"label1":true,"label2":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([]swarm.Task{ { ID: "task_id1", }, { ID: "task_id2", }, }) if err != nil { return nil, err } return &http.Response{ StatusCode: http.StatusOK, Body: ioutil.NopCloser(bytes.NewReader(content)), }, nil }), } tasks, err := client.TaskList(context.Background(), listCase.options) if err != nil { t.Fatal(err) } if len(tasks) != 2 { t.Fatalf("expected 2 tasks, got %v", tasks) } } } engine-api-0.4.0/client/testdata/000077500000000000000000000000001274620210500166155ustar00rootroot00000000000000engine-api-0.4.0/client/testdata/ca.pem000066400000000000000000000020161274620210500177020ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- MIIC0jCCAbqgAwIBAgIRAILlP5WWLaHkQ/m2ASHP7SowDQYJKoZIhvcNAQELBQAw EjEQMA4GA1UEChMHdmluY2VudDAeFw0xNjAzMjQxMDE5MDBaFw0xOTAzMDkxMDE5 MDBaMBIxEDAOBgNVBAoTB3ZpbmNlbnQwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw ggEKAoIBAQD0yZPKAGncoaxaU/QW9tWEHbrvDoGVF/65L8Si/jBrlAgLjhmmV1di vKG9QPzuU8snxHro3/uCwyA6kTqw0U8bGwHxJq2Bpa6JBYj8N2jMJ+M+sjXgSo2t E0zIzjTW2Pir3C8qwfrVL6NFp9xClwMD23SFZ0UsEH36NkfyrKBVeM8IOjJd4Wjs xIcuvF3BTVkji84IJBW2JIKf9ZrzJwUlSCPgptRp4Evdbyp5d+UPxtwxD7qjW4lM yQQ8vfcC4lKkVx5s/RNJ4fzd5uEgLdEbZ20qt7Zt/bLcxFHpUhH2teA0QjmrOWFh gbL83s95/+hbSVhsO4hoFW7vTeiCCY4xAgMBAAGjIzAhMA4GA1UdDwEB/wQEAwIC rDAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQBY51RHajuDuhO2 tcm26jeNROzfffnjhvbOVPjSEdo9vI3JpMU/RuQw+nbNcLwJrdjL6UH7tD/36Y+q NXH+xSIjWFH0zXGxrIUsVrvt6f8CbOvw7vD+gygOG+849PDQMbL6czP8rvXY7vZV 9pdpQfrENk4b5kePRW/6HaGSTvtgN7XOrYD9fp3pm/G534T2e3IxgYMRNwdB9Ul9 bLwMqQqf4eiqqMs6x4IVmZUkGVMKiFKcvkNg9a+Ozx5pMizHeAezWMcZ5V+QJZVT 8lElSCKZ2Yy2xkcl7aeQMLwcAeZwfTp+Yu9dVzlqXiiBTLd1+LtAQCuKHzmw4Q8k EvD5m49l -----END CERTIFICATE----- engine-api-0.4.0/client/testdata/cert.pem000066400000000000000000000020661274620210500202610ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- MIIC8DCCAdigAwIBAgIRAJAS1glgcke4q7eCaretwgUwDQYJKoZIhvcNAQELBQAw EjEQMA4GA1UEChMHdmluY2VudDAeFw0xNjAzMjQxMDE5MDBaFw0xOTAzMDkxMDE5 MDBaMB4xHDAaBgNVBAoME3ZpbmNlbnQuPGJvb3RzdHJhcD4wggEiMA0GCSqGSIb3 DQEBAQUAA4IBDwAwggEKAoIBAQClpvG442dGEvrRgmCrqY4kBml1LVlw2Y7ZDn6B TKa52+MuGDmfXbO1UhclNqTXjLgAwKjPz/OvnPRxNEUoQEDbBd+Xev7rxTY5TvYI 27YH3fMH2LL2j62jum649abfhZ6ekD5eD8tCn3mnrEOgqRIlK7efPIVixq/ZqU1H 7ez0ggB7dmWHlhnUaxyQOCSnAX/7nKYQXqZgVvGhDeR2jp7GcnhbK/qPrZ/mOm83 2IjCeYN145opYlzTSp64GYIZz7uqMNcnDKK37ZbS8MYcTjrRaHEiqZVVdIC+ghbx qYqzbZRVfgztI9jwmifn0mYrN4yt+nhNYwBcRJ4Pv3uLFbo7AgMBAAGjNTAzMA4G A1UdDwEB/wQEAwIHgDATBgNVHSUEDDAKBggrBgEFBQcDAjAMBgNVHRMBAf8EAjAA MA0GCSqGSIb3DQEBCwUAA4IBAQDg1r7nksjYgDFYEcBbrRrRHddIoK+RVmSBTTrq 8giC77m0srKdh9XTVWK1PUbGfODV1oD8m9QhPE8zPDyYQ8jeXNRSU5wXdkrTRmmY w/T3SREqmE7CObMtusokHidjYFuqqCR07sJzqBKRlzr3o0EGe3tuEhUlF5ARY028 eipaDcVlT5ChGcDa6LeJ4e05u4cVap0dd6Rp1w3Rx1AYAecdgtgBMnw1iWdl/nrC sp26ZXNaAhFOUovlY9VY257AMd9hQV7WvAK4yNEHcckVu3uXTBmDgNSOPtl0QLsL Kjlj75ksCx8nCln/hCut/0+kGTsGZqdV5c6ktgcGYRir/5Hs -----END CERTIFICATE----- engine-api-0.4.0/client/testdata/key.pem000066400000000000000000000032131274620210500201070ustar00rootroot00000000000000-----BEGIN RSA PRIVATE KEY----- MIIEowIBAAKCAQEApabxuONnRhL60YJgq6mOJAZpdS1ZcNmO2Q5+gUymudvjLhg5 n12ztVIXJTak14y4AMCoz8/zr5z0cTRFKEBA2wXfl3r+68U2OU72CNu2B93zB9iy 9o+to7puuPWm34WenpA+Xg/LQp95p6xDoKkSJSu3nzyFYsav2alNR+3s9IIAe3Zl h5YZ1GsckDgkpwF/+5ymEF6mYFbxoQ3kdo6exnJ4Wyv6j62f5jpvN9iIwnmDdeOa KWJc00qeuBmCGc+7qjDXJwyit+2W0vDGHE460WhxIqmVVXSAvoIW8amKs22UVX4M 7SPY8Jon59JmKzeMrfp4TWMAXESeD797ixW6OwIDAQABAoIBAHfyAAleL8NfrtnR S+pApbmUIvxD0AWUooispBE/zWG6xC72P5MTqDJctIGvpYCmVf3Fgvamns7EGYN2 07Sngc6V3Ca1WqyhaffpIuGbJZ1gqr89u6gotRRexBmNVj13ZTlvPJmjWgxtqQsu AvHsOkVL+HOGwRaaw24Z1umEcBVCepl7PGTqsLeJUtBUZBiqdJTu4JYLAB6BggBI OxhHoTWvlNWwzezo2C/IXkXcXD/tp3i5vTn5rAXHSMQkdMAUh7/xJ73Fl36gxZhp W7NoPKaS9qNh8jhs6p54S7tInb6+mrKtvRFKl5XAR3istXrXteT5UaukpuBbQ/5d qf4BXuECgYEAzoOKxMee5tG/G9iC6ImNq5xGAZm0OnmteNgIEQj49If1Q68av525 FioqdC9zV+blfHQqXEIUeum4JAou4xqmB8Lw2H0lYwOJ1IkpUy3QJjU1IrI+U5Qy ryZuA9cxSTLf1AJFbROsoZDpjaBh0uUQkD/4PHpwXMgHu/3CaJ4nTEkCgYEAzVjE VWgczWJGyRxmHSeR51ft1jrlChZHEd3HwgLfo854JIj+MGUH4KPLSMIkYNuyiwNQ W7zdXCB47U8afSL/lPTv1M5+ZsWY6sZAT6gtp/IeU0Va943h9cj10fAOBJaz1H6M jnZS4jjWhVInE7wpCDVCwDRoHHJ84kb6JeflamMCgYBDQDcKie9HP3q6uLE4xMKr 5gIuNz2n5UQGnGNUGNXp2/SVDArr55MEksqsd19aesi01KeOz74XoNDke6R1NJJo 6KTB+08XhWl3GwuoGL02FBGvsNf3I8W1oBAnlAZqzfRx+CNfuA55ttU318jDgvD3 6L0QBNdef411PNf4dbhacQKBgAd/e0PHFm4lbYJAaDYeUMSKwGN3KQ/SOmwblgSu iC36BwcGfYmU1tHMCUsx05Q50W4kA9Ylskt/4AqCPexdz8lHnE4/7/uesXO5I3YF JQ2h2Jufx6+MXbjUyq0Mv+ZI/m3+5PD6vxIFk0ew9T5SO4lSMIrGHxsSzx6QCuhB bG4TAoGBAJ5PWG7d2CyCjLtfF8J4NxykRvIQ8l/3kDvDdNrXiXbgonojo2lgRYaM 5LoK9ApN8KHdedpTRipBaDA22Sp5SjMcUE7A6q42PJCL9r+BRYF0foFQx/rqpCff pVWKgwIPoKnfxDqN1RUgyFcx1jbA3XVJZCuT+wbMuDQ9nlvulD1W -----END RSA PRIVATE KEY----- engine-api-0.4.0/client/transport/000077500000000000000000000000001274620210500170405ustar00rootroot00000000000000engine-api-0.4.0/client/transport/cancellable/000077500000000000000000000000001274620210500212655ustar00rootroot00000000000000engine-api-0.4.0/client/transport/cancellable/LICENSE000066400000000000000000000027071274620210500223000ustar00rootroot00000000000000Copyright (c) 2009 The Go Authors. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of Google Inc. nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. engine-api-0.4.0/client/transport/cancellable/canceler.go000066400000000000000000000007331274620210500233730ustar00rootroot00000000000000// Copyright 2015 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. // +build go1.5 package cancellable import ( "net/http" "github.com/docker/engine-api/client/transport" ) func canceler(client transport.Sender, req *http.Request) func() { // TODO(djd): Respect any existing value of req.Cancel. ch := make(chan struct{}) req.Cancel = ch return func() { close(ch) } } engine-api-0.4.0/client/transport/cancellable/canceler_go14.go000066400000000000000000000010101274620210500242120ustar00rootroot00000000000000// Copyright 2015 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. // +build !go1.5 package cancellable import ( "net/http" "github.com/docker/engine-api/client/transport" ) type requestCanceler interface { CancelRequest(*http.Request) } func canceler(client transport.Sender, req *http.Request) func() { rc, ok := client.(requestCanceler) if !ok { return func() {} } return func() { rc.CancelRequest(req) } } engine-api-0.4.0/client/transport/cancellable/cancellable.go000066400000000000000000000051421274620210500240430ustar00rootroot00000000000000// Copyright 2015 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. // Package cancellable provides helper function to cancel http requests. package cancellable import ( "io" "net/http" "github.com/docker/engine-api/client/transport" "golang.org/x/net/context" ) func nop() {} var ( testHookContextDoneBeforeHeaders = nop testHookDoReturned = nop testHookDidBodyClose = nop ) // Do sends an HTTP request with the provided transport.Sender and returns an HTTP response. // If the client is nil, http.DefaultClient is used. // If the context is canceled or times out, ctx.Err() will be returned. // // FORK INFORMATION: // // This function deviates from the upstream version in golang.org/x/net/context/ctxhttp by // taking a Sender interface rather than a *http.Client directly. That allow us to use // this function with mocked clients and hijacked connections. func Do(ctx context.Context, client transport.Sender, req *http.Request) (*http.Response, error) { if client == nil { client = http.DefaultClient } // Request cancelation changed in Go 1.5, see canceler.go and canceler_go14.go. cancel := canceler(client, req) type responseAndError struct { resp *http.Response err error } result := make(chan responseAndError, 1) go func() { resp, err := client.Do(req) testHookDoReturned() result <- responseAndError{resp, err} }() var resp *http.Response select { case <-ctx.Done(): testHookContextDoneBeforeHeaders() cancel() // Clean up after the goroutine calling client.Do: go func() { if r := <-result; r.resp != nil && r.resp.Body != nil { testHookDidBodyClose() r.resp.Body.Close() } }() return nil, ctx.Err() case r := <-result: var err error resp, err = r.resp, r.err if err != nil { return resp, err } } c := make(chan struct{}) go func() { select { case <-ctx.Done(): cancel() case <-c: // The response's Body is closed. } }() resp.Body = ¬ifyingReader{resp.Body, c} return resp, nil } // notifyingReader is an io.ReadCloser that closes the notify channel after // Close is called or a Read fails on the underlying ReadCloser. type notifyingReader struct { io.ReadCloser notify chan<- struct{} } func (r *notifyingReader) Read(p []byte) (int, error) { n, err := r.ReadCloser.Read(p) if err != nil && r.notify != nil { close(r.notify) r.notify = nil } return n, err } func (r *notifyingReader) Close() error { err := r.ReadCloser.Close() if r.notify != nil { close(r.notify) r.notify = nil } return err } engine-api-0.4.0/client/transport/client.go000066400000000000000000000021061274620210500206440ustar00rootroot00000000000000package transport import ( "crypto/tls" "net/http" ) // Sender is an interface that clients must implement // to be able to send requests to a remote connection. type Sender interface { // Do sends request to a remote endpoint. Do(*http.Request) (*http.Response, error) } // Client is an interface that abstracts all remote connections. type Client interface { Sender // Secure tells whether the connection is secure or not. Secure() bool // Scheme returns the connection protocol the client uses. Scheme() string // TLSConfig returns any TLS configuration the client uses. TLSConfig() *tls.Config } // tlsInfo returns information about the TLS configuration. type tlsInfo struct { tlsConfig *tls.Config } // TLSConfig returns the TLS configuration. func (t *tlsInfo) TLSConfig() *tls.Config { return t.tlsConfig } // Scheme returns protocol scheme to use. func (t *tlsInfo) Scheme() string { if t.tlsConfig != nil { return "https" } return "http" } // Secure returns true if there is a TLS configuration. func (t *tlsInfo) Secure() bool { return t.tlsConfig != nil } engine-api-0.4.0/client/transport/transport.go000066400000000000000000000030211274620210500214170ustar00rootroot00000000000000// Package transport provides function to send request to remote endpoints. package transport import ( "fmt" "net/http" "github.com/docker/go-connections/sockets" ) // apiTransport holds information about the http transport to connect with the API. type apiTransport struct { *http.Client *tlsInfo transport *http.Transport } // NewTransportWithHTTP creates a new transport based on the provided proto, address and http client. // It uses Docker's default http transport configuration if the client is nil. // It does not modify the client's transport if it's not nil. func NewTransportWithHTTP(proto, addr string, client *http.Client) (Client, error) { var transport *http.Transport if client != nil { tr, ok := client.Transport.(*http.Transport) if !ok { return nil, fmt.Errorf("unable to verify TLS configuration, invalid transport %v", client.Transport) } transport = tr } else { transport = defaultTransport(proto, addr) client = &http.Client{ Transport: transport, } } return &apiTransport{ Client: client, tlsInfo: &tlsInfo{transport.TLSClientConfig}, transport: transport, }, nil } // CancelRequest stops a request execution. func (a *apiTransport) CancelRequest(req *http.Request) { a.transport.CancelRequest(req) } // defaultTransport creates a new http.Transport with Docker's // default transport configuration. func defaultTransport(proto, addr string) *http.Transport { tr := new(http.Transport) sockets.ConfigureTransport(tr, proto, addr) return tr } var _ Client = &apiTransport{} engine-api-0.4.0/client/version.go000066400000000000000000000007521274620210500170240ustar00rootroot00000000000000package client import ( "encoding/json" "github.com/docker/engine-api/types" "golang.org/x/net/context" ) // ServerVersion returns information of the docker client and server host. func (cli *Client) ServerVersion(ctx context.Context) (types.Version, error) { resp, err := cli.get(ctx, "/version", nil, nil) if err != nil { return types.Version{}, err } var server types.Version err = json.NewDecoder(resp.body).Decode(&server) ensureReaderClosed(resp) return server, err } engine-api-0.4.0/client/volume_create.go000066400000000000000000000007731274620210500201740ustar00rootroot00000000000000package client import ( "encoding/json" "github.com/docker/engine-api/types" "golang.org/x/net/context" ) // VolumeCreate creates a volume in the docker host. func (cli *Client) VolumeCreate(ctx context.Context, options types.VolumeCreateRequest) (types.Volume, error) { var volume types.Volume resp, err := cli.post(ctx, "/volumes/create", nil, options, nil) if err != nil { return volume, err } err = json.NewDecoder(resp.body).Decode(&volume) ensureReaderClosed(resp) return volume, err } engine-api-0.4.0/client/volume_create_test.go000066400000000000000000000035071274620210500212310ustar00rootroot00000000000000package client import ( "bytes" "encoding/json" "fmt" "io/ioutil" "net/http" "strings" "testing" "github.com/docker/engine-api/types" "golang.org/x/net/context" ) func TestVolumeCreateError(t *testing.T) { client := &Client{ transport: newMockClient(nil, errorMock(http.StatusInternalServerError, "Server error")), } _, err := client.VolumeCreate(context.Background(), types.VolumeCreateRequest{}) if err == nil || err.Error() != "Error response from daemon: Server error" { t.Fatalf("expected a Server Error, got %v", err) } } func TestVolumeCreate(t *testing.T) { expectedURL := "/volumes/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) } if req.Method != "POST" { return nil, fmt.Errorf("expected POST method, got %s", req.Method) } content, err := json.Marshal(types.Volume{ Name: "volume", Driver: "local", Mountpoint: "mountpoint", }) if err != nil { return nil, err } return &http.Response{ StatusCode: http.StatusOK, Body: ioutil.NopCloser(bytes.NewReader(content)), }, nil }), } volume, err := client.VolumeCreate(context.Background(), types.VolumeCreateRequest{ Name: "myvolume", Driver: "mydriver", DriverOpts: map[string]string{ "opt-key": "opt-value", }, }) if err != nil { t.Fatal(err) } if volume.Name != "volume" { t.Fatalf("expected volume.Name to be 'volume', got %s", volume.Name) } if volume.Driver != "local" { t.Fatalf("expected volume.Driver to be 'local', got %s", volume.Driver) } if volume.Mountpoint != "mountpoint" { t.Fatalf("expected volume.Mountpoint to be 'mountpoint', got %s", volume.Mountpoint) } } engine-api-0.4.0/client/volume_inspect.go000066400000000000000000000021021274620210500203620ustar00rootroot00000000000000package client import ( "bytes" "encoding/json" "io/ioutil" "net/http" "github.com/docker/engine-api/types" "golang.org/x/net/context" ) // VolumeInspect returns the information about a specific volume in the docker host. func (cli *Client) VolumeInspect(ctx context.Context, volumeID string) (types.Volume, error) { volume, _, err := cli.VolumeInspectWithRaw(ctx, volumeID) return volume, err } // VolumeInspectWithRaw returns the information about a specific volume in the docker host and its raw representation func (cli *Client) VolumeInspectWithRaw(ctx context.Context, volumeID string) (types.Volume, []byte, error) { var volume types.Volume resp, err := cli.get(ctx, "/volumes/"+volumeID, nil, nil) if err != nil { if resp.statusCode == http.StatusNotFound { return volume, nil, volumeNotFoundError{volumeID} } return volume, nil, err } defer ensureReaderClosed(resp) body, err := ioutil.ReadAll(resp.body) if err != nil { return volume, nil, err } rdr := bytes.NewReader(body) err = json.NewDecoder(rdr).Decode(&volume) return volume, body, err } engine-api-0.4.0/client/volume_inspect_test.go000066400000000000000000000036341274620210500214340ustar00rootroot00000000000000package client import ( "bytes" "encoding/json" "fmt" "io/ioutil" "net/http" "strings" "testing" "github.com/docker/engine-api/types" "golang.org/x/net/context" ) func TestVolumeInspectError(t *testing.T) { client := &Client{ transport: newMockClient(nil, errorMock(http.StatusInternalServerError, "Server error")), } _, err := client.VolumeInspect(context.Background(), "nothing") if err == nil || err.Error() != "Error response from daemon: Server error" { t.Fatalf("expected a Server Error, got %v", err) } } func TestVolumeInspectNotFound(t *testing.T) { client := &Client{ transport: newMockClient(nil, errorMock(http.StatusNotFound, "Server error")), } _, err := client.VolumeInspect(context.Background(), "unknown") if err == nil || !IsErrVolumeNotFound(err) { t.Fatalf("expected a volumeNotFound error, got %v", err) } } func TestVolumeInspect(t *testing.T) { expectedURL := "/volumes/volume_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 != "GET" { return nil, fmt.Errorf("expected GET method, got %s", req.Method) } content, err := json.Marshal(types.Volume{ Name: "name", Driver: "driver", Mountpoint: "mountpoint", }) if err != nil { return nil, err } return &http.Response{ StatusCode: http.StatusOK, Body: ioutil.NopCloser(bytes.NewReader(content)), }, nil }), } v, err := client.VolumeInspect(context.Background(), "volume_id") if err != nil { t.Fatal(err) } if v.Name != "name" { t.Fatalf("expected `name`, got %s", v.Name) } if v.Driver != "driver" { t.Fatalf("expected `driver`, got %s", v.Driver) } if v.Mountpoint != "mountpoint" { t.Fatalf("expected `mountpoint`, got %s", v.Mountpoint) } } engine-api-0.4.0/client/volume_list.go000066400000000000000000000014071274620210500176770ustar00rootroot00000000000000package client import ( "encoding/json" "net/url" "github.com/docker/engine-api/types" "github.com/docker/engine-api/types/filters" "golang.org/x/net/context" ) // VolumeList returns the volumes configured in the docker host. func (cli *Client) VolumeList(ctx context.Context, filter filters.Args) (types.VolumesListResponse, error) { var volumes types.VolumesListResponse query := url.Values{} if filter.Len() > 0 { filterJSON, err := filters.ToParamWithVersion(cli.version, filter) if err != nil { return volumes, err } query.Set("filters", filterJSON) } resp, err := cli.get(ctx, "/volumes", query, nil) if err != nil { return volumes, err } err = json.NewDecoder(resp.body).Decode(&volumes) ensureReaderClosed(resp) return volumes, err } engine-api-0.4.0/client/volume_list_test.go000066400000000000000000000047301274620210500207400ustar00rootroot00000000000000package 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 TestVolumeListError(t *testing.T) { client := &Client{ transport: newMockClient(nil, errorMock(http.StatusInternalServerError, "Server error")), } _, err := client.VolumeList(context.Background(), filters.NewArgs()) if err == nil || err.Error() != "Error response from daemon: Server error" { t.Fatalf("expected a Server Error, got %v", err) } } func TestVolumeList(t *testing.T) { expectedURL := "/volumes" noDanglingFilters := filters.NewArgs() noDanglingFilters.Add("dangling", "false") danglingFilters := filters.NewArgs() danglingFilters.Add("dangling", "true") labelFilters := filters.NewArgs() labelFilters.Add("label", "label1") labelFilters.Add("label", "label2") listCases := []struct { filters filters.Args expectedFilters string }{ { filters: filters.NewArgs(), expectedFilters: "", }, { filters: noDanglingFilters, expectedFilters: `{"dangling":{"false":true}}`, }, { filters: danglingFilters, expectedFilters: `{"dangling":{"true":true}}`, }, { filters: labelFilters, expectedFilters: `{"label":{"label1":true,"label2":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() actualFilters := query.Get("filters") if actualFilters != listCase.expectedFilters { return nil, fmt.Errorf("filters not set in URL query properly. Expected '%s', got %s", listCase.expectedFilters, actualFilters) } content, err := json.Marshal(types.VolumesListResponse{ Volumes: []*types.Volume{ { Name: "volume", Driver: "local", }, }, }) if err != nil { return nil, err } return &http.Response{ StatusCode: http.StatusOK, Body: ioutil.NopCloser(bytes.NewReader(content)), }, nil }), } volumeResponse, err := client.VolumeList(context.Background(), listCase.filters) if err != nil { t.Fatal(err) } if len(volumeResponse.Volumes) != 1 { t.Fatalf("expected 1 volume, got %v", volumeResponse.Volumes) } } } engine-api-0.4.0/client/volume_remove.go000066400000000000000000000004361274620210500202220ustar00rootroot00000000000000package client import "golang.org/x/net/context" // VolumeRemove removes a volume from the docker host. func (cli *Client) VolumeRemove(ctx context.Context, volumeID string) error { resp, err := cli.delete(ctx, "/volumes/"+volumeID, nil, nil) ensureReaderClosed(resp) return err } engine-api-0.4.0/client/volume_remove_test.go000066400000000000000000000021731274620210500212610ustar00rootroot00000000000000package client import ( "bytes" "fmt" "io/ioutil" "net/http" "strings" "testing" "golang.org/x/net/context" ) func TestVolumeRemoveError(t *testing.T) { client := &Client{ transport: newMockClient(nil, errorMock(http.StatusInternalServerError, "Server error")), } err := client.VolumeRemove(context.Background(), "volume_id") if err == nil || err.Error() != "Error response from daemon: Server error" { t.Fatalf("expected a Server Error, got %v", err) } } func TestVolumeRemove(t *testing.T) { expectedURL := "/volumes/volume_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("body"))), }, nil }), } err := client.VolumeRemove(context.Background(), "volume_id") if err != nil { t.Fatal(err) } } engine-api-0.4.0/doc.go000066400000000000000000000017401274620210500146240ustar00rootroot00000000000000/* Package engineapi provides libraries to implement client and server components compatible with the Docker engine. The client package in github.com/docker/engine-api/client implements all necessary requests to implement the official Docker engine cli. Create a new client, then use it to send and receive messages to the Docker engine API: defaultHeaders := map[string]string{"User-Agent": "engine-api-cli-1.0"} cli, err := client.NewClient("unix:///var/run/docker.sock", "v1.22", nil, defaultHeaders) Other programs, like Docker Machine, can set the default Docker engine environment for you. There is a shortcut to use its variables to configure the client: cli, err := client.NewEnvClient() All request arguments are defined as typed structures in the types package. For instance, this is how to get all containers running in the host: options := types.ContainerListOptions{All: true} containers, err := cli.ContainerList(context.Background(), options) */ package engineapi engine-api-0.4.0/types/000077500000000000000000000000001274620210500146725ustar00rootroot00000000000000engine-api-0.4.0/types/auth.go000066400000000000000000000013711274620210500161640ustar00rootroot00000000000000package types // AuthConfig contains authorization information for connecting to a Registry type AuthConfig struct { Username string `json:"username,omitempty"` Password string `json:"password,omitempty"` Auth string `json:"auth,omitempty"` // Email is an optional value associated with the username. // This field is deprecated and will be removed in a later // version of docker. Email string `json:"email,omitempty"` ServerAddress string `json:"serveraddress,omitempty"` // IdentityToken is used to authenticate the user and get // an access token for the registry. IdentityToken string `json:"identitytoken,omitempty"` // RegistryToken is a bearer token to be sent to a registry RegistryToken string `json:"registrytoken,omitempty"` } engine-api-0.4.0/types/blkiodev/000077500000000000000000000000001274620210500164715ustar00rootroot00000000000000engine-api-0.4.0/types/blkiodev/blkio.go000066400000000000000000000007201274620210500201170ustar00rootroot00000000000000package blkiodev import "fmt" // WeightDevice is a structure that holds device:weight pair type WeightDevice struct { Path string Weight uint16 } func (w *WeightDevice) String() string { return fmt.Sprintf("%s:%d", w.Path, w.Weight) } // ThrottleDevice is a structure that holds device:rate_per_second pair type ThrottleDevice struct { Path string Rate uint64 } func (t *ThrottleDevice) String() string { return fmt.Sprintf("%s:%d", t.Path, t.Rate) } engine-api-0.4.0/types/client.go000066400000000000000000000170141274620210500165020ustar00rootroot00000000000000package types import ( "bufio" "io" "net" "github.com/docker/engine-api/types/container" "github.com/docker/engine-api/types/filters" "github.com/docker/go-units" ) // CheckpointCreateOptions holds parameters to create a checkpoint from a container type CheckpointCreateOptions struct { CheckpointID string Exit bool } // ContainerAttachOptions holds parameters to attach to a container. type ContainerAttachOptions struct { Stream bool Stdin bool Stdout bool Stderr bool DetachKeys string } // ContainerCommitOptions holds parameters to commit changes into a container. type ContainerCommitOptions struct { Reference string Comment string Author string Changes []string Pause bool Config *container.Config } // ContainerExecInspect holds information returned by exec inspect. type ContainerExecInspect struct { ExecID string ContainerID string Running bool ExitCode int } // ContainerListOptions holds parameters to list containers with. type ContainerListOptions struct { Quiet bool Size bool All bool Latest bool Since string Before string Limit int Filter filters.Args } // ContainerLogsOptions holds parameters to filter logs with. type ContainerLogsOptions struct { ShowStdout bool ShowStderr bool Since string Timestamps bool Follow bool Tail string Details bool } // ContainerRemoveOptions holds parameters to remove containers. type ContainerRemoveOptions struct { RemoveVolumes bool RemoveLinks bool Force bool } // ContainerStartOptions holds parameters to start containers. type ContainerStartOptions struct { CheckpointID string } // CopyToContainerOptions holds information // about files to copy into a container type CopyToContainerOptions struct { AllowOverwriteDirWithFile bool } // EventsOptions hold parameters to filter events with. type EventsOptions struct { Since string Until string Filters filters.Args } // NetworkListOptions holds parameters to filter the list of networks with. type NetworkListOptions struct { Filters filters.Args } // HijackedResponse holds connection information for a hijacked request. type HijackedResponse struct { Conn net.Conn Reader *bufio.Reader } // Close closes the hijacked connection and reader. func (h *HijackedResponse) Close() { h.Conn.Close() } // CloseWriter is an interface that implements structs // that close input streams to prevent from writing. type CloseWriter interface { CloseWrite() error } // CloseWrite closes a readWriter for writing. func (h *HijackedResponse) CloseWrite() error { if conn, ok := h.Conn.(CloseWriter); ok { return conn.CloseWrite() } return nil } // ImageBuildOptions holds the information // necessary to build images. type ImageBuildOptions struct { Tags []string SuppressOutput bool RemoteContext string NoCache bool Remove bool ForceRemove bool PullParent bool Isolation container.Isolation CPUSetCPUs string CPUSetMems string CPUShares int64 CPUQuota int64 CPUPeriod int64 Memory int64 MemorySwap int64 CgroupParent string ShmSize int64 Dockerfile string Ulimits []*units.Ulimit BuildArgs map[string]string AuthConfigs map[string]AuthConfig Context io.Reader Labels map[string]string } // ImageBuildResponse holds information // returned by a server after building // an image. type ImageBuildResponse struct { Body io.ReadCloser OSType string } // ImageCreateOptions holds information to create images. type ImageCreateOptions struct { RegistryAuth string // RegistryAuth is the base64 encoded credentials for the registry } // ImageImportSource holds source information for ImageImport type ImageImportSource struct { Source io.Reader // Source is the data to send to the server to create this image from (mutually exclusive with SourceName) SourceName string // SourceName is the name of the image to pull (mutually exclusive with Source) } // ImageImportOptions holds information to import images from the client host. type ImageImportOptions struct { Tag string // Tag is the name to tag this image with. This attribute is deprecated. Message string // Message is the message to tag the image with Changes []string // Changes are the raw changes to apply to this image } // ImageListOptions holds parameters to filter the list of images with. type ImageListOptions struct { MatchName string All bool Filters filters.Args } // ImageLoadResponse returns information to the client about a load process. type ImageLoadResponse struct { // Body must be closed to avoid a resource leak Body io.ReadCloser JSON bool } // ImagePullOptions holds information to pull images. type ImagePullOptions struct { All bool RegistryAuth string // RegistryAuth is the base64 encoded credentials for the registry PrivilegeFunc RequestPrivilegeFunc } // RequestPrivilegeFunc is a function interface that // clients can supply to retry operations after // getting an authorization error. // This function returns the registry authentication // header value in base 64 format, or an error // if the privilege request fails. type RequestPrivilegeFunc func() (string, error) //ImagePushOptions holds information to push images. type ImagePushOptions ImagePullOptions // ImageRemoveOptions holds parameters to remove images. type ImageRemoveOptions struct { Force bool PruneChildren bool } // ImageSearchOptions holds parameters to search images with. type ImageSearchOptions struct { RegistryAuth string PrivilegeFunc RequestPrivilegeFunc Filters filters.Args Limit int } // ResizeOptions holds parameters to resize a tty. // It can be used to resize container ttys and // exec process ttys too. type ResizeOptions struct { Height int Width int } // VersionResponse holds version information for the client and the server type VersionResponse struct { Client *Version Server *Version } // ServerOK returns true when the client could connect to the docker server // and parse the information received. It returns false otherwise. func (v VersionResponse) ServerOK() bool { return v.Server != nil } // NodeListOptions holds parameters to list nodes with. type NodeListOptions struct { Filter filters.Args } // ServiceCreateOptions contains the options to use when creating a service. type ServiceCreateOptions struct { // EncodedRegistryAuth is the encoded registry authorization credentials to // use when updating the service. // // This field follows the format of the X-Registry-Auth header. EncodedRegistryAuth string } // ServiceCreateResponse contains the information returned to a client // on the creation of a new service. type ServiceCreateResponse struct { // ID is the ID of the created service. ID string } // ServiceUpdateOptions contains the options to be used for updating services. type ServiceUpdateOptions struct { // EncodedRegistryAuth is the encoded registry authorization credentials to // use when updating the service. // // This field follows the format of the X-Registry-Auth header. EncodedRegistryAuth string // TODO(stevvooe): Consider moving the version parameter of ServiceUpdate // into this field. While it does open API users up to racy writes, most // users may not need that level of consistency in practice. } // ServiceListOptions holds parameters to list services with. type ServiceListOptions struct { Filter filters.Args } // TaskListOptions holds parameters to list tasks with. type TaskListOptions struct { Filter filters.Args } engine-api-0.4.0/types/configs.go000066400000000000000000000034301274620210500166510ustar00rootroot00000000000000package types import ( "github.com/docker/engine-api/types/container" "github.com/docker/engine-api/types/network" ) // configs holds structs used for internal communication between the // frontend (such as an http server) and the backend (such as the // docker daemon). // ContainerCreateConfig is the parameter set to ContainerCreate() type ContainerCreateConfig struct { Name string Config *container.Config HostConfig *container.HostConfig NetworkingConfig *network.NetworkingConfig AdjustCPUShares bool } // ContainerRmConfig holds arguments for the container remove // operation. This struct is used to tell the backend what operations // to perform. type ContainerRmConfig struct { ForceRemove, RemoveVolume, RemoveLink bool } // ContainerCommitConfig contains build configs for commit operation, // and is used when making a commit with the current state of the container. type ContainerCommitConfig struct { Pause bool Repo string Tag string Author string Comment string // merge container config into commit config before commit MergeConfigs bool Config *container.Config } // ExecConfig is a small subset of the Config struct that holds the configuration // for the exec feature of docker. type ExecConfig struct { User string // User that will run the command Privileged bool // Is the container in privileged mode Tty bool // Attach standard streams to a tty. AttachStdin bool // Attach the standard input, makes possible user interaction AttachStderr bool // Attach the standard error AttachStdout bool // Attach the standard output Detach bool // Execute in detach mode DetachKeys string // Escape keys for detach Cmd []string // Execution commands and args } engine-api-0.4.0/types/container/000077500000000000000000000000001274620210500166545ustar00rootroot00000000000000engine-api-0.4.0/types/container/config.go000066400000000000000000000071761274620210500204630ustar00rootroot00000000000000package container import ( "time" "github.com/docker/engine-api/types/strslice" "github.com/docker/go-connections/nat" ) // HealthConfig holds configuration settings for the HEALTHCHECK feature. type HealthConfig struct { // Test is the test to perform to check that the container is healthy. // An empty slice means to inherit the default. // The options are: // {} : inherit healthcheck // {"NONE"} : disable healthcheck // {"CMD", args...} : exec arguments directly // {"CMD-SHELL", command} : run command with system's default shell Test []string `json:",omitempty"` // Zero means to inherit. Durations are expressed as integer nanoseconds. Interval time.Duration `json:",omitempty"` // Interval is the time to wait between checks. Timeout time.Duration `json:",omitempty"` // Timeout is the time to wait before considering the check to have hung. // Retries is the number of consecutive failures needed to consider a container as unhealthy. // Zero means inherit. Retries int `json:",omitempty"` } // Config contains the configuration data about a container. // It should hold only portable information about the container. // Here, "portable" means "independent from the host we are running on". // Non-portable information *should* appear in HostConfig. // All fields added to this struct must be marked `omitempty` to keep getting // predictable hashes from the old `v1Compatibility` configuration. type Config struct { Hostname string // Hostname Domainname string // Domainname User string // User that will run the command(s) inside the container AttachStdin bool // Attach the standard input, makes possible user interaction AttachStdout bool // Attach the standard output AttachStderr bool // Attach the standard error ExposedPorts map[nat.Port]struct{} `json:",omitempty"` // List of exposed ports Tty bool // Attach standard streams to a tty, including stdin if it is not closed. OpenStdin bool // Open stdin StdinOnce bool // If true, close stdin after the 1 attached client disconnects. Env []string // List of environment variable to set in the container Cmd strslice.StrSlice // Command to run when starting the container Healthcheck *HealthConfig `json:",omitempty"` // Healthcheck describes how to check the container is healthy ArgsEscaped bool `json:",omitempty"` // True if command is already escaped (Windows specific) Image string // Name of the image as it was passed by the operator (eg. could be symbolic) Volumes map[string]struct{} // List of volumes (mounts) used for the container WorkingDir string // Current directory (PWD) in the command will be launched Entrypoint strslice.StrSlice // Entrypoint to run when starting the container NetworkDisabled bool `json:",omitempty"` // Is network disabled MacAddress string `json:",omitempty"` // Mac Address of the container OnBuild []string // ONBUILD metadata that were defined on the image Dockerfile Labels map[string]string // List of labels set to this container StopSignal string `json:",omitempty"` // Signal to stop a container StopTimeout *int `json:",omitempty"` // Timeout (in seconds) to stop a container Shell strslice.StrSlice `json:",omitempty"` // Shell for shell-form of RUN, CMD, ENTRYPOINT } engine-api-0.4.0/types/container/host_config.go000066400000000000000000000270271274620210500215150ustar00rootroot00000000000000package container import ( "strings" "github.com/docker/engine-api/types/blkiodev" "github.com/docker/engine-api/types/strslice" "github.com/docker/go-connections/nat" "github.com/docker/go-units" ) // NetworkMode represents the container network stack. type NetworkMode string // Isolation represents the isolation technology of a container. The supported // values are platform specific type Isolation string // IsDefault indicates the default isolation technology of a container. On Linux this // is the native driver. On Windows, this is a Windows Server Container. func (i Isolation) IsDefault() bool { return strings.ToLower(string(i)) == "default" || string(i) == "" } // IpcMode represents the container ipc stack. type IpcMode string // IsPrivate indicates whether the container uses its private ipc stack. func (n IpcMode) IsPrivate() bool { return !(n.IsHost() || n.IsContainer()) } // IsHost indicates whether the container uses the host's ipc stack. func (n IpcMode) IsHost() bool { return n == "host" } // IsContainer indicates whether the container uses a container's ipc stack. func (n IpcMode) IsContainer() bool { parts := strings.SplitN(string(n), ":", 2) return len(parts) > 1 && parts[0] == "container" } // Valid indicates whether the ipc stack is valid. func (n IpcMode) Valid() bool { parts := strings.Split(string(n), ":") switch mode := parts[0]; mode { case "", "host": case "container": if len(parts) != 2 || parts[1] == "" { return false } default: return false } return true } // Container returns the name of the container ipc stack is going to be used. func (n IpcMode) Container() string { parts := strings.SplitN(string(n), ":", 2) if len(parts) > 1 { return parts[1] } return "" } // UsernsMode represents userns mode in the container. type UsernsMode string // IsHost indicates whether the container uses the host's userns. func (n UsernsMode) IsHost() bool { return n == "host" } // IsPrivate indicates whether the container uses the a private userns. func (n UsernsMode) IsPrivate() bool { return !(n.IsHost()) } // Valid indicates whether the userns is valid. func (n UsernsMode) Valid() bool { parts := strings.Split(string(n), ":") switch mode := parts[0]; mode { case "", "host": default: return false } return true } // CgroupSpec represents the cgroup to use for the container. type CgroupSpec string // IsContainer indicates whether the container is using another container cgroup func (c CgroupSpec) IsContainer() bool { parts := strings.SplitN(string(c), ":", 2) return len(parts) > 1 && parts[0] == "container" } // Valid indicates whether the cgroup spec is valid. func (c CgroupSpec) Valid() bool { return c.IsContainer() || c == "" } // Container returns the name of the container whose cgroup will be used. func (c CgroupSpec) Container() string { parts := strings.SplitN(string(c), ":", 2) if len(parts) > 1 { return parts[1] } return "" } // UTSMode represents the UTS namespace of the container. type UTSMode string // IsPrivate indicates whether the container uses its private UTS namespace. func (n UTSMode) IsPrivate() bool { return !(n.IsHost()) } // IsHost indicates whether the container uses the host's UTS namespace. func (n UTSMode) IsHost() bool { return n == "host" } // Valid indicates whether the UTS namespace is valid. func (n UTSMode) Valid() bool { parts := strings.Split(string(n), ":") switch mode := parts[0]; mode { case "", "host": default: return false } return true } // PidMode represents the pid namespace of the container. type PidMode string // IsPrivate indicates whether the container uses its own new pid namespace. func (n PidMode) IsPrivate() bool { return !(n.IsHost() || n.IsContainer()) } // IsHost indicates whether the container uses the host's pid namespace. func (n PidMode) IsHost() bool { return n == "host" } // IsContainer indicates whether the container uses a container's pid namespace. func (n PidMode) IsContainer() bool { parts := strings.SplitN(string(n), ":", 2) return len(parts) > 1 && parts[0] == "container" } // Valid indicates whether the pid namespace is valid. func (n PidMode) Valid() bool { parts := strings.Split(string(n), ":") switch mode := parts[0]; mode { case "", "host": case "container": if len(parts) != 2 || parts[1] == "" { return false } default: return false } return true } // Container returns the name of the container whose pid namespace is going to be used. func (n PidMode) Container() string { parts := strings.SplitN(string(n), ":", 2) if len(parts) > 1 { return parts[1] } return "" } // DeviceMapping represents the device mapping between the host and the container. type DeviceMapping struct { PathOnHost string PathInContainer string CgroupPermissions string } // RestartPolicy represents the restart policies of the container. type RestartPolicy struct { Name string MaximumRetryCount int } // IsNone indicates whether the container has the "no" restart policy. // This means the container will not automatically restart when exiting. func (rp *RestartPolicy) IsNone() bool { return rp.Name == "no" || rp.Name == "" } // IsAlways indicates whether the container has the "always" restart policy. // This means the container will automatically restart regardless of the exit status. func (rp *RestartPolicy) IsAlways() bool { return rp.Name == "always" } // IsOnFailure indicates whether the container has the "on-failure" restart policy. // This means the container will automatically restart of exiting with a non-zero exit status. func (rp *RestartPolicy) IsOnFailure() bool { return rp.Name == "on-failure" } // IsUnlessStopped indicates whether the container has the // "unless-stopped" restart policy. This means the container will // automatically restart unless user has put it to stopped state. func (rp *RestartPolicy) IsUnlessStopped() bool { return rp.Name == "unless-stopped" } // IsSame compares two RestartPolicy to see if they are the same func (rp *RestartPolicy) IsSame(tp *RestartPolicy) bool { return rp.Name == tp.Name && rp.MaximumRetryCount == tp.MaximumRetryCount } // LogConfig represents the logging configuration of the container. type LogConfig struct { Type string Config map[string]string } // Resources contains container's resources (cgroups config, ulimits...) type Resources struct { // Applicable to all platforms CPUShares int64 `json:"CpuShares"` // CPU shares (relative weight vs. other containers) Memory int64 // Memory limit (in bytes) // Applicable to UNIX platforms CgroupParent string // Parent cgroup. BlkioWeight uint16 // Block IO weight (relative weight vs. other containers) BlkioWeightDevice []*blkiodev.WeightDevice BlkioDeviceReadBps []*blkiodev.ThrottleDevice BlkioDeviceWriteBps []*blkiodev.ThrottleDevice BlkioDeviceReadIOps []*blkiodev.ThrottleDevice BlkioDeviceWriteIOps []*blkiodev.ThrottleDevice CPUPeriod int64 `json:"CpuPeriod"` // CPU CFS (Completely Fair Scheduler) period CPUQuota int64 `json:"CpuQuota"` // CPU CFS (Completely Fair Scheduler) quota CpusetCpus string // CpusetCpus 0-2, 0,1 CpusetMems string // CpusetMems 0-2, 0,1 Devices []DeviceMapping // List of devices to map inside the container DiskQuota int64 // Disk limit (in bytes) KernelMemory int64 // Kernel memory limit (in bytes) MemoryReservation int64 // Memory soft limit (in bytes) MemorySwap int64 // Total memory usage (memory + swap); set `-1` to enable unlimited swap MemorySwappiness *int64 // Tuning container memory swappiness behaviour OomKillDisable *bool // Whether to disable OOM Killer or not PidsLimit int64 // Setting pids limit for a container Ulimits []*units.Ulimit // List of ulimits to be set in the container // Applicable to Windows CPUCount int64 `json:"CpuCount"` // CPU count CPUPercent int64 `json:"CpuPercent"` // CPU percent IOMaximumIOps uint64 // Maximum IOps for the container system drive IOMaximumBandwidth uint64 // Maximum IO in bytes per second for the container system drive } // UpdateConfig holds the mutable attributes of a Container. // Those attributes can be updated at runtime. type UpdateConfig struct { // Contains container's resources (cgroups, ulimits) Resources RestartPolicy RestartPolicy } // HostConfig the non-portable Config structure of a container. // Here, "non-portable" means "dependent of the host we are running on". // Portable information *should* appear in Config. type HostConfig struct { // Applicable to all platforms Binds []string // List of volume bindings for this container ContainerIDFile string // File (path) where the containerId is written LogConfig LogConfig // Configuration of the logs for this container NetworkMode NetworkMode // Network mode to use for the container PortBindings nat.PortMap // Port mapping between the exposed port (container) and the host RestartPolicy RestartPolicy // Restart policy to be used for the container AutoRemove bool // Automatically remove container when it exits VolumeDriver string // Name of the volume driver used to mount volumes VolumesFrom []string // List of volumes to take from other container // Applicable to UNIX platforms CapAdd strslice.StrSlice // List of kernel capabilities to add to the container CapDrop strslice.StrSlice // List of kernel capabilities to remove from the container DNS []string `json:"Dns"` // List of DNS server to lookup DNSOptions []string `json:"DnsOptions"` // List of DNSOption to look for DNSSearch []string `json:"DnsSearch"` // List of DNSSearch to look for ExtraHosts []string // List of extra hosts GroupAdd []string // List of additional groups that the container process will run as IpcMode IpcMode // IPC namespace to use for the container Cgroup CgroupSpec // Cgroup to use for the container Links []string // List of links (in the name:alias form) OomScoreAdj int // Container preference for OOM-killing PidMode PidMode // PID namespace to use for the container Privileged bool // Is the container in privileged mode PublishAllPorts bool // Should docker publish all exposed port for the container ReadonlyRootfs bool // Is the container root filesystem in read-only SecurityOpt []string // List of string values to customize labels for MLS systems, such as SELinux. StorageOpt map[string]string `json:",omitempty"` // Storage driver options per container. Tmpfs map[string]string `json:",omitempty"` // List of tmpfs (mounts) used for the container UTSMode UTSMode // UTS namespace to use for the container UsernsMode UsernsMode // The user namespace to use for the container ShmSize int64 // Total shm memory usage Sysctls map[string]string `json:",omitempty"` // List of Namespaced sysctls used for the container Runtime string `json:",omitempty"` // Runtime to use with this container // Applicable to Windows ConsoleSize [2]int // Initial console size Isolation Isolation // Isolation technology of the container (eg default, hyperv) // Contains container's resources (cgroups, ulimits) Resources } engine-api-0.4.0/types/container/hostconfig_unix.go000066400000000000000000000037651274620210500224240ustar00rootroot00000000000000// +build !windows package container import "strings" // IsValid indicates if an isolation technology is valid func (i Isolation) IsValid() bool { return i.IsDefault() } // IsPrivate indicates whether container uses it's private network stack. func (n NetworkMode) IsPrivate() bool { return !(n.IsHost() || n.IsContainer()) } // IsDefault indicates whether container uses the default network stack. func (n NetworkMode) IsDefault() bool { return n == "default" } // NetworkName returns the name of the network stack. func (n NetworkMode) NetworkName() string { if n.IsBridge() { return "bridge" } else if n.IsHost() { return "host" } else if n.IsContainer() { return "container" } else if n.IsNone() { return "none" } else if n.IsDefault() { return "default" } else if n.IsUserDefined() { return n.UserDefined() } return "" } // IsBridge indicates whether container uses the bridge network stack func (n NetworkMode) IsBridge() bool { return n == "bridge" } // IsHost indicates whether container uses the host network stack. func (n NetworkMode) IsHost() bool { return n == "host" } // IsContainer indicates whether container uses a container network stack. func (n NetworkMode) IsContainer() bool { parts := strings.SplitN(string(n), ":", 2) return len(parts) > 1 && parts[0] == "container" } // IsNone indicates whether container isn't using a network stack. func (n NetworkMode) IsNone() bool { return n == "none" } // ConnectedContainer is the id of the container which network this container is connected to. func (n NetworkMode) ConnectedContainer() string { parts := strings.SplitN(string(n), ":", 2) if len(parts) > 1 { return parts[1] } return "" } // IsUserDefined indicates user-created network func (n NetworkMode) IsUserDefined() bool { return !n.IsDefault() && !n.IsBridge() && !n.IsHost() && !n.IsNone() && !n.IsContainer() } //UserDefined indicates user-created network func (n NetworkMode) UserDefined() string { if n.IsUserDefined() { return string(n) } return "" } engine-api-0.4.0/types/container/hostconfig_windows.go000066400000000000000000000042641274620210500231260ustar00rootroot00000000000000package container import ( "strings" ) // IsDefault indicates whether container uses the default network stack. func (n NetworkMode) IsDefault() bool { return n == "default" } // IsNone indicates whether container isn't using a network stack. func (n NetworkMode) IsNone() bool { return n == "none" } // IsContainer indicates whether container uses a container network stack. // Returns false as windows doesn't support this mode func (n NetworkMode) IsContainer() bool { return false } // IsBridge indicates whether container uses the bridge network stack // in windows it is given the name NAT func (n NetworkMode) IsBridge() bool { return n == "nat" } // IsHost indicates whether container uses the host network stack. // returns false as this is not supported by windows func (n NetworkMode) IsHost() bool { return false } // IsPrivate indicates whether container uses its private network stack. func (n NetworkMode) IsPrivate() bool { return !(n.IsHost() || n.IsContainer()) } // ConnectedContainer is the id of the container which network this container is connected to. // Returns blank string on windows func (n NetworkMode) ConnectedContainer() string { return "" } // IsUserDefined indicates user-created network func (n NetworkMode) IsUserDefined() bool { return !n.IsDefault() && !n.IsNone() && !n.IsBridge() } // IsHyperV indicates the use of a Hyper-V partition for isolation func (i Isolation) IsHyperV() bool { return strings.ToLower(string(i)) == "hyperv" } // IsProcess indicates the use of process isolation func (i Isolation) IsProcess() bool { return strings.ToLower(string(i)) == "process" } // IsValid indicates if an isolation technology is valid func (i Isolation) IsValid() bool { return i.IsDefault() || i.IsHyperV() || i.IsProcess() } // NetworkName returns the name of the network stack. func (n NetworkMode) NetworkName() string { if n.IsDefault() { return "default" } else if n.IsBridge() { return "nat" } else if n.IsNone() { return "none" } else if n.IsUserDefined() { return n.UserDefined() } return "" } //UserDefined indicates user-created network func (n NetworkMode) UserDefined() string { if n.IsUserDefined() { return string(n) } return "" } engine-api-0.4.0/types/errors.go000066400000000000000000000002031274620210500165300ustar00rootroot00000000000000package types // ErrorResponse is the response body of API errors. type ErrorResponse struct { Message string `json:"message"` } engine-api-0.4.0/types/events/000077500000000000000000000000001274620210500161765ustar00rootroot00000000000000engine-api-0.4.0/types/events/events.go000066400000000000000000000024411274620210500200320ustar00rootroot00000000000000package events const ( // ContainerEventType is the event type that containers generate ContainerEventType = "container" // DaemonEventType is the event type that daemon generate DaemonEventType = "daemon" // ImageEventType is the event type that images generate ImageEventType = "image" // NetworkEventType is the event type that networks generate NetworkEventType = "network" // PluginEventType is the event type that plugins generate PluginEventType = "plugin" // VolumeEventType is the event type that volumes generate VolumeEventType = "volume" ) // Actor describes something that generates events, // like a container, or a network, or a volume. // It has a defined name and a set or attributes. // The container attributes are its labels, other actors // can generate these attributes from other properties. type Actor struct { ID string Attributes map[string]string } // Message represents the information an event contains type Message struct { // Deprecated information from JSONMessage. // With data only in container events. Status string `json:"status,omitempty"` ID string `json:"id,omitempty"` From string `json:"from,omitempty"` Type string Action string Actor Actor Time int64 `json:"time,omitempty"` TimeNano int64 `json:"timeNano,omitempty"` } engine-api-0.4.0/types/filters/000077500000000000000000000000001274620210500163425ustar00rootroot00000000000000engine-api-0.4.0/types/filters/parse.go000066400000000000000000000200151274620210500200010ustar00rootroot00000000000000// Package filters provides helper function to parse and handle command line // filter, used for example in docker ps or docker images commands. package filters import ( "encoding/json" "errors" "fmt" "regexp" "strings" "github.com/docker/engine-api/types/versions" ) // Args stores filter arguments as map key:{map key: bool}. // It contains an aggregation of the map of arguments (which are in the form // of -f 'key=value') based on the key, and stores values for the same key // in a map with string keys and boolean values. // e.g given -f 'label=label1=1' -f 'label=label2=2' -f 'image.name=ubuntu' // the args will be {"image.name":{"ubuntu":true},"label":{"label1=1":true,"label2=2":true}} type Args struct { fields map[string]map[string]bool } // NewArgs initializes a new Args struct. func NewArgs() Args { return Args{fields: map[string]map[string]bool{}} } // ParseFlag parses the argument to the filter flag. Like // // `docker ps -f 'created=today' -f 'image.name=ubuntu*'` // // If prev map is provided, then it is appended to, and returned. By default a new // map is created. func ParseFlag(arg string, prev Args) (Args, error) { filters := prev if len(arg) == 0 { return filters, nil } if !strings.Contains(arg, "=") { return filters, ErrBadFormat } f := strings.SplitN(arg, "=", 2) name := strings.ToLower(strings.TrimSpace(f[0])) value := strings.TrimSpace(f[1]) filters.Add(name, value) return filters, nil } // ErrBadFormat is an error returned in case of bad format for a filter. var ErrBadFormat = errors.New("bad format of filter (expected name=value)") // ToParam packs the Args into a string for easy transport from client to server. func ToParam(a Args) (string, error) { // this way we don't URL encode {}, just empty space if a.Len() == 0 { return "", nil } buf, err := json.Marshal(a.fields) if err != nil { return "", err } return string(buf), nil } // ToParamWithVersion packs the Args into a string for easy transport from client to server. // The generated string will depend on the specified version (corresponding to the API version). func ToParamWithVersion(version string, a Args) (string, error) { // this way we don't URL encode {}, just empty space if a.Len() == 0 { return "", nil } // for daemons older than v1.10, filter must be of the form map[string][]string buf := []byte{} err := errors.New("") if version != "" && versions.LessThan(version, "1.22") { buf, err = json.Marshal(convertArgsToSlice(a.fields)) } else { buf, err = json.Marshal(a.fields) } if err != nil { return "", err } return string(buf), nil } // FromParam unpacks the filter Args. func FromParam(p string) (Args, error) { if len(p) == 0 { return NewArgs(), nil } r := strings.NewReader(p) d := json.NewDecoder(r) m := map[string]map[string]bool{} if err := d.Decode(&m); err != nil { r.Seek(0, 0) // Allow parsing old arguments in slice format. // Because other libraries might be sending them in this format. deprecated := map[string][]string{} if deprecatedErr := d.Decode(&deprecated); deprecatedErr == nil { m = deprecatedArgs(deprecated) } else { return NewArgs(), err } } return Args{m}, nil } // Get returns the list of values associates with a field. // It returns a slice of strings to keep backwards compatibility with old code. func (filters Args) Get(field string) []string { values := filters.fields[field] if values == nil { return make([]string, 0) } slice := make([]string, 0, len(values)) for key := range values { slice = append(slice, key) } return slice } // Add adds a new value to a filter field. func (filters Args) Add(name, value string) { if _, ok := filters.fields[name]; ok { filters.fields[name][value] = true } else { filters.fields[name] = map[string]bool{value: true} } } // Del removes a value from a filter field. func (filters Args) Del(name, value string) { if _, ok := filters.fields[name]; ok { delete(filters.fields[name], value) } } // Len returns the number of fields in the arguments. func (filters Args) Len() int { return len(filters.fields) } // MatchKVList returns true if the values for the specified field matches the ones // from the sources. // e.g. given Args are {'label': {'label1=1','label2=1'}, 'image.name', {'ubuntu'}}, // field is 'label' and sources are {'label1': '1', 'label2': '2'} // it returns true. func (filters Args) MatchKVList(field string, sources map[string]string) bool { fieldValues := filters.fields[field] //do not filter if there is no filter set or cannot determine filter if len(fieldValues) == 0 { return true } if sources == nil || len(sources) == 0 { return false } for name2match := range fieldValues { testKV := strings.SplitN(name2match, "=", 2) v, ok := sources[testKV[0]] if !ok { return false } if len(testKV) == 2 && testKV[1] != v { return false } } return true } // Match returns true if the values for the specified field matches the source string // e.g. given Args are {'label': {'label1=1','label2=1'}, 'image.name', {'ubuntu'}}, // field is 'image.name' and source is 'ubuntu' // it returns true. func (filters Args) Match(field, source string) bool { if filters.ExactMatch(field, source) { return true } fieldValues := filters.fields[field] for name2match := range fieldValues { match, err := regexp.MatchString(name2match, source) if err != nil { continue } if match { return true } } return false } // ExactMatch returns true if the source matches exactly one of the filters. func (filters Args) ExactMatch(field, source string) bool { fieldValues, ok := filters.fields[field] //do not filter if there is no filter set or cannot determine filter if !ok || len(fieldValues) == 0 { return true } // try to match full name value to avoid O(N) regular expression matching return fieldValues[source] } // UniqueExactMatch returns true if there is only one filter and the source matches exactly this one. func (filters Args) UniqueExactMatch(field, source string) bool { fieldValues := filters.fields[field] //do not filter if there is no filter set or cannot determine filter if len(fieldValues) == 0 { return true } if len(filters.fields[field]) != 1 { return false } // try to match full name value to avoid O(N) regular expression matching return fieldValues[source] } // FuzzyMatch returns true if the source matches exactly one of the filters, // or the source has one of the filters as a prefix. func (filters Args) FuzzyMatch(field, source string) bool { if filters.ExactMatch(field, source) { return true } fieldValues := filters.fields[field] for prefix := range fieldValues { if strings.HasPrefix(source, prefix) { return true } } return false } // Include returns true if the name of the field to filter is in the filters. func (filters Args) Include(field string) bool { _, ok := filters.fields[field] return ok } // Validate ensures that all the fields in the filter are valid. // It returns an error as soon as it finds an invalid field. func (filters Args) Validate(accepted map[string]bool) error { for name := range filters.fields { if !accepted[name] { return fmt.Errorf("Invalid filter '%s'", name) } } return nil } // WalkValues iterates over the list of filtered values for a field. // It stops the iteration if it finds an error and it returns that error. func (filters Args) WalkValues(field string, op func(value string) error) error { if _, ok := filters.fields[field]; !ok { return nil } for v := range filters.fields[field] { if err := op(v); err != nil { return err } } return nil } func deprecatedArgs(d map[string][]string) map[string]map[string]bool { m := map[string]map[string]bool{} for k, v := range d { values := map[string]bool{} for _, vv := range v { values[vv] = true } m[k] = values } return m } func convertArgsToSlice(f map[string]map[string]bool) map[string][]string { m := map[string][]string{} for k, v := range f { values := []string{} for kk := range v { if v[kk] { values = append(values, kk) } } m[k] = values } return m } engine-api-0.4.0/types/filters/parse_test.go000066400000000000000000000243521274620210500210500ustar00rootroot00000000000000package filters import ( "fmt" "testing" ) func TestParseArgs(t *testing.T) { // equivalent of `docker ps -f 'created=today' -f 'image.name=ubuntu*' -f 'image.name=*untu'` flagArgs := []string{ "created=today", "image.name=ubuntu*", "image.name=*untu", } var ( args = NewArgs() err error ) for i := range flagArgs { args, err = ParseFlag(flagArgs[i], args) if err != nil { t.Errorf("failed to parse %s: %s", flagArgs[i], err) } } if len(args.Get("created")) != 1 { t.Errorf("failed to set this arg") } if len(args.Get("image.name")) != 2 { t.Errorf("the args should have collapsed") } } func TestParseArgsEdgeCase(t *testing.T) { var filters Args args, err := ParseFlag("", filters) if err != nil { t.Fatal(err) } if args.Len() != 0 { t.Fatalf("Expected an empty Args (map), got %v", args) } if args, err = ParseFlag("anything", args); err == nil || err != ErrBadFormat { t.Fatalf("Expected ErrBadFormat, got %v", err) } } func TestToParam(t *testing.T) { fields := map[string]map[string]bool{ "created": {"today": true}, "image.name": {"ubuntu*": true, "*untu": true}, } a := Args{fields: fields} _, err := ToParam(a) if err != nil { t.Errorf("failed to marshal the filters: %s", err) } } func TestToParamWithVersion(t *testing.T) { fields := map[string]map[string]bool{ "created": {"today": true}, "image.name": {"ubuntu*": true, "*untu": true}, } a := Args{fields: fields} str1, err := ToParamWithVersion("1.21", a) if err != nil { t.Errorf("failed to marshal the filters with version < 1.22: %s", err) } str2, err := ToParamWithVersion("1.22", a) if err != nil { t.Errorf("failed to marshal the filters with version >= 1.22: %s", err) } if str1 != `{"created":["today"],"image.name":["*untu","ubuntu*"]}` && str1 != `{"created":["today"],"image.name":["ubuntu*","*untu"]}` { t.Errorf("incorrectly marshaled the filters: %s", str1) } if str2 != `{"created":{"today":true},"image.name":{"*untu":true,"ubuntu*":true}}` && str2 != `{"created":{"today":true},"image.name":{"ubuntu*":true,"*untu":true}}` { t.Errorf("incorrectly marshaled the filters: %s", str2) } } func TestFromParam(t *testing.T) { invalids := []string{ "anything", "['a','list']", "{'key': 'value'}", `{"key": "value"}`, } valid := map[*Args][]string{ &Args{fields: map[string]map[string]bool{"key": {"value": true}}}: { `{"key": ["value"]}`, `{"key": {"value": true}}`, }, &Args{fields: map[string]map[string]bool{"key": {"value1": true, "value2": true}}}: { `{"key": ["value1", "value2"]}`, `{"key": {"value1": true, "value2": true}}`, }, &Args{fields: map[string]map[string]bool{"key1": {"value1": true}, "key2": {"value2": true}}}: { `{"key1": ["value1"], "key2": ["value2"]}`, `{"key1": {"value1": true}, "key2": {"value2": true}}`, }, } for _, invalid := range invalids { if _, err := FromParam(invalid); err == nil { t.Fatalf("Expected an error with %v, got nothing", invalid) } } for expectedArgs, matchers := range valid { for _, json := range matchers { args, err := FromParam(json) if err != nil { t.Fatal(err) } if args.Len() != expectedArgs.Len() { t.Fatalf("Expected %v, go %v", expectedArgs, args) } for key, expectedValues := range expectedArgs.fields { values := args.Get(key) if len(values) != len(expectedValues) { t.Fatalf("Expected %v, go %v", expectedArgs, args) } for _, v := range values { if !expectedValues[v] { t.Fatalf("Expected %v, go %v", expectedArgs, args) } } } } } } func TestEmpty(t *testing.T) { a := Args{} v, err := ToParam(a) if err != nil { t.Errorf("failed to marshal the filters: %s", err) } v1, err := FromParam(v) if err != nil { t.Errorf("%s", err) } if a.Len() != v1.Len() { t.Errorf("these should both be empty sets") } } func TestArgsMatchKVListEmptySources(t *testing.T) { args := NewArgs() if !args.MatchKVList("created", map[string]string{}) { t.Fatalf("Expected true for (%v,created), got true", args) } args = Args{map[string]map[string]bool{"created": {"today": true}}} if args.MatchKVList("created", map[string]string{}) { t.Fatalf("Expected false for (%v,created), got true", args) } } func TestArgsMatchKVList(t *testing.T) { // Not empty sources sources := map[string]string{ "key1": "value1", "key2": "value2", "key3": "value3", } matches := map[*Args]string{ &Args{}: "field", &Args{map[string]map[string]bool{ "created": map[string]bool{"today": true}, "labels": map[string]bool{"key1": true}}, }: "labels", &Args{map[string]map[string]bool{ "created": map[string]bool{"today": true}, "labels": map[string]bool{"key1=value1": true}}, }: "labels", } for args, field := range matches { if args.MatchKVList(field, sources) != true { t.Fatalf("Expected true for %v on %v, got false", sources, args) } } differs := map[*Args]string{ &Args{map[string]map[string]bool{ "created": map[string]bool{"today": true}}, }: "created", &Args{map[string]map[string]bool{ "created": map[string]bool{"today": true}, "labels": map[string]bool{"key4": true}}, }: "labels", &Args{map[string]map[string]bool{ "created": map[string]bool{"today": true}, "labels": map[string]bool{"key1=value3": true}}, }: "labels", } for args, field := range differs { if args.MatchKVList(field, sources) != false { t.Fatalf("Expected false for %v on %v, got true", sources, args) } } } func TestArgsMatch(t *testing.T) { source := "today" matches := map[*Args]string{ &Args{}: "field", &Args{map[string]map[string]bool{ "created": map[string]bool{"today": true}}, }: "today", &Args{map[string]map[string]bool{ "created": map[string]bool{"to*": true}}, }: "created", &Args{map[string]map[string]bool{ "created": map[string]bool{"to(.*)": true}}, }: "created", &Args{map[string]map[string]bool{ "created": map[string]bool{"tod": true}}, }: "created", &Args{map[string]map[string]bool{ "created": map[string]bool{"anyting": true, "to*": true}}, }: "created", } for args, field := range matches { if args.Match(field, source) != true { t.Fatalf("Expected true for %v on %v, got false", source, args) } } differs := map[*Args]string{ &Args{map[string]map[string]bool{ "created": map[string]bool{"tomorrow": true}}, }: "created", &Args{map[string]map[string]bool{ "created": map[string]bool{"to(day": true}}, }: "created", &Args{map[string]map[string]bool{ "created": map[string]bool{"tom(.*)": true}}, }: "created", &Args{map[string]map[string]bool{ "created": map[string]bool{"tom": true}}, }: "created", &Args{map[string]map[string]bool{ "created": map[string]bool{"today1": true}, "labels": map[string]bool{"today": true}}, }: "created", } for args, field := range differs { if args.Match(field, source) != false { t.Fatalf("Expected false for %v on %v, got true", source, args) } } } func TestAdd(t *testing.T) { f := NewArgs() f.Add("status", "running") v := f.fields["status"] if len(v) != 1 || !v["running"] { t.Fatalf("Expected to include a running status, got %v", v) } f.Add("status", "paused") if len(v) != 2 || !v["paused"] { t.Fatalf("Expected to include a paused status, got %v", v) } } func TestDel(t *testing.T) { f := NewArgs() f.Add("status", "running") f.Del("status", "running") v := f.fields["status"] if v["running"] { t.Fatalf("Expected to not include a running status filter, got true") } } func TestLen(t *testing.T) { f := NewArgs() if f.Len() != 0 { t.Fatalf("Expected to not include any field") } f.Add("status", "running") if f.Len() != 1 { t.Fatalf("Expected to include one field") } } func TestExactMatch(t *testing.T) { f := NewArgs() if !f.ExactMatch("status", "running") { t.Fatalf("Expected to match `running` when there are no filters, got false") } f.Add("status", "running") f.Add("status", "pause*") if !f.ExactMatch("status", "running") { t.Fatalf("Expected to match `running` with one of the filters, got false") } if f.ExactMatch("status", "paused") { t.Fatalf("Expected to not match `paused` with one of the filters, got true") } } func TestOnlyOneExactMatch(t *testing.T) { f := NewArgs() if !f.UniqueExactMatch("status", "running") { t.Fatalf("Expected to match `running` when there are no filters, got false") } f.Add("status", "running") if !f.UniqueExactMatch("status", "running") { t.Fatalf("Expected to match `running` with one of the filters, got false") } if f.UniqueExactMatch("status", "paused") { t.Fatalf("Expected to not match `paused` with one of the filters, got true") } f.Add("status", "pause") if f.UniqueExactMatch("status", "running") { t.Fatalf("Expected to not match only `running` with two filters, got true") } } func TestInclude(t *testing.T) { f := NewArgs() if f.Include("status") { t.Fatalf("Expected to not include a status key, got true") } f.Add("status", "running") if !f.Include("status") { t.Fatalf("Expected to include a status key, got false") } } func TestValidate(t *testing.T) { f := NewArgs() f.Add("status", "running") valid := map[string]bool{ "status": true, "dangling": true, } if err := f.Validate(valid); err != nil { t.Fatal(err) } f.Add("bogus", "running") if err := f.Validate(valid); err == nil { t.Fatalf("Expected to return an error, got nil") } } func TestWalkValues(t *testing.T) { f := NewArgs() f.Add("status", "running") f.Add("status", "paused") f.WalkValues("status", func(value string) error { if value != "running" && value != "paused" { t.Fatalf("Unexpected value %s", value) } return nil }) err := f.WalkValues("status", func(value string) error { return fmt.Errorf("return") }) if err == nil { t.Fatalf("Expected to get an error, got nil") } err = f.WalkValues("foo", func(value string) error { return fmt.Errorf("return") }) if err != nil { t.Fatalf("Expected to not iterate when the field doesn't exist, got %v", err) } } func TestFuzzyMatch(t *testing.T) { f := NewArgs() f.Add("container", "foo") cases := map[string]bool{ "foo": true, "foobar": true, "barfoo": false, "bar": false, } for source, match := range cases { got := f.FuzzyMatch("container", source) if got != match { t.Fatalf("Expected %v, got %v: %s", match, got, source) } } } engine-api-0.4.0/types/network/000077500000000000000000000000001274620210500163635ustar00rootroot00000000000000engine-api-0.4.0/types/network/network.go000066400000000000000000000030511274620210500204020ustar00rootroot00000000000000package network // Address represents an IP address type Address struct { Addr string PrefixLen int } // IPAM represents IP Address Management type IPAM struct { Driver string Options map[string]string //Per network IPAM driver options Config []IPAMConfig } // IPAMConfig represents IPAM configurations type IPAMConfig struct { Subnet string `json:",omitempty"` IPRange string `json:",omitempty"` Gateway string `json:",omitempty"` AuxAddress map[string]string `json:"AuxiliaryAddresses,omitempty"` } // EndpointIPAMConfig represents IPAM configurations for the endpoint type EndpointIPAMConfig struct { IPv4Address string `json:",omitempty"` IPv6Address string `json:",omitempty"` LinkLocalIPs []string `json:",omitempty"` } // EndpointSettings stores the network endpoint details type EndpointSettings struct { // Configurations IPAMConfig *EndpointIPAMConfig Links []string Aliases []string // Operational data NetworkID string EndpointID string Gateway string IPAddress string IPPrefixLen int IPv6Gateway string GlobalIPv6Address string GlobalIPv6PrefixLen int MacAddress string } // NetworkingConfig represents the container's networking configuration for each of its interfaces // Carries the networking configs specified in the `docker run` and `docker network connect` commands type NetworkingConfig struct { EndpointsConfig map[string]*EndpointSettings // Endpoint configs for each connecting network } engine-api-0.4.0/types/plugin.go000066400000000000000000000102441274620210500165200ustar00rootroot00000000000000// +build experimental package types import ( "encoding/json" "fmt" ) // PluginInstallOptions holds parameters to install a plugin. type PluginInstallOptions struct { Disabled bool AcceptAllPermissions bool RegistryAuth string // RegistryAuth is the base64 encoded credentials for the registry PrivilegeFunc RequestPrivilegeFunc AcceptPermissionsFunc func(PluginPrivileges) (bool, error) } // PluginConfig represents the values of settings potentially modifiable by a user type PluginConfig struct { Mounts []PluginMount Env []string Args []string Devices []PluginDevice } // Plugin represents a Docker plugin for the remote API type Plugin struct { ID string `json:"Id,omitempty"` Name string Tag string Active bool Config PluginConfig Manifest PluginManifest } // PluginsListResponse contains the response for the remote API type PluginsListResponse []*Plugin const ( authzDriver = "AuthzDriver" graphDriver = "GraphDriver" ipamDriver = "IpamDriver" networkDriver = "NetworkDriver" volumeDriver = "VolumeDriver" ) // PluginInterfaceType represents a type that a plugin implements. type PluginInterfaceType struct { Prefix string // This is always "docker" Capability string // Capability should be validated against the above list. Version string // Plugin API version. Depends on the capability } // UnmarshalJSON implements json.Unmarshaler for PluginInterfaceType func (t *PluginInterfaceType) UnmarshalJSON(p []byte) error { versionIndex := len(p) prefixIndex := 0 if len(p) < 2 || p[0] != '"' || p[len(p)-1] != '"' { return fmt.Errorf("%q is not a plugin interface type", p) } p = p[1 : len(p)-1] loop: for i, b := range p { switch b { case '.': prefixIndex = i case '/': versionIndex = i break loop } } t.Prefix = string(p[:prefixIndex]) t.Capability = string(p[prefixIndex+1 : versionIndex]) if versionIndex < len(p) { t.Version = string(p[versionIndex+1:]) } return nil } // MarshalJSON implements json.Marshaler for PluginInterfaceType func (t *PluginInterfaceType) MarshalJSON() ([]byte, error) { return json.Marshal(t.String()) } // String implements fmt.Stringer for PluginInterfaceType func (t PluginInterfaceType) String() string { return fmt.Sprintf("%s.%s/%s", t.Prefix, t.Capability, t.Version) } // PluginInterface describes the interface between Docker and plugin type PluginInterface struct { Types []PluginInterfaceType Socket string } // PluginSetting is to be embedded in other structs, if they are supposed to be // modifiable by the user. type PluginSetting struct { Name string Description string Settable []string } // PluginNetwork represents the network configuration for a plugin type PluginNetwork struct { Type string } // PluginMount represents the mount configuration for a plugin type PluginMount struct { PluginSetting Source *string Destination string Type string Options []string } // PluginEnv represents an environment variable for a plugin type PluginEnv struct { PluginSetting Value *string } // PluginArgs represents the command line arguments for a plugin type PluginArgs struct { PluginSetting Value []string } // PluginDevice represents a device for a plugin type PluginDevice struct { PluginSetting Path *string } // PluginUser represents the user for the plugin's process type PluginUser struct { UID uint32 `json:"Uid,omitempty"` GID uint32 `json:"Gid,omitempty"` } // PluginManifest represents the manifest of a plugin type PluginManifest struct { ManifestVersion string Description string Documentation string Interface PluginInterface Entrypoint []string Workdir string User PluginUser `json:",omitempty"` Network PluginNetwork Capabilities []string Mounts []PluginMount Devices []PluginDevice Env []PluginEnv Args PluginArgs } // PluginPrivilege describes a permission the user has to accept // upon installing a plugin. type PluginPrivilege struct { Name string Description string Value []string } // PluginPrivileges is a list of PluginPrivilege type PluginPrivileges []PluginPrivilege engine-api-0.4.0/types/reference/000077500000000000000000000000001274620210500166305ustar00rootroot00000000000000engine-api-0.4.0/types/reference/image_reference.go000066400000000000000000000016311274620210500222600ustar00rootroot00000000000000package reference import ( distreference "github.com/docker/distribution/reference" ) // Parse parses the given references and returns the repository and // tag (if present) from it. If there is an error during parsing, it will // return an error. func Parse(ref string) (string, string, error) { distributionRef, err := distreference.ParseNamed(ref) if err != nil { return "", "", err } tag := GetTagFromNamedRef(distributionRef) return distributionRef.Name(), tag, nil } // GetTagFromNamedRef returns a tag from the specified reference. // This function is necessary as long as the docker "server" api makes the distinction between repository // and tags. func GetTagFromNamedRef(ref distreference.Named) string { var tag string switch x := ref.(type) { case distreference.Digested: tag = x.Digest().String() case distreference.NamedTagged: tag = x.Tag() default: tag = "latest" } return tag } engine-api-0.4.0/types/reference/image_reference_test.go000066400000000000000000000034151274620210500233210ustar00rootroot00000000000000package reference import ( "testing" ) func TestParse(t *testing.T) { testCases := []struct { ref string expectedName string expectedTag string expectedError bool }{ { ref: "", expectedName: "", expectedTag: "", expectedError: true, }, { ref: "repository", expectedName: "repository", expectedTag: "latest", expectedError: false, }, { ref: "repository:tag", expectedName: "repository", expectedTag: "tag", expectedError: false, }, { ref: "test.com/repository", expectedName: "test.com/repository", expectedTag: "latest", expectedError: false, }, { ref: "test.com:5000/test/repository", expectedName: "test.com:5000/test/repository", expectedTag: "latest", expectedError: false, }, { ref: "test.com:5000/repo@sha256:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", expectedName: "test.com:5000/repo", expectedTag: "sha256:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", expectedError: false, }, { ref: "test.com:5000/repo:tag@sha256:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", expectedName: "test.com:5000/repo", expectedTag: "sha256:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", expectedError: false, }, } for _, c := range testCases { name, tag, err := Parse(c.ref) if err != nil && c.expectedError { continue } else if err != nil { t.Fatalf("error with %s: %s", c.ref, err.Error()) } if name != c.expectedName { t.Fatalf("expected name %s, got %s", c.expectedName, name) } if tag != c.expectedTag { t.Fatalf("expected tag %s, got %s", c.expectedTag, tag) } } } engine-api-0.4.0/types/registry/000077500000000000000000000000001274620210500165425ustar00rootroot00000000000000engine-api-0.4.0/types/registry/registry.go000066400000000000000000000060001274620210500207350ustar00rootroot00000000000000package registry import ( "encoding/json" "net" ) // ServiceConfig stores daemon registry services configuration. type ServiceConfig struct { InsecureRegistryCIDRs []*NetIPNet `json:"InsecureRegistryCIDRs"` IndexConfigs map[string]*IndexInfo `json:"IndexConfigs"` Mirrors []string } // NetIPNet is the net.IPNet type, which can be marshalled and // unmarshalled to JSON type NetIPNet net.IPNet // MarshalJSON returns the JSON representation of the IPNet func (ipnet *NetIPNet) MarshalJSON() ([]byte, error) { return json.Marshal((*net.IPNet)(ipnet).String()) } // UnmarshalJSON sets the IPNet from a byte array of JSON func (ipnet *NetIPNet) UnmarshalJSON(b []byte) (err error) { var ipnetStr string if err = json.Unmarshal(b, &ipnetStr); err == nil { var cidr *net.IPNet if _, cidr, err = net.ParseCIDR(ipnetStr); err == nil { *ipnet = NetIPNet(*cidr) } } return } // IndexInfo contains information about a registry // // RepositoryInfo Examples: // { // "Index" : { // "Name" : "docker.io", // "Mirrors" : ["https://registry-2.docker.io/v1/", "https://registry-3.docker.io/v1/"], // "Secure" : true, // "Official" : true, // }, // "RemoteName" : "library/debian", // "LocalName" : "debian", // "CanonicalName" : "docker.io/debian" // "Official" : true, // } // // { // "Index" : { // "Name" : "127.0.0.1:5000", // "Mirrors" : [], // "Secure" : false, // "Official" : false, // }, // "RemoteName" : "user/repo", // "LocalName" : "127.0.0.1:5000/user/repo", // "CanonicalName" : "127.0.0.1:5000/user/repo", // "Official" : false, // } type IndexInfo struct { // Name is the name of the registry, such as "docker.io" Name string // Mirrors is a list of mirrors, expressed as URIs Mirrors []string // Secure is set to false if the registry is part of the list of // insecure registries. Insecure registries accept HTTP and/or accept // HTTPS with certificates from unknown CAs. Secure bool // Official indicates whether this is an official registry Official bool } // SearchResult describes a search result returned from a registry type SearchResult struct { // StarCount indicates the number of stars this repository has StarCount int `json:"star_count"` // IsOfficial is true if the result is from an official repository. IsOfficial bool `json:"is_official"` // Name is the name of the repository Name string `json:"name"` // IsAutomated indicates whether the result is automated IsAutomated bool `json:"is_automated"` // Description is a textual description of the repository Description string `json:"description"` } // SearchResults lists a collection search results returned from a registry type SearchResults struct { // Query contains the query string that generated the search results Query string `json:"query"` // NumResults indicates the number of results the query returned NumResults int `json:"num_results"` // Results is a slice containing the actual results for the search Results []SearchResult `json:"results"` } engine-api-0.4.0/types/seccomp.go000066400000000000000000000043151274620210500166550ustar00rootroot00000000000000package types // Seccomp represents the config for a seccomp profile for syscall restriction. type Seccomp struct { DefaultAction Action `json:"defaultAction"` Architectures []Arch `json:"architectures"` Syscalls []*Syscall `json:"syscalls"` } // Arch used for additional architectures type Arch string // Additional architectures permitted to be used for system calls // By default only the native architecture of the kernel is permitted const ( ArchX86 Arch = "SCMP_ARCH_X86" ArchX86_64 Arch = "SCMP_ARCH_X86_64" ArchX32 Arch = "SCMP_ARCH_X32" ArchARM Arch = "SCMP_ARCH_ARM" ArchAARCH64 Arch = "SCMP_ARCH_AARCH64" ArchMIPS Arch = "SCMP_ARCH_MIPS" ArchMIPS64 Arch = "SCMP_ARCH_MIPS64" ArchMIPS64N32 Arch = "SCMP_ARCH_MIPS64N32" ArchMIPSEL Arch = "SCMP_ARCH_MIPSEL" ArchMIPSEL64 Arch = "SCMP_ARCH_MIPSEL64" ArchMIPSEL64N32 Arch = "SCMP_ARCH_MIPSEL64N32" ArchPPC Arch = "SCMP_ARCH_PPC" ArchPPC64 Arch = "SCMP_ARCH_PPC64" ArchPPC64LE Arch = "SCMP_ARCH_PPC64LE" ArchS390 Arch = "SCMP_ARCH_S390" ArchS390X Arch = "SCMP_ARCH_S390X" ) // Action taken upon Seccomp rule match type Action string // Define actions for Seccomp rules const ( ActKill Action = "SCMP_ACT_KILL" ActTrap Action = "SCMP_ACT_TRAP" ActErrno Action = "SCMP_ACT_ERRNO" ActTrace Action = "SCMP_ACT_TRACE" ActAllow Action = "SCMP_ACT_ALLOW" ) // Operator used to match syscall arguments in Seccomp type Operator string // Define operators for syscall arguments in Seccomp const ( OpNotEqual Operator = "SCMP_CMP_NE" OpLessThan Operator = "SCMP_CMP_LT" OpLessEqual Operator = "SCMP_CMP_LE" OpEqualTo Operator = "SCMP_CMP_EQ" OpGreaterEqual Operator = "SCMP_CMP_GE" OpGreaterThan Operator = "SCMP_CMP_GT" OpMaskedEqual Operator = "SCMP_CMP_MASKED_EQ" ) // Arg used for matching specific syscall arguments in Seccomp type Arg struct { Index uint `json:"index"` Value uint64 `json:"value"` ValueTwo uint64 `json:"valueTwo"` Op Operator `json:"op"` } // Syscall is used to match a syscall in Seccomp type Syscall struct { Name string `json:"name"` Action Action `json:"action"` Args []*Arg `json:"args"` } engine-api-0.4.0/types/stats.go000066400000000000000000000102241274620210500163560ustar00rootroot00000000000000// Package types is used for API stability in the types and response to the // consumers of the API stats endpoint. package types import "time" // ThrottlingData stores CPU throttling stats of one running container type ThrottlingData struct { // Number of periods with throttling active Periods uint64 `json:"periods"` // Number of periods when the container hits its throttling limit. ThrottledPeriods uint64 `json:"throttled_periods"` // Aggregate time the container was throttled for in nanoseconds. ThrottledTime uint64 `json:"throttled_time"` } // CPUUsage stores All CPU stats aggregated since container inception. type CPUUsage struct { // Total CPU time consumed. // Units: nanoseconds. TotalUsage uint64 `json:"total_usage"` // Total CPU time consumed per core. // Units: nanoseconds. PercpuUsage []uint64 `json:"percpu_usage"` // Time spent by tasks of the cgroup in kernel mode. // Units: nanoseconds. UsageInKernelmode uint64 `json:"usage_in_kernelmode"` // Time spent by tasks of the cgroup in user mode. // Units: nanoseconds. UsageInUsermode uint64 `json:"usage_in_usermode"` } // CPUStats aggregates and wraps all CPU related info of container type CPUStats struct { CPUUsage CPUUsage `json:"cpu_usage"` SystemUsage uint64 `json:"system_cpu_usage"` ThrottlingData ThrottlingData `json:"throttling_data,omitempty"` } // MemoryStats aggregates All memory stats since container inception type MemoryStats struct { // current res_counter usage for memory Usage uint64 `json:"usage"` // maximum usage ever recorded. MaxUsage uint64 `json:"max_usage"` // TODO(vishh): Export these as stronger types. // all the stats exported via memory.stat. Stats map[string]uint64 `json:"stats"` // number of times memory usage hits limits. Failcnt uint64 `json:"failcnt"` Limit uint64 `json:"limit"` } // BlkioStatEntry is one small entity to store a piece of Blkio stats // TODO Windows: This can be factored out type BlkioStatEntry struct { Major uint64 `json:"major"` Minor uint64 `json:"minor"` Op string `json:"op"` Value uint64 `json:"value"` } // BlkioStats stores All IO service stats for data read and write // TODO Windows: This can be factored out type BlkioStats struct { // number of bytes transferred to and from the block device IoServiceBytesRecursive []BlkioStatEntry `json:"io_service_bytes_recursive"` IoServicedRecursive []BlkioStatEntry `json:"io_serviced_recursive"` IoQueuedRecursive []BlkioStatEntry `json:"io_queue_recursive"` IoServiceTimeRecursive []BlkioStatEntry `json:"io_service_time_recursive"` IoWaitTimeRecursive []BlkioStatEntry `json:"io_wait_time_recursive"` IoMergedRecursive []BlkioStatEntry `json:"io_merged_recursive"` IoTimeRecursive []BlkioStatEntry `json:"io_time_recursive"` SectorsRecursive []BlkioStatEntry `json:"sectors_recursive"` } // NetworkStats aggregates All network stats of one container // TODO Windows: This will require refactoring type NetworkStats struct { RxBytes uint64 `json:"rx_bytes"` RxPackets uint64 `json:"rx_packets"` RxErrors uint64 `json:"rx_errors"` RxDropped uint64 `json:"rx_dropped"` TxBytes uint64 `json:"tx_bytes"` TxPackets uint64 `json:"tx_packets"` TxErrors uint64 `json:"tx_errors"` TxDropped uint64 `json:"tx_dropped"` } // PidsStats contains the stats of a container's pids type PidsStats struct { // Current is the number of pids in the cgroup Current uint64 `json:"current,omitempty"` // Limit is the hard limit on the number of pids in the cgroup. // A "Limit" of 0 means that there is no limit. Limit uint64 `json:"limit,omitempty"` } // Stats is Ultimate struct aggregating all types of stats of one container type Stats struct { Read time.Time `json:"read"` PreCPUStats CPUStats `json:"precpu_stats,omitempty"` CPUStats CPUStats `json:"cpu_stats,omitempty"` MemoryStats MemoryStats `json:"memory_stats,omitempty"` BlkioStats BlkioStats `json:"blkio_stats,omitempty"` PidsStats PidsStats `json:"pids_stats,omitempty"` } // StatsJSON is newly used Networks type StatsJSON struct { Stats // Networks request version >=1.21 Networks map[string]NetworkStats `json:"networks,omitempty"` } engine-api-0.4.0/types/strslice/000077500000000000000000000000001274620210500165225ustar00rootroot00000000000000engine-api-0.4.0/types/strslice/strslice.go000066400000000000000000000014041274620210500207000ustar00rootroot00000000000000package strslice import "encoding/json" // StrSlice represents a string or an array of strings. // We need to override the json decoder to accept both options. type StrSlice []string // UnmarshalJSON decodes the byte slice whether it's a string or an array of // strings. This method is needed to implement json.Unmarshaler. func (e *StrSlice) UnmarshalJSON(b []byte) error { if len(b) == 0 { // With no input, we preserve the existing value by returning nil and // leaving the target alone. This allows defining default values for // the type. return nil } p := make([]string, 0, 1) if err := json.Unmarshal(b, &p); err != nil { var s string if err := json.Unmarshal(b, &s); err != nil { return err } p = append(p, s) } *e = p return nil } engine-api-0.4.0/types/strslice/strslice_test.go000066400000000000000000000035701274620210500217450ustar00rootroot00000000000000package strslice import ( "encoding/json" "reflect" "testing" ) func TestStrSliceMarshalJSON(t *testing.T) { for _, testcase := range []struct { input StrSlice expected string }{ // MADNESS(stevvooe): No clue why nil would be "" but empty would be // "null". Had to make a change here that may affect compatibility. {input: nil, expected: "null"}, {StrSlice{}, "[]"}, {StrSlice{"/bin/sh", "-c", "echo"}, `["/bin/sh","-c","echo"]`}, } { data, err := json.Marshal(testcase.input) if err != nil { t.Fatal(err) } if string(data) != testcase.expected { t.Fatalf("%#v: expected %v, got %v", testcase.input, testcase.expected, string(data)) } } } func TestStrSliceUnmarshalJSON(t *testing.T) { parts := map[string][]string{ "": {"default", "values"}, "[]": {}, `["/bin/sh","-c","echo"]`: {"/bin/sh", "-c", "echo"}, } for json, expectedParts := range parts { strs := StrSlice{"default", "values"} if err := strs.UnmarshalJSON([]byte(json)); err != nil { t.Fatal(err) } actualParts := []string(strs) if !reflect.DeepEqual(actualParts, expectedParts) { t.Fatalf("%#v: expected %v, got %v", json, expectedParts, actualParts) } } } func TestStrSliceUnmarshalString(t *testing.T) { var e StrSlice echo, err := json.Marshal("echo") if err != nil { t.Fatal(err) } if err := json.Unmarshal(echo, &e); err != nil { t.Fatal(err) } if len(e) != 1 { t.Fatalf("expected 1 element after unmarshal: %q", e) } if e[0] != "echo" { t.Fatalf("expected `echo`, got: %q", e[0]) } } func TestStrSliceUnmarshalSlice(t *testing.T) { var e StrSlice echo, err := json.Marshal([]string{"echo"}) if err != nil { t.Fatal(err) } if err := json.Unmarshal(echo, &e); err != nil { t.Fatal(err) } if len(e) != 1 { t.Fatalf("expected 1 element after unmarshal: %q", e) } if e[0] != "echo" { t.Fatalf("expected `echo`, got: %q", e[0]) } } engine-api-0.4.0/types/swarm/000077500000000000000000000000001274620210500160235ustar00rootroot00000000000000engine-api-0.4.0/types/swarm/common.go000066400000000000000000000010061274620210500176370ustar00rootroot00000000000000package swarm import "time" // Version represent the internal object version. type Version struct { Index uint64 `json:",omitempty"` } // Meta is base object inherited by most of the other once. type Meta struct { Version Version `json:",omitempty"` CreatedAt time.Time `json:",omitempty"` UpdatedAt time.Time `json:",omitempty"` } // Annotations represents how to describe an object. type Annotations struct { Name string `json:",omitempty"` Labels map[string]string `json:",omitempty"` } engine-api-0.4.0/types/swarm/container.go000066400000000000000000000041571274620210500203430ustar00rootroot00000000000000package swarm import "time" // ContainerSpec represents the spec of a container. type ContainerSpec struct { Image string `json:",omitempty"` Labels map[string]string `json:",omitempty"` Command []string `json:",omitempty"` Args []string `json:",omitempty"` Env []string `json:",omitempty"` Dir string `json:",omitempty"` User string `json:",omitempty"` Mounts []Mount `json:",omitempty"` StopGracePeriod *time.Duration `json:",omitempty"` } // MountType represents the type of a mount. type MountType string const ( // MountTypeBind BIND MountTypeBind MountType = "bind" // MountTypeVolume VOLUME MountTypeVolume MountType = "volume" ) // Mount represents a mount (volume). type Mount struct { Type MountType `json:",omitempty"` Source string `json:",omitempty"` Target string `json:",omitempty"` ReadOnly bool `json:",omitempty"` BindOptions *BindOptions `json:",omitempty"` VolumeOptions *VolumeOptions `json:",omitempty"` } // MountPropagation represents the propagation of a mount. type MountPropagation string const ( // MountPropagationRPrivate RPRIVATE MountPropagationRPrivate MountPropagation = "rprivate" // MountPropagationPrivate PRIVATE MountPropagationPrivate MountPropagation = "private" // MountPropagationRShared RSHARED MountPropagationRShared MountPropagation = "rshared" // MountPropagationShared SHARED MountPropagationShared MountPropagation = "shared" // MountPropagationRSlave RSLAVE MountPropagationRSlave MountPropagation = "rslave" // MountPropagationSlave SLAVE MountPropagationSlave MountPropagation = "slave" ) // BindOptions defines options specific to mounts of type "bind". type BindOptions struct { Propagation MountPropagation `json:",omitempty"` } // VolumeOptions represents the options for a mount of type volume. type VolumeOptions struct { NoCopy bool `json:",omitempty"` Labels map[string]string `json:",omitempty"` DriverConfig *Driver `json:",omitempty"` } engine-api-0.4.0/types/swarm/network.go000066400000000000000000000053761274620210500200560ustar00rootroot00000000000000package swarm // Endpoint represents an endpoint. type Endpoint struct { Spec EndpointSpec `json:",omitempty"` Ports []PortConfig `json:",omitempty"` VirtualIPs []EndpointVirtualIP `json:",omitempty"` } // EndpointSpec represents the spec of an endpoint. type EndpointSpec struct { Mode ResolutionMode `json:",omitempty"` Ports []PortConfig `json:",omitempty"` } // ResolutionMode represents a resolution mode. type ResolutionMode string const ( // ResolutionModeVIP VIP ResolutionModeVIP ResolutionMode = "vip" // ResolutionModeDNSRR DNSRR ResolutionModeDNSRR ResolutionMode = "dnsrr" ) // PortConfig represents the config of a port. type PortConfig struct { Name string `json:",omitempty"` Protocol PortConfigProtocol `json:",omitempty"` TargetPort uint32 `json:",omitempty"` PublishedPort uint32 `json:",omitempty"` } // PortConfigProtocol represents the protocol of a port. type PortConfigProtocol string const ( // TODO(stevvooe): These should be used generally, not just for PortConfig. // PortConfigProtocolTCP TCP PortConfigProtocolTCP PortConfigProtocol = "tcp" // PortConfigProtocolUDP UDP PortConfigProtocolUDP PortConfigProtocol = "udp" ) // EndpointVirtualIP represents the virtual ip of a port. type EndpointVirtualIP struct { NetworkID string `json:",omitempty"` Addr string `json:",omitempty"` } // Network represents a network. type Network struct { ID string Meta Spec NetworkSpec `json:",omitempty"` DriverState Driver `json:",omitempty"` IPAMOptions *IPAMOptions `json:",omitempty"` } // NetworkSpec represents the spec of a network. type NetworkSpec struct { Annotations DriverConfiguration *Driver `json:",omitempty"` IPv6Enabled bool `json:",omitempty"` Internal bool `json:",omitempty"` IPAMOptions *IPAMOptions `json:",omitempty"` } // NetworkAttachmentConfig represents the configuration of a network attachment. type NetworkAttachmentConfig struct { Target string `json:",omitempty"` Aliases []string `json:",omitempty"` } // NetworkAttachment represents a network attachment. type NetworkAttachment struct { Network Network `json:",omitempty"` Addresses []string `json:",omitempty"` } // IPAMOptions represents ipam options. type IPAMOptions struct { Driver Driver `json:",omitempty"` Configs []IPAMConfig `json:",omitempty"` } // IPAMConfig represents ipam configuration. type IPAMConfig struct { Subnet string `json:",omitempty"` Range string `json:",omitempty"` Gateway string `json:",omitempty"` } // Driver represents a driver (network/volume). type Driver struct { Name string `json:",omitempty"` Options map[string]string `json:",omitempty"` } engine-api-0.4.0/types/swarm/node.go000066400000000000000000000056431274620210500173070ustar00rootroot00000000000000package swarm // Node represents a node. type Node struct { ID string Meta Spec NodeSpec `json:",omitempty"` Description NodeDescription `json:",omitempty"` Status NodeStatus `json:",omitempty"` ManagerStatus *ManagerStatus `json:",omitempty"` } // NodeSpec represents the spec of a node. type NodeSpec struct { Annotations Role NodeRole `json:",omitempty"` Availability NodeAvailability `json:",omitempty"` } // NodeRole represents the role of a node. type NodeRole string const ( // NodeRoleWorker WORKER NodeRoleWorker NodeRole = "worker" // NodeRoleManager MANAGER NodeRoleManager NodeRole = "manager" ) // NodeAvailability represents the availability of a node. type NodeAvailability string const ( // NodeAvailabilityActive ACTIVE NodeAvailabilityActive NodeAvailability = "active" // NodeAvailabilityPause PAUSE NodeAvailabilityPause NodeAvailability = "pause" // NodeAvailabilityDrain DRAIN NodeAvailabilityDrain NodeAvailability = "drain" ) // NodeDescription represents the description of a node. type NodeDescription struct { Hostname string `json:",omitempty"` Platform Platform `json:",omitempty"` Resources Resources `json:",omitempty"` Engine EngineDescription `json:",omitempty"` } // Platform represents the platfrom (Arch/OS). type Platform struct { Architecture string `json:",omitempty"` OS string `json:",omitempty"` } // EngineDescription represents the description of an engine. type EngineDescription struct { EngineVersion string `json:",omitempty"` Labels map[string]string `json:",omitempty"` Plugins []PluginDescription `json:",omitempty"` } // PluginDescription represents the description of an engine plugin. type PluginDescription struct { Type string `json:",omitempty"` Name string `json:",omitempty"` } // NodeStatus represents the status of a node. type NodeStatus struct { State NodeState `json:",omitempty"` Message string `json:",omitempty"` } // Reachability represents the reachability of a node. type Reachability string const ( // ReachabilityUnknown UNKNOWN ReachabilityUnknown Reachability = "unknown" // ReachabilityUnreachable UNREACHABLE ReachabilityUnreachable Reachability = "unreachable" // ReachabilityReachable REACHABLE ReachabilityReachable Reachability = "reachable" ) // ManagerStatus represents the status of a manager. type ManagerStatus struct { Leader bool `json:",omitempty"` Reachability Reachability `json:",omitempty"` Addr string `json:",omitempty"` } // NodeState represents the state of a node. type NodeState string const ( // NodeStateUnknown UNKNOWN NodeStateUnknown NodeState = "unknown" // NodeStateDown DOWN NodeStateDown NodeState = "down" // NodeStateReady READY NodeStateReady NodeState = "ready" // NodeStateDisconnected DISCONNECTED NodeStateDisconnected NodeState = "disconnected" ) engine-api-0.4.0/types/swarm/service.go000066400000000000000000000041271274620210500200160ustar00rootroot00000000000000package swarm import "time" // Service represents a service. type Service struct { ID string Meta Spec ServiceSpec `json:",omitempty"` Endpoint Endpoint `json:",omitempty"` UpdateStatus UpdateStatus `json:",omitempty"` } // ServiceSpec represents the spec of a service. type ServiceSpec struct { Annotations // TaskTemplate defines how the service should construct new tasks when // orchestrating this service. TaskTemplate TaskSpec `json:",omitempty"` Mode ServiceMode `json:",omitempty"` UpdateConfig *UpdateConfig `json:",omitempty"` Networks []NetworkAttachmentConfig `json:",omitempty"` EndpointSpec *EndpointSpec `json:",omitempty"` } // ServiceMode represents the mode of a service. type ServiceMode struct { Replicated *ReplicatedService `json:",omitempty"` Global *GlobalService `json:",omitempty"` } // UpdateState is the state of a service update. type UpdateState string const ( // UpdateStateUpdating is the updating state. UpdateStateUpdating UpdateState = "updating" // UpdateStatePaused is the paused state. UpdateStatePaused UpdateState = "paused" // UpdateStateCompleted is the completed state. UpdateStateCompleted UpdateState = "completed" ) // UpdateStatus reports the status of a service update. type UpdateStatus struct { State UpdateState `json:",omitempty"` StartedAt time.Time `json:",omitempty"` CompletedAt time.Time `json:",omitempty"` Message string `json:",omitempty"` } // ReplicatedService is a kind of ServiceMode. type ReplicatedService struct { Replicas *uint64 `json:",omitempty"` } // GlobalService is a kind of ServiceMode. type GlobalService struct{} const ( // UpdateFailureActionPause PAUSE UpdateFailureActionPause = "pause" // UpdateFailureActionContinue CONTINUE UpdateFailureActionContinue = "continue" ) // UpdateConfig represents the update configuration. type UpdateConfig struct { Parallelism uint64 `json:",omitempty"` Delay time.Duration `json:",omitempty"` FailureAction string `json:",omitempty"` } engine-api-0.4.0/types/swarm/swarm.go000066400000000000000000000071271274620210500175120ustar00rootroot00000000000000package swarm import "time" // ClusterInfo represents info about a the cluster for outputing in "info" // it contains the same information as "Swarm", but without the JoinTokens type ClusterInfo struct { ID string Meta Spec Spec } // Swarm represents a swarm. type Swarm struct { ClusterInfo JoinTokens JoinTokens } // JoinTokens contains the tokens workers and managers need to join the swarm. type JoinTokens struct { Worker string Manager string } // Spec represents the spec of a swarm. type Spec struct { Annotations Orchestration OrchestrationConfig `json:",omitempty"` Raft RaftConfig `json:",omitempty"` Dispatcher DispatcherConfig `json:",omitempty"` CAConfig CAConfig `json:",omitempty"` TaskDefaults TaskDefaults `json:",omitempty"` } // OrchestrationConfig represents orchestration configuration. type OrchestrationConfig struct { TaskHistoryRetentionLimit int64 `json:",omitempty"` } // TaskDefaults parameterizes cluster-level task creation with default values. type TaskDefaults struct { // LogDriver selects the log driver to use for tasks created in the // orchestrator if unspecified by a service. // // Updating this value will only have an affect on new tasks. Old tasks // will continue use their previously configured log driver until // recreated. LogDriver *Driver `json:",omitempty"` } // RaftConfig represents raft configuration. type RaftConfig struct { SnapshotInterval uint64 `json:",omitempty"` KeepOldSnapshots uint64 `json:",omitempty"` LogEntriesForSlowFollowers uint64 `json:",omitempty"` HeartbeatTick uint32 `json:",omitempty"` ElectionTick uint32 `json:",omitempty"` } // DispatcherConfig represents dispatcher configuration. type DispatcherConfig struct { HeartbeatPeriod uint64 `json:",omitempty"` } // CAConfig represents CA configuration. type CAConfig struct { NodeCertExpiry time.Duration `json:",omitempty"` ExternalCAs []*ExternalCA `json:",omitempty"` } // ExternalCAProtocol represents type of external CA. type ExternalCAProtocol string // ExternalCAProtocolCFSSL CFSSL const ExternalCAProtocolCFSSL ExternalCAProtocol = "cfssl" // ExternalCA defines external CA to be used by the cluster. type ExternalCA struct { Protocol ExternalCAProtocol URL string Options map[string]string `json:",omitempty"` } // InitRequest is the request used to init a swarm. type InitRequest struct { ListenAddr string AdvertiseAddr string ForceNewCluster bool Spec Spec } // JoinRequest is the request used to join a swarm. type JoinRequest struct { ListenAddr string AdvertiseAddr string RemoteAddrs []string JoinToken string // accept by secret } // LocalNodeState represents the state of the local node. type LocalNodeState string const ( // LocalNodeStateInactive INACTIVE LocalNodeStateInactive LocalNodeState = "inactive" // LocalNodeStatePending PENDING LocalNodeStatePending LocalNodeState = "pending" // LocalNodeStateActive ACTIVE LocalNodeStateActive LocalNodeState = "active" // LocalNodeStateError ERROR LocalNodeStateError LocalNodeState = "error" ) // Info represents generic information about swarm. type Info struct { NodeID string NodeAddr string LocalNodeState LocalNodeState ControlAvailable bool Error string RemoteManagers []Peer Nodes int Managers int Cluster ClusterInfo } // Peer represents a peer. type Peer struct { NodeID string Addr string } // UpdateFlags contains flags for SwarmUpdate. type UpdateFlags struct { RotateWorkerToken bool RotateManagerToken bool } engine-api-0.4.0/types/swarm/task.go000066400000000000000000000073121274620210500173170ustar00rootroot00000000000000package swarm import "time" // TaskState represents the state of a task. type TaskState string const ( // TaskStateNew NEW TaskStateNew TaskState = "new" // TaskStateAllocated ALLOCATED TaskStateAllocated TaskState = "allocated" // TaskStatePending PENDING TaskStatePending TaskState = "pending" // TaskStateAssigned ASSIGNED TaskStateAssigned TaskState = "assigned" // TaskStateAccepted ACCEPTED TaskStateAccepted TaskState = "accepted" // TaskStatePreparing PREPARING TaskStatePreparing TaskState = "preparing" // TaskStateReady READY TaskStateReady TaskState = "ready" // TaskStateStarting STARTING TaskStateStarting TaskState = "starting" // TaskStateRunning RUNNING TaskStateRunning TaskState = "running" // TaskStateComplete COMPLETE TaskStateComplete TaskState = "complete" // TaskStateShutdown SHUTDOWN TaskStateShutdown TaskState = "shutdown" // TaskStateFailed FAILED TaskStateFailed TaskState = "failed" // TaskStateRejected REJECTED TaskStateRejected TaskState = "rejected" ) // Task represents a task. type Task struct { ID string Meta Spec TaskSpec `json:",omitempty"` ServiceID string `json:",omitempty"` Slot int `json:",omitempty"` NodeID string `json:",omitempty"` Status TaskStatus `json:",omitempty"` DesiredState TaskState `json:",omitempty"` NetworksAttachments []NetworkAttachment `json:",omitempty"` } // TaskSpec represents the spec of a task. type TaskSpec struct { ContainerSpec ContainerSpec `json:",omitempty"` Resources *ResourceRequirements `json:",omitempty"` RestartPolicy *RestartPolicy `json:",omitempty"` Placement *Placement `json:",omitempty"` // LogDriver specifies the LogDriver to use for tasks created from this // spec. If not present, the one on cluster default on swarm.Spec will be // used, finally falling back to the engine default if not specified. LogDriver *Driver `json:",omitempty"` } // Resources represents resources (CPU/Memory). type Resources struct { NanoCPUs int64 `json:",omitempty"` MemoryBytes int64 `json:",omitempty"` } // ResourceRequirements represents resources requirements. type ResourceRequirements struct { Limits *Resources `json:",omitempty"` Reservations *Resources `json:",omitempty"` } // Placement represents orchestration parameters. type Placement struct { Constraints []string `json:",omitempty"` } // RestartPolicy represents the restart policy. type RestartPolicy struct { Condition RestartPolicyCondition `json:",omitempty"` Delay *time.Duration `json:",omitempty"` MaxAttempts *uint64 `json:",omitempty"` Window *time.Duration `json:",omitempty"` } // RestartPolicyCondition represents when to restart. type RestartPolicyCondition string const ( // RestartPolicyConditionNone NONE RestartPolicyConditionNone RestartPolicyCondition = "none" // RestartPolicyConditionOnFailure ON_FAILURE RestartPolicyConditionOnFailure RestartPolicyCondition = "on-failure" // RestartPolicyConditionAny ANY RestartPolicyConditionAny RestartPolicyCondition = "any" ) // TaskStatus represents the status of a task. type TaskStatus struct { Timestamp time.Time `json:",omitempty"` State TaskState `json:",omitempty"` Message string `json:",omitempty"` Err string `json:",omitempty"` ContainerStatus ContainerStatus `json:",omitempty"` } // ContainerStatus represents the status of a container. type ContainerStatus struct { ContainerID string `json:",omitempty"` PID int `json:",omitempty"` ExitCode int `json:",omitempty"` } engine-api-0.4.0/types/time/000077500000000000000000000000001274620210500156305ustar00rootroot00000000000000engine-api-0.4.0/types/time/duration_convert.go000066400000000000000000000004431274620210500215450ustar00rootroot00000000000000package time import ( "strconv" "time" ) // DurationToSecondsString converts the specified duration to the number // seconds it represents, formatted as a string. func DurationToSecondsString(duration time.Duration) string { return strconv.FormatFloat(duration.Seconds(), 'f', 0, 64) } engine-api-0.4.0/types/time/duration_convert_test.go000066400000000000000000000007261274620210500226100ustar00rootroot00000000000000package time import ( "testing" "time" ) func TestDurationToSecondsString(t *testing.T) { cases := []struct { in time.Duration expected string }{ {0 * time.Second, "0"}, {1 * time.Second, "1"}, {1 * time.Minute, "60"}, {24 * time.Hour, "86400"}, } for _, c := range cases { s := DurationToSecondsString(c.in) if s != c.expected { t.Errorf("wrong value for input `%v`: expected `%s`, got `%s`", c.in, c.expected, s) t.Fail() } } } engine-api-0.4.0/types/time/timestamp.go000066400000000000000000000100301274620210500201540ustar00rootroot00000000000000package time import ( "fmt" "math" "strconv" "strings" "time" ) // These are additional predefined layouts for use in Time.Format and Time.Parse // with --since and --until parameters for `docker logs` and `docker events` const ( rFC3339Local = "2006-01-02T15:04:05" // RFC3339 with local timezone rFC3339NanoLocal = "2006-01-02T15:04:05.999999999" // RFC3339Nano with local timezone dateWithZone = "2006-01-02Z07:00" // RFC3339 with time at 00:00:00 dateLocal = "2006-01-02" // RFC3339 with local timezone and time at 00:00:00 ) // GetTimestamp tries to parse given string as golang duration, // then RFC3339 time and finally as a Unix timestamp. If // any of these were successful, it returns a Unix timestamp // as string otherwise returns the given value back. // In case of duration input, the returned timestamp is computed // as the given reference time minus the amount of the duration. func GetTimestamp(value string, reference time.Time) (string, error) { if d, err := time.ParseDuration(value); value != "0" && err == nil { return strconv.FormatInt(reference.Add(-d).Unix(), 10), nil } var format string var parseInLocation bool // if the string has a Z or a + or three dashes use parse otherwise use parseinlocation parseInLocation = !(strings.ContainsAny(value, "zZ+") || strings.Count(value, "-") == 3) if strings.Contains(value, ".") { if parseInLocation { format = rFC3339NanoLocal } else { format = time.RFC3339Nano } } else if strings.Contains(value, "T") { // we want the number of colons in the T portion of the timestamp tcolons := strings.Count(value, ":") // if parseInLocation is off and we have a +/- zone offset (not Z) then // there will be an extra colon in the input for the tz offset subtract that // colon from the tcolons count if !parseInLocation && !strings.ContainsAny(value, "zZ") && tcolons > 0 { tcolons-- } if parseInLocation { switch tcolons { case 0: format = "2006-01-02T15" case 1: format = "2006-01-02T15:04" default: format = rFC3339Local } } else { switch tcolons { case 0: format = "2006-01-02T15Z07:00" case 1: format = "2006-01-02T15:04Z07:00" default: format = time.RFC3339 } } } else if parseInLocation { format = dateLocal } else { format = dateWithZone } var t time.Time var err error if parseInLocation { t, err = time.ParseInLocation(format, value, time.FixedZone(reference.Zone())) } else { t, err = time.Parse(format, value) } if err != nil { // if there is a `-` then its an RFC3339 like timestamp otherwise assume unixtimestamp if strings.Contains(value, "-") { return "", err // was probably an RFC3339 like timestamp but the parser failed with an error } return value, nil // unixtimestamp in and out case (meaning: the value passed at the command line is already in the right format for passing to the server) } return fmt.Sprintf("%d.%09d", t.Unix(), int64(t.Nanosecond())), nil } // ParseTimestamps returns seconds and nanoseconds from a timestamp that has the // format "%d.%09d", time.Unix(), int64(time.Nanosecond())) // if the incoming nanosecond portion is longer or shorter than 9 digits it is // converted to nanoseconds. The expectation is that the seconds and // seconds will be used to create a time variable. For example: // seconds, nanoseconds, err := ParseTimestamp("1136073600.000000001",0) // if err == nil since := time.Unix(seconds, nanoseconds) // returns seconds as def(aultSeconds) if value == "" func ParseTimestamps(value string, def int64) (int64, int64, error) { if value == "" { return def, 0, nil } sa := strings.SplitN(value, ".", 2) s, err := strconv.ParseInt(sa[0], 10, 64) if err != nil { return s, 0, err } if len(sa) != 2 { return s, 0, nil } n, err := strconv.ParseInt(sa[1], 10, 64) if err != nil { return s, n, err } // should already be in nanoseconds but just in case convert n to nanoseonds n = int64(float64(n) * math.Pow(float64(10), float64(9-len(sa[1])))) return s, n, nil } engine-api-0.4.0/types/time/timestamp_test.go000066400000000000000000000063141274620210500212250ustar00rootroot00000000000000package time import ( "fmt" "testing" "time" ) func TestGetTimestamp(t *testing.T) { now := time.Now().In(time.UTC) cases := []struct { in, expected string expectedErr bool }{ // Partial RFC3339 strings get parsed with second precision {"2006-01-02T15:04:05.999999999+07:00", "1136189045.999999999", false}, {"2006-01-02T15:04:05.999999999Z", "1136214245.999999999", false}, {"2006-01-02T15:04:05.999999999", "1136214245.999999999", false}, {"2006-01-02T15:04:05Z", "1136214245.000000000", false}, {"2006-01-02T15:04:05", "1136214245.000000000", false}, {"2006-01-02T15:04:0Z", "", true}, {"2006-01-02T15:04:0", "", true}, {"2006-01-02T15:04Z", "1136214240.000000000", false}, {"2006-01-02T15:04+00:00", "1136214240.000000000", false}, {"2006-01-02T15:04-00:00", "1136214240.000000000", false}, {"2006-01-02T15:04", "1136214240.000000000", false}, {"2006-01-02T15:0Z", "", true}, {"2006-01-02T15:0", "", true}, {"2006-01-02T15Z", "1136214000.000000000", false}, {"2006-01-02T15+00:00", "1136214000.000000000", false}, {"2006-01-02T15-00:00", "1136214000.000000000", false}, {"2006-01-02T15", "1136214000.000000000", false}, {"2006-01-02T1Z", "1136163600.000000000", false}, {"2006-01-02T1", "1136163600.000000000", false}, {"2006-01-02TZ", "", true}, {"2006-01-02T", "", true}, {"2006-01-02+00:00", "1136160000.000000000", false}, {"2006-01-02-00:00", "1136160000.000000000", false}, {"2006-01-02-00:01", "1136160060.000000000", false}, {"2006-01-02Z", "1136160000.000000000", false}, {"2006-01-02", "1136160000.000000000", false}, {"2015-05-13T20:39:09Z", "1431549549.000000000", false}, // unix timestamps returned as is {"1136073600", "1136073600", false}, {"1136073600.000000001", "1136073600.000000001", false}, // Durations {"1m", fmt.Sprintf("%d", now.Add(-1*time.Minute).Unix()), false}, {"1.5h", fmt.Sprintf("%d", now.Add(-90*time.Minute).Unix()), false}, {"1h30m", fmt.Sprintf("%d", now.Add(-90*time.Minute).Unix()), false}, // String fallback {"invalid", "invalid", false}, } for _, c := range cases { o, err := GetTimestamp(c.in, now) if o != c.expected || (err == nil && c.expectedErr) || (err != nil && !c.expectedErr) { t.Errorf("wrong value for '%s'. expected:'%s' got:'%s' with error: `%s`", c.in, c.expected, o, err) t.Fail() } } } func TestParseTimestamps(t *testing.T) { cases := []struct { in string def, expectedS, expectedN int64 expectedErr bool }{ // unix timestamps {"1136073600", 0, 1136073600, 0, false}, {"1136073600.000000001", 0, 1136073600, 1, false}, {"1136073600.0000000010", 0, 1136073600, 1, false}, {"1136073600.00000001", 0, 1136073600, 10, false}, {"foo.bar", 0, 0, 0, true}, {"1136073600.bar", 0, 1136073600, 0, true}, {"", -1, -1, 0, false}, } for _, c := range cases { s, n, err := ParseTimestamps(c.in, c.def) if s != c.expectedS || n != c.expectedN || (err == nil && c.expectedErr) || (err != nil && !c.expectedErr) { t.Errorf("wrong values for input `%s` with default `%d` expected:'%d'seconds and `%d`nanosecond got:'%d'seconds and `%d`nanoseconds with error: `%s`", c.in, c.def, c.expectedS, c.expectedN, s, n, err) t.Fail() } } } engine-api-0.4.0/types/types.go000066400000000000000000000411061274620210500163670ustar00rootroot00000000000000package types import ( "os" "time" "github.com/docker/engine-api/types/container" "github.com/docker/engine-api/types/network" "github.com/docker/engine-api/types/registry" "github.com/docker/engine-api/types/swarm" "github.com/docker/go-connections/nat" ) // ContainerCreateResponse contains the information returned to a client on the // creation of a new container. type ContainerCreateResponse struct { // ID is the ID of the created container. ID string `json:"Id"` // Warnings are any warnings encountered during the creation of the container. Warnings []string `json:"Warnings"` } // ContainerExecCreateResponse contains response of Remote API: // POST "/containers/{name:.*}/exec" type ContainerExecCreateResponse struct { // ID is the exec ID. ID string `json:"Id"` } // ContainerUpdateResponse contains response of Remote API: // POST "/containers/{name:.*}/update" type ContainerUpdateResponse struct { // Warnings are any warnings encountered during the updating of the container. Warnings []string `json:"Warnings"` } // AuthResponse contains response of Remote API: // POST "/auth" type AuthResponse struct { // Status is the authentication status Status string `json:"Status"` // IdentityToken is an opaque token used for authenticating // a user after a successful login. IdentityToken string `json:"IdentityToken,omitempty"` } // ContainerWaitResponse contains response of Remote API: // POST "/containers/"+containerID+"/wait" type ContainerWaitResponse struct { // StatusCode is the status code of the wait job StatusCode int `json:"StatusCode"` } // ContainerCommitResponse contains response of Remote API: // POST "/commit?container="+containerID type ContainerCommitResponse struct { ID string `json:"Id"` } // ContainerChange contains response of Remote API: // GET "/containers/{name:.*}/changes" type ContainerChange struct { Kind int Path string } // ImageHistory contains response of Remote API: // GET "/images/{name:.*}/history" type ImageHistory struct { ID string `json:"Id"` Created int64 CreatedBy string Tags []string Size int64 Comment string } // ImageDelete contains response of Remote API: // DELETE "/images/{name:.*}" type ImageDelete struct { Untagged string `json:",omitempty"` Deleted string `json:",omitempty"` } // Image contains response of Remote API: // GET "/images/json" type Image struct { ID string `json:"Id"` ParentID string `json:"ParentId"` RepoTags []string RepoDigests []string Created int64 Size int64 VirtualSize int64 Labels map[string]string } // GraphDriverData returns Image's graph driver config info // when calling inspect command type GraphDriverData struct { Name string Data map[string]string } // RootFS returns Image's RootFS description including the layer IDs. type RootFS struct { Type string Layers []string `json:",omitempty"` BaseLayer string `json:",omitempty"` } // ImageInspect contains response of Remote API: // GET "/images/{name:.*}/json" type ImageInspect struct { ID string `json:"Id"` RepoTags []string RepoDigests []string Parent string Comment string Created string Container string ContainerConfig *container.Config DockerVersion string Author string Config *container.Config Architecture string Os string Size int64 VirtualSize int64 GraphDriver GraphDriverData RootFS RootFS } // Port stores open ports info of container // e.g. {"PrivatePort": 8080, "PublicPort": 80, "Type": "tcp"} type Port struct { IP string `json:",omitempty"` PrivatePort int PublicPort int `json:",omitempty"` Type string } // Container contains response of Remote API: // GET "/containers/json" type Container struct { ID string `json:"Id"` Names []string Image string ImageID string Command string Created int64 Ports []Port SizeRw int64 `json:",omitempty"` SizeRootFs int64 `json:",omitempty"` Labels map[string]string State string Status string HostConfig struct { NetworkMode string `json:",omitempty"` } NetworkSettings *SummaryNetworkSettings Mounts []MountPoint } // CopyConfig contains request body of Remote API: // POST "/containers/"+containerID+"/copy" type CopyConfig struct { Resource string } // ContainerPathStat is used to encode the header from // GET "/containers/{name:.*}/archive" // "Name" is the file or directory name. type ContainerPathStat struct { Name string `json:"name"` Size int64 `json:"size"` Mode os.FileMode `json:"mode"` Mtime time.Time `json:"mtime"` LinkTarget string `json:"linkTarget"` } // ContainerProcessList contains response of Remote API: // GET "/containers/{name:.*}/top" type ContainerProcessList struct { Processes [][]string Titles []string } // Version contains response of Remote API: // GET "/version" type Version struct { Version string APIVersion string `json:"ApiVersion"` GitCommit string GoVersion string Os string Arch string KernelVersion string `json:",omitempty"` Experimental bool `json:",omitempty"` BuildTime string `json:",omitempty"` } // Info contains response of Remote API: // GET "/info" type Info struct { ID string Containers int ContainersRunning int ContainersPaused int ContainersStopped int Images int Driver string DriverStatus [][2]string SystemStatus [][2]string Plugins PluginsInfo MemoryLimit bool SwapLimit bool KernelMemory bool CPUCfsPeriod bool `json:"CpuCfsPeriod"` CPUCfsQuota bool `json:"CpuCfsQuota"` CPUShares bool CPUSet bool IPv4Forwarding bool BridgeNfIptables bool BridgeNfIP6tables bool `json:"BridgeNfIp6tables"` Debug bool NFd int OomKillDisable bool NGoroutines int SystemTime string ExecutionDriver string LoggingDriver string CgroupDriver string NEventsListener int KernelVersion string OperatingSystem string OSType string Architecture string IndexServerAddress string RegistryConfig *registry.ServiceConfig NCPU int MemTotal int64 DockerRootDir string HTTPProxy string `json:"HttpProxy"` HTTPSProxy string `json:"HttpsProxy"` NoProxy string Name string Labels []string ExperimentalBuild bool ServerVersion string ClusterStore string ClusterAdvertise string SecurityOptions []string Runtimes map[string]Runtime DefaultRuntime string Swarm swarm.Info // LiveRestoreEnabled determines whether containers should be kept // running when the daemon is shutdown or upon daemon start if // running containers are detected LiveRestoreEnabled bool } // PluginsInfo is a temp struct holding Plugins name // registered with docker daemon. It is used by Info struct type PluginsInfo struct { // List of Volume plugins registered Volume []string // List of Network plugins registered Network []string // List of Authorization plugins registered Authorization []string } // ExecStartCheck is a temp struct used by execStart // Config fields is part of ExecConfig in runconfig package type ExecStartCheck struct { // ExecStart will first check if it's detached Detach bool // Check if there's a tty Tty bool } // HealthcheckResult stores information about a single run of a healthcheck probe type HealthcheckResult struct { Start time.Time // Start is the time this check started End time.Time // End is the time this check ended ExitCode int // ExitCode meanings: 0=healthy, 1=unhealthy, 2=reserved (considered unhealthy), else=error running probe Output string // Output from last check } // Health states const ( Starting = "starting" // Starting indicates that the container is not yet ready Healthy = "healthy" // Healthy indicates that the container is running correctly Unhealthy = "unhealthy" // Unhealthy indicates that the container has a problem ) // Health stores information about the container's healthcheck results type Health struct { Status string // Status is one of Starting, Healthy or Unhealthy FailingStreak int // FailingStreak is the number of consecutive failures Log []*HealthcheckResult // Log contains the last few results (oldest first) } // ContainerState stores container's running state // it's part of ContainerJSONBase and will return by "inspect" command type ContainerState struct { Status string Running bool Paused bool Restarting bool OOMKilled bool Dead bool Pid int ExitCode int Error string StartedAt string FinishedAt string Health *Health `json:",omitempty"` } // ContainerNode stores information about the node that a container // is running on. It's only available in Docker Swarm type ContainerNode struct { ID string IPAddress string `json:"IP"` Addr string Name string Cpus int Memory int64 Labels map[string]string } // ContainerJSONBase contains response of Remote API: // GET "/containers/{name:.*}/json" type ContainerJSONBase struct { ID string `json:"Id"` Created string Path string Args []string State *ContainerState Image string ResolvConfPath string HostnamePath string HostsPath string LogPath string Node *ContainerNode `json:",omitempty"` Name string RestartCount int Driver string MountLabel string ProcessLabel string AppArmorProfile string ExecIDs []string HostConfig *container.HostConfig GraphDriver GraphDriverData SizeRw *int64 `json:",omitempty"` SizeRootFs *int64 `json:",omitempty"` } // ContainerJSON is newly used struct along with MountPoint type ContainerJSON struct { *ContainerJSONBase Mounts []MountPoint Config *container.Config NetworkSettings *NetworkSettings } // NetworkSettings exposes the network settings in the api type NetworkSettings struct { NetworkSettingsBase DefaultNetworkSettings Networks map[string]*network.EndpointSettings } // SummaryNetworkSettings provides a summary of container's networks // in /containers/json type SummaryNetworkSettings struct { Networks map[string]*network.EndpointSettings } // NetworkSettingsBase holds basic information about networks type NetworkSettingsBase struct { Bridge string // Bridge is the Bridge name the network uses(e.g. `docker0`) SandboxID string // SandboxID uniquely represents a container's network stack HairpinMode bool // HairpinMode specifies if hairpin NAT should be enabled on the virtual interface LinkLocalIPv6Address string // LinkLocalIPv6Address is an IPv6 unicast address using the link-local prefix LinkLocalIPv6PrefixLen int // LinkLocalIPv6PrefixLen is the prefix length of an IPv6 unicast address Ports nat.PortMap // Ports is a collection of PortBinding indexed by Port SandboxKey string // SandboxKey identifies the sandbox SecondaryIPAddresses []network.Address SecondaryIPv6Addresses []network.Address } // DefaultNetworkSettings holds network information // during the 2 release deprecation period. // It will be removed in Docker 1.11. type DefaultNetworkSettings struct { EndpointID string // EndpointID uniquely represents a service endpoint in a Sandbox Gateway string // Gateway holds the gateway address for the network GlobalIPv6Address string // GlobalIPv6Address holds network's global IPv6 address GlobalIPv6PrefixLen int // GlobalIPv6PrefixLen represents mask length of network's global IPv6 address IPAddress string // IPAddress holds the IPv4 address for the network IPPrefixLen int // IPPrefixLen represents mask length of network's IPv4 address IPv6Gateway string // IPv6Gateway holds gateway address specific for IPv6 MacAddress string // MacAddress holds the MAC address for the network } // MountPoint represents a mount point configuration inside the container. type MountPoint struct { Name string `json:",omitempty"` Source string Destination string Driver string `json:",omitempty"` Mode string RW bool Propagation string } // Volume represents the configuration of a volume for the remote API type Volume struct { Name string // Name is the name of the volume Driver string // Driver is the Driver name used to create the volume Mountpoint string // Mountpoint is the location on disk of the volume Status map[string]interface{} `json:",omitempty"` // Status provides low-level status information about the volume Labels map[string]string // Labels is metadata specific to the volume Scope string // Scope describes the level at which the volume exists (e.g. `global` for cluster-wide or `local` for machine level) } // VolumesListResponse contains the response for the remote API: // GET "/volumes" type VolumesListResponse struct { Volumes []*Volume // Volumes is the list of volumes being returned Warnings []string // Warnings is a list of warnings that occurred when getting the list from the volume drivers } // VolumeCreateRequest contains the response for the remote API: // POST "/volumes/create" type VolumeCreateRequest struct { Name string // Name is the requested name of the volume Driver string // Driver is the name of the driver that should be used to create the volume DriverOpts map[string]string // DriverOpts holds the driver specific options to use for when creating the volume. Labels map[string]string // Labels holds metadata specific to the volume being created. } // NetworkResource is the body of the "get network" http response message type NetworkResource struct { Name string // Name is the requested name of the network ID string `json:"Id"` // ID uniquely identifies a network on a single machine Scope string // Scope describes the level at which the network exists (e.g. `global` for cluster-wide or `local` for machine level) Driver string // Driver is the Driver name used to create the network (e.g. `bridge`, `overlay`) EnableIPv6 bool // EnableIPv6 represents whether to enable IPv6 IPAM network.IPAM // IPAM is the network's IP Address Management Internal bool // Internal represents if the network is used internal only Containers map[string]EndpointResource // Containers contains endpoints belonging to the network Options map[string]string // Options holds the network specific options to use for when creating the network Labels map[string]string // Labels holds metadata specific to the network being created } // EndpointResource contains network resources allocated and used for a container in a network type EndpointResource struct { Name string EndpointID string MacAddress string IPv4Address string IPv6Address string } // NetworkCreate is the expected body of the "create network" http request message type NetworkCreate struct { CheckDuplicate bool Driver string EnableIPv6 bool IPAM network.IPAM Internal bool Options map[string]string Labels map[string]string } // NetworkCreateRequest is the request message sent to the server for network create call. type NetworkCreateRequest struct { NetworkCreate Name string } // NetworkCreateResponse is the response message sent by the server for network create call type NetworkCreateResponse struct { ID string `json:"Id"` Warning string } // NetworkConnect represents the data to be used to connect a container to the network type NetworkConnect struct { Container string EndpointConfig *network.EndpointSettings `json:",omitempty"` } // NetworkDisconnect represents the data to be used to disconnect a container from the network type NetworkDisconnect struct { Container string Force bool } // Checkpoint represents the details of a checkpoint type Checkpoint struct { Name string // Name is the name of the checkpoint } // Runtime describes an OCI runtime type Runtime struct { Path string `json:"path"` Args []string `json:"runtimeArgs,omitempty"` } engine-api-0.4.0/types/versions/000077500000000000000000000000001274620210500165425ustar00rootroot00000000000000engine-api-0.4.0/types/versions/README.md000066400000000000000000000022751274620210500200270ustar00rootroot00000000000000## Legacy API type versions This package includes types for legacy API versions. The stable version of the API types live in `api/types/*.go`. Consider moving a type here when you need to keep backwards compatibility in the API. This legacy types are organized by the latest API version they appear in. For instance, types in the `v1p19` package are valid for API versions below or equal `1.19`. Types in the `v1p20` package are valid for the API version `1.20`, since the versions below that will use the legacy types in `v1p19`. ### Package name conventions The package name convention is to use `v` as a prefix for the version number and `p`(patch) as a separator. We use this nomenclature due to a few restrictions in the Go package name convention: 1. We cannot use `.` because it's interpreted by the language, think of `v1.20.CallFunction`. 2. We cannot use `_` because golint complains about it. The code is actually valid, but it looks probably more weird: `v1_20.CallFunction`. For instance, if you want to modify a type that was available in the version `1.21` of the API but it will have different fields in the version `1.22`, you want to create a new package under `api/types/versions/v1p21`. engine-api-0.4.0/types/versions/compare.go000066400000000000000000000024611274620210500205220ustar00rootroot00000000000000package versions import ( "strconv" "strings" ) // compare compares two version strings // returns -1 if v1 < v2, 1 if v1 > v2, 0 otherwise. func compare(v1, v2 string) int { var ( currTab = strings.Split(v1, ".") otherTab = strings.Split(v2, ".") ) max := len(currTab) if len(otherTab) > max { max = len(otherTab) } for i := 0; i < max; i++ { var currInt, otherInt int if len(currTab) > i { currInt, _ = strconv.Atoi(currTab[i]) } if len(otherTab) > i { otherInt, _ = strconv.Atoi(otherTab[i]) } if currInt > otherInt { return 1 } if otherInt > currInt { return -1 } } return 0 } // LessThan checks if a version is less than another func LessThan(v, other string) bool { return compare(v, other) == -1 } // LessThanOrEqualTo checks if a version is less than or equal to another func LessThanOrEqualTo(v, other string) bool { return compare(v, other) <= 0 } // GreaterThan checks if a version is greater than another func GreaterThan(v, other string) bool { return compare(v, other) == 1 } // GreaterThanOrEqualTo checks if a version is greater than or equal to another func GreaterThanOrEqualTo(v, other string) bool { return compare(v, other) >= 0 } // Equal checks if a version is equal to another func Equal(v, other string) bool { return compare(v, other) == 0 } engine-api-0.4.0/types/versions/compare_test.go000066400000000000000000000013411274620210500215550ustar00rootroot00000000000000package versions import ( "testing" ) func assertVersion(t *testing.T, a, b string, result int) { if r := compare(a, b); r != result { t.Fatalf("Unexpected version comparison result. Found %d, expected %d", r, result) } } func TestCompareVersion(t *testing.T) { assertVersion(t, "1.12", "1.12", 0) assertVersion(t, "1.0.0", "1", 0) assertVersion(t, "1", "1.0.0", 0) assertVersion(t, "1.05.00.0156", "1.0.221.9289", 1) assertVersion(t, "1", "1.0.1", -1) assertVersion(t, "1.0.1", "1", 1) assertVersion(t, "1.0.1", "1.0.2", -1) assertVersion(t, "1.0.2", "1.0.3", -1) assertVersion(t, "1.0.3", "1.1", -1) assertVersion(t, "1.1", "1.1.1", -1) assertVersion(t, "1.1.1", "1.1.2", -1) assertVersion(t, "1.1.2", "1.2", -1) } engine-api-0.4.0/types/versions/v1p19/000077500000000000000000000000001274620210500174225ustar00rootroot00000000000000engine-api-0.4.0/types/versions/v1p19/types.go000066400000000000000000000017571274620210500211270ustar00rootroot00000000000000// Package v1p19 provides specific API types for the API version 1, patch 19. package v1p19 import ( "github.com/docker/engine-api/types" "github.com/docker/engine-api/types/container" "github.com/docker/engine-api/types/versions/v1p20" "github.com/docker/go-connections/nat" ) // ContainerJSON is a backcompatibility struct for APIs prior to 1.20. // Note this is not used by the Windows daemon. type ContainerJSON struct { *types.ContainerJSONBase Volumes map[string]string VolumesRW map[string]bool Config *ContainerConfig NetworkSettings *v1p20.NetworkSettings } // ContainerConfig is a backcompatibility struct for APIs prior to 1.20. type ContainerConfig struct { *container.Config MacAddress string NetworkDisabled bool ExposedPorts map[nat.Port]struct{} // backward compatibility, they now live in HostConfig VolumeDriver string Memory int64 MemorySwap int64 CPUShares int64 `json:"CpuShares"` CPUSet string `json:"Cpuset"` } engine-api-0.4.0/types/versions/v1p20/000077500000000000000000000000001274620210500174125ustar00rootroot00000000000000engine-api-0.4.0/types/versions/v1p20/types.go000066400000000000000000000021121274620210500211010ustar00rootroot00000000000000// Package v1p20 provides specific API types for the API version 1, patch 20. package v1p20 import ( "github.com/docker/engine-api/types" "github.com/docker/engine-api/types/container" "github.com/docker/go-connections/nat" ) // ContainerJSON is a backcompatibility struct for the API 1.20 type ContainerJSON struct { *types.ContainerJSONBase Mounts []types.MountPoint Config *ContainerConfig NetworkSettings *NetworkSettings } // ContainerConfig is a backcompatibility struct used in ContainerJSON for the API 1.20 type ContainerConfig struct { *container.Config MacAddress string NetworkDisabled bool ExposedPorts map[nat.Port]struct{} // backward compatibility, they now live in HostConfig VolumeDriver string } // StatsJSON is a backcompatibility struct used in Stats for APIs prior to 1.21 type StatsJSON struct { types.Stats Network types.NetworkStats `json:"network,omitempty"` } // NetworkSettings is a backward compatible struct for APIs prior to 1.21 type NetworkSettings struct { types.NetworkSettingsBase types.DefaultNetworkSettings }