├── .travis.yml ├── CHANGELOG.md ├── CONTRIBUTING.md ├── LICENSE ├── MAINTAINERS ├── Makefile ├── README.md ├── appveyor.yml ├── client ├── checkpoint_create.go ├── checkpoint_create_test.go ├── checkpoint_delete.go ├── checkpoint_delete_test.go ├── checkpoint_list.go ├── checkpoint_list_test.go ├── client.go ├── client_mock_test.go ├── client_test.go ├── client_unix.go ├── client_windows.go ├── container_attach.go ├── container_commit.go ├── container_commit_test.go ├── container_copy.go ├── container_copy_test.go ├── container_create.go ├── container_create_test.go ├── container_diff.go ├── container_diff_test.go ├── container_exec.go ├── container_exec_test.go ├── container_export.go ├── container_export_test.go ├── container_inspect.go ├── container_inspect_test.go ├── container_kill.go ├── container_kill_test.go ├── container_list.go ├── container_list_test.go ├── container_logs.go ├── container_logs_test.go ├── container_pause.go ├── container_pause_test.go ├── container_remove.go ├── container_remove_test.go ├── container_rename.go ├── container_rename_test.go ├── container_resize.go ├── container_resize_test.go ├── container_restart.go ├── container_restart_test.go ├── container_start.go ├── container_start_test.go ├── container_stats.go ├── container_stats_test.go ├── container_stop.go ├── container_stop_test.go ├── container_top.go ├── container_top_test.go ├── container_unpause.go ├── container_unpause_test.go ├── container_update.go ├── container_update_test.go ├── container_wait.go ├── container_wait_test.go ├── errors.go ├── events.go ├── events_test.go ├── hijack.go ├── image_build.go ├── image_build_test.go ├── image_create.go ├── image_create_test.go ├── image_history.go ├── image_history_test.go ├── image_import.go ├── image_import_test.go ├── image_inspect.go ├── image_inspect_test.go ├── image_list.go ├── image_list_test.go ├── image_load.go ├── image_load_test.go ├── image_pull.go ├── image_pull_test.go ├── image_push.go ├── image_push_test.go ├── image_remove.go ├── image_remove_test.go ├── image_save.go ├── image_save_test.go ├── image_search.go ├── image_search_test.go ├── image_tag.go ├── image_tag_test.go ├── info.go ├── info_test.go ├── interface.go ├── interface_experimental.go ├── interface_stable.go ├── login.go ├── network_connect.go ├── network_connect_test.go ├── network_create.go ├── network_create_test.go ├── network_disconnect.go ├── network_disconnect_test.go ├── network_inspect.go ├── network_inspect_test.go ├── network_list.go ├── network_list_test.go ├── network_remove.go ├── network_remove_test.go ├── node_inspect.go ├── node_inspect_test.go ├── node_list.go ├── node_list_test.go ├── node_remove.go ├── node_remove_test.go ├── node_update.go ├── node_update_test.go ├── plugin_disable.go ├── plugin_disable_test.go ├── plugin_enable.go ├── plugin_enable_test.go ├── plugin_inspect.go ├── plugin_inspect_test.go ├── plugin_install.go ├── plugin_list.go ├── plugin_list_test.go ├── plugin_push.go ├── plugin_push_test.go ├── plugin_remove.go ├── plugin_remove_test.go ├── plugin_set.go ├── plugin_set_test.go ├── request.go ├── request_test.go ├── service_create.go ├── service_create_test.go ├── service_inspect.go ├── service_inspect_test.go ├── service_list.go ├── service_list_test.go ├── service_remove.go ├── service_remove_test.go ├── service_update.go ├── service_update_test.go ├── swarm_init.go ├── swarm_init_test.go ├── swarm_inspect.go ├── swarm_inspect_test.go ├── swarm_join.go ├── swarm_join_test.go ├── swarm_leave.go ├── swarm_leave_test.go ├── swarm_update.go ├── swarm_update_test.go ├── task_inspect.go ├── task_inspect_test.go ├── task_list.go ├── task_list_test.go ├── testdata │ ├── ca.pem │ ├── cert.pem │ └── key.pem ├── transport │ ├── cancellable │ │ ├── LICENSE │ │ ├── canceler.go │ │ ├── canceler_go14.go │ │ └── cancellable.go │ ├── client.go │ ├── tlsconfig_clone.go │ ├── tlsconfig_clone_go17.go │ └── transport.go ├── version.go ├── volume_create.go ├── volume_create_test.go ├── volume_inspect.go ├── volume_inspect_test.go ├── volume_list.go ├── volume_list_test.go ├── volume_remove.go └── volume_remove_test.go ├── doc.go └── types ├── auth.go ├── blkiodev └── blkio.go ├── client.go ├── configs.go ├── container ├── config.go ├── host_config.go ├── hostconfig_unix.go └── hostconfig_windows.go ├── errors.go ├── events └── events.go ├── filters ├── parse.go └── parse_test.go ├── mount └── mount.go ├── network └── network.go ├── plugin.go ├── reference ├── image_reference.go └── image_reference_test.go ├── registry └── registry.go ├── seccomp.go ├── stats.go ├── strslice ├── strslice.go └── strslice_test.go ├── swarm ├── common.go ├── container.go ├── network.go ├── node.go ├── service.go ├── swarm.go └── task.go ├── time ├── duration_convert.go ├── duration_convert_test.go ├── timestamp.go └── timestamp_test.go ├── types.go └── versions ├── README.md ├── compare.go ├── compare_test.go ├── v1p19 └── types.go └── v1p20 └── types.go /.travis.yml: -------------------------------------------------------------------------------- 1 | --- 2 | language: go 3 | sudo: false 4 | notifications: 5 | email: false 6 | go: 7 | - 1.7 8 | install: make deps 9 | script: make validate && make test 10 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to Docker 2 | 3 | ### Sign your work 4 | 5 | The sign-off is a simple line at the end of the explanation for the patch. Your 6 | signature certifies that you wrote the patch or otherwise have the right to pass 7 | it on as an open-source patch. The rules are pretty simple: if you can certify 8 | the below (from [developercertificate.org](http://developercertificate.org/)): 9 | 10 | ``` 11 | Developer Certificate of Origin 12 | Version 1.1 13 | 14 | Copyright (C) 2004, 2006 The Linux Foundation and its contributors. 15 | 660 York Street, Suite 102, 16 | San Francisco, CA 94110 USA 17 | 18 | Everyone is permitted to copy and distribute verbatim copies of this 19 | license document, but changing it is not allowed. 20 | 21 | Developer's Certificate of Origin 1.1 22 | 23 | By making a contribution to this project, I certify that: 24 | 25 | (a) The contribution was created in whole or in part by me and I 26 | have the right to submit it under the open source license 27 | indicated in the file; or 28 | 29 | (b) The contribution is based upon previous work that, to the best 30 | of my knowledge, is covered under an appropriate open source 31 | license and I have the right under that license to submit that 32 | work with modifications, whether created in whole or in part 33 | by me, under the same open source license (unless I am 34 | permitted to submit under a different license), as indicated 35 | in the file; or 36 | 37 | (c) The contribution was provided directly to me by some other 38 | person who certified (a), (b) or (c) and I have not modified 39 | it. 40 | 41 | (d) I understand and agree that this project and the contribution 42 | are public and that a record of the contribution (including all 43 | personal information I submit with it, including my sign-off) is 44 | maintained indefinitely and may be redistributed consistent with 45 | this project or the open source license(s) involved. 46 | ``` 47 | 48 | Then you just add a line to every git commit message: 49 | 50 | Signed-off-by: Joe Smith 51 | 52 | Use your real name (sorry, no pseudonyms or anonymous contributions.) 53 | 54 | If you set your `user.name` and `user.email` git configs, you can sign your 55 | commit automatically with `git commit -s`. 56 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: all deps test validate lint 2 | 3 | all: deps test validate 4 | 5 | deps: 6 | go get -t ./... 7 | go get -u github.com/golang/lint/golint 8 | 9 | test: 10 | go test -tags experimental -race -cover ./... 11 | 12 | validate: lint 13 | go vet ./... 14 | test -z "$(gofmt -s -l . | tee /dev/stderr)" 15 | 16 | lint: 17 | out="$$(golint ./...)"; \ 18 | if [ -n "$$(golint ./...)" ]; then \ 19 | echo "$$out"; \ 20 | exit 1; \ 21 | fi 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![GoDoc](https://godoc.org/github.com/docker/engine-api?status.svg)](https://godoc.org/github.com/docker/engine-api) 2 | 3 | # Deprecated 4 | 5 | For new projects please do not use this project anymore. The Docker API client and types have been moved 6 | to the main docker repo under the following import paths: 7 | 8 | * https://github.com/docker/docker/tree/master/client 9 | * https://github.com/docker/docker/tree/master/api/types 10 | 11 | All pull requests and issues should be filed under the docker/docker repository. 12 | This repo will not receive any future updates and you should update your existing import 13 | paths to the new package paths. 14 | 15 | ## License 16 | 17 | engine-api is licensed under the Apache License, Version 2.0. See [LICENSE](LICENSE) for the full license text. 18 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | version: "{build}" 2 | 3 | # Source Config 4 | clone_folder: c:\gopath\src\github.com\docker\engine-api 5 | 6 | # Build host 7 | 8 | environment: 9 | GOPATH: c:\gopath 10 | GOVERSION: 1.6 11 | 12 | init: 13 | - git config --global core.autocrlf input 14 | 15 | # Build 16 | 17 | install: 18 | # Install Go 1.6. 19 | - rmdir c:\go /s /q 20 | - appveyor DownloadFile https://storage.googleapis.com/golang/go%GOVERSION%.windows-amd64.msi 21 | - msiexec /i go%GOVERSION%.windows-amd64.msi /q 22 | - set Path=c:\go\bin;c:\gopath\bin;%Path% 23 | - go version 24 | - go env 25 | 26 | build: false 27 | deploy: false 28 | 29 | before_test: 30 | - go get -t ./... 31 | - go get github.com/golang/lint/golint 32 | 33 | test_script: 34 | - go vet ./... 35 | - golint ./... 36 | - gofmt -s -l . 37 | - go test -race -cover -v -tags=test ./... 38 | -------------------------------------------------------------------------------- /client/checkpoint_create.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "github.com/docker/engine-api/types" 5 | "golang.org/x/net/context" 6 | ) 7 | 8 | // CheckpointCreate creates a checkpoint from the given container with the given name 9 | func (cli *Client) CheckpointCreate(ctx context.Context, container string, options types.CheckpointCreateOptions) error { 10 | resp, err := cli.post(ctx, "/containers/"+container+"/checkpoints", nil, options, nil) 11 | ensureReaderClosed(resp) 12 | return err 13 | } 14 | -------------------------------------------------------------------------------- /client/checkpoint_create_test.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "fmt" 7 | "io/ioutil" 8 | "net/http" 9 | "strings" 10 | "testing" 11 | 12 | "github.com/docker/engine-api/types" 13 | "golang.org/x/net/context" 14 | ) 15 | 16 | func TestCheckpointCreateError(t *testing.T) { 17 | client := &Client{ 18 | transport: newMockClient(nil, errorMock(http.StatusInternalServerError, "Server error")), 19 | } 20 | err := client.CheckpointCreate(context.Background(), "nothing", types.CheckpointCreateOptions{ 21 | CheckpointID: "noting", 22 | Exit: true, 23 | }) 24 | 25 | if err == nil || err.Error() != "Error response from daemon: Server error" { 26 | t.Fatalf("expected a Server Error, got %v", err) 27 | } 28 | } 29 | 30 | func TestCheckpointCreate(t *testing.T) { 31 | expectedContainerID := "container_id" 32 | expectedCheckpointID := "checkpoint_id" 33 | expectedURL := "/containers/container_id/checkpoints" 34 | 35 | client := &Client{ 36 | transport: newMockClient(nil, func(req *http.Request) (*http.Response, error) { 37 | if !strings.HasPrefix(req.URL.Path, expectedURL) { 38 | return nil, fmt.Errorf("Expected URL '%s', got '%s'", expectedURL, req.URL) 39 | } 40 | 41 | if req.Method != "POST" { 42 | return nil, fmt.Errorf("expected POST method, got %s", req.Method) 43 | } 44 | 45 | createOptions := &types.CheckpointCreateOptions{} 46 | if err := json.NewDecoder(req.Body).Decode(createOptions); err != nil { 47 | return nil, err 48 | } 49 | 50 | if createOptions.CheckpointID != expectedCheckpointID { 51 | return nil, fmt.Errorf("expected CheckpointID to be 'checkpoint_id', got %v", createOptions.CheckpointID) 52 | } 53 | 54 | if !createOptions.Exit { 55 | return nil, fmt.Errorf("expected Exit to be true") 56 | } 57 | 58 | return &http.Response{ 59 | StatusCode: http.StatusOK, 60 | Body: ioutil.NopCloser(bytes.NewReader([]byte(""))), 61 | }, nil 62 | }), 63 | } 64 | 65 | err := client.CheckpointCreate(context.Background(), expectedContainerID, types.CheckpointCreateOptions{ 66 | CheckpointID: expectedCheckpointID, 67 | Exit: true, 68 | }) 69 | 70 | if err != nil { 71 | t.Fatal(err) 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /client/checkpoint_delete.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "golang.org/x/net/context" 5 | ) 6 | 7 | // CheckpointDelete deletes the checkpoint with the given name from the given container 8 | func (cli *Client) CheckpointDelete(ctx context.Context, containerID string, checkpointID string) error { 9 | resp, err := cli.delete(ctx, "/containers/"+containerID+"/checkpoints/"+checkpointID, nil, nil) 10 | ensureReaderClosed(resp) 11 | return err 12 | } 13 | -------------------------------------------------------------------------------- /client/checkpoint_delete_test.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "io/ioutil" 7 | "net/http" 8 | "strings" 9 | "testing" 10 | 11 | "golang.org/x/net/context" 12 | ) 13 | 14 | func TestCheckpointDeleteError(t *testing.T) { 15 | client := &Client{ 16 | transport: newMockClient(nil, errorMock(http.StatusInternalServerError, "Server error")), 17 | } 18 | 19 | err := client.CheckpointDelete(context.Background(), "container_id", "checkpoint_id") 20 | if err == nil || err.Error() != "Error response from daemon: Server error" { 21 | t.Fatalf("expected a Server Error, got %v", err) 22 | } 23 | } 24 | 25 | func TestCheckpointDelete(t *testing.T) { 26 | expectedURL := "/containers/container_id/checkpoints/checkpoint_id" 27 | 28 | client := &Client{ 29 | transport: newMockClient(nil, func(req *http.Request) (*http.Response, error) { 30 | if !strings.HasPrefix(req.URL.Path, expectedURL) { 31 | return nil, fmt.Errorf("Expected URL '%s', got '%s'", expectedURL, req.URL) 32 | } 33 | if req.Method != "DELETE" { 34 | return nil, fmt.Errorf("expected DELETE method, got %s", req.Method) 35 | } 36 | return &http.Response{ 37 | StatusCode: http.StatusOK, 38 | Body: ioutil.NopCloser(bytes.NewReader([]byte(""))), 39 | }, nil 40 | }), 41 | } 42 | 43 | err := client.CheckpointDelete(context.Background(), "container_id", "checkpoint_id") 44 | if err != nil { 45 | t.Fatal(err) 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /client/checkpoint_list.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "encoding/json" 5 | 6 | "github.com/docker/engine-api/types" 7 | "golang.org/x/net/context" 8 | ) 9 | 10 | // CheckpointList returns the volumes configured in the docker host. 11 | func (cli *Client) CheckpointList(ctx context.Context, container string) ([]types.Checkpoint, error) { 12 | var checkpoints []types.Checkpoint 13 | 14 | resp, err := cli.get(ctx, "/containers/"+container+"/checkpoints", nil, nil) 15 | if err != nil { 16 | return checkpoints, err 17 | } 18 | 19 | err = json.NewDecoder(resp.body).Decode(&checkpoints) 20 | ensureReaderClosed(resp) 21 | return checkpoints, err 22 | } 23 | -------------------------------------------------------------------------------- /client/checkpoint_list_test.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "fmt" 7 | "io/ioutil" 8 | "net/http" 9 | "strings" 10 | "testing" 11 | 12 | "github.com/docker/engine-api/types" 13 | "golang.org/x/net/context" 14 | ) 15 | 16 | func TestCheckpointListError(t *testing.T) { 17 | client := &Client{ 18 | transport: newMockClient(nil, errorMock(http.StatusInternalServerError, "Server error")), 19 | } 20 | 21 | _, err := client.CheckpointList(context.Background(), "container_id") 22 | if err == nil || err.Error() != "Error response from daemon: Server error" { 23 | t.Fatalf("expected a Server Error, got %v", err) 24 | } 25 | } 26 | 27 | func TestCheckpointList(t *testing.T) { 28 | expectedURL := "/containers/container_id/checkpoints" 29 | 30 | client := &Client{ 31 | transport: newMockClient(nil, func(req *http.Request) (*http.Response, error) { 32 | if !strings.HasPrefix(req.URL.Path, expectedURL) { 33 | return nil, fmt.Errorf("Expected URL '%s', got '%s'", expectedURL, req.URL) 34 | } 35 | content, err := json.Marshal([]types.Checkpoint{ 36 | { 37 | Name: "checkpoint", 38 | }, 39 | }) 40 | if err != nil { 41 | return nil, err 42 | } 43 | return &http.Response{ 44 | StatusCode: http.StatusOK, 45 | Body: ioutil.NopCloser(bytes.NewReader(content)), 46 | }, nil 47 | }), 48 | } 49 | 50 | checkpoints, err := client.CheckpointList(context.Background(), "container_id") 51 | if err != nil { 52 | t.Fatal(err) 53 | } 54 | if len(checkpoints) != 1 { 55 | t.Fatalf("expected 1 checkpoint, got %v", checkpoints) 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /client/client_mock_test.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "bytes" 5 | "crypto/tls" 6 | "encoding/json" 7 | "io/ioutil" 8 | "net/http" 9 | 10 | "github.com/docker/engine-api/client/transport" 11 | "github.com/docker/engine-api/types" 12 | ) 13 | 14 | type mockClient struct { 15 | do func(*http.Request) (*http.Response, error) 16 | } 17 | 18 | // TLSConfig returns the TLS configuration. 19 | func (m *mockClient) TLSConfig() *tls.Config { 20 | return &tls.Config{} 21 | } 22 | 23 | // Scheme returns protocol scheme to use. 24 | func (m *mockClient) Scheme() string { 25 | return "http" 26 | } 27 | 28 | // Secure returns true if there is a TLS configuration. 29 | func (m *mockClient) Secure() bool { 30 | return false 31 | } 32 | 33 | // NewMockClient returns a mocked client that runs the function supplied as `client.Do` call 34 | func newMockClient(tlsConfig *tls.Config, doer func(*http.Request) (*http.Response, error)) transport.Client { 35 | if tlsConfig != nil { 36 | panic("this actually gets set!") 37 | } 38 | 39 | return &mockClient{ 40 | do: doer, 41 | } 42 | } 43 | 44 | // Do executes the supplied function for the mock. 45 | func (m mockClient) Do(req *http.Request) (*http.Response, error) { 46 | return m.do(req) 47 | } 48 | 49 | func errorMock(statusCode int, message string) func(req *http.Request) (*http.Response, error) { 50 | return func(req *http.Request) (*http.Response, error) { 51 | header := http.Header{} 52 | header.Set("Content-Type", "application/json") 53 | 54 | body, err := json.Marshal(&types.ErrorResponse{ 55 | Message: message, 56 | }) 57 | if err != nil { 58 | return nil, err 59 | } 60 | 61 | return &http.Response{ 62 | StatusCode: statusCode, 63 | Body: ioutil.NopCloser(bytes.NewReader(body)), 64 | Header: header, 65 | }, nil 66 | } 67 | } 68 | 69 | func plainTextErrorMock(statusCode int, message string) func(req *http.Request) (*http.Response, error) { 70 | return func(req *http.Request) (*http.Response, error) { 71 | return &http.Response{ 72 | StatusCode: statusCode, 73 | Body: ioutil.NopCloser(bytes.NewReader([]byte(message))), 74 | }, nil 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /client/client_unix.go: -------------------------------------------------------------------------------- 1 | // +build linux freebsd solaris openbsd darwin 2 | 3 | package client 4 | 5 | // DefaultDockerHost defines os specific default if DOCKER_HOST is unset 6 | const DefaultDockerHost = "unix:///var/run/docker.sock" 7 | -------------------------------------------------------------------------------- /client/client_windows.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | // DefaultDockerHost defines os specific default if DOCKER_HOST is unset 4 | const DefaultDockerHost = "npipe:////./pipe/docker_engine" 5 | -------------------------------------------------------------------------------- /client/container_attach.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "net/url" 5 | 6 | "github.com/docker/engine-api/types" 7 | "golang.org/x/net/context" 8 | ) 9 | 10 | // ContainerAttach attaches a connection to a container in the server. 11 | // It returns a types.HijackedConnection with the hijacked connection 12 | // and the a reader to get output. It's up to the called to close 13 | // the hijacked connection by calling types.HijackedResponse.Close. 14 | func (cli *Client) ContainerAttach(ctx context.Context, container string, options types.ContainerAttachOptions) (types.HijackedResponse, error) { 15 | query := url.Values{} 16 | if options.Stream { 17 | query.Set("stream", "1") 18 | } 19 | if options.Stdin { 20 | query.Set("stdin", "1") 21 | } 22 | if options.Stdout { 23 | query.Set("stdout", "1") 24 | } 25 | if options.Stderr { 26 | query.Set("stderr", "1") 27 | } 28 | if options.DetachKeys != "" { 29 | query.Set("detachKeys", options.DetachKeys) 30 | } 31 | 32 | headers := map[string][]string{"Content-Type": {"text/plain"}} 33 | return cli.postHijacked(ctx, "/containers/"+container+"/attach", query, nil, headers) 34 | } 35 | -------------------------------------------------------------------------------- /client/container_commit.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "encoding/json" 5 | "errors" 6 | "net/url" 7 | 8 | distreference "github.com/docker/distribution/reference" 9 | "github.com/docker/engine-api/types" 10 | "github.com/docker/engine-api/types/reference" 11 | "golang.org/x/net/context" 12 | ) 13 | 14 | // ContainerCommit applies changes into a container and creates a new tagged image. 15 | func (cli *Client) ContainerCommit(ctx context.Context, container string, options types.ContainerCommitOptions) (types.ContainerCommitResponse, error) { 16 | var repository, tag string 17 | if options.Reference != "" { 18 | distributionRef, err := distreference.ParseNamed(options.Reference) 19 | if err != nil { 20 | return types.ContainerCommitResponse{}, err 21 | } 22 | 23 | if _, isCanonical := distributionRef.(distreference.Canonical); isCanonical { 24 | return types.ContainerCommitResponse{}, errors.New("refusing to create a tag with a digest reference") 25 | } 26 | 27 | tag = reference.GetTagFromNamedRef(distributionRef) 28 | repository = distributionRef.Name() 29 | } 30 | 31 | query := url.Values{} 32 | query.Set("container", container) 33 | query.Set("repo", repository) 34 | query.Set("tag", tag) 35 | query.Set("comment", options.Comment) 36 | query.Set("author", options.Author) 37 | for _, change := range options.Changes { 38 | query.Add("changes", change) 39 | } 40 | if options.Pause != true { 41 | query.Set("pause", "0") 42 | } 43 | 44 | var response types.ContainerCommitResponse 45 | resp, err := cli.post(ctx, "/commit", query, options.Config, nil) 46 | if err != nil { 47 | return response, err 48 | } 49 | 50 | err = json.NewDecoder(resp.body).Decode(&response) 51 | ensureReaderClosed(resp) 52 | return response, err 53 | } 54 | -------------------------------------------------------------------------------- /client/container_create.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "encoding/json" 5 | "net/url" 6 | "strings" 7 | 8 | "github.com/docker/engine-api/types" 9 | "github.com/docker/engine-api/types/container" 10 | "github.com/docker/engine-api/types/network" 11 | "golang.org/x/net/context" 12 | ) 13 | 14 | type configWrapper struct { 15 | *container.Config 16 | HostConfig *container.HostConfig 17 | NetworkingConfig *network.NetworkingConfig 18 | } 19 | 20 | // ContainerCreate creates a new container based in the given configuration. 21 | // It can be associated with a name, but it's not mandatory. 22 | func (cli *Client) ContainerCreate(ctx context.Context, config *container.Config, hostConfig *container.HostConfig, networkingConfig *network.NetworkingConfig, containerName string) (types.ContainerCreateResponse, error) { 23 | var response types.ContainerCreateResponse 24 | query := url.Values{} 25 | if containerName != "" { 26 | query.Set("name", containerName) 27 | } 28 | 29 | body := configWrapper{ 30 | Config: config, 31 | HostConfig: hostConfig, 32 | NetworkingConfig: networkingConfig, 33 | } 34 | 35 | serverResp, err := cli.post(ctx, "/containers/create", query, body, nil) 36 | if err != nil { 37 | if serverResp.statusCode == 404 && strings.Contains(err.Error(), "No such image") { 38 | return response, imageNotFoundError{config.Image} 39 | } 40 | return response, err 41 | } 42 | 43 | err = json.NewDecoder(serverResp.body).Decode(&response) 44 | ensureReaderClosed(serverResp) 45 | return response, err 46 | } 47 | -------------------------------------------------------------------------------- /client/container_create_test.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "fmt" 7 | "io/ioutil" 8 | "net/http" 9 | "strings" 10 | "testing" 11 | 12 | "github.com/docker/engine-api/types" 13 | "github.com/docker/engine-api/types/container" 14 | "golang.org/x/net/context" 15 | ) 16 | 17 | func TestContainerCreateError(t *testing.T) { 18 | client := &Client{ 19 | transport: newMockClient(nil, errorMock(http.StatusInternalServerError, "Server error")), 20 | } 21 | _, err := client.ContainerCreate(context.Background(), nil, nil, nil, "nothing") 22 | if err == nil || err.Error() != "Error response from daemon: Server error" { 23 | t.Fatalf("expected a Server Error, got %v", err) 24 | } 25 | 26 | // 404 doesn't automagitally means an unknown image 27 | client = &Client{ 28 | transport: newMockClient(nil, errorMock(http.StatusNotFound, "Server error")), 29 | } 30 | _, err = client.ContainerCreate(context.Background(), nil, nil, nil, "nothing") 31 | if err == nil || err.Error() != "Error response from daemon: Server error" { 32 | t.Fatalf("expected a Server Error, got %v", err) 33 | } 34 | } 35 | 36 | func TestContainerCreateImageNotFound(t *testing.T) { 37 | client := &Client{ 38 | transport: newMockClient(nil, errorMock(http.StatusNotFound, "No such image")), 39 | } 40 | _, err := client.ContainerCreate(context.Background(), &container.Config{Image: "unknown_image"}, nil, nil, "unknown") 41 | if err == nil || !IsErrImageNotFound(err) { 42 | t.Fatalf("expected an imageNotFound error, got %v", err) 43 | } 44 | } 45 | 46 | func TestContainerCreateWithName(t *testing.T) { 47 | expectedURL := "/containers/create" 48 | client := &Client{ 49 | transport: newMockClient(nil, func(req *http.Request) (*http.Response, error) { 50 | if !strings.HasPrefix(req.URL.Path, expectedURL) { 51 | return nil, fmt.Errorf("Expected URL '%s', got '%s'", expectedURL, req.URL) 52 | } 53 | name := req.URL.Query().Get("name") 54 | if name != "container_name" { 55 | return nil, fmt.Errorf("container name not set in URL query properly. Expected `container_name`, got %s", name) 56 | } 57 | b, err := json.Marshal(types.ContainerCreateResponse{ 58 | ID: "container_id", 59 | }) 60 | if err != nil { 61 | return nil, err 62 | } 63 | return &http.Response{ 64 | StatusCode: http.StatusOK, 65 | Body: ioutil.NopCloser(bytes.NewReader(b)), 66 | }, nil 67 | }), 68 | } 69 | 70 | r, err := client.ContainerCreate(context.Background(), nil, nil, nil, "container_name") 71 | if err != nil { 72 | t.Fatal(err) 73 | } 74 | if r.ID != "container_id" { 75 | t.Fatalf("expected `container_id`, got %s", r.ID) 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /client/container_diff.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "encoding/json" 5 | "net/url" 6 | 7 | "github.com/docker/engine-api/types" 8 | "golang.org/x/net/context" 9 | ) 10 | 11 | // ContainerDiff shows differences in a container filesystem since it was started. 12 | func (cli *Client) ContainerDiff(ctx context.Context, containerID string) ([]types.ContainerChange, error) { 13 | var changes []types.ContainerChange 14 | 15 | serverResp, err := cli.get(ctx, "/containers/"+containerID+"/changes", url.Values{}, nil) 16 | if err != nil { 17 | return changes, err 18 | } 19 | 20 | err = json.NewDecoder(serverResp.body).Decode(&changes) 21 | ensureReaderClosed(serverResp) 22 | return changes, err 23 | } 24 | -------------------------------------------------------------------------------- /client/container_diff_test.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "fmt" 7 | "io/ioutil" 8 | "net/http" 9 | "strings" 10 | "testing" 11 | 12 | "github.com/docker/engine-api/types" 13 | "golang.org/x/net/context" 14 | ) 15 | 16 | func TestContainerDiffError(t *testing.T) { 17 | client := &Client{ 18 | transport: newMockClient(nil, errorMock(http.StatusInternalServerError, "Server error")), 19 | } 20 | _, err := client.ContainerDiff(context.Background(), "nothing") 21 | if err == nil || err.Error() != "Error response from daemon: Server error" { 22 | t.Fatalf("expected a Server Error, got %v", err) 23 | } 24 | 25 | } 26 | 27 | func TestContainerDiff(t *testing.T) { 28 | expectedURL := "/containers/container_id/changes" 29 | client := &Client{ 30 | transport: newMockClient(nil, func(req *http.Request) (*http.Response, error) { 31 | if !strings.HasPrefix(req.URL.Path, expectedURL) { 32 | return nil, fmt.Errorf("Expected URL '%s', got '%s'", expectedURL, req.URL) 33 | } 34 | b, err := json.Marshal([]types.ContainerChange{ 35 | { 36 | Kind: 0, 37 | Path: "/path/1", 38 | }, 39 | { 40 | Kind: 1, 41 | Path: "/path/2", 42 | }, 43 | }) 44 | if err != nil { 45 | return nil, err 46 | } 47 | return &http.Response{ 48 | StatusCode: http.StatusOK, 49 | Body: ioutil.NopCloser(bytes.NewReader(b)), 50 | }, nil 51 | }), 52 | } 53 | 54 | changes, err := client.ContainerDiff(context.Background(), "container_id") 55 | if err != nil { 56 | t.Fatal(err) 57 | } 58 | if len(changes) != 2 { 59 | t.Fatalf("expected an array of 2 changes, got %v", changes) 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /client/container_exec.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "encoding/json" 5 | 6 | "github.com/docker/engine-api/types" 7 | "golang.org/x/net/context" 8 | ) 9 | 10 | // ContainerExecCreate creates a new exec configuration to run an exec process. 11 | func (cli *Client) ContainerExecCreate(ctx context.Context, container string, config types.ExecConfig) (types.ContainerExecCreateResponse, error) { 12 | var response types.ContainerExecCreateResponse 13 | resp, err := cli.post(ctx, "/containers/"+container+"/exec", nil, config, nil) 14 | if err != nil { 15 | return response, err 16 | } 17 | err = json.NewDecoder(resp.body).Decode(&response) 18 | ensureReaderClosed(resp) 19 | return response, err 20 | } 21 | 22 | // ContainerExecStart starts an exec process already created in the docker host. 23 | func (cli *Client) ContainerExecStart(ctx context.Context, execID string, config types.ExecStartCheck) error { 24 | resp, err := cli.post(ctx, "/exec/"+execID+"/start", nil, config, nil) 25 | ensureReaderClosed(resp) 26 | return err 27 | } 28 | 29 | // ContainerExecAttach attaches a connection to an exec process in the server. 30 | // It returns a types.HijackedConnection with the hijacked connection 31 | // and the a reader to get output. It's up to the called to close 32 | // the hijacked connection by calling types.HijackedResponse.Close. 33 | func (cli *Client) ContainerExecAttach(ctx context.Context, execID string, config types.ExecConfig) (types.HijackedResponse, error) { 34 | headers := map[string][]string{"Content-Type": {"application/json"}} 35 | return cli.postHijacked(ctx, "/exec/"+execID+"/start", nil, config, headers) 36 | } 37 | 38 | // ContainerExecInspect returns information about a specific exec process on the docker host. 39 | func (cli *Client) ContainerExecInspect(ctx context.Context, execID string) (types.ContainerExecInspect, error) { 40 | var response types.ContainerExecInspect 41 | resp, err := cli.get(ctx, "/exec/"+execID+"/json", nil, nil) 42 | if err != nil { 43 | return response, err 44 | } 45 | 46 | err = json.NewDecoder(resp.body).Decode(&response) 47 | ensureReaderClosed(resp) 48 | return response, err 49 | } 50 | -------------------------------------------------------------------------------- /client/container_export.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "io" 5 | "net/url" 6 | 7 | "golang.org/x/net/context" 8 | ) 9 | 10 | // ContainerExport retrieves the raw contents of a container 11 | // and returns them as an io.ReadCloser. It's up to the caller 12 | // to close the stream. 13 | func (cli *Client) ContainerExport(ctx context.Context, containerID string) (io.ReadCloser, error) { 14 | serverResp, err := cli.get(ctx, "/containers/"+containerID+"/export", url.Values{}, nil) 15 | if err != nil { 16 | return nil, err 17 | } 18 | 19 | return serverResp.body, nil 20 | } 21 | -------------------------------------------------------------------------------- /client/container_export_test.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "io/ioutil" 7 | "net/http" 8 | "strings" 9 | "testing" 10 | 11 | "golang.org/x/net/context" 12 | ) 13 | 14 | func TestContainerExportError(t *testing.T) { 15 | client := &Client{ 16 | transport: newMockClient(nil, errorMock(http.StatusInternalServerError, "Server error")), 17 | } 18 | _, err := client.ContainerExport(context.Background(), "nothing") 19 | if err == nil || err.Error() != "Error response from daemon: Server error" { 20 | t.Fatalf("expected a Server Error, got %v", err) 21 | } 22 | } 23 | 24 | func TestContainerExport(t *testing.T) { 25 | expectedURL := "/containers/container_id/export" 26 | client := &Client{ 27 | transport: newMockClient(nil, func(r *http.Request) (*http.Response, error) { 28 | if !strings.HasPrefix(r.URL.Path, expectedURL) { 29 | return nil, fmt.Errorf("Expected URL '%s', got '%s'", expectedURL, r.URL) 30 | } 31 | 32 | return &http.Response{ 33 | StatusCode: http.StatusOK, 34 | Body: ioutil.NopCloser(bytes.NewReader([]byte("response"))), 35 | }, nil 36 | }), 37 | } 38 | body, err := client.ContainerExport(context.Background(), "container_id") 39 | if err != nil { 40 | t.Fatal(err) 41 | } 42 | defer body.Close() 43 | content, err := ioutil.ReadAll(body) 44 | if err != nil { 45 | t.Fatal(err) 46 | } 47 | if string(content) != "response" { 48 | t.Fatalf("expected response to contain 'response', got %s", string(content)) 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /client/container_inspect.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "io/ioutil" 7 | "net/http" 8 | "net/url" 9 | 10 | "github.com/docker/engine-api/types" 11 | "golang.org/x/net/context" 12 | ) 13 | 14 | // ContainerInspect returns the container information. 15 | func (cli *Client) ContainerInspect(ctx context.Context, containerID string) (types.ContainerJSON, error) { 16 | serverResp, err := cli.get(ctx, "/containers/"+containerID+"/json", nil, nil) 17 | if err != nil { 18 | if serverResp.statusCode == http.StatusNotFound { 19 | return types.ContainerJSON{}, containerNotFoundError{containerID} 20 | } 21 | return types.ContainerJSON{}, err 22 | } 23 | 24 | var response types.ContainerJSON 25 | err = json.NewDecoder(serverResp.body).Decode(&response) 26 | ensureReaderClosed(serverResp) 27 | return response, err 28 | } 29 | 30 | // ContainerInspectWithRaw returns the container information and its raw representation. 31 | func (cli *Client) ContainerInspectWithRaw(ctx context.Context, containerID string, getSize bool) (types.ContainerJSON, []byte, error) { 32 | query := url.Values{} 33 | if getSize { 34 | query.Set("size", "1") 35 | } 36 | serverResp, err := cli.get(ctx, "/containers/"+containerID+"/json", query, nil) 37 | if err != nil { 38 | if serverResp.statusCode == http.StatusNotFound { 39 | return types.ContainerJSON{}, nil, containerNotFoundError{containerID} 40 | } 41 | return types.ContainerJSON{}, nil, err 42 | } 43 | defer ensureReaderClosed(serverResp) 44 | 45 | body, err := ioutil.ReadAll(serverResp.body) 46 | if err != nil { 47 | return types.ContainerJSON{}, nil, err 48 | } 49 | 50 | var response types.ContainerJSON 51 | rdr := bytes.NewReader(body) 52 | err = json.NewDecoder(rdr).Decode(&response) 53 | return response, body, err 54 | } 55 | -------------------------------------------------------------------------------- /client/container_kill.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "net/url" 5 | 6 | "golang.org/x/net/context" 7 | ) 8 | 9 | // ContainerKill terminates the container process but does not remove the container from the docker host. 10 | func (cli *Client) ContainerKill(ctx context.Context, containerID, signal string) error { 11 | query := url.Values{} 12 | query.Set("signal", signal) 13 | 14 | resp, err := cli.post(ctx, "/containers/"+containerID+"/kill", query, nil, nil) 15 | ensureReaderClosed(resp) 16 | return err 17 | } 18 | -------------------------------------------------------------------------------- /client/container_kill_test.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "io/ioutil" 7 | "net/http" 8 | "strings" 9 | "testing" 10 | 11 | "golang.org/x/net/context" 12 | ) 13 | 14 | func TestContainerKillError(t *testing.T) { 15 | client := &Client{ 16 | transport: newMockClient(nil, errorMock(http.StatusInternalServerError, "Server error")), 17 | } 18 | err := client.ContainerKill(context.Background(), "nothing", "SIGKILL") 19 | if err == nil || err.Error() != "Error response from daemon: Server error" { 20 | t.Fatalf("expected a Server Error, got %v", err) 21 | } 22 | } 23 | 24 | func TestContainerKill(t *testing.T) { 25 | expectedURL := "/containers/container_id/kill" 26 | client := &Client{ 27 | transport: newMockClient(nil, func(req *http.Request) (*http.Response, error) { 28 | if !strings.HasPrefix(req.URL.Path, expectedURL) { 29 | return nil, fmt.Errorf("Expected URL '%s', got '%s'", expectedURL, req.URL) 30 | } 31 | signal := req.URL.Query().Get("signal") 32 | if signal != "SIGKILL" { 33 | return nil, fmt.Errorf("signal not set in URL query properly. Expected 'SIGKILL', got %s", signal) 34 | } 35 | return &http.Response{ 36 | StatusCode: http.StatusOK, 37 | Body: ioutil.NopCloser(bytes.NewReader([]byte(""))), 38 | }, nil 39 | }), 40 | } 41 | 42 | err := client.ContainerKill(context.Background(), "container_id", "SIGKILL") 43 | if err != nil { 44 | t.Fatal(err) 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /client/container_list.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "encoding/json" 5 | "net/url" 6 | "strconv" 7 | 8 | "github.com/docker/engine-api/types" 9 | "github.com/docker/engine-api/types/filters" 10 | "golang.org/x/net/context" 11 | ) 12 | 13 | // ContainerList returns the list of containers in the docker host. 14 | func (cli *Client) ContainerList(ctx context.Context, options types.ContainerListOptions) ([]types.Container, error) { 15 | query := url.Values{} 16 | 17 | if options.All { 18 | query.Set("all", "1") 19 | } 20 | 21 | if options.Limit != -1 { 22 | query.Set("limit", strconv.Itoa(options.Limit)) 23 | } 24 | 25 | if options.Since != "" { 26 | query.Set("since", options.Since) 27 | } 28 | 29 | if options.Before != "" { 30 | query.Set("before", options.Before) 31 | } 32 | 33 | if options.Size { 34 | query.Set("size", "1") 35 | } 36 | 37 | if options.Filter.Len() > 0 { 38 | filterJSON, err := filters.ToParamWithVersion(cli.version, options.Filter) 39 | 40 | if err != nil { 41 | return nil, err 42 | } 43 | 44 | query.Set("filters", filterJSON) 45 | } 46 | 47 | resp, err := cli.get(ctx, "/containers/json", query, nil) 48 | if err != nil { 49 | return nil, err 50 | } 51 | 52 | var containers []types.Container 53 | err = json.NewDecoder(resp.body).Decode(&containers) 54 | ensureReaderClosed(resp) 55 | return containers, err 56 | } 57 | -------------------------------------------------------------------------------- /client/container_logs.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "io" 5 | "net/url" 6 | "time" 7 | 8 | "golang.org/x/net/context" 9 | 10 | "github.com/docker/engine-api/types" 11 | timetypes "github.com/docker/engine-api/types/time" 12 | ) 13 | 14 | // ContainerLogs returns the logs generated by a container in an io.ReadCloser. 15 | // It's up to the caller to close the stream. 16 | func (cli *Client) ContainerLogs(ctx context.Context, container string, options types.ContainerLogsOptions) (io.ReadCloser, error) { 17 | query := url.Values{} 18 | if options.ShowStdout { 19 | query.Set("stdout", "1") 20 | } 21 | 22 | if options.ShowStderr { 23 | query.Set("stderr", "1") 24 | } 25 | 26 | if options.Since != "" { 27 | ts, err := timetypes.GetTimestamp(options.Since, time.Now()) 28 | if err != nil { 29 | return nil, err 30 | } 31 | query.Set("since", ts) 32 | } 33 | 34 | if options.Timestamps { 35 | query.Set("timestamps", "1") 36 | } 37 | 38 | if options.Details { 39 | query.Set("details", "1") 40 | } 41 | 42 | if options.Follow { 43 | query.Set("follow", "1") 44 | } 45 | query.Set("tail", options.Tail) 46 | 47 | resp, err := cli.get(ctx, "/containers/"+container+"/logs", query, nil) 48 | if err != nil { 49 | return nil, err 50 | } 51 | return resp.body, nil 52 | } 53 | -------------------------------------------------------------------------------- /client/container_pause.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import "golang.org/x/net/context" 4 | 5 | // ContainerPause pauses the main process of a given container without terminating it. 6 | func (cli *Client) ContainerPause(ctx context.Context, containerID string) error { 7 | resp, err := cli.post(ctx, "/containers/"+containerID+"/pause", nil, nil, nil) 8 | ensureReaderClosed(resp) 9 | return err 10 | } 11 | -------------------------------------------------------------------------------- /client/container_pause_test.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "io/ioutil" 7 | "net/http" 8 | "strings" 9 | "testing" 10 | 11 | "golang.org/x/net/context" 12 | ) 13 | 14 | func TestContainerPauseError(t *testing.T) { 15 | client := &Client{ 16 | transport: newMockClient(nil, errorMock(http.StatusInternalServerError, "Server error")), 17 | } 18 | err := client.ContainerPause(context.Background(), "nothing") 19 | if err == nil || err.Error() != "Error response from daemon: Server error" { 20 | t.Fatalf("expected a Server Error, got %v", err) 21 | } 22 | } 23 | 24 | func TestContainerPause(t *testing.T) { 25 | expectedURL := "/containers/container_id/pause" 26 | client := &Client{ 27 | transport: newMockClient(nil, func(req *http.Request) (*http.Response, error) { 28 | if !strings.HasPrefix(req.URL.Path, expectedURL) { 29 | return nil, fmt.Errorf("Expected URL '%s', got '%s'", expectedURL, req.URL) 30 | } 31 | return &http.Response{ 32 | StatusCode: http.StatusOK, 33 | Body: ioutil.NopCloser(bytes.NewReader([]byte(""))), 34 | }, nil 35 | }), 36 | } 37 | err := client.ContainerPause(context.Background(), "container_id") 38 | if err != nil { 39 | t.Fatal(err) 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /client/container_remove.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "net/url" 5 | 6 | "github.com/docker/engine-api/types" 7 | "golang.org/x/net/context" 8 | ) 9 | 10 | // ContainerRemove kills and removes a container from the docker host. 11 | func (cli *Client) ContainerRemove(ctx context.Context, containerID string, options types.ContainerRemoveOptions) error { 12 | query := url.Values{} 13 | if options.RemoveVolumes { 14 | query.Set("v", "1") 15 | } 16 | if options.RemoveLinks { 17 | query.Set("link", "1") 18 | } 19 | 20 | if options.Force { 21 | query.Set("force", "1") 22 | } 23 | 24 | resp, err := cli.delete(ctx, "/containers/"+containerID, query, nil) 25 | ensureReaderClosed(resp) 26 | return err 27 | } 28 | -------------------------------------------------------------------------------- /client/container_remove_test.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "io/ioutil" 7 | "net/http" 8 | "strings" 9 | "testing" 10 | 11 | "github.com/docker/engine-api/types" 12 | "golang.org/x/net/context" 13 | ) 14 | 15 | func TestContainerRemoveError(t *testing.T) { 16 | client := &Client{ 17 | transport: newMockClient(nil, errorMock(http.StatusInternalServerError, "Server error")), 18 | } 19 | err := client.ContainerRemove(context.Background(), "container_id", types.ContainerRemoveOptions{}) 20 | if err == nil || err.Error() != "Error response from daemon: Server error" { 21 | t.Fatalf("expected a Server Error, got %v", err) 22 | } 23 | } 24 | 25 | func TestContainerRemove(t *testing.T) { 26 | expectedURL := "/containers/container_id" 27 | client := &Client{ 28 | transport: newMockClient(nil, func(req *http.Request) (*http.Response, error) { 29 | if !strings.HasPrefix(req.URL.Path, expectedURL) { 30 | return nil, fmt.Errorf("Expected URL '%s', got '%s'", expectedURL, req.URL) 31 | } 32 | query := req.URL.Query() 33 | volume := query.Get("v") 34 | if volume != "1" { 35 | return nil, fmt.Errorf("v (volume) not set in URL query properly. Expected '1', got %s", volume) 36 | } 37 | force := query.Get("force") 38 | if force != "1" { 39 | return nil, fmt.Errorf("force not set in URL query properly. Expected '1', got %s", force) 40 | } 41 | link := query.Get("link") 42 | if link != "" { 43 | return nil, fmt.Errorf("link should have not be present in query, go %s", link) 44 | } 45 | return &http.Response{ 46 | StatusCode: http.StatusOK, 47 | Body: ioutil.NopCloser(bytes.NewReader([]byte(""))), 48 | }, nil 49 | }), 50 | } 51 | 52 | err := client.ContainerRemove(context.Background(), "container_id", types.ContainerRemoveOptions{ 53 | RemoveVolumes: true, 54 | Force: true, 55 | }) 56 | if err != nil { 57 | t.Fatal(err) 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /client/container_rename.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "net/url" 5 | 6 | "golang.org/x/net/context" 7 | ) 8 | 9 | // ContainerRename changes the name of a given container. 10 | func (cli *Client) ContainerRename(ctx context.Context, containerID, newContainerName string) error { 11 | query := url.Values{} 12 | query.Set("name", newContainerName) 13 | resp, err := cli.post(ctx, "/containers/"+containerID+"/rename", query, nil, nil) 14 | ensureReaderClosed(resp) 15 | return err 16 | } 17 | -------------------------------------------------------------------------------- /client/container_rename_test.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "io/ioutil" 7 | "net/http" 8 | "strings" 9 | "testing" 10 | 11 | "golang.org/x/net/context" 12 | ) 13 | 14 | func TestContainerRenameError(t *testing.T) { 15 | client := &Client{ 16 | transport: newMockClient(nil, errorMock(http.StatusInternalServerError, "Server error")), 17 | } 18 | err := client.ContainerRename(context.Background(), "nothing", "newNothing") 19 | if err == nil || err.Error() != "Error response from daemon: Server error" { 20 | t.Fatalf("expected a Server Error, got %v", err) 21 | } 22 | } 23 | 24 | func TestContainerRename(t *testing.T) { 25 | expectedURL := "/containers/container_id/rename" 26 | client := &Client{ 27 | transport: newMockClient(nil, func(req *http.Request) (*http.Response, error) { 28 | if !strings.HasPrefix(req.URL.Path, expectedURL) { 29 | return nil, fmt.Errorf("Expected URL '%s', got '%s'", expectedURL, req.URL) 30 | } 31 | name := req.URL.Query().Get("name") 32 | if name != "newName" { 33 | return nil, fmt.Errorf("name not set in URL query properly. Expected 'newName', got %s", name) 34 | } 35 | return &http.Response{ 36 | StatusCode: http.StatusOK, 37 | Body: ioutil.NopCloser(bytes.NewReader([]byte(""))), 38 | }, nil 39 | }), 40 | } 41 | 42 | err := client.ContainerRename(context.Background(), "container_id", "newName") 43 | if err != nil { 44 | t.Fatal(err) 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /client/container_resize.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "net/url" 5 | "strconv" 6 | 7 | "github.com/docker/engine-api/types" 8 | "golang.org/x/net/context" 9 | ) 10 | 11 | // ContainerResize changes the size of the tty for a container. 12 | func (cli *Client) ContainerResize(ctx context.Context, containerID string, options types.ResizeOptions) error { 13 | return cli.resize(ctx, "/containers/"+containerID, options.Height, options.Width) 14 | } 15 | 16 | // ContainerExecResize changes the size of the tty for an exec process running inside a container. 17 | func (cli *Client) ContainerExecResize(ctx context.Context, execID string, options types.ResizeOptions) error { 18 | return cli.resize(ctx, "/exec/"+execID, options.Height, options.Width) 19 | } 20 | 21 | func (cli *Client) resize(ctx context.Context, basePath string, height, width int) error { 22 | query := url.Values{} 23 | query.Set("h", strconv.Itoa(height)) 24 | query.Set("w", strconv.Itoa(width)) 25 | 26 | resp, err := cli.post(ctx, basePath+"/resize", query, nil, nil) 27 | ensureReaderClosed(resp) 28 | return err 29 | } 30 | -------------------------------------------------------------------------------- /client/container_resize_test.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "io/ioutil" 7 | "net/http" 8 | "strings" 9 | "testing" 10 | 11 | "github.com/docker/engine-api/types" 12 | "golang.org/x/net/context" 13 | ) 14 | 15 | func TestContainerResizeError(t *testing.T) { 16 | client := &Client{ 17 | transport: newMockClient(nil, errorMock(http.StatusInternalServerError, "Server error")), 18 | } 19 | err := client.ContainerResize(context.Background(), "container_id", types.ResizeOptions{}) 20 | if err == nil || err.Error() != "Error response from daemon: Server error" { 21 | t.Fatalf("expected a Server Error, got %v", err) 22 | } 23 | } 24 | 25 | func TestContainerExecResizeError(t *testing.T) { 26 | client := &Client{ 27 | transport: newMockClient(nil, errorMock(http.StatusInternalServerError, "Server error")), 28 | } 29 | err := client.ContainerExecResize(context.Background(), "exec_id", types.ResizeOptions{}) 30 | if err == nil || err.Error() != "Error response from daemon: Server error" { 31 | t.Fatalf("expected a Server Error, got %v", err) 32 | } 33 | } 34 | 35 | func TestContainerResize(t *testing.T) { 36 | client := &Client{ 37 | transport: newMockClient(nil, resizeTransport("/containers/container_id/resize")), 38 | } 39 | 40 | err := client.ContainerResize(context.Background(), "container_id", types.ResizeOptions{ 41 | Height: 500, 42 | Width: 600, 43 | }) 44 | if err != nil { 45 | t.Fatal(err) 46 | } 47 | } 48 | 49 | func TestContainerExecResize(t *testing.T) { 50 | client := &Client{ 51 | transport: newMockClient(nil, resizeTransport("/exec/exec_id/resize")), 52 | } 53 | 54 | err := client.ContainerExecResize(context.Background(), "exec_id", types.ResizeOptions{ 55 | Height: 500, 56 | Width: 600, 57 | }) 58 | if err != nil { 59 | t.Fatal(err) 60 | } 61 | } 62 | 63 | func resizeTransport(expectedURL string) func(req *http.Request) (*http.Response, error) { 64 | return func(req *http.Request) (*http.Response, error) { 65 | if !strings.HasPrefix(req.URL.Path, expectedURL) { 66 | return nil, fmt.Errorf("Expected URL '%s', got '%s'", expectedURL, req.URL) 67 | } 68 | query := req.URL.Query() 69 | h := query.Get("h") 70 | if h != "500" { 71 | return nil, fmt.Errorf("h not set in URL query properly. Expected '500', got %s", h) 72 | } 73 | w := query.Get("w") 74 | if w != "600" { 75 | return nil, fmt.Errorf("w not set in URL query properly. Expected '600', got %s", w) 76 | } 77 | return &http.Response{ 78 | StatusCode: http.StatusOK, 79 | Body: ioutil.NopCloser(bytes.NewReader([]byte(""))), 80 | }, nil 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /client/container_restart.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "net/url" 5 | "time" 6 | 7 | timetypes "github.com/docker/engine-api/types/time" 8 | "golang.org/x/net/context" 9 | ) 10 | 11 | // ContainerRestart stops and starts a container again. 12 | // It makes the daemon to wait for the container to be up again for 13 | // a specific amount of time, given the timeout. 14 | func (cli *Client) ContainerRestart(ctx context.Context, containerID string, timeout *time.Duration) error { 15 | query := url.Values{} 16 | if timeout != nil { 17 | query.Set("t", timetypes.DurationToSecondsString(*timeout)) 18 | } 19 | resp, err := cli.post(ctx, "/containers/"+containerID+"/restart", query, nil, nil) 20 | ensureReaderClosed(resp) 21 | return err 22 | } 23 | -------------------------------------------------------------------------------- /client/container_restart_test.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "io/ioutil" 7 | "net/http" 8 | "strings" 9 | "testing" 10 | "time" 11 | 12 | "golang.org/x/net/context" 13 | ) 14 | 15 | func TestContainerRestartError(t *testing.T) { 16 | client := &Client{ 17 | transport: newMockClient(nil, errorMock(http.StatusInternalServerError, "Server error")), 18 | } 19 | timeout := 0*time.Second 20 | err := client.ContainerRestart(context.Background(), "nothing", &timeout) 21 | if err == nil || err.Error() != "Error response from daemon: Server error" { 22 | t.Fatalf("expected a Server Error, got %v", err) 23 | } 24 | } 25 | 26 | func TestContainerRestart(t *testing.T) { 27 | expectedURL := "/containers/container_id/restart" 28 | client := &Client{ 29 | transport: newMockClient(nil, func(req *http.Request) (*http.Response, error) { 30 | if !strings.HasPrefix(req.URL.Path, expectedURL) { 31 | return nil, fmt.Errorf("Expected URL '%s', got '%s'", expectedURL, req.URL) 32 | } 33 | t := req.URL.Query().Get("t") 34 | if t != "100" { 35 | return nil, fmt.Errorf("t (timeout) not set in URL query properly. Expected '100', got %s", t) 36 | } 37 | return &http.Response{ 38 | StatusCode: http.StatusOK, 39 | Body: ioutil.NopCloser(bytes.NewReader([]byte(""))), 40 | }, nil 41 | }), 42 | } 43 | timeout := 100*time.Second 44 | err := client.ContainerRestart(context.Background(), "container_id", &timeout) 45 | if err != nil { 46 | t.Fatal(err) 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /client/container_start.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "net/url" 5 | 6 | "golang.org/x/net/context" 7 | 8 | "github.com/docker/engine-api/types" 9 | ) 10 | 11 | // ContainerStart sends a request to the docker daemon to start a container. 12 | func (cli *Client) ContainerStart(ctx context.Context, containerID string, options types.ContainerStartOptions) error { 13 | query := url.Values{} 14 | if len(options.CheckpointID) != 0 { 15 | query.Set("checkpoint", options.CheckpointID) 16 | } 17 | 18 | resp, err := cli.post(ctx, "/containers/"+containerID+"/start", query, nil, nil) 19 | ensureReaderClosed(resp) 20 | return err 21 | } 22 | -------------------------------------------------------------------------------- /client/container_start_test.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "fmt" 7 | "io/ioutil" 8 | "net/http" 9 | "strings" 10 | "testing" 11 | 12 | "golang.org/x/net/context" 13 | 14 | "github.com/docker/engine-api/types" 15 | ) 16 | 17 | func TestContainerStartError(t *testing.T) { 18 | client := &Client{ 19 | transport: newMockClient(nil, errorMock(http.StatusInternalServerError, "Server error")), 20 | } 21 | err := client.ContainerStart(context.Background(), "nothing", types.ContainerStartOptions{}) 22 | if err == nil || err.Error() != "Error response from daemon: Server error" { 23 | t.Fatalf("expected a Server Error, got %v", err) 24 | } 25 | } 26 | 27 | func TestContainerStart(t *testing.T) { 28 | expectedURL := "/containers/container_id/start" 29 | client := &Client{ 30 | transport: newMockClient(nil, func(req *http.Request) (*http.Response, error) { 31 | if !strings.HasPrefix(req.URL.Path, expectedURL) { 32 | return nil, fmt.Errorf("Expected URL '%s', got '%s'", expectedURL, req.URL) 33 | } 34 | // we're not expecting any payload, but if one is supplied, check it is valid. 35 | if req.Header.Get("Content-Type") == "application/json" { 36 | var startConfig interface{} 37 | if err := json.NewDecoder(req.Body).Decode(&startConfig); err != nil { 38 | return nil, fmt.Errorf("Unable to parse json: %s", err) 39 | } 40 | } 41 | 42 | checkpoint := req.URL.Query().Get("checkpoint") 43 | if checkpoint != "checkpoint_id" { 44 | return nil, fmt.Errorf("checkpoint not set in URL query properly. Expected 'checkpoint_id', got %s", checkpoint) 45 | } 46 | 47 | return &http.Response{ 48 | StatusCode: http.StatusOK, 49 | Body: ioutil.NopCloser(bytes.NewReader([]byte(""))), 50 | }, nil 51 | }), 52 | } 53 | 54 | err := client.ContainerStart(context.Background(), "container_id", types.ContainerStartOptions{CheckpointID: "checkpoint_id"}) 55 | if err != nil { 56 | t.Fatal(err) 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /client/container_stats.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "io" 5 | "net/url" 6 | 7 | "golang.org/x/net/context" 8 | ) 9 | 10 | // ContainerStats returns near realtime stats for a given container. 11 | // It's up to the caller to close the io.ReadCloser returned. 12 | func (cli *Client) ContainerStats(ctx context.Context, containerID string, stream bool) (io.ReadCloser, error) { 13 | query := url.Values{} 14 | query.Set("stream", "0") 15 | if stream { 16 | query.Set("stream", "1") 17 | } 18 | 19 | resp, err := cli.get(ctx, "/containers/"+containerID+"/stats", query, nil) 20 | if err != nil { 21 | return nil, err 22 | } 23 | return resp.body, err 24 | } 25 | -------------------------------------------------------------------------------- /client/container_stats_test.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "io/ioutil" 7 | "net/http" 8 | "strings" 9 | "testing" 10 | 11 | "golang.org/x/net/context" 12 | ) 13 | 14 | func TestContainerStatsError(t *testing.T) { 15 | client := &Client{ 16 | transport: newMockClient(nil, errorMock(http.StatusInternalServerError, "Server error")), 17 | } 18 | _, err := client.ContainerStats(context.Background(), "nothing", false) 19 | if err == nil || err.Error() != "Error response from daemon: Server error" { 20 | t.Fatalf("expected a Server Error, got %v", err) 21 | } 22 | } 23 | 24 | func TestContainerStats(t *testing.T) { 25 | expectedURL := "/containers/container_id/stats" 26 | cases := []struct { 27 | stream bool 28 | expectedStream string 29 | }{ 30 | { 31 | expectedStream: "0", 32 | }, 33 | { 34 | stream: true, 35 | expectedStream: "1", 36 | }, 37 | } 38 | for _, c := range cases { 39 | client := &Client{ 40 | transport: newMockClient(nil, func(r *http.Request) (*http.Response, error) { 41 | if !strings.HasPrefix(r.URL.Path, expectedURL) { 42 | return nil, fmt.Errorf("Expected URL '%s', got '%s'", expectedURL, r.URL) 43 | } 44 | 45 | query := r.URL.Query() 46 | stream := query.Get("stream") 47 | if stream != c.expectedStream { 48 | return nil, fmt.Errorf("stream not set in URL query properly. Expected '%s', got %s", c.expectedStream, stream) 49 | } 50 | 51 | return &http.Response{ 52 | StatusCode: http.StatusOK, 53 | Body: ioutil.NopCloser(bytes.NewReader([]byte("response"))), 54 | }, nil 55 | }), 56 | } 57 | body, err := client.ContainerStats(context.Background(), "container_id", c.stream) 58 | if err != nil { 59 | t.Fatal(err) 60 | } 61 | defer body.Close() 62 | content, err := ioutil.ReadAll(body) 63 | if err != nil { 64 | t.Fatal(err) 65 | } 66 | if string(content) != "response" { 67 | t.Fatalf("expected response to contain 'response', got %s", string(content)) 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /client/container_stop.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "net/url" 5 | "time" 6 | 7 | timetypes "github.com/docker/engine-api/types/time" 8 | "golang.org/x/net/context" 9 | ) 10 | 11 | // ContainerStop stops a container without terminating the process. 12 | // The process is blocked until the container stops or the timeout expires. 13 | func (cli *Client) ContainerStop(ctx context.Context, containerID string, timeout *time.Duration) error { 14 | query := url.Values{} 15 | if timeout != nil { 16 | query.Set("t", timetypes.DurationToSecondsString(*timeout)) 17 | } 18 | resp, err := cli.post(ctx, "/containers/"+containerID+"/stop", query, nil, nil) 19 | ensureReaderClosed(resp) 20 | return err 21 | } 22 | -------------------------------------------------------------------------------- /client/container_stop_test.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "io/ioutil" 7 | "net/http" 8 | "strings" 9 | "testing" 10 | "time" 11 | 12 | "golang.org/x/net/context" 13 | ) 14 | 15 | func TestContainerStopError(t *testing.T) { 16 | client := &Client{ 17 | transport: newMockClient(nil, errorMock(http.StatusInternalServerError, "Server error")), 18 | } 19 | timeout := 0*time.Second 20 | err := client.ContainerStop(context.Background(), "nothing", &timeout) 21 | if err == nil || err.Error() != "Error response from daemon: Server error" { 22 | t.Fatalf("expected a Server Error, got %v", err) 23 | } 24 | } 25 | 26 | func TestContainerStop(t *testing.T) { 27 | expectedURL := "/containers/container_id/stop" 28 | client := &Client{ 29 | transport: newMockClient(nil, func(req *http.Request) (*http.Response, error) { 30 | if !strings.HasPrefix(req.URL.Path, expectedURL) { 31 | return nil, fmt.Errorf("Expected URL '%s', got '%s'", expectedURL, req.URL) 32 | } 33 | t := req.URL.Query().Get("t") 34 | if t != "100" { 35 | return nil, fmt.Errorf("t (timeout) not set in URL query properly. Expected '100', got %s", t) 36 | } 37 | return &http.Response{ 38 | StatusCode: http.StatusOK, 39 | Body: ioutil.NopCloser(bytes.NewReader([]byte(""))), 40 | }, nil 41 | }), 42 | } 43 | timeout := 100*time.Second 44 | err := client.ContainerStop(context.Background(), "container_id", &timeout) 45 | if err != nil { 46 | t.Fatal(err) 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /client/container_top.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "encoding/json" 5 | "net/url" 6 | "strings" 7 | 8 | "github.com/docker/engine-api/types" 9 | "golang.org/x/net/context" 10 | ) 11 | 12 | // ContainerTop shows process information from within a container. 13 | func (cli *Client) ContainerTop(ctx context.Context, containerID string, arguments []string) (types.ContainerProcessList, error) { 14 | var response types.ContainerProcessList 15 | query := url.Values{} 16 | if len(arguments) > 0 { 17 | query.Set("ps_args", strings.Join(arguments, " ")) 18 | } 19 | 20 | resp, err := cli.get(ctx, "/containers/"+containerID+"/top", query, nil) 21 | if err != nil { 22 | return response, err 23 | } 24 | 25 | err = json.NewDecoder(resp.body).Decode(&response) 26 | ensureReaderClosed(resp) 27 | return response, err 28 | } 29 | -------------------------------------------------------------------------------- /client/container_top_test.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "fmt" 7 | "io/ioutil" 8 | "net/http" 9 | "reflect" 10 | "strings" 11 | "testing" 12 | 13 | "github.com/docker/engine-api/types" 14 | "golang.org/x/net/context" 15 | ) 16 | 17 | func TestContainerTopError(t *testing.T) { 18 | client := &Client{ 19 | transport: newMockClient(nil, errorMock(http.StatusInternalServerError, "Server error")), 20 | } 21 | _, err := client.ContainerTop(context.Background(), "nothing", []string{}) 22 | if err == nil || err.Error() != "Error response from daemon: Server error" { 23 | t.Fatalf("expected a Server Error, got %v", err) 24 | } 25 | } 26 | 27 | func TestContainerTop(t *testing.T) { 28 | expectedURL := "/containers/container_id/top" 29 | expectedProcesses := [][]string{ 30 | {"p1", "p2"}, 31 | {"p3"}, 32 | } 33 | expectedTitles := []string{"title1", "title2"} 34 | 35 | client := &Client{ 36 | transport: newMockClient(nil, func(req *http.Request) (*http.Response, error) { 37 | if !strings.HasPrefix(req.URL.Path, expectedURL) { 38 | return nil, fmt.Errorf("Expected URL '%s', got '%s'", expectedURL, req.URL) 39 | } 40 | query := req.URL.Query() 41 | args := query.Get("ps_args") 42 | if args != "arg1 arg2" { 43 | return nil, fmt.Errorf("args not set in URL query properly. Expected 'arg1 arg2', got %v", args) 44 | } 45 | 46 | b, err := json.Marshal(types.ContainerProcessList{ 47 | Processes: [][]string{ 48 | {"p1", "p2"}, 49 | {"p3"}, 50 | }, 51 | Titles: []string{"title1", "title2"}, 52 | }) 53 | if err != nil { 54 | return nil, err 55 | } 56 | 57 | return &http.Response{ 58 | StatusCode: http.StatusOK, 59 | Body: ioutil.NopCloser(bytes.NewReader(b)), 60 | }, nil 61 | }), 62 | } 63 | 64 | processList, err := client.ContainerTop(context.Background(), "container_id", []string{"arg1", "arg2"}) 65 | if err != nil { 66 | t.Fatal(err) 67 | } 68 | if !reflect.DeepEqual(expectedProcesses, processList.Processes) { 69 | t.Fatalf("Processes: expected %v, got %v", expectedProcesses, processList.Processes) 70 | } 71 | if !reflect.DeepEqual(expectedTitles, processList.Titles) { 72 | t.Fatalf("Titles: expected %v, got %v", expectedTitles, processList.Titles) 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /client/container_unpause.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import "golang.org/x/net/context" 4 | 5 | // ContainerUnpause resumes the process execution within a container 6 | func (cli *Client) ContainerUnpause(ctx context.Context, containerID string) error { 7 | resp, err := cli.post(ctx, "/containers/"+containerID+"/unpause", nil, nil, nil) 8 | ensureReaderClosed(resp) 9 | return err 10 | } 11 | -------------------------------------------------------------------------------- /client/container_unpause_test.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "io/ioutil" 7 | "net/http" 8 | "strings" 9 | "testing" 10 | 11 | "golang.org/x/net/context" 12 | ) 13 | 14 | func TestContainerUnpauseError(t *testing.T) { 15 | client := &Client{ 16 | transport: newMockClient(nil, errorMock(http.StatusInternalServerError, "Server error")), 17 | } 18 | err := client.ContainerUnpause(context.Background(), "nothing") 19 | if err == nil || err.Error() != "Error response from daemon: Server error" { 20 | t.Fatalf("expected a Server Error, got %v", err) 21 | } 22 | } 23 | 24 | func TestContainerUnpause(t *testing.T) { 25 | expectedURL := "/containers/container_id/unpause" 26 | client := &Client{ 27 | transport: newMockClient(nil, func(req *http.Request) (*http.Response, error) { 28 | if !strings.HasPrefix(req.URL.Path, expectedURL) { 29 | return nil, fmt.Errorf("Expected URL '%s', got '%s'", expectedURL, req.URL) 30 | } 31 | return &http.Response{ 32 | StatusCode: http.StatusOK, 33 | Body: ioutil.NopCloser(bytes.NewReader([]byte(""))), 34 | }, nil 35 | }), 36 | } 37 | err := client.ContainerUnpause(context.Background(), "container_id") 38 | if err != nil { 39 | t.Fatal(err) 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /client/container_update.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "encoding/json" 5 | 6 | "github.com/docker/engine-api/types" 7 | "github.com/docker/engine-api/types/container" 8 | "golang.org/x/net/context" 9 | ) 10 | 11 | // ContainerUpdate updates resources of a container 12 | func (cli *Client) ContainerUpdate(ctx context.Context, containerID string, updateConfig container.UpdateConfig) (types.ContainerUpdateResponse, error) { 13 | var response types.ContainerUpdateResponse 14 | serverResp, err := cli.post(ctx, "/containers/"+containerID+"/update", nil, updateConfig, nil) 15 | if err != nil { 16 | return response, err 17 | } 18 | 19 | err = json.NewDecoder(serverResp.body).Decode(&response) 20 | 21 | ensureReaderClosed(serverResp) 22 | return response, err 23 | } 24 | -------------------------------------------------------------------------------- /client/container_update_test.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "fmt" 7 | "io/ioutil" 8 | "net/http" 9 | "strings" 10 | "testing" 11 | 12 | "github.com/docker/engine-api/types" 13 | "github.com/docker/engine-api/types/container" 14 | "golang.org/x/net/context" 15 | ) 16 | 17 | func TestContainerUpdateError(t *testing.T) { 18 | client := &Client{ 19 | transport: newMockClient(nil, errorMock(http.StatusInternalServerError, "Server error")), 20 | } 21 | _, err := client.ContainerUpdate(context.Background(), "nothing", container.UpdateConfig{}) 22 | if err == nil || err.Error() != "Error response from daemon: Server error" { 23 | t.Fatalf("expected a Server Error, got %v", err) 24 | } 25 | } 26 | 27 | func TestContainerUpdate(t *testing.T) { 28 | expectedURL := "/containers/container_id/update" 29 | 30 | client := &Client{ 31 | transport: newMockClient(nil, func(req *http.Request) (*http.Response, error) { 32 | if !strings.HasPrefix(req.URL.Path, expectedURL) { 33 | return nil, fmt.Errorf("Expected URL '%s', got '%s'", expectedURL, req.URL) 34 | } 35 | 36 | b, err := json.Marshal(types.ContainerUpdateResponse{}) 37 | if err != nil { 38 | return nil, err 39 | } 40 | 41 | return &http.Response{ 42 | StatusCode: http.StatusOK, 43 | Body: ioutil.NopCloser(bytes.NewReader(b)), 44 | }, nil 45 | }), 46 | } 47 | 48 | _, err := client.ContainerUpdate(context.Background(), "container_id", container.UpdateConfig{ 49 | Resources: container.Resources{ 50 | CPUPeriod: 1, 51 | }, 52 | RestartPolicy: container.RestartPolicy{ 53 | Name: "always", 54 | }, 55 | }) 56 | if err != nil { 57 | t.Fatal(err) 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /client/container_wait.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "encoding/json" 5 | 6 | "golang.org/x/net/context" 7 | 8 | "github.com/docker/engine-api/types" 9 | ) 10 | 11 | // ContainerWait pauses execution until a container exits. 12 | // It returns the API status code as response of its readiness. 13 | func (cli *Client) ContainerWait(ctx context.Context, containerID string) (int, error) { 14 | resp, err := cli.post(ctx, "/containers/"+containerID+"/wait", nil, nil, nil) 15 | if err != nil { 16 | return -1, err 17 | } 18 | defer ensureReaderClosed(resp) 19 | 20 | var res types.ContainerWaitResponse 21 | if err := json.NewDecoder(resp.body).Decode(&res); err != nil { 22 | return -1, err 23 | } 24 | 25 | return res.StatusCode, nil 26 | } 27 | -------------------------------------------------------------------------------- /client/container_wait_test.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "fmt" 7 | "io/ioutil" 8 | "log" 9 | "net/http" 10 | "strings" 11 | "testing" 12 | "time" 13 | 14 | "github.com/docker/engine-api/types" 15 | 16 | "golang.org/x/net/context" 17 | ) 18 | 19 | func TestContainerWaitError(t *testing.T) { 20 | client := &Client{ 21 | transport: newMockClient(nil, errorMock(http.StatusInternalServerError, "Server error")), 22 | } 23 | code, err := client.ContainerWait(context.Background(), "nothing") 24 | if err == nil || err.Error() != "Error response from daemon: Server error" { 25 | t.Fatalf("expected a Server Error, got %v", err) 26 | } 27 | if code != -1 { 28 | t.Fatalf("expected a status code equal to '-1', got %d", code) 29 | } 30 | } 31 | 32 | func TestContainerWait(t *testing.T) { 33 | expectedURL := "/containers/container_id/wait" 34 | client := &Client{ 35 | transport: newMockClient(nil, func(req *http.Request) (*http.Response, error) { 36 | if !strings.HasPrefix(req.URL.Path, expectedURL) { 37 | return nil, fmt.Errorf("Expected URL '%s', got '%s'", expectedURL, req.URL) 38 | } 39 | b, err := json.Marshal(types.ContainerWaitResponse{ 40 | StatusCode: 15, 41 | }) 42 | if err != nil { 43 | return nil, err 44 | } 45 | return &http.Response{ 46 | StatusCode: http.StatusOK, 47 | Body: ioutil.NopCloser(bytes.NewReader(b)), 48 | }, nil 49 | }), 50 | } 51 | 52 | code, err := client.ContainerWait(context.Background(), "container_id") 53 | if err != nil { 54 | t.Fatal(err) 55 | } 56 | if code != 15 { 57 | t.Fatalf("expected a status code equal to '15', got %d", code) 58 | } 59 | } 60 | 61 | func ExampleClient_ContainerWait_withTimeout() { 62 | ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) 63 | defer cancel() 64 | 65 | client, _ := NewEnvClient() 66 | _, err := client.ContainerWait(ctx, "container_id") 67 | if err != nil { 68 | log.Fatal(err) 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /client/events.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "io" 5 | "net/url" 6 | "time" 7 | 8 | "golang.org/x/net/context" 9 | 10 | "github.com/docker/engine-api/types" 11 | "github.com/docker/engine-api/types/filters" 12 | timetypes "github.com/docker/engine-api/types/time" 13 | ) 14 | 15 | // Events returns a stream of events in the daemon in a ReadCloser. 16 | // It's up to the caller to close the stream. 17 | func (cli *Client) Events(ctx context.Context, options types.EventsOptions) (io.ReadCloser, error) { 18 | query := url.Values{} 19 | ref := time.Now() 20 | 21 | if options.Since != "" { 22 | ts, err := timetypes.GetTimestamp(options.Since, ref) 23 | if err != nil { 24 | return nil, err 25 | } 26 | query.Set("since", ts) 27 | } 28 | if options.Until != "" { 29 | ts, err := timetypes.GetTimestamp(options.Until, ref) 30 | if err != nil { 31 | return nil, err 32 | } 33 | query.Set("until", ts) 34 | } 35 | if options.Filters.Len() > 0 { 36 | filterJSON, err := filters.ToParamWithVersion(cli.version, options.Filters) 37 | if err != nil { 38 | return nil, err 39 | } 40 | query.Set("filters", filterJSON) 41 | } 42 | 43 | serverResponse, err := cli.get(ctx, "/events", query, nil) 44 | if err != nil { 45 | return nil, err 46 | } 47 | return serverResponse.body, nil 48 | } 49 | -------------------------------------------------------------------------------- /client/image_create.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "io" 5 | "net/url" 6 | 7 | "golang.org/x/net/context" 8 | 9 | "github.com/docker/engine-api/types" 10 | "github.com/docker/engine-api/types/reference" 11 | ) 12 | 13 | // ImageCreate creates a new image based in the parent options. 14 | // It returns the JSON content in the response body. 15 | func (cli *Client) ImageCreate(ctx context.Context, parentReference string, options types.ImageCreateOptions) (io.ReadCloser, error) { 16 | repository, tag, err := reference.Parse(parentReference) 17 | if err != nil { 18 | return nil, err 19 | } 20 | 21 | query := url.Values{} 22 | query.Set("fromImage", repository) 23 | query.Set("tag", tag) 24 | resp, err := cli.tryImageCreate(ctx, query, options.RegistryAuth) 25 | if err != nil { 26 | return nil, err 27 | } 28 | return resp.body, nil 29 | } 30 | 31 | func (cli *Client) tryImageCreate(ctx context.Context, query url.Values, registryAuth string) (serverResponse, error) { 32 | headers := map[string][]string{"X-Registry-Auth": {registryAuth}} 33 | return cli.post(ctx, "/images/create", query, nil, headers) 34 | } 35 | -------------------------------------------------------------------------------- /client/image_history.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "encoding/json" 5 | "net/url" 6 | 7 | "github.com/docker/engine-api/types" 8 | "golang.org/x/net/context" 9 | ) 10 | 11 | // ImageHistory returns the changes in an image in history format. 12 | func (cli *Client) ImageHistory(ctx context.Context, imageID string) ([]types.ImageHistory, error) { 13 | var history []types.ImageHistory 14 | serverResp, err := cli.get(ctx, "/images/"+imageID+"/history", url.Values{}, nil) 15 | if err != nil { 16 | return history, err 17 | } 18 | 19 | err = json.NewDecoder(serverResp.body).Decode(&history) 20 | ensureReaderClosed(serverResp) 21 | return history, err 22 | } 23 | -------------------------------------------------------------------------------- /client/image_history_test.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "fmt" 7 | "io/ioutil" 8 | "net/http" 9 | "strings" 10 | "testing" 11 | 12 | "github.com/docker/engine-api/types" 13 | "golang.org/x/net/context" 14 | ) 15 | 16 | func TestImageHistoryError(t *testing.T) { 17 | client := &Client{ 18 | transport: newMockClient(nil, errorMock(http.StatusInternalServerError, "Server error")), 19 | } 20 | _, err := client.ImageHistory(context.Background(), "nothing") 21 | if err == nil || err.Error() != "Error response from daemon: Server error" { 22 | t.Fatalf("expected a Server error, got %v", err) 23 | } 24 | } 25 | 26 | func TestImageHistory(t *testing.T) { 27 | expectedURL := "/images/image_id/history" 28 | client := &Client{ 29 | transport: newMockClient(nil, func(r *http.Request) (*http.Response, error) { 30 | if !strings.HasPrefix(r.URL.Path, expectedURL) { 31 | return nil, fmt.Errorf("Expected URL '%s', got '%s'", expectedURL, r.URL) 32 | } 33 | b, err := json.Marshal([]types.ImageHistory{ 34 | { 35 | ID: "image_id1", 36 | Tags: []string{"tag1", "tag2"}, 37 | }, 38 | { 39 | ID: "image_id2", 40 | Tags: []string{"tag1", "tag2"}, 41 | }, 42 | }) 43 | if err != nil { 44 | return nil, err 45 | } 46 | 47 | return &http.Response{ 48 | StatusCode: http.StatusOK, 49 | Body: ioutil.NopCloser(bytes.NewReader(b)), 50 | }, nil 51 | }), 52 | } 53 | imageHistories, err := client.ImageHistory(context.Background(), "image_id") 54 | if err != nil { 55 | t.Fatal(err) 56 | } 57 | if len(imageHistories) != 2 { 58 | t.Fatalf("expected 2 containers, got %v", imageHistories) 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /client/image_import.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "io" 5 | "net/url" 6 | 7 | "golang.org/x/net/context" 8 | 9 | "github.com/docker/distribution/reference" 10 | "github.com/docker/engine-api/types" 11 | ) 12 | 13 | // ImageImport creates a new image based in the source options. 14 | // It returns the JSON content in the response body. 15 | func (cli *Client) ImageImport(ctx context.Context, source types.ImageImportSource, ref string, options types.ImageImportOptions) (io.ReadCloser, error) { 16 | if ref != "" { 17 | //Check if the given image name can be resolved 18 | if _, err := reference.ParseNamed(ref); err != nil { 19 | return nil, err 20 | } 21 | } 22 | 23 | query := url.Values{} 24 | query.Set("fromSrc", source.SourceName) 25 | query.Set("repo", ref) 26 | query.Set("tag", options.Tag) 27 | query.Set("message", options.Message) 28 | for _, change := range options.Changes { 29 | query.Add("changes", change) 30 | } 31 | 32 | resp, err := cli.postRaw(ctx, "/images/create", query, source.Source, nil) 33 | if err != nil { 34 | return nil, err 35 | } 36 | return resp.body, nil 37 | } 38 | -------------------------------------------------------------------------------- /client/image_inspect.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "io/ioutil" 7 | "net/http" 8 | 9 | "github.com/docker/engine-api/types" 10 | "golang.org/x/net/context" 11 | ) 12 | 13 | // ImageInspectWithRaw returns the image information and its raw representation. 14 | func (cli *Client) ImageInspectWithRaw(ctx context.Context, imageID string) (types.ImageInspect, []byte, error) { 15 | serverResp, err := cli.get(ctx, "/images/"+imageID+"/json", nil, nil) 16 | if err != nil { 17 | if serverResp.statusCode == http.StatusNotFound { 18 | return types.ImageInspect{}, nil, imageNotFoundError{imageID} 19 | } 20 | return types.ImageInspect{}, nil, err 21 | } 22 | defer ensureReaderClosed(serverResp) 23 | 24 | body, err := ioutil.ReadAll(serverResp.body) 25 | if err != nil { 26 | return types.ImageInspect{}, nil, err 27 | } 28 | 29 | var response types.ImageInspect 30 | rdr := bytes.NewReader(body) 31 | err = json.NewDecoder(rdr).Decode(&response) 32 | return response, body, err 33 | } 34 | -------------------------------------------------------------------------------- /client/image_inspect_test.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "fmt" 7 | "io/ioutil" 8 | "net/http" 9 | "reflect" 10 | "strings" 11 | "testing" 12 | 13 | "github.com/docker/engine-api/types" 14 | "golang.org/x/net/context" 15 | ) 16 | 17 | func TestImageInspectError(t *testing.T) { 18 | client := &Client{ 19 | transport: newMockClient(nil, errorMock(http.StatusInternalServerError, "Server error")), 20 | } 21 | 22 | _, _, err := client.ImageInspectWithRaw(context.Background(), "nothing") 23 | if err == nil || err.Error() != "Error response from daemon: Server error" { 24 | t.Fatalf("expected a Server Error, got %v", err) 25 | } 26 | } 27 | 28 | func TestImageInspectImageNotFound(t *testing.T) { 29 | client := &Client{ 30 | transport: newMockClient(nil, errorMock(http.StatusNotFound, "Server error")), 31 | } 32 | 33 | _, _, err := client.ImageInspectWithRaw(context.Background(), "unknown") 34 | if err == nil || !IsErrImageNotFound(err) { 35 | t.Fatalf("expected an imageNotFound error, got %v", err) 36 | } 37 | } 38 | 39 | func TestImageInspect(t *testing.T) { 40 | expectedURL := "/images/image_id/json" 41 | expectedTags := []string{"tag1", "tag2"} 42 | client := &Client{ 43 | transport: newMockClient(nil, func(req *http.Request) (*http.Response, error) { 44 | if !strings.HasPrefix(req.URL.Path, expectedURL) { 45 | return nil, fmt.Errorf("Expected URL '%s', got '%s'", expectedURL, req.URL) 46 | } 47 | content, err := json.Marshal(types.ImageInspect{ 48 | ID: "image_id", 49 | RepoTags: expectedTags, 50 | }) 51 | if err != nil { 52 | return nil, err 53 | } 54 | return &http.Response{ 55 | StatusCode: http.StatusOK, 56 | Body: ioutil.NopCloser(bytes.NewReader(content)), 57 | }, nil 58 | }), 59 | } 60 | 61 | imageInspect, _, err := client.ImageInspectWithRaw(context.Background(), "image_id") 62 | if err != nil { 63 | t.Fatal(err) 64 | } 65 | if imageInspect.ID != "image_id" { 66 | t.Fatalf("expected `image_id`, got %s", imageInspect.ID) 67 | } 68 | if !reflect.DeepEqual(imageInspect.RepoTags, expectedTags) { 69 | t.Fatalf("expected `%v`, got %v", expectedTags, imageInspect.RepoTags) 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /client/image_list.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "encoding/json" 5 | "net/url" 6 | 7 | "github.com/docker/engine-api/types" 8 | "github.com/docker/engine-api/types/filters" 9 | "golang.org/x/net/context" 10 | ) 11 | 12 | // ImageList returns a list of images in the docker host. 13 | func (cli *Client) ImageList(ctx context.Context, options types.ImageListOptions) ([]types.Image, error) { 14 | var images []types.Image 15 | query := url.Values{} 16 | 17 | if options.Filters.Len() > 0 { 18 | filterJSON, err := filters.ToParamWithVersion(cli.version, options.Filters) 19 | if err != nil { 20 | return images, err 21 | } 22 | query.Set("filters", filterJSON) 23 | } 24 | if options.MatchName != "" { 25 | // FIXME rename this parameter, to not be confused with the filters flag 26 | query.Set("filter", options.MatchName) 27 | } 28 | if options.All { 29 | query.Set("all", "1") 30 | } 31 | 32 | serverResp, err := cli.get(ctx, "/images/json", query, nil) 33 | if err != nil { 34 | return images, err 35 | } 36 | 37 | err = json.NewDecoder(serverResp.body).Decode(&images) 38 | ensureReaderClosed(serverResp) 39 | return images, err 40 | } 41 | -------------------------------------------------------------------------------- /client/image_load.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "io" 5 | "net/url" 6 | 7 | "golang.org/x/net/context" 8 | 9 | "github.com/docker/engine-api/types" 10 | ) 11 | 12 | // ImageLoad loads an image in the docker host from the client host. 13 | // It's up to the caller to close the io.ReadCloser in the 14 | // ImageLoadResponse returned by this function. 15 | func (cli *Client) ImageLoad(ctx context.Context, input io.Reader, quiet bool) (types.ImageLoadResponse, error) { 16 | v := url.Values{} 17 | v.Set("quiet", "0") 18 | if quiet { 19 | v.Set("quiet", "1") 20 | } 21 | headers := map[string][]string{"Content-Type": {"application/x-tar"}} 22 | resp, err := cli.postRaw(ctx, "/images/load", v, input, headers) 23 | if err != nil { 24 | return types.ImageLoadResponse{}, err 25 | } 26 | return types.ImageLoadResponse{ 27 | Body: resp.body, 28 | JSON: resp.header.Get("Content-Type") == "application/json", 29 | }, nil 30 | } 31 | -------------------------------------------------------------------------------- /client/image_pull.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "io" 5 | "net/http" 6 | "net/url" 7 | 8 | "golang.org/x/net/context" 9 | 10 | "github.com/docker/engine-api/types" 11 | "github.com/docker/engine-api/types/reference" 12 | ) 13 | 14 | // ImagePull requests the docker host to pull an image from a remote registry. 15 | // It executes the privileged function if the operation is unauthorized 16 | // and it tries one more time. 17 | // It's up to the caller to handle the io.ReadCloser and close it properly. 18 | // 19 | // FIXME(vdemeester): there is currently used in a few way in docker/docker 20 | // - if not in trusted content, ref is used to pass the whole reference, and tag is empty 21 | // - if in trusted content, ref is used to pass the reference name, and tag for the digest 22 | func (cli *Client) ImagePull(ctx context.Context, ref string, options types.ImagePullOptions) (io.ReadCloser, error) { 23 | repository, tag, err := reference.Parse(ref) 24 | if err != nil { 25 | return nil, err 26 | } 27 | 28 | query := url.Values{} 29 | query.Set("fromImage", repository) 30 | if tag != "" && !options.All { 31 | query.Set("tag", tag) 32 | } 33 | 34 | resp, err := cli.tryImageCreate(ctx, query, options.RegistryAuth) 35 | if resp.statusCode == http.StatusUnauthorized && options.PrivilegeFunc != nil { 36 | newAuthHeader, privilegeErr := options.PrivilegeFunc() 37 | if privilegeErr != nil { 38 | return nil, privilegeErr 39 | } 40 | resp, err = cli.tryImageCreate(ctx, query, newAuthHeader) 41 | } 42 | if err != nil { 43 | return nil, err 44 | } 45 | return resp.body, nil 46 | } 47 | -------------------------------------------------------------------------------- /client/image_push.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "errors" 5 | "io" 6 | "net/http" 7 | "net/url" 8 | 9 | "golang.org/x/net/context" 10 | 11 | distreference "github.com/docker/distribution/reference" 12 | "github.com/docker/engine-api/types" 13 | ) 14 | 15 | // ImagePush requests the docker host to push an image to a remote registry. 16 | // It executes the privileged function if the operation is unauthorized 17 | // and it tries one more time. 18 | // It's up to the caller to handle the io.ReadCloser and close it properly. 19 | func (cli *Client) ImagePush(ctx context.Context, ref string, options types.ImagePushOptions) (io.ReadCloser, error) { 20 | distributionRef, err := distreference.ParseNamed(ref) 21 | if err != nil { 22 | return nil, err 23 | } 24 | 25 | if _, isCanonical := distributionRef.(distreference.Canonical); isCanonical { 26 | return nil, errors.New("cannot push a digest reference") 27 | } 28 | 29 | var tag = "" 30 | if nameTaggedRef, isNamedTagged := distributionRef.(distreference.NamedTagged); isNamedTagged { 31 | tag = nameTaggedRef.Tag() 32 | } 33 | 34 | query := url.Values{} 35 | query.Set("tag", tag) 36 | 37 | resp, err := cli.tryImagePush(ctx, distributionRef.Name(), query, options.RegistryAuth) 38 | if resp.statusCode == http.StatusUnauthorized && options.PrivilegeFunc != nil { 39 | newAuthHeader, privilegeErr := options.PrivilegeFunc() 40 | if privilegeErr != nil { 41 | return nil, privilegeErr 42 | } 43 | resp, err = cli.tryImagePush(ctx, distributionRef.Name(), query, newAuthHeader) 44 | } 45 | if err != nil { 46 | return nil, err 47 | } 48 | return resp.body, nil 49 | } 50 | 51 | func (cli *Client) tryImagePush(ctx context.Context, imageID string, query url.Values, registryAuth string) (serverResponse, error) { 52 | headers := map[string][]string{"X-Registry-Auth": {registryAuth}} 53 | return cli.post(ctx, "/images/"+imageID+"/push", query, nil, headers) 54 | } 55 | -------------------------------------------------------------------------------- /client/image_remove.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "encoding/json" 5 | "net/url" 6 | 7 | "github.com/docker/engine-api/types" 8 | "golang.org/x/net/context" 9 | ) 10 | 11 | // ImageRemove removes an image from the docker host. 12 | func (cli *Client) ImageRemove(ctx context.Context, imageID string, options types.ImageRemoveOptions) ([]types.ImageDelete, error) { 13 | query := url.Values{} 14 | 15 | if options.Force { 16 | query.Set("force", "1") 17 | } 18 | if !options.PruneChildren { 19 | query.Set("noprune", "1") 20 | } 21 | 22 | resp, err := cli.delete(ctx, "/images/"+imageID, query, nil) 23 | if err != nil { 24 | return nil, err 25 | } 26 | 27 | var dels []types.ImageDelete 28 | err = json.NewDecoder(resp.body).Decode(&dels) 29 | ensureReaderClosed(resp) 30 | return dels, err 31 | } 32 | -------------------------------------------------------------------------------- /client/image_save.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "io" 5 | "net/url" 6 | 7 | "golang.org/x/net/context" 8 | ) 9 | 10 | // ImageSave retrieves one or more images from the docker host as an io.ReadCloser. 11 | // It's up to the caller to store the images and close the stream. 12 | func (cli *Client) ImageSave(ctx context.Context, imageIDs []string) (io.ReadCloser, error) { 13 | query := url.Values{ 14 | "names": imageIDs, 15 | } 16 | 17 | resp, err := cli.get(ctx, "/images/get", query, nil) 18 | if err != nil { 19 | return nil, err 20 | } 21 | return resp.body, nil 22 | } 23 | -------------------------------------------------------------------------------- /client/image_save_test.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "io/ioutil" 7 | "net/http" 8 | "reflect" 9 | "testing" 10 | 11 | "golang.org/x/net/context" 12 | 13 | "strings" 14 | ) 15 | 16 | func TestImageSaveError(t *testing.T) { 17 | client := &Client{ 18 | transport: newMockClient(nil, errorMock(http.StatusInternalServerError, "Server error")), 19 | } 20 | _, err := client.ImageSave(context.Background(), []string{"nothing"}) 21 | if err == nil || err.Error() != "Error response from daemon: Server error" { 22 | t.Fatalf("expected a Server error, got %v", err) 23 | } 24 | } 25 | 26 | func TestImageSave(t *testing.T) { 27 | expectedURL := "/images/get" 28 | client := &Client{ 29 | transport: newMockClient(nil, func(r *http.Request) (*http.Response, error) { 30 | if !strings.HasPrefix(r.URL.Path, expectedURL) { 31 | return nil, fmt.Errorf("Expected URL '%s', got '%s'", expectedURL, r.URL) 32 | } 33 | query := r.URL.Query() 34 | names := query["names"] 35 | expectedNames := []string{"image_id1", "image_id2"} 36 | if !reflect.DeepEqual(names, expectedNames) { 37 | return nil, fmt.Errorf("names not set in URL query properly. Expected %v, got %v", names, expectedNames) 38 | } 39 | 40 | return &http.Response{ 41 | StatusCode: http.StatusOK, 42 | Body: ioutil.NopCloser(bytes.NewReader([]byte("response"))), 43 | }, nil 44 | }), 45 | } 46 | saveResponse, err := client.ImageSave(context.Background(), []string{"image_id1", "image_id2"}) 47 | if err != nil { 48 | t.Fatal(err) 49 | } 50 | response, err := ioutil.ReadAll(saveResponse) 51 | if err != nil { 52 | t.Fatal(err) 53 | } 54 | saveResponse.Close() 55 | if string(response) != "response" { 56 | t.Fatalf("expected response to contain 'response', got %s", string(response)) 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /client/image_search.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "net/http" 7 | "net/url" 8 | 9 | "github.com/docker/engine-api/types" 10 | "github.com/docker/engine-api/types/filters" 11 | "github.com/docker/engine-api/types/registry" 12 | "golang.org/x/net/context" 13 | ) 14 | 15 | // ImageSearch makes the docker host to search by a term in a remote registry. 16 | // The list of results is not sorted in any fashion. 17 | func (cli *Client) ImageSearch(ctx context.Context, term string, options types.ImageSearchOptions) ([]registry.SearchResult, error) { 18 | var results []registry.SearchResult 19 | query := url.Values{} 20 | query.Set("term", term) 21 | query.Set("limit", fmt.Sprintf("%d", options.Limit)) 22 | 23 | if options.Filters.Len() > 0 { 24 | filterJSON, err := filters.ToParam(options.Filters) 25 | if err != nil { 26 | return results, err 27 | } 28 | query.Set("filters", filterJSON) 29 | } 30 | 31 | resp, err := cli.tryImageSearch(ctx, query, options.RegistryAuth) 32 | if resp.statusCode == http.StatusUnauthorized && options.PrivilegeFunc != nil { 33 | newAuthHeader, privilegeErr := options.PrivilegeFunc() 34 | if privilegeErr != nil { 35 | return results, privilegeErr 36 | } 37 | resp, err = cli.tryImageSearch(ctx, query, newAuthHeader) 38 | } 39 | if err != nil { 40 | return results, err 41 | } 42 | 43 | err = json.NewDecoder(resp.body).Decode(&results) 44 | ensureReaderClosed(resp) 45 | return results, err 46 | } 47 | 48 | func (cli *Client) tryImageSearch(ctx context.Context, query url.Values, registryAuth string) (serverResponse, error) { 49 | headers := map[string][]string{"X-Registry-Auth": {registryAuth}} 50 | return cli.get(ctx, "/images/search", query, headers) 51 | } 52 | -------------------------------------------------------------------------------- /client/image_tag.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "net/url" 7 | 8 | "golang.org/x/net/context" 9 | 10 | distreference "github.com/docker/distribution/reference" 11 | "github.com/docker/engine-api/types/reference" 12 | ) 13 | 14 | // ImageTag tags an image in the docker host 15 | func (cli *Client) ImageTag(ctx context.Context, imageID, ref string) error { 16 | distributionRef, err := distreference.ParseNamed(ref) 17 | if err != nil { 18 | return fmt.Errorf("Error parsing reference: %q is not a valid repository/tag", ref) 19 | } 20 | 21 | if _, isCanonical := distributionRef.(distreference.Canonical); isCanonical { 22 | return errors.New("refusing to create a tag with a digest reference") 23 | } 24 | 25 | tag := reference.GetTagFromNamedRef(distributionRef) 26 | 27 | query := url.Values{} 28 | query.Set("repo", distributionRef.Name()) 29 | query.Set("tag", tag) 30 | 31 | resp, err := cli.post(ctx, "/images/"+imageID+"/tag", query, nil, nil) 32 | ensureReaderClosed(resp) 33 | return err 34 | } 35 | -------------------------------------------------------------------------------- /client/info.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "net/url" 7 | 8 | "github.com/docker/engine-api/types" 9 | "golang.org/x/net/context" 10 | ) 11 | 12 | // Info returns information about the docker server. 13 | func (cli *Client) Info(ctx context.Context) (types.Info, error) { 14 | var info types.Info 15 | serverResp, err := cli.get(ctx, "/info", url.Values{}, nil) 16 | if err != nil { 17 | return info, err 18 | } 19 | defer ensureReaderClosed(serverResp) 20 | 21 | if err := json.NewDecoder(serverResp.body).Decode(&info); err != nil { 22 | return info, fmt.Errorf("Error reading remote info: %v", err) 23 | } 24 | 25 | return info, nil 26 | } 27 | -------------------------------------------------------------------------------- /client/info_test.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "fmt" 7 | "io/ioutil" 8 | "net/http" 9 | "strings" 10 | "testing" 11 | 12 | "github.com/docker/engine-api/types" 13 | "golang.org/x/net/context" 14 | ) 15 | 16 | func TestInfoServerError(t *testing.T) { 17 | client := &Client{ 18 | transport: newMockClient(nil, errorMock(http.StatusInternalServerError, "Server error")), 19 | } 20 | _, err := client.Info(context.Background()) 21 | if err == nil || err.Error() != "Error response from daemon: Server error" { 22 | t.Fatalf("expected a Server Error, got %v", err) 23 | } 24 | } 25 | 26 | func TestInfoInvalidResponseJSONError(t *testing.T) { 27 | client := &Client{ 28 | transport: newMockClient(nil, func(req *http.Request) (*http.Response, error) { 29 | return &http.Response{ 30 | StatusCode: http.StatusOK, 31 | Body: ioutil.NopCloser(bytes.NewReader([]byte("invalid json"))), 32 | }, nil 33 | }), 34 | } 35 | _, err := client.Info(context.Background()) 36 | if err == nil || !strings.Contains(err.Error(), "invalid character") { 37 | t.Fatalf("expected a 'invalid character' error, got %v", err) 38 | } 39 | } 40 | 41 | func TestInfo(t *testing.T) { 42 | expectedURL := "/info" 43 | client := &Client{ 44 | transport: newMockClient(nil, func(req *http.Request) (*http.Response, error) { 45 | if !strings.HasPrefix(req.URL.Path, expectedURL) { 46 | return nil, fmt.Errorf("Expected URL '%s', got '%s'", expectedURL, req.URL) 47 | } 48 | info := &types.Info{ 49 | ID: "daemonID", 50 | Containers: 3, 51 | } 52 | b, err := json.Marshal(info) 53 | if err != nil { 54 | return nil, err 55 | } 56 | 57 | return &http.Response{ 58 | StatusCode: http.StatusOK, 59 | Body: ioutil.NopCloser(bytes.NewReader(b)), 60 | }, nil 61 | }), 62 | } 63 | 64 | info, err := client.Info(context.Background()) 65 | if err != nil { 66 | t.Fatal(err) 67 | } 68 | 69 | if info.ID != "daemonID" { 70 | t.Fatalf("expected daemonID, got %s", info.ID) 71 | } 72 | 73 | if info.Containers != 3 { 74 | t.Fatalf("expected 3 containers, got %d", info.Containers) 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /client/interface_experimental.go: -------------------------------------------------------------------------------- 1 | // +build experimental 2 | 3 | package client 4 | 5 | import ( 6 | "github.com/docker/engine-api/types" 7 | "golang.org/x/net/context" 8 | ) 9 | 10 | // APIClient is an interface that clients that talk with a docker server must implement. 11 | type APIClient interface { 12 | CommonAPIClient 13 | CheckpointAPIClient 14 | PluginAPIClient 15 | } 16 | 17 | // CheckpointAPIClient defines API client methods for the checkpoints 18 | type CheckpointAPIClient interface { 19 | CheckpointCreate(ctx context.Context, container string, options types.CheckpointCreateOptions) error 20 | CheckpointDelete(ctx context.Context, container string, checkpointID string) error 21 | CheckpointList(ctx context.Context, container string) ([]types.Checkpoint, error) 22 | } 23 | 24 | // PluginAPIClient defines API client methods for the plugins 25 | type PluginAPIClient interface { 26 | PluginList(ctx context.Context) (types.PluginsListResponse, error) 27 | PluginRemove(ctx context.Context, name string, options types.PluginRemoveOptions) error 28 | PluginEnable(ctx context.Context, name string) error 29 | PluginDisable(ctx context.Context, name string) error 30 | PluginInstall(ctx context.Context, name string, options types.PluginInstallOptions) error 31 | PluginPush(ctx context.Context, name string, registryAuth string) error 32 | PluginSet(ctx context.Context, name string, args []string) error 33 | PluginInspectWithRaw(ctx context.Context, name string) (*types.Plugin, []byte, error) 34 | } 35 | 36 | // Ensure that Client always implements APIClient. 37 | var _ APIClient = &Client{} 38 | -------------------------------------------------------------------------------- /client/interface_stable.go: -------------------------------------------------------------------------------- 1 | // +build !experimental 2 | 3 | package client 4 | 5 | // APIClient is an interface that clients that talk with a docker server must implement. 6 | type APIClient interface { 7 | CommonAPIClient 8 | } 9 | 10 | // Ensure that Client always implements APIClient. 11 | var _ APIClient = &Client{} 12 | -------------------------------------------------------------------------------- /client/login.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "encoding/json" 5 | "net/http" 6 | "net/url" 7 | 8 | "github.com/docker/engine-api/types" 9 | "golang.org/x/net/context" 10 | ) 11 | 12 | // RegistryLogin authenticates the docker server with a given docker registry. 13 | // It returns UnauthorizerError when the authentication fails. 14 | func (cli *Client) RegistryLogin(ctx context.Context, auth types.AuthConfig) (types.AuthResponse, error) { 15 | resp, err := cli.post(ctx, "/auth", url.Values{}, auth, nil) 16 | 17 | if resp.statusCode == http.StatusUnauthorized { 18 | return types.AuthResponse{}, unauthorizedError{err} 19 | } 20 | if err != nil { 21 | return types.AuthResponse{}, err 22 | } 23 | 24 | var response types.AuthResponse 25 | err = json.NewDecoder(resp.body).Decode(&response) 26 | ensureReaderClosed(resp) 27 | return response, err 28 | } 29 | -------------------------------------------------------------------------------- /client/network_connect.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "github.com/docker/engine-api/types" 5 | "github.com/docker/engine-api/types/network" 6 | "golang.org/x/net/context" 7 | ) 8 | 9 | // NetworkConnect connects a container to an existent network in the docker host. 10 | func (cli *Client) NetworkConnect(ctx context.Context, networkID, containerID string, config *network.EndpointSettings) error { 11 | nc := types.NetworkConnect{ 12 | Container: containerID, 13 | EndpointConfig: config, 14 | } 15 | resp, err := cli.post(ctx, "/networks/"+networkID+"/connect", nil, nc, nil) 16 | ensureReaderClosed(resp) 17 | return err 18 | } 19 | -------------------------------------------------------------------------------- /client/network_create.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "encoding/json" 5 | 6 | "github.com/docker/engine-api/types" 7 | "golang.org/x/net/context" 8 | ) 9 | 10 | // NetworkCreate creates a new network in the docker host. 11 | func (cli *Client) NetworkCreate(ctx context.Context, name string, options types.NetworkCreate) (types.NetworkCreateResponse, error) { 12 | networkCreateRequest := types.NetworkCreateRequest{ 13 | NetworkCreate: options, 14 | Name: name, 15 | } 16 | var response types.NetworkCreateResponse 17 | serverResp, err := cli.post(ctx, "/networks/create", nil, networkCreateRequest, nil) 18 | if err != nil { 19 | return response, err 20 | } 21 | 22 | json.NewDecoder(serverResp.body).Decode(&response) 23 | ensureReaderClosed(serverResp) 24 | return response, err 25 | } 26 | -------------------------------------------------------------------------------- /client/network_create_test.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "fmt" 7 | "io/ioutil" 8 | "net/http" 9 | "strings" 10 | "testing" 11 | 12 | "github.com/docker/engine-api/types" 13 | "golang.org/x/net/context" 14 | ) 15 | 16 | func TestNetworkCreateError(t *testing.T) { 17 | client := &Client{ 18 | transport: newMockClient(nil, errorMock(http.StatusInternalServerError, "Server error")), 19 | } 20 | 21 | _, err := client.NetworkCreate(context.Background(), "mynetwork", types.NetworkCreate{}) 22 | if err == nil || err.Error() != "Error response from daemon: Server error" { 23 | t.Fatalf("expected a Server Error, got %v", err) 24 | } 25 | } 26 | 27 | func TestNetworkCreate(t *testing.T) { 28 | expectedURL := "/networks/create" 29 | 30 | client := &Client{ 31 | transport: newMockClient(nil, func(req *http.Request) (*http.Response, error) { 32 | if !strings.HasPrefix(req.URL.Path, expectedURL) { 33 | return nil, fmt.Errorf("Expected URL '%s', got '%s'", expectedURL, req.URL) 34 | } 35 | 36 | if req.Method != "POST" { 37 | return nil, fmt.Errorf("expected POST method, got %s", req.Method) 38 | } 39 | 40 | content, err := json.Marshal(types.NetworkCreateResponse{ 41 | ID: "network_id", 42 | Warning: "warning", 43 | }) 44 | if err != nil { 45 | return nil, err 46 | } 47 | return &http.Response{ 48 | StatusCode: http.StatusOK, 49 | Body: ioutil.NopCloser(bytes.NewReader(content)), 50 | }, nil 51 | }), 52 | } 53 | 54 | networkResponse, err := client.NetworkCreate(context.Background(), "mynetwork", types.NetworkCreate{ 55 | CheckDuplicate: true, 56 | Driver: "mydriver", 57 | EnableIPv6: true, 58 | Internal: true, 59 | Options: map[string]string{ 60 | "opt-key": "opt-value", 61 | }, 62 | }) 63 | if err != nil { 64 | t.Fatal(err) 65 | } 66 | if networkResponse.ID != "network_id" { 67 | t.Fatalf("expected networkResponse.ID to be 'network_id', got %s", networkResponse.ID) 68 | } 69 | if networkResponse.Warning != "warning" { 70 | t.Fatalf("expected networkResponse.Warning to be 'warning', got %s", networkResponse.Warning) 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /client/network_disconnect.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "github.com/docker/engine-api/types" 5 | "golang.org/x/net/context" 6 | ) 7 | 8 | // NetworkDisconnect disconnects a container from an existent network in the docker host. 9 | func (cli *Client) NetworkDisconnect(ctx context.Context, networkID, containerID string, force bool) error { 10 | nd := types.NetworkDisconnect{Container: containerID, Force: force} 11 | resp, err := cli.post(ctx, "/networks/"+networkID+"/disconnect", nil, nd, nil) 12 | ensureReaderClosed(resp) 13 | return err 14 | } 15 | -------------------------------------------------------------------------------- /client/network_disconnect_test.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "fmt" 7 | "io/ioutil" 8 | "net/http" 9 | "strings" 10 | "testing" 11 | 12 | "github.com/docker/engine-api/types" 13 | "golang.org/x/net/context" 14 | ) 15 | 16 | func TestNetworkDisconnectError(t *testing.T) { 17 | client := &Client{ 18 | transport: newMockClient(nil, errorMock(http.StatusInternalServerError, "Server error")), 19 | } 20 | 21 | err := client.NetworkDisconnect(context.Background(), "network_id", "container_id", false) 22 | if err == nil || err.Error() != "Error response from daemon: Server error" { 23 | t.Fatalf("expected a Server Error, got %v", err) 24 | } 25 | } 26 | 27 | func TestNetworkDisconnect(t *testing.T) { 28 | expectedURL := "/networks/network_id/disconnect" 29 | 30 | client := &Client{ 31 | transport: newMockClient(nil, func(req *http.Request) (*http.Response, error) { 32 | if !strings.HasPrefix(req.URL.Path, expectedURL) { 33 | return nil, fmt.Errorf("Expected URL '%s', got '%s'", expectedURL, req.URL) 34 | } 35 | 36 | if req.Method != "POST" { 37 | return nil, fmt.Errorf("expected POST method, got %s", req.Method) 38 | } 39 | 40 | var disconnect types.NetworkDisconnect 41 | if err := json.NewDecoder(req.Body).Decode(&disconnect); err != nil { 42 | return nil, err 43 | } 44 | 45 | if disconnect.Container != "container_id" { 46 | return nil, fmt.Errorf("expected 'container_id', got %s", disconnect.Container) 47 | } 48 | 49 | if !disconnect.Force { 50 | return nil, fmt.Errorf("expected Force to be true, got %v", disconnect.Force) 51 | } 52 | 53 | return &http.Response{ 54 | StatusCode: http.StatusOK, 55 | Body: ioutil.NopCloser(bytes.NewReader([]byte(""))), 56 | }, nil 57 | }), 58 | } 59 | 60 | err := client.NetworkDisconnect(context.Background(), "network_id", "container_id", true) 61 | if err != nil { 62 | t.Fatal(err) 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /client/network_inspect.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "io/ioutil" 7 | "net/http" 8 | 9 | "github.com/docker/engine-api/types" 10 | "golang.org/x/net/context" 11 | ) 12 | 13 | // NetworkInspect returns the information for a specific network configured in the docker host. 14 | func (cli *Client) NetworkInspect(ctx context.Context, networkID string) (types.NetworkResource, error) { 15 | networkResource, _, err := cli.NetworkInspectWithRaw(ctx, networkID) 16 | return networkResource, err 17 | } 18 | 19 | // NetworkInspectWithRaw returns the information for a specific network configured in the docker host and its raw representation. 20 | func (cli *Client) NetworkInspectWithRaw(ctx context.Context, networkID string) (types.NetworkResource, []byte, error) { 21 | var networkResource types.NetworkResource 22 | resp, err := cli.get(ctx, "/networks/"+networkID, nil, nil) 23 | if err != nil { 24 | if resp.statusCode == http.StatusNotFound { 25 | return networkResource, nil, networkNotFoundError{networkID} 26 | } 27 | return networkResource, nil, err 28 | } 29 | defer ensureReaderClosed(resp) 30 | 31 | body, err := ioutil.ReadAll(resp.body) 32 | if err != nil { 33 | return networkResource, nil, err 34 | } 35 | rdr := bytes.NewReader(body) 36 | err = json.NewDecoder(rdr).Decode(&networkResource) 37 | return networkResource, body, err 38 | } 39 | -------------------------------------------------------------------------------- /client/network_inspect_test.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "fmt" 7 | "io/ioutil" 8 | "net/http" 9 | "strings" 10 | "testing" 11 | 12 | "github.com/docker/engine-api/types" 13 | "golang.org/x/net/context" 14 | ) 15 | 16 | func TestNetworkInspectError(t *testing.T) { 17 | client := &Client{ 18 | transport: newMockClient(nil, errorMock(http.StatusInternalServerError, "Server error")), 19 | } 20 | 21 | _, err := client.NetworkInspect(context.Background(), "nothing") 22 | if err == nil || err.Error() != "Error response from daemon: Server error" { 23 | t.Fatalf("expected a Server Error, got %v", err) 24 | } 25 | } 26 | 27 | func TestNetworkInspectContainerNotFound(t *testing.T) { 28 | client := &Client{ 29 | transport: newMockClient(nil, errorMock(http.StatusNotFound, "Server error")), 30 | } 31 | 32 | _, err := client.NetworkInspect(context.Background(), "unknown") 33 | if err == nil || !IsErrNetworkNotFound(err) { 34 | t.Fatalf("expected a containerNotFound error, got %v", err) 35 | } 36 | } 37 | 38 | func TestNetworkInspect(t *testing.T) { 39 | expectedURL := "/networks/network_id" 40 | client := &Client{ 41 | transport: newMockClient(nil, func(req *http.Request) (*http.Response, error) { 42 | if !strings.HasPrefix(req.URL.Path, expectedURL) { 43 | return nil, fmt.Errorf("Expected URL '%s', got '%s'", expectedURL, req.URL) 44 | } 45 | if req.Method != "GET" { 46 | return nil, fmt.Errorf("expected GET method, got %s", req.Method) 47 | } 48 | 49 | content, err := json.Marshal(types.NetworkResource{ 50 | Name: "mynetwork", 51 | }) 52 | if err != nil { 53 | return nil, err 54 | } 55 | return &http.Response{ 56 | StatusCode: http.StatusOK, 57 | Body: ioutil.NopCloser(bytes.NewReader(content)), 58 | }, nil 59 | }), 60 | } 61 | 62 | r, err := client.NetworkInspect(context.Background(), "network_id") 63 | if err != nil { 64 | t.Fatal(err) 65 | } 66 | if r.Name != "mynetwork" { 67 | t.Fatalf("expected `mynetwork`, got %s", r.Name) 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /client/network_list.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "encoding/json" 5 | "net/url" 6 | 7 | "github.com/docker/engine-api/types" 8 | "github.com/docker/engine-api/types/filters" 9 | "golang.org/x/net/context" 10 | ) 11 | 12 | // NetworkList returns the list of networks configured in the docker host. 13 | func (cli *Client) NetworkList(ctx context.Context, options types.NetworkListOptions) ([]types.NetworkResource, error) { 14 | query := url.Values{} 15 | if options.Filters.Len() > 0 { 16 | filterJSON, err := filters.ToParamWithVersion(cli.version, options.Filters) 17 | if err != nil { 18 | return nil, err 19 | } 20 | 21 | query.Set("filters", filterJSON) 22 | } 23 | var networkResources []types.NetworkResource 24 | resp, err := cli.get(ctx, "/networks", query, nil) 25 | if err != nil { 26 | return networkResources, err 27 | } 28 | err = json.NewDecoder(resp.body).Decode(&networkResources) 29 | ensureReaderClosed(resp) 30 | return networkResources, err 31 | } 32 | -------------------------------------------------------------------------------- /client/network_remove.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import "golang.org/x/net/context" 4 | 5 | // NetworkRemove removes an existent network from the docker host. 6 | func (cli *Client) NetworkRemove(ctx context.Context, networkID string) error { 7 | resp, err := cli.delete(ctx, "/networks/"+networkID, nil, nil) 8 | ensureReaderClosed(resp) 9 | return err 10 | } 11 | -------------------------------------------------------------------------------- /client/network_remove_test.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "io/ioutil" 7 | "net/http" 8 | "strings" 9 | "testing" 10 | 11 | "golang.org/x/net/context" 12 | ) 13 | 14 | func TestNetworkRemoveError(t *testing.T) { 15 | client := &Client{ 16 | transport: newMockClient(nil, errorMock(http.StatusInternalServerError, "Server error")), 17 | } 18 | 19 | err := client.NetworkRemove(context.Background(), "network_id") 20 | if err == nil || err.Error() != "Error response from daemon: Server error" { 21 | t.Fatalf("expected a Server Error, got %v", err) 22 | } 23 | } 24 | 25 | func TestNetworkRemove(t *testing.T) { 26 | expectedURL := "/networks/network_id" 27 | 28 | client := &Client{ 29 | transport: newMockClient(nil, func(req *http.Request) (*http.Response, error) { 30 | if !strings.HasPrefix(req.URL.Path, expectedURL) { 31 | return nil, fmt.Errorf("Expected URL '%s', got '%s'", expectedURL, req.URL) 32 | } 33 | if req.Method != "DELETE" { 34 | return nil, fmt.Errorf("expected DELETE method, got %s", req.Method) 35 | } 36 | return &http.Response{ 37 | StatusCode: http.StatusOK, 38 | Body: ioutil.NopCloser(bytes.NewReader([]byte("body"))), 39 | }, nil 40 | }), 41 | } 42 | 43 | err := client.NetworkRemove(context.Background(), "network_id") 44 | if err != nil { 45 | t.Fatal(err) 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /client/node_inspect.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "io/ioutil" 7 | "net/http" 8 | 9 | "github.com/docker/engine-api/types/swarm" 10 | "golang.org/x/net/context" 11 | ) 12 | 13 | // NodeInspectWithRaw returns the node information. 14 | func (cli *Client) NodeInspectWithRaw(ctx context.Context, nodeID string) (swarm.Node, []byte, error) { 15 | serverResp, err := cli.get(ctx, "/nodes/"+nodeID, nil, nil) 16 | if err != nil { 17 | if serverResp.statusCode == http.StatusNotFound { 18 | return swarm.Node{}, nil, nodeNotFoundError{nodeID} 19 | } 20 | return swarm.Node{}, nil, err 21 | } 22 | defer ensureReaderClosed(serverResp) 23 | 24 | body, err := ioutil.ReadAll(serverResp.body) 25 | if err != nil { 26 | return swarm.Node{}, nil, err 27 | } 28 | 29 | var response swarm.Node 30 | rdr := bytes.NewReader(body) 31 | err = json.NewDecoder(rdr).Decode(&response) 32 | return response, body, err 33 | } 34 | -------------------------------------------------------------------------------- /client/node_inspect_test.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "fmt" 7 | "io/ioutil" 8 | "net/http" 9 | "strings" 10 | "testing" 11 | 12 | "github.com/docker/engine-api/types/swarm" 13 | "golang.org/x/net/context" 14 | ) 15 | 16 | func TestNodeInspectError(t *testing.T) { 17 | client := &Client{ 18 | transport: newMockClient(nil, errorMock(http.StatusInternalServerError, "Server error")), 19 | } 20 | 21 | _, _, err := client.NodeInspectWithRaw(context.Background(), "nothing") 22 | if err == nil || err.Error() != "Error response from daemon: Server error" { 23 | t.Fatalf("expected a Server Error, got %v", err) 24 | } 25 | } 26 | 27 | func TestNodeInspectNodeNotFound(t *testing.T) { 28 | client := &Client{ 29 | transport: newMockClient(nil, errorMock(http.StatusNotFound, "Server error")), 30 | } 31 | 32 | _, _, err := client.NodeInspectWithRaw(context.Background(), "unknown") 33 | if err == nil || !IsErrNodeNotFound(err) { 34 | t.Fatalf("expected an nodeNotFoundError error, got %v", err) 35 | } 36 | } 37 | 38 | func TestNodeInspect(t *testing.T) { 39 | expectedURL := "/nodes/node_id" 40 | client := &Client{ 41 | transport: newMockClient(nil, func(req *http.Request) (*http.Response, error) { 42 | if !strings.HasPrefix(req.URL.Path, expectedURL) { 43 | return nil, fmt.Errorf("Expected URL '%s', got '%s'", expectedURL, req.URL) 44 | } 45 | content, err := json.Marshal(swarm.Node{ 46 | ID: "node_id", 47 | }) 48 | if err != nil { 49 | return nil, err 50 | } 51 | return &http.Response{ 52 | StatusCode: http.StatusOK, 53 | Body: ioutil.NopCloser(bytes.NewReader(content)), 54 | }, nil 55 | }), 56 | } 57 | 58 | nodeInspect, _, err := client.NodeInspectWithRaw(context.Background(), "node_id") 59 | if err != nil { 60 | t.Fatal(err) 61 | } 62 | if nodeInspect.ID != "node_id" { 63 | t.Fatalf("expected `node_id`, got %s", nodeInspect.ID) 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /client/node_list.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "encoding/json" 5 | "net/url" 6 | 7 | "github.com/docker/engine-api/types" 8 | "github.com/docker/engine-api/types/filters" 9 | "github.com/docker/engine-api/types/swarm" 10 | "golang.org/x/net/context" 11 | ) 12 | 13 | // NodeList returns the list of nodes. 14 | func (cli *Client) NodeList(ctx context.Context, options types.NodeListOptions) ([]swarm.Node, error) { 15 | query := url.Values{} 16 | 17 | if options.Filter.Len() > 0 { 18 | filterJSON, err := filters.ToParam(options.Filter) 19 | 20 | if err != nil { 21 | return nil, err 22 | } 23 | 24 | query.Set("filters", filterJSON) 25 | } 26 | 27 | resp, err := cli.get(ctx, "/nodes", query, nil) 28 | if err != nil { 29 | return nil, err 30 | } 31 | 32 | var nodes []swarm.Node 33 | err = json.NewDecoder(resp.body).Decode(&nodes) 34 | ensureReaderClosed(resp) 35 | return nodes, err 36 | } 37 | -------------------------------------------------------------------------------- /client/node_list_test.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "fmt" 7 | "io/ioutil" 8 | "net/http" 9 | "strings" 10 | "testing" 11 | 12 | "github.com/docker/engine-api/types" 13 | "github.com/docker/engine-api/types/filters" 14 | "github.com/docker/engine-api/types/swarm" 15 | "golang.org/x/net/context" 16 | ) 17 | 18 | func TestNodeListError(t *testing.T) { 19 | client := &Client{ 20 | transport: newMockClient(nil, errorMock(http.StatusInternalServerError, "Server error")), 21 | } 22 | 23 | _, err := client.NodeList(context.Background(), types.NodeListOptions{}) 24 | if err == nil || err.Error() != "Error response from daemon: Server error" { 25 | t.Fatalf("expected a Server Error, got %v", err) 26 | } 27 | } 28 | 29 | func TestNodeList(t *testing.T) { 30 | expectedURL := "/nodes" 31 | 32 | filters := filters.NewArgs() 33 | filters.Add("label", "label1") 34 | filters.Add("label", "label2") 35 | 36 | listCases := []struct { 37 | options types.NodeListOptions 38 | expectedQueryParams map[string]string 39 | }{ 40 | { 41 | options: types.NodeListOptions{}, 42 | expectedQueryParams: map[string]string{ 43 | "filters": "", 44 | }, 45 | }, 46 | { 47 | options: types.NodeListOptions{ 48 | Filter: filters, 49 | }, 50 | expectedQueryParams: map[string]string{ 51 | "filters": `{"label":{"label1":true,"label2":true}}`, 52 | }, 53 | }, 54 | } 55 | for _, listCase := range listCases { 56 | client := &Client{ 57 | transport: newMockClient(nil, func(req *http.Request) (*http.Response, error) { 58 | if !strings.HasPrefix(req.URL.Path, expectedURL) { 59 | return nil, fmt.Errorf("Expected URL '%s', got '%s'", expectedURL, req.URL) 60 | } 61 | query := req.URL.Query() 62 | for key, expected := range listCase.expectedQueryParams { 63 | actual := query.Get(key) 64 | if actual != expected { 65 | return nil, fmt.Errorf("%s not set in URL query properly. Expected '%s', got %s", key, expected, actual) 66 | } 67 | } 68 | content, err := json.Marshal([]swarm.Node{ 69 | { 70 | ID: "node_id1", 71 | }, 72 | { 73 | ID: "node_id2", 74 | }, 75 | }) 76 | if err != nil { 77 | return nil, err 78 | } 79 | return &http.Response{ 80 | StatusCode: http.StatusOK, 81 | Body: ioutil.NopCloser(bytes.NewReader(content)), 82 | }, nil 83 | }), 84 | } 85 | 86 | nodes, err := client.NodeList(context.Background(), listCase.options) 87 | if err != nil { 88 | t.Fatal(err) 89 | } 90 | if len(nodes) != 2 { 91 | t.Fatalf("expected 2 nodes, got %v", nodes) 92 | } 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /client/node_remove.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "net/url" 5 | 6 | "github.com/docker/engine-api/types" 7 | 8 | "golang.org/x/net/context" 9 | ) 10 | 11 | // NodeRemove removes a Node. 12 | func (cli *Client) NodeRemove(ctx context.Context, nodeID string, options types.NodeRemoveOptions) error { 13 | query := url.Values{} 14 | if options.Force { 15 | query.Set("force", "1") 16 | } 17 | 18 | resp, err := cli.delete(ctx, "/nodes/"+nodeID, query, nil) 19 | ensureReaderClosed(resp) 20 | return err 21 | } 22 | -------------------------------------------------------------------------------- /client/node_remove_test.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "io/ioutil" 7 | "net/http" 8 | "strings" 9 | "testing" 10 | 11 | "github.com/docker/engine-api/types" 12 | 13 | "golang.org/x/net/context" 14 | ) 15 | 16 | func TestNodeRemoveError(t *testing.T) { 17 | client := &Client{ 18 | transport: newMockClient(nil, errorMock(http.StatusInternalServerError, "Server error")), 19 | } 20 | 21 | err := client.NodeRemove(context.Background(), "node_id", types.NodeRemoveOptions{Force: false}) 22 | if err == nil || err.Error() != "Error response from daemon: Server error" { 23 | t.Fatalf("expected a Server Error, got %v", err) 24 | } 25 | } 26 | 27 | func TestNodeRemove(t *testing.T) { 28 | expectedURL := "/nodes/node_id" 29 | 30 | removeCases := []struct { 31 | force bool 32 | expectedForce string 33 | }{ 34 | { 35 | expectedForce: "", 36 | }, 37 | { 38 | force: true, 39 | expectedForce: "1", 40 | }, 41 | } 42 | 43 | for _, removeCase := range removeCases { 44 | client := &Client{ 45 | transport: newMockClient(nil, func(req *http.Request) (*http.Response, error) { 46 | if !strings.HasPrefix(req.URL.Path, expectedURL) { 47 | return nil, fmt.Errorf("Expected URL '%s', got '%s'", expectedURL, req.URL) 48 | } 49 | if req.Method != "DELETE" { 50 | return nil, fmt.Errorf("expected DELETE method, got %s", req.Method) 51 | } 52 | force := req.URL.Query().Get("force") 53 | if force != removeCase.expectedForce { 54 | return nil, fmt.Errorf("force not set in URL query properly. expected '%s', got %s", removeCase.expectedForce, force) 55 | } 56 | 57 | return &http.Response{ 58 | StatusCode: http.StatusOK, 59 | Body: ioutil.NopCloser(bytes.NewReader([]byte("body"))), 60 | }, nil 61 | }), 62 | } 63 | 64 | err := client.NodeRemove(context.Background(), "node_id", types.NodeRemoveOptions{Force: removeCase.force}) 65 | if err != nil { 66 | t.Fatal(err) 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /client/node_update.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "net/url" 5 | "strconv" 6 | 7 | "github.com/docker/engine-api/types/swarm" 8 | "golang.org/x/net/context" 9 | ) 10 | 11 | // NodeUpdate updates a Node. 12 | func (cli *Client) NodeUpdate(ctx context.Context, nodeID string, version swarm.Version, node swarm.NodeSpec) error { 13 | query := url.Values{} 14 | query.Set("version", strconv.FormatUint(version.Index, 10)) 15 | resp, err := cli.post(ctx, "/nodes/"+nodeID+"/update", query, node, nil) 16 | ensureReaderClosed(resp) 17 | return err 18 | } 19 | -------------------------------------------------------------------------------- /client/node_update_test.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "io/ioutil" 7 | "net/http" 8 | "strings" 9 | "testing" 10 | 11 | "golang.org/x/net/context" 12 | 13 | "github.com/docker/engine-api/types/swarm" 14 | ) 15 | 16 | func TestNodeUpdateError(t *testing.T) { 17 | client := &Client{ 18 | transport: newMockClient(nil, errorMock(http.StatusInternalServerError, "Server error")), 19 | } 20 | 21 | err := client.NodeUpdate(context.Background(), "node_id", swarm.Version{}, swarm.NodeSpec{}) 22 | if err == nil || err.Error() != "Error response from daemon: Server error" { 23 | t.Fatalf("expected a Server Error, got %v", err) 24 | } 25 | } 26 | 27 | func TestNodeUpdate(t *testing.T) { 28 | expectedURL := "/nodes/node_id/update" 29 | 30 | client := &Client{ 31 | transport: newMockClient(nil, func(req *http.Request) (*http.Response, error) { 32 | if !strings.HasPrefix(req.URL.Path, expectedURL) { 33 | return nil, fmt.Errorf("Expected URL '%s', got '%s'", expectedURL, req.URL) 34 | } 35 | if req.Method != "POST" { 36 | return nil, fmt.Errorf("expected POST method, got %s", req.Method) 37 | } 38 | return &http.Response{ 39 | StatusCode: http.StatusOK, 40 | Body: ioutil.NopCloser(bytes.NewReader([]byte("body"))), 41 | }, nil 42 | }), 43 | } 44 | 45 | err := client.NodeUpdate(context.Background(), "node_id", swarm.Version{}, swarm.NodeSpec{}) 46 | if err != nil { 47 | t.Fatal(err) 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /client/plugin_disable.go: -------------------------------------------------------------------------------- 1 | // +build experimental 2 | 3 | package client 4 | 5 | import ( 6 | "golang.org/x/net/context" 7 | ) 8 | 9 | // PluginDisable disables a plugin 10 | func (cli *Client) PluginDisable(ctx context.Context, name string) error { 11 | resp, err := cli.post(ctx, "/plugins/"+name+"/disable", nil, nil, nil) 12 | ensureReaderClosed(resp) 13 | return err 14 | } 15 | -------------------------------------------------------------------------------- /client/plugin_disable_test.go: -------------------------------------------------------------------------------- 1 | // +build experimental 2 | 3 | package client 4 | 5 | import ( 6 | "bytes" 7 | "fmt" 8 | "io/ioutil" 9 | "net/http" 10 | "strings" 11 | "testing" 12 | 13 | "golang.org/x/net/context" 14 | ) 15 | 16 | func TestPluginDisableError(t *testing.T) { 17 | client := &Client{ 18 | transport: newMockClient(nil, errorMock(http.StatusInternalServerError, "Server error")), 19 | } 20 | 21 | err := client.PluginDisable(context.Background(), "plugin_name") 22 | if err == nil || err.Error() != "Error response from daemon: Server error" { 23 | t.Fatalf("expected a Server Error, got %v", err) 24 | } 25 | } 26 | 27 | func TestPluginDisable(t *testing.T) { 28 | expectedURL := "/plugins/plugin_name/disable" 29 | 30 | client := &Client{ 31 | transport: newMockClient(nil, func(req *http.Request) (*http.Response, error) { 32 | if !strings.HasPrefix(req.URL.Path, expectedURL) { 33 | return nil, fmt.Errorf("Expected URL '%s', got '%s'", expectedURL, req.URL) 34 | } 35 | if req.Method != "POST" { 36 | return nil, fmt.Errorf("expected POST method, got %s", req.Method) 37 | } 38 | return &http.Response{ 39 | StatusCode: http.StatusOK, 40 | Body: ioutil.NopCloser(bytes.NewReader([]byte(""))), 41 | }, nil 42 | }), 43 | } 44 | 45 | err := client.PluginDisable(context.Background(), "plugin_name") 46 | if err != nil { 47 | t.Fatal(err) 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /client/plugin_enable.go: -------------------------------------------------------------------------------- 1 | // +build experimental 2 | 3 | package client 4 | 5 | import ( 6 | "golang.org/x/net/context" 7 | ) 8 | 9 | // PluginEnable enables a plugin 10 | func (cli *Client) PluginEnable(ctx context.Context, name string) error { 11 | resp, err := cli.post(ctx, "/plugins/"+name+"/enable", nil, nil, nil) 12 | ensureReaderClosed(resp) 13 | return err 14 | } 15 | -------------------------------------------------------------------------------- /client/plugin_enable_test.go: -------------------------------------------------------------------------------- 1 | // +build experimental 2 | 3 | package client 4 | 5 | import ( 6 | "bytes" 7 | "fmt" 8 | "io/ioutil" 9 | "net/http" 10 | "strings" 11 | "testing" 12 | 13 | "golang.org/x/net/context" 14 | ) 15 | 16 | func TestPluginEnableError(t *testing.T) { 17 | client := &Client{ 18 | transport: newMockClient(nil, errorMock(http.StatusInternalServerError, "Server error")), 19 | } 20 | 21 | err := client.PluginEnable(context.Background(), "plugin_name") 22 | if err == nil || err.Error() != "Error response from daemon: Server error" { 23 | t.Fatalf("expected a Server Error, got %v", err) 24 | } 25 | } 26 | 27 | func TestPluginEnable(t *testing.T) { 28 | expectedURL := "/plugins/plugin_name/enable" 29 | 30 | client := &Client{ 31 | transport: newMockClient(nil, func(req *http.Request) (*http.Response, error) { 32 | if !strings.HasPrefix(req.URL.Path, expectedURL) { 33 | return nil, fmt.Errorf("Expected URL '%s', got '%s'", expectedURL, req.URL) 34 | } 35 | if req.Method != "POST" { 36 | return nil, fmt.Errorf("expected POST method, got %s", req.Method) 37 | } 38 | return &http.Response{ 39 | StatusCode: http.StatusOK, 40 | Body: ioutil.NopCloser(bytes.NewReader([]byte(""))), 41 | }, nil 42 | }), 43 | } 44 | 45 | err := client.PluginEnable(context.Background(), "plugin_name") 46 | if err != nil { 47 | t.Fatal(err) 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /client/plugin_inspect.go: -------------------------------------------------------------------------------- 1 | // +build experimental 2 | 3 | package client 4 | 5 | import ( 6 | "bytes" 7 | "encoding/json" 8 | "io/ioutil" 9 | 10 | "github.com/docker/engine-api/types" 11 | "golang.org/x/net/context" 12 | ) 13 | 14 | // PluginInspectWithRaw inspects an existing plugin 15 | func (cli *Client) PluginInspectWithRaw(ctx context.Context, name string) (*types.Plugin, []byte, error) { 16 | resp, err := cli.get(ctx, "/plugins/"+name, nil, nil) 17 | if err != nil { 18 | return nil, nil, err 19 | } 20 | 21 | defer ensureReaderClosed(resp) 22 | body, err := ioutil.ReadAll(resp.body) 23 | if err != nil { 24 | return nil, nil, err 25 | } 26 | var p types.Plugin 27 | rdr := bytes.NewReader(body) 28 | err = json.NewDecoder(rdr).Decode(&p) 29 | return &p, body, err 30 | } 31 | -------------------------------------------------------------------------------- /client/plugin_inspect_test.go: -------------------------------------------------------------------------------- 1 | // +build experimental 2 | 3 | package client 4 | 5 | import ( 6 | "bytes" 7 | "encoding/json" 8 | "fmt" 9 | "io/ioutil" 10 | "net/http" 11 | "strings" 12 | "testing" 13 | 14 | "github.com/docker/engine-api/types" 15 | "golang.org/x/net/context" 16 | ) 17 | 18 | func TestPluginInspectError(t *testing.T) { 19 | client := &Client{ 20 | transport: newMockClient(nil, errorMock(http.StatusInternalServerError, "Server error")), 21 | } 22 | 23 | _, _, err := client.PluginInspectWithRaw(context.Background(), "nothing") 24 | if err == nil || err.Error() != "Error response from daemon: Server error" { 25 | t.Fatalf("expected a Server Error, got %v", err) 26 | } 27 | } 28 | 29 | func TestPluginInspect(t *testing.T) { 30 | expectedURL := "/plugins/plugin_name" 31 | client := &Client{ 32 | transport: newMockClient(nil, func(req *http.Request) (*http.Response, error) { 33 | if !strings.HasPrefix(req.URL.Path, expectedURL) { 34 | return nil, fmt.Errorf("Expected URL '%s', got '%s'", expectedURL, req.URL) 35 | } 36 | content, err := json.Marshal(types.Plugin{ 37 | ID: "plugin_id", 38 | }) 39 | if err != nil { 40 | return nil, err 41 | } 42 | return &http.Response{ 43 | StatusCode: http.StatusOK, 44 | Body: ioutil.NopCloser(bytes.NewReader(content)), 45 | }, nil 46 | }), 47 | } 48 | 49 | pluginInspect, _, err := client.PluginInspectWithRaw(context.Background(), "plugin_name") 50 | if err != nil { 51 | t.Fatal(err) 52 | } 53 | if pluginInspect.ID != "plugin_id" { 54 | t.Fatalf("expected `plugin_id`, got %s", pluginInspect.ID) 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /client/plugin_install.go: -------------------------------------------------------------------------------- 1 | // +build experimental 2 | 3 | package client 4 | 5 | import ( 6 | "encoding/json" 7 | "net/http" 8 | "net/url" 9 | 10 | "github.com/docker/engine-api/types" 11 | "golang.org/x/net/context" 12 | ) 13 | 14 | // PluginInstall installs a plugin 15 | func (cli *Client) PluginInstall(ctx context.Context, name string, options types.PluginInstallOptions) error { 16 | // FIXME(vdemeester) name is a ref, we might want to parse/validate it here. 17 | query := url.Values{} 18 | query.Set("name", name) 19 | resp, err := cli.tryPluginPull(ctx, query, options.RegistryAuth) 20 | if resp.statusCode == http.StatusUnauthorized && options.PrivilegeFunc != nil { 21 | newAuthHeader, privilegeErr := options.PrivilegeFunc() 22 | if privilegeErr != nil { 23 | ensureReaderClosed(resp) 24 | return privilegeErr 25 | } 26 | resp, err = cli.tryPluginPull(ctx, query, newAuthHeader) 27 | } 28 | if err != nil { 29 | ensureReaderClosed(resp) 30 | return err 31 | } 32 | var privileges types.PluginPrivileges 33 | if err := json.NewDecoder(resp.body).Decode(&privileges); err != nil { 34 | ensureReaderClosed(resp) 35 | return err 36 | } 37 | ensureReaderClosed(resp) 38 | 39 | if !options.AcceptAllPermissions && options.AcceptPermissionsFunc != nil && len(privileges) > 0 { 40 | accept, err := options.AcceptPermissionsFunc(privileges) 41 | if err != nil { 42 | return err 43 | } 44 | if !accept { 45 | resp, _ := cli.delete(ctx, "/plugins/"+name, nil, nil) 46 | ensureReaderClosed(resp) 47 | return pluginPermissionDenied{name} 48 | } 49 | } 50 | if options.Disabled { 51 | return nil 52 | } 53 | return cli.PluginEnable(ctx, name) 54 | } 55 | 56 | func (cli *Client) tryPluginPull(ctx context.Context, query url.Values, registryAuth string) (serverResponse, error) { 57 | headers := map[string][]string{"X-Registry-Auth": {registryAuth}} 58 | return cli.post(ctx, "/plugins/pull", query, nil, headers) 59 | } 60 | -------------------------------------------------------------------------------- /client/plugin_list.go: -------------------------------------------------------------------------------- 1 | // +build experimental 2 | 3 | package client 4 | 5 | import ( 6 | "encoding/json" 7 | 8 | "github.com/docker/engine-api/types" 9 | "golang.org/x/net/context" 10 | ) 11 | 12 | // PluginList returns the installed plugins 13 | func (cli *Client) PluginList(ctx context.Context) (types.PluginsListResponse, error) { 14 | var plugins types.PluginsListResponse 15 | resp, err := cli.get(ctx, "/plugins", nil, nil) 16 | if err != nil { 17 | return plugins, err 18 | } 19 | 20 | err = json.NewDecoder(resp.body).Decode(&plugins) 21 | ensureReaderClosed(resp) 22 | return plugins, err 23 | } 24 | -------------------------------------------------------------------------------- /client/plugin_list_test.go: -------------------------------------------------------------------------------- 1 | // +build experimental 2 | 3 | package client 4 | 5 | import ( 6 | "bytes" 7 | "encoding/json" 8 | "fmt" 9 | "io/ioutil" 10 | "net/http" 11 | "strings" 12 | "testing" 13 | 14 | "github.com/docker/engine-api/types" 15 | "golang.org/x/net/context" 16 | ) 17 | 18 | func TestPluginListError(t *testing.T) { 19 | client := &Client{ 20 | transport: newMockClient(nil, errorMock(http.StatusInternalServerError, "Server error")), 21 | } 22 | 23 | _, err := client.PluginList(context.Background()) 24 | if err == nil || err.Error() != "Error response from daemon: Server error" { 25 | t.Fatalf("expected a Server Error, got %v", err) 26 | } 27 | } 28 | 29 | func TestPluginList(t *testing.T) { 30 | expectedURL := "/plugins" 31 | client := &Client{ 32 | transport: newMockClient(nil, func(req *http.Request) (*http.Response, error) { 33 | if !strings.HasPrefix(req.URL.Path, expectedURL) { 34 | return nil, fmt.Errorf("Expected URL '%s', got '%s'", expectedURL, req.URL) 35 | } 36 | content, err := json.Marshal([]*types.Plugin{ 37 | { 38 | ID: "plugin_id1", 39 | }, 40 | { 41 | ID: "plugin_id2", 42 | }, 43 | }) 44 | if err != nil { 45 | return nil, err 46 | } 47 | return &http.Response{ 48 | StatusCode: http.StatusOK, 49 | Body: ioutil.NopCloser(bytes.NewReader(content)), 50 | }, nil 51 | }), 52 | } 53 | 54 | plugins, err := client.PluginList(context.Background()) 55 | if err != nil { 56 | t.Fatal(err) 57 | } 58 | if len(plugins) != 2 { 59 | t.Fatalf("expected 2 plugins, got %v", plugins) 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /client/plugin_push.go: -------------------------------------------------------------------------------- 1 | // +build experimental 2 | 3 | package client 4 | 5 | import ( 6 | "golang.org/x/net/context" 7 | ) 8 | 9 | // PluginPush pushes a plugin to a registry 10 | func (cli *Client) PluginPush(ctx context.Context, name string, registryAuth string) error { 11 | headers := map[string][]string{"X-Registry-Auth": {registryAuth}} 12 | resp, err := cli.post(ctx, "/plugins/"+name+"/push", nil, nil, headers) 13 | ensureReaderClosed(resp) 14 | return err 15 | } 16 | -------------------------------------------------------------------------------- /client/plugin_push_test.go: -------------------------------------------------------------------------------- 1 | // +build experimental 2 | 3 | package client 4 | 5 | import ( 6 | "bytes" 7 | "fmt" 8 | "io/ioutil" 9 | "net/http" 10 | "strings" 11 | "testing" 12 | 13 | "golang.org/x/net/context" 14 | ) 15 | 16 | func TestPluginPushError(t *testing.T) { 17 | client := &Client{ 18 | transport: newMockClient(nil, errorMock(http.StatusInternalServerError, "Server error")), 19 | } 20 | 21 | err := client.PluginPush(context.Background(), "plugin_name", "") 22 | if err == nil || err.Error() != "Error response from daemon: Server error" { 23 | t.Fatalf("expected a Server Error, got %v", err) 24 | } 25 | } 26 | 27 | func TestPluginPush(t *testing.T) { 28 | expectedURL := "/plugins/plugin_name" 29 | 30 | client := &Client{ 31 | transport: newMockClient(nil, func(req *http.Request) (*http.Response, error) { 32 | if !strings.HasPrefix(req.URL.Path, expectedURL) { 33 | return nil, fmt.Errorf("Expected URL '%s', got '%s'", expectedURL, req.URL) 34 | } 35 | if req.Method != "POST" { 36 | return nil, fmt.Errorf("expected POST method, got %s", req.Method) 37 | } 38 | auth := req.Header.Get("X-Registry-Auth") 39 | if auth != "authtoken" { 40 | return nil, fmt.Errorf("Invalid auth header : expected %s, got %s", "authtoken", auth) 41 | } 42 | return &http.Response{ 43 | StatusCode: http.StatusOK, 44 | Body: ioutil.NopCloser(bytes.NewReader([]byte(""))), 45 | }, nil 46 | }), 47 | } 48 | 49 | err := client.PluginPush(context.Background(), "plugin_name", "authtoken") 50 | if err != nil { 51 | t.Fatal(err) 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /client/plugin_remove.go: -------------------------------------------------------------------------------- 1 | // +build experimental 2 | 3 | package client 4 | 5 | import ( 6 | "net/url" 7 | 8 | "github.com/docker/engine-api/types" 9 | "golang.org/x/net/context" 10 | ) 11 | 12 | // PluginRemove removes a plugin 13 | func (cli *Client) PluginRemove(ctx context.Context, name string, options types.PluginRemoveOptions) error { 14 | query := url.Values{} 15 | if options.Force { 16 | query.Set("force", "1") 17 | } 18 | 19 | resp, err := cli.delete(ctx, "/plugins/"+name, query, nil) 20 | ensureReaderClosed(resp) 21 | return err 22 | } 23 | -------------------------------------------------------------------------------- /client/plugin_remove_test.go: -------------------------------------------------------------------------------- 1 | // +build experimental 2 | 3 | package client 4 | 5 | import ( 6 | "bytes" 7 | "fmt" 8 | "io/ioutil" 9 | "net/http" 10 | "strings" 11 | "testing" 12 | 13 | "github.com/docker/engine-api/types" 14 | 15 | "golang.org/x/net/context" 16 | ) 17 | 18 | func TestPluginRemoveError(t *testing.T) { 19 | client := &Client{ 20 | transport: newMockClient(nil, errorMock(http.StatusInternalServerError, "Server error")), 21 | } 22 | 23 | err := client.PluginRemove(context.Background(), "plugin_name", types.PluginRemoveOptions{}) 24 | if err == nil || err.Error() != "Error response from daemon: Server error" { 25 | t.Fatalf("expected a Server Error, got %v", err) 26 | } 27 | } 28 | 29 | func TestPluginRemove(t *testing.T) { 30 | expectedURL := "/plugins/plugin_name" 31 | 32 | client := &Client{ 33 | transport: newMockClient(nil, func(req *http.Request) (*http.Response, error) { 34 | if !strings.HasPrefix(req.URL.Path, expectedURL) { 35 | return nil, fmt.Errorf("Expected URL '%s', got '%s'", expectedURL, req.URL) 36 | } 37 | if req.Method != "DELETE" { 38 | return nil, fmt.Errorf("expected POST method, got %s", req.Method) 39 | } 40 | return &http.Response{ 41 | StatusCode: http.StatusOK, 42 | Body: ioutil.NopCloser(bytes.NewReader([]byte(""))), 43 | }, nil 44 | }), 45 | } 46 | 47 | err := client.PluginRemove(context.Background(), "plugin_name", types.PluginRemoveOptions{}) 48 | if err != nil { 49 | t.Fatal(err) 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /client/plugin_set.go: -------------------------------------------------------------------------------- 1 | // +build experimental 2 | 3 | package client 4 | 5 | import ( 6 | "golang.org/x/net/context" 7 | ) 8 | 9 | // PluginSet modifies settings for an existing plugin 10 | func (cli *Client) PluginSet(ctx context.Context, name string, args []string) error { 11 | resp, err := cli.post(ctx, "/plugins/"+name+"/set", nil, args, nil) 12 | ensureReaderClosed(resp) 13 | return err 14 | } 15 | -------------------------------------------------------------------------------- /client/plugin_set_test.go: -------------------------------------------------------------------------------- 1 | // +build experimental 2 | 3 | package client 4 | 5 | import ( 6 | "bytes" 7 | "fmt" 8 | "io/ioutil" 9 | "net/http" 10 | "strings" 11 | "testing" 12 | 13 | "golang.org/x/net/context" 14 | ) 15 | 16 | func TestPluginSetError(t *testing.T) { 17 | client := &Client{ 18 | transport: newMockClient(nil, errorMock(http.StatusInternalServerError, "Server error")), 19 | } 20 | 21 | err := client.PluginSet(context.Background(), "plugin_name", []string{}) 22 | if err == nil || err.Error() != "Error response from daemon: Server error" { 23 | t.Fatalf("expected a Server Error, got %v", err) 24 | } 25 | } 26 | 27 | func TestPluginSet(t *testing.T) { 28 | expectedURL := "/plugins/plugin_name/set" 29 | 30 | client := &Client{ 31 | transport: newMockClient(nil, func(req *http.Request) (*http.Response, error) { 32 | if !strings.HasPrefix(req.URL.Path, expectedURL) { 33 | return nil, fmt.Errorf("Expected URL '%s', got '%s'", expectedURL, req.URL) 34 | } 35 | if req.Method != "POST" { 36 | return nil, fmt.Errorf("expected POST method, got %s", req.Method) 37 | } 38 | return &http.Response{ 39 | StatusCode: http.StatusOK, 40 | Body: ioutil.NopCloser(bytes.NewReader([]byte(""))), 41 | }, nil 42 | }), 43 | } 44 | 45 | err := client.PluginSet(context.Background(), "plugin_name", []string{"arg1"}) 46 | if err != nil { 47 | t.Fatal(err) 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /client/service_create.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "encoding/json" 5 | 6 | "github.com/docker/engine-api/types" 7 | "github.com/docker/engine-api/types/swarm" 8 | "golang.org/x/net/context" 9 | ) 10 | 11 | // ServiceCreate creates a new Service. 12 | func (cli *Client) ServiceCreate(ctx context.Context, service swarm.ServiceSpec, options types.ServiceCreateOptions) (types.ServiceCreateResponse, error) { 13 | var headers map[string][]string 14 | 15 | if options.EncodedRegistryAuth != "" { 16 | headers = map[string][]string{ 17 | "X-Registry-Auth": []string{options.EncodedRegistryAuth}, 18 | } 19 | } 20 | 21 | var response types.ServiceCreateResponse 22 | resp, err := cli.post(ctx, "/services/create", nil, service, headers) 23 | if err != nil { 24 | return response, err 25 | } 26 | 27 | err = json.NewDecoder(resp.body).Decode(&response) 28 | ensureReaderClosed(resp) 29 | return response, err 30 | } 31 | -------------------------------------------------------------------------------- /client/service_create_test.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "fmt" 7 | "io/ioutil" 8 | "net/http" 9 | "strings" 10 | "testing" 11 | 12 | "github.com/docker/engine-api/types" 13 | "github.com/docker/engine-api/types/swarm" 14 | "golang.org/x/net/context" 15 | ) 16 | 17 | func TestServiceCreateError(t *testing.T) { 18 | client := &Client{ 19 | transport: newMockClient(nil, errorMock(http.StatusInternalServerError, "Server error")), 20 | } 21 | _, err := client.ServiceCreate(context.Background(), swarm.ServiceSpec{}, types.ServiceCreateOptions{}) 22 | if err == nil || err.Error() != "Error response from daemon: Server error" { 23 | t.Fatalf("expected a Server Error, got %v", err) 24 | } 25 | } 26 | 27 | func TestServiceCreate(t *testing.T) { 28 | expectedURL := "/services/create" 29 | client := &Client{ 30 | transport: newMockClient(nil, func(req *http.Request) (*http.Response, error) { 31 | if !strings.HasPrefix(req.URL.Path, expectedURL) { 32 | return nil, fmt.Errorf("Expected URL '%s', got '%s'", expectedURL, req.URL) 33 | } 34 | if req.Method != "POST" { 35 | return nil, fmt.Errorf("expected POST method, got %s", req.Method) 36 | } 37 | b, err := json.Marshal(types.ServiceCreateResponse{ 38 | ID: "service_id", 39 | }) 40 | if err != nil { 41 | return nil, err 42 | } 43 | return &http.Response{ 44 | StatusCode: http.StatusOK, 45 | Body: ioutil.NopCloser(bytes.NewReader(b)), 46 | }, nil 47 | }), 48 | } 49 | 50 | r, err := client.ServiceCreate(context.Background(), swarm.ServiceSpec{}, types.ServiceCreateOptions{}) 51 | if err != nil { 52 | t.Fatal(err) 53 | } 54 | if r.ID != "service_id" { 55 | t.Fatalf("expected `service_id`, got %s", r.ID) 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /client/service_inspect.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "io/ioutil" 7 | "net/http" 8 | 9 | "github.com/docker/engine-api/types/swarm" 10 | "golang.org/x/net/context" 11 | ) 12 | 13 | // ServiceInspectWithRaw returns the service information and the raw data. 14 | func (cli *Client) ServiceInspectWithRaw(ctx context.Context, serviceID string) (swarm.Service, []byte, error) { 15 | serverResp, err := cli.get(ctx, "/services/"+serviceID, nil, nil) 16 | if err != nil { 17 | if serverResp.statusCode == http.StatusNotFound { 18 | return swarm.Service{}, nil, serviceNotFoundError{serviceID} 19 | } 20 | return swarm.Service{}, nil, err 21 | } 22 | defer ensureReaderClosed(serverResp) 23 | 24 | body, err := ioutil.ReadAll(serverResp.body) 25 | if err != nil { 26 | return swarm.Service{}, nil, err 27 | } 28 | 29 | var response swarm.Service 30 | rdr := bytes.NewReader(body) 31 | err = json.NewDecoder(rdr).Decode(&response) 32 | return response, body, err 33 | } 34 | -------------------------------------------------------------------------------- /client/service_inspect_test.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "fmt" 7 | "io/ioutil" 8 | "net/http" 9 | "strings" 10 | "testing" 11 | 12 | "github.com/docker/engine-api/types/swarm" 13 | "golang.org/x/net/context" 14 | ) 15 | 16 | func TestServiceInspectError(t *testing.T) { 17 | client := &Client{ 18 | transport: newMockClient(nil, errorMock(http.StatusInternalServerError, "Server error")), 19 | } 20 | 21 | _, _, err := client.ServiceInspectWithRaw(context.Background(), "nothing") 22 | if err == nil || err.Error() != "Error response from daemon: Server error" { 23 | t.Fatalf("expected a Server Error, got %v", err) 24 | } 25 | } 26 | 27 | func TestServiceInspectServiceNotFound(t *testing.T) { 28 | client := &Client{ 29 | transport: newMockClient(nil, errorMock(http.StatusNotFound, "Server error")), 30 | } 31 | 32 | _, _, err := client.ServiceInspectWithRaw(context.Background(), "unknown") 33 | if err == nil || !IsErrServiceNotFound(err) { 34 | t.Fatalf("expected an serviceNotFoundError error, got %v", err) 35 | } 36 | } 37 | 38 | func TestServiceInspect(t *testing.T) { 39 | expectedURL := "/services/service_id" 40 | client := &Client{ 41 | transport: newMockClient(nil, func(req *http.Request) (*http.Response, error) { 42 | if !strings.HasPrefix(req.URL.Path, expectedURL) { 43 | return nil, fmt.Errorf("Expected URL '%s', got '%s'", expectedURL, req.URL) 44 | } 45 | content, err := json.Marshal(swarm.Service{ 46 | ID: "service_id", 47 | }) 48 | if err != nil { 49 | return nil, err 50 | } 51 | return &http.Response{ 52 | StatusCode: http.StatusOK, 53 | Body: ioutil.NopCloser(bytes.NewReader(content)), 54 | }, nil 55 | }), 56 | } 57 | 58 | serviceInspect, _, err := client.ServiceInspectWithRaw(context.Background(), "service_id") 59 | if err != nil { 60 | t.Fatal(err) 61 | } 62 | if serviceInspect.ID != "service_id" { 63 | t.Fatalf("expected `service_id`, got %s", serviceInspect.ID) 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /client/service_list.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "encoding/json" 5 | "net/url" 6 | 7 | "github.com/docker/engine-api/types" 8 | "github.com/docker/engine-api/types/filters" 9 | "github.com/docker/engine-api/types/swarm" 10 | "golang.org/x/net/context" 11 | ) 12 | 13 | // ServiceList returns the list of services. 14 | func (cli *Client) ServiceList(ctx context.Context, options types.ServiceListOptions) ([]swarm.Service, error) { 15 | query := url.Values{} 16 | 17 | if options.Filter.Len() > 0 { 18 | filterJSON, err := filters.ToParam(options.Filter) 19 | if err != nil { 20 | return nil, err 21 | } 22 | 23 | query.Set("filters", filterJSON) 24 | } 25 | 26 | resp, err := cli.get(ctx, "/services", query, nil) 27 | if err != nil { 28 | return nil, err 29 | } 30 | 31 | var services []swarm.Service 32 | err = json.NewDecoder(resp.body).Decode(&services) 33 | ensureReaderClosed(resp) 34 | return services, err 35 | } 36 | -------------------------------------------------------------------------------- /client/service_remove.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import "golang.org/x/net/context" 4 | 5 | // ServiceRemove kills and removes a service. 6 | func (cli *Client) ServiceRemove(ctx context.Context, serviceID string) error { 7 | resp, err := cli.delete(ctx, "/services/"+serviceID, nil, nil) 8 | ensureReaderClosed(resp) 9 | return err 10 | } 11 | -------------------------------------------------------------------------------- /client/service_remove_test.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "io/ioutil" 7 | "net/http" 8 | "strings" 9 | "testing" 10 | 11 | "golang.org/x/net/context" 12 | ) 13 | 14 | func TestServiceRemoveError(t *testing.T) { 15 | client := &Client{ 16 | transport: newMockClient(nil, errorMock(http.StatusInternalServerError, "Server error")), 17 | } 18 | 19 | err := client.ServiceRemove(context.Background(), "service_id") 20 | if err == nil || err.Error() != "Error response from daemon: Server error" { 21 | t.Fatalf("expected a Server Error, got %v", err) 22 | } 23 | } 24 | 25 | func TestServiceRemove(t *testing.T) { 26 | expectedURL := "/services/service_id" 27 | 28 | client := &Client{ 29 | transport: newMockClient(nil, func(req *http.Request) (*http.Response, error) { 30 | if !strings.HasPrefix(req.URL.Path, expectedURL) { 31 | return nil, fmt.Errorf("Expected URL '%s', got '%s'", expectedURL, req.URL) 32 | } 33 | if req.Method != "DELETE" { 34 | return nil, fmt.Errorf("expected DELETE method, got %s", req.Method) 35 | } 36 | return &http.Response{ 37 | StatusCode: http.StatusOK, 38 | Body: ioutil.NopCloser(bytes.NewReader([]byte("body"))), 39 | }, nil 40 | }), 41 | } 42 | 43 | err := client.ServiceRemove(context.Background(), "service_id") 44 | if err != nil { 45 | t.Fatal(err) 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /client/service_update.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "net/url" 5 | "strconv" 6 | 7 | "github.com/docker/engine-api/types" 8 | "github.com/docker/engine-api/types/swarm" 9 | "golang.org/x/net/context" 10 | ) 11 | 12 | // ServiceUpdate updates a Service. 13 | func (cli *Client) ServiceUpdate(ctx context.Context, serviceID string, version swarm.Version, service swarm.ServiceSpec, options types.ServiceUpdateOptions) error { 14 | var ( 15 | headers map[string][]string 16 | query = url.Values{} 17 | ) 18 | 19 | if options.EncodedRegistryAuth != "" { 20 | headers = map[string][]string{ 21 | "X-Registry-Auth": []string{options.EncodedRegistryAuth}, 22 | } 23 | } 24 | 25 | query.Set("version", strconv.FormatUint(version.Index, 10)) 26 | 27 | resp, err := cli.post(ctx, "/services/"+serviceID+"/update", query, service, headers) 28 | ensureReaderClosed(resp) 29 | return err 30 | } 31 | -------------------------------------------------------------------------------- /client/service_update_test.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "io/ioutil" 7 | "net/http" 8 | "strings" 9 | "testing" 10 | 11 | "golang.org/x/net/context" 12 | 13 | "github.com/docker/engine-api/types" 14 | "github.com/docker/engine-api/types/swarm" 15 | ) 16 | 17 | func TestServiceUpdateError(t *testing.T) { 18 | client := &Client{ 19 | transport: newMockClient(nil, errorMock(http.StatusInternalServerError, "Server error")), 20 | } 21 | 22 | err := client.ServiceUpdate(context.Background(), "service_id", swarm.Version{}, swarm.ServiceSpec{}, types.ServiceUpdateOptions{}) 23 | if err == nil || err.Error() != "Error response from daemon: Server error" { 24 | t.Fatalf("expected a Server Error, got %v", err) 25 | } 26 | } 27 | 28 | func TestServiceUpdate(t *testing.T) { 29 | expectedURL := "/services/service_id/update" 30 | 31 | updateCases := []struct { 32 | swarmVersion swarm.Version 33 | expectedVersion string 34 | }{ 35 | { 36 | expectedVersion: "0", 37 | }, 38 | { 39 | swarmVersion: swarm.Version{ 40 | Index: 0, 41 | }, 42 | expectedVersion: "0", 43 | }, 44 | { 45 | swarmVersion: swarm.Version{ 46 | Index: 10, 47 | }, 48 | expectedVersion: "10", 49 | }, 50 | } 51 | 52 | for _, updateCase := range updateCases { 53 | client := &Client{ 54 | transport: newMockClient(nil, func(req *http.Request) (*http.Response, error) { 55 | if !strings.HasPrefix(req.URL.Path, expectedURL) { 56 | return nil, fmt.Errorf("Expected URL '%s', got '%s'", expectedURL, req.URL) 57 | } 58 | if req.Method != "POST" { 59 | return nil, fmt.Errorf("expected POST method, got %s", req.Method) 60 | } 61 | version := req.URL.Query().Get("version") 62 | if version != updateCase.expectedVersion { 63 | return nil, fmt.Errorf("version not set in URL query properly, expected '%s', got %s", updateCase.expectedVersion, version) 64 | } 65 | return &http.Response{ 66 | StatusCode: http.StatusOK, 67 | Body: ioutil.NopCloser(bytes.NewReader([]byte("body"))), 68 | }, nil 69 | }), 70 | } 71 | 72 | err := client.ServiceUpdate(context.Background(), "service_id", updateCase.swarmVersion, swarm.ServiceSpec{}, types.ServiceUpdateOptions{}) 73 | if err != nil { 74 | t.Fatal(err) 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /client/swarm_init.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "encoding/json" 5 | 6 | "github.com/docker/engine-api/types/swarm" 7 | "golang.org/x/net/context" 8 | ) 9 | 10 | // SwarmInit initializes the Swarm. 11 | func (cli *Client) SwarmInit(ctx context.Context, req swarm.InitRequest) (string, error) { 12 | serverResp, err := cli.post(ctx, "/swarm/init", nil, req, nil) 13 | if err != nil { 14 | return "", err 15 | } 16 | 17 | var response string 18 | err = json.NewDecoder(serverResp.body).Decode(&response) 19 | ensureReaderClosed(serverResp) 20 | return response, err 21 | } 22 | -------------------------------------------------------------------------------- /client/swarm_init_test.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "io/ioutil" 7 | "net/http" 8 | "strings" 9 | "testing" 10 | 11 | "golang.org/x/net/context" 12 | 13 | "github.com/docker/engine-api/types/swarm" 14 | ) 15 | 16 | func TestSwarmInitError(t *testing.T) { 17 | client := &Client{ 18 | transport: newMockClient(nil, errorMock(http.StatusInternalServerError, "Server error")), 19 | } 20 | 21 | _, err := client.SwarmInit(context.Background(), swarm.InitRequest{}) 22 | if err == nil || err.Error() != "Error response from daemon: Server error" { 23 | t.Fatalf("expected a Server Error, got %v", err) 24 | } 25 | } 26 | 27 | func TestSwarmInit(t *testing.T) { 28 | expectedURL := "/swarm/init" 29 | 30 | client := &Client{ 31 | transport: newMockClient(nil, func(req *http.Request) (*http.Response, error) { 32 | if !strings.HasPrefix(req.URL.Path, expectedURL) { 33 | return nil, fmt.Errorf("Expected URL '%s', got '%s'", expectedURL, req.URL) 34 | } 35 | if req.Method != "POST" { 36 | return nil, fmt.Errorf("expected POST method, got %s", req.Method) 37 | } 38 | return &http.Response{ 39 | StatusCode: http.StatusOK, 40 | Body: ioutil.NopCloser(bytes.NewReader([]byte(`"body"`))), 41 | }, nil 42 | }), 43 | } 44 | 45 | resp, err := client.SwarmInit(context.Background(), swarm.InitRequest{ 46 | ListenAddr: "0.0.0.0:2377", 47 | }) 48 | if err != nil { 49 | t.Fatal(err) 50 | } 51 | if resp != "body" { 52 | t.Fatalf("Expected 'body', got %s", resp) 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /client/swarm_inspect.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "encoding/json" 5 | 6 | "github.com/docker/engine-api/types/swarm" 7 | "golang.org/x/net/context" 8 | ) 9 | 10 | // SwarmInspect inspects the Swarm. 11 | func (cli *Client) SwarmInspect(ctx context.Context) (swarm.Swarm, error) { 12 | serverResp, err := cli.get(ctx, "/swarm", nil, nil) 13 | if err != nil { 14 | return swarm.Swarm{}, err 15 | } 16 | 17 | var response swarm.Swarm 18 | err = json.NewDecoder(serverResp.body).Decode(&response) 19 | ensureReaderClosed(serverResp) 20 | return response, err 21 | } 22 | -------------------------------------------------------------------------------- /client/swarm_inspect_test.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "fmt" 7 | "io/ioutil" 8 | "net/http" 9 | "strings" 10 | "testing" 11 | 12 | "github.com/docker/engine-api/types/swarm" 13 | "golang.org/x/net/context" 14 | ) 15 | 16 | func TestSwarmInspectError(t *testing.T) { 17 | client := &Client{ 18 | transport: newMockClient(nil, errorMock(http.StatusInternalServerError, "Server error")), 19 | } 20 | 21 | _, err := client.SwarmInspect(context.Background()) 22 | if err == nil || err.Error() != "Error response from daemon: Server error" { 23 | t.Fatalf("expected a Server Error, got %v", err) 24 | } 25 | } 26 | 27 | func TestSwarmInspect(t *testing.T) { 28 | expectedURL := "/swarm" 29 | client := &Client{ 30 | transport: newMockClient(nil, func(req *http.Request) (*http.Response, error) { 31 | if !strings.HasPrefix(req.URL.Path, expectedURL) { 32 | return nil, fmt.Errorf("Expected URL '%s', got '%s'", expectedURL, req.URL) 33 | } 34 | content, err := json.Marshal(swarm.Swarm{ 35 | ClusterInfo: swarm.ClusterInfo{ 36 | ID: "swarm_id", 37 | }, 38 | }) 39 | if err != nil { 40 | return nil, err 41 | } 42 | return &http.Response{ 43 | StatusCode: http.StatusOK, 44 | Body: ioutil.NopCloser(bytes.NewReader(content)), 45 | }, nil 46 | }), 47 | } 48 | 49 | swarmInspect, err := client.SwarmInspect(context.Background()) 50 | if err != nil { 51 | t.Fatal(err) 52 | } 53 | if swarmInspect.ID != "swarm_id" { 54 | t.Fatalf("expected `swarm_id`, got %s", swarmInspect.ID) 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /client/swarm_join.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "github.com/docker/engine-api/types/swarm" 5 | "golang.org/x/net/context" 6 | ) 7 | 8 | // SwarmJoin joins the Swarm. 9 | func (cli *Client) SwarmJoin(ctx context.Context, req swarm.JoinRequest) error { 10 | resp, err := cli.post(ctx, "/swarm/join", nil, req, nil) 11 | ensureReaderClosed(resp) 12 | return err 13 | } 14 | -------------------------------------------------------------------------------- /client/swarm_join_test.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "io/ioutil" 7 | "net/http" 8 | "strings" 9 | "testing" 10 | 11 | "golang.org/x/net/context" 12 | 13 | "github.com/docker/engine-api/types/swarm" 14 | ) 15 | 16 | func TestSwarmJoinError(t *testing.T) { 17 | client := &Client{ 18 | transport: newMockClient(nil, errorMock(http.StatusInternalServerError, "Server error")), 19 | } 20 | 21 | err := client.SwarmJoin(context.Background(), swarm.JoinRequest{}) 22 | if err == nil || err.Error() != "Error response from daemon: Server error" { 23 | t.Fatalf("expected a Server Error, got %v", err) 24 | } 25 | } 26 | 27 | func TestSwarmJoin(t *testing.T) { 28 | expectedURL := "/swarm/join" 29 | 30 | client := &Client{ 31 | transport: newMockClient(nil, func(req *http.Request) (*http.Response, error) { 32 | if !strings.HasPrefix(req.URL.Path, expectedURL) { 33 | return nil, fmt.Errorf("Expected URL '%s', got '%s'", expectedURL, req.URL) 34 | } 35 | if req.Method != "POST" { 36 | return nil, fmt.Errorf("expected POST method, got %s", req.Method) 37 | } 38 | return &http.Response{ 39 | StatusCode: http.StatusOK, 40 | Body: ioutil.NopCloser(bytes.NewReader([]byte(""))), 41 | }, nil 42 | }), 43 | } 44 | 45 | err := client.SwarmJoin(context.Background(), swarm.JoinRequest{ 46 | ListenAddr: "0.0.0.0:2377", 47 | }) 48 | if err != nil { 49 | t.Fatal(err) 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /client/swarm_leave.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "net/url" 5 | 6 | "golang.org/x/net/context" 7 | ) 8 | 9 | // SwarmLeave leaves the Swarm. 10 | func (cli *Client) SwarmLeave(ctx context.Context, force bool) error { 11 | query := url.Values{} 12 | if force { 13 | query.Set("force", "1") 14 | } 15 | resp, err := cli.post(ctx, "/swarm/leave", query, nil, nil) 16 | ensureReaderClosed(resp) 17 | return err 18 | } 19 | -------------------------------------------------------------------------------- /client/swarm_leave_test.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "io/ioutil" 7 | "net/http" 8 | "strings" 9 | "testing" 10 | 11 | "golang.org/x/net/context" 12 | ) 13 | 14 | func TestSwarmLeaveError(t *testing.T) { 15 | client := &Client{ 16 | transport: newMockClient(nil, errorMock(http.StatusInternalServerError, "Server error")), 17 | } 18 | 19 | err := client.SwarmLeave(context.Background(), false) 20 | if err == nil || err.Error() != "Error response from daemon: Server error" { 21 | t.Fatalf("expected a Server Error, got %v", err) 22 | } 23 | } 24 | 25 | func TestSwarmLeave(t *testing.T) { 26 | expectedURL := "/swarm/leave" 27 | 28 | leaveCases := []struct { 29 | force bool 30 | expectedForce string 31 | }{ 32 | { 33 | expectedForce: "", 34 | }, 35 | { 36 | force: true, 37 | expectedForce: "1", 38 | }, 39 | } 40 | 41 | for _, leaveCase := range leaveCases { 42 | client := &Client{ 43 | transport: newMockClient(nil, func(req *http.Request) (*http.Response, error) { 44 | if !strings.HasPrefix(req.URL.Path, expectedURL) { 45 | return nil, fmt.Errorf("Expected URL '%s', got '%s'", expectedURL, req.URL) 46 | } 47 | if req.Method != "POST" { 48 | return nil, fmt.Errorf("expected POST method, got %s", req.Method) 49 | } 50 | force := req.URL.Query().Get("force") 51 | if force != leaveCase.expectedForce { 52 | return nil, fmt.Errorf("force not set in URL query properly. expected '%s', got %s", leaveCase.expectedForce, force) 53 | } 54 | return &http.Response{ 55 | StatusCode: http.StatusOK, 56 | Body: ioutil.NopCloser(bytes.NewReader([]byte(""))), 57 | }, nil 58 | }), 59 | } 60 | 61 | err := client.SwarmLeave(context.Background(), leaveCase.force) 62 | if err != nil { 63 | t.Fatal(err) 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /client/swarm_update.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "fmt" 5 | "net/url" 6 | "strconv" 7 | 8 | "github.com/docker/engine-api/types/swarm" 9 | "golang.org/x/net/context" 10 | ) 11 | 12 | // SwarmUpdate updates the Swarm. 13 | func (cli *Client) SwarmUpdate(ctx context.Context, version swarm.Version, swarm swarm.Spec, flags swarm.UpdateFlags) error { 14 | query := url.Values{} 15 | query.Set("version", strconv.FormatUint(version.Index, 10)) 16 | query.Set("rotateWorkerToken", fmt.Sprintf("%v", flags.RotateWorkerToken)) 17 | query.Set("rotateManagerToken", fmt.Sprintf("%v", flags.RotateManagerToken)) 18 | resp, err := cli.post(ctx, "/swarm/update", query, swarm, nil) 19 | ensureReaderClosed(resp) 20 | return err 21 | } 22 | -------------------------------------------------------------------------------- /client/swarm_update_test.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "io/ioutil" 7 | "net/http" 8 | "strings" 9 | "testing" 10 | 11 | "golang.org/x/net/context" 12 | 13 | "github.com/docker/engine-api/types/swarm" 14 | ) 15 | 16 | func TestSwarmUpdateError(t *testing.T) { 17 | client := &Client{ 18 | transport: newMockClient(nil, errorMock(http.StatusInternalServerError, "Server error")), 19 | } 20 | 21 | err := client.SwarmUpdate(context.Background(), swarm.Version{}, swarm.Spec{}, swarm.UpdateFlags{}) 22 | if err == nil || err.Error() != "Error response from daemon: Server error" { 23 | t.Fatalf("expected a Server Error, got %v", err) 24 | } 25 | } 26 | 27 | func TestSwarmUpdate(t *testing.T) { 28 | expectedURL := "/swarm/update" 29 | 30 | client := &Client{ 31 | transport: newMockClient(nil, func(req *http.Request) (*http.Response, error) { 32 | if !strings.HasPrefix(req.URL.Path, expectedURL) { 33 | return nil, fmt.Errorf("Expected URL '%s', got '%s'", expectedURL, req.URL) 34 | } 35 | if req.Method != "POST" { 36 | return nil, fmt.Errorf("expected POST method, got %s", req.Method) 37 | } 38 | return &http.Response{ 39 | StatusCode: http.StatusOK, 40 | Body: ioutil.NopCloser(bytes.NewReader([]byte(""))), 41 | }, nil 42 | }), 43 | } 44 | 45 | err := client.SwarmUpdate(context.Background(), swarm.Version{}, swarm.Spec{}, swarm.UpdateFlags{}) 46 | if err != nil { 47 | t.Fatal(err) 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /client/task_inspect.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "io/ioutil" 7 | "net/http" 8 | 9 | "github.com/docker/engine-api/types/swarm" 10 | 11 | "golang.org/x/net/context" 12 | ) 13 | 14 | // TaskInspectWithRaw returns the task information and its raw representation.. 15 | func (cli *Client) TaskInspectWithRaw(ctx context.Context, taskID string) (swarm.Task, []byte, error) { 16 | serverResp, err := cli.get(ctx, "/tasks/"+taskID, nil, nil) 17 | if err != nil { 18 | if serverResp.statusCode == http.StatusNotFound { 19 | return swarm.Task{}, nil, taskNotFoundError{taskID} 20 | } 21 | return swarm.Task{}, nil, err 22 | } 23 | defer ensureReaderClosed(serverResp) 24 | 25 | body, err := ioutil.ReadAll(serverResp.body) 26 | if err != nil { 27 | return swarm.Task{}, nil, err 28 | } 29 | 30 | var response swarm.Task 31 | rdr := bytes.NewReader(body) 32 | err = json.NewDecoder(rdr).Decode(&response) 33 | return response, body, err 34 | } 35 | -------------------------------------------------------------------------------- /client/task_inspect_test.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "fmt" 7 | "io/ioutil" 8 | "net/http" 9 | "strings" 10 | "testing" 11 | 12 | "github.com/docker/engine-api/types/swarm" 13 | "golang.org/x/net/context" 14 | ) 15 | 16 | func TestTaskInspectError(t *testing.T) { 17 | client := &Client{ 18 | transport: newMockClient(nil, errorMock(http.StatusInternalServerError, "Server error")), 19 | } 20 | 21 | _, _, err := client.TaskInspectWithRaw(context.Background(), "nothing") 22 | if err == nil || err.Error() != "Error response from daemon: Server error" { 23 | t.Fatalf("expected a Server Error, got %v", err) 24 | } 25 | } 26 | 27 | func TestTaskInspect(t *testing.T) { 28 | expectedURL := "/tasks/task_id" 29 | client := &Client{ 30 | transport: newMockClient(nil, func(req *http.Request) (*http.Response, error) { 31 | if !strings.HasPrefix(req.URL.Path, expectedURL) { 32 | return nil, fmt.Errorf("Expected URL '%s', got '%s'", expectedURL, req.URL) 33 | } 34 | content, err := json.Marshal(swarm.Task{ 35 | ID: "task_id", 36 | }) 37 | if err != nil { 38 | return nil, err 39 | } 40 | return &http.Response{ 41 | StatusCode: http.StatusOK, 42 | Body: ioutil.NopCloser(bytes.NewReader(content)), 43 | }, nil 44 | }), 45 | } 46 | 47 | taskInspect, _, err := client.TaskInspectWithRaw(context.Background(), "task_id") 48 | if err != nil { 49 | t.Fatal(err) 50 | } 51 | if taskInspect.ID != "task_id" { 52 | t.Fatalf("expected `task_id`, got %s", taskInspect.ID) 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /client/task_list.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "encoding/json" 5 | "net/url" 6 | 7 | "github.com/docker/engine-api/types" 8 | "github.com/docker/engine-api/types/filters" 9 | "github.com/docker/engine-api/types/swarm" 10 | "golang.org/x/net/context" 11 | ) 12 | 13 | // TaskList returns the list of tasks. 14 | func (cli *Client) TaskList(ctx context.Context, options types.TaskListOptions) ([]swarm.Task, error) { 15 | query := url.Values{} 16 | 17 | if options.Filter.Len() > 0 { 18 | filterJSON, err := filters.ToParam(options.Filter) 19 | if err != nil { 20 | return nil, err 21 | } 22 | 23 | query.Set("filters", filterJSON) 24 | } 25 | 26 | resp, err := cli.get(ctx, "/tasks", query, nil) 27 | if err != nil { 28 | return nil, err 29 | } 30 | 31 | var tasks []swarm.Task 32 | err = json.NewDecoder(resp.body).Decode(&tasks) 33 | ensureReaderClosed(resp) 34 | return tasks, err 35 | } 36 | -------------------------------------------------------------------------------- /client/task_list_test.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "fmt" 7 | "io/ioutil" 8 | "net/http" 9 | "strings" 10 | "testing" 11 | 12 | "github.com/docker/engine-api/types" 13 | "github.com/docker/engine-api/types/filters" 14 | "github.com/docker/engine-api/types/swarm" 15 | "golang.org/x/net/context" 16 | ) 17 | 18 | func TestTaskListError(t *testing.T) { 19 | client := &Client{ 20 | transport: newMockClient(nil, errorMock(http.StatusInternalServerError, "Server error")), 21 | } 22 | 23 | _, err := client.TaskList(context.Background(), types.TaskListOptions{}) 24 | if err == nil || err.Error() != "Error response from daemon: Server error" { 25 | t.Fatalf("expected a Server Error, got %v", err) 26 | } 27 | } 28 | 29 | func TestTaskList(t *testing.T) { 30 | expectedURL := "/tasks" 31 | 32 | filters := filters.NewArgs() 33 | filters.Add("label", "label1") 34 | filters.Add("label", "label2") 35 | 36 | listCases := []struct { 37 | options types.TaskListOptions 38 | expectedQueryParams map[string]string 39 | }{ 40 | { 41 | options: types.TaskListOptions{}, 42 | expectedQueryParams: map[string]string{ 43 | "filters": "", 44 | }, 45 | }, 46 | { 47 | options: types.TaskListOptions{ 48 | Filter: filters, 49 | }, 50 | expectedQueryParams: map[string]string{ 51 | "filters": `{"label":{"label1":true,"label2":true}}`, 52 | }, 53 | }, 54 | } 55 | for _, listCase := range listCases { 56 | client := &Client{ 57 | transport: newMockClient(nil, func(req *http.Request) (*http.Response, error) { 58 | if !strings.HasPrefix(req.URL.Path, expectedURL) { 59 | return nil, fmt.Errorf("Expected URL '%s', got '%s'", expectedURL, req.URL) 60 | } 61 | query := req.URL.Query() 62 | for key, expected := range listCase.expectedQueryParams { 63 | actual := query.Get(key) 64 | if actual != expected { 65 | return nil, fmt.Errorf("%s not set in URL query properly. Expected '%s', got %s", key, expected, actual) 66 | } 67 | } 68 | content, err := json.Marshal([]swarm.Task{ 69 | { 70 | ID: "task_id1", 71 | }, 72 | { 73 | ID: "task_id2", 74 | }, 75 | }) 76 | if err != nil { 77 | return nil, err 78 | } 79 | return &http.Response{ 80 | StatusCode: http.StatusOK, 81 | Body: ioutil.NopCloser(bytes.NewReader(content)), 82 | }, nil 83 | }), 84 | } 85 | 86 | tasks, err := client.TaskList(context.Background(), listCase.options) 87 | if err != nil { 88 | t.Fatal(err) 89 | } 90 | if len(tasks) != 2 { 91 | t.Fatalf("expected 2 tasks, got %v", tasks) 92 | } 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /client/testdata/ca.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIC0jCCAbqgAwIBAgIRAILlP5WWLaHkQ/m2ASHP7SowDQYJKoZIhvcNAQELBQAw 3 | EjEQMA4GA1UEChMHdmluY2VudDAeFw0xNjAzMjQxMDE5MDBaFw0xOTAzMDkxMDE5 4 | MDBaMBIxEDAOBgNVBAoTB3ZpbmNlbnQwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw 5 | ggEKAoIBAQD0yZPKAGncoaxaU/QW9tWEHbrvDoGVF/65L8Si/jBrlAgLjhmmV1di 6 | vKG9QPzuU8snxHro3/uCwyA6kTqw0U8bGwHxJq2Bpa6JBYj8N2jMJ+M+sjXgSo2t 7 | E0zIzjTW2Pir3C8qwfrVL6NFp9xClwMD23SFZ0UsEH36NkfyrKBVeM8IOjJd4Wjs 8 | xIcuvF3BTVkji84IJBW2JIKf9ZrzJwUlSCPgptRp4Evdbyp5d+UPxtwxD7qjW4lM 9 | yQQ8vfcC4lKkVx5s/RNJ4fzd5uEgLdEbZ20qt7Zt/bLcxFHpUhH2teA0QjmrOWFh 10 | gbL83s95/+hbSVhsO4hoFW7vTeiCCY4xAgMBAAGjIzAhMA4GA1UdDwEB/wQEAwIC 11 | rDAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQBY51RHajuDuhO2 12 | tcm26jeNROzfffnjhvbOVPjSEdo9vI3JpMU/RuQw+nbNcLwJrdjL6UH7tD/36Y+q 13 | NXH+xSIjWFH0zXGxrIUsVrvt6f8CbOvw7vD+gygOG+849PDQMbL6czP8rvXY7vZV 14 | 9pdpQfrENk4b5kePRW/6HaGSTvtgN7XOrYD9fp3pm/G534T2e3IxgYMRNwdB9Ul9 15 | bLwMqQqf4eiqqMs6x4IVmZUkGVMKiFKcvkNg9a+Ozx5pMizHeAezWMcZ5V+QJZVT 16 | 8lElSCKZ2Yy2xkcl7aeQMLwcAeZwfTp+Yu9dVzlqXiiBTLd1+LtAQCuKHzmw4Q8k 17 | EvD5m49l 18 | -----END CERTIFICATE----- 19 | -------------------------------------------------------------------------------- /client/testdata/cert.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIC8DCCAdigAwIBAgIRAJAS1glgcke4q7eCaretwgUwDQYJKoZIhvcNAQELBQAw 3 | EjEQMA4GA1UEChMHdmluY2VudDAeFw0xNjAzMjQxMDE5MDBaFw0xOTAzMDkxMDE5 4 | MDBaMB4xHDAaBgNVBAoME3ZpbmNlbnQuPGJvb3RzdHJhcD4wggEiMA0GCSqGSIb3 5 | DQEBAQUAA4IBDwAwggEKAoIBAQClpvG442dGEvrRgmCrqY4kBml1LVlw2Y7ZDn6B 6 | TKa52+MuGDmfXbO1UhclNqTXjLgAwKjPz/OvnPRxNEUoQEDbBd+Xev7rxTY5TvYI 7 | 27YH3fMH2LL2j62jum649abfhZ6ekD5eD8tCn3mnrEOgqRIlK7efPIVixq/ZqU1H 8 | 7ez0ggB7dmWHlhnUaxyQOCSnAX/7nKYQXqZgVvGhDeR2jp7GcnhbK/qPrZ/mOm83 9 | 2IjCeYN145opYlzTSp64GYIZz7uqMNcnDKK37ZbS8MYcTjrRaHEiqZVVdIC+ghbx 10 | qYqzbZRVfgztI9jwmifn0mYrN4yt+nhNYwBcRJ4Pv3uLFbo7AgMBAAGjNTAzMA4G 11 | A1UdDwEB/wQEAwIHgDATBgNVHSUEDDAKBggrBgEFBQcDAjAMBgNVHRMBAf8EAjAA 12 | MA0GCSqGSIb3DQEBCwUAA4IBAQDg1r7nksjYgDFYEcBbrRrRHddIoK+RVmSBTTrq 13 | 8giC77m0srKdh9XTVWK1PUbGfODV1oD8m9QhPE8zPDyYQ8jeXNRSU5wXdkrTRmmY 14 | w/T3SREqmE7CObMtusokHidjYFuqqCR07sJzqBKRlzr3o0EGe3tuEhUlF5ARY028 15 | eipaDcVlT5ChGcDa6LeJ4e05u4cVap0dd6Rp1w3Rx1AYAecdgtgBMnw1iWdl/nrC 16 | sp26ZXNaAhFOUovlY9VY257AMd9hQV7WvAK4yNEHcckVu3uXTBmDgNSOPtl0QLsL 17 | Kjlj75ksCx8nCln/hCut/0+kGTsGZqdV5c6ktgcGYRir/5Hs 18 | -----END CERTIFICATE----- 19 | -------------------------------------------------------------------------------- /client/testdata/key.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIIEowIBAAKCAQEApabxuONnRhL60YJgq6mOJAZpdS1ZcNmO2Q5+gUymudvjLhg5 3 | n12ztVIXJTak14y4AMCoz8/zr5z0cTRFKEBA2wXfl3r+68U2OU72CNu2B93zB9iy 4 | 9o+to7puuPWm34WenpA+Xg/LQp95p6xDoKkSJSu3nzyFYsav2alNR+3s9IIAe3Zl 5 | h5YZ1GsckDgkpwF/+5ymEF6mYFbxoQ3kdo6exnJ4Wyv6j62f5jpvN9iIwnmDdeOa 6 | KWJc00qeuBmCGc+7qjDXJwyit+2W0vDGHE460WhxIqmVVXSAvoIW8amKs22UVX4M 7 | 7SPY8Jon59JmKzeMrfp4TWMAXESeD797ixW6OwIDAQABAoIBAHfyAAleL8NfrtnR 8 | S+pApbmUIvxD0AWUooispBE/zWG6xC72P5MTqDJctIGvpYCmVf3Fgvamns7EGYN2 9 | 07Sngc6V3Ca1WqyhaffpIuGbJZ1gqr89u6gotRRexBmNVj13ZTlvPJmjWgxtqQsu 10 | AvHsOkVL+HOGwRaaw24Z1umEcBVCepl7PGTqsLeJUtBUZBiqdJTu4JYLAB6BggBI 11 | OxhHoTWvlNWwzezo2C/IXkXcXD/tp3i5vTn5rAXHSMQkdMAUh7/xJ73Fl36gxZhp 12 | W7NoPKaS9qNh8jhs6p54S7tInb6+mrKtvRFKl5XAR3istXrXteT5UaukpuBbQ/5d 13 | qf4BXuECgYEAzoOKxMee5tG/G9iC6ImNq5xGAZm0OnmteNgIEQj49If1Q68av525 14 | FioqdC9zV+blfHQqXEIUeum4JAou4xqmB8Lw2H0lYwOJ1IkpUy3QJjU1IrI+U5Qy 15 | ryZuA9cxSTLf1AJFbROsoZDpjaBh0uUQkD/4PHpwXMgHu/3CaJ4nTEkCgYEAzVjE 16 | VWgczWJGyRxmHSeR51ft1jrlChZHEd3HwgLfo854JIj+MGUH4KPLSMIkYNuyiwNQ 17 | W7zdXCB47U8afSL/lPTv1M5+ZsWY6sZAT6gtp/IeU0Va943h9cj10fAOBJaz1H6M 18 | jnZS4jjWhVInE7wpCDVCwDRoHHJ84kb6JeflamMCgYBDQDcKie9HP3q6uLE4xMKr 19 | 5gIuNz2n5UQGnGNUGNXp2/SVDArr55MEksqsd19aesi01KeOz74XoNDke6R1NJJo 20 | 6KTB+08XhWl3GwuoGL02FBGvsNf3I8W1oBAnlAZqzfRx+CNfuA55ttU318jDgvD3 21 | 6L0QBNdef411PNf4dbhacQKBgAd/e0PHFm4lbYJAaDYeUMSKwGN3KQ/SOmwblgSu 22 | iC36BwcGfYmU1tHMCUsx05Q50W4kA9Ylskt/4AqCPexdz8lHnE4/7/uesXO5I3YF 23 | JQ2h2Jufx6+MXbjUyq0Mv+ZI/m3+5PD6vxIFk0ew9T5SO4lSMIrGHxsSzx6QCuhB 24 | bG4TAoGBAJ5PWG7d2CyCjLtfF8J4NxykRvIQ8l/3kDvDdNrXiXbgonojo2lgRYaM 25 | 5LoK9ApN8KHdedpTRipBaDA22Sp5SjMcUE7A6q42PJCL9r+BRYF0foFQx/rqpCff 26 | pVWKgwIPoKnfxDqN1RUgyFcx1jbA3XVJZCuT+wbMuDQ9nlvulD1W 27 | -----END RSA PRIVATE KEY----- 28 | -------------------------------------------------------------------------------- /client/transport/cancellable/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2009 The Go Authors. All rights reserved. 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions are 5 | met: 6 | 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above 10 | copyright notice, this list of conditions and the following disclaimer 11 | in the documentation and/or other materials provided with the 12 | distribution. 13 | * Neither the name of Google Inc. nor the names of its 14 | contributors may be used to endorse or promote products derived from 15 | this software without specific prior written permission. 16 | 17 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 18 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 19 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 20 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 21 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 22 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 23 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 24 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 25 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /client/transport/cancellable/canceler.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // +build go1.5 6 | 7 | package cancellable 8 | 9 | import ( 10 | "net/http" 11 | 12 | "github.com/docker/engine-api/client/transport" 13 | ) 14 | 15 | func canceler(client transport.Sender, req *http.Request) func() { 16 | // TODO(djd): Respect any existing value of req.Cancel. 17 | ch := make(chan struct{}) 18 | req.Cancel = ch 19 | 20 | return func() { 21 | close(ch) 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /client/transport/cancellable/canceler_go14.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // +build !go1.5 6 | 7 | package cancellable 8 | 9 | import ( 10 | "net/http" 11 | 12 | "github.com/docker/engine-api/client/transport" 13 | ) 14 | 15 | type requestCanceler interface { 16 | CancelRequest(*http.Request) 17 | } 18 | 19 | func canceler(client transport.Sender, req *http.Request) func() { 20 | rc, ok := client.(requestCanceler) 21 | if !ok { 22 | return func() {} 23 | } 24 | return func() { 25 | rc.CancelRequest(req) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /client/transport/client.go: -------------------------------------------------------------------------------- 1 | package transport 2 | 3 | import ( 4 | "crypto/tls" 5 | "net/http" 6 | ) 7 | 8 | // Sender is an interface that clients must implement 9 | // to be able to send requests to a remote connection. 10 | type Sender interface { 11 | // Do sends request to a remote endpoint. 12 | Do(*http.Request) (*http.Response, error) 13 | } 14 | 15 | // Client is an interface that abstracts all remote connections. 16 | type Client interface { 17 | Sender 18 | // Secure tells whether the connection is secure or not. 19 | Secure() bool 20 | // Scheme returns the connection protocol the client uses. 21 | Scheme() string 22 | // TLSConfig returns any TLS configuration the client uses. 23 | TLSConfig() *tls.Config 24 | } 25 | 26 | // tlsInfo returns information about the TLS configuration. 27 | type tlsInfo struct { 28 | tlsConfig *tls.Config 29 | } 30 | 31 | // TLSConfig returns the TLS configuration. 32 | func (t *tlsInfo) TLSConfig() *tls.Config { 33 | return t.tlsConfig 34 | } 35 | 36 | // Scheme returns protocol scheme to use. 37 | func (t *tlsInfo) Scheme() string { 38 | if t.tlsConfig != nil { 39 | return "https" 40 | } 41 | return "http" 42 | } 43 | 44 | // Secure returns true if there is a TLS configuration. 45 | func (t *tlsInfo) Secure() bool { 46 | return t.tlsConfig != nil 47 | } 48 | -------------------------------------------------------------------------------- /client/transport/tlsconfig_clone.go: -------------------------------------------------------------------------------- 1 | // +build !go1.7 2 | 3 | package transport 4 | 5 | import "crypto/tls" 6 | 7 | // TLSConfigClone returns a clone of tls.Config. This function is provided for 8 | // compatibility for go1.7 that doesn't include this method in stdlib. 9 | func TLSConfigClone(c *tls.Config) *tls.Config { 10 | return c.Clone() 11 | } 12 | -------------------------------------------------------------------------------- /client/transport/tlsconfig_clone_go17.go: -------------------------------------------------------------------------------- 1 | // +build go1.7 2 | 3 | package transport 4 | 5 | import "crypto/tls" 6 | 7 | // TLSConfigClone returns a clone of tls.Config. This function is provided for 8 | // compatibility for go1.7 that doesn't include this method in stdlib. 9 | func TLSConfigClone(c *tls.Config) *tls.Config { 10 | return &tls.Config{ 11 | Rand: c.Rand, 12 | Time: c.Time, 13 | Certificates: c.Certificates, 14 | NameToCertificate: c.NameToCertificate, 15 | GetCertificate: c.GetCertificate, 16 | RootCAs: c.RootCAs, 17 | NextProtos: c.NextProtos, 18 | ServerName: c.ServerName, 19 | ClientAuth: c.ClientAuth, 20 | ClientCAs: c.ClientCAs, 21 | InsecureSkipVerify: c.InsecureSkipVerify, 22 | CipherSuites: c.CipherSuites, 23 | PreferServerCipherSuites: c.PreferServerCipherSuites, 24 | SessionTicketsDisabled: c.SessionTicketsDisabled, 25 | SessionTicketKey: c.SessionTicketKey, 26 | ClientSessionCache: c.ClientSessionCache, 27 | MinVersion: c.MinVersion, 28 | MaxVersion: c.MaxVersion, 29 | CurvePreferences: c.CurvePreferences, 30 | DynamicRecordSizingDisabled: c.DynamicRecordSizingDisabled, 31 | Renegotiation: c.Renegotiation, 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /client/transport/transport.go: -------------------------------------------------------------------------------- 1 | // Package transport provides function to send request to remote endpoints. 2 | package transport 3 | 4 | import ( 5 | "fmt" 6 | "net/http" 7 | 8 | "github.com/docker/go-connections/sockets" 9 | ) 10 | 11 | // apiTransport holds information about the http transport to connect with the API. 12 | type apiTransport struct { 13 | *http.Client 14 | *tlsInfo 15 | transport *http.Transport 16 | } 17 | 18 | // NewTransportWithHTTP creates a new transport based on the provided proto, address and http client. 19 | // It uses Docker's default http transport configuration if the client is nil. 20 | // It does not modify the client's transport if it's not nil. 21 | func NewTransportWithHTTP(proto, addr string, client *http.Client) (Client, error) { 22 | var transport *http.Transport 23 | 24 | if client != nil { 25 | tr, ok := client.Transport.(*http.Transport) 26 | if !ok { 27 | return nil, fmt.Errorf("unable to verify TLS configuration, invalid transport %v", client.Transport) 28 | } 29 | transport = tr 30 | } else { 31 | transport = defaultTransport(proto, addr) 32 | client = &http.Client{ 33 | Transport: transport, 34 | } 35 | } 36 | 37 | return &apiTransport{ 38 | Client: client, 39 | tlsInfo: &tlsInfo{transport.TLSClientConfig}, 40 | transport: transport, 41 | }, nil 42 | } 43 | 44 | // CancelRequest stops a request execution. 45 | func (a *apiTransport) CancelRequest(req *http.Request) { 46 | a.transport.CancelRequest(req) 47 | } 48 | 49 | // defaultTransport creates a new http.Transport with Docker's 50 | // default transport configuration. 51 | func defaultTransport(proto, addr string) *http.Transport { 52 | tr := new(http.Transport) 53 | sockets.ConfigureTransport(tr, proto, addr) 54 | return tr 55 | } 56 | 57 | var _ Client = &apiTransport{} 58 | -------------------------------------------------------------------------------- /client/version.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "encoding/json" 5 | 6 | "github.com/docker/engine-api/types" 7 | "golang.org/x/net/context" 8 | ) 9 | 10 | // ServerVersion returns information of the docker client and server host. 11 | func (cli *Client) ServerVersion(ctx context.Context) (types.Version, error) { 12 | resp, err := cli.get(ctx, "/version", nil, nil) 13 | if err != nil { 14 | return types.Version{}, err 15 | } 16 | 17 | var server types.Version 18 | err = json.NewDecoder(resp.body).Decode(&server) 19 | ensureReaderClosed(resp) 20 | return server, err 21 | } 22 | -------------------------------------------------------------------------------- /client/volume_create.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "encoding/json" 5 | 6 | "github.com/docker/engine-api/types" 7 | "golang.org/x/net/context" 8 | ) 9 | 10 | // VolumeCreate creates a volume in the docker host. 11 | func (cli *Client) VolumeCreate(ctx context.Context, options types.VolumeCreateRequest) (types.Volume, error) { 12 | var volume types.Volume 13 | resp, err := cli.post(ctx, "/volumes/create", nil, options, nil) 14 | if err != nil { 15 | return volume, err 16 | } 17 | err = json.NewDecoder(resp.body).Decode(&volume) 18 | ensureReaderClosed(resp) 19 | return volume, err 20 | } 21 | -------------------------------------------------------------------------------- /client/volume_create_test.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "fmt" 7 | "io/ioutil" 8 | "net/http" 9 | "strings" 10 | "testing" 11 | 12 | "github.com/docker/engine-api/types" 13 | "golang.org/x/net/context" 14 | ) 15 | 16 | func TestVolumeCreateError(t *testing.T) { 17 | client := &Client{ 18 | transport: newMockClient(nil, errorMock(http.StatusInternalServerError, "Server error")), 19 | } 20 | 21 | _, err := client.VolumeCreate(context.Background(), types.VolumeCreateRequest{}) 22 | if err == nil || err.Error() != "Error response from daemon: Server error" { 23 | t.Fatalf("expected a Server Error, got %v", err) 24 | } 25 | } 26 | 27 | func TestVolumeCreate(t *testing.T) { 28 | expectedURL := "/volumes/create" 29 | 30 | client := &Client{ 31 | transport: newMockClient(nil, func(req *http.Request) (*http.Response, error) { 32 | if !strings.HasPrefix(req.URL.Path, expectedURL) { 33 | return nil, fmt.Errorf("Expected URL '%s', got '%s'", expectedURL, req.URL) 34 | } 35 | 36 | if req.Method != "POST" { 37 | return nil, fmt.Errorf("expected POST method, got %s", req.Method) 38 | } 39 | 40 | content, err := json.Marshal(types.Volume{ 41 | Name: "volume", 42 | Driver: "local", 43 | Mountpoint: "mountpoint", 44 | }) 45 | if err != nil { 46 | return nil, err 47 | } 48 | return &http.Response{ 49 | StatusCode: http.StatusOK, 50 | Body: ioutil.NopCloser(bytes.NewReader(content)), 51 | }, nil 52 | }), 53 | } 54 | 55 | volume, err := client.VolumeCreate(context.Background(), types.VolumeCreateRequest{ 56 | Name: "myvolume", 57 | Driver: "mydriver", 58 | DriverOpts: map[string]string{ 59 | "opt-key": "opt-value", 60 | }, 61 | }) 62 | if err != nil { 63 | t.Fatal(err) 64 | } 65 | if volume.Name != "volume" { 66 | t.Fatalf("expected volume.Name to be 'volume', got %s", volume.Name) 67 | } 68 | if volume.Driver != "local" { 69 | t.Fatalf("expected volume.Driver to be 'local', got %s", volume.Driver) 70 | } 71 | if volume.Mountpoint != "mountpoint" { 72 | t.Fatalf("expected volume.Mountpoint to be 'mountpoint', got %s", volume.Mountpoint) 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /client/volume_inspect.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "io/ioutil" 7 | "net/http" 8 | 9 | "github.com/docker/engine-api/types" 10 | "golang.org/x/net/context" 11 | ) 12 | 13 | // VolumeInspect returns the information about a specific volume in the docker host. 14 | func (cli *Client) VolumeInspect(ctx context.Context, volumeID string) (types.Volume, error) { 15 | volume, _, err := cli.VolumeInspectWithRaw(ctx, volumeID) 16 | return volume, err 17 | } 18 | 19 | // VolumeInspectWithRaw returns the information about a specific volume in the docker host and its raw representation 20 | func (cli *Client) VolumeInspectWithRaw(ctx context.Context, volumeID string) (types.Volume, []byte, error) { 21 | var volume types.Volume 22 | resp, err := cli.get(ctx, "/volumes/"+volumeID, nil, nil) 23 | if err != nil { 24 | if resp.statusCode == http.StatusNotFound { 25 | return volume, nil, volumeNotFoundError{volumeID} 26 | } 27 | return volume, nil, err 28 | } 29 | defer ensureReaderClosed(resp) 30 | 31 | body, err := ioutil.ReadAll(resp.body) 32 | if err != nil { 33 | return volume, nil, err 34 | } 35 | rdr := bytes.NewReader(body) 36 | err = json.NewDecoder(rdr).Decode(&volume) 37 | return volume, body, err 38 | } 39 | -------------------------------------------------------------------------------- /client/volume_inspect_test.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "fmt" 7 | "io/ioutil" 8 | "net/http" 9 | "strings" 10 | "testing" 11 | 12 | "github.com/docker/engine-api/types" 13 | "golang.org/x/net/context" 14 | ) 15 | 16 | func TestVolumeInspectError(t *testing.T) { 17 | client := &Client{ 18 | transport: newMockClient(nil, errorMock(http.StatusInternalServerError, "Server error")), 19 | } 20 | 21 | _, err := client.VolumeInspect(context.Background(), "nothing") 22 | if err == nil || err.Error() != "Error response from daemon: Server error" { 23 | t.Fatalf("expected a Server Error, got %v", err) 24 | } 25 | } 26 | 27 | func TestVolumeInspectNotFound(t *testing.T) { 28 | client := &Client{ 29 | transport: newMockClient(nil, errorMock(http.StatusNotFound, "Server error")), 30 | } 31 | 32 | _, err := client.VolumeInspect(context.Background(), "unknown") 33 | if err == nil || !IsErrVolumeNotFound(err) { 34 | t.Fatalf("expected a volumeNotFound error, got %v", err) 35 | } 36 | } 37 | 38 | func TestVolumeInspect(t *testing.T) { 39 | expectedURL := "/volumes/volume_id" 40 | client := &Client{ 41 | transport: newMockClient(nil, func(req *http.Request) (*http.Response, error) { 42 | if !strings.HasPrefix(req.URL.Path, expectedURL) { 43 | return nil, fmt.Errorf("Expected URL '%s', got '%s'", expectedURL, req.URL) 44 | } 45 | if req.Method != "GET" { 46 | return nil, fmt.Errorf("expected GET method, got %s", req.Method) 47 | } 48 | content, err := json.Marshal(types.Volume{ 49 | Name: "name", 50 | Driver: "driver", 51 | Mountpoint: "mountpoint", 52 | }) 53 | if err != nil { 54 | return nil, err 55 | } 56 | return &http.Response{ 57 | StatusCode: http.StatusOK, 58 | Body: ioutil.NopCloser(bytes.NewReader(content)), 59 | }, nil 60 | }), 61 | } 62 | 63 | v, err := client.VolumeInspect(context.Background(), "volume_id") 64 | if err != nil { 65 | t.Fatal(err) 66 | } 67 | if v.Name != "name" { 68 | t.Fatalf("expected `name`, got %s", v.Name) 69 | } 70 | if v.Driver != "driver" { 71 | t.Fatalf("expected `driver`, got %s", v.Driver) 72 | } 73 | if v.Mountpoint != "mountpoint" { 74 | t.Fatalf("expected `mountpoint`, got %s", v.Mountpoint) 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /client/volume_list.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "encoding/json" 5 | "net/url" 6 | 7 | "github.com/docker/engine-api/types" 8 | "github.com/docker/engine-api/types/filters" 9 | "golang.org/x/net/context" 10 | ) 11 | 12 | // VolumeList returns the volumes configured in the docker host. 13 | func (cli *Client) VolumeList(ctx context.Context, filter filters.Args) (types.VolumesListResponse, error) { 14 | var volumes types.VolumesListResponse 15 | query := url.Values{} 16 | 17 | if filter.Len() > 0 { 18 | filterJSON, err := filters.ToParamWithVersion(cli.version, filter) 19 | if err != nil { 20 | return volumes, err 21 | } 22 | query.Set("filters", filterJSON) 23 | } 24 | resp, err := cli.get(ctx, "/volumes", query, nil) 25 | if err != nil { 26 | return volumes, err 27 | } 28 | 29 | err = json.NewDecoder(resp.body).Decode(&volumes) 30 | ensureReaderClosed(resp) 31 | return volumes, err 32 | } 33 | -------------------------------------------------------------------------------- /client/volume_remove.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "net/url" 5 | 6 | "golang.org/x/net/context" 7 | ) 8 | 9 | // VolumeRemove removes a volume from the docker host. 10 | func (cli *Client) VolumeRemove(ctx context.Context, volumeID string, force bool) error { 11 | query := url.Values{} 12 | if force { 13 | query.Set("force", "1") 14 | } 15 | resp, err := cli.delete(ctx, "/volumes/"+volumeID, query, nil) 16 | ensureReaderClosed(resp) 17 | return err 18 | } 19 | -------------------------------------------------------------------------------- /client/volume_remove_test.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "io/ioutil" 7 | "net/http" 8 | "strings" 9 | "testing" 10 | 11 | "golang.org/x/net/context" 12 | ) 13 | 14 | func TestVolumeRemoveError(t *testing.T) { 15 | client := &Client{ 16 | transport: newMockClient(nil, errorMock(http.StatusInternalServerError, "Server error")), 17 | } 18 | 19 | err := client.VolumeRemove(context.Background(), "volume_id", false) 20 | if err == nil || err.Error() != "Error response from daemon: Server error" { 21 | t.Fatalf("expected a Server Error, got %v", err) 22 | } 23 | } 24 | 25 | func TestVolumeRemove(t *testing.T) { 26 | expectedURL := "/volumes/volume_id" 27 | 28 | client := &Client{ 29 | transport: newMockClient(nil, func(req *http.Request) (*http.Response, error) { 30 | if !strings.HasPrefix(req.URL.Path, expectedURL) { 31 | return nil, fmt.Errorf("Expected URL '%s', got '%s'", expectedURL, req.URL) 32 | } 33 | if req.Method != "DELETE" { 34 | return nil, fmt.Errorf("expected DELETE method, got %s", req.Method) 35 | } 36 | return &http.Response{ 37 | StatusCode: http.StatusOK, 38 | Body: ioutil.NopCloser(bytes.NewReader([]byte("body"))), 39 | }, nil 40 | }), 41 | } 42 | 43 | err := client.VolumeRemove(context.Background(), "volume_id", false) 44 | if err != nil { 45 | t.Fatal(err) 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /doc.go: -------------------------------------------------------------------------------- 1 | /* 2 | Package engineapi provides libraries to implement client and server components compatible with the Docker engine. 3 | 4 | The client package in github.com/docker/engine-api/client implements all necessary requests to implement the official Docker engine cli. 5 | 6 | Create a new client, then use it to send and receive messages to the Docker engine API: 7 | 8 | defaultHeaders := map[string]string{"User-Agent": "engine-api-cli-1.0"} 9 | cli, err := client.NewClient("unix:///var/run/docker.sock", "v1.22", nil, defaultHeaders) 10 | 11 | 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: 12 | 13 | cli, err := client.NewEnvClient() 14 | 15 | 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: 16 | 17 | options := types.ContainerListOptions{All: true} 18 | containers, err := cli.ContainerList(context.Background(), options) 19 | 20 | */ 21 | package engineapi 22 | -------------------------------------------------------------------------------- /types/auth.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | // AuthConfig contains authorization information for connecting to a Registry 4 | type AuthConfig struct { 5 | Username string `json:"username,omitempty"` 6 | Password string `json:"password,omitempty"` 7 | Auth string `json:"auth,omitempty"` 8 | 9 | // Email is an optional value associated with the username. 10 | // This field is deprecated and will be removed in a later 11 | // version of docker. 12 | Email string `json:"email,omitempty"` 13 | 14 | ServerAddress string `json:"serveraddress,omitempty"` 15 | 16 | // IdentityToken is used to authenticate the user and get 17 | // an access token for the registry. 18 | IdentityToken string `json:"identitytoken,omitempty"` 19 | 20 | // RegistryToken is a bearer token to be sent to a registry 21 | RegistryToken string `json:"registrytoken,omitempty"` 22 | } 23 | -------------------------------------------------------------------------------- /types/blkiodev/blkio.go: -------------------------------------------------------------------------------- 1 | package blkiodev 2 | 3 | import "fmt" 4 | 5 | // WeightDevice is a structure that holds device:weight pair 6 | type WeightDevice struct { 7 | Path string 8 | Weight uint16 9 | } 10 | 11 | func (w *WeightDevice) String() string { 12 | return fmt.Sprintf("%s:%d", w.Path, w.Weight) 13 | } 14 | 15 | // ThrottleDevice is a structure that holds device:rate_per_second pair 16 | type ThrottleDevice struct { 17 | Path string 18 | Rate uint64 19 | } 20 | 21 | func (t *ThrottleDevice) String() string { 22 | return fmt.Sprintf("%s:%d", t.Path, t.Rate) 23 | } 24 | -------------------------------------------------------------------------------- /types/configs.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import ( 4 | "github.com/docker/engine-api/types/container" 5 | "github.com/docker/engine-api/types/network" 6 | ) 7 | 8 | // configs holds structs used for internal communication between the 9 | // frontend (such as an http server) and the backend (such as the 10 | // docker daemon). 11 | 12 | // ContainerCreateConfig is the parameter set to ContainerCreate() 13 | type ContainerCreateConfig struct { 14 | Name string 15 | Config *container.Config 16 | HostConfig *container.HostConfig 17 | NetworkingConfig *network.NetworkingConfig 18 | AdjustCPUShares bool 19 | } 20 | 21 | // ContainerRmConfig holds arguments for the container remove 22 | // operation. This struct is used to tell the backend what operations 23 | // to perform. 24 | type ContainerRmConfig struct { 25 | ForceRemove, RemoveVolume, RemoveLink bool 26 | } 27 | 28 | // ContainerCommitConfig contains build configs for commit operation, 29 | // and is used when making a commit with the current state of the container. 30 | type ContainerCommitConfig struct { 31 | Pause bool 32 | Repo string 33 | Tag string 34 | Author string 35 | Comment string 36 | // merge container config into commit config before commit 37 | MergeConfigs bool 38 | Config *container.Config 39 | } 40 | 41 | // ExecConfig is a small subset of the Config struct that holds the configuration 42 | // for the exec feature of docker. 43 | type ExecConfig struct { 44 | User string // User that will run the command 45 | Privileged bool // Is the container in privileged mode 46 | Tty bool // Attach standard streams to a tty. 47 | AttachStdin bool // Attach the standard input, makes possible user interaction 48 | AttachStderr bool // Attach the standard error 49 | AttachStdout bool // Attach the standard output 50 | Detach bool // Execute in detach mode 51 | DetachKeys string // Escape keys for detach 52 | Env []string // Environment variables 53 | Cmd []string // Execution commands and args 54 | } 55 | 56 | // PluginRmConfig holds arguments for the plugin remove 57 | // operation. This struct is used to tell the backend what operations 58 | // to perform. 59 | type PluginRmConfig struct { 60 | ForceRemove bool 61 | } 62 | -------------------------------------------------------------------------------- /types/container/hostconfig_unix.go: -------------------------------------------------------------------------------- 1 | // +build !windows 2 | 3 | package container 4 | 5 | import "strings" 6 | 7 | // IsValid indicates if an isolation technology is valid 8 | func (i Isolation) IsValid() bool { 9 | return i.IsDefault() 10 | } 11 | 12 | // IsPrivate indicates whether container uses it's private network stack. 13 | func (n NetworkMode) IsPrivate() bool { 14 | return !(n.IsHost() || n.IsContainer()) 15 | } 16 | 17 | // IsDefault indicates whether container uses the default network stack. 18 | func (n NetworkMode) IsDefault() bool { 19 | return n == "default" 20 | } 21 | 22 | // NetworkName returns the name of the network stack. 23 | func (n NetworkMode) NetworkName() string { 24 | if n.IsBridge() { 25 | return "bridge" 26 | } else if n.IsHost() { 27 | return "host" 28 | } else if n.IsContainer() { 29 | return "container" 30 | } else if n.IsNone() { 31 | return "none" 32 | } else if n.IsDefault() { 33 | return "default" 34 | } else if n.IsUserDefined() { 35 | return n.UserDefined() 36 | } 37 | return "" 38 | } 39 | 40 | // IsBridge indicates whether container uses the bridge network stack 41 | func (n NetworkMode) IsBridge() bool { 42 | return n == "bridge" 43 | } 44 | 45 | // IsHost indicates whether container uses the host network stack. 46 | func (n NetworkMode) IsHost() bool { 47 | return n == "host" 48 | } 49 | 50 | // IsContainer indicates whether container uses a container network stack. 51 | func (n NetworkMode) IsContainer() bool { 52 | parts := strings.SplitN(string(n), ":", 2) 53 | return len(parts) > 1 && parts[0] == "container" 54 | } 55 | 56 | // IsNone indicates whether container isn't using a network stack. 57 | func (n NetworkMode) IsNone() bool { 58 | return n == "none" 59 | } 60 | 61 | // ConnectedContainer is the id of the container which network this container is connected to. 62 | func (n NetworkMode) ConnectedContainer() string { 63 | parts := strings.SplitN(string(n), ":", 2) 64 | if len(parts) > 1 { 65 | return parts[1] 66 | } 67 | return "" 68 | } 69 | 70 | // IsUserDefined indicates user-created network 71 | func (n NetworkMode) IsUserDefined() bool { 72 | return !n.IsDefault() && !n.IsBridge() && !n.IsHost() && !n.IsNone() && !n.IsContainer() 73 | } 74 | 75 | //UserDefined indicates user-created network 76 | func (n NetworkMode) UserDefined() string { 77 | if n.IsUserDefined() { 78 | return string(n) 79 | } 80 | return "" 81 | } 82 | -------------------------------------------------------------------------------- /types/container/hostconfig_windows.go: -------------------------------------------------------------------------------- 1 | package container 2 | 3 | import ( 4 | "strings" 5 | ) 6 | 7 | // IsDefault indicates whether container uses the default network stack. 8 | func (n NetworkMode) IsDefault() bool { 9 | return n == "default" 10 | } 11 | 12 | // IsNone indicates whether container isn't using a network stack. 13 | func (n NetworkMode) IsNone() bool { 14 | return n == "none" 15 | } 16 | 17 | // IsContainer indicates whether container uses a container network stack. 18 | // Returns false as windows doesn't support this mode 19 | func (n NetworkMode) IsContainer() bool { 20 | return false 21 | } 22 | 23 | // IsBridge indicates whether container uses the bridge network stack 24 | // in windows it is given the name NAT 25 | func (n NetworkMode) IsBridge() bool { 26 | return n == "nat" 27 | } 28 | 29 | // IsHost indicates whether container uses the host network stack. 30 | // returns false as this is not supported by windows 31 | func (n NetworkMode) IsHost() bool { 32 | return false 33 | } 34 | 35 | // IsPrivate indicates whether container uses its private network stack. 36 | func (n NetworkMode) IsPrivate() bool { 37 | return !(n.IsHost() || n.IsContainer()) 38 | } 39 | 40 | // ConnectedContainer is the id of the container which network this container is connected to. 41 | // Returns blank string on windows 42 | func (n NetworkMode) ConnectedContainer() string { 43 | return "" 44 | } 45 | 46 | // IsUserDefined indicates user-created network 47 | func (n NetworkMode) IsUserDefined() bool { 48 | return !n.IsDefault() && !n.IsNone() && !n.IsBridge() 49 | } 50 | 51 | // IsHyperV indicates the use of a Hyper-V partition for isolation 52 | func (i Isolation) IsHyperV() bool { 53 | return strings.ToLower(string(i)) == "hyperv" 54 | } 55 | 56 | // IsProcess indicates the use of process isolation 57 | func (i Isolation) IsProcess() bool { 58 | return strings.ToLower(string(i)) == "process" 59 | } 60 | 61 | // IsValid indicates if an isolation technology is valid 62 | func (i Isolation) IsValid() bool { 63 | return i.IsDefault() || i.IsHyperV() || i.IsProcess() 64 | } 65 | 66 | // NetworkName returns the name of the network stack. 67 | func (n NetworkMode) NetworkName() string { 68 | if n.IsDefault() { 69 | return "default" 70 | } else if n.IsBridge() { 71 | return "nat" 72 | } else if n.IsNone() { 73 | return "none" 74 | } else if n.IsUserDefined() { 75 | return n.UserDefined() 76 | } 77 | 78 | return "" 79 | } 80 | 81 | //UserDefined indicates user-created network 82 | func (n NetworkMode) UserDefined() string { 83 | if n.IsUserDefined() { 84 | return string(n) 85 | } 86 | return "" 87 | } 88 | -------------------------------------------------------------------------------- /types/errors.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | // ErrorResponse is the response body of API errors. 4 | type ErrorResponse struct { 5 | Message string `json:"message"` 6 | } 7 | -------------------------------------------------------------------------------- /types/events/events.go: -------------------------------------------------------------------------------- 1 | package events 2 | 3 | const ( 4 | // ContainerEventType is the event type that containers generate 5 | ContainerEventType = "container" 6 | // DaemonEventType is the event type that daemon generate 7 | DaemonEventType = "daemon" 8 | // ImageEventType is the event type that images generate 9 | ImageEventType = "image" 10 | // NetworkEventType is the event type that networks generate 11 | NetworkEventType = "network" 12 | // PluginEventType is the event type that plugins generate 13 | PluginEventType = "plugin" 14 | // VolumeEventType is the event type that volumes generate 15 | VolumeEventType = "volume" 16 | ) 17 | 18 | // Actor describes something that generates events, 19 | // like a container, or a network, or a volume. 20 | // It has a defined name and a set or attributes. 21 | // The container attributes are its labels, other actors 22 | // can generate these attributes from other properties. 23 | type Actor struct { 24 | ID string 25 | Attributes map[string]string 26 | } 27 | 28 | // Message represents the information an event contains 29 | type Message struct { 30 | // Deprecated information from JSONMessage. 31 | // With data only in container events. 32 | Status string `json:"status,omitempty"` 33 | ID string `json:"id,omitempty"` 34 | From string `json:"from,omitempty"` 35 | 36 | Type string 37 | Action string 38 | Actor Actor 39 | 40 | Time int64 `json:"time,omitempty"` 41 | TimeNano int64 `json:"timeNano,omitempty"` 42 | } 43 | -------------------------------------------------------------------------------- /types/mount/mount.go: -------------------------------------------------------------------------------- 1 | package mount 2 | 3 | // Type represents the type of a mount. 4 | type Type string 5 | 6 | const ( 7 | // TypeBind BIND 8 | TypeBind Type = "bind" 9 | // TypeVolume VOLUME 10 | TypeVolume Type = "volume" 11 | ) 12 | 13 | // Mount represents a mount (volume). 14 | type Mount struct { 15 | Type Type `json:",omitempty"` 16 | Source string `json:",omitempty"` 17 | Target string `json:",omitempty"` 18 | ReadOnly bool `json:",omitempty"` 19 | 20 | BindOptions *BindOptions `json:",omitempty"` 21 | VolumeOptions *VolumeOptions `json:",omitempty"` 22 | } 23 | 24 | // Propagation represents the propagation of a mount. 25 | type Propagation string 26 | 27 | const ( 28 | // PropagationRPrivate RPRIVATE 29 | PropagationRPrivate Propagation = "rprivate" 30 | // PropagationPrivate PRIVATE 31 | PropagationPrivate Propagation = "private" 32 | // PropagationRShared RSHARED 33 | PropagationRShared Propagation = "rshared" 34 | // PropagationShared SHARED 35 | PropagationShared Propagation = "shared" 36 | // PropagationRSlave RSLAVE 37 | PropagationRSlave Propagation = "rslave" 38 | // PropagationSlave SLAVE 39 | PropagationSlave Propagation = "slave" 40 | ) 41 | 42 | // BindOptions defines options specific to mounts of type "bind". 43 | type BindOptions struct { 44 | Propagation Propagation `json:",omitempty"` 45 | } 46 | 47 | // VolumeOptions represents the options for a mount of type volume. 48 | type VolumeOptions struct { 49 | NoCopy bool `json:",omitempty"` 50 | Labels map[string]string `json:",omitempty"` 51 | DriverConfig *Driver `json:",omitempty"` 52 | } 53 | 54 | // Driver represents a volume driver. 55 | type Driver struct { 56 | Name string `json:",omitempty"` 57 | Options map[string]string `json:",omitempty"` 58 | } 59 | -------------------------------------------------------------------------------- /types/network/network.go: -------------------------------------------------------------------------------- 1 | package network 2 | 3 | // Address represents an IP address 4 | type Address struct { 5 | Addr string 6 | PrefixLen int 7 | } 8 | 9 | // IPAM represents IP Address Management 10 | type IPAM struct { 11 | Driver string 12 | Options map[string]string //Per network IPAM driver options 13 | Config []IPAMConfig 14 | } 15 | 16 | // IPAMConfig represents IPAM configurations 17 | type IPAMConfig struct { 18 | Subnet string `json:",omitempty"` 19 | IPRange string `json:",omitempty"` 20 | Gateway string `json:",omitempty"` 21 | AuxAddress map[string]string `json:"AuxiliaryAddresses,omitempty"` 22 | } 23 | 24 | // EndpointIPAMConfig represents IPAM configurations for the endpoint 25 | type EndpointIPAMConfig struct { 26 | IPv4Address string `json:",omitempty"` 27 | IPv6Address string `json:",omitempty"` 28 | LinkLocalIPs []string `json:",omitempty"` 29 | } 30 | 31 | // EndpointSettings stores the network endpoint details 32 | type EndpointSettings struct { 33 | // Configurations 34 | IPAMConfig *EndpointIPAMConfig 35 | Links []string 36 | Aliases []string 37 | // Operational data 38 | NetworkID string 39 | EndpointID string 40 | Gateway string 41 | IPAddress string 42 | IPPrefixLen int 43 | IPv6Gateway string 44 | GlobalIPv6Address string 45 | GlobalIPv6PrefixLen int 46 | MacAddress string 47 | } 48 | 49 | // NetworkingConfig represents the container's networking configuration for each of its interfaces 50 | // Carries the networking configs specified in the `docker run` and `docker network connect` commands 51 | type NetworkingConfig struct { 52 | EndpointsConfig map[string]*EndpointSettings // Endpoint configs for each connecting network 53 | } 54 | -------------------------------------------------------------------------------- /types/reference/image_reference.go: -------------------------------------------------------------------------------- 1 | package reference 2 | 3 | import ( 4 | distreference "github.com/docker/distribution/reference" 5 | ) 6 | 7 | // Parse parses the given references and returns the repository and 8 | // tag (if present) from it. If there is an error during parsing, it will 9 | // return an error. 10 | func Parse(ref string) (string, string, error) { 11 | distributionRef, err := distreference.ParseNamed(ref) 12 | if err != nil { 13 | return "", "", err 14 | } 15 | 16 | tag := GetTagFromNamedRef(distributionRef) 17 | return distributionRef.Name(), tag, nil 18 | } 19 | 20 | // GetTagFromNamedRef returns a tag from the specified reference. 21 | // This function is necessary as long as the docker "server" api makes the distinction between repository 22 | // and tags. 23 | func GetTagFromNamedRef(ref distreference.Named) string { 24 | var tag string 25 | switch x := ref.(type) { 26 | case distreference.Digested: 27 | tag = x.Digest().String() 28 | case distreference.NamedTagged: 29 | tag = x.Tag() 30 | default: 31 | tag = "latest" 32 | } 33 | return tag 34 | } 35 | -------------------------------------------------------------------------------- /types/reference/image_reference_test.go: -------------------------------------------------------------------------------- 1 | package reference 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestParse(t *testing.T) { 8 | testCases := []struct { 9 | ref string 10 | expectedName string 11 | expectedTag string 12 | expectedError bool 13 | }{ 14 | { 15 | ref: "", 16 | expectedName: "", 17 | expectedTag: "", 18 | expectedError: true, 19 | }, 20 | { 21 | ref: "repository", 22 | expectedName: "repository", 23 | expectedTag: "latest", 24 | expectedError: false, 25 | }, 26 | { 27 | ref: "repository:tag", 28 | expectedName: "repository", 29 | expectedTag: "tag", 30 | expectedError: false, 31 | }, 32 | { 33 | ref: "test.com/repository", 34 | expectedName: "test.com/repository", 35 | expectedTag: "latest", 36 | expectedError: false, 37 | }, 38 | { 39 | ref: "test.com:5000/test/repository", 40 | expectedName: "test.com:5000/test/repository", 41 | expectedTag: "latest", 42 | expectedError: false, 43 | }, 44 | { 45 | ref: "test.com:5000/repo@sha256:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", 46 | expectedName: "test.com:5000/repo", 47 | expectedTag: "sha256:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", 48 | expectedError: false, 49 | }, 50 | { 51 | ref: "test.com:5000/repo:tag@sha256:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", 52 | expectedName: "test.com:5000/repo", 53 | expectedTag: "sha256:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", 54 | expectedError: false, 55 | }, 56 | } 57 | 58 | for _, c := range testCases { 59 | name, tag, err := Parse(c.ref) 60 | if err != nil && c.expectedError { 61 | continue 62 | } else if err != nil { 63 | t.Fatalf("error with %s: %s", c.ref, err.Error()) 64 | } 65 | if name != c.expectedName { 66 | t.Fatalf("expected name %s, got %s", c.expectedName, name) 67 | } 68 | if tag != c.expectedTag { 69 | t.Fatalf("expected tag %s, got %s", c.expectedTag, tag) 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /types/strslice/strslice.go: -------------------------------------------------------------------------------- 1 | package strslice 2 | 3 | import "encoding/json" 4 | 5 | // StrSlice represents a string or an array of strings. 6 | // We need to override the json decoder to accept both options. 7 | type StrSlice []string 8 | 9 | // UnmarshalJSON decodes the byte slice whether it's a string or an array of 10 | // strings. This method is needed to implement json.Unmarshaler. 11 | func (e *StrSlice) UnmarshalJSON(b []byte) error { 12 | if len(b) == 0 { 13 | // With no input, we preserve the existing value by returning nil and 14 | // leaving the target alone. This allows defining default values for 15 | // the type. 16 | return nil 17 | } 18 | 19 | p := make([]string, 0, 1) 20 | if err := json.Unmarshal(b, &p); err != nil { 21 | var s string 22 | if err := json.Unmarshal(b, &s); err != nil { 23 | return err 24 | } 25 | p = append(p, s) 26 | } 27 | 28 | *e = p 29 | return nil 30 | } 31 | -------------------------------------------------------------------------------- /types/strslice/strslice_test.go: -------------------------------------------------------------------------------- 1 | package strslice 2 | 3 | import ( 4 | "encoding/json" 5 | "reflect" 6 | "testing" 7 | ) 8 | 9 | func TestStrSliceMarshalJSON(t *testing.T) { 10 | for _, testcase := range []struct { 11 | input StrSlice 12 | expected string 13 | }{ 14 | // MADNESS(stevvooe): No clue why nil would be "" but empty would be 15 | // "null". Had to make a change here that may affect compatibility. 16 | {input: nil, expected: "null"}, 17 | {StrSlice{}, "[]"}, 18 | {StrSlice{"/bin/sh", "-c", "echo"}, `["/bin/sh","-c","echo"]`}, 19 | } { 20 | data, err := json.Marshal(testcase.input) 21 | if err != nil { 22 | t.Fatal(err) 23 | } 24 | if string(data) != testcase.expected { 25 | t.Fatalf("%#v: expected %v, got %v", testcase.input, testcase.expected, string(data)) 26 | } 27 | } 28 | } 29 | 30 | func TestStrSliceUnmarshalJSON(t *testing.T) { 31 | parts := map[string][]string{ 32 | "": {"default", "values"}, 33 | "[]": {}, 34 | `["/bin/sh","-c","echo"]`: {"/bin/sh", "-c", "echo"}, 35 | } 36 | for json, expectedParts := range parts { 37 | strs := StrSlice{"default", "values"} 38 | if err := strs.UnmarshalJSON([]byte(json)); err != nil { 39 | t.Fatal(err) 40 | } 41 | 42 | actualParts := []string(strs) 43 | if !reflect.DeepEqual(actualParts, expectedParts) { 44 | t.Fatalf("%#v: expected %v, got %v", json, expectedParts, actualParts) 45 | } 46 | 47 | } 48 | } 49 | 50 | func TestStrSliceUnmarshalString(t *testing.T) { 51 | var e StrSlice 52 | echo, err := json.Marshal("echo") 53 | if err != nil { 54 | t.Fatal(err) 55 | } 56 | if err := json.Unmarshal(echo, &e); err != nil { 57 | t.Fatal(err) 58 | } 59 | 60 | if len(e) != 1 { 61 | t.Fatalf("expected 1 element after unmarshal: %q", e) 62 | } 63 | 64 | if e[0] != "echo" { 65 | t.Fatalf("expected `echo`, got: %q", e[0]) 66 | } 67 | } 68 | 69 | func TestStrSliceUnmarshalSlice(t *testing.T) { 70 | var e StrSlice 71 | echo, err := json.Marshal([]string{"echo"}) 72 | if err != nil { 73 | t.Fatal(err) 74 | } 75 | if err := json.Unmarshal(echo, &e); err != nil { 76 | t.Fatal(err) 77 | } 78 | 79 | if len(e) != 1 { 80 | t.Fatalf("expected 1 element after unmarshal: %q", e) 81 | } 82 | 83 | if e[0] != "echo" { 84 | t.Fatalf("expected `echo`, got: %q", e[0]) 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /types/swarm/common.go: -------------------------------------------------------------------------------- 1 | package swarm 2 | 3 | import "time" 4 | 5 | // Version represent the internal object version. 6 | type Version struct { 7 | Index uint64 `json:",omitempty"` 8 | } 9 | 10 | // Meta is base object inherited by most of the other once. 11 | type Meta struct { 12 | Version Version `json:",omitempty"` 13 | CreatedAt time.Time `json:",omitempty"` 14 | UpdatedAt time.Time `json:",omitempty"` 15 | } 16 | 17 | // Annotations represents how to describe an object. 18 | type Annotations struct { 19 | Name string `json:",omitempty"` 20 | Labels map[string]string `json:",omitempty"` 21 | } 22 | -------------------------------------------------------------------------------- /types/swarm/container.go: -------------------------------------------------------------------------------- 1 | package swarm 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/docker/engine-api/types/mount" 7 | ) 8 | 9 | // ContainerSpec represents the spec of a container. 10 | type ContainerSpec struct { 11 | Image string `json:",omitempty"` 12 | Labels map[string]string `json:",omitempty"` 13 | Command []string `json:",omitempty"` 14 | Args []string `json:",omitempty"` 15 | Env []string `json:",omitempty"` 16 | Dir string `json:",omitempty"` 17 | User string `json:",omitempty"` 18 | Groups []string `json:",omitempty"` 19 | TTY bool `json:",omitempty"` 20 | Mounts []mount.Mount `json:",omitempty"` 21 | StopGracePeriod *time.Duration `json:",omitempty"` 22 | } 23 | -------------------------------------------------------------------------------- /types/swarm/service.go: -------------------------------------------------------------------------------- 1 | package swarm 2 | 3 | import "time" 4 | 5 | // Service represents a service. 6 | type Service struct { 7 | ID string 8 | Meta 9 | Spec ServiceSpec `json:",omitempty"` 10 | Endpoint Endpoint `json:",omitempty"` 11 | UpdateStatus UpdateStatus `json:",omitempty"` 12 | } 13 | 14 | // ServiceSpec represents the spec of a service. 15 | type ServiceSpec struct { 16 | Annotations 17 | 18 | // TaskTemplate defines how the service should construct new tasks when 19 | // orchestrating this service. 20 | TaskTemplate TaskSpec `json:",omitempty"` 21 | Mode ServiceMode `json:",omitempty"` 22 | UpdateConfig *UpdateConfig `json:",omitempty"` 23 | 24 | // Networks field in ServiceSpec is being deprecated. Users of 25 | // engine-api should start using the same field in 26 | // TaskSpec. This field will be removed in future releases. 27 | Networks []NetworkAttachmentConfig `json:",omitempty"` 28 | EndpointSpec *EndpointSpec `json:",omitempty"` 29 | } 30 | 31 | // ServiceMode represents the mode of a service. 32 | type ServiceMode struct { 33 | Replicated *ReplicatedService `json:",omitempty"` 34 | Global *GlobalService `json:",omitempty"` 35 | } 36 | 37 | // UpdateState is the state of a service update. 38 | type UpdateState string 39 | 40 | const ( 41 | // UpdateStateUpdating is the updating state. 42 | UpdateStateUpdating UpdateState = "updating" 43 | // UpdateStatePaused is the paused state. 44 | UpdateStatePaused UpdateState = "paused" 45 | // UpdateStateCompleted is the completed state. 46 | UpdateStateCompleted UpdateState = "completed" 47 | ) 48 | 49 | // UpdateStatus reports the status of a service update. 50 | type UpdateStatus struct { 51 | State UpdateState `json:",omitempty"` 52 | StartedAt time.Time `json:",omitempty"` 53 | CompletedAt time.Time `json:",omitempty"` 54 | Message string `json:",omitempty"` 55 | } 56 | 57 | // ReplicatedService is a kind of ServiceMode. 58 | type ReplicatedService struct { 59 | Replicas *uint64 `json:",omitempty"` 60 | } 61 | 62 | // GlobalService is a kind of ServiceMode. 63 | type GlobalService struct{} 64 | 65 | const ( 66 | // UpdateFailureActionPause PAUSE 67 | UpdateFailureActionPause = "pause" 68 | // UpdateFailureActionContinue CONTINUE 69 | UpdateFailureActionContinue = "continue" 70 | ) 71 | 72 | // UpdateConfig represents the update configuration. 73 | type UpdateConfig struct { 74 | Parallelism uint64 `json:",omitempty"` 75 | Delay time.Duration `json:",omitempty"` 76 | FailureAction string `json:",omitempty"` 77 | } 78 | -------------------------------------------------------------------------------- /types/time/duration_convert.go: -------------------------------------------------------------------------------- 1 | package time 2 | 3 | import ( 4 | "strconv" 5 | "time" 6 | ) 7 | 8 | // DurationToSecondsString converts the specified duration to the number 9 | // seconds it represents, formatted as a string. 10 | func DurationToSecondsString(duration time.Duration) string { 11 | return strconv.FormatFloat(duration.Seconds(), 'f', 0, 64) 12 | } 13 | -------------------------------------------------------------------------------- /types/time/duration_convert_test.go: -------------------------------------------------------------------------------- 1 | package time 2 | 3 | import ( 4 | "testing" 5 | "time" 6 | ) 7 | 8 | func TestDurationToSecondsString(t *testing.T) { 9 | cases := []struct { 10 | in time.Duration 11 | expected string 12 | }{ 13 | {0 * time.Second, "0"}, 14 | {1 * time.Second, "1"}, 15 | {1 * time.Minute, "60"}, 16 | {24 * time.Hour, "86400"}, 17 | } 18 | 19 | for _, c := range cases { 20 | s := DurationToSecondsString(c.in) 21 | if s != c.expected { 22 | t.Errorf("wrong value for input `%v`: expected `%s`, got `%s`", c.in, c.expected, s) 23 | t.Fail() 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /types/versions/README.md: -------------------------------------------------------------------------------- 1 | ## Legacy API type versions 2 | 3 | This package includes types for legacy API versions. The stable version of the API types live in `api/types/*.go`. 4 | 5 | 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`. 6 | 7 | ### Package name conventions 8 | 9 | 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: 10 | 11 | 1. We cannot use `.` because it's interpreted by the language, think of `v1.20.CallFunction`. 12 | 2. We cannot use `_` because golint complains about it. The code is actually valid, but it looks probably more weird: `v1_20.CallFunction`. 13 | 14 | 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`. 15 | -------------------------------------------------------------------------------- /types/versions/compare.go: -------------------------------------------------------------------------------- 1 | package versions 2 | 3 | import ( 4 | "strconv" 5 | "strings" 6 | ) 7 | 8 | // compare compares two version strings 9 | // returns -1 if v1 < v2, 1 if v1 > v2, 0 otherwise. 10 | func compare(v1, v2 string) int { 11 | var ( 12 | currTab = strings.Split(v1, ".") 13 | otherTab = strings.Split(v2, ".") 14 | ) 15 | 16 | max := len(currTab) 17 | if len(otherTab) > max { 18 | max = len(otherTab) 19 | } 20 | for i := 0; i < max; i++ { 21 | var currInt, otherInt int 22 | 23 | if len(currTab) > i { 24 | currInt, _ = strconv.Atoi(currTab[i]) 25 | } 26 | if len(otherTab) > i { 27 | otherInt, _ = strconv.Atoi(otherTab[i]) 28 | } 29 | if currInt > otherInt { 30 | return 1 31 | } 32 | if otherInt > currInt { 33 | return -1 34 | } 35 | } 36 | return 0 37 | } 38 | 39 | // LessThan checks if a version is less than another 40 | func LessThan(v, other string) bool { 41 | return compare(v, other) == -1 42 | } 43 | 44 | // LessThanOrEqualTo checks if a version is less than or equal to another 45 | func LessThanOrEqualTo(v, other string) bool { 46 | return compare(v, other) <= 0 47 | } 48 | 49 | // GreaterThan checks if a version is greater than another 50 | func GreaterThan(v, other string) bool { 51 | return compare(v, other) == 1 52 | } 53 | 54 | // GreaterThanOrEqualTo checks if a version is greater than or equal to another 55 | func GreaterThanOrEqualTo(v, other string) bool { 56 | return compare(v, other) >= 0 57 | } 58 | 59 | // Equal checks if a version is equal to another 60 | func Equal(v, other string) bool { 61 | return compare(v, other) == 0 62 | } 63 | -------------------------------------------------------------------------------- /types/versions/compare_test.go: -------------------------------------------------------------------------------- 1 | package versions 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func assertVersion(t *testing.T, a, b string, result int) { 8 | if r := compare(a, b); r != result { 9 | t.Fatalf("Unexpected version comparison result. Found %d, expected %d", r, result) 10 | } 11 | } 12 | 13 | func TestCompareVersion(t *testing.T) { 14 | assertVersion(t, "1.12", "1.12", 0) 15 | assertVersion(t, "1.0.0", "1", 0) 16 | assertVersion(t, "1", "1.0.0", 0) 17 | assertVersion(t, "1.05.00.0156", "1.0.221.9289", 1) 18 | assertVersion(t, "1", "1.0.1", -1) 19 | assertVersion(t, "1.0.1", "1", 1) 20 | assertVersion(t, "1.0.1", "1.0.2", -1) 21 | assertVersion(t, "1.0.2", "1.0.3", -1) 22 | assertVersion(t, "1.0.3", "1.1", -1) 23 | assertVersion(t, "1.1", "1.1.1", -1) 24 | assertVersion(t, "1.1.1", "1.1.2", -1) 25 | assertVersion(t, "1.1.2", "1.2", -1) 26 | } 27 | -------------------------------------------------------------------------------- /types/versions/v1p19/types.go: -------------------------------------------------------------------------------- 1 | // Package v1p19 provides specific API types for the API version 1, patch 19. 2 | package v1p19 3 | 4 | import ( 5 | "github.com/docker/engine-api/types" 6 | "github.com/docker/engine-api/types/container" 7 | "github.com/docker/engine-api/types/versions/v1p20" 8 | "github.com/docker/go-connections/nat" 9 | ) 10 | 11 | // ContainerJSON is a backcompatibility struct for APIs prior to 1.20. 12 | // Note this is not used by the Windows daemon. 13 | type ContainerJSON struct { 14 | *types.ContainerJSONBase 15 | Volumes map[string]string 16 | VolumesRW map[string]bool 17 | Config *ContainerConfig 18 | NetworkSettings *v1p20.NetworkSettings 19 | } 20 | 21 | // ContainerConfig is a backcompatibility struct for APIs prior to 1.20. 22 | type ContainerConfig struct { 23 | *container.Config 24 | 25 | MacAddress string 26 | NetworkDisabled bool 27 | ExposedPorts map[nat.Port]struct{} 28 | 29 | // backward compatibility, they now live in HostConfig 30 | VolumeDriver string 31 | Memory int64 32 | MemorySwap int64 33 | CPUShares int64 `json:"CpuShares"` 34 | CPUSet string `json:"Cpuset"` 35 | } 36 | -------------------------------------------------------------------------------- /types/versions/v1p20/types.go: -------------------------------------------------------------------------------- 1 | // Package v1p20 provides specific API types for the API version 1, patch 20. 2 | package v1p20 3 | 4 | import ( 5 | "github.com/docker/engine-api/types" 6 | "github.com/docker/engine-api/types/container" 7 | "github.com/docker/go-connections/nat" 8 | ) 9 | 10 | // ContainerJSON is a backcompatibility struct for the API 1.20 11 | type ContainerJSON struct { 12 | *types.ContainerJSONBase 13 | Mounts []types.MountPoint 14 | Config *ContainerConfig 15 | NetworkSettings *NetworkSettings 16 | } 17 | 18 | // ContainerConfig is a backcompatibility struct used in ContainerJSON for the API 1.20 19 | type ContainerConfig struct { 20 | *container.Config 21 | 22 | MacAddress string 23 | NetworkDisabled bool 24 | ExposedPorts map[nat.Port]struct{} 25 | 26 | // backward compatibility, they now live in HostConfig 27 | VolumeDriver string 28 | } 29 | 30 | // StatsJSON is a backcompatibility struct used in Stats for APIs prior to 1.21 31 | type StatsJSON struct { 32 | types.Stats 33 | Network types.NetworkStats `json:"network,omitempty"` 34 | } 35 | 36 | // NetworkSettings is a backward compatible struct for APIs prior to 1.21 37 | type NetworkSettings struct { 38 | types.NetworkSettingsBase 39 | types.DefaultNetworkSettings 40 | } 41 | --------------------------------------------------------------------------------