pax_global_header00006660000000000000000000000064146571711120014517gustar00rootroot0000000000000052 comment=e9d929d0e66a4a98e21a6ab06e1e7e38ff8de70c go-dockerclient-1.12.0/000077500000000000000000000000001465717111200146515ustar00rootroot00000000000000go-dockerclient-1.12.0/.gitattributes000066400000000000000000000000231465717111200175370ustar00rootroot00000000000000* text=auto eol=lf go-dockerclient-1.12.0/.github/000077500000000000000000000000001465717111200162115ustar00rootroot00000000000000go-dockerclient-1.12.0/.github/dependabot.yml000066400000000000000000000004071465717111200210420ustar00rootroot00000000000000version: 2 updates: - package-ecosystem: gomod directory: "/" schedule: interval: daily open-pull-requests-limit: 99 - package-ecosystem: github-actions directory: "/" schedule: interval: daily open-pull-requests-limit: 99 go-dockerclient-1.12.0/.github/stale.yml000066400000000000000000000004331465717111200200440ustar00rootroot00000000000000daysUntilStale: 30 daysUntilClose: 5 exemptLabels: - bug - enhancement - nostale staleLabel: stale markComment: > This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. closeComment: false go-dockerclient-1.12.0/.github/workflows/000077500000000000000000000000001465717111200202465ustar00rootroot00000000000000go-dockerclient-1.12.0/.github/workflows/codeql.yml000066400000000000000000000007231465717111200222420ustar00rootroot00000000000000name: "Code Scanning - Action" on: pull_request: jobs: CodeQL-Build: runs-on: ubuntu-latest permissions: security-events: write steps: - name: Checkout repository uses: actions/checkout@v4.1.7 - name: Initialize CodeQL uses: github/codeql-action/init@v3 - name: Autobuild uses: github/codeql-action/autobuild@v3 - name: Perform CodeQL Analysis uses: github/codeql-action/analyze@v3 go-dockerclient-1.12.0/.github/workflows/main.yml000066400000000000000000000036331465717111200217220ustar00rootroot00000000000000name: Build on: push: branches: - main pull_request: branches: - main schedule: - cron: "0 0 * * 4" jobs: build: strategy: matrix: goos: - darwin - freebsd - linux - windows goarch: - amd64 - arm64 name: cross-compilation (GOOS=${{ matrix.goos }}, GOARCH=${{ matrix.goarch }}) runs-on: ubuntu-latest steps: - uses: actions/checkout@v4.1.7 - uses: actions/setup-go@v5.0.2 with: go-version: "1.x" - run: go build env: GOOS: ${{ matrix.goos }} GOARCH: ${{ matrix.goarch }} test: strategy: matrix: go-version: - "1.22" - "1.23" os: - macos - ubuntu - windows goarch: - 386 - amd64 exclude: - os: macos goarch: 386 name: tests (${{ matrix.os }}/go-${{ matrix.go-version }}/${{ matrix.goarch }}) runs-on: ${{ matrix.os }}-latest steps: - uses: actions/checkout@v4.1.7 - uses: actions/setup-go@v5.0.2 id: go with: go-version: ${{ matrix.go-version }} - run: go mod download - run: make staticcheck if: matrix.go-version == '1.23' - run: make gotest env: GOARCH: ${{ matrix.goarch }} GOPROXY: off integration-tests: strategy: matrix: os: - ubuntu - windows name: integration tests (${{ matrix.os }}) runs-on: ${{ matrix.os }}-latest steps: - uses: actions/checkout@v4.1.7 - uses: actions/setup-go@v5.0.2 id: go with: go-version: "1.x" - run: make integration lint: name: lint runs-on: ubuntu-latest steps: - uses: actions/checkout@v4.1.7 - uses: golangci/golangci-lint-action@v6.1.0 go-dockerclient-1.12.0/.gitignore000066400000000000000000000000651465717111200166420ustar00rootroot00000000000000# temporary symlink for testing testing/data/symlink go-dockerclient-1.12.0/.golangci.yaml000066400000000000000000000001321465717111200173720ustar00rootroot00000000000000run: deadline: 5m linters: disable-all: true enable: - gofumpt - goimports go-dockerclient-1.12.0/DOCKER-LICENSE000066400000000000000000000004221465717111200166210ustar00rootroot00000000000000 Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ You can find the Docker license at the following link: https://raw.githubusercontent.com/docker/docker/HEAD/LICENSE go-dockerclient-1.12.0/LICENSE000066400000000000000000000024241465717111200156600ustar00rootroot00000000000000Copyright (c) go-dockerclient authors All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. go-dockerclient-1.12.0/Makefile000066400000000000000000000010741465717111200163130ustar00rootroot00000000000000ifeq "$(strip $(shell go env GOARCH))" "amd64" RACE_FLAG := -race endif .PHONY: test test: pretest gotest .PHONY: golangci-lint golangci-lint: go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest golangci-lint run .PHONY: staticcheck staticcheck: go install honnef.co/go/tools/cmd/staticcheck@master staticcheck ./... .PHONY: lint lint: golangci-lint staticcheck .PHONY: pretest pretest: lint .PHONY: gotest gotest: go test $(RACE_FLAG) -vet all ./... .PHONY: integration integration: go test -tags docker_integration -run TestIntegration -v go-dockerclient-1.12.0/README.md000066400000000000000000000062351465717111200161360ustar00rootroot00000000000000# go-dockerclient [![Build Status](https://github.com/fsouza/go-dockerclient/workflows/Build/badge.svg)](https://github.com/fsouza/go-dockerclient/actions?query=branch:main+workflow:Build) [![GoDoc](https://img.shields.io/badge/api-Godoc-blue.svg?style=flat-square)](https://pkg.go.dev/github.com/fsouza/go-dockerclient) This package presents a client for the Docker remote API. It also provides support for the extensions in the [Swarm API](https://docs.docker.com/swarm/swarm-api/). This package also provides support for docker's network API, which is a simple passthrough to the libnetwork remote API. For more details, check the [remote API documentation](https://docs.docker.com/engine/api/latest/). ## Difference between go-dockerclient and the official SDK Link for the official SDK: https://docs.docker.com/develop/sdk/ go-dockerclient was created before Docker had an official Go SDK and is still maintained and active because it's still used out there. New features in the Docker API do not get automatically implemented here: it's based on demand, if someone wants it, they can file an issue or a PR and the feature may get implemented/merged. For new projects, using the official SDK is probably more appropriate as go-dockerclient lags behind the official SDK. ## Example ```go package main import ( "fmt" docker "github.com/fsouza/go-dockerclient" ) func main() { client, err := docker.NewClientFromEnv() if err != nil { panic(err) } imgs, err := client.ListImages(docker.ListImagesOptions{All: false}) if err != nil { panic(err) } for _, img := range imgs { fmt.Println("ID: ", img.ID) fmt.Println("RepoTags: ", img.RepoTags) fmt.Println("Created: ", img.Created) fmt.Println("Size: ", img.Size) fmt.Println("VirtualSize: ", img.VirtualSize) fmt.Println("ParentId: ", img.ParentID) } } ``` ## Using with TLS In order to instantiate the client for a TLS-enabled daemon, you should use NewTLSClient, passing the endpoint and path for key and certificates as parameters. ```go package main import ( "fmt" docker "github.com/fsouza/go-dockerclient" ) func main() { const endpoint = "tcp://[ip]:[port]" path := os.Getenv("DOCKER_CERT_PATH") ca := fmt.Sprintf("%s/ca.pem", path) cert := fmt.Sprintf("%s/cert.pem", path) key := fmt.Sprintf("%s/key.pem", path) client, _ := docker.NewTLSClient(endpoint, cert, key, ca) // use client } ``` If using [docker-machine](https://docs.docker.com/machine/), or another application that exports environment variables `DOCKER_HOST`, `DOCKER_TLS_VERIFY`, `DOCKER_CERT_PATH`, `DOCKER_API_VERSION`, you can use NewClientFromEnv. ```go package main import ( "fmt" docker "github.com/fsouza/go-dockerclient" ) func main() { client, err := docker.NewClientFromEnv() if err != nil { // handle err } // use client } ``` See the documentation for more details. ## Developing All development commands can be seen in the [Makefile](Makefile). Committed code must pass: * [golangci-lint](https://github.com/golangci/golangci-lint) * [go test](https://golang.org/cmd/go/#hdr-Test_packages) * [staticcheck](https://staticcheck.io/) Running ``make test`` will run all checks, as well as install any required dependencies. go-dockerclient-1.12.0/auth.go000066400000000000000000000254431465717111200161510ustar00rootroot00000000000000// Copyright 2015 go-dockerclient authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package docker import ( "bytes" "context" "encoding/base64" "encoding/json" "errors" "io" "net/http" "os" "os/exec" "path" "strings" ) // ErrCannotParseDockercfg is the error returned by NewAuthConfigurations when the dockercfg cannot be parsed. var ErrCannotParseDockercfg = errors.New("failed to read authentication from dockercfg") // AuthConfiguration represents authentication options to use in the PushImage // method. It represents the authentication in the Docker index server. type AuthConfiguration struct { Username string `json:"username,omitempty"` Password string `json:"password,omitempty"` Email string `json:"email,omitempty"` ServerAddress string `json:"serveraddress,omitempty"` // IdentityToken can be supplied with the identitytoken response of the AuthCheck call // see https://pkg.go.dev/github.com/docker/docker/api/types?tab=doc#AuthConfig // It can be used in place of password not in conjunction with it IdentityToken string `json:"identitytoken,omitempty"` // RegistryToken can be supplied with the registrytoken RegistryToken string `json:"registrytoken,omitempty"` } func (c AuthConfiguration) isEmpty() bool { return c == AuthConfiguration{} } func (c AuthConfiguration) headerKey() string { return "X-Registry-Auth" } // AuthConfigurations represents authentication options to use for the // PushImage method accommodating the new X-Registry-Config header type AuthConfigurations struct { Configs map[string]AuthConfiguration `json:"configs"` } func (c AuthConfigurations) isEmpty() bool { return len(c.Configs) == 0 } func (AuthConfigurations) headerKey() string { return "X-Registry-Config" } // merge updates the configuration. If a key is defined in both maps, the one // in c.Configs takes precedence. func (c *AuthConfigurations) merge(other AuthConfigurations) { for k, v := range other.Configs { if c.Configs == nil { c.Configs = make(map[string]AuthConfiguration) } if _, ok := c.Configs[k]; !ok { c.Configs[k] = v } } } // AuthConfigurations119 is used to serialize a set of AuthConfigurations // for Docker API >= 1.19. type AuthConfigurations119 map[string]AuthConfiguration func (c AuthConfigurations119) isEmpty() bool { return len(c) == 0 } func (c AuthConfigurations119) headerKey() string { return "X-Registry-Config" } // dockerConfig represents a registry authentation configuration from the // .dockercfg file. type dockerConfig struct { Auth string `json:"auth"` Email string `json:"email"` IdentityToken string `json:"identitytoken"` RegistryToken string `json:"registrytoken"` } // NewAuthConfigurationsFromFile returns AuthConfigurations from a path containing JSON // in the same format as the .dockercfg file. func NewAuthConfigurationsFromFile(path string) (*AuthConfigurations, error) { r, err := os.Open(path) if err != nil { return nil, err } return NewAuthConfigurations(r) } func cfgPaths(dockerConfigEnv string, homeEnv string) []string { if dockerConfigEnv != "" { return []string{ path.Join(dockerConfigEnv, "plaintext-passwords.json"), path.Join(dockerConfigEnv, "config.json"), } } if homeEnv != "" { return []string{ path.Join(homeEnv, ".docker", "plaintext-passwords.json"), path.Join(homeEnv, ".docker", "config.json"), path.Join(homeEnv, ".dockercfg"), } } return nil } // NewAuthConfigurationsFromDockerCfg returns AuthConfigurations from system // config files. The following files are checked in the order listed: // // If the environment variable DOCKER_CONFIG is set to a non-empty string: // // - $DOCKER_CONFIG/plaintext-passwords.json // - $DOCKER_CONFIG/config.json // // Otherwise, it looks for files in the $HOME directory and the legacy // location: // // - $HOME/.docker/plaintext-passwords.json // - $HOME/.docker/config.json // - $HOME/.dockercfg func NewAuthConfigurationsFromDockerCfg() (*AuthConfigurations, error) { pathsToTry := cfgPaths(os.Getenv("DOCKER_CONFIG"), os.Getenv("HOME")) if len(pathsToTry) < 1 { return nil, errors.New("no docker configuration found") } return newAuthConfigurationsFromDockerCfg(pathsToTry) } func newAuthConfigurationsFromDockerCfg(pathsToTry []string) (*AuthConfigurations, error) { var result *AuthConfigurations var auths *AuthConfigurations var err error for _, path := range pathsToTry { auths, err = NewAuthConfigurationsFromFile(path) if err != nil { continue } if result == nil { result = auths } else { result.merge(*auths) } } if result != nil { return result, nil } return result, err } // NewAuthConfigurations returns AuthConfigurations from a JSON encoded string in the // same format as the .dockercfg file. func NewAuthConfigurations(r io.Reader) (*AuthConfigurations, error) { var auth *AuthConfigurations confs, err := parseDockerConfig(r) if err != nil { return nil, err } auth, err = authConfigs(confs) if err != nil { return nil, err } return auth, nil } func parseDockerConfig(r io.Reader) (map[string]dockerConfig, error) { buf := new(bytes.Buffer) buf.ReadFrom(r) byteData := buf.Bytes() confsWrapper := struct { Auths map[string]dockerConfig `json:"auths"` }{} if err := json.Unmarshal(byteData, &confsWrapper); err == nil { if len(confsWrapper.Auths) > 0 { return confsWrapper.Auths, nil } } var confs map[string]dockerConfig if err := json.Unmarshal(byteData, &confs); err != nil { return nil, err } return confs, nil } // authConfigs converts a dockerConfigs map to a AuthConfigurations object. func authConfigs(confs map[string]dockerConfig) (*AuthConfigurations, error) { c := &AuthConfigurations{ Configs: make(map[string]AuthConfiguration), } for reg, conf := range confs { if conf.Auth == "" { continue } // support both padded and unpadded encoding data, err := base64.StdEncoding.DecodeString(conf.Auth) if err != nil { data, err = base64.StdEncoding.WithPadding(base64.NoPadding).DecodeString(conf.Auth) } if err != nil { return nil, errors.New("error decoding plaintext credentials") } userpass := strings.SplitN(string(data), ":", 2) if len(userpass) != 2 { return nil, ErrCannotParseDockercfg } authConfig := AuthConfiguration{ Email: conf.Email, Username: userpass[0], Password: userpass[1], ServerAddress: reg, } // if identitytoken provided then zero the password and set it if conf.IdentityToken != "" { authConfig.Password = "" authConfig.IdentityToken = conf.IdentityToken } // if registrytoken provided then zero the password and set it if conf.RegistryToken != "" { authConfig.Password = "" authConfig.RegistryToken = conf.RegistryToken } c.Configs[reg] = authConfig } return c, nil } // AuthStatus returns the authentication status for Docker API versions >= 1.23. type AuthStatus struct { Status string `json:"Status,omitempty" yaml:"Status,omitempty" toml:"Status,omitempty"` IdentityToken string `json:"IdentityToken,omitempty" yaml:"IdentityToken,omitempty" toml:"IdentityToken,omitempty"` } // AuthCheck validates the given credentials. It returns nil if successful. // // For Docker API versions >= 1.23, the AuthStatus struct will be populated, otherwise it will be empty.` // // See https://goo.gl/6nsZkH for more details. func (c *Client) AuthCheck(conf *AuthConfiguration) (AuthStatus, error) { return c.AuthCheckWithContext(conf, context.TODO()) } // AuthCheckWithContext validates the given credentials. It returns nil if successful. The context object // can be used to cancel the request. // // For Docker API versions >= 1.23, the AuthStatus struct will be populated, otherwise it will be empty. // // See https://goo.gl/6nsZkH for more details. func (c *Client) AuthCheckWithContext(conf *AuthConfiguration, ctx context.Context) (AuthStatus, error) { var authStatus AuthStatus if conf == nil { return authStatus, errors.New("conf is nil") } resp, err := c.do(http.MethodPost, "/auth", doOptions{data: conf, context: ctx}) if err != nil { return authStatus, err } defer resp.Body.Close() data, err := io.ReadAll(resp.Body) if err != nil { return authStatus, err } if len(data) == 0 { return authStatus, nil } if err := json.Unmarshal(data, &authStatus); err != nil { return authStatus, err } return authStatus, nil } // helperCredentials represents credentials commit from an helper type helperCredentials struct { Username string `json:"Username,omitempty"` Secret string `json:"Secret,omitempty"` } // NewAuthConfigurationsFromCredsHelpers returns AuthConfigurations from // installed credentials helpers func NewAuthConfigurationsFromCredsHelpers(registry string) (*AuthConfiguration, error) { // Load docker configuration file in order to find a possible helper provider pathsToTry := cfgPaths(os.Getenv("DOCKER_CONFIG"), os.Getenv("HOME")) if len(pathsToTry) < 1 { return nil, errors.New("no docker configuration found") } provider, err := getHelperProviderFromDockerCfg(pathsToTry, registry) if err != nil { return nil, err } c, err := getCredentialsFromHelper(provider, registry) if err != nil { return nil, err } creds := new(AuthConfiguration) creds.Username = c.Username creds.Password = c.Secret return creds, nil } func getHelperProviderFromDockerCfg(pathsToTry []string, registry string) (string, error) { for _, path := range pathsToTry { content, err := os.ReadFile(path) if err != nil { // if we can't read the file keep going continue } provider, err := parseCredsDockerConfig(content, registry) if err != nil { continue } if provider != "" { return provider, nil } } return "", errors.New("no docker credentials provider found") } func parseCredsDockerConfig(config []byte, registry string) (string, error) { creds := struct { CredsStore string `json:"credsStore,omitempty"` CredHelpers map[string]string `json:"credHelpers,omitempty"` }{} err := json.Unmarshal(config, &creds) if err != nil { return "", err } provider, ok := creds.CredHelpers[registry] if ok { return provider, nil } return creds.CredsStore, nil } // Run and parse the found credential helper func getCredentialsFromHelper(provider string, registry string) (*helperCredentials, error) { helpercreds, err := runDockerCredentialsHelper(provider, registry) if err != nil { return nil, err } c := new(helperCredentials) err = json.Unmarshal(helpercreds, c) if err != nil { return nil, err } return c, nil } func runDockerCredentialsHelper(provider string, registry string) ([]byte, error) { cmd := exec.Command("docker-credential-"+provider, "get") var stdout bytes.Buffer cmd.Stdin = bytes.NewBuffer([]byte(registry)) cmd.Stdout = &stdout err := cmd.Run() if err != nil { return nil, err } return stdout.Bytes(), nil } go-dockerclient-1.12.0/auth_test.go000066400000000000000000000341031465717111200172010ustar00rootroot00000000000000// Copyright 2015 go-dockerclient authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package docker import ( "encoding/base64" "errors" "fmt" "net/http" "os" "path" "reflect" "strings" "testing" ) func TestAuthConfigurationSearchPath(t *testing.T) { t.Parallel() testData := []struct { dockerConfigEnv string homeEnv string expectedPaths []string }{ {"", "", []string{}}, {"", "home", []string{path.Join("home", ".docker", "plaintext-passwords.json"), path.Join("home", ".docker", "config.json"), path.Join("home", ".dockercfg")}}, {"docker_config", "", []string{path.Join("docker_config", "plaintext-passwords.json"), path.Join("docker_config", "config.json")}}, {"a", "b", []string{path.Join("a", "plaintext-passwords.json"), path.Join("a", "config.json")}}, } for _, tt := range testData { tt := tt t.Run(tt.dockerConfigEnv+tt.homeEnv, func(t *testing.T) { t.Parallel() paths := cfgPaths(tt.dockerConfigEnv, tt.homeEnv) if got, want := strings.Join(paths, ","), strings.Join(tt.expectedPaths, ","); got != want { t.Errorf("cfgPaths: wrong result. Want: %s. Got: %s", want, got) } }) } } func TestAuthConfigurationsFromFile(t *testing.T) { t.Parallel() tmpDir, err := os.MkdirTemp("", "go-dockerclient-auth-test") if err != nil { t.Fatalf("Unable to create temporary directory for TestAuthConfigurationsFromFile: %s", err) } defer os.RemoveAll(tmpDir) authString := base64.StdEncoding.EncodeToString([]byte("user:pass")) content := fmt.Sprintf(`{"auths":{"foo": {"auth": "%s"}}}`, authString) configFile := path.Join(tmpDir, "docker_config") if err = os.WriteFile(configFile, []byte(content), 0o600); err != nil { t.Errorf("Error writing auth config for TestAuthConfigurationsFromFile: %s", err) } auths, err := NewAuthConfigurationsFromFile(configFile) if err != nil { t.Errorf("Error calling NewAuthConfigurationsFromFile: %s", err) } if _, hasKey := auths.Configs["foo"]; !hasKey { t.Errorf("Returned auths did not include expected auth key foo") } } func TestAuthConfigurationsFromDockerCfg(t *testing.T) { t.Parallel() tmpDir, err := os.MkdirTemp("", "go-dockerclient-auth-dockercfg-test") if err != nil { t.Fatalf("Unable to create temporary directory for TestAuthConfigurationsFromDockerCfg: %s", err) } defer os.RemoveAll(tmpDir) keys := []string{ "docker.io", "us.gcr.io", } pathsToTry := []string{"some/unknown/path"} for i, key := range keys { authString := base64.StdEncoding.EncodeToString([]byte("user:pass")) content := fmt.Sprintf(`{"auths":{"%s": {"auth": "%s"}}}`, key, authString) configFile := path.Join(tmpDir, fmt.Sprintf("docker_config_%d.json", i)) if err = os.WriteFile(configFile, []byte(content), 0o600); err != nil { t.Errorf("Error writing auth config for TestAuthConfigurationsFromFile: %s", err) } pathsToTry = append(pathsToTry, configFile) } auths, err := newAuthConfigurationsFromDockerCfg(pathsToTry) if err != nil { t.Errorf("Error calling NewAuthConfigurationsFromFile: %s", err) } for _, key := range keys { if _, hasKey := auths.Configs[key]; !hasKey { t.Errorf("Returned auths did not include expected auth key %q", key) } } } func TestAuthConfigurationsFromDockerCfgError(t *testing.T) { t.Parallel() auths, err := newAuthConfigurationsFromDockerCfg([]string{"this/doesnt/exist.json"}) if err == nil { t.Fatalf("unexpected error, returned auth config: %#v", auths) } } func TestAuthLegacyConfig(t *testing.T) { t.Parallel() auth := base64.StdEncoding.EncodeToString([]byte("user:pa:ss")) read := strings.NewReader(fmt.Sprintf(`{"docker.io":{"auth":"%s","email":"user@example.com"}}`, auth)) ac, err := NewAuthConfigurations(read) if err != nil { t.Error(err) } c, ok := ac.Configs["docker.io"] if !ok { t.Error("NewAuthConfigurations: Expected Configs to contain docker.io") } if got, want := c.Email, "user@example.com"; got != want { t.Errorf(`AuthConfigurations.Configs["docker.io"].Email: wrong result. Want %q. Got %q`, want, got) } if got, want := c.Username, "user"; got != want { t.Errorf(`AuthConfigurations.Configs["docker.io"].Username: wrong result. Want %q. Got %q`, want, got) } if got, want := c.Password, "pa:ss"; got != want { t.Errorf(`AuthConfigurations.Configs["docker.io"].Password: wrong result. Want %q. Got %q`, want, got) } if got, want := c.ServerAddress, "docker.io"; got != want { t.Errorf(`AuthConfigurations.Configs["docker.io"].ServerAddress: wrong result. Want %q. Got %q`, want, got) } } func TestAuthBadConfig(t *testing.T) { t.Parallel() auth := base64.StdEncoding.EncodeToString([]byte("userpass")) read := strings.NewReader(fmt.Sprintf(`{"docker.io":{"auth":"%s","email":"user@example.com"}}`, auth)) ac, err := NewAuthConfigurations(read) if !errors.Is(err, ErrCannotParseDockercfg) { t.Errorf("Incorrect error returned %v\n", err) } if ac != nil { t.Errorf("Invalid auth configuration returned, should be nil %v\n", ac) } } func TestAuthMixedWithKeyChain(t *testing.T) { t.Parallel() auth := base64.StdEncoding.EncodeToString([]byte("user:pass")) read := strings.NewReader(fmt.Sprintf(`{"auths":{"docker.io":{},"localhost:5000":{"auth":"%s"}},"credsStore":"osxkeychain"}`, auth)) ac, err := NewAuthConfigurations(read) if err != nil { t.Fatal(err) } c, ok := ac.Configs["localhost:5000"] if !ok { t.Error("NewAuthConfigurations: Expected Configs to contain localhost:5000") } if got, want := c.Username, "user"; got != want { t.Errorf(`AuthConfigurations.Configs["docker.io"].Username: wrong result. Want %q. Got %q`, want, got) } if got, want := c.Password, "pass"; got != want { t.Errorf(`AuthConfigurations.Configs["docker.io"].Password: wrong result. Want %q. Got %q`, want, got) } if got, want := c.ServerAddress, "localhost:5000"; got != want { t.Errorf(`AuthConfigurations.Configs["localhost:5000"].ServerAddress: wrong result. Want %q. Got %q`, want, got) } } func TestAuthAndOtherFields(t *testing.T) { t.Parallel() auth := base64.StdEncoding.EncodeToString([]byte("user:pass")) read := strings.NewReader(fmt.Sprintf(`{ "auths":{"docker.io":{"auth":"%s","email":"user@example.com"}}, "detachKeys": "ctrl-e,e", "HttpHeaders": { "MyHeader": "MyValue" }}`, auth)) ac, err := NewAuthConfigurations(read) if err != nil { t.Error(err) } c, ok := ac.Configs["docker.io"] if !ok { t.Error("NewAuthConfigurations: Expected Configs to contain docker.io") } if got, want := c.Email, "user@example.com"; got != want { t.Errorf(`AuthConfigurations.Configs["docker.io"].Email: wrong result. Want %q. Got %q`, want, got) } if got, want := c.Username, "user"; got != want { t.Errorf(`AuthConfigurations.Configs["docker.io"].Username: wrong result. Want %q. Got %q`, want, got) } if got, want := c.Password, "pass"; got != want { t.Errorf(`AuthConfigurations.Configs["docker.io"].Password: wrong result. Want %q. Got %q`, want, got) } if got, want := c.ServerAddress, "docker.io"; got != want { t.Errorf(`AuthConfigurations.Configs["docker.io"].ServerAddress: wrong result. Want %q. Got %q`, want, got) } } func TestAuthConfig(t *testing.T) { t.Parallel() auth := base64.StdEncoding.EncodeToString([]byte("user:pass")) read := strings.NewReader(fmt.Sprintf(`{"auths":{"docker.io":{"auth":"%s","email":"user@example.com"}}}`, auth)) ac, err := NewAuthConfigurations(read) if err != nil { t.Error(err) } c, ok := ac.Configs["docker.io"] if !ok { t.Error("NewAuthConfigurations: Expected Configs to contain docker.io") } if got, want := c.Email, "user@example.com"; got != want { t.Errorf(`AuthConfigurations.Configs["docker.io"].Email: wrong result. Want %q. Got %q`, want, got) } if got, want := c.Username, "user"; got != want { t.Errorf(`AuthConfigurations.Configs["docker.io"].Username: wrong result. Want %q. Got %q`, want, got) } if got, want := c.Password, "pass"; got != want { t.Errorf(`AuthConfigurations.Configs["docker.io"].Password: wrong result. Want %q. Got %q`, want, got) } if got, want := c.ServerAddress, "docker.io"; got != want { t.Errorf(`AuthConfigurations.Configs["docker.io"].ServerAddress: wrong result. Want %q. Got %q`, want, got) } } func TestAuthConfigIdentityToken(t *testing.T) { t.Parallel() auth := base64.StdEncoding.EncodeToString([]byte("someuser:")) read := strings.NewReader(fmt.Sprintf(`{"auths":{"docker.io":{"auth":"%s","identitytoken":"sometoken"}}}`, auth)) ac, err := NewAuthConfigurations(read) if err != nil { t.Fatal(err) } c, ok := ac.Configs["docker.io"] if !ok { t.Error("NewAuthConfigurations: Expected Configs to contain docker.io") } if got, want := c.Username, "someuser"; got != want { t.Errorf(`AuthConfigurations.Configs["docker.io"].Username: wrong result. Want %q. Got %q`, want, got) } if got, want := c.IdentityToken, "sometoken"; got != want { t.Errorf(`AuthConfigurations.Configs["docker.io"].IdentityToken: wrong result. Want %q. Got %q`, want, got) } } func TestAuthConfigRegistryToken(t *testing.T) { t.Parallel() auth := base64.StdEncoding.EncodeToString([]byte("someuser:")) read := strings.NewReader(fmt.Sprintf(`{"auths":{"docker.io":{"auth":"%s","registrytoken":"sometoken"}}}`, auth)) ac, err := NewAuthConfigurations(read) if err != nil { t.Fatal(err) } c, ok := ac.Configs["docker.io"] if !ok { t.Error("NewAuthConfigurations: Expected Configs to contain docker.io") } if got, want := c.Username, "someuser"; got != want { t.Errorf(`AuthConfigurations.Configs["docker.io"].Username: wrong result. Want %q. Got %q`, want, got) } if got, want := c.RegistryToken, "sometoken"; got != want { t.Errorf(`AuthConfigurations.Configs["docker.io"].RegistryToken: wrong result. Want %q. Got %q`, want, got) } } func TestAuthCheck(t *testing.T) { t.Parallel() fakeRT := &FakeRoundTripper{status: http.StatusOK} client := newTestClient(fakeRT) if _, err := client.AuthCheck(nil); err == nil { t.Fatalf("expected error on nil auth config") } // test good auth if _, err := client.AuthCheck(&AuthConfiguration{}); err != nil { t.Fatal(err) } *fakeRT = FakeRoundTripper{status: http.StatusUnauthorized} if _, err := client.AuthCheck(&AuthConfiguration{}); err == nil { t.Fatal("expected failure from unauthorized auth") } } func TestAuthConfigurationsMerge(t *testing.T) { t.Parallel() tests := []struct { name string left AuthConfigurations right AuthConfigurations expected AuthConfigurations }{ { name: "empty configs", expected: AuthConfigurations{}, }, { name: "empty left config", right: AuthConfigurations{ Configs: map[string]AuthConfiguration{ "docker.io": {Email: "user@example.com"}, }, }, expected: AuthConfigurations{ Configs: map[string]AuthConfiguration{ "docker.io": {Email: "user@example.com"}, }, }, }, { name: "empty right config", left: AuthConfigurations{ Configs: map[string]AuthConfiguration{ "docker.io": {Email: "user@example.com"}, }, }, expected: AuthConfigurations{ Configs: map[string]AuthConfiguration{ "docker.io": {Email: "user@example.com"}, }, }, }, { name: "no conflicts", left: AuthConfigurations{ Configs: map[string]AuthConfiguration{ "docker.io": {Email: "user@example.com"}, }, }, right: AuthConfigurations{ Configs: map[string]AuthConfiguration{ "us.gcr.io": {Email: "user@google.com"}, }, }, expected: AuthConfigurations{ Configs: map[string]AuthConfiguration{ "docker.io": {Email: "user@example.com"}, "us.gcr.io": {Email: "user@google.com"}, }, }, }, { name: "no conflicts", left: AuthConfigurations{ Configs: map[string]AuthConfiguration{ "docker.io": {Email: "user@example.com"}, "us.gcr.io": {Email: "google-user@example.com"}, }, }, right: AuthConfigurations{ Configs: map[string]AuthConfiguration{ "us.gcr.io": {Email: "user@google.com"}, }, }, expected: AuthConfigurations{ Configs: map[string]AuthConfiguration{ "docker.io": {Email: "user@example.com"}, "us.gcr.io": {Email: "google-user@example.com"}, }, }, }, } for _, test := range tests { test := test t.Run(test.name, func(t *testing.T) { t.Parallel() test.left.merge(test.right) if !reflect.DeepEqual(test.left, test.expected) { t.Errorf("wrong configuration map after merge\nwant %#v\ngot %#v", test.expected, test.left) } }) } } func TestGetHelperProviderFromDockerCfg(t *testing.T) { t.Parallel() tmpDir, err := os.MkdirTemp("", "go-dockerclient-creds-test") if err != nil { t.Fatalf("Unable to create temporary directory for TestGetHelperProviderFromDockerCfg: %s", err) } defer os.RemoveAll(tmpDir) expectedProvider := "ecr-login-test" content := fmt.Sprintf(`{"credsStore": "ecr-login","credHelpers":{"docker.io":"%s"}}`, expectedProvider) configFile := path.Join(tmpDir, "docker_config") if err = os.WriteFile(configFile, []byte(content), 0o600); err != nil { t.Errorf("Error writing auth config for TestGetHelperProviderFromDockerCfg: %s", err) } configFileNotExists := path.Join(tmpDir, "do_not_exists") provider, err := getHelperProviderFromDockerCfg([]string{configFileNotExists, configFile}, "docker.io") if err != nil { t.Fatal(err) } if provider != expectedProvider { t.Errorf("wrong provider found: \nwant %s\ngot %s", expectedProvider, provider) } } func TestParseCredsDockerConfig(t *testing.T) { t.Parallel() tests := []struct { config []byte provider string registry string }{ { config: []byte(`{"credsStore": "ecr-login"}`), provider: "ecr-login", registry: "docker.io", }, { config: []byte(`{"credsStore": "ecr-login","credHelpers":{"docker.io":"ecr-login-test"}}`), provider: "ecr-login-test", registry: "docker.io", }, { config: []byte(`{"credsStore": "ecr-login","credHelpers":{"docker.io":"ecr-login-test"}}`), provider: "ecr-login", registry: "docker.io2", }, } for _, test := range tests { provider, err := parseCredsDockerConfig(test.config, test.registry) if err != nil { t.Fatal(err) } if provider != test.provider { t.Errorf("wrong provider found: \nwant %s\ngot %s", test.provider, provider) } } } go-dockerclient-1.12.0/build_test.go000066400000000000000000000100101465717111200173260ustar00rootroot00000000000000// Copyright 2014 go-dockerclient authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package docker import ( "bytes" "errors" "io" "net/http" "os" "path/filepath" "reflect" "testing" "github.com/docker/docker/pkg/archive" ) func TestBuildImageMultipleContextsError(t *testing.T) { t.Parallel() fakeRT := &FakeRoundTripper{message: "", status: http.StatusOK} client := newTestClient(fakeRT) var buf bytes.Buffer opts := BuildImageOptions{ Name: "testImage", NoCache: true, CacheFrom: []string{"a", "b", "c"}, SuppressOutput: true, RmTmpContainer: true, ForceRmTmpContainer: true, InputStream: &buf, OutputStream: &buf, ContextDir: "testing/data", } err := client.BuildImage(opts) if !errors.Is(err, ErrMultipleContexts) { t.Errorf("BuildImage: providing both InputStream and ContextDir should produce an error") } } func TestBuildImageContextDirDockerignoreParsing(t *testing.T) { t.Parallel() fakeRT := &FakeRoundTripper{message: "", status: http.StatusOK} client := newTestClient(fakeRT) if err := os.Symlink("doesnotexist", "testing/data/symlink"); err != nil { t.Errorf("error creating symlink on demand: %s", err) } defer func() { if err := os.Remove("testing/data/symlink"); err != nil { t.Errorf("error removing symlink on demand: %s", err) } }() workingdir, err := os.Getwd() if err != nil { t.Fatal(err) } var buf bytes.Buffer opts := BuildImageOptions{ Name: "testImage", NoCache: true, CacheFrom: []string{"a", "b", "c"}, SuppressOutput: true, RmTmpContainer: true, ForceRmTmpContainer: true, OutputStream: &buf, ContextDir: filepath.Join(workingdir, "testing", "data"), } err = client.BuildImage(opts) if err != nil { t.Fatal(err) } reqBody := fakeRT.requests[0].Body tmpdir, err := unpackBodyTarball(reqBody) if err != nil { t.Fatal(err) } defer func() { if err = os.RemoveAll(tmpdir); err != nil { t.Fatal(err) } }() files, err := os.ReadDir(tmpdir) if err != nil { t.Fatal(err) } foundFiles := []string{} for _, file := range files { foundFiles = append(foundFiles, file.Name()) } expectedFiles := []string{ ".dockerignore", "Dockerfile", "barfile", "ca.pem", "cert.pem", "key.pem", "server.pem", "serverkey.pem", "symlink", } if !reflect.DeepEqual(expectedFiles, foundFiles) { t.Errorf( "BuildImage: incorrect files sent in tarball to docker server\nexpected %+v, found %+v", expectedFiles, foundFiles, ) } } func TestBuildImageSendXRegistryConfig(t *testing.T) { t.Parallel() fakeRT := &FakeRoundTripper{message: "", status: http.StatusOK} client := newTestClient(fakeRT) var buf bytes.Buffer opts := BuildImageOptions{ Name: "testImage", NoCache: true, SuppressOutput: true, RmTmpContainer: true, ForceRmTmpContainer: true, OutputStream: &buf, ContextDir: "testing/data", AuthConfigs: AuthConfigurations{ Configs: map[string]AuthConfiguration{ "quay.io": { Username: "foo", Password: "bar", Email: "baz", ServerAddress: "quay.io", }, }, }, } encodedConfig := "eyJjb25maWdzIjp7InF1YXkuaW8iOnsidXNlcm5hbWUiOiJmb28iLCJwYXNzd29yZCI6ImJhciIsImVtYWlsIjoiYmF6Iiwic2VydmVyYWRkcmVzcyI6InF1YXkuaW8ifX19" if err := client.BuildImage(opts); err != nil { t.Fatal(err) } xRegistryConfig := fakeRT.requests[0].Header.Get("X-Registry-Config") if xRegistryConfig != encodedConfig { t.Errorf( "BuildImage: X-Registry-Config not set currectly: expected %q, got %q", encodedConfig, xRegistryConfig, ) } } func unpackBodyTarball(req io.Reader) (tmpdir string, err error) { tmpdir, err = os.MkdirTemp("", "go-dockerclient-test") if err != nil { return } err = archive.Untar(req, tmpdir, &archive.TarOptions{ Compression: archive.Uncompressed, NoLchown: true, }) return } go-dockerclient-1.12.0/change.go000066400000000000000000000016601465717111200164300ustar00rootroot00000000000000// Copyright 2014 go-dockerclient authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package docker import "fmt" // ChangeType is a type for constants indicating the type of change // in a container type ChangeType int const ( // ChangeModify is the ChangeType for container modifications ChangeModify ChangeType = iota // ChangeAdd is the ChangeType for additions to a container ChangeAdd // ChangeDelete is the ChangeType for deletions from a container ChangeDelete ) // Change represents a change in a container. // // See https://goo.gl/Wo0JJp for more details. type Change struct { Path string Kind ChangeType } func (change *Change) String() string { var kind string switch change.Kind { case ChangeModify: kind = "C" case ChangeAdd: kind = "A" case ChangeDelete: kind = "D" } return fmt.Sprintf("%s %s", kind, change.Path) } go-dockerclient-1.12.0/change_test.go000066400000000000000000000014151465717111200174650ustar00rootroot00000000000000// Copyright 2014 go-dockerclient authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package docker import "testing" func TestChangeString(t *testing.T) { t.Parallel() tests := []struct { change Change expected string }{ {Change{"/etc/passwd", ChangeModify}, "C /etc/passwd"}, {Change{"/etc/passwd", ChangeAdd}, "A /etc/passwd"}, {Change{"/etc/passwd", ChangeDelete}, "D /etc/passwd"}, {Change{"/etc/passwd", 33}, " /etc/passwd"}, } for _, tt := range tests { test := tt t.Run(test.expected, func(t *testing.T) { t.Parallel() if got := test.change.String(); got != test.expected { t.Errorf("Change.String(): want %q. Got %q.", test.expected, got) } }) } } go-dockerclient-1.12.0/client.go000066400000000000000000000774331465717111200164740ustar00rootroot00000000000000// Copyright 2013 go-dockerclient authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. // Package docker provides a client for the Docker remote API. // // See https://goo.gl/o2v3rk for more details on the remote API. package docker import ( "bufio" "bytes" "context" "crypto/tls" "crypto/x509" "encoding/json" "errors" "fmt" "io" "net" "net/http" "net/http/httputil" "net/url" "os" "path/filepath" "reflect" "runtime" "strconv" "strings" "sync/atomic" "time" "github.com/docker/docker/pkg/homedir" "github.com/docker/docker/pkg/jsonmessage" "github.com/docker/docker/pkg/stdcopy" ) const ( userAgent = "go-dockerclient" unixProtocol = "unix" namedPipeProtocol = "npipe" ) var ( // ErrInvalidEndpoint is returned when the endpoint is not a valid HTTP URL. ErrInvalidEndpoint = errors.New("invalid endpoint") // ErrConnectionRefused is returned when the client cannot connect to the given endpoint. ErrConnectionRefused = errors.New("cannot connect to Docker endpoint") // ErrInactivityTimeout is returned when a streamable call has been inactive for some time. ErrInactivityTimeout = errors.New("inactivity time exceeded timeout") apiVersion112, _ = NewAPIVersion("1.12") apiVersion118, _ = NewAPIVersion("1.18") apiVersion119, _ = NewAPIVersion("1.19") apiVersion121, _ = NewAPIVersion("1.21") apiVersion124, _ = NewAPIVersion("1.24") apiVersion125, _ = NewAPIVersion("1.25") apiVersion135, _ = NewAPIVersion("1.35") ) // APIVersion is an internal representation of a version of the Remote API. type APIVersion []int // NewAPIVersion returns an instance of APIVersion for the given string. // // The given string must be in the form .., where , // and are integer numbers. func NewAPIVersion(input string) (APIVersion, error) { if !strings.Contains(input, ".") { return nil, fmt.Errorf("unable to parse version %q", input) } raw := strings.Split(input, "-") arr := strings.Split(raw[0], ".") ret := make(APIVersion, len(arr)) var err error for i, val := range arr { ret[i], err = strconv.Atoi(val) if err != nil { return nil, fmt.Errorf("unable to parse version %q: %q is not an integer", input, val) } } return ret, nil } func (version APIVersion) String() string { parts := make([]string, len(version)) for i, val := range version { parts[i] = strconv.Itoa(val) } return strings.Join(parts, ".") } // LessThan is a function for comparing APIVersion structs. func (version APIVersion) LessThan(other APIVersion) bool { return version.compare(other) < 0 } // LessThanOrEqualTo is a function for comparing APIVersion structs. func (version APIVersion) LessThanOrEqualTo(other APIVersion) bool { return version.compare(other) <= 0 } // GreaterThan is a function for comparing APIVersion structs. func (version APIVersion) GreaterThan(other APIVersion) bool { return version.compare(other) > 0 } // GreaterThanOrEqualTo is a function for comparing APIVersion structs. func (version APIVersion) GreaterThanOrEqualTo(other APIVersion) bool { return version.compare(other) >= 0 } func (version APIVersion) compare(other APIVersion) int { for i, v := range version { if i <= len(other)-1 { otherVersion := other[i] if v < otherVersion { return -1 } else if v > otherVersion { return 1 } } } if len(version) > len(other) { return 1 } if len(version) < len(other) { return -1 } return 0 } // Client is the basic type of this package. It provides methods for // interaction with the API. type Client struct { SkipServerVersionCheck bool HTTPClient *http.Client TLSConfig *tls.Config Dialer Dialer endpoint string endpointURL *url.URL eventMonitor *eventMonitoringState requestedAPIVersion APIVersion serverAPIVersion APIVersion expectedAPIVersion APIVersion } // Dialer is an interface that allows network connections to be dialed // (net.Dialer fulfills this interface) and named pipes (a shim using // winio.DialPipe) type Dialer interface { Dial(network, address string) (net.Conn, error) } // NewClient returns a Client instance ready for communication with the given // server endpoint. It will use the latest remote API version available in the // server. func NewClient(endpoint string) (*Client, error) { client, err := NewVersionedClient(endpoint, "") if err != nil { return nil, err } client.SkipServerVersionCheck = true return client, nil } // NewTLSClient returns a Client instance ready for TLS communications with the givens // server endpoint, key and certificates . It will use the latest remote API version // available in the server. func NewTLSClient(endpoint string, cert, key, ca string) (*Client, error) { client, err := NewVersionedTLSClient(endpoint, cert, key, ca, "") if err != nil { return nil, err } client.SkipServerVersionCheck = true return client, nil } // NewTLSClientFromBytes returns a Client instance ready for TLS communications with the givens // server endpoint, key and certificates (passed inline to the function as opposed to being // read from a local file). It will use the latest remote API version available in the server. func NewTLSClientFromBytes(endpoint string, certPEMBlock, keyPEMBlock, caPEMCert []byte) (*Client, error) { client, err := NewVersionedTLSClientFromBytes(endpoint, certPEMBlock, keyPEMBlock, caPEMCert, "") if err != nil { return nil, err } client.SkipServerVersionCheck = true return client, nil } // NewVersionedClient returns a Client instance ready for communication with // the given server endpoint, using a specific remote API version. func NewVersionedClient(endpoint string, apiVersionString string) (*Client, error) { u, err := parseEndpoint(endpoint, false) if err != nil { return nil, err } var requestedAPIVersion APIVersion if strings.Contains(apiVersionString, ".") { requestedAPIVersion, err = NewAPIVersion(apiVersionString) if err != nil { return nil, err } } c := &Client{ HTTPClient: defaultClient(), Dialer: &net.Dialer{}, endpoint: endpoint, endpointURL: u, eventMonitor: new(eventMonitoringState), requestedAPIVersion: requestedAPIVersion, } c.initializeNativeClient(defaultTransport) return c, nil } // WithTransport replaces underlying HTTP client of Docker Client by accepting // a function that returns pointer to a transport object. func (c *Client) WithTransport(trFunc func() *http.Transport) { c.initializeNativeClient(trFunc) } // NewVersionnedTLSClient is like NewVersionedClient, but with ann extra n. // // Deprecated: Use NewVersionedTLSClient instead. func NewVersionnedTLSClient(endpoint string, cert, key, ca, apiVersionString string) (*Client, error) { return NewVersionedTLSClient(endpoint, cert, key, ca, apiVersionString) } // NewVersionedTLSClient returns a Client instance ready for TLS communications with the givens // server endpoint, key and certificates, using a specific remote API version. func NewVersionedTLSClient(endpoint string, cert, key, ca, apiVersionString string) (*Client, error) { var certPEMBlock []byte var keyPEMBlock []byte var caPEMCert []byte if _, err := os.Stat(cert); !os.IsNotExist(err) { certPEMBlock, err = os.ReadFile(cert) if err != nil { return nil, err } } if _, err := os.Stat(key); !os.IsNotExist(err) { keyPEMBlock, err = os.ReadFile(key) if err != nil { return nil, err } } if _, err := os.Stat(ca); !os.IsNotExist(err) { caPEMCert, err = os.ReadFile(ca) if err != nil { return nil, err } } return NewVersionedTLSClientFromBytes(endpoint, certPEMBlock, keyPEMBlock, caPEMCert, apiVersionString) } // NewClientFromEnv returns a Client instance ready for communication created from // Docker's default logic for the environment variables DOCKER_HOST, DOCKER_TLS_VERIFY, DOCKER_CERT_PATH, // and DOCKER_API_VERSION. // // See https://github.com/docker/docker/blob/1f963af697e8df3a78217f6fdbf67b8123a7db94/docker/docker.go#L68. // See https://github.com/docker/compose/blob/81707ef1ad94403789166d2fe042c8a718a4c748/compose/cli/docker_client.py#L7. // See https://github.com/moby/moby/blob/28d7dba41d0c0d9c7f0dafcc79d3c59f2b3f5dc3/client/options.go#L51 func NewClientFromEnv() (*Client, error) { apiVersionString := os.Getenv("DOCKER_API_VERSION") client, err := NewVersionedClientFromEnv(apiVersionString) if err != nil { return nil, err } client.SkipServerVersionCheck = apiVersionString == "" return client, nil } // NewVersionedClientFromEnv returns a Client instance ready for TLS communications created from // Docker's default logic for the environment variables DOCKER_HOST, DOCKER_TLS_VERIFY, and DOCKER_CERT_PATH, // and using a specific remote API version. // // See https://github.com/docker/docker/blob/1f963af697e8df3a78217f6fdbf67b8123a7db94/docker/docker.go#L68. // See https://github.com/docker/compose/blob/81707ef1ad94403789166d2fe042c8a718a4c748/compose/cli/docker_client.py#L7. func NewVersionedClientFromEnv(apiVersionString string) (*Client, error) { dockerEnv, err := getDockerEnv() if err != nil { return nil, err } dockerHost := dockerEnv.dockerHost if dockerEnv.dockerTLSVerify { parts := strings.SplitN(dockerEnv.dockerHost, "://", 2) if len(parts) != 2 { return nil, fmt.Errorf("could not split %s into two parts by ://", dockerHost) } cert := filepath.Join(dockerEnv.dockerCertPath, "cert.pem") key := filepath.Join(dockerEnv.dockerCertPath, "key.pem") ca := filepath.Join(dockerEnv.dockerCertPath, "ca.pem") return NewVersionedTLSClient(dockerEnv.dockerHost, cert, key, ca, apiVersionString) } return NewVersionedClient(dockerEnv.dockerHost, apiVersionString) } // NewVersionedTLSClientFromBytes returns a Client instance ready for TLS communications with the givens // server endpoint, key and certificates (passed inline to the function as opposed to being // read from a local file), using a specific remote API version. func NewVersionedTLSClientFromBytes(endpoint string, certPEMBlock, keyPEMBlock, caPEMCert []byte, apiVersionString string) (*Client, error) { u, err := parseEndpoint(endpoint, true) if err != nil { return nil, err } var requestedAPIVersion APIVersion if strings.Contains(apiVersionString, ".") { requestedAPIVersion, err = NewAPIVersion(apiVersionString) if err != nil { return nil, err } } tlsConfig := &tls.Config{MinVersion: tls.VersionTLS12} if certPEMBlock != nil && keyPEMBlock != nil { tlsCert, err := tls.X509KeyPair(certPEMBlock, keyPEMBlock) if err != nil { return nil, err } tlsConfig.Certificates = []tls.Certificate{tlsCert} } if caPEMCert == nil { tlsConfig.InsecureSkipVerify = true } else { caPool := x509.NewCertPool() if !caPool.AppendCertsFromPEM(caPEMCert) { return nil, errors.New("could not add RootCA pem") } tlsConfig.RootCAs = caPool } tr := defaultTransport() tr.TLSClientConfig = tlsConfig if err != nil { return nil, err } c := &Client{ HTTPClient: &http.Client{Transport: tr}, TLSConfig: tlsConfig, Dialer: &net.Dialer{}, endpoint: endpoint, endpointURL: u, eventMonitor: new(eventMonitoringState), requestedAPIVersion: requestedAPIVersion, } c.initializeNativeClient(defaultTransport) return c, nil } // SetTimeout takes a timeout and applies it to the HTTPClient. It should not // be called concurrently with any other Client methods. func (c *Client) SetTimeout(t time.Duration) { if c.HTTPClient != nil { c.HTTPClient.Timeout = t } } func (c *Client) checkAPIVersion() error { serverAPIVersionString, err := c.getServerAPIVersionString() if err != nil { return err } c.serverAPIVersion, err = NewAPIVersion(serverAPIVersionString) if err != nil { return err } if c.requestedAPIVersion == nil { c.expectedAPIVersion = c.serverAPIVersion } else { c.expectedAPIVersion = c.requestedAPIVersion } return nil } // Endpoint returns the current endpoint. It's useful for getting the endpoint // when using functions that get this data from the environment (like // NewClientFromEnv. func (c *Client) Endpoint() string { return c.endpoint } // Ping pings the docker server // // See https://goo.gl/wYfgY1 for more details. func (c *Client) Ping() error { return c.PingWithContext(context.TODO()) } // PingWithContext pings the docker server // The context object can be used to cancel the ping request. // // See https://goo.gl/wYfgY1 for more details. func (c *Client) PingWithContext(ctx context.Context) error { path := "/_ping" resp, err := c.do(http.MethodGet, path, doOptions{context: ctx}) if err != nil { return err } if resp.StatusCode != http.StatusOK { return newError(resp) } resp.Body.Close() return nil } func (c *Client) getServerAPIVersionString() (version string, err error) { resp, err := c.do(http.MethodGet, "/version", doOptions{}) if err != nil { return "", err } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { return "", fmt.Errorf("received unexpected status %d while trying to retrieve the server version", resp.StatusCode) } var versionResponse map[string]any if err := json.NewDecoder(resp.Body).Decode(&versionResponse); err != nil { return "", err } if version, ok := (versionResponse["ApiVersion"]).(string); ok { return version, nil } return "", nil } type doOptions struct { data any forceJSON bool headers map[string]string context context.Context } func (c *Client) do(method, path string, doOptions doOptions) (*http.Response, error) { var params io.Reader if doOptions.data != nil || doOptions.forceJSON { buf, err := json.Marshal(doOptions.data) if err != nil { return nil, err } params = bytes.NewBuffer(buf) } if path != "/version" && !c.SkipServerVersionCheck && c.expectedAPIVersion == nil { err := c.checkAPIVersion() if err != nil { return nil, err } } protocol := c.endpointURL.Scheme var u string switch protocol { case unixProtocol, namedPipeProtocol: u = c.getFakeNativeURL(path) default: u = c.getURL(path) } req, err := http.NewRequest(method, u, params) if err != nil { return nil, err } req.Header.Set("User-Agent", userAgent) if doOptions.data != nil { req.Header.Set("Content-Type", "application/json") } else if method == http.MethodPost { req.Header.Set("Content-Type", "plain/text") } for k, v := range doOptions.headers { req.Header.Set(k, v) } ctx := doOptions.context if ctx == nil { ctx = context.Background() } resp, err := c.HTTPClient.Do(req.WithContext(ctx)) if err != nil { if strings.Contains(err.Error(), "connection refused") { return nil, ErrConnectionRefused } return nil, chooseError(ctx, err) } if resp.StatusCode < http.StatusOK || resp.StatusCode >= http.StatusBadRequest { return nil, newError(resp) } return resp, nil } type streamOptions struct { setRawTerminal bool rawJSONStream bool useJSONDecoder bool headers map[string]string in io.Reader stdout io.Writer stderr io.Writer reqSent chan struct{} // timeout is the initial connection timeout timeout time.Duration // Timeout with no data is received, it's reset every time new data // arrives inactivityTimeout time.Duration context context.Context } func chooseError(ctx context.Context, err error) error { select { case <-ctx.Done(): return context.Cause(ctx) default: return err } } func (c *Client) stream(method, path string, streamOptions streamOptions) error { if (method == http.MethodPost || method == http.MethodPut) && streamOptions.in == nil { streamOptions.in = bytes.NewReader(nil) } if path != "/version" && !c.SkipServerVersionCheck && c.expectedAPIVersion == nil { err := c.checkAPIVersion() if err != nil { return err } } return c.streamURL(method, c.getURL(path), streamOptions) } func (c *Client) streamURL(method, url string, streamOptions streamOptions) error { if (method == http.MethodPost || method == http.MethodPut) && streamOptions.in == nil { streamOptions.in = bytes.NewReader(nil) } if !c.SkipServerVersionCheck && c.expectedAPIVersion == nil { err := c.checkAPIVersion() if err != nil { return err } } // make a sub-context so that our active cancellation does not affect parent ctx := streamOptions.context if ctx == nil { ctx = context.Background() } subCtx, cancelRequest := context.WithCancel(ctx) defer cancelRequest() req, err := http.NewRequestWithContext(ctx, method, url, streamOptions.in) if err != nil { return err } req.Header.Set("User-Agent", userAgent) if method == http.MethodPost { req.Header.Set("Content-Type", "plain/text") } for key, val := range streamOptions.headers { req.Header.Set(key, val) } var resp *http.Response protocol := c.endpointURL.Scheme address := c.endpointURL.Path if streamOptions.stdout == nil { streamOptions.stdout = io.Discard } if streamOptions.stderr == nil { streamOptions.stderr = io.Discard } if protocol == unixProtocol || protocol == namedPipeProtocol { var dial net.Conn dial, err = c.Dialer.Dial(protocol, address) if err != nil { return err } go func() { <-subCtx.Done() dial.Close() }() breader := bufio.NewReader(dial) err = req.Write(dial) if err != nil { return chooseError(subCtx, err) } // ReadResponse may hang if server does not replay if streamOptions.timeout > 0 { dial.SetDeadline(time.Now().Add(streamOptions.timeout)) } if streamOptions.reqSent != nil { close(streamOptions.reqSent) } if resp, err = http.ReadResponse(breader, req); err != nil { // Cancel timeout for future I/O operations if streamOptions.timeout > 0 { dial.SetDeadline(time.Time{}) } if strings.Contains(err.Error(), "connection refused") { return ErrConnectionRefused } return chooseError(subCtx, err) } defer resp.Body.Close() } else { if resp, err = c.HTTPClient.Do(req.WithContext(subCtx)); err != nil { if strings.Contains(err.Error(), "connection refused") { return ErrConnectionRefused } return chooseError(subCtx, err) } defer resp.Body.Close() if streamOptions.reqSent != nil { close(streamOptions.reqSent) } } if resp.StatusCode < 200 || resp.StatusCode >= 400 { return newError(resp) } var canceled uint32 if streamOptions.inactivityTimeout > 0 { var ch chan<- struct{} resp.Body, ch = handleInactivityTimeout(resp.Body, streamOptions.inactivityTimeout, cancelRequest, &canceled) defer close(ch) } err = handleStreamResponse(resp, &streamOptions) if err != nil { if atomic.LoadUint32(&canceled) != 0 { return ErrInactivityTimeout } return chooseError(subCtx, err) } return nil } func handleStreamResponse(resp *http.Response, streamOptions *streamOptions) error { var err error if !streamOptions.useJSONDecoder && resp.Header.Get("Content-Type") != "application/json" { if streamOptions.setRawTerminal { _, err = io.Copy(streamOptions.stdout, resp.Body) } else { _, err = stdcopy.StdCopy(streamOptions.stdout, streamOptions.stderr, resp.Body) } return err } // if we want to get raw json stream, just copy it back to output // without decoding it if streamOptions.rawJSONStream { _, err = io.Copy(streamOptions.stdout, resp.Body) return err } if st, ok := streamOptions.stdout.(stream); ok { err = jsonmessage.DisplayJSONMessagesToStream(resp.Body, st, nil) } else { err = jsonmessage.DisplayJSONMessagesStream(resp.Body, streamOptions.stdout, 0, false, nil) } return err } type stream interface { io.Writer FD() uintptr IsTerminal() bool } type proxyReader struct { io.ReadCloser calls uint64 } func (p *proxyReader) callCount() uint64 { return atomic.LoadUint64(&p.calls) } func (p *proxyReader) Read(data []byte) (int, error) { atomic.AddUint64(&p.calls, 1) return p.ReadCloser.Read(data) } func handleInactivityTimeout(reader io.ReadCloser, timeout time.Duration, cancelRequest func(), canceled *uint32) (io.ReadCloser, chan<- struct{}) { done := make(chan struct{}) proxyReader := &proxyReader{ReadCloser: reader} go func() { var lastCallCount uint64 for { select { case <-time.After(timeout): case <-done: return } curCallCount := proxyReader.callCount() if curCallCount == lastCallCount { atomic.AddUint32(canceled, 1) cancelRequest() return } lastCallCount = curCallCount } }() return proxyReader, done } type hijackOptions struct { success chan struct{} setRawTerminal bool in io.Reader stdout io.Writer stderr io.Writer data any } // CloseWaiter is an interface with methods for closing the underlying resource // and then waiting for it to finish processing. type CloseWaiter interface { io.Closer Wait() error } type waiterFunc func() error func (w waiterFunc) Wait() error { return w() } type closerFunc func() error func (c closerFunc) Close() error { return c() } func (c *Client) hijack(method, path string, hijackOptions hijackOptions) (CloseWaiter, error) { if path != "/version" && !c.SkipServerVersionCheck && c.expectedAPIVersion == nil { err := c.checkAPIVersion() if err != nil { return nil, err } } var params io.Reader if hijackOptions.data != nil { buf, err := json.Marshal(hijackOptions.data) if err != nil { return nil, err } params = bytes.NewBuffer(buf) } req, err := http.NewRequest(method, c.getURL(path), params) if err != nil { return nil, err } req.Header.Set("Content-Type", "application/json") req.Header.Set("Connection", "Upgrade") req.Header.Set("Upgrade", "tcp") protocol := c.endpointURL.Scheme address := c.endpointURL.Path if protocol != unixProtocol && protocol != namedPipeProtocol { protocol = "tcp" address = c.endpointURL.Host } var dial net.Conn if c.TLSConfig != nil && protocol != unixProtocol && protocol != namedPipeProtocol { netDialer, ok := c.Dialer.(*net.Dialer) if !ok { return nil, ErrTLSNotSupported } dial, err = tlsDialWithDialer(netDialer, protocol, address, c.TLSConfig) if err != nil { return nil, err } } else { dial, err = c.Dialer.Dial(protocol, address) if err != nil { return nil, err } } errs := make(chan error, 1) quit := make(chan struct{}) go func() { //lint:ignore SA1019 the alternative doesn't quite work, so keep using the deprecated thing. clientconn := httputil.NewClientConn(dial, nil) defer clientconn.Close() clientconn.Do(req) if hijackOptions.success != nil { hijackOptions.success <- struct{}{} <-hijackOptions.success } rwc, br := clientconn.Hijack() defer rwc.Close() errChanOut := make(chan error, 1) errChanIn := make(chan error, 2) if hijackOptions.stdout == nil && hijackOptions.stderr == nil { close(errChanOut) } else { // Only copy if hijackOptions.stdout and/or hijackOptions.stderr is actually set. // Otherwise, if the only stream you care about is stdin, your attach session // will "hang" until the container terminates, even though you're not reading // stdout/stderr if hijackOptions.stdout == nil { hijackOptions.stdout = io.Discard } if hijackOptions.stderr == nil { hijackOptions.stderr = io.Discard } go func() { defer func() { if hijackOptions.in != nil { if closer, ok := hijackOptions.in.(io.Closer); ok { closer.Close() } errChanIn <- nil } }() var err error if hijackOptions.setRawTerminal { _, err = io.Copy(hijackOptions.stdout, br) } else { _, err = stdcopy.StdCopy(hijackOptions.stdout, hijackOptions.stderr, br) } errChanOut <- err }() } go func() { var err error if hijackOptions.in != nil { _, err = io.Copy(rwc, hijackOptions.in) } errChanIn <- err rwc.(interface { CloseWrite() error }).CloseWrite() }() var errIn error select { case errIn = <-errChanIn: case <-quit: } var errOut error select { case errOut = <-errChanOut: case <-quit: } if errIn != nil { errs <- errIn } else { errs <- errOut } }() return struct { closerFunc waiterFunc }{ closerFunc(func() error { close(quit); return nil }), waiterFunc(func() error { return <-errs }), }, nil } func (c *Client) getURL(path string) string { urlStr := strings.TrimRight(c.endpointURL.String(), "/") if c.endpointURL.Scheme == unixProtocol || c.endpointURL.Scheme == namedPipeProtocol { urlStr = "" } if c.requestedAPIVersion != nil { return fmt.Sprintf("%s/v%s%s", urlStr, c.requestedAPIVersion, path) } return fmt.Sprintf("%s%s", urlStr, path) } func (c *Client) getPath(basepath string, opts any) (string, error) { queryStr, requiredAPIVersion := queryStringVersion(opts) return c.pathVersionCheck(basepath, queryStr, requiredAPIVersion) } func (c *Client) pathVersionCheck(basepath, queryStr string, requiredAPIVersion APIVersion) (string, error) { urlStr := strings.TrimRight(c.endpointURL.String(), "/") if c.endpointURL.Scheme == unixProtocol || c.endpointURL.Scheme == namedPipeProtocol { urlStr = "" } if c.requestedAPIVersion != nil { if c.requestedAPIVersion.GreaterThanOrEqualTo(requiredAPIVersion) { return fmt.Sprintf("%s/v%s%s?%s", urlStr, c.requestedAPIVersion, basepath, queryStr), nil } return "", fmt.Errorf("API %s requires version %s, requested version %s is insufficient", basepath, requiredAPIVersion, c.requestedAPIVersion) } if requiredAPIVersion != nil { return fmt.Sprintf("%s/v%s%s?%s", urlStr, requiredAPIVersion, basepath, queryStr), nil } return fmt.Sprintf("%s%s?%s", urlStr, basepath, queryStr), nil } // getFakeNativeURL returns the URL needed to make an HTTP request over a UNIX // domain socket to the given path. func (c *Client) getFakeNativeURL(path string) string { u := *c.endpointURL // Copy. // Override URL so that net/http will not complain. u.Scheme = "http" u.Host = "unix.sock" // Doesn't matter what this is - it's not used. u.Path = "" urlStr := strings.TrimRight(u.String(), "/") if c.requestedAPIVersion != nil { return fmt.Sprintf("%s/v%s%s", urlStr, c.requestedAPIVersion, path) } return fmt.Sprintf("%s%s", urlStr, path) } func queryStringVersion(opts any) (string, APIVersion) { if opts == nil { return "", nil } value := reflect.ValueOf(opts) if value.Kind() == reflect.Ptr { value = value.Elem() } if value.Kind() != reflect.Struct { return "", nil } var apiVersion APIVersion items := url.Values(map[string][]string{}) for i := 0; i < value.NumField(); i++ { field := value.Type().Field(i) if field.PkgPath != "" { continue } key := field.Tag.Get("qs") if key == "" { key = strings.ToLower(field.Name) } else if key == "-" { continue } if addQueryStringValue(items, key, value.Field(i)) { verstr := field.Tag.Get("ver") if verstr != "" { ver, _ := NewAPIVersion(verstr) if apiVersion == nil { apiVersion = ver } else if ver.GreaterThan(apiVersion) { apiVersion = ver } } } } return items.Encode(), apiVersion } func queryString(opts any) string { s, _ := queryStringVersion(opts) return s } func addQueryStringValue(items url.Values, key string, v reflect.Value) bool { switch v.Kind() { case reflect.Bool: if v.Bool() { items.Add(key, "1") return true } case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: if v.Int() > 0 { items.Add(key, strconv.FormatInt(v.Int(), 10)) return true } case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: if v.Uint() > 0 { items.Add(key, strconv.FormatUint(v.Uint(), 10)) return true } case reflect.Float32, reflect.Float64: if v.Float() > 0 { items.Add(key, strconv.FormatFloat(v.Float(), 'f', -1, 64)) return true } case reflect.String: if v.String() != "" { items.Add(key, v.String()) return true } case reflect.Ptr: if !v.IsNil() { if b, err := json.Marshal(v.Interface()); err == nil { items.Add(key, string(b)) return true } } case reflect.Map: if len(v.MapKeys()) > 0 { if b, err := json.Marshal(v.Interface()); err == nil { items.Add(key, string(b)) return true } } case reflect.Array, reflect.Slice: vLen := v.Len() var valuesAdded int if vLen > 0 { for i := 0; i < vLen; i++ { if addQueryStringValue(items, key, v.Index(i)) { valuesAdded++ } } } return valuesAdded > 0 } return false } // Error represents failures in the API. It represents a failure from the API. type Error struct { Status int Message string } func newError(resp *http.Response) *Error { type ErrMsg struct { Message string `json:"message"` } defer resp.Body.Close() data, err := io.ReadAll(resp.Body) if err != nil { return &Error{Status: resp.StatusCode, Message: fmt.Sprintf("cannot read body, err: %v", err)} } var emsg ErrMsg err = json.Unmarshal(data, &emsg) if err != nil { return &Error{Status: resp.StatusCode, Message: string(data)} } return &Error{Status: resp.StatusCode, Message: emsg.Message} } func (e *Error) Error() string { return fmt.Sprintf("API error (%d): %s", e.Status, e.Message) } func parseEndpoint(endpoint string, tls bool) (*url.URL, error) { if endpoint != "" && !strings.Contains(endpoint, "://") { endpoint = "tcp://" + endpoint } u, err := url.Parse(endpoint) if err != nil { return nil, ErrInvalidEndpoint } if tls && u.Scheme != "unix" { u.Scheme = "https" } switch u.Scheme { case unixProtocol, namedPipeProtocol: return u, nil case "http", "https", "tcp": _, port, err := net.SplitHostPort(u.Host) if err != nil { var e *net.AddrError if errors.As(err, &e) { if e.Err == "missing port in address" { return u, nil } } return nil, ErrInvalidEndpoint } number, err := strconv.ParseInt(port, 10, 64) if err == nil && number > 0 && number < 65536 { if u.Scheme == "tcp" { if tls { u.Scheme = "https" } else { u.Scheme = "http" } } return u, nil } return nil, ErrInvalidEndpoint default: return nil, ErrInvalidEndpoint } } type dockerEnv struct { dockerHost string dockerTLSVerify bool dockerCertPath string } func getDockerEnv() (*dockerEnv, error) { dockerHost := os.Getenv("DOCKER_HOST") var err error if dockerHost == "" { dockerHost = defaultHost } dockerTLSVerify := os.Getenv("DOCKER_TLS_VERIFY") != "" var dockerCertPath string if dockerTLSVerify { dockerCertPath = os.Getenv("DOCKER_CERT_PATH") if dockerCertPath == "" { home := homedir.Get() if home == "" { return nil, errors.New("environment variable HOME must be set if DOCKER_CERT_PATH is not set") } dockerCertPath = filepath.Join(home, ".docker") dockerCertPath, err = filepath.Abs(dockerCertPath) if err != nil { return nil, err } } } return &dockerEnv{ dockerHost: dockerHost, dockerTLSVerify: dockerTLSVerify, dockerCertPath: dockerCertPath, }, nil } // defaultTransport returns a new http.Transport with similar default values to // http.DefaultTransport, but with idle connections and keepalives disabled. func defaultTransport() *http.Transport { transport := defaultPooledTransport() transport.DisableKeepAlives = true transport.MaxIdleConnsPerHost = -1 return transport } // defaultPooledTransport returns a new http.Transport with similar default // values to http.DefaultTransport. Do not use this for transient transports as // it can leak file descriptors over time. Only use this for transports that // will be re-used for the same host(s). func defaultPooledTransport() *http.Transport { transport := &http.Transport{ Proxy: http.ProxyFromEnvironment, DialContext: (&net.Dialer{ Timeout: 30 * time.Second, KeepAlive: 30 * time.Second, }).DialContext, MaxIdleConns: 100, IdleConnTimeout: 90 * time.Second, TLSHandshakeTimeout: 10 * time.Second, ExpectContinueTimeout: 1 * time.Second, MaxIdleConnsPerHost: runtime.GOMAXPROCS(0) + 1, } return transport } // defaultClient returns a new http.Client with similar default values to // http.Client, but with a non-shared Transport, idle connections disabled, and // keepalives disabled. func defaultClient() *http.Client { return &http.Client{ Transport: defaultTransport(), } } go-dockerclient-1.12.0/client_stress_test.go000066400000000000000000000072141465717111200211240ustar00rootroot00000000000000// Copyright 2017 go-dockerclient authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. //go:build !windows package docker import ( "fmt" "net/http" "net/http/httptest" "os" "reflect" "slices" "sync" "testing" "time" ) func TestClientDoConcurrentStress(t *testing.T) { t.Parallel() var reqs []*http.Request var mu sync.Mutex handler := http.HandlerFunc(func(_ http.ResponseWriter, r *http.Request) { mu.Lock() reqs = append(reqs, r) mu.Unlock() }) var nativeSrvs []*httptest.Server for i := 0; i < 3; i++ { srv, cleanup, err := newNativeServer(handler) if err != nil { t.Fatal(err) } defer cleanup() nativeSrvs = append(nativeSrvs, srv) } tests := []struct { testCase string srv *httptest.Server scheme string withTimeout bool withTLSServer bool withTLSClient bool }{ {testCase: "http server", srv: httptest.NewUnstartedServer(handler), scheme: "http"}, {testCase: "native server", srv: nativeSrvs[0], scheme: nativeProtocol}, {testCase: "http with timeout", srv: httptest.NewUnstartedServer(handler), scheme: "http", withTimeout: true}, {testCase: "native with timeout", srv: nativeSrvs[1], scheme: nativeProtocol, withTimeout: true}, {testCase: "http with tls", srv: httptest.NewUnstartedServer(handler), scheme: "https", withTLSServer: true, withTLSClient: true}, {testCase: "native with client-only tls", srv: nativeSrvs[2], scheme: nativeProtocol, withTLSServer: false, withTLSClient: nativeProtocol == unixProtocol}, // TLS client only works with unix protocol } for _, tt := range tests { tt := tt t.Run(tt.testCase, func(t *testing.T) { reqs = nil var client *Client var err error endpoint := tt.scheme + "://" + tt.srv.Listener.Addr().String() if tt.withTLSServer { tt.srv.StartTLS() } else { tt.srv.Start() } defer tt.srv.Close() if tt.withTLSClient { certPEMBlock, certErr := os.ReadFile("testing/data/cert.pem") if certErr != nil { t.Fatal(certErr) } keyPEMBlock, certErr := os.ReadFile("testing/data/key.pem") if certErr != nil { t.Fatal(certErr) } client, err = NewTLSClientFromBytes(endpoint, certPEMBlock, keyPEMBlock, nil) } else { client, err = NewClient(endpoint) } if err != nil { t.Fatal(err) } if tt.withTimeout { client.SetTimeout(time.Minute) } n := 50 wg := sync.WaitGroup{} var paths []string errsCh := make(chan error, 3*n) waiters := make(chan CloseWaiter, n) for i := 0; i < n; i++ { path := fmt.Sprintf("/%05d", i) paths = append(paths, http.MethodGet+path) paths = append(paths, http.MethodPost+path) paths = append(paths, "HEAD"+path) wg.Add(1) go func() { defer wg.Done() _, clientErr := client.do(http.MethodGet, path, doOptions{}) if clientErr != nil { errsCh <- clientErr } clientErr = client.stream(http.MethodPost, path, streamOptions{}) if clientErr != nil { errsCh <- clientErr } cw, clientErr := client.hijack("HEAD", path, hijackOptions{}) if clientErr != nil { errsCh <- clientErr } else { waiters <- cw } }() } wg.Wait() close(errsCh) close(waiters) for cw := range waiters { cw.Wait() cw.Close() } for err = range errsCh { t.Error(err) } var reqPaths []string for _, r := range reqs { reqPaths = append(reqPaths, r.Method+r.URL.Path) } slices.Sort(paths) slices.Sort(reqPaths) if !reflect.DeepEqual(reqPaths, paths) { t.Fatalf("expected server request paths to equal %v, got: %v", paths, reqPaths) } }) } } go-dockerclient-1.12.0/client_test.go000066400000000000000000000740371465717111200175300ustar00rootroot00000000000000// Copyright 2013 go-dockerclient authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package docker import ( "bytes" "context" "errors" "fmt" "io" "net/http" "net/http/httptest" "net/url" "os" "path/filepath" "reflect" "strconv" "strings" "testing" "time" "golang.org/x/term" ) func TestNewAPIClient(t *testing.T) { t.Parallel() endpoint := "http://localhost:4243" client, err := NewClient(endpoint) if err != nil { t.Fatal(err) } if client.endpoint != endpoint { t.Errorf("Expected endpoint %s. Got %s.", endpoint, client.endpoint) } // test native endpoints endpoint = nativeRealEndpoint client, err = NewClient(endpoint) if err != nil { t.Fatal(err) } if client.endpoint != endpoint { t.Errorf("Expected endpoint %s. Got %s.", endpoint, client.endpoint) } if !client.SkipServerVersionCheck { t.Error("Expected SkipServerVersionCheck to be true, got false") } if client.requestedAPIVersion != nil { t.Errorf("Expected requestedAPIVersion to be nil, got %#v.", client.requestedAPIVersion) } } func newTLSClient(endpoint string) (*Client, error) { return NewTLSClient(endpoint, "testing/data/cert.pem", "testing/data/key.pem", "testing/data/ca.pem") } func TestNewTSLAPIClient(t *testing.T) { t.Parallel() endpoint := "https://localhost:4243" client, err := newTLSClient(endpoint) if err != nil { t.Fatal(err) } if client.endpoint != endpoint { t.Errorf("Expected endpoint %s. Got %s.", endpoint, client.endpoint) } if !client.SkipServerVersionCheck { t.Error("Expected SkipServerVersionCheck to be true, got false") } if client.requestedAPIVersion != nil { t.Errorf("Expected requestedAPIVersion to be nil, got %#v.", client.requestedAPIVersion) } } func TestNewVersionedClient(t *testing.T) { t.Parallel() endpoint := "http://localhost:4243" client, err := NewVersionedClient(endpoint, "1.12") if err != nil { t.Fatal(err) } if client.endpoint != endpoint { t.Errorf("Expected endpoint %s. Got %s.", endpoint, client.endpoint) } if reqVersion := client.requestedAPIVersion.String(); reqVersion != "1.12" { t.Errorf("Wrong requestAPIVersion. Want %q. Got %q.", "1.12", reqVersion) } if client.SkipServerVersionCheck { t.Error("Expected SkipServerVersionCheck to be false, got true") } } func TestNewVersionedClientFromEnv(t *testing.T) { endpoint := "tcp://localhost:2376" endpointURL := "http://localhost:2376" t.Setenv("DOCKER_HOST", endpoint) t.Setenv("DOCKER_TLS_VERIFY", "") client, err := NewVersionedClientFromEnv("1.12") if err != nil { t.Fatal(err) } if client.endpoint != endpoint { t.Errorf("Expected endpoint %q. Got %q.", endpoint, client.endpoint) } if client.endpointURL.String() != endpointURL { t.Errorf("Expected endpointURL %q. Got %q.", endpointURL, client.endpointURL.String()) } const expectedReqVersion = "1.12" if reqVersion := client.requestedAPIVersion.String(); reqVersion != expectedReqVersion { t.Errorf("Wrong requestAPIVersion. Want %q. Got %q.", expectedReqVersion, reqVersion) } if client.SkipServerVersionCheck { t.Error("Expected SkipServerVersionCheck to be false, got true") } } func TestNewVersionedClientFromEnvTLS(t *testing.T) { endpoint := "tcp://localhost:2376" endpointURL := "https://localhost:2376" base, _ := os.Getwd() t.Setenv("DOCKER_CERT_PATH", filepath.Join(base, "/testing/data/")) t.Setenv("DOCKER_HOST", endpoint) t.Setenv("DOCKER_TLS_VERIFY", "1") client, err := NewVersionedClientFromEnv("1.12") if err != nil { t.Fatal(err) } if client.endpoint != endpoint { t.Errorf("Expected endpoint %s. Got %s.", endpoint, client.endpoint) } if client.endpointURL.String() != endpointURL { t.Errorf("Expected endpointURL %s. Got %s.", endpoint, client.endpoint) } if reqVersion := client.requestedAPIVersion.String(); reqVersion != "1.12" { t.Errorf("Wrong requestAPIVersion. Want %q. Got %q.", "1.12", reqVersion) } if client.SkipServerVersionCheck { t.Error("Expected SkipServerVersionCheck to be false, got true") } } func TestNewTLSVersionedClient(t *testing.T) { t.Parallel() certPath := "testing/data/cert.pem" keyPath := "testing/data/key.pem" caPath := "testing/data/ca.pem" endpoint := "https://localhost:4243" client, err := NewVersionedTLSClient(endpoint, certPath, keyPath, caPath, "1.14") if err != nil { t.Fatal(err) } if client.endpoint != endpoint { t.Errorf("Expected endpoint %s. Got %s.", endpoint, client.endpoint) } if reqVersion := client.requestedAPIVersion.String(); reqVersion != "1.14" { t.Errorf("Wrong requestAPIVersion. Want %q. Got %q.", "1.14", reqVersion) } if client.SkipServerVersionCheck { t.Error("Expected SkipServerVersionCheck to be false, got true") } } func TestNewTLSVersionedClientNoClientCert(t *testing.T) { t.Parallel() certPath := "testing/data/cert_doesnotexist.pem" keyPath := "testing/data/key_doesnotexist.pem" caPath := "testing/data/ca.pem" endpoint := "https://localhost:4243" client, err := NewVersionedTLSClient(endpoint, certPath, keyPath, caPath, "1.14") if err != nil { t.Fatal(err) } if client.endpoint != endpoint { t.Errorf("Expected endpoint %s. Got %s.", endpoint, client.endpoint) } if reqVersion := client.requestedAPIVersion.String(); reqVersion != "1.14" { t.Errorf("Wrong requestAPIVersion. Want %q. Got %q.", "1.14", reqVersion) } if client.SkipServerVersionCheck { t.Error("Expected SkipServerVersionCheck to be false, got true") } } func TestNewTLSVersionedClientInvalidCA(t *testing.T) { t.Parallel() certPath := "testing/data/cert.pem" keyPath := "testing/data/key.pem" caPath := "testing/data/key.pem" endpoint := "https://localhost:4243" _, err := NewVersionedTLSClient(endpoint, certPath, keyPath, caPath, "1.14") if err == nil { t.Errorf("Expected invalid ca at %s", caPath) } } func TestNewTLSVersionedClientInvalidCANoClientCert(t *testing.T) { t.Parallel() certPath := "testing/data/cert_doesnotexist.pem" keyPath := "testing/data/key_doesnotexist.pem" caPath := "testing/data/key.pem" endpoint := "https://localhost:4243" _, err := NewVersionedTLSClient(endpoint, certPath, keyPath, caPath, "1.14") if err == nil { t.Errorf("Expected invalid ca at %s", caPath) } } func TestNewClientInvalidEndpoint(t *testing.T) { t.Parallel() cases := []string{ "htp://localhost:3243", "http://localhost:a", "", "http://localhost:8080:8383", "http://localhost:65536", "https://localhost:-20", } for _, c := range cases { testCase := c t.Run(testCase, func(t *testing.T) { t.Parallel() client, err := NewClient(testCase) if client != nil { t.Errorf("Want client for invalid endpoint, got %#v.", client) } if !errors.Is(err, ErrInvalidEndpoint) { t.Errorf("NewClient(%q): Got invalid error for invalid endpoint. Want %#v. Got %#v.", testCase, ErrInvalidEndpoint, err) } }) } } func TestNewClientNoSchemeEndpoint(t *testing.T) { t.Parallel() cases := []string{"localhost", "localhost:8080"} for _, c := range cases { testCase := c t.Run(testCase, func(t *testing.T) { client, err := NewClient(testCase) if client == nil { t.Errorf("Want client for scheme-less endpoint, got ") } if err != nil { t.Errorf("Got unexpected error scheme-less endpoint: %q", err) } }) } } func TestNewTLSClient(t *testing.T) { t.Parallel() tests := []struct { endpoint string expected string }{ {"tcp://localhost:2376", "https"}, {"tcp://localhost:2375", "https"}, {"tcp://localhost:4000", "https"}, {"http://localhost:4000", "https"}, } for _, tt := range tests { test := tt t.Run(test.endpoint, func(t *testing.T) { t.Parallel() client, err := newTLSClient(test.endpoint) if err != nil { t.Error(err) } got := client.endpointURL.Scheme if got != test.expected { t.Errorf("endpointURL.Scheme: Got %s. Want %s.", got, test.expected) } }) } } func TestEndpoint(t *testing.T) { t.Parallel() client, err := NewVersionedClient("http://localhost:4243", "1.12") if err != nil { t.Fatal(err) } if endpoint := client.Endpoint(); endpoint != client.endpoint { t.Errorf("Client.Endpoint(): want %q. Got %q", client.endpoint, endpoint) } } func TestGetURL(t *testing.T) { t.Parallel() tests := []struct { endpoint string path string expected string }{ {"http://localhost:4243/", "/", "http://localhost:4243/"}, {"http://localhost:4243", "/", "http://localhost:4243/"}, {"http://localhost:4243", "/containers/ps", "http://localhost:4243/containers/ps"}, {"tcp://localhost:4243", "/containers/ps", "http://localhost:4243/containers/ps"}, {"http://localhost:4243/////", "/", "http://localhost:4243/"}, {nativeRealEndpoint, "/containers", "/containers"}, } for _, tt := range tests { test := tt t.Run(test.endpoint+test.path, func(t *testing.T) { t.Parallel() client, _ := NewClient(test.endpoint) client.endpoint = test.endpoint client.SkipServerVersionCheck = true got := client.getURL(test.path) if got != test.expected { t.Errorf("getURL(%q): Got %s. Want %s.", test.path, got, test.expected) } }) } } func TestGetFakeNativeURL(t *testing.T) { t.Parallel() tests := []struct { endpoint string path string expected string }{ {nativeRealEndpoint, "/", "http://unix.sock/"}, {nativeRealEndpoint, "/", "http://unix.sock/"}, {nativeRealEndpoint, "/containers/ps", "http://unix.sock/containers/ps"}, } for _, tt := range tests { test := tt t.Run(test.path, func(t *testing.T) { t.Parallel() client, _ := NewClient(test.endpoint) client.endpoint = test.endpoint client.SkipServerVersionCheck = true got := client.getFakeNativeURL(test.path) if got != test.expected { t.Errorf("getURL(%q): Got %s. Want %s.", test.path, got, test.expected) } }) } } func TestError(t *testing.T) { t.Parallel() fakeBody := io.NopCloser(bytes.NewBufferString("bad parameter")) resp := &http.Response{ StatusCode: 400, Body: fakeBody, } err := newError(resp) expected := Error{Status: 400, Message: "bad parameter"} if !reflect.DeepEqual(expected, *err) { t.Errorf("Wrong error type. Want %#v. Got %#v.", expected, *err) } message := "API error (400): bad parameter" if err.Error() != message { t.Errorf("Wrong error message. Want %q. Got %q.", message, err.Error()) } } func TestQueryString(t *testing.T) { t.Parallel() v := float32(2.4) f32QueryString := fmt.Sprintf("w=%s&x=10&y=10.35", strconv.FormatFloat(float64(v), 'f', -1, 64)) jsonPerson := url.QueryEscape(`{"Name":"gopher","age":4}`) tests := []struct { input any want string wantAPI APIVersion }{ {&ListContainersOptions{All: true}, "all=1", nil}, {ListContainersOptions{All: true}, "all=1", nil}, {ListContainersOptions{Before: "something"}, "before=something", nil}, {ListContainersOptions{Before: "something", Since: "other"}, "before=something&since=other", nil}, {ListContainersOptions{Filters: map[string][]string{"status": {"paused", "running"}}}, "filters=%7B%22status%22%3A%5B%22paused%22%2C%22running%22%5D%7D", nil}, {dumb{X: 10, Y: 10.35000}, "x=10&y=10.35", apiVersion119}, {dumb{W: v, X: 10, Y: 10.35000}, f32QueryString, apiVersion124}, {dumb{X: 10, Y: 10.35000, Z: 10}, "x=10&y=10.35&zee=10", apiVersion119}, {dumb{v: 4, X: 10, Y: 10.35000}, "x=10&y=10.35", apiVersion119}, {dumb{T: 10, Y: 10.35000}, "y=10.35", nil}, {dumb{Person: &person{Name: "gopher", Age: 4}}, "p=" + jsonPerson, nil}, {nil, "", nil}, {10, "", nil}, {"not_a_struct", "", nil}, } for _, tt := range tests { test := tt t.Run("", func(t *testing.T) { t.Parallel() got := queryString(test.input) if got != test.want { t.Errorf("queryString(%v). Want %q. Got %q.", test.input, test.want, got) } gotstring, gotAPI := queryStringVersion(test.input) if gotstring != test.want { t.Errorf("queryStringVersion(%v). Want %q. Got %q.", test.input, test.want, gotstring) } if gotAPI.compare(test.wantAPI) != 0 { t.Errorf("queryStringVersion(%v). Want API %q. Got API %q.", test.input, test.wantAPI, gotAPI) } }) } } func TestAPIVersions(t *testing.T) { t.Parallel() tests := []struct { a string b string expectedALessThanB bool expectedALessThanOrEqualToB bool expectedAGreaterThanB bool expectedAGreaterThanOrEqualToB bool }{ {"1.11", "1.11", false, true, false, true}, {"1.10", "1.11", true, true, false, false}, {"1.11", "1.10", false, false, true, true}, {"1.11-ubuntu0", "1.11", false, true, false, true}, {"1.10", "1.11-el7", true, true, false, false}, {"1.9", "1.11", true, true, false, false}, {"1.11", "1.9", false, false, true, true}, {"1.1.1", "1.1", false, false, true, true}, {"1.1", "1.1.1", true, true, false, false}, {"2.1", "1.1.1", false, false, true, true}, {"2.1", "1.3.1", false, false, true, true}, {"1.1.1", "2.1", true, true, false, false}, {"1.3.1", "2.1", true, true, false, false}, } for _, tt := range tests { test := tt t.Run(test.a+test.b, func(t *testing.T) { t.Parallel() a, _ := NewAPIVersion(test.a) b, _ := NewAPIVersion(test.b) if test.expectedALessThanB && !a.LessThan(b) { t.Errorf("Expected %#v < %#v", a, b) } if test.expectedALessThanOrEqualToB && !a.LessThanOrEqualTo(b) { t.Errorf("Expected %#v <= %#v", a, b) } if test.expectedAGreaterThanB && !a.GreaterThan(b) { t.Errorf("Expected %#v > %#v", a, b) } if test.expectedAGreaterThanOrEqualToB && !a.GreaterThanOrEqualTo(b) { t.Errorf("Expected %#v >= %#v", a, b) } }) } } func TestPing(t *testing.T) { t.Parallel() fakeRT := &FakeRoundTripper{message: "", status: http.StatusOK} client := newTestClient(fakeRT) err := client.Ping() if err != nil { t.Fatal(err) } } func TestPingFailing(t *testing.T) { t.Parallel() fakeRT := &FakeRoundTripper{message: "", status: http.StatusInternalServerError} client := newTestClient(fakeRT) err := client.Ping() if err == nil { t.Fatal("Expected non nil error, got nil") } expectedErrMsg := "API error (500): " if err.Error() != expectedErrMsg { t.Fatalf("Expected error to be %q, got: %q", expectedErrMsg, err.Error()) } } func TestPingFailingWrongStatus(t *testing.T) { t.Parallel() fakeRT := &FakeRoundTripper{message: "", status: http.StatusAccepted} client := newTestClient(fakeRT) err := client.Ping() if err == nil { t.Fatal("Expected non nil error, got nil") } expectedErrMsg := "API error (202): " if err.Error() != expectedErrMsg { t.Fatalf("Expected error to be %q, got: %q", expectedErrMsg, err.Error()) } } func TestPingErrorWithNativeClient(t *testing.T) { t.Parallel() srv, cleanup, err := newNativeServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { w.Write([]byte("aaaaaaaaaaa-invalid-aaaaaaaaaaa")) })) if err != nil { t.Fatal(err) } defer cleanup() srv.Start() defer srv.Close() endpoint := nativeBadEndpoint client, err := NewClient(endpoint) if err != nil { t.Fatal(err) } err = client.Ping() if err == nil { t.Fatal("Expected non nil error, got nil") } } func TestClientStreamTimeoutNotHit(t *testing.T) { t.Parallel() srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { for i := 0; i < 5; i++ { fmt.Fprintf(w, "%d\n", i) if f, ok := w.(http.Flusher); ok { f.Flush() } time.Sleep(100 * time.Millisecond) } })) defer srv.Close() client, err := NewClient(srv.URL) if err != nil { t.Fatal(err) } var w bytes.Buffer err = client.stream(http.MethodPost, "/image/create", streamOptions{ setRawTerminal: true, stdout: &w, inactivityTimeout: 300 * time.Millisecond, }) if err != nil { t.Fatal(err) } expected := "0\n1\n2\n3\n4\n" result := w.String() if result != expected { t.Fatalf("expected stream result %q, got: %q", expected, result) } } func TestClientStreamInactivityTimeout(t *testing.T) { t.Parallel() srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { for i := 0; i < 5; i++ { fmt.Fprintf(w, "%d\n", i) if f, ok := w.(http.Flusher); ok { f.Flush() } time.Sleep(500 * time.Millisecond) } })) defer srv.Close() client, err := NewClient(srv.URL) if err != nil { t.Fatal(err) } var w bytes.Buffer err = client.stream(http.MethodPost, "/image/create", streamOptions{ setRawTerminal: true, stdout: &w, inactivityTimeout: 100 * time.Millisecond, }) if !errors.Is(err, ErrInactivityTimeout) { t.Fatalf("expected request canceled error, got: %s", err) } expected := "0\n" result := w.String() if result != expected { t.Fatalf("expected stream result %q, got: %q", expected, result) } } func TestClientStreamContextDeadline(t *testing.T) { t.Parallel() srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { fmt.Fprint(w, "abc\n") if f, ok := w.(http.Flusher); ok { f.Flush() } time.Sleep(time.Second) fmt.Fprint(w, "def\n") if f, ok := w.(http.Flusher); ok { f.Flush() } })) defer srv.Close() client, err := NewClient(srv.URL) if err != nil { t.Fatal(err) } var w bytes.Buffer ctx, cancel := context.WithTimeout(context.Background(), 400*time.Millisecond) defer cancel() err = client.stream(http.MethodPost, "/image/create", streamOptions{ setRawTerminal: true, stdout: &w, context: ctx, }) if !errors.Is(err, context.DeadlineExceeded) { t.Fatalf("expected %s, got: %s", context.DeadlineExceeded, err) } expected := "abc\n" result := w.String() if result != expected { t.Fatalf("expected stream result %q, got: %q", expected, result) } } func TestClientStreamContextCancel(t *testing.T) { t.Parallel() srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { fmt.Fprint(w, "abc\n") if f, ok := w.(http.Flusher); ok { f.Flush() } time.Sleep(500 * time.Millisecond) fmt.Fprint(w, "def\n") if f, ok := w.(http.Flusher); ok { f.Flush() } })) defer srv.Close() client, err := NewClient(srv.URL) if err != nil { t.Fatal(err) } var w bytes.Buffer ctx, cancel := context.WithCancel(context.Background()) go func() { time.Sleep(200 * time.Millisecond) cancel() }() err = client.stream(http.MethodPost, "/image/create", streamOptions{ setRawTerminal: true, stdout: &w, context: ctx, }) if !errors.Is(err, context.Canceled) { t.Fatalf("expected %s, got: %s", context.Canceled, err) } expected := "abc\n" result := w.String() if result != expected { t.Fatalf("expected stream result %q, got: %q", expected, result) } } var mockPullOutput = `{"status":"Pulling from tsuru/static","id":"latest"} {"status":"Already exists","progressDetail":{},"id":"a6aa3b66376f"} {"status":"Pulling fs layer","progressDetail":{},"id":"106572778bf7"} {"status":"Pulling fs layer","progressDetail":{},"id":"bac681833e51"} {"status":"Pulling fs layer","progressDetail":{},"id":"7302e23ef08a"} {"status":"Downloading","progressDetail":{"current":621,"total":621},"progress":"[==================================================\u003e] 621 B/621 B","id":"bac681833e51"} {"status":"Verifying Checksum","progressDetail":{},"id":"bac681833e51"} {"status":"Download complete","progressDetail":{},"id":"bac681833e51"} {"status":"Downloading","progressDetail":{"current":1854,"total":1854},"progress":"[==================================================\u003e] 1.854 kB/1.854 kB","id":"106572778bf7"} {"status":"Verifying Checksum","progressDetail":{},"id":"106572778bf7"} {"status":"Download complete","progressDetail":{},"id":"106572778bf7"} {"status":"Extracting","progressDetail":{"current":1854,"total":1854},"progress":"[==================================================\u003e] 1.854 kB/1.854 kB","id":"106572778bf7"} {"status":"Extracting","progressDetail":{"current":1854,"total":1854},"progress":"[==================================================\u003e] 1.854 kB/1.854 kB","id":"106572778bf7"} {"status":"Downloading","progressDetail":{"current":233019,"total":21059403},"progress":"[\u003e ] 233 kB/21.06 MB","id":"7302e23ef08a"} {"status":"Downloading","progressDetail":{"current":462395,"total":21059403},"progress":"[=\u003e ] 462.4 kB/21.06 MB","id":"7302e23ef08a"} {"status":"Downloading","progressDetail":{"current":8490555,"total":21059403},"progress":"[====================\u003e ] 8.491 MB/21.06 MB","id":"7302e23ef08a"} {"status":"Downloading","progressDetail":{"current":20876859,"total":21059403},"progress":"[=================================================\u003e ] 20.88 MB/21.06 MB","id":"7302e23ef08a"} {"status":"Verifying Checksum","progressDetail":{},"id":"7302e23ef08a"} {"status":"Download complete","progressDetail":{},"id":"7302e23ef08a"} {"status":"Pull complete","progressDetail":{},"id":"106572778bf7"} {"status":"Extracting","progressDetail":{"current":621,"total":621},"progress":"[==================================================\u003e] 621 B/621 B","id":"bac681833e51"} {"status":"Extracting","progressDetail":{"current":621,"total":621},"progress":"[==================================================\u003e] 621 B/621 B","id":"bac681833e51"} {"status":"Pull complete","progressDetail":{},"id":"bac681833e51"} {"status":"Extracting","progressDetail":{"current":229376,"total":21059403},"progress":"[\u003e ] 229.4 kB/21.06 MB","id":"7302e23ef08a"} {"status":"Extracting","progressDetail":{"current":458752,"total":21059403},"progress":"[=\u003e ] 458.8 kB/21.06 MB","id":"7302e23ef08a"} {"status":"Extracting","progressDetail":{"current":11239424,"total":21059403},"progress":"[==========================\u003e ] 11.24 MB/21.06 MB","id":"7302e23ef08a"} {"status":"Extracting","progressDetail":{"current":21059403,"total":21059403},"progress":"[==================================================\u003e] 21.06 MB/21.06 MB","id":"7302e23ef08a"} {"status":"Pull complete","progressDetail":{},"id":"7302e23ef08a"} {"status":"Digest: sha256:b754472891aa7e33fc0214e3efa988174f2c2289285fcae868b7ec8b6675fc77"} {"status":"Status: Downloaded newer image for 192.168.50.4:5000/tsuru/static"} ` func TestClientStreamJSONDecode(t *testing.T) { t.Parallel() srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { w.Write([]byte(mockPullOutput)) })) defer srv.Close() client, err := NewClient(srv.URL) if err != nil { t.Fatal(err) } var w bytes.Buffer err = client.stream(http.MethodPost, "/image/create", streamOptions{ stdout: &w, useJSONDecoder: true, }) if err != nil { t.Fatal(err) } expected := `latest: Pulling from tsuru/static a6aa3b66376f: Already exists 106572778bf7: Pulling fs layer bac681833e51: Pulling fs layer 7302e23ef08a: Pulling fs layer bac681833e51: Verifying Checksum bac681833e51: Download complete 106572778bf7: Verifying Checksum 106572778bf7: Download complete 7302e23ef08a: Verifying Checksum 7302e23ef08a: Download complete 106572778bf7: Pull complete bac681833e51: Pull complete 7302e23ef08a: Pull complete Digest: sha256:b754472891aa7e33fc0214e3efa988174f2c2289285fcae868b7ec8b6675fc77 Status: Downloaded newer image for 192.168.50.4:5000/tsuru/static ` result := w.String() if result != expected { t.Fatalf("expected stream result %q, got: %q", expected, result) } } type terminalBuffer struct { bytes.Buffer } func (b *terminalBuffer) FD() uintptr { return os.Stdout.Fd() } func (b *terminalBuffer) IsTerminal() bool { return true } func TestClientStreamJSONDecodeWithTerminal(t *testing.T) { if !term.IsTerminal(int(os.Stdout.Fd())) { t.Skip("requires a terminal") } t.Parallel() srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { w.Write([]byte(mockPullOutput)) })) defer srv.Close() client, err := NewClient(srv.URL) if err != nil { t.Fatal(err) } var w terminalBuffer err = client.stream(http.MethodPost, "/image/create", streamOptions{ stdout: &w, useJSONDecoder: true, }) if err != nil { t.Fatal(err) } const expected = "latest: Pulling from tsuru/static\n\n" + "\x1b[1A\x1b[2K\ra6aa3b66376f: Already exists \r\x1b[1B\n" + "\x1b[1A\x1b[2K\r106572778bf7: Pulling fs layer \r\x1b[1B\n" + "\x1b[1A\x1b[2K\rbac681833e51: Pulling fs layer \r\x1b[1B\n" + "\x1b[1A\x1b[2K\r7302e23ef08a: Pulling fs layer \r\x1b[1B\x1b[2A\x1b[2K\rbac681833e51: Downloading [==================================================>] 621B/621B\r\x1b[2B\x1b[2A\x1b[2K\rbac681833e51: Verifying Checksum \r\x1b[2B\x1b[2A\x1b[2K\rbac681833e51: Download complete \r\x1b[2B\x1b[3A\x1b[2K\r106572778bf7: Downloading [==================================================>] 1.854kB/1.854kB\r\x1b[3B\x1b[3A\x1b[2K\r106572778bf7: Verifying Checksum \r\x1b[3B\x1b[3A\x1b[2K\r106572778bf7: Download complete \r\x1b[3B\x1b[3A\x1b[2K\r106572778bf7: Extracting [==================================================>] 1.854kB/1.854kB\r\x1b[3B\x1b[3A\x1b[2K\r106572778bf7: Extracting [==================================================>] 1.854kB/1.854kB\r\x1b[3B\x1b[1A\x1b[2K\r7302e23ef08a: Downloading [> ] 233kB/21.06MB\r\x1b[1B\x1b[1A\x1b[2K\r7302e23ef08a: Downloading [=> ] 462.4kB/21.06MB\r\x1b[1B\x1b[1A\x1b[2K\r7302e23ef08a: Downloading [====================> ] 8.491MB/21.06MB\r\x1b[1B\x1b[1A\x1b[2K\r7302e23ef08a: Downloading [=================================================> ] 20.88MB/21.06MB\r\x1b[1B\x1b[1A\x1b[2K\r7302e23ef08a: Verifying Checksum \r\x1b[1B\x1b[1A\x1b[2K\r7302e23ef08a: Download complete \r\x1b[1B\x1b[3A\x1b[2K\r106572778bf7: Pull complete \r\x1b[3B\x1b[2A\x1b[2K\rbac681833e51: Extracting [==================================================>] 621B/621B\r\x1b[2B\x1b[2A\x1b[2K\rbac681833e51: Extracting [==================================================>] 621B/621B\r\x1b[2B\x1b[2A\x1b[2K\rbac681833e51: Pull complete \r\x1b[2B\x1b[1A\x1b[2K\r7302e23ef08a: Extracting [> ] 229.4kB/21.06MB\r\x1b[1B\x1b[1A\x1b[2K\r7302e23ef08a: Extracting [=> ] 458.8kB/21.06MB\r\x1b[1B\x1b[1A\x1b[2K\r7302e23ef08a: Extracting [==========================> ] 11.24MB/21.06MB\r\x1b[1B\x1b[1A\x1b[2K\r7302e23ef08a: Extracting [==================================================>] 21.06MB/21.06MB\r\x1b[1B\x1b[1A\x1b[2K\r7302e23ef08a: Pull complete \r\x1b[1BDigest: sha256:b754472891aa7e33fc0214e3efa988174f2c2289285fcae868b7ec8b6675fc77\n" + "Status: Downloaded newer image for 192.168.50.4:5000/tsuru/static\n" result := w.String() if result != expected { t.Fatalf("wrong stream result\nwant %q\ngot: %q", expected, result) } } func TestClientDoContextDeadline(t *testing.T) { t.Parallel() srv := httptest.NewServer(http.HandlerFunc(func(http.ResponseWriter, *http.Request) { time.Sleep(500 * time.Millisecond) })) defer srv.Close() client, err := NewClient(srv.URL) if err != nil { t.Fatal(err) } ctx, cancel := context.WithTimeout(context.Background(), 100*time.Millisecond) defer cancel() _, err = client.do(http.MethodPost, "/image/create", doOptions{ context: ctx, }) if !errors.Is(err, context.DeadlineExceeded) { t.Fatalf("expected %s, got: %s", context.DeadlineExceeded, err) } } func TestClientDoContextCancel(t *testing.T) { t.Parallel() srv := httptest.NewServer(http.HandlerFunc(func(http.ResponseWriter, *http.Request) { time.Sleep(500 * time.Millisecond) })) defer srv.Close() client, err := NewClient(srv.URL) if err != nil { t.Fatal(err) } ctx, cancel := context.WithCancel(context.Background()) go func() { time.Sleep(100 * time.Millisecond) cancel() }() _, err = client.do(http.MethodPost, "/image/create", doOptions{ context: ctx, }) if !errors.Is(err, context.Canceled) { t.Fatalf("expected %s, got: %s", context.Canceled, err) } } func TestClientStreamTimeoutNativeClient(t *testing.T) { t.Parallel() srv, cleanup, err := newNativeServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { for i := 0; i < 5; i++ { fmt.Fprintf(w, "%d\n", i) if f, ok := w.(http.Flusher); ok { f.Flush() } time.Sleep(500 * time.Millisecond) } })) if err != nil { t.Fatal(err) } defer cleanup() srv.Start() defer srv.Close() client, err := NewClient(nativeProtocol + "://" + srv.Listener.Addr().String()) if err != nil { t.Fatal(err) } var w bytes.Buffer err = client.stream(http.MethodPost, "/image/create", streamOptions{ setRawTerminal: true, stdout: &w, inactivityTimeout: 50 * time.Millisecond, }) if !errors.Is(err, ErrInactivityTimeout) { t.Fatalf("expected request canceled error, got: %s", err) } expected := "0\n" result := w.String() if result != expected { t.Fatalf("expected stream result %q, got: %q", expected, result) } } func TestClientStreamJSONDecoderEOFOutputWriter(t *testing.T) { t.Parallel() srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { fmt.Fprint(w, "{}") time.Sleep(500 * time.Millisecond) })) defer srv.Close() client, err := NewClient(srv.URL) if err != nil { t.Fatal(err) } var w eofWriter err = client.stream(http.MethodPost, "/image/create", streamOptions{ setRawTerminal: true, useJSONDecoder: true, stdout: &w, }) if err != nil { t.Fatal(err) } } type eofWriter struct{} func (w eofWriter) Write(b []byte) (int, error) { return len(b), io.EOF } type FakeRoundTripper struct { message string status int header map[string]string requests []*http.Request } func (rt *FakeRoundTripper) RoundTrip(r *http.Request) (*http.Response, error) { body := strings.NewReader(rt.message) rt.requests = append(rt.requests, r) res := &http.Response{ StatusCode: rt.status, Body: io.NopCloser(body), Header: make(http.Header), } for k, v := range rt.header { res.Header.Set(k, v) } return res, nil } func (rt *FakeRoundTripper) Reset() { rt.requests = nil } type person struct { Name string Age int `json:"age"` } type dumb struct { T int `qs:"-"` v int W float32 `ver:"1.24"` X int `ver:"1.19"` Y float64 Z int `qs:"zee"` Person *person `qs:"p"` } go-dockerclient-1.12.0/client_unix.go000066400000000000000000000013771465717111200175310ustar00rootroot00000000000000// Copyright 2016 go-dockerclient authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. //go:build !windows package docker import ( "context" "net" "net/http" ) const defaultHost = "unix:///var/run/docker.sock" // initializeNativeClient initializes the native Unix domain socket client on // Unix-style operating systems func (c *Client) initializeNativeClient(trFunc func() *http.Transport) { if c.endpointURL.Scheme != unixProtocol { return } sockPath := c.endpointURL.Path tr := trFunc() tr.Proxy = nil tr.DialContext = func(_ context.Context, network, addr string) (net.Conn, error) { return c.Dialer.Dial(unixProtocol, sockPath) } c.HTTPClient.Transport = tr } go-dockerclient-1.12.0/client_unix_test.go000066400000000000000000000032031465717111200205560ustar00rootroot00000000000000//go:build !windows // Copyright 2016 go-dockerclient authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package docker import ( "io" "net" "net/http" "net/http/httptest" "os" "path/filepath" "testing" ) const ( nativeProtocol = unixProtocol nativeRealEndpoint = "unix:///var/run/docker.sock" nativeBadEndpoint = "unix:///tmp/echo.sock" ) func TestNewTSLAPIClientUnixEndpoint(t *testing.T) { t.Parallel() srv, cleanup, err := newNativeServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { w.Write([]byte("ok")) })) if err != nil { t.Fatal(err) } defer cleanup() srv.Start() defer srv.Close() endpoint := nativeProtocol + "://" + srv.Listener.Addr().String() client, err := newTLSClient(endpoint) if err != nil { t.Fatal(err) } if client.endpoint != endpoint { t.Errorf("Expected endpoint %s. Got %s.", endpoint, client.endpoint) } rsp, err := client.do(http.MethodGet, "/", doOptions{}) if err != nil { t.Fatal(err) } data, err := io.ReadAll(rsp.Body) if err != nil { t.Fatal(err) } if string(data) != "ok" { t.Fatalf("Expected response to be %q, got: %q", "ok", string(data)) } } func newNativeServer(handler http.Handler) (*httptest.Server, func(), error) { tmpdir, err := os.MkdirTemp("", "socket") if err != nil { return nil, nil, err } socketPath := filepath.Join(tmpdir, "docker_test_stress.sock") l, err := net.Listen("unix", socketPath) if err != nil { return nil, nil, err } srv := httptest.NewUnstartedServer(handler) srv.Listener = l return srv, func() { os.RemoveAll(tmpdir) }, nil } go-dockerclient-1.12.0/client_windows.go000066400000000000000000000022511465717111200202300ustar00rootroot00000000000000// Copyright 2016 go-dockerclient authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package docker import ( "context" "net" "net/http" "time" winio "github.com/Microsoft/go-winio" ) const ( defaultHost = "npipe:////./pipe/docker_engine" namedPipeConnectTimeout = 2 * time.Second ) type pipeDialer struct { dialFunc func(network, addr string) (net.Conn, error) } func (p pipeDialer) Dial(network, address string) (net.Conn, error) { return p.dialFunc(network, address) } // initializeNativeClient initializes the native Named Pipe client for Windows func (c *Client) initializeNativeClient(trFunc func() *http.Transport) { if c.endpointURL.Scheme != namedPipeProtocol { return } namedPipePath := c.endpointURL.Path dialFunc := func(_, addr string) (net.Conn, error) { timeout := namedPipeConnectTimeout return winio.DialPipe(namedPipePath, &timeout) } tr := trFunc() tr.Proxy = nil tr.DialContext = func(ctx context.Context, network, addr string) (net.Conn, error) { return dialFunc(network, addr) } c.Dialer = &pipeDialer{dialFunc} c.HTTPClient.Transport = tr } go-dockerclient-1.12.0/client_windows_test.go000066400000000000000000000021451465717111200212710ustar00rootroot00000000000000// Copyright 2016 go-dockerclient authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package docker import ( "fmt" "net/http" "net/http/httptest" "sync" winio "github.com/Microsoft/go-winio" ) const ( nativeProtocol = namedPipeProtocol nativeRealEndpoint = "npipe:////./pipe/docker_engine" nativeBadEndpoint = "npipe:////./pipe/godockerclient_test_echo" ) var ( // namedPipeCount is used to provide uniqueness in the named pipes generated // by newNativeServer namedPipeCount int // namedPipesAllocateLock protects namedPipeCount namedPipeAllocateLock sync.Mutex ) func newNativeServer(handler http.Handler) (*httptest.Server, func(), error) { namedPipeAllocateLock.Lock() defer namedPipeAllocateLock.Unlock() pipeName := fmt.Sprintf("//./pipe/godockerclient_test_%d", namedPipeCount) namedPipeCount++ l, err := winio.ListenPipe(pipeName, &winio.PipeConfig{MessageMode: true}) if err != nil { return nil, nil, err } srv := httptest.NewUnstartedServer(handler) srv.Listener = l return srv, func() {}, nil } go-dockerclient-1.12.0/container.go000066400000000000000000001151161465717111200171670ustar00rootroot00000000000000// Copyright 2013 go-dockerclient authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package docker import ( "fmt" "strconv" "strings" "time" units "github.com/docker/go-units" ) // APIPort is a type that represents a port mapping returned by the Docker API type APIPort struct { PrivatePort int64 `json:"PrivatePort,omitempty" yaml:"PrivatePort,omitempty" toml:"PrivatePort,omitempty"` PublicPort int64 `json:"PublicPort,omitempty" yaml:"PublicPort,omitempty" toml:"PublicPort,omitempty"` Type string `json:"Type,omitempty" yaml:"Type,omitempty" toml:"Type,omitempty"` IP string `json:"IP,omitempty" yaml:"IP,omitempty" toml:"IP,omitempty"` } // APIMount represents a mount point for a container. type APIMount struct { Name string `json:"Name,omitempty" yaml:"Name,omitempty" toml:"Name,omitempty"` Source string `json:"Source,omitempty" yaml:"Source,omitempty" toml:"Source,omitempty"` Destination string `json:"Destination,omitempty" yaml:"Destination,omitempty" toml:"Destination,omitempty"` Driver string `json:"Driver,omitempty" yaml:"Driver,omitempty" toml:"Driver,omitempty"` Mode string `json:"Mode,omitempty" yaml:"Mode,omitempty" toml:"Mode,omitempty"` RW bool `json:"RW,omitempty" yaml:"RW,omitempty" toml:"RW,omitempty"` Propagation string `json:"Propagation,omitempty" yaml:"Propagation,omitempty" toml:"Propagation,omitempty"` Type string `json:"Type,omitempty" yaml:"Type,omitempty" toml:"Type,omitempty"` } // APIContainers represents each container in the list returned by // ListContainers. type APIContainers struct { ID string `json:"Id" yaml:"Id" toml:"Id"` Image string `json:"Image,omitempty" yaml:"Image,omitempty" toml:"Image,omitempty"` Command string `json:"Command,omitempty" yaml:"Command,omitempty" toml:"Command,omitempty"` Created int64 `json:"Created,omitempty" yaml:"Created,omitempty" toml:"Created,omitempty"` State string `json:"State,omitempty" yaml:"State,omitempty" toml:"State,omitempty"` Status string `json:"Status,omitempty" yaml:"Status,omitempty" toml:"Status,omitempty"` Ports []APIPort `json:"Ports,omitempty" yaml:"Ports,omitempty" toml:"Ports,omitempty"` SizeRw int64 `json:"SizeRw,omitempty" yaml:"SizeRw,omitempty" toml:"SizeRw,omitempty"` SizeRootFs int64 `json:"SizeRootFs,omitempty" yaml:"SizeRootFs,omitempty" toml:"SizeRootFs,omitempty"` Names []string `json:"Names,omitempty" yaml:"Names,omitempty" toml:"Names,omitempty"` Labels map[string]string `json:"Labels,omitempty" yaml:"Labels,omitempty" toml:"Labels,omitempty"` Networks NetworkList `json:"NetworkSettings,omitempty" yaml:"NetworkSettings,omitempty" toml:"NetworkSettings,omitempty"` Mounts []APIMount `json:"Mounts,omitempty" yaml:"Mounts,omitempty" toml:"Mounts,omitempty"` } // NetworkList encapsulates a map of networks, as returned by the Docker API in // ListContainers. type NetworkList struct { Networks map[string]ContainerNetwork `json:"Networks" yaml:"Networks,omitempty" toml:"Networks,omitempty"` } // Port represents the port number and the protocol, in the form // /. For example: 80/tcp. type Port string // Port returns the number of the port. func (p Port) Port() string { return strings.Split(string(p), "/")[0] } // Proto returns the name of the protocol. func (p Port) Proto() string { parts := strings.Split(string(p), "/") if len(parts) == 1 { return "tcp" } return parts[1] } // HealthCheck represents one check of health. type HealthCheck struct { Start time.Time `json:"Start,omitempty" yaml:"Start,omitempty" toml:"Start,omitempty"` End time.Time `json:"End,omitempty" yaml:"End,omitempty" toml:"End,omitempty"` ExitCode int `json:"ExitCode,omitempty" yaml:"ExitCode,omitempty" toml:"ExitCode,omitempty"` Output string `json:"Output,omitempty" yaml:"Output,omitempty" toml:"Output,omitempty"` } // Health represents the health of a container. type Health struct { Status string `json:"Status,omitempty" yaml:"Status,omitempty" toml:"Status,omitempty"` FailingStreak int `json:"FailingStreak,omitempty" yaml:"FailingStreak,omitempty" toml:"FailingStreak,omitempty"` Log []HealthCheck `json:"Log,omitempty" yaml:"Log,omitempty" toml:"Log,omitempty"` } // State represents the state of a container. type State struct { Status string `json:"Status,omitempty" yaml:"Status,omitempty" toml:"Status,omitempty"` Running bool `json:"Running,omitempty" yaml:"Running,omitempty" toml:"Running,omitempty"` Paused bool `json:"Paused,omitempty" yaml:"Paused,omitempty" toml:"Paused,omitempty"` Restarting bool `json:"Restarting,omitempty" yaml:"Restarting,omitempty" toml:"Restarting,omitempty"` OOMKilled bool `json:"OOMKilled,omitempty" yaml:"OOMKilled,omitempty" toml:"OOMKilled,omitempty"` RemovalInProgress bool `json:"RemovalInProgress,omitempty" yaml:"RemovalInProgress,omitempty" toml:"RemovalInProgress,omitempty"` Dead bool `json:"Dead,omitempty" yaml:"Dead,omitempty" toml:"Dead,omitempty"` Pid int `json:"Pid,omitempty" yaml:"Pid,omitempty" toml:"Pid,omitempty"` ExitCode int `json:"ExitCode,omitempty" yaml:"ExitCode,omitempty" toml:"ExitCode,omitempty"` Error string `json:"Error,omitempty" yaml:"Error,omitempty" toml:"Error,omitempty"` StartedAt time.Time `json:"StartedAt,omitempty" yaml:"StartedAt,omitempty" toml:"StartedAt,omitempty"` FinishedAt time.Time `json:"FinishedAt,omitempty" yaml:"FinishedAt,omitempty" toml:"FinishedAt,omitempty"` Health Health `json:"Health,omitempty" yaml:"Health,omitempty" toml:"Health,omitempty"` } // String returns a human-readable description of the state func (s *State) String() string { if s.Running { if s.Paused { return fmt.Sprintf("Up %s (Paused)", units.HumanDuration(time.Now().UTC().Sub(s.StartedAt))) } if s.Restarting { return fmt.Sprintf("Restarting (%d) %s ago", s.ExitCode, units.HumanDuration(time.Now().UTC().Sub(s.FinishedAt))) } return fmt.Sprintf("Up %s", units.HumanDuration(time.Now().UTC().Sub(s.StartedAt))) } if s.RemovalInProgress { return "Removal In Progress" } if s.Dead { return "Dead" } if s.StartedAt.IsZero() { return "Created" } if s.FinishedAt.IsZero() { return "" } return fmt.Sprintf("Exited (%d) %s ago", s.ExitCode, units.HumanDuration(time.Now().UTC().Sub(s.FinishedAt))) } // StateString returns a single string to describe state func (s *State) StateString() string { if s.Running { if s.Paused { return "paused" } if s.Restarting { return "restarting" } return "running" } if s.Dead { return "dead" } if s.StartedAt.IsZero() { return "created" } return "exited" } // PortBinding represents the host/container port mapping as returned in the // `docker inspect` json type PortBinding struct { HostIP string `json:"HostIp,omitempty" yaml:"HostIp,omitempty" toml:"HostIp,omitempty"` HostPort string `json:"HostPort,omitempty" yaml:"HostPort,omitempty" toml:"HostPort,omitempty"` } // PortMapping represents a deprecated field in the `docker inspect` output, // and its value as found in NetworkSettings should always be nil type PortMapping map[string]string // ContainerNetwork represents the networking settings of a container per network. type ContainerNetwork struct { Aliases []string `json:"Aliases,omitempty" yaml:"Aliases,omitempty" toml:"Aliases,omitempty"` MacAddress string `json:"MacAddress,omitempty" yaml:"MacAddress,omitempty" toml:"MacAddress,omitempty"` GlobalIPv6PrefixLen int `json:"GlobalIPv6PrefixLen,omitempty" yaml:"GlobalIPv6PrefixLen,omitempty" toml:"GlobalIPv6PrefixLen,omitempty"` GlobalIPv6Address string `json:"GlobalIPv6Address,omitempty" yaml:"GlobalIPv6Address,omitempty" toml:"GlobalIPv6Address,omitempty"` IPv6Gateway string `json:"IPv6Gateway,omitempty" yaml:"IPv6Gateway,omitempty" toml:"IPv6Gateway,omitempty"` IPPrefixLen int `json:"IPPrefixLen,omitempty" yaml:"IPPrefixLen,omitempty" toml:"IPPrefixLen,omitempty"` IPAddress string `json:"IPAddress,omitempty" yaml:"IPAddress,omitempty" toml:"IPAddress,omitempty"` Gateway string `json:"Gateway,omitempty" yaml:"Gateway,omitempty" toml:"Gateway,omitempty"` EndpointID string `json:"EndpointID,omitempty" yaml:"EndpointID,omitempty" toml:"EndpointID,omitempty"` NetworkID string `json:"NetworkID,omitempty" yaml:"NetworkID,omitempty" toml:"NetworkID,omitempty"` } // NetworkSettings contains network-related information about a container type NetworkSettings struct { Networks map[string]ContainerNetwork `json:"Networks,omitempty" yaml:"Networks,omitempty" toml:"Networks,omitempty"` IPAddress string `json:"IPAddress,omitempty" yaml:"IPAddress,omitempty" toml:"IPAddress,omitempty"` IPPrefixLen int `json:"IPPrefixLen,omitempty" yaml:"IPPrefixLen,omitempty" toml:"IPPrefixLen,omitempty"` MacAddress string `json:"MacAddress,omitempty" yaml:"MacAddress,omitempty" toml:"MacAddress,omitempty"` Gateway string `json:"Gateway,omitempty" yaml:"Gateway,omitempty" toml:"Gateway,omitempty"` Bridge string `json:"Bridge,omitempty" yaml:"Bridge,omitempty" toml:"Bridge,omitempty"` PortMapping map[string]PortMapping `json:"PortMapping,omitempty" yaml:"PortMapping,omitempty" toml:"PortMapping,omitempty"` Ports map[Port][]PortBinding `json:"Ports,omitempty" yaml:"Ports,omitempty" toml:"Ports,omitempty"` NetworkID string `json:"NetworkID,omitempty" yaml:"NetworkID,omitempty" toml:"NetworkID,omitempty"` EndpointID string `json:"EndpointID,omitempty" yaml:"EndpointID,omitempty" toml:"EndpointID,omitempty"` SandboxKey string `json:"SandboxKey,omitempty" yaml:"SandboxKey,omitempty" toml:"SandboxKey,omitempty"` GlobalIPv6Address string `json:"GlobalIPv6Address,omitempty" yaml:"GlobalIPv6Address,omitempty" toml:"GlobalIPv6Address,omitempty"` GlobalIPv6PrefixLen int `json:"GlobalIPv6PrefixLen,omitempty" yaml:"GlobalIPv6PrefixLen,omitempty" toml:"GlobalIPv6PrefixLen,omitempty"` IPv6Gateway string `json:"IPv6Gateway,omitempty" yaml:"IPv6Gateway,omitempty" toml:"IPv6Gateway,omitempty"` LinkLocalIPv6Address string `json:"LinkLocalIPv6Address,omitempty" yaml:"LinkLocalIPv6Address,omitempty" toml:"LinkLocalIPv6Address,omitempty"` LinkLocalIPv6PrefixLen int `json:"LinkLocalIPv6PrefixLen,omitempty" yaml:"LinkLocalIPv6PrefixLen,omitempty" toml:"LinkLocalIPv6PrefixLen,omitempty"` SecondaryIPAddresses []string `json:"SecondaryIPAddresses,omitempty" yaml:"SecondaryIPAddresses,omitempty" toml:"SecondaryIPAddresses,omitempty"` SecondaryIPv6Addresses []string `json:"SecondaryIPv6Addresses,omitempty" yaml:"SecondaryIPv6Addresses,omitempty" toml:"SecondaryIPv6Addresses,omitempty"` } // PortMappingAPI translates the port mappings as contained in NetworkSettings // into the format in which they would appear when returned by the API func (settings *NetworkSettings) PortMappingAPI() []APIPort { var mapping []APIPort for port, bindings := range settings.Ports { p, _ := parsePort(port.Port()) if len(bindings) == 0 { mapping = append(mapping, APIPort{ PrivatePort: int64(p), Type: port.Proto(), }) continue } for _, binding := range bindings { p, _ := parsePort(port.Port()) h, _ := parsePort(binding.HostPort) mapping = append(mapping, APIPort{ PrivatePort: int64(p), PublicPort: int64(h), Type: port.Proto(), IP: binding.HostIP, }) } } return mapping } func parsePort(rawPort string) (int, error) { port, err := strconv.ParseUint(rawPort, 10, 16) if err != nil { return 0, err } return int(port), nil } // Config is the list of configuration options used when creating a container. // Config does not contain the options that are specific to starting a container on a // given host. Those are contained in HostConfig type Config struct { Hostname string `json:"Hostname,omitempty" yaml:"Hostname,omitempty" toml:"Hostname,omitempty"` Domainname string `json:"Domainname,omitempty" yaml:"Domainname,omitempty" toml:"Domainname,omitempty"` User string `json:"User,omitempty" yaml:"User,omitempty" toml:"User,omitempty"` Memory int64 `json:"Memory,omitempty" yaml:"Memory,omitempty" toml:"Memory,omitempty"` MemorySwap int64 `json:"MemorySwap,omitempty" yaml:"MemorySwap,omitempty" toml:"MemorySwap,omitempty"` MemoryReservation int64 `json:"MemoryReservation,omitempty" yaml:"MemoryReservation,omitempty" toml:"MemoryReservation,omitempty"` KernelMemory int64 `json:"KernelMemory,omitempty" yaml:"KernelMemory,omitempty" toml:"KernelMemory,omitempty"` CPUShares int64 `json:"CpuShares,omitempty" yaml:"CpuShares,omitempty" toml:"CpuShares,omitempty"` CPUSet string `json:"Cpuset,omitempty" yaml:"Cpuset,omitempty" toml:"Cpuset,omitempty"` PortSpecs []string `json:"PortSpecs,omitempty" yaml:"PortSpecs,omitempty" toml:"PortSpecs,omitempty"` ExposedPorts map[Port]struct{} `json:"ExposedPorts,omitempty" yaml:"ExposedPorts,omitempty" toml:"ExposedPorts,omitempty"` PublishService string `json:"PublishService,omitempty" yaml:"PublishService,omitempty" toml:"PublishService,omitempty"` StopSignal string `json:"StopSignal,omitempty" yaml:"StopSignal,omitempty" toml:"StopSignal,omitempty"` StopTimeout int `json:"StopTimeout,omitempty" yaml:"StopTimeout,omitempty" toml:"StopTimeout,omitempty"` Env []string `json:"Env,omitempty" yaml:"Env,omitempty" toml:"Env,omitempty"` Cmd []string `json:"Cmd" yaml:"Cmd" toml:"Cmd"` Shell []string `json:"Shell,omitempty" yaml:"Shell,omitempty" toml:"Shell,omitempty"` Healthcheck *HealthConfig `json:"Healthcheck,omitempty" yaml:"Healthcheck,omitempty" toml:"Healthcheck,omitempty"` DNS []string `json:"Dns,omitempty" yaml:"Dns,omitempty" toml:"Dns,omitempty"` // For Docker API v1.9 and below only Image string `json:"Image,omitempty" yaml:"Image,omitempty" toml:"Image,omitempty"` Volumes map[string]struct{} `json:"Volumes,omitempty" yaml:"Volumes,omitempty" toml:"Volumes,omitempty"` VolumeDriver string `json:"VolumeDriver,omitempty" yaml:"VolumeDriver,omitempty" toml:"VolumeDriver,omitempty"` WorkingDir string `json:"WorkingDir,omitempty" yaml:"WorkingDir,omitempty" toml:"WorkingDir,omitempty"` MacAddress string `json:"MacAddress,omitempty" yaml:"MacAddress,omitempty" toml:"MacAddress,omitempty"` Entrypoint []string `json:"Entrypoint" yaml:"Entrypoint" toml:"Entrypoint"` SecurityOpts []string `json:"SecurityOpts,omitempty" yaml:"SecurityOpts,omitempty" toml:"SecurityOpts,omitempty"` OnBuild []string `json:"OnBuild,omitempty" yaml:"OnBuild,omitempty" toml:"OnBuild,omitempty"` Mounts []Mount `json:"Mounts,omitempty" yaml:"Mounts,omitempty" toml:"Mounts,omitempty"` Labels map[string]string `json:"Labels,omitempty" yaml:"Labels,omitempty" toml:"Labels,omitempty"` AttachStdin bool `json:"AttachStdin,omitempty" yaml:"AttachStdin,omitempty" toml:"AttachStdin,omitempty"` AttachStdout bool `json:"AttachStdout,omitempty" yaml:"AttachStdout,omitempty" toml:"AttachStdout,omitempty"` AttachStderr bool `json:"AttachStderr,omitempty" yaml:"AttachStderr,omitempty" toml:"AttachStderr,omitempty"` ArgsEscaped bool `json:"ArgsEscaped,omitempty" yaml:"ArgsEscaped,omitempty" toml:"ArgsEscaped,omitempty"` Tty bool `json:"Tty,omitempty" yaml:"Tty,omitempty" toml:"Tty,omitempty"` OpenStdin bool `json:"OpenStdin,omitempty" yaml:"OpenStdin,omitempty" toml:"OpenStdin,omitempty"` StdinOnce bool `json:"StdinOnce,omitempty" yaml:"StdinOnce,omitempty" toml:"StdinOnce,omitempty"` NetworkDisabled bool `json:"NetworkDisabled,omitempty" yaml:"NetworkDisabled,omitempty" toml:"NetworkDisabled,omitempty"` // This is no longer used and has been kept here for backward // compatibility, please use HostConfig.VolumesFrom. VolumesFrom string `json:"VolumesFrom,omitempty" yaml:"VolumesFrom,omitempty" toml:"VolumesFrom,omitempty"` } // HostMount represents a mount point in the container in HostConfig. // // It has been added in the version 1.25 of the Docker API type HostMount struct { Target string `json:"Target,omitempty" yaml:"Target,omitempty" toml:"Target,omitempty"` Source string `json:"Source,omitempty" yaml:"Source,omitempty" toml:"Source,omitempty"` Type string `json:"Type,omitempty" yaml:"Type,omitempty" toml:"Type,omitempty"` ReadOnly bool `json:"ReadOnly,omitempty" yaml:"ReadOnly,omitempty" toml:"ReadOnly,omitempty"` BindOptions *BindOptions `json:"BindOptions,omitempty" yaml:"BindOptions,omitempty" toml:"BindOptions,omitempty"` VolumeOptions *VolumeOptions `json:"VolumeOptions,omitempty" yaml:"VolumeOptions,omitempty" toml:"VolumeOptions,omitempty"` TempfsOptions *TempfsOptions `json:"TmpfsOptions,omitempty" yaml:"TmpfsOptions,omitempty" toml:"TmpfsOptions,omitempty"` } // BindOptions contains optional configuration for the bind type type BindOptions struct { Propagation string `json:"Propagation,omitempty" yaml:"Propagation,omitempty" toml:"Propagation,omitempty"` } // VolumeOptions contains optional configuration for the volume type type VolumeOptions struct { NoCopy bool `json:"NoCopy,omitempty" yaml:"NoCopy,omitempty" toml:"NoCopy,omitempty"` Labels map[string]string `json:"Labels,omitempty" yaml:"Labels,omitempty" toml:"Labels,omitempty"` DriverConfig VolumeDriverConfig `json:"DriverConfig,omitempty" yaml:"DriverConfig,omitempty" toml:"DriverConfig,omitempty"` } // TempfsOptions contains optional configuration for the tempfs type type TempfsOptions struct { SizeBytes int64 `json:"SizeBytes,omitempty" yaml:"SizeBytes,omitempty" toml:"SizeBytes,omitempty"` Mode int `json:"Mode,omitempty" yaml:"Mode,omitempty" toml:"Mode,omitempty"` Options [][]string `json:"Options,omitempty" yaml:"Options,omitempty" toml:"Options,omitempty"` } // VolumeDriverConfig holds a map of volume driver specific options type VolumeDriverConfig struct { Name string `json:"Name,omitempty" yaml:"Name,omitempty" toml:"Name,omitempty"` Options map[string]string `json:"Options,omitempty" yaml:"Options,omitempty" toml:"Options,omitempty"` } // Mount represents a mount point in the container. // // It has been added in the version 1.20 of the Docker API, available since // Docker 1.8. type Mount struct { Name string Source string Destination string Driver string Mode string RW bool } // LogConfig defines the log driver type and the configuration for it. type LogConfig struct { Type string `json:"Type,omitempty" yaml:"Type,omitempty" toml:"Type,omitempty"` Config map[string]string `json:"Config,omitempty" yaml:"Config,omitempty" toml:"Config,omitempty"` } // ULimit defines system-wide resource limitations This can help a lot in // system administration, e.g. when a user starts too many processes and // therefore makes the system unresponsive for other users. type ULimit struct { Name string `json:"Name,omitempty" yaml:"Name,omitempty" toml:"Name,omitempty"` Soft int64 `json:"Soft,omitempty" yaml:"Soft,omitempty" toml:"Soft,omitempty"` Hard int64 `json:"Hard,omitempty" yaml:"Hard,omitempty" toml:"Hard,omitempty"` } // SwarmNode containers information about which Swarm node the container is on. type SwarmNode struct { ID string `json:"ID,omitempty" yaml:"ID,omitempty" toml:"ID,omitempty"` IP string `json:"IP,omitempty" yaml:"IP,omitempty" toml:"IP,omitempty"` Addr string `json:"Addr,omitempty" yaml:"Addr,omitempty" toml:"Addr,omitempty"` Name string `json:"Name,omitempty" yaml:"Name,omitempty" toml:"Name,omitempty"` CPUs int64 `json:"CPUs,omitempty" yaml:"CPUs,omitempty" toml:"CPUs,omitempty"` Memory int64 `json:"Memory,omitempty" yaml:"Memory,omitempty" toml:"Memory,omitempty"` Labels map[string]string `json:"Labels,omitempty" yaml:"Labels,omitempty" toml:"Labels,omitempty"` } // GraphDriver contains information about the GraphDriver used by the // container. type GraphDriver struct { Name string `json:"Name,omitempty" yaml:"Name,omitempty" toml:"Name,omitempty"` Data map[string]string `json:"Data,omitempty" yaml:"Data,omitempty" toml:"Data,omitempty"` } // HealthConfig holds configuration settings for the HEALTHCHECK feature // // It has been added in the version 1.24 of the Docker API, available since // Docker 1.12. type HealthConfig struct { // Test is the test to perform to check that the container is healthy. // An empty slice means to inherit the default. // The options are: // {} : inherit healthcheck // {"NONE"} : disable healthcheck // {"CMD", args...} : exec arguments directly // {"CMD-SHELL", command} : run command with system's default shell Test []string `json:"Test,omitempty" yaml:"Test,omitempty" toml:"Test,omitempty"` // Zero means to inherit. Durations are expressed as integer nanoseconds. Interval time.Duration `json:"Interval,omitempty" yaml:"Interval,omitempty" toml:"Interval,omitempty"` // Interval is the time to wait between checks. Timeout time.Duration `json:"Timeout,omitempty" yaml:"Timeout,omitempty" toml:"Timeout,omitempty"` // Timeout is the time to wait before considering the check to have hung. StartPeriod time.Duration `json:"StartPeriod,omitempty" yaml:"StartPeriod,omitempty" toml:"StartPeriod,omitempty"` // The start period for the container to initialize before the retries starts to count down. StartInterval time.Duration `json:"StartInterval,omitempty" yaml:"StartInterval,omitempty" toml:"StartInterval,omitempty"` // The start interval is the time to wait between checks during the start period. // Retries is the number of consecutive failures needed to consider a container as unhealthy. // Zero means inherit. Retries int `json:"Retries,omitempty" yaml:"Retries,omitempty" toml:"Retries,omitempty"` } // Container is the type encompasing everything about a container - its config, // hostconfig, etc. type Container struct { ID string `json:"Id" yaml:"Id" toml:"Id"` Created time.Time `json:"Created,omitempty" yaml:"Created,omitempty" toml:"Created,omitempty"` Path string `json:"Path,omitempty" yaml:"Path,omitempty" toml:"Path,omitempty"` Args []string `json:"Args,omitempty" yaml:"Args,omitempty" toml:"Args,omitempty"` Config *Config `json:"Config,omitempty" yaml:"Config,omitempty" toml:"Config,omitempty"` State State `json:"State,omitempty" yaml:"State,omitempty" toml:"State,omitempty"` Image string `json:"Image,omitempty" yaml:"Image,omitempty" toml:"Image,omitempty"` Node *SwarmNode `json:"Node,omitempty" yaml:"Node,omitempty" toml:"Node,omitempty"` NetworkSettings *NetworkSettings `json:"NetworkSettings,omitempty" yaml:"NetworkSettings,omitempty" toml:"NetworkSettings,omitempty"` SysInitPath string `json:"SysInitPath,omitempty" yaml:"SysInitPath,omitempty" toml:"SysInitPath,omitempty"` ResolvConfPath string `json:"ResolvConfPath,omitempty" yaml:"ResolvConfPath,omitempty" toml:"ResolvConfPath,omitempty"` HostnamePath string `json:"HostnamePath,omitempty" yaml:"HostnamePath,omitempty" toml:"HostnamePath,omitempty"` HostsPath string `json:"HostsPath,omitempty" yaml:"HostsPath,omitempty" toml:"HostsPath,omitempty"` LogPath string `json:"LogPath,omitempty" yaml:"LogPath,omitempty" toml:"LogPath,omitempty"` Name string `json:"Name,omitempty" yaml:"Name,omitempty" toml:"Name,omitempty"` Driver string `json:"Driver,omitempty" yaml:"Driver,omitempty" toml:"Driver,omitempty"` Mounts []Mount `json:"Mounts,omitempty" yaml:"Mounts,omitempty" toml:"Mounts,omitempty"` Volumes map[string]string `json:"Volumes,omitempty" yaml:"Volumes,omitempty" toml:"Volumes,omitempty"` VolumesRW map[string]bool `json:"VolumesRW,omitempty" yaml:"VolumesRW,omitempty" toml:"VolumesRW,omitempty"` HostConfig *HostConfig `json:"HostConfig,omitempty" yaml:"HostConfig,omitempty" toml:"HostConfig,omitempty"` ExecIDs []string `json:"ExecIDs,omitempty" yaml:"ExecIDs,omitempty" toml:"ExecIDs,omitempty"` GraphDriver *GraphDriver `json:"GraphDriver,omitempty" yaml:"GraphDriver,omitempty" toml:"GraphDriver,omitempty"` RestartCount int `json:"RestartCount,omitempty" yaml:"RestartCount,omitempty" toml:"RestartCount,omitempty"` AppArmorProfile string `json:"AppArmorProfile,omitempty" yaml:"AppArmorProfile,omitempty" toml:"AppArmorProfile,omitempty"` MountLabel string `json:"MountLabel,omitempty" yaml:"MountLabel,omitempty" toml:"MountLabel,omitempty"` ProcessLabel string `json:"ProcessLabel,omitempty" yaml:"ProcessLabel,omitempty" toml:"ProcessLabel,omitempty"` Platform string `json:"Platform,omitempty" yaml:"Platform,omitempty" toml:"Platform,omitempty"` SizeRw int64 `json:"SizeRw,omitempty" yaml:"SizeRw,omitempty" toml:"SizeRw,omitempty"` SizeRootFs int64 `json:"SizeRootFs,omitempty" yaml:"SizeRootFs,omitempty" toml:"SizeRootFs,omitempty"` } // KeyValuePair is a type for generic key/value pairs as used in the Lxc // configuration type KeyValuePair struct { Key string `json:"Key,omitempty" yaml:"Key,omitempty" toml:"Key,omitempty"` Value string `json:"Value,omitempty" yaml:"Value,omitempty" toml:"Value,omitempty"` } // Device represents a device mapping between the Docker host and the // container. type Device struct { PathOnHost string `json:"PathOnHost,omitempty" yaml:"PathOnHost,omitempty" toml:"PathOnHost,omitempty"` PathInContainer string `json:"PathInContainer,omitempty" yaml:"PathInContainer,omitempty" toml:"PathInContainer,omitempty"` CgroupPermissions string `json:"CgroupPermissions,omitempty" yaml:"CgroupPermissions,omitempty" toml:"CgroupPermissions,omitempty"` } // DeviceRequest represents a request for device that's sent to device drivers. type DeviceRequest struct { Driver string `json:"Driver,omitempty" yaml:"Driver,omitempty" toml:"Driver,omitempty"` Count int `json:"Count,omitempty" yaml:"Count,omitempty" toml:"Count,omitempty"` DeviceIDs []string `json:"DeviceIDs,omitempty" yaml:"DeviceIDs,omitempty" toml:"DeviceIDs,omitempty"` Capabilities [][]string `json:"Capabilities,omitempty" yaml:"Capabilities,omitempty" toml:"Capabilities,omitempty"` Options map[string]string `json:"Options,omitempty" yaml:"Options,omitempty" toml:"Options,omitempty"` } // BlockWeight represents a relative device weight for an individual device inside // of a container type BlockWeight struct { Path string `json:"Path,omitempty"` Weight string `json:"Weight,omitempty"` } // BlockLimit represents a read/write limit in IOPS or Bandwidth for a device // inside of a container type BlockLimit struct { Path string `json:"Path,omitempty"` Rate int64 `json:"Rate,omitempty"` } // HostConfig contains the container options related to starting a container on // a given host type HostConfig struct { Binds []string `json:"Binds,omitempty" yaml:"Binds,omitempty" toml:"Binds,omitempty"` CapAdd []string `json:"CapAdd,omitempty" yaml:"CapAdd,omitempty" toml:"CapAdd,omitempty"` CapDrop []string `json:"CapDrop,omitempty" yaml:"CapDrop,omitempty" toml:"CapDrop,omitempty"` Capabilities []string `json:"Capabilities,omitempty" yaml:"Capabilities,omitempty" toml:"Capabilities,omitempty"` // Mutually exclusive w.r.t. CapAdd and CapDrop API v1.40 GroupAdd []string `json:"GroupAdd,omitempty" yaml:"GroupAdd,omitempty" toml:"GroupAdd,omitempty"` ContainerIDFile string `json:"ContainerIDFile,omitempty" yaml:"ContainerIDFile,omitempty" toml:"ContainerIDFile,omitempty"` LxcConf []KeyValuePair `json:"LxcConf,omitempty" yaml:"LxcConf,omitempty" toml:"LxcConf,omitempty"` PortBindings map[Port][]PortBinding `json:"PortBindings,omitempty" yaml:"PortBindings,omitempty" toml:"PortBindings,omitempty"` Links []string `json:"Links,omitempty" yaml:"Links,omitempty" toml:"Links,omitempty"` DNS []string `json:"Dns,omitempty" yaml:"Dns,omitempty" toml:"Dns,omitempty"` // For Docker API v1.10 and above only DNSOptions []string `json:"DnsOptions,omitempty" yaml:"DnsOptions,omitempty" toml:"DnsOptions,omitempty"` DNSSearch []string `json:"DnsSearch,omitempty" yaml:"DnsSearch,omitempty" toml:"DnsSearch,omitempty"` ExtraHosts []string `json:"ExtraHosts,omitempty" yaml:"ExtraHosts,omitempty" toml:"ExtraHosts,omitempty"` VolumesFrom []string `json:"VolumesFrom,omitempty" yaml:"VolumesFrom,omitempty" toml:"VolumesFrom,omitempty"` UsernsMode string `json:"UsernsMode,omitempty" yaml:"UsernsMode,omitempty" toml:"UsernsMode,omitempty"` NetworkMode string `json:"NetworkMode,omitempty" yaml:"NetworkMode,omitempty" toml:"NetworkMode,omitempty"` IpcMode string `json:"IpcMode,omitempty" yaml:"IpcMode,omitempty" toml:"IpcMode,omitempty"` Isolation string `json:"Isolation,omitempty" yaml:"Isolation,omitempty" toml:"Isolation,omitempty"` // Windows only ConsoleSize [2]int `json:"ConsoleSize,omitempty" yaml:"ConsoleSize,omitempty" toml:"ConsoleSize,omitempty"` // Windows only height x width PidMode string `json:"PidMode,omitempty" yaml:"PidMode,omitempty" toml:"PidMode,omitempty"` UTSMode string `json:"UTSMode,omitempty" yaml:"UTSMode,omitempty" toml:"UTSMode,omitempty"` RestartPolicy RestartPolicy `json:"RestartPolicy,omitempty" yaml:"RestartPolicy,omitempty" toml:"RestartPolicy,omitempty"` Devices []Device `json:"Devices,omitempty" yaml:"Devices,omitempty" toml:"Devices,omitempty"` DeviceCgroupRules []string `json:"DeviceCgroupRules,omitempty" yaml:"DeviceCgroupRules,omitempty" toml:"DeviceCgroupRules,omitempty"` DeviceRequests []DeviceRequest `json:"DeviceRequests,omitempty" yaml:"DeviceRequests,omitempty" toml:"DeviceRequests,omitempty"` LogConfig LogConfig `json:"LogConfig,omitempty" yaml:"LogConfig,omitempty" toml:"LogConfig,omitempty"` SecurityOpt []string `json:"SecurityOpt,omitempty" yaml:"SecurityOpt,omitempty" toml:"SecurityOpt,omitempty"` CgroupnsMode string `json:"CgroupnsMode,omitempty" yaml:"CgroupnsMode,omitempty" toml:"CgroupnsMode,omitempty"` // v1.40+ Cgroup string `json:"Cgroup,omitempty" yaml:"Cgroup,omitempty" toml:"Cgroup,omitempty"` CgroupParent string `json:"CgroupParent,omitempty" yaml:"CgroupParent,omitempty" toml:"CgroupParent,omitempty"` Memory int64 `json:"Memory,omitempty" yaml:"Memory,omitempty" toml:"Memory,omitempty"` MemoryReservation int64 `json:"MemoryReservation,omitempty" yaml:"MemoryReservation,omitempty" toml:"MemoryReservation,omitempty"` KernelMemory int64 `json:"KernelMemory,omitempty" yaml:"KernelMemory,omitempty" toml:"KernelMemory,omitempty"` MemorySwap int64 `json:"MemorySwap,omitempty" yaml:"MemorySwap,omitempty" toml:"MemorySwap,omitempty"` CPUShares int64 `json:"CpuShares,omitempty" yaml:"CpuShares,omitempty" toml:"CpuShares,omitempty"` CPUSet string `json:"Cpuset,omitempty" yaml:"Cpuset,omitempty" toml:"Cpuset,omitempty"` CPUSetCPUs string `json:"CpusetCpus,omitempty" yaml:"CpusetCpus,omitempty" toml:"CpusetCpus,omitempty"` CPUSetMEMs string `json:"CpusetMems,omitempty" yaml:"CpusetMems,omitempty" toml:"CpusetMems,omitempty"` CPUQuota int64 `json:"CpuQuota,omitempty" yaml:"CpuQuota,omitempty" toml:"CpuQuota,omitempty"` CPUPeriod int64 `json:"CpuPeriod,omitempty" yaml:"CpuPeriod,omitempty" toml:"CpuPeriod,omitempty"` CPURealtimePeriod int64 `json:"CpuRealtimePeriod,omitempty" yaml:"CpuRealtimePeriod,omitempty" toml:"CpuRealtimePeriod,omitempty"` CPURealtimeRuntime int64 `json:"CpuRealtimeRuntime,omitempty" yaml:"CpuRealtimeRuntime,omitempty" toml:"CpuRealtimeRuntime,omitempty"` NanoCPUs int64 `json:"NanoCpus,omitempty" yaml:"NanoCpus,omitempty" toml:"NanoCpus,omitempty"` BlkioWeight int64 `json:"BlkioWeight,omitempty" yaml:"BlkioWeight,omitempty" toml:"BlkioWeight,omitempty"` BlkioWeightDevice []BlockWeight `json:"BlkioWeightDevice,omitempty" yaml:"BlkioWeightDevice,omitempty" toml:"BlkioWeightDevice,omitempty"` BlkioDeviceReadBps []BlockLimit `json:"BlkioDeviceReadBps,omitempty" yaml:"BlkioDeviceReadBps,omitempty" toml:"BlkioDeviceReadBps,omitempty"` BlkioDeviceReadIOps []BlockLimit `json:"BlkioDeviceReadIOps,omitempty" yaml:"BlkioDeviceReadIOps,omitempty" toml:"BlkioDeviceReadIOps,omitempty"` BlkioDeviceWriteBps []BlockLimit `json:"BlkioDeviceWriteBps,omitempty" yaml:"BlkioDeviceWriteBps,omitempty" toml:"BlkioDeviceWriteBps,omitempty"` BlkioDeviceWriteIOps []BlockLimit `json:"BlkioDeviceWriteIOps,omitempty" yaml:"BlkioDeviceWriteIOps,omitempty" toml:"BlkioDeviceWriteIOps,omitempty"` Ulimits []ULimit `json:"Ulimits,omitempty" yaml:"Ulimits,omitempty" toml:"Ulimits,omitempty"` VolumeDriver string `json:"VolumeDriver,omitempty" yaml:"VolumeDriver,omitempty" toml:"VolumeDriver,omitempty"` OomScoreAdj int `json:"OomScoreAdj,omitempty" yaml:"OomScoreAdj,omitempty" toml:"OomScoreAdj,omitempty"` MemorySwappiness *int64 `json:"MemorySwappiness,omitempty" yaml:"MemorySwappiness,omitempty" toml:"MemorySwappiness,omitempty"` PidsLimit *int64 `json:"PidsLimit,omitempty" yaml:"PidsLimit,omitempty" toml:"PidsLimit,omitempty"` OOMKillDisable *bool `json:"OomKillDisable,omitempty" yaml:"OomKillDisable,omitempty" toml:"OomKillDisable,omitempty"` ShmSize int64 `json:"ShmSize,omitempty" yaml:"ShmSize,omitempty" toml:"ShmSize,omitempty"` Tmpfs map[string]string `json:"Tmpfs,omitempty" yaml:"Tmpfs,omitempty" toml:"Tmpfs,omitempty"` StorageOpt map[string]string `json:"StorageOpt,omitempty" yaml:"StorageOpt,omitempty" toml:"StorageOpt,omitempty"` Sysctls map[string]string `json:"Sysctls,omitempty" yaml:"Sysctls,omitempty" toml:"Sysctls,omitempty"` CPUCount int64 `json:"CpuCount,omitempty" yaml:"CpuCount,omitempty"` CPUPercent int64 `json:"CpuPercent,omitempty" yaml:"CpuPercent,omitempty"` IOMaximumBandwidth int64 `json:"IOMaximumBandwidth,omitempty" yaml:"IOMaximumBandwidth,omitempty"` IOMaximumIOps int64 `json:"IOMaximumIOps,omitempty" yaml:"IOMaximumIOps,omitempty"` Mounts []HostMount `json:"Mounts,omitempty" yaml:"Mounts,omitempty" toml:"Mounts,omitempty"` MaskedPaths []string `json:"MaskedPaths,omitempty" yaml:"MaskedPaths,omitempty" toml:"MaskedPaths,omitempty"` ReadonlyPaths []string `json:"ReadonlyPaths,omitempty" yaml:"ReadonlyPaths,omitempty" toml:"ReadonlyPaths,omitempty"` Runtime string `json:"Runtime,omitempty" yaml:"Runtime,omitempty" toml:"Runtime,omitempty"` Init bool `json:",omitempty" yaml:",omitempty"` Privileged bool `json:"Privileged,omitempty" yaml:"Privileged,omitempty" toml:"Privileged,omitempty"` PublishAllPorts bool `json:"PublishAllPorts,omitempty" yaml:"PublishAllPorts,omitempty" toml:"PublishAllPorts,omitempty"` ReadonlyRootfs bool `json:"ReadonlyRootfs,omitempty" yaml:"ReadonlyRootfs,omitempty" toml:"ReadonlyRootfs,omitempty"` AutoRemove bool `json:"AutoRemove,omitempty" yaml:"AutoRemove,omitempty" toml:"AutoRemove,omitempty"` Annotations map[string]string `json:"Annotations,omitempty" yaml:"Annotations,omitempty" toml:"Annotations,omitempty"` } // NetworkingConfig represents the container's networking configuration for each of its interfaces // Carries the networking configs specified in the `docker run` and `docker network connect` commands type NetworkingConfig struct { EndpointsConfig map[string]*EndpointConfig `json:"EndpointsConfig" yaml:"EndpointsConfig" toml:"EndpointsConfig"` // Endpoint configs for each connecting network } // NoSuchContainer is the error returned when a given container does not exist. type NoSuchContainer struct { ID string Err error } func (err *NoSuchContainer) Error() string { if err.Err != nil { return err.Err.Error() } return "No such container: " + err.ID } // ContainerAlreadyRunning is the error returned when a given container is // already running. type ContainerAlreadyRunning struct { ID string } func (err *ContainerAlreadyRunning) Error() string { return "Container already running: " + err.ID } // ContainerNotRunning is the error returned when a given container is not // running. type ContainerNotRunning struct { ID string } func (err *ContainerNotRunning) Error() string { return "Container not running: " + err.ID } go-dockerclient-1.12.0/container_archive.go000066400000000000000000000034011465717111200206610ustar00rootroot00000000000000package docker import ( "context" "fmt" "io" "net/http" "time" ) // UploadToContainerOptions is the set of options that can be used when // uploading an archive into a container. // // See https://goo.gl/g25o7u for more details. type UploadToContainerOptions struct { InputStream io.Reader `json:"-" qs:"-"` Path string `qs:"path"` NoOverwriteDirNonDir bool `qs:"noOverwriteDirNonDir"` Context context.Context } // UploadToContainer uploads a tar archive to be extracted to a path in the // filesystem of the container. // // See https://goo.gl/g25o7u for more details. func (c *Client) UploadToContainer(id string, opts UploadToContainerOptions) error { url := fmt.Sprintf("/containers/%s/archive?", id) + queryString(opts) return c.stream(http.MethodPut, url, streamOptions{ in: opts.InputStream, context: opts.Context, }) } // DownloadFromContainerOptions is the set of options that can be used when // downloading resources from a container. // // See https://goo.gl/W49jxK for more details. type DownloadFromContainerOptions struct { OutputStream io.Writer `json:"-" qs:"-"` Path string `qs:"path"` InactivityTimeout time.Duration `qs:"-"` Context context.Context } // DownloadFromContainer downloads a tar archive of files or folders in a container. // // See https://goo.gl/W49jxK for more details. func (c *Client) DownloadFromContainer(id string, opts DownloadFromContainerOptions) error { url := fmt.Sprintf("/containers/%s/archive?", id) + queryString(opts) return c.stream(http.MethodGet, url, streamOptions{ setRawTerminal: true, stdout: opts.OutputStream, inactivityTimeout: opts.InactivityTimeout, context: opts.Context, }) } go-dockerclient-1.12.0/container_archive_test.go000066400000000000000000000026541465717111200217310ustar00rootroot00000000000000package docker import ( "bytes" "net/http" "testing" ) func TestUploadToContainer(t *testing.T) { t.Parallel() content := "File content" in := stdinMock{bytes.NewBufferString(content)} fakeRT := &FakeRoundTripper{status: http.StatusOK} client := newTestClient(fakeRT) opts := UploadToContainerOptions{ Path: "abc", InputStream: in, } err := client.UploadToContainer("a123456", opts) if err != nil { t.Errorf("UploadToContainer: caught error %#v while uploading archive to container, expected nil", err) } req := fakeRT.requests[0] if req.Method != http.MethodPut { t.Errorf("UploadToContainer{Path:abc}: Wrong HTTP method. Want PUT. Got %s", req.Method) } if pathParam := req.URL.Query().Get("path"); pathParam != "abc" { t.Errorf("ListImages({Path:abc}): Wrong parameter. Want path=abc. Got path=%s", pathParam) } } func TestDownloadFromContainer(t *testing.T) { t.Parallel() filecontent := "File content" client := newTestClient(&FakeRoundTripper{message: filecontent, status: http.StatusOK}) var out bytes.Buffer opts := DownloadFromContainerOptions{ OutputStream: &out, } err := client.DownloadFromContainer("a123456", opts) if err != nil { t.Errorf("DownloadFromContainer: caught error %#v while downloading from container, expected nil", err.Error()) } if out.String() != filecontent { t.Errorf("DownloadFromContainer: wrong stdout. Want %#v. Got %#v.", filecontent, out.String()) } } go-dockerclient-1.12.0/container_attach.go000066400000000000000000000037601465717111200205140ustar00rootroot00000000000000package docker import ( "io" "net/http" ) // AttachToContainerOptions is the set of options that can be used when // attaching to a container. // // See https://goo.gl/JF10Zk for more details. type AttachToContainerOptions struct { Container string `qs:"-"` InputStream io.Reader `qs:"-"` OutputStream io.Writer `qs:"-"` ErrorStream io.Writer `qs:"-"` // If set, after a successful connect, a sentinel will be sent and then the // client will block on receive before continuing. // // It must be an unbuffered channel. Using a buffered channel can lead // to unexpected behavior. Success chan struct{} // Override the key sequence for detaching a container. DetachKeys string // Use raw terminal? Usually true when the container contains a TTY. RawTerminal bool `qs:"-"` // Get container logs, sending it to OutputStream. Logs bool // Stream the response? Stream bool // Attach to stdin, and use InputStream. Stdin bool // Attach to stdout, and use OutputStream. Stdout bool // Attach to stderr, and use ErrorStream. Stderr bool } // AttachToContainer attaches to a container, using the given options. // // See https://goo.gl/JF10Zk for more details. func (c *Client) AttachToContainer(opts AttachToContainerOptions) error { cw, err := c.AttachToContainerNonBlocking(opts) if err != nil { return err } return cw.Wait() } // AttachToContainerNonBlocking attaches to a container, using the given options. // This function does not block. // // See https://goo.gl/NKpkFk for more details. func (c *Client) AttachToContainerNonBlocking(opts AttachToContainerOptions) (CloseWaiter, error) { if opts.Container == "" { return nil, &NoSuchContainer{ID: opts.Container} } path := "/containers/" + opts.Container + "/attach?" + queryString(opts) return c.hijack(http.MethodPost, path, hijackOptions{ success: opts.Success, setRawTerminal: opts.RawTerminal, in: opts.InputStream, stdout: opts.OutputStream, stderr: opts.ErrorStream, }) } go-dockerclient-1.12.0/container_attach_test.go000066400000000000000000000173041465717111200215520ustar00rootroot00000000000000package docker import ( "bytes" "net/http" "net/http/httptest" "net/url" "reflect" "strings" "testing" "time" ) func TestAttachToContainerLogs(t *testing.T) { t.Parallel() var req http.Request server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Write([]byte{1, 0, 0, 0, 0, 0, 0, 19}) w.Write([]byte("something happened!")) req = *r })) defer server.Close() client, _ := NewClient(server.URL) client.SkipServerVersionCheck = true var buf bytes.Buffer opts := AttachToContainerOptions{ Container: "a123456", OutputStream: &buf, Stdout: true, Stderr: true, Logs: true, } err := client.AttachToContainer(opts) if err != nil { t.Fatal(err) } expected := "something happened!" if buf.String() != expected { t.Errorf("AttachToContainer for logs: wrong output. Want %q. Got %q.", expected, buf.String()) } if req.Method != http.MethodPost { t.Errorf("AttachToContainer: wrong HTTP method. Want POST. Got %s.", req.Method) } u, _ := url.Parse(client.getURL("/containers/a123456/attach")) if req.URL.Path != u.Path { t.Errorf("AttachToContainer for logs: wrong HTTP path. Want %q. Got %q.", u.Path, req.URL.Path) } expectedQs := map[string][]string{ "logs": {"1"}, "stdout": {"1"}, "stderr": {"1"}, } got := map[string][]string(req.URL.Query()) if !reflect.DeepEqual(got, expectedQs) { t.Errorf("AttachToContainer: wrong query string. Want %#v. Got %#v.", expectedQs, got) } } func TestAttachToContainer(t *testing.T) { t.Parallel() reader := strings.NewReader("send value") var req http.Request server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Write([]byte{1, 0, 0, 0, 0, 0, 0, 5}) w.Write([]byte("hello")) req = *r })) defer server.Close() client, _ := NewClient(server.URL) client.SkipServerVersionCheck = true var stdout, stderr bytes.Buffer opts := AttachToContainerOptions{ Container: "a123456", OutputStream: &stdout, ErrorStream: &stderr, InputStream: reader, Stdin: true, Stdout: true, Stderr: true, Stream: true, RawTerminal: true, } err := client.AttachToContainer(opts) if err != nil { t.Fatal(err) } expected := map[string][]string{ "stdin": {"1"}, "stdout": {"1"}, "stderr": {"1"}, "stream": {"1"}, } got := map[string][]string(req.URL.Query()) if !reflect.DeepEqual(got, expected) { t.Errorf("AttachToContainer: wrong query string. Want %#v. Got %#v.", expected, got) } } func TestAttachToContainerSentinel(t *testing.T) { t.Parallel() reader := strings.NewReader("send value") server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { w.Write([]byte{1, 0, 0, 0, 0, 0, 0, 5}) w.Write([]byte("hello")) })) defer server.Close() client, _ := NewClient(server.URL) client.SkipServerVersionCheck = true var stdout, stderr bytes.Buffer success := make(chan struct{}) opts := AttachToContainerOptions{ Container: "a123456", OutputStream: &stdout, ErrorStream: &stderr, InputStream: reader, Stdin: true, Stdout: true, Stderr: true, Stream: true, RawTerminal: true, Success: success, } errCh := make(chan error) go func() { errCh <- client.AttachToContainer(opts) }() success <- <-success if err := <-errCh; err != nil { t.Error(err) } } func TestAttachToContainerNilStdout(t *testing.T) { t.Parallel() reader := strings.NewReader("send value") server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { w.Write([]byte{1, 0, 0, 0, 0, 0, 0, 5}) w.Write([]byte("hello")) })) defer server.Close() client, _ := NewClient(server.URL) client.SkipServerVersionCheck = true var stderr bytes.Buffer opts := AttachToContainerOptions{ Container: "a123456", OutputStream: nil, ErrorStream: &stderr, InputStream: reader, Stdin: true, Stdout: true, Stderr: true, Stream: true, RawTerminal: true, } err := client.AttachToContainer(opts) if err != nil { t.Fatal(err) } } func TestAttachToContainerNilStderr(t *testing.T) { t.Parallel() reader := strings.NewReader("send value") server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { w.Write([]byte{1, 0, 0, 0, 0, 0, 0, 5}) w.Write([]byte("hello")) })) defer server.Close() client, _ := NewClient(server.URL) client.SkipServerVersionCheck = true var stdout bytes.Buffer opts := AttachToContainerOptions{ Container: "a123456", OutputStream: &stdout, InputStream: reader, Stdin: true, Stdout: true, Stderr: true, Stream: true, RawTerminal: true, } err := client.AttachToContainer(opts) if err != nil { t.Fatal(err) } } func TestAttachToContainerStdinOnly(t *testing.T) { t.Parallel() reader := strings.NewReader("send value") serverFinished := make(chan struct{}) clientFinished := make(chan struct{}) server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { w.WriteHeader(http.StatusOK) hj, ok := w.(http.Hijacker) if !ok { t.Fatal("cannot hijack server connection") } conn, _, err := hj.Hijack() if err != nil { t.Fatal(err) } // wait for client to indicate it's finished <-clientFinished // inform test that the server has finished close(serverFinished) conn.Close() })) defer server.Close() client, _ := NewClient(server.URL) client.SkipServerVersionCheck = true success := make(chan struct{}) opts := AttachToContainerOptions{ Container: "a123456", InputStream: reader, Stdin: true, Stdout: false, Stderr: false, Stream: true, RawTerminal: false, Success: success, } go func() { if err := client.AttachToContainer(opts); err != nil { t.Error(err) } // client's attach session is over close(clientFinished) }() success <- <-success // wait for server to finish handling attach <-serverFinished } func TestAttachToContainerRawTerminalFalse(t *testing.T) { t.Parallel() input := strings.NewReader("send value") var req http.Request server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { req = *r w.WriteHeader(http.StatusOK) hj, ok := w.(http.Hijacker) if !ok { t.Fatal("cannot hijack server connection") } conn, _, err := hj.Hijack() if err != nil { t.Fatal(err) } conn.Write([]byte{1, 0, 0, 0, 0, 0, 0, 5}) conn.Write([]byte("hello")) conn.Write([]byte{2, 0, 0, 0, 0, 0, 0, 6}) conn.Write([]byte("hello!")) time.Sleep(10 * time.Millisecond) conn.Close() })) client, _ := NewClient(server.URL) client.SkipServerVersionCheck = true var stdout, stderr bytes.Buffer opts := AttachToContainerOptions{ Container: "a123456", OutputStream: &stdout, ErrorStream: &stderr, InputStream: input, Stdin: true, Stdout: true, Stderr: true, Stream: true, RawTerminal: false, } client.AttachToContainer(opts) expected := map[string][]string{ "stdin": {"1"}, "stdout": {"1"}, "stderr": {"1"}, "stream": {"1"}, } got := map[string][]string(req.URL.Query()) server.Close() if !reflect.DeepEqual(got, expected) { t.Errorf("AttachToContainer: wrong query string. Want %#v. Got %#v.", expected, got) } if stdout.String() != "hello" { t.Errorf("AttachToContainer: wrong content written to stdout. Want %q. Got %q.", "hello", stdout.String()) } if stderr.String() != "hello!" { t.Errorf("AttachToContainer: wrong content written to stderr. Want %q. Got %q.", "hello!", stderr.String()) } } func TestAttachToContainerWithoutContainer(t *testing.T) { t.Parallel() var client Client err := client.AttachToContainer(AttachToContainerOptions{}) expectNoSuchContainer(t, "", err) } go-dockerclient-1.12.0/container_changes.go000066400000000000000000000012421465717111200206510ustar00rootroot00000000000000package docker import ( "encoding/json" "errors" "net/http" ) // ContainerChanges returns changes in the filesystem of the given container. // // See https://goo.gl/15KKzh for more details. func (c *Client) ContainerChanges(id string) ([]Change, error) { path := "/containers/" + id + "/changes" resp, err := c.do(http.MethodGet, path, doOptions{}) if err != nil { var e *Error if errors.As(err, &e) && e.Status == http.StatusNotFound { return nil, &NoSuchContainer{ID: id} } return nil, err } defer resp.Body.Close() var changes []Change if err := json.NewDecoder(resp.Body).Decode(&changes); err != nil { return nil, err } return changes, nil } go-dockerclient-1.12.0/container_changes_test.go000066400000000000000000000036511465717111200217160ustar00rootroot00000000000000package docker import ( "encoding/json" "net/http" "net/url" "reflect" "testing" ) func TestContainerChanges(t *testing.T) { t.Parallel() jsonChanges := `[ { "Path":"/dev", "Kind":0 }, { "Path":"/dev/kmsg", "Kind":1 }, { "Path":"/test", "Kind":1 } ]` var expected []Change err := json.Unmarshal([]byte(jsonChanges), &expected) if err != nil { t.Fatal(err) } fakeRT := &FakeRoundTripper{message: jsonChanges, status: http.StatusOK} client := newTestClient(fakeRT) id := "4fa6e0f0c678" changes, err := client.ContainerChanges(id) if err != nil { t.Fatal(err) } if !reflect.DeepEqual(changes, expected) { t.Errorf("ContainerChanges(%q): Expected %#v. Got %#v.", id, expected, changes) } expectedURL, _ := url.Parse(client.getURL("/containers/4fa6e0f0c678/changes")) if gotPath := fakeRT.requests[0].URL.Path; gotPath != expectedURL.Path { t.Errorf("ContainerChanges(%q): Wrong path in request. Want %q. Got %q.", id, expectedURL.Path, gotPath) } } func TestContainerChangesFailure(t *testing.T) { t.Parallel() client := newTestClient(&FakeRoundTripper{message: "server error", status: 500}) expected := Error{Status: 500, Message: "server error"} changes, err := client.ContainerChanges("abe033") if changes != nil { t.Errorf("ContainerChanges: Expected changes, got %#v", changes) } if !reflect.DeepEqual(expected, *err.(*Error)) { t.Errorf("ContainerChanges: Wrong error information. Want %#v. Got %#v.", expected, err) } } func TestContainerChangesNotFound(t *testing.T) { t.Parallel() const containerID = "abe033" client := newTestClient(&FakeRoundTripper{message: "no such container", status: 404}) changes, err := client.ContainerChanges(containerID) if changes != nil { t.Errorf("ContainerChanges: Expected changes, got %#v", changes) } expectNoSuchContainer(t, containerID, err) } go-dockerclient-1.12.0/container_commit.go000066400000000000000000000021451465717111200205340ustar00rootroot00000000000000package docker import ( "context" "encoding/json" "errors" "net/http" ) // CommitContainerOptions aggregates parameters to the CommitContainer method. // // See https://goo.gl/CzIguf for more details. type CommitContainerOptions struct { Container string Repository string `qs:"repo"` Tag string Message string `qs:"comment"` Author string Changes []string `qs:"changes"` Run *Config `qs:"-"` Context context.Context } // CommitContainer creates a new image from a container's changes. // // See https://goo.gl/CzIguf for more details. func (c *Client) CommitContainer(opts CommitContainerOptions) (*Image, error) { path := "/commit?" + queryString(opts) resp, err := c.do(http.MethodPost, path, doOptions{ data: opts.Run, context: opts.Context, }) if err != nil { var e *Error if errors.As(err, &e) && e.Status == http.StatusNotFound { return nil, &NoSuchContainer{ID: opts.Container} } return nil, err } defer resp.Body.Close() var image Image if err := json.NewDecoder(resp.Body).Decode(&image); err != nil { return nil, err } return &image, nil } go-dockerclient-1.12.0/container_commit_test.go000066400000000000000000000055351465717111200216010ustar00rootroot00000000000000package docker import ( "bytes" "encoding/json" "io" "net/http" "reflect" "testing" ) func TestCommitContainer(t *testing.T) { t.Parallel() response := `{"Id":"596069db4bf5"}` client := newTestClient(&FakeRoundTripper{message: response, status: http.StatusOK}) id := "596069db4bf5" image, err := client.CommitContainer(CommitContainerOptions{}) if err != nil { t.Fatal(err) } if image.ID != id { t.Errorf("CommitContainer: Wrong image id. Want %q. Got %q.", id, image.ID) } } func TestCommitContainerParams(t *testing.T) { t.Parallel() cfg := Config{Memory: 67108864} json, _ := json.Marshal(&cfg) tests := []struct { input CommitContainerOptions params map[string][]string body []byte }{ {CommitContainerOptions{}, map[string][]string{}, nil}, {CommitContainerOptions{Container: "44c004db4b17"}, map[string][]string{"container": {"44c004db4b17"}}, nil}, { CommitContainerOptions{Container: "44c004db4b17", Repository: "tsuru/python", Message: "something"}, map[string][]string{"container": {"44c004db4b17"}, "repo": {"tsuru/python"}, "comment": {"something"}}, nil, }, { CommitContainerOptions{Container: "44c004db4b17", Run: &cfg}, map[string][]string{"container": {"44c004db4b17"}}, json, }, } const expectedPath = "/commit" for _, tt := range tests { test := tt t.Run("", func(t *testing.T) { t.Parallel() fakeRT := &FakeRoundTripper{message: "{}", status: http.StatusOK} client := newTestClient(fakeRT) if _, err := client.CommitContainer(test.input); err != nil { t.Error(err) } got := map[string][]string(fakeRT.requests[0].URL.Query()) if !reflect.DeepEqual(got, test.params) { t.Errorf("Expected %#v, got %#v.", test.params, got) } if path := fakeRT.requests[0].URL.Path; path != expectedPath { t.Errorf("Wrong path on request. Want %q. Got %q.", expectedPath, path) } if meth := fakeRT.requests[0].Method; meth != http.MethodPost { t.Errorf("Wrong HTTP method. Want POST. Got %s.", meth) } if test.body != nil { if requestBody, err := io.ReadAll(fakeRT.requests[0].Body); err == nil { if !bytes.Equal(requestBody, test.body) { t.Errorf("Expected body %#v, got %#v", test.body, requestBody) } } else { t.Errorf("Error reading request body: %#v", err) } } }) } } func TestCommitContainerFailure(t *testing.T) { t.Parallel() client := newTestClient(&FakeRoundTripper{message: "no such container", status: http.StatusInternalServerError}) _, err := client.CommitContainer(CommitContainerOptions{}) if err == nil { t.Error("Expected non-nil error, got .") } } func TestCommitContainerNotFound(t *testing.T) { t.Parallel() client := newTestClient(&FakeRoundTripper{message: "no such container", status: http.StatusNotFound}) _, err := client.CommitContainer(CommitContainerOptions{}) expectNoSuchContainer(t, "", err) } go-dockerclient-1.12.0/container_copy.go000066400000000000000000000025731465717111200202230ustar00rootroot00000000000000package docker import ( "context" "errors" "fmt" "io" "net/http" ) // CopyFromContainerOptions contains the set of options used for copying // files from a container. // // Deprecated: Use DownloadFromContainerOptions and DownloadFromContainer instead. type CopyFromContainerOptions struct { OutputStream io.Writer `json:"-"` Container string `json:"-"` Resource string Context context.Context `json:"-"` } // CopyFromContainer copies files from a container. // // Deprecated: Use DownloadFromContainer and DownloadFromContainer instead. func (c *Client) CopyFromContainer(opts CopyFromContainerOptions) error { if opts.Container == "" { return &NoSuchContainer{ID: opts.Container} } if c.serverAPIVersion == nil { c.checkAPIVersion() } if c.serverAPIVersion != nil && c.serverAPIVersion.GreaterThanOrEqualTo(apiVersion124) { return errors.New("go-dockerclient: CopyFromContainer is no longer available in Docker >= 1.12, use DownloadFromContainer instead") } url := fmt.Sprintf("/containers/%s/copy", opts.Container) resp, err := c.do(http.MethodPost, url, doOptions{ data: opts, context: opts.Context, }) if err != nil { var e *Error if errors.As(err, &e) && e.Status == http.StatusNotFound { return &NoSuchContainer{ID: opts.Container} } return err } defer resp.Body.Close() _, err = io.Copy(opts.OutputStream, resp.Body) return err } go-dockerclient-1.12.0/container_copy_test.go000066400000000000000000000030331465717111200212520ustar00rootroot00000000000000package docker import ( "bytes" "net/http" "testing" ) func TestCopyFromContainer(t *testing.T) { t.Parallel() content := "File content" out := stdoutMock{bytes.NewBufferString(content)} client := newTestClient(&FakeRoundTripper{status: http.StatusOK}) opts := CopyFromContainerOptions{ Container: "a123456", OutputStream: &out, } err := client.CopyFromContainer(opts) if err != nil { t.Errorf("CopyFromContainer: caught error %#v while copying from container, expected nil", err.Error()) } if out.String() != content { t.Errorf("CopyFromContainer: wrong stdout. Want %#v. Got %#v.", content, out.String()) } } func TestCopyFromContainerEmptyContainer(t *testing.T) { t.Parallel() client := newTestClient(&FakeRoundTripper{status: http.StatusOK}) err := client.CopyFromContainer(CopyFromContainerOptions{}) _, ok := err.(*NoSuchContainer) if !ok { t.Errorf("CopyFromContainer: invalid error returned. Want NoSuchContainer, got %#v.", err) } } func TestCopyFromContainerDockerAPI124(t *testing.T) { t.Parallel() client := newTestClient(&FakeRoundTripper{status: http.StatusOK}) client.serverAPIVersion = apiVersion124 opts := CopyFromContainerOptions{ Container: "a123456", } err := client.CopyFromContainer(opts) if err == nil { t.Fatal("got unexpected error") } expectedMsg := "go-dockerclient: CopyFromContainer is no longer available in Docker >= 1.12, use DownloadFromContainer instead" if err.Error() != expectedMsg { t.Errorf("wrong error message\nWant %q\nGot %q", expectedMsg, err.Error()) } } go-dockerclient-1.12.0/container_create.go000066400000000000000000000043771465717111200205200ustar00rootroot00000000000000package docker import ( "context" "encoding/json" "errors" "net/http" "strings" ) // ErrContainerAlreadyExists is the error returned by CreateContainer when the // container already exists. var ErrContainerAlreadyExists = errors.New("container already exists") // CreateContainerOptions specify parameters to the CreateContainer function. // // See https://goo.gl/tyzwVM for more details. type CreateContainerOptions struct { Name string Platform string Config *Config `qs:"-"` HostConfig *HostConfig `qs:"-"` NetworkingConfig *NetworkingConfig `qs:"-"` Context context.Context } // CreateContainer creates a new container, returning the container instance, // or an error in case of failure. // // The returned container instance contains only the container ID. To get more // details about the container after creating it, use InspectContainer. // // See https://goo.gl/tyzwVM for more details. func (c *Client) CreateContainer(opts CreateContainerOptions) (*Container, error) { path := "/containers/create?" + queryString(opts) resp, err := c.do( http.MethodPost, path, doOptions{ data: struct { *Config HostConfig *HostConfig `json:"HostConfig,omitempty" yaml:"HostConfig,omitempty" toml:"HostConfig,omitempty"` NetworkingConfig *NetworkingConfig `json:"NetworkingConfig,omitempty" yaml:"NetworkingConfig,omitempty" toml:"NetworkingConfig,omitempty"` }{ opts.Config, opts.HostConfig, opts.NetworkingConfig, }, context: opts.Context, }, ) var e *Error if errors.As(err, &e) { if e.Status == http.StatusNotFound && strings.Contains(e.Message, "No such image") { return nil, ErrNoSuchImage } if e.Status == http.StatusConflict { return nil, ErrContainerAlreadyExists } // Workaround for 17.09 bug returning 400 instead of 409. // See https://github.com/moby/moby/issues/35021 if e.Status == http.StatusBadRequest && strings.Contains(e.Message, "Conflict.") { return nil, ErrContainerAlreadyExists } } if err != nil { return nil, err } defer resp.Body.Close() var container Container if err := json.NewDecoder(resp.Body).Decode(&container); err != nil { return nil, err } container.Name = opts.Name return &container, nil } go-dockerclient-1.12.0/container_create_test.go000066400000000000000000000123741465717111200215530ustar00rootroot00000000000000package docker import ( "encoding/json" "errors" "net/http" "net/url" "testing" ) func TestCreateContainer(t *testing.T) { t.Parallel() jsonContainer := `{ "Id": "4fa6e0f0c6786287e131c3852c58a2e01cc697a68231826813597e4994f1d6e2", "Warnings": [] }` var expected Container err := json.Unmarshal([]byte(jsonContainer), &expected) if err != nil { t.Fatal(err) } fakeRT := &FakeRoundTripper{message: jsonContainer, status: http.StatusOK} client := newTestClient(fakeRT) config := Config{AttachStdout: true, AttachStdin: true} opts := CreateContainerOptions{Name: "TestCreateContainer", Config: &config} container, err := client.CreateContainer(opts) if err != nil { t.Fatal(err) } id := "4fa6e0f0c6786287e131c3852c58a2e01cc697a68231826813597e4994f1d6e2" if container.ID != id { t.Errorf("CreateContainer: wrong ID. Want %q. Got %q.", id, container.ID) } req := fakeRT.requests[0] if req.Method != http.MethodPost { t.Errorf("CreateContainer: wrong HTTP method. Want %q. Got %q.", http.MethodPost, req.Method) } expectedURL, _ := url.Parse(client.getURL("/containers/create")) if gotPath := req.URL.Path; gotPath != expectedURL.Path { t.Errorf("CreateContainer: Wrong path in request. Want %q. Got %q.", expectedURL.Path, gotPath) } var gotBody Config err = json.NewDecoder(req.Body).Decode(&gotBody) if err != nil { t.Fatal(err) } } func TestCreateContainerImageNotFound(t *testing.T) { t.Parallel() client := newTestClient(&FakeRoundTripper{message: "No such image: whatever", status: http.StatusNotFound}) config := Config{AttachStdout: true, AttachStdin: true} container, err := client.CreateContainer(CreateContainerOptions{Config: &config}) if container != nil { t.Errorf("CreateContainer: expected container, got %#v.", container) } if !errors.Is(err, ErrNoSuchImage) { t.Errorf("CreateContainer: Wrong error type. Want %#v. Got %#v.", ErrNoSuchImage, err) } } func TestCreateContainerDuplicateName(t *testing.T) { t.Parallel() client := newTestClient(&FakeRoundTripper{message: "No such image", status: http.StatusConflict}) config := Config{AttachStdout: true, AttachStdin: true} container, err := client.CreateContainer(CreateContainerOptions{Config: &config}) if container != nil { t.Errorf("CreateContainer: expected container, got %#v.", container) } if !errors.Is(err, ErrContainerAlreadyExists) { t.Errorf("CreateContainer: Wrong error type. Want %#v. Got %#v.", ErrContainerAlreadyExists, err) } } // Workaround for 17.09 bug returning 400 instead of 409. // See https://github.com/moby/moby/issues/35021 func TestCreateContainerDuplicateNameWorkaroundDocker17_09(t *testing.T) { t.Parallel() client := newTestClient(&FakeRoundTripper{message: `{"message":"Conflict. The container name \"/c1\" is already in use by container \"2ce137e165dfca5e087f247b5d05a2311f91ef3da4bb7772168446a1a47e2f68\". You have to remove (or rename) that container to be able to reuse that name."}`, status: http.StatusBadRequest}) config := Config{AttachStdout: true, AttachStdin: true} container, err := client.CreateContainer(CreateContainerOptions{Config: &config}) if container != nil { t.Errorf("CreateContainer: expected container, got %#v.", container) } if !errors.Is(err, ErrContainerAlreadyExists) { t.Errorf("CreateContainer: Wrong error type. Want %#v. Got %#v.", ErrContainerAlreadyExists, err) } } func TestCreateContainerWithHostConfig(t *testing.T) { t.Parallel() fakeRT := &FakeRoundTripper{message: "{}", status: http.StatusOK} client := newTestClient(fakeRT) config := Config{} hostConfig := HostConfig{PublishAllPorts: true} opts := CreateContainerOptions{Name: "TestCreateContainerWithHostConfig", Config: &config, HostConfig: &hostConfig} _, err := client.CreateContainer(opts) if err != nil { t.Fatal(err) } req := fakeRT.requests[0] var gotBody map[string]any err = json.NewDecoder(req.Body).Decode(&gotBody) if err != nil { t.Fatal(err) } if _, ok := gotBody["HostConfig"]; !ok { t.Errorf("CreateContainer: wrong body. HostConfig was not serialized") } } func TestPassingNameOptToCreateContainerReturnsItInContainer(t *testing.T) { t.Parallel() jsonContainer := `{ "Id": "4fa6e0f0c6786287e131c3852c58a2e01cc697a68231826813597e4994f1d6e2", "Warnings": [] }` fakeRT := &FakeRoundTripper{message: jsonContainer, status: http.StatusOK} client := newTestClient(fakeRT) config := Config{AttachStdout: true, AttachStdin: true} opts := CreateContainerOptions{Name: "TestCreateContainer", Config: &config} container, err := client.CreateContainer(opts) if err != nil { t.Fatal(err) } if container.Name != "TestCreateContainer" { t.Errorf("Container name expected to be TestCreateContainer, was %s", container.Name) } } func TestPassingPlatformOpt(t *testing.T) { t.Parallel() fakeRT := &FakeRoundTripper{message: "{}", status: http.StatusOK} client := newTestClient(fakeRT) config := Config{} opts := CreateContainerOptions{Name: "TestCreateContainerWithPlatform", Platform: "darwin/arm64", Config: &config} _, err := client.CreateContainer(opts) if err != nil { t.Fatal(err) } req := fakeRT.requests[0] gotQs := req.URL.Query().Get("platform") if gotQs != "darwin/arm64" { t.Errorf("CreateContainer: missing expected platform query string (%v)", req.URL.RequestURI()) } } go-dockerclient-1.12.0/container_export.go000066400000000000000000000016521465717111200205670ustar00rootroot00000000000000package docker import ( "context" "fmt" "io" "net/http" "time" ) // ExportContainerOptions is the set of parameters to the ExportContainer // method. // // See https://goo.gl/yGJCIh for more details. type ExportContainerOptions struct { ID string OutputStream io.Writer InactivityTimeout time.Duration `qs:"-"` Context context.Context } // ExportContainer export the contents of container id as tar archive // and prints the exported contents to stdout. // // See https://goo.gl/yGJCIh for more details. func (c *Client) ExportContainer(opts ExportContainerOptions) error { if opts.ID == "" { return &NoSuchContainer{ID: opts.ID} } url := fmt.Sprintf("/containers/%s/export", opts.ID) return c.stream(http.MethodGet, url, streamOptions{ setRawTerminal: true, stdout: opts.OutputStream, inactivityTimeout: opts.InactivityTimeout, context: opts.Context, }) } go-dockerclient-1.12.0/container_export_test.go000066400000000000000000000020211465717111200216150ustar00rootroot00000000000000package docker import ( "bytes" "net/http" "testing" ) func TestExportContainer(t *testing.T) { t.Parallel() content := "exported container tar content" out := stdoutMock{bytes.NewBufferString(content)} client := newTestClient(&FakeRoundTripper{status: http.StatusOK}) opts := ExportContainerOptions{ID: "4fa6e0f0c678", OutputStream: out} err := client.ExportContainer(opts) if err != nil { t.Errorf("ExportContainer: caugh error %#v while exporting container, expected nil", err.Error()) } if out.String() != content { t.Errorf("ExportContainer: wrong stdout. Want %#v. Got %#v.", content, out.String()) } } func TestExportContainerNoId(t *testing.T) { t.Parallel() client := Client{} out := stdoutMock{bytes.NewBufferString("")} err := client.ExportContainer(ExportContainerOptions{OutputStream: out}) e, ok := err.(*NoSuchContainer) if !ok { t.Errorf("ExportContainer: wrong error. Want NoSuchContainer. Got %#v.", e) } if e.ID != "" { t.Errorf("ExportContainer: wrong ID. Want %q. Got %q", "", e.ID) } } go-dockerclient-1.12.0/container_inspect.go000066400000000000000000000032161465717111200207110ustar00rootroot00000000000000package docker import ( "context" "encoding/json" "errors" "net/http" ) // InspectContainer returns information about a container by its ID. // // Deprecated: Use InspectContainerWithOptions instead. func (c *Client) InspectContainer(id string) (*Container, error) { return c.InspectContainerWithOptions(InspectContainerOptions{ID: id}) } // InspectContainerWithContext returns information about a container by its ID. // The context object can be used to cancel the inspect request. // // Deprecated: Use InspectContainerWithOptions instead. func (c *Client) InspectContainerWithContext(id string, ctx context.Context) (*Container, error) { return c.InspectContainerWithOptions(InspectContainerOptions{ID: id, Context: ctx}) } // InspectContainerWithOptions returns information about a container by its ID. // // See https://goo.gl/FaI5JT for more details. func (c *Client) InspectContainerWithOptions(opts InspectContainerOptions) (*Container, error) { path := "/containers/" + opts.ID + "/json?" + queryString(opts) resp, err := c.do(http.MethodGet, path, doOptions{ context: opts.Context, }) if err != nil { var e *Error if errors.As(err, &e) && e.Status == http.StatusNotFound { return nil, &NoSuchContainer{ID: opts.ID} } return nil, err } defer resp.Body.Close() var container Container if err := json.NewDecoder(resp.Body).Decode(&container); err != nil { return nil, err } return &container, nil } // InspectContainerOptions specifies parameters for InspectContainerWithOptions. // // See https://goo.gl/FaI5JT for more details. type InspectContainerOptions struct { Context context.Context ID string `qs:"-"` Size bool } go-dockerclient-1.12.0/container_inspect_test.go000066400000000000000000000654271465717111200217640ustar00rootroot00000000000000package docker import ( "context" "encoding/json" "errors" "fmt" "net/http" "net/url" "reflect" "testing" "time" ) func TestInspectContainer(t *testing.T) { t.Parallel() jsonContainer := `{ "Id": "4fa6e0f0c6786287e131c3852c58a2e01cc697a68231826813597e4994f1d6e2", "AppArmorProfile": "Profile", "Created": "2013-05-07T14:51:42.087658+02:00", "Path": "date", "Args": [], "Config": { "Hostname": "4fa6e0f0c678", "User": "", "Memory": 17179869184, "MemorySwap": 34359738368, "AttachStdin": false, "AttachStdout": true, "AttachStderr": true, "PortSpecs": null, "Tty": false, "OpenStdin": false, "StdinOnce": false, "Env": null, "Cmd": [ "date" ], "Image": "base", "Volumes": {}, "VolumesFrom": "", "SecurityOpt": [ "label:user:USER" ], "Ulimits": [ { "Name": "nofile", "Soft": 1024, "Hard": 2048 } ], "Shell": [ "/bin/sh", "-c" ] }, "State": { "Running": false, "Pid": 0, "ExitCode": 0, "StartedAt": "2013-05-07T14:51:42.087658+02:00", "Ghost": false }, "Node": { "ID": "4I4E:QR4I:Z733:QEZK:5X44:Q4T7:W2DD:JRDY:KB2O:PODO:Z5SR:XRB6", "IP": "192.168.99.105", "Addra": "192.168.99.105:2376", "Name": "node-01", "Cpus": 4, "Memory": 1048436736, "Labels": { "executiondriver": "native-0.2", "kernelversion": "3.18.5-tinycore64", "operatingsystem": "Boot2Docker 1.5.0 (TCL 5.4); master : a66bce5 - Tue Feb 10 23:31:27 UTC 2015", "provider": "virtualbox", "storagedriver": "aufs" } }, "Image": "b750fe79269d2ec9a3c593ef05b4332b1d1a02a62b4accb2c21d589ff2f5f2dc", "NetworkSettings": { "IpAddress": "", "IpPrefixLen": 0, "Gateway": "", "Bridge": "", "PortMapping": null }, "SysInitPath": "/home/kitty/go/src/github.com/dotcloud/docker/bin/docker", "ResolvConfPath": "/etc/resolv.conf", "Volumes": {}, "HostConfig": { "Binds": null, "ContainerIDFile": "", "LxcConf": [], "Privileged": false, "PortBindings": { "80/tcp": [ { "HostIp": "0.0.0.0", "HostPort": "49153" } ] }, "Links": null, "PublishAllPorts": false, "CgroupParent": "/mesos", "Memory": 17179869184, "MemorySwap": 34359738368, "GroupAdd": ["fake", "12345"], "OomScoreAdj": 642 } }` var expected Container err := json.Unmarshal([]byte(jsonContainer), &expected) if err != nil { t.Fatal(err) } fakeRT := &FakeRoundTripper{message: jsonContainer, status: http.StatusOK} client := newTestClient(fakeRT) id := "4fa6e0f0c678" container, err := client.InspectContainer(id) if err != nil { t.Fatal(err) } if !reflect.DeepEqual(*container, expected) { t.Errorf("InspectContainer(%q): Expected %#v. Got %#v.", id, expected, container) } expectedURL, _ := url.Parse(client.getURL("/containers/4fa6e0f0c678/json")) if gotPath := fakeRT.requests[0].URL.Path; gotPath != expectedURL.Path { t.Errorf("InspectContainer(%q): Wrong path in request. Want %q. Got %q.", id, expectedURL.Path, gotPath) } } func TestInspectContainerWithContext(t *testing.T) { t.Parallel() jsonContainer := `{ "Id": "4fa6e0f0c6786287e131c3852c58a2e01cc697a68231826813597e4994f1d6e2", "AppArmorProfile": "Profile", "Created": "2013-05-07T14:51:42.087658+02:00", "Path": "date", "Args": [], "Config": { "Hostname": "4fa6e0f0c678", "User": "", "Memory": 17179869184, "MemorySwap": 34359738368, "AttachStdin": false, "AttachStdout": true, "AttachStderr": true, "PortSpecs": null, "Tty": false, "OpenStdin": false, "StdinOnce": false, "Env": null, "Cmd": [ "date" ], "Image": "base", "Volumes": {}, "VolumesFrom": "", "SecurityOpt": [ "label:user:USER" ], "Ulimits": [ { "Name": "nofile", "Soft": 1024, "Hard": 2048 } ] }, "State": { "Running": false, "Pid": 0, "ExitCode": 0, "StartedAt": "2013-05-07T14:51:42.087658+02:00", "Ghost": false }, "Node": { "ID": "4I4E:QR4I:Z733:QEZK:5X44:Q4T7:W2DD:JRDY:KB2O:PODO:Z5SR:XRB6", "IP": "192.168.99.105", "Addra": "192.168.99.105:2376", "Name": "node-01", "Cpus": 4, "Memory": 1048436736, "Labels": { "executiondriver": "native-0.2", "kernelversion": "3.18.5-tinycore64", "operatingsystem": "Boot2Docker 1.5.0 (TCL 5.4); master : a66bce5 - Tue Feb 10 23:31:27 UTC 2015", "provider": "virtualbox", "storagedriver": "aufs" } }, "Image": "b750fe79269d2ec9a3c593ef05b4332b1d1a02a62b4accb2c21d589ff2f5f2dc", "NetworkSettings": { "IpAddress": "", "IpPrefixLen": 0, "Gateway": "", "Bridge": "", "PortMapping": null }, "SysInitPath": "/home/kitty/go/src/github.com/dotcloud/docker/bin/docker", "ResolvConfPath": "/etc/resolv.conf", "Volumes": {}, "HostConfig": { "Binds": null, "BlkioDeviceReadIOps": [ { "Path": "/dev/sdb", "Rate": 100 } ], "BlkioDeviceWriteBps": [ { "Path": "/dev/sdb", "Rate": 5000 } ], "ContainerIDFile": "", "LxcConf": [], "Privileged": false, "PortBindings": { "80/tcp": [ { "HostIp": "0.0.0.0", "HostPort": "49153" } ] }, "Links": null, "PublishAllPorts": false, "CgroupParent": "/mesos", "Memory": 17179869184, "MemorySwap": 34359738368, "GroupAdd": ["fake", "12345"], "OomScoreAdj": 642 } }` var expected Container err := json.Unmarshal([]byte(jsonContainer), &expected) if err != nil { t.Fatal(err) } fakeRT := &FakeRoundTripper{message: jsonContainer, status: http.StatusOK} client := newTestClient(fakeRT) id := "4fa6e0f0c678" ctx, cancel := context.WithTimeout(context.TODO(), 1*time.Second) defer cancel() inspectError := make(chan error) // Invoke InspectContainer in a goroutine. The response is sent to the 'inspectError' // channel. go func() { container, err := client.InspectContainer(id) if err != nil { inspectError <- err return } if !reflect.DeepEqual(*container, expected) { inspectError <- fmt.Errorf("inspectContainer(%q): Expected %#v. Got %#v", id, expected, container) return } expectedURL, _ := url.Parse(client.getURL("/containers/4fa6e0f0c678/json")) if gotPath := fakeRT.requests[0].URL.Path; gotPath != expectedURL.Path { inspectError <- fmt.Errorf("inspectContainer(%q): Wrong path in request. Want %q. Got %q", id, expectedURL.Path, gotPath) return } // No errors to tbe reported. Send 'nil' inspectError <- nil }() // Wait for either the inspect response or for the context. select { case err := <-inspectError: if err != nil { t.Fatalf("Error inspecting container with context: %v", err) } case <-ctx.Done(): // Context was canceled unexpectedly. Report the same. t.Fatalf("Context canceled when waiting for inspect container response: %v", ctx.Err()) } } func TestInspectContainerWithOptions(t *testing.T) { t.Parallel() jsonContainer := `{ "Id": "4fa6e0f0c6786287e131c3852c58a2e01cc697a68231826813597e4994f1d6e2", "AppArmorProfile": "Profile", "Created": "2013-05-07T14:51:42.087658+02:00", "Path": "date", "Args": [], "Config": { "Hostname": "4fa6e0f0c678", "User": "", "Memory": 17179869184, "MemorySwap": 34359738368, "AttachStdin": false, "AttachStdout": true, "AttachStderr": true, "PortSpecs": null, "Tty": false, "OpenStdin": false, "StdinOnce": false, "Env": null, "Cmd": [ "date" ], "Image": "base", "Volumes": {}, "VolumesFrom": "", "SecurityOpt": [ "label:user:USER" ], "Ulimits": [ { "Name": "nofile", "Soft": 1024, "Hard": 2048 } ], "Shell": [ "/bin/sh", "-c" ] }, "State": { "Running": false, "Pid": 0, "ExitCode": 0, "StartedAt": "2013-05-07T14:51:42.087658+02:00", "Ghost": false }, "Node": { "ID": "4I4E:QR4I:Z733:QEZK:5X44:Q4T7:W2DD:JRDY:KB2O:PODO:Z5SR:XRB6", "IP": "192.168.99.105", "Addra": "192.168.99.105:2376", "Name": "node-01", "Cpus": 4, "Memory": 1048436736, "Labels": { "executiondriver": "native-0.2", "kernelversion": "3.18.5-tinycore64", "operatingsystem": "Boot2Docker 1.5.0 (TCL 5.4); master : a66bce5 - Tue Feb 10 23:31:27 UTC 2015", "provider": "virtualbox", "storagedriver": "aufs" } }, "Image": "b750fe79269d2ec9a3c593ef05b4332b1d1a02a62b4accb2c21d589ff2f5f2dc", "NetworkSettings": { "IpAddress": "", "IpPrefixLen": 0, "Gateway": "", "Bridge": "", "PortMapping": null }, "SysInitPath": "/home/kitty/go/src/github.com/dotcloud/docker/bin/docker", "ResolvConfPath": "/etc/resolv.conf", "Volumes": {}, "HostConfig": { "Binds": null, "ContainerIDFile": "", "LxcConf": [], "Privileged": false, "PortBindings": { "80/tcp": [ { "HostIp": "0.0.0.0", "HostPort": "49153" } ] }, "Links": null, "PublishAllPorts": false, "CgroupParent": "/mesos", "Memory": 17179869184, "MemorySwap": 34359738368, "GroupAdd": ["fake", "12345"], "OomScoreAdj": 642, "SizeRw": 3, "SizeRootFs": 5552693 } }` var expected Container err := json.Unmarshal([]byte(jsonContainer), &expected) if err != nil { t.Fatal(err) } fakeRT := &FakeRoundTripper{message: jsonContainer, status: http.StatusOK} client := newTestClient(fakeRT) const id = "4fa6e0f0c678" container, err := client.InspectContainerWithOptions(InspectContainerOptions{ ID: id, Size: true, }) if err != nil { t.Fatal(err) } if !reflect.DeepEqual(*container, expected) { t.Errorf("InspectContainer(%q): Expected %#v. Got %#v.", id, expected, container) } expectedURL, _ := url.Parse(client.getURL("/containers/4fa6e0f0c678/json?size=true")) if gotPath := fakeRT.requests[0].URL.Path; gotPath != expectedURL.Path { t.Errorf("InspectContainer(%q): Wrong path in request. Want %q. Got %q.", id, expectedURL.Path, gotPath) } } func TestInspectContainerNetwork(t *testing.T) { t.Parallel() jsonContainer := `{ "Id": "81e1bbe20b5508349e1c804eb08b7b6ca8366751dbea9f578b3ea0773fa66c1c", "Created": "2015-11-12T14:54:04.791485659Z", "Path": "consul-template", "Args": [ "-config=/tmp/haproxy.json", "-consul=192.168.99.120:8500" ], "State": { "Status": "running", "Running": true, "Paused": false, "Restarting": false, "OOMKilled": false, "Dead": false, "Pid": 3196, "ExitCode": 0, "Error": "", "StartedAt": "2015-11-12T14:54:05.026747471Z", "FinishedAt": "0001-01-01T00:00:00Z" }, "Image": "4921c5917fc117df3dec32f4c1976635dc6c56ccd3336fe1db3477f950e78bf7", "ResolvConfPath": "/mnt/sda1/var/lib/docker/containers/81e1bbe20b5508349e1c804eb08b7b6ca8366751dbea9f578b3ea0773fa66c1c/resolv.conf", "HostnamePath": "/mnt/sda1/var/lib/docker/containers/81e1bbe20b5508349e1c804eb08b7b6ca8366751dbea9f578b3ea0773fa66c1c/hostname", "HostsPath": "/mnt/sda1/var/lib/docker/containers/81e1bbe20b5508349e1c804eb08b7b6ca8366751dbea9f578b3ea0773fa66c1c/hosts", "LogPath": "/mnt/sda1/var/lib/docker/containers/81e1bbe20b5508349e1c804eb08b7b6ca8366751dbea9f578b3ea0773fa66c1c/81e1bbe20b5508349e1c804eb08b7b6ca8366751dbea9f578b3ea0773fa66c1c-json.log", "Node": { "ID": "AUIB:LFOT:3LSF:SCFS:OYDQ:NLXD:JZNE:4INI:3DRC:ZFBB:GWCY:DWJK", "IP": "192.168.99.121", "Addr": "192.168.99.121:2376", "Name": "swl-demo1", "Cpus": 1, "Memory": 2099945472, "Labels": { "executiondriver": "native-0.2", "kernelversion": "4.1.12-boot2docker", "operatingsystem": "Boot2Docker 1.9.0 (TCL 6.4); master : 16e4a2a - Tue Nov 3 19:49:22 UTC 2015", "provider": "virtualbox", "storagedriver": "aufs" } }, "Name": "/docker-proxy.swl-demo1", "RestartCount": 0, "Driver": "aufs", "ExecDriver": "native-0.2", "MountLabel": "", "ProcessLabel": "", "AppArmorProfile": "", "ExecIDs": null, "HostConfig": { "Binds": null, "ContainerIDFile": "", "LxcConf": [], "Memory": 0, "MemoryReservation": 0, "MemorySwap": 0, "KernelMemory": 0, "CpuShares": 0, "CpuPeriod": 0, "CpusetCpus": "", "CpusetMems": "", "CpuQuota": 0, "BlkioWeight": 0, "OomKillDisable": false, "MemorySwappiness": -1, "Privileged": false, "PortBindings": { "443/tcp": [ { "HostIp": "", "HostPort": "443" } ] }, "Links": null, "PublishAllPorts": false, "Dns": null, "DnsOptions": null, "DnsSearch": null, "ExtraHosts": null, "VolumesFrom": null, "Devices": [], "NetworkMode": "swl-net", "IpcMode": "", "PidMode": "", "UTSMode": "", "CapAdd": null, "CapDrop": null, "GroupAdd": null, "RestartPolicy": { "Name": "no", "MaximumRetryCount": 0 }, "SecurityOpt": null, "ReadonlyRootfs": false, "Ulimits": null, "LogConfig": { "Type": "json-file", "Config": {} }, "CgroupParent": "", "ConsoleSize": [ 0, 0 ], "VolumeDriver": "" }, "GraphDriver": { "Name": "aufs", "Data": null }, "Mounts": [], "Config": { "Hostname": "81e1bbe20b55", "Domainname": "", "User": "", "AttachStdin": false, "AttachStdout": false, "AttachStderr": false, "ExposedPorts": { "443/tcp": {} }, "Tty": false, "OpenStdin": false, "StdinOnce": false, "Env": [ "DOMAIN=local.auto", "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", "CONSUL_TEMPLATE_VERSION=0.11.1" ], "Cmd": [ "-consul=192.168.99.120:8500" ], "Image": "docker-proxy:latest", "Volumes": null, "WorkingDir": "", "Entrypoint": [ "consul-template", "-config=/tmp/haproxy.json" ], "OnBuild": null, "Labels": {}, "StopSignal": "SIGTERM" }, "NetworkSettings": { "Bridge": "", "SandboxID": "c6b903dc5c1a96113a22dbc44709e30194079bd2d262eea1eb4f38d85821f6e1", "HairpinMode": false, "LinkLocalIPv6Address": "", "LinkLocalIPv6PrefixLen": 0, "Ports": { "443/tcp": [ { "HostIp": "192.168.99.121", "HostPort": "443" } ] }, "SandboxKey": "/var/run/docker/netns/c6b903dc5c1a", "SecondaryIPAddresses": null, "SecondaryIPv6Addresses": null, "EndpointID": "", "Gateway": "", "GlobalIPv6Address": "", "GlobalIPv6PrefixLen": 0, "IPAddress": "", "IPPrefixLen": 0, "IPv6Gateway": "", "MacAddress": "", "Networks": { "swl-net": { "Aliases": [ "testalias", "81e1bbe20b55" ], "NetworkID": "7ea29fc1412292a2d7bba362f9253545fecdfa8ce9a6e37dd10ba8bee7129812", "EndpointID": "683e3092275782a53c3b0968cc7e3a10f23264022ded9cb20490902f96fc5981", "Gateway": "", "IPAddress": "10.0.0.3", "IPPrefixLen": 24, "IPv6Gateway": "", "GlobalIPv6Address": "", "GlobalIPv6PrefixLen": 0, "MacAddress": "02:42:0a:00:00:03" } } } }` fakeRT := &FakeRoundTripper{message: jsonContainer, status: http.StatusOK} client := newTestClient(fakeRT) id := "81e1bbe20b55" expIP := "10.0.0.3" expNetworkID := "7ea29fc1412292a2d7bba362f9253545fecdfa8ce9a6e37dd10ba8bee7129812" expectedAliases := []string{"testalias", "81e1bbe20b55"} container, err := client.InspectContainer(id) if err != nil { t.Fatal(err) } s := reflect.Indirect(reflect.ValueOf(container.NetworkSettings)) networks := s.FieldByName("Networks") if networks.IsValid() { var ip string for _, net := range networks.MapKeys() { if net.Interface().(string) == container.HostConfig.NetworkMode { ip = networks.MapIndex(net).FieldByName("IPAddress").Interface().(string) t.Logf("%s %v", net, ip) } } if ip != expIP { t.Errorf("InspectContainerNetworks(%q): Expected %#v. Got %#v.", id, expIP, ip) } var networkID string for _, net := range networks.MapKeys() { if net.Interface().(string) == container.HostConfig.NetworkMode { networkID = networks.MapIndex(net).FieldByName("NetworkID").Interface().(string) t.Logf("%s %v", net, networkID) } } var aliases []string for _, net := range networks.MapKeys() { if net.Interface().(string) == container.HostConfig.NetworkMode { aliases = networks.MapIndex(net).FieldByName("Aliases").Interface().([]string) } } if !reflect.DeepEqual(aliases, expectedAliases) { t.Errorf("InspectContainerNetworks(%q): Expected Aliases %#v. Got %#v.", id, expectedAliases, aliases) } if networkID != expNetworkID { t.Errorf("InspectContainerNetworks(%q): Expected %#v. Got %#v.", id, expNetworkID, networkID) } } else { t.Errorf("InspectContainerNetworks(%q): No method Networks for NetworkSettings", id) } } func TestInspectContainerNegativeSwap(t *testing.T) { t.Parallel() jsonContainer := `{ "Id": "4fa6e0f0c6786287e131c3852c58a2e01cc697a68231826813597e4994f1d6e2", "Created": "2013-05-07T14:51:42.087658+02:00", "Path": "date", "Args": [], "Config": { "Hostname": "4fa6e0f0c678", "User": "", "Memory": 17179869184, "MemorySwap": -1, "AttachStdin": false, "AttachStdout": true, "AttachStderr": true, "PortSpecs": null, "Tty": false, "OpenStdin": false, "StdinOnce": false, "Env": null, "Cmd": [ "date" ], "Image": "base", "Volumes": {}, "VolumesFrom": "" }, "State": { "Running": false, "Pid": 0, "ExitCode": 0, "StartedAt": "2013-05-07T14:51:42.087658+02:00", "Ghost": false }, "Image": "b750fe79269d2ec9a3c593ef05b4332b1d1a02a62b4accb2c21d589ff2f5f2dc", "NetworkSettings": { "IpAddress": "", "IpPrefixLen": 0, "Gateway": "", "Bridge": "", "PortMapping": null }, "SysInitPath": "/home/kitty/go/src/github.com/dotcloud/docker/bin/docker", "ResolvConfPath": "/etc/resolv.conf", "Volumes": {}, "HostConfig": { "Binds": null, "ContainerIDFile": "", "LxcConf": [], "Privileged": false, "PortBindings": { "80/tcp": [ { "HostIp": "0.0.0.0", "HostPort": "49153" } ] }, "Links": null, "PublishAllPorts": false } }` var expected Container err := json.Unmarshal([]byte(jsonContainer), &expected) if err != nil { t.Fatal(err) } fakeRT := &FakeRoundTripper{message: jsonContainer, status: http.StatusOK} client := newTestClient(fakeRT) id := "4fa6e0f0c678" container, err := client.InspectContainer(id) if err != nil { t.Fatal(err) } if !reflect.DeepEqual(*container, expected) { t.Errorf("InspectContainer(%q): Expected %#v. Got %#v.", id, expected, container) } expectedURL, _ := url.Parse(client.getURL("/containers/4fa6e0f0c678/json")) if gotPath := fakeRT.requests[0].URL.Path; gotPath != expectedURL.Path { t.Errorf("InspectContainer(%q): Wrong path in request. Want %q. Got %q.", id, expectedURL.Path, gotPath) } } func TestInspectContainerFailure(t *testing.T) { t.Parallel() client := newTestClient(&FakeRoundTripper{message: "server error", status: 500}) expected := Error{Status: 500, Message: "server error"} container, err := client.InspectContainer("abe033") if container != nil { t.Errorf("InspectContainer: Expected container, got %#v", container) } if !reflect.DeepEqual(expected, *err.(*Error)) { t.Errorf("InspectContainer: Wrong error information. Want %#v. Got %#v.", expected, err) } } func TestInspectContainerNotFound(t *testing.T) { t.Parallel() const containerID = "abe033" client := newTestClient(&FakeRoundTripper{message: "no such container", status: 404}) container, err := client.InspectContainer(containerID) if container != nil { t.Errorf("InspectContainer: Expected container, got %#v", container) } expectNoSuchContainer(t, containerID, err) } func TestInspectContainerWhenContextTimesOut(t *testing.T) { t.Parallel() rt := sleepyRoudTripper{sleepDuration: 200 * time.Millisecond} client := newTestClient(&rt) ctx, cancel := context.WithTimeout(context.TODO(), 100*time.Millisecond) defer cancel() _, err := client.InspectContainerWithContext("id", ctx) if !errors.Is(err, context.DeadlineExceeded) { t.Errorf("Expected 'DeadlineExceededError', got: %#v", err) } } go-dockerclient-1.12.0/container_kill.go000066400000000000000000000021021465717111200201700ustar00rootroot00000000000000package docker import ( "context" "errors" "net/http" ) // KillContainerOptions represents the set of options that can be used in a // call to KillContainer. // // See https://goo.gl/JnTxXZ for more details. type KillContainerOptions struct { // The ID of the container. ID string `qs:"-"` // The signal to send to the container. When omitted, Docker server // will assume SIGKILL. Signal Signal Context context.Context } // KillContainer sends a signal to a container, returning an error in case of // failure. // // See https://goo.gl/JnTxXZ for more details. func (c *Client) KillContainer(opts KillContainerOptions) error { path := "/containers/" + opts.ID + "/kill" + "?" + queryString(opts) resp, err := c.do(http.MethodPost, path, doOptions{context: opts.Context}) if err != nil { var e *Error if !errors.As(err, &e) { return err } switch e.Status { case http.StatusNotFound: return &NoSuchContainer{ID: opts.ID} case http.StatusConflict: return &ContainerNotRunning{ID: opts.ID} default: return err } } resp.Body.Close() return nil } go-dockerclient-1.12.0/container_kill_test.go000066400000000000000000000043321465717111200212360ustar00rootroot00000000000000package docker import ( "fmt" "net/http" "net/url" "reflect" "testing" ) func TestKillContainer(t *testing.T) { t.Parallel() fakeRT := &FakeRoundTripper{message: "", status: http.StatusNoContent} client := newTestClient(fakeRT) id := "4fa6e0f0c6786287e131c3852c58a2e01cc697a68231826813597e4994f1d6e2" err := client.KillContainer(KillContainerOptions{ID: id}) if err != nil { t.Fatal(err) } req := fakeRT.requests[0] if req.Method != http.MethodPost { t.Errorf("KillContainer(%q): wrong HTTP method. Want %q. Got %q.", id, http.MethodPost, req.Method) } expectedURL, _ := url.Parse(client.getURL("/containers/" + id + "/kill")) if gotPath := req.URL.Path; gotPath != expectedURL.Path { t.Errorf("KillContainer(%q): Wrong path in request. Want %q. Got %q.", id, expectedURL.Path, gotPath) } } func TestKillContainerSignal(t *testing.T) { t.Parallel() fakeRT := &FakeRoundTripper{message: "", status: http.StatusNoContent} client := newTestClient(fakeRT) id := "4fa6e0f0c6786287e131c3852c58a2e01cc697a68231826813597e4994f1d6e2" err := client.KillContainer(KillContainerOptions{ID: id, Signal: SIGTERM}) if err != nil { t.Fatal(err) } req := fakeRT.requests[0] if req.Method != http.MethodPost { t.Errorf("KillContainer(%q): wrong HTTP method. Want %q. Got %q.", id, http.MethodPost, req.Method) } if signal := req.URL.Query().Get("signal"); signal != "15" { t.Errorf("KillContainer(%q): Wrong query string in request. Want %q. Got %q.", id, "15", signal) } } func TestKillContainerNotFound(t *testing.T) { t.Parallel() client := newTestClient(&FakeRoundTripper{message: "no such container", status: http.StatusNotFound}) err := client.KillContainer(KillContainerOptions{ID: "a2334"}) expectNoSuchContainer(t, "a2334", err) } func TestKillContainerNotRunning(t *testing.T) { t.Parallel() id := "abcd1234567890" msg := fmt.Sprintf("Cannot kill container: %[1]s: Container %[1]s is not running", id) client := newTestClient(&FakeRoundTripper{message: msg, status: http.StatusConflict}) err := client.KillContainer(KillContainerOptions{ID: id}) expected := &ContainerNotRunning{ID: id} if !reflect.DeepEqual(err, expected) { t.Errorf("KillContainer: Wrong error returned. Want %#v. Got %#v.", expected, err) } } go-dockerclient-1.12.0/container_list.go000066400000000000000000000016201465717111200202140ustar00rootroot00000000000000package docker import ( "context" "encoding/json" "net/http" ) // ListContainersOptions specify parameters to the ListContainers function. // // See https://goo.gl/kaOHGw for more details. type ListContainersOptions struct { All bool Size bool Limit int Since string Before string Filters map[string][]string Context context.Context } // ListContainers returns a slice of containers matching the given criteria. // // See https://goo.gl/kaOHGw for more details. func (c *Client) ListContainers(opts ListContainersOptions) ([]APIContainers, error) { path := "/containers/json?" + queryString(opts) resp, err := c.do(http.MethodGet, path, doOptions{context: opts.Context}) if err != nil { return nil, err } defer resp.Body.Close() var containers []APIContainers if err := json.NewDecoder(resp.Body).Decode(&containers); err != nil { return nil, err } return containers, nil } go-dockerclient-1.12.0/container_list_test.go000066400000000000000000000102431465717111200212540ustar00rootroot00000000000000package docker import ( "encoding/json" "net/http" "reflect" "strconv" "testing" ) func TestListContainers(t *testing.T) { t.Parallel() jsonContainers := `[ { "Id": "8dfafdbc3a40", "Image": "base:latest", "Command": "echo 1", "Created": 1367854155, "Ports":[{"PrivatePort": 2222, "PublicPort": 3333, "Type": "tcp"}], "Status": "Exit 0" }, { "Id": "9cd87474be90", "Image": "base:latest", "Command": "echo 222222", "Created": 1367854155, "Ports":[{"PrivatePort": 2222, "PublicPort": 3333, "Type": "tcp"}], "Status": "Exit 0" }, { "Id": "3176a2479c92", "Image": "base:latest", "Command": "echo 3333333333333333", "Created": 1367854154, "Ports":[{"PrivatePort": 2221, "PublicPort": 3331, "Type": "tcp"}], "Status": "Exit 0" }, { "Id": "4cb07b47f9fb", "Image": "base:latest", "Command": "echo 444444444444444444444444444444444", "Ports":[{"PrivatePort": 2223, "PublicPort": 3332, "Type": "tcp"}], "Created": 1367854152, "Status": "Exit 0" } ]` var expected []APIContainers err := json.Unmarshal([]byte(jsonContainers), &expected) if err != nil { t.Fatal(err) } client := newTestClient(&FakeRoundTripper{message: jsonContainers, status: http.StatusOK}) containers, err := client.ListContainers(ListContainersOptions{}) if err != nil { t.Fatal(err) } if !reflect.DeepEqual(containers, expected) { t.Errorf("ListContainers: Expected %#v. Got %#v.", expected, containers) } } func TestListContainersParams(t *testing.T) { t.Parallel() tests := []struct { input ListContainersOptions params map[string][]string }{ {ListContainersOptions{}, map[string][]string{}}, {ListContainersOptions{All: true}, map[string][]string{"all": {"1"}}}, {ListContainersOptions{All: true, Limit: 10}, map[string][]string{"all": {"1"}, "limit": {"10"}}}, { ListContainersOptions{All: true, Limit: 10, Since: "adf9983", Before: "abdeef"}, map[string][]string{"all": {"1"}, "limit": {"10"}, "since": {"adf9983"}, "before": {"abdeef"}}, }, { ListContainersOptions{Filters: map[string][]string{"status": {"paused", "running"}}}, map[string][]string{"filters": {"{\"status\":[\"paused\",\"running\"]}"}}, }, { ListContainersOptions{All: true, Filters: map[string][]string{"exited": {"0"}, "status": {"exited"}}}, map[string][]string{"all": {"1"}, "filters": {"{\"exited\":[\"0\"],\"status\":[\"exited\"]}"}}, }, } const expectedPath = "/containers/json" for _, tt := range tests { test := tt t.Run("", func(t *testing.T) { t.Parallel() fakeRT := &FakeRoundTripper{message: "[]", status: http.StatusOK} client := newTestClient(fakeRT) if _, err := client.ListContainers(test.input); err != nil { t.Error(err) } got := map[string][]string(fakeRT.requests[0].URL.Query()) if !reflect.DeepEqual(got, test.params) { t.Errorf("Expected %#v, got %#v.", test.params, got) } if path := fakeRT.requests[0].URL.Path; path != expectedPath { t.Errorf("Wrong path on request. Want %q. Got %q.", expectedPath, path) } if meth := fakeRT.requests[0].Method; meth != http.MethodGet { t.Errorf("Wrong HTTP method. Want GET. Got %s.", meth) } }) } } func TestListContainersFailure(t *testing.T) { t.Parallel() tests := []struct { status int message string }{ {400, "bad parameter"}, {500, "internal server error"}, } for _, tt := range tests { test := tt t.Run(strconv.Itoa(test.status), func(t *testing.T) { t.Parallel() client := newTestClient(&FakeRoundTripper{message: test.message, status: test.status}) expected := Error{Status: test.status, Message: test.message} containers, err := client.ListContainers(ListContainersOptions{}) if !reflect.DeepEqual(expected, *err.(*Error)) { t.Errorf("Wrong error in ListContainers. Want %#v. Got %#v.", expected, err) } if len(containers) > 0 { t.Errorf("ListContainers failure. Expected empty list. Got %#v.", containers) } }) } } go-dockerclient-1.12.0/container_logs.go000066400000000000000000000031611465717111200202070ustar00rootroot00000000000000package docker import ( "context" "io" "net/http" "time" ) // LogsOptions represents the set of options used when getting logs from a // container. // // See https://goo.gl/krK0ZH for more details. type LogsOptions struct { Context context.Context Container string `qs:"-"` OutputStream io.Writer `qs:"-"` ErrorStream io.Writer `qs:"-"` InactivityTimeout time.Duration `qs:"-"` Tail string Since int64 Follow bool Stdout bool Stderr bool Timestamps bool // Use raw terminal? Usually true when the container contains a TTY. RawTerminal bool `qs:"-"` } // Logs gets stdout and stderr logs from the specified container. // // When LogsOptions.RawTerminal is set to false, go-dockerclient will multiplex // the streams and send the containers stdout to LogsOptions.OutputStream, and // stderr to LogsOptions.ErrorStream. // // When LogsOptions.RawTerminal is true, callers will get the raw stream on // LogsOptions.OutputStream. The caller can use libraries such as dlog // (github.com/ahmetalpbalkan/dlog). // // See https://goo.gl/krK0ZH for more details. func (c *Client) Logs(opts LogsOptions) error { if opts.Container == "" { return &NoSuchContainer{ID: opts.Container} } if opts.Tail == "" { opts.Tail = "all" } path := "/containers/" + opts.Container + "/logs?" + queryString(opts) return c.stream(http.MethodGet, path, streamOptions{ setRawTerminal: opts.RawTerminal, stdout: opts.OutputStream, stderr: opts.ErrorStream, inactivityTimeout: opts.InactivityTimeout, context: opts.Context, }) } go-dockerclient-1.12.0/container_logs_test.go000066400000000000000000000114441465717111200212510ustar00rootroot00000000000000package docker import ( "bytes" "net/http" "net/http/httptest" "net/url" "reflect" "testing" ) func TestLogs(t *testing.T) { t.Parallel() var req http.Request server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { prefix := []byte{1, 0, 0, 0, 0, 0, 0, 19} w.Write(prefix) w.Write([]byte("something happened!")) req = *r })) defer server.Close() client, _ := NewClient(server.URL) client.SkipServerVersionCheck = true var buf bytes.Buffer opts := LogsOptions{ Container: "a123456", OutputStream: &buf, Follow: true, Stdout: true, Stderr: true, Timestamps: true, } err := client.Logs(opts) if err != nil { t.Fatal(err) } expected := "something happened!" if buf.String() != expected { t.Errorf("Logs: wrong output. Want %q. Got %q.", expected, buf.String()) } if req.Method != http.MethodGet { t.Errorf("Logs: wrong HTTP method. Want GET. Got %s.", req.Method) } u, _ := url.Parse(client.getURL("/containers/a123456/logs")) if req.URL.Path != u.Path { t.Errorf("AttachToContainer for logs: wrong HTTP path. Want %q. Got %q.", u.Path, req.URL.Path) } expectedQs := map[string][]string{ "follow": {"1"}, "stdout": {"1"}, "stderr": {"1"}, "timestamps": {"1"}, "tail": {"all"}, } got := map[string][]string(req.URL.Query()) if !reflect.DeepEqual(got, expectedQs) { t.Errorf("Logs: wrong query string. Want %#v. Got %#v.", expectedQs, got) } } func TestLogsNilStdoutDoesntFail(t *testing.T) { t.Parallel() server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { prefix := []byte{1, 0, 0, 0, 0, 0, 0, 19} w.Write(prefix) w.Write([]byte("something happened!")) })) defer server.Close() client, _ := NewClient(server.URL) client.SkipServerVersionCheck = true opts := LogsOptions{ Container: "a123456", Follow: true, Stdout: true, Stderr: true, Timestamps: true, } err := client.Logs(opts) if err != nil { t.Fatal(err) } } func TestLogsNilStderrDoesntFail(t *testing.T) { t.Parallel() server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { prefix := []byte{2, 0, 0, 0, 0, 0, 0, 19} w.Write(prefix) w.Write([]byte("something happened!")) })) defer server.Close() client, _ := NewClient(server.URL) client.SkipServerVersionCheck = true opts := LogsOptions{ Container: "a123456", Follow: true, Stdout: true, Stderr: true, Timestamps: true, } err := client.Logs(opts) if err != nil { t.Fatal(err) } } func TestLogsSpecifyingTail(t *testing.T) { t.Parallel() var req http.Request server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { prefix := []byte{1, 0, 0, 0, 0, 0, 0, 19} w.Write(prefix) w.Write([]byte("something happened!")) req = *r })) defer server.Close() client, _ := NewClient(server.URL) client.SkipServerVersionCheck = true var buf bytes.Buffer opts := LogsOptions{ Container: "a123456", OutputStream: &buf, Follow: true, Stdout: true, Stderr: true, Timestamps: true, Tail: "100", } err := client.Logs(opts) if err != nil { t.Fatal(err) } expected := "something happened!" if buf.String() != expected { t.Errorf("Logs: wrong output. Want %q. Got %q.", expected, buf.String()) } if req.Method != http.MethodGet { t.Errorf("Logs: wrong HTTP method. Want GET. Got %s.", req.Method) } u, _ := url.Parse(client.getURL("/containers/a123456/logs")) if req.URL.Path != u.Path { t.Errorf("AttachToContainer for logs: wrong HTTP path. Want %q. Got %q.", u.Path, req.URL.Path) } expectedQs := map[string][]string{ "follow": {"1"}, "stdout": {"1"}, "stderr": {"1"}, "timestamps": {"1"}, "tail": {"100"}, } got := map[string][]string(req.URL.Query()) if !reflect.DeepEqual(got, expectedQs) { t.Errorf("Logs: wrong query string. Want %#v. Got %#v.", expectedQs, got) } } func TestLogsRawTerminal(t *testing.T) { t.Parallel() server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { w.Write([]byte("something happened!")) })) defer server.Close() client, _ := NewClient(server.URL) client.SkipServerVersionCheck = true var buf bytes.Buffer opts := LogsOptions{ Container: "a123456", OutputStream: &buf, Follow: true, RawTerminal: true, Stdout: true, Stderr: true, Timestamps: true, Tail: "100", } err := client.Logs(opts) if err != nil { t.Fatal(err) } expected := "something happened!" if buf.String() != expected { t.Errorf("Logs: wrong output. Want %q. Got %q.", expected, buf.String()) } } func TestLogsNoContainer(t *testing.T) { t.Parallel() var client Client err := client.Logs(LogsOptions{}) expectNoSuchContainer(t, "", err) } go-dockerclient-1.12.0/container_pause.go000066400000000000000000000007511465717111200203620ustar00rootroot00000000000000package docker import ( "errors" "fmt" "net/http" ) // PauseContainer pauses the given container. // // See https://goo.gl/D1Yaii for more details. func (c *Client) PauseContainer(id string) error { path := fmt.Sprintf("/containers/%s/pause", id) resp, err := c.do(http.MethodPost, path, doOptions{}) if err != nil { var e *Error if errors.As(err, &e) && e.Status == http.StatusNotFound { return &NoSuchContainer{ID: id} } return err } resp.Body.Close() return nil } go-dockerclient-1.12.0/container_pause_test.go000066400000000000000000000020011465717111200214070ustar00rootroot00000000000000package docker import ( "net/http" "net/url" "testing" ) func TestPauseContainer(t *testing.T) { t.Parallel() fakeRT := &FakeRoundTripper{message: "", status: http.StatusNoContent} client := newTestClient(fakeRT) id := "4fa6e0f0c6786287e131c3852c58a2e01cc697a68231826813597e4994f1d6e2" err := client.PauseContainer(id) if err != nil { t.Fatal(err) } req := fakeRT.requests[0] if req.Method != http.MethodPost { t.Errorf("PauseContainer(%q): wrong HTTP method. Want %q. Got %q.", id, http.MethodPost, req.Method) } expectedURL, _ := url.Parse(client.getURL("/containers/" + id + "/pause")) if gotPath := req.URL.Path; gotPath != expectedURL.Path { t.Errorf("PauseContainer(%q): Wrong path in request. Want %q. Got %q.", id, expectedURL.Path, gotPath) } } func TestPauseContainerNotFound(t *testing.T) { t.Parallel() client := newTestClient(&FakeRoundTripper{message: "no such container", status: http.StatusNotFound}) err := client.PauseContainer("a2334") expectNoSuchContainer(t, "a2334", err) } go-dockerclient-1.12.0/container_prune.go000066400000000000000000000020351465717111200203730ustar00rootroot00000000000000package docker import ( "context" "encoding/json" "net/http" ) // PruneContainersOptions specify parameters to the PruneContainers function. // // See https://goo.gl/wnkgDT for more details. type PruneContainersOptions struct { Filters map[string][]string Context context.Context } // PruneContainersResults specify results from the PruneContainers function. // // See https://goo.gl/wnkgDT for more details. type PruneContainersResults struct { ContainersDeleted []string SpaceReclaimed int64 } // PruneContainers deletes containers which are stopped. // // See https://goo.gl/wnkgDT for more details. func (c *Client) PruneContainers(opts PruneContainersOptions) (*PruneContainersResults, error) { path := "/containers/prune?" + queryString(opts) resp, err := c.do(http.MethodPost, path, doOptions{context: opts.Context}) if err != nil { return nil, err } defer resp.Body.Close() var results PruneContainersResults if err := json.NewDecoder(resp.Body).Decode(&results); err != nil { return nil, err } return &results, nil } go-dockerclient-1.12.0/container_prune_test.go000066400000000000000000000012051465717111200214300ustar00rootroot00000000000000package docker import ( "encoding/json" "net/http" "reflect" "testing" ) func TestPruneContainers(t *testing.T) { t.Parallel() results := `{ "ContainersDeleted": [ "a", "b", "c" ], "SpaceReclaimed": 123 }` expected := &PruneContainersResults{} err := json.Unmarshal([]byte(results), expected) if err != nil { t.Fatal(err) } client := newTestClient(&FakeRoundTripper{message: results, status: http.StatusOK}) got, err := client.PruneContainers(PruneContainersOptions{}) if err != nil { t.Fatal(err) } if !reflect.DeepEqual(got, expected) { t.Errorf("PruneContainers: Expected %#v. Got %#v.", expected, got) } } go-dockerclient-1.12.0/container_remove.go000066400000000000000000000020551465717111200205410ustar00rootroot00000000000000package docker import ( "context" "errors" "net/http" ) // RemoveContainerOptions encapsulates options to remove a container. // // See https://goo.gl/hL5IPC for more details. type RemoveContainerOptions struct { // The ID of the container. ID string `qs:"-"` // A flag that indicates whether Docker should remove the volumes // associated to the container. RemoveVolumes bool `qs:"v"` // A flag that indicates whether Docker should remove the container // even if it is currently running. Force bool Context context.Context } // RemoveContainer removes a container, returning an error in case of failure. // // See https://goo.gl/hL5IPC for more details. func (c *Client) RemoveContainer(opts RemoveContainerOptions) error { path := "/containers/" + opts.ID + "?" + queryString(opts) resp, err := c.do(http.MethodDelete, path, doOptions{context: opts.Context}) if err != nil { var e *Error if errors.As(err, &e) && e.Status == http.StatusNotFound { return &NoSuchContainer{ID: opts.ID} } return err } resp.Body.Close() return nil } go-dockerclient-1.12.0/container_remove_test.go000066400000000000000000000033061465717111200216000ustar00rootroot00000000000000package docker import ( "net/http" "net/url" "reflect" "testing" ) func TestRemoveContainer(t *testing.T) { t.Parallel() fakeRT := &FakeRoundTripper{message: "", status: http.StatusOK} client := newTestClient(fakeRT) id := "4fa6e0f0c6786287e131c3852c58a2e01cc697a68231826813597e4994f1d6e2" opts := RemoveContainerOptions{ID: id} err := client.RemoveContainer(opts) if err != nil { t.Fatal(err) } req := fakeRT.requests[0] if req.Method != http.MethodDelete { t.Errorf("RemoveContainer(%q): wrong HTTP method. Want %q. Got %q.", id, http.MethodDelete, req.Method) } expectedURL, _ := url.Parse(client.getURL("/containers/" + id)) if gotPath := req.URL.Path; gotPath != expectedURL.Path { t.Errorf("RemoveContainer(%q): Wrong path in request. Want %q. Got %q.", id, expectedURL.Path, gotPath) } } func TestRemoveContainerRemoveVolumes(t *testing.T) { t.Parallel() fakeRT := &FakeRoundTripper{message: "", status: http.StatusOK} client := newTestClient(fakeRT) id := "4fa6e0f0c6786287e131c3852c58a2e01cc697a68231826813597e4994f1d6e2" opts := RemoveContainerOptions{ID: id, RemoveVolumes: true} err := client.RemoveContainer(opts) if err != nil { t.Fatal(err) } req := fakeRT.requests[0] params := map[string][]string(req.URL.Query()) expected := map[string][]string{"v": {"1"}} if !reflect.DeepEqual(params, expected) { t.Errorf("RemoveContainer(%q): wrong parameters. Want %#v. Got %#v.", id, expected, params) } } func TestRemoveContainerNotFound(t *testing.T) { t.Parallel() client := newTestClient(&FakeRoundTripper{message: "no such container", status: http.StatusNotFound}) err := client.RemoveContainer(RemoveContainerOptions{ID: "a2334"}) expectNoSuchContainer(t, "a2334", err) } go-dockerclient-1.12.0/container_rename.go000066400000000000000000000013741465717111200205160ustar00rootroot00000000000000package docker import ( "context" "fmt" "net/http" ) // RenameContainerOptions specify parameters to the RenameContainer function. // // See https://goo.gl/46inai for more details. type RenameContainerOptions struct { // ID of container to rename ID string `qs:"-"` // New name Name string `json:"name,omitempty" yaml:"name,omitempty"` Context context.Context } // RenameContainer updates and existing containers name // // See https://goo.gl/46inai for more details. func (c *Client) RenameContainer(opts RenameContainerOptions) error { resp, err := c.do(http.MethodPost, fmt.Sprintf("/containers/"+opts.ID+"/rename?%s", queryString(opts)), doOptions{ context: opts.Context, }) if err != nil { return err } resp.Body.Close() return nil } go-dockerclient-1.12.0/container_rename_test.go000066400000000000000000000020311465717111200215440ustar00rootroot00000000000000package docker import ( "net/http" "net/url" "testing" ) func TestRenameContainer(t *testing.T) { t.Parallel() fakeRT := &FakeRoundTripper{message: "", status: http.StatusOK} client := newTestClient(fakeRT) opts := RenameContainerOptions{ID: "something_old", Name: "something_new"} err := client.RenameContainer(opts) if err != nil { t.Fatal(err) } req := fakeRT.requests[0] if req.Method != http.MethodPost { t.Errorf("RenameContainer: wrong HTTP method. Want %q. Got %q.", http.MethodPost, req.Method) } expectedURL, _ := url.Parse(client.getURL("/containers/something_old/rename?name=something_new")) if gotPath := req.URL.Path; gotPath != expectedURL.Path { t.Errorf("RenameContainer: Wrong path in request. Want %q. Got %q.", expectedURL.Path, gotPath) } expectedValues := expectedURL.Query()["name"] actualValues := req.URL.Query()["name"] if len(actualValues) != 1 || expectedValues[0] != actualValues[0] { t.Errorf("RenameContainer: Wrong params in request. Want %q. Got %q.", expectedValues, actualValues) } } go-dockerclient-1.12.0/container_resize.go000066400000000000000000000010161465717111200205410ustar00rootroot00000000000000package docker import ( "net/http" "net/url" "strconv" ) // ResizeContainerTTY resizes the terminal to the given height and width. // // See https://goo.gl/FImjeq for more details. func (c *Client) ResizeContainerTTY(id string, height, width int) error { params := make(url.Values) params.Set("h", strconv.Itoa(height)) params.Set("w", strconv.Itoa(width)) resp, err := c.do(http.MethodPost, "/containers/"+id+"/resize?"+params.Encode(), doOptions{}) if err != nil { return err } resp.Body.Close() return nil } go-dockerclient-1.12.0/container_resize_test.go000066400000000000000000000020101465717111200215730ustar00rootroot00000000000000package docker import ( "net/http" "net/url" "reflect" "testing" ) func TestResizeContainerTTY(t *testing.T) { t.Parallel() fakeRT := &FakeRoundTripper{message: "", status: http.StatusOK} client := newTestClient(fakeRT) id := "4fa6e0f0c6786287e131c3852c58a2e01cc697a68231826813597e4994f1d6e2" err := client.ResizeContainerTTY(id, 40, 80) if err != nil { t.Fatal(err) } req := fakeRT.requests[0] if req.Method != http.MethodPost { t.Errorf("ResizeContainerTTY(%q): wrong HTTP method. Want %q. Got %q.", id, http.MethodPost, req.Method) } expectedURL, _ := url.Parse(client.getURL("/containers/" + id + "/resize")) if gotPath := req.URL.Path; gotPath != expectedURL.Path { t.Errorf("ResizeContainerTTY(%q): Wrong path in request. Want %q. Got %q.", id, expectedURL.Path, gotPath) } got := map[string][]string(req.URL.Query()) expectedParams := map[string][]string{ "w": {"80"}, "h": {"40"}, } if !reflect.DeepEqual(got, expectedParams) { t.Errorf("Expected %#v, got %#v.", expectedParams, got) } } go-dockerclient-1.12.0/container_restart.go000066400000000000000000000043211465717111200207260ustar00rootroot00000000000000package docker import ( "errors" "fmt" "net/http" ) // RestartPolicy represents the policy for automatically restarting a container. // // Possible values are: // // - always: the docker daemon will always restart the container // - on-failure: the docker daemon will restart the container on failures, at // most MaximumRetryCount times // - unless-stopped: the docker daemon will always restart the container except // when user has manually stopped the container // - no: the docker daemon will not restart the container automatically type RestartPolicy struct { Name string `json:"Name,omitempty" yaml:"Name,omitempty" toml:"Name,omitempty"` MaximumRetryCount int `json:"MaximumRetryCount,omitempty" yaml:"MaximumRetryCount,omitempty" toml:"MaximumRetryCount,omitempty"` } // AlwaysRestart returns a restart policy that tells the Docker daemon to // always restart the container. func AlwaysRestart() RestartPolicy { return RestartPolicy{Name: "always"} } // RestartOnFailure returns a restart policy that tells the Docker daemon to // restart the container on failures, trying at most maxRetry times. func RestartOnFailure(maxRetry int) RestartPolicy { return RestartPolicy{Name: "on-failure", MaximumRetryCount: maxRetry} } // RestartUnlessStopped returns a restart policy that tells the Docker daemon to // always restart the container except when user has manually stopped the container. func RestartUnlessStopped() RestartPolicy { return RestartPolicy{Name: "unless-stopped"} } // NeverRestart returns a restart policy that tells the Docker daemon to never // restart the container on failures. func NeverRestart() RestartPolicy { return RestartPolicy{Name: "no"} } // RestartContainer stops a container, killing it after the given timeout (in // seconds), during the stop process. // // See https://goo.gl/MrAKQ5 for more details. func (c *Client) RestartContainer(id string, timeout uint) error { path := fmt.Sprintf("/containers/%s/restart?t=%d", id, timeout) resp, err := c.do(http.MethodPost, path, doOptions{}) if err != nil { var e *Error if errors.As(err, &e) && e.Status == http.StatusNotFound { return &NoSuchContainer{ID: id} } return err } resp.Body.Close() return nil } go-dockerclient-1.12.0/container_restart_test.go000066400000000000000000000047371465717111200220000ustar00rootroot00000000000000package docker import ( "net/http" "net/url" "testing" ) func TestRestartContainer(t *testing.T) { t.Parallel() fakeRT := &FakeRoundTripper{message: "", status: http.StatusNoContent} client := newTestClient(fakeRT) id := "4fa6e0f0c6786287e131c3852c58a2e01cc697a68231826813597e4994f1d6e2" err := client.RestartContainer(id, 10) if err != nil { t.Fatal(err) } req := fakeRT.requests[0] if req.Method != http.MethodPost { t.Errorf("RestartContainer(%q, 10): wrong HTTP method. Want %q. Got %q.", id, http.MethodPost, req.Method) } expectedURL, _ := url.Parse(client.getURL("/containers/" + id + "/restart")) if gotPath := req.URL.Path; gotPath != expectedURL.Path { t.Errorf("RestartContainer(%q, 10): Wrong path in request. Want %q. Got %q.", id, expectedURL.Path, gotPath) } } func TestRestartContainerNotFound(t *testing.T) { t.Parallel() client := newTestClient(&FakeRoundTripper{message: "no such container", status: http.StatusNotFound}) err := client.RestartContainer("a2334", 10) expectNoSuchContainer(t, "a2334", err) } func TestAlwaysRestart(t *testing.T) { t.Parallel() policy := AlwaysRestart() if policy.Name != "always" { t.Errorf("AlwaysRestart(): wrong policy name. Want %q. Got %q", "always", policy.Name) } if policy.MaximumRetryCount != 0 { t.Errorf("AlwaysRestart(): wrong MaximumRetryCount. Want 0. Got %d", policy.MaximumRetryCount) } } func TestRestartOnFailure(t *testing.T) { t.Parallel() const retry = 5 policy := RestartOnFailure(retry) if policy.Name != "on-failure" { t.Errorf("RestartOnFailure(%d): wrong policy name. Want %q. Got %q", retry, "on-failure", policy.Name) } if policy.MaximumRetryCount != retry { t.Errorf("RestartOnFailure(%d): wrong MaximumRetryCount. Want %d. Got %d", retry, retry, policy.MaximumRetryCount) } } func TestRestartUnlessStopped(t *testing.T) { t.Parallel() policy := RestartUnlessStopped() if policy.Name != "unless-stopped" { t.Errorf("RestartUnlessStopped(): wrong policy name. Want %q. Got %q", "unless-stopped", policy.Name) } if policy.MaximumRetryCount != 0 { t.Errorf("RestartUnlessStopped(): wrong MaximumRetryCount. Want 0. Got %d", policy.MaximumRetryCount) } } func TestNeverRestart(t *testing.T) { t.Parallel() policy := NeverRestart() if policy.Name != "no" { t.Errorf("NeverRestart(): wrong policy name. Want %q. Got %q", "always", policy.Name) } if policy.MaximumRetryCount != 0 { t.Errorf("NeverRestart(): wrong MaximumRetryCount. Want 0. Got %d", policy.MaximumRetryCount) } } go-dockerclient-1.12.0/container_start.go000066400000000000000000000035611465717111200204040ustar00rootroot00000000000000package docker import ( "context" "errors" "net/http" ) // StartContainer starts a container, returning an error in case of failure. // // Passing the HostConfig to this method has been deprecated in Docker API 1.22 // (Docker Engine 1.10.x) and totally removed in Docker API 1.24 (Docker Engine // 1.12.x). The client will ignore the parameter when communicating with Docker // API 1.24 or greater. // // See https://goo.gl/fbOSZy for more details. func (c *Client) StartContainer(id string, hostConfig *HostConfig) error { return c.startContainer(id, hostConfig, doOptions{}) } // StartContainerWithContext starts a container, returning an error in case of // failure. The context can be used to cancel the outstanding start container // request. // // Passing the HostConfig to this method has been deprecated in Docker API 1.22 // (Docker Engine 1.10.x) and totally removed in Docker API 1.24 (Docker Engine // 1.12.x). The client will ignore the parameter when communicating with Docker // API 1.24 or greater. // // See https://goo.gl/fbOSZy for more details. func (c *Client) StartContainerWithContext(id string, hostConfig *HostConfig, ctx context.Context) error { return c.startContainer(id, hostConfig, doOptions{context: ctx}) } func (c *Client) startContainer(id string, hostConfig *HostConfig, opts doOptions) error { path := "/containers/" + id + "/start" if c.serverAPIVersion == nil { c.checkAPIVersion() } if c.serverAPIVersion != nil && c.serverAPIVersion.LessThan(apiVersion124) { opts.data = hostConfig opts.forceJSON = true } resp, err := c.do(http.MethodPost, path, opts) if err != nil { var e *Error if errors.As(err, &e) && e.Status == http.StatusNotFound { return &NoSuchContainer{ID: id, Err: err} } return err } defer resp.Body.Close() if resp.StatusCode == http.StatusNotModified { return &ContainerAlreadyRunning{ID: id} } return nil } go-dockerclient-1.12.0/container_start_test.go000066400000000000000000000133171465717111200214430ustar00rootroot00000000000000package docker import ( "context" "errors" "io" "net/http" "net/url" "reflect" "testing" "time" ) func TestStartContainer(t *testing.T) { t.Parallel() fakeRT := &FakeRoundTripper{message: "", status: http.StatusOK} client := newTestClient(fakeRT) id := "4fa6e0f0c6786287e131c3852c58a2e01cc697a68231826813597e4994f1d6e2" err := client.StartContainer(id, &HostConfig{}) if err != nil { t.Fatal(err) } req := fakeRT.requests[0] if req.Method != http.MethodPost { t.Errorf("StartContainer(%q): wrong HTTP method. Want %q. Got %q.", id, http.MethodPost, req.Method) } expectedURL, _ := url.Parse(client.getURL("/containers/" + id + "/start")) if gotPath := req.URL.Path; gotPath != expectedURL.Path { t.Errorf("StartContainer(%q): Wrong path in request. Want %q. Got %q.", id, expectedURL.Path, gotPath) } expectedContentType := "application/json" if contentType := req.Header.Get("Content-Type"); contentType != expectedContentType { t.Errorf("StartContainer(%q): Wrong content-type in request. Want %q. Got %q.", id, expectedContentType, contentType) } } func TestStartContainerHostConfigAPI124(t *testing.T) { t.Parallel() fakeRT := &FakeRoundTripper{message: "", status: http.StatusOK} client := newTestClient(fakeRT) client.serverAPIVersion = apiVersion124 id := "4fa6e0f0c6786287e131c3852c58a2e01cc697a68231826813597e4994f1d6e2" err := client.StartContainer(id, &HostConfig{}) if err != nil { t.Fatal(err) } req := fakeRT.requests[0] if req.Method != http.MethodPost { t.Errorf("StartContainer(%q): wrong HTTP method. Want %q. Got %q.", id, http.MethodPost, req.Method) } expectedURL, _ := url.Parse(client.getURL("/containers/" + id + "/start")) if gotPath := req.URL.Path; gotPath != expectedURL.Path { t.Errorf("StartContainer(%q): Wrong path in request. Want %q. Got %q.", id, expectedURL.Path, gotPath) } notAcceptedContentType := "application/json" if contentType := req.Header.Get("Content-Type"); contentType == notAcceptedContentType { t.Errorf("StartContainer(%q): Unepected %q Content-Type in request.", id, contentType) } if req.Body != nil { data, _ := io.ReadAll(req.Body) t.Errorf("StartContainer(%q): Unexpected data sent: %s", id, data) } } func TestStartContainerNilHostConfig(t *testing.T) { t.Parallel() fakeRT := &FakeRoundTripper{message: "", status: http.StatusOK} client := newTestClient(fakeRT) id := "4fa6e0f0c6786287e131c3852c58a2e01cc697a68231826813597e4994f1d6e2" err := client.StartContainer(id, nil) if err != nil { t.Fatal(err) } req := fakeRT.requests[0] if req.Method != http.MethodPost { t.Errorf("StartContainer(%q): wrong HTTP method. Want %q. Got %q.", id, http.MethodPost, req.Method) } expectedURL, _ := url.Parse(client.getURL("/containers/" + id + "/start")) if gotPath := req.URL.Path; gotPath != expectedURL.Path { t.Errorf("StartContainer(%q): Wrong path in request. Want %q. Got %q.", id, expectedURL.Path, gotPath) } expectedContentType := "application/json" if contentType := req.Header.Get("Content-Type"); contentType != expectedContentType { t.Errorf("StartContainer(%q): Wrong content-type in request. Want %q. Got %q.", id, expectedContentType, contentType) } var buf [4]byte req.Body.Read(buf[:]) if string(buf[:]) != "null" { t.Errorf("Startcontainer(%q): Wrong body. Want null. Got %s", id, buf[:]) } } func TestStartContainerWithContext(t *testing.T) { t.Parallel() fakeRT := &FakeRoundTripper{message: "", status: http.StatusOK} client := newTestClient(fakeRT) id := "4fa6e0f0c6786287e131c3852c58a2e01cc697a68231826813597e4994f1d6e2" ctx, cancel := context.WithTimeout(context.TODO(), 1*time.Second) defer cancel() startError := make(chan error) go func() { startError <- client.StartContainerWithContext(id, &HostConfig{}, ctx) }() select { case err := <-startError: if err != nil { t.Fatal(err) } req := fakeRT.requests[0] if req.Method != http.MethodPost { t.Errorf("StartContainer(%q): wrong HTTP method. Want %q. Got %q.", id, http.MethodPost, req.Method) } expectedURL, _ := url.Parse(client.getURL("/containers/" + id + "/start")) if gotPath := req.URL.Path; gotPath != expectedURL.Path { t.Errorf("StartContainer(%q): Wrong path in request. Want %q. Got %q.", id, expectedURL.Path, gotPath) } expectedContentType := "application/json" if contentType := req.Header.Get("Content-Type"); contentType != expectedContentType { t.Errorf("StartContainer(%q): Wrong content-type in request. Want %q. Got %q.", id, expectedContentType, contentType) } case <-ctx.Done(): // Context was canceled unexpectedly. Report the same. t.Fatalf("Context canceled when waiting for start container response: %v", ctx.Err()) } } func TestStartContainerNotFound(t *testing.T) { t.Parallel() client := newTestClient(&FakeRoundTripper{message: "no such container", status: http.StatusNotFound}) err := client.StartContainer("a2344", &HostConfig{}) expectNoSuchContainer(t, "a2344", err) } func TestStartContainerAlreadyRunning(t *testing.T) { t.Parallel() client := newTestClient(&FakeRoundTripper{message: "container already running", status: http.StatusNotModified}) err := client.StartContainer("a2334", &HostConfig{}) expected := &ContainerAlreadyRunning{ID: "a2334"} if !reflect.DeepEqual(err, expected) { t.Errorf("StartContainer: Wrong error returned. Want %#v. Got %#v.", expected, err) } } func TestStartContainerWhenContextTimesOut(t *testing.T) { t.Parallel() rt := sleepyRoudTripper{sleepDuration: 200 * time.Millisecond} client := newTestClient(&rt) ctx, cancel := context.WithTimeout(context.TODO(), 100*time.Millisecond) defer cancel() err := client.StartContainerWithContext("id", nil, ctx) if !errors.Is(err, context.DeadlineExceeded) { t.Errorf("Expected 'DeadlineExceededError', got: %v", err) } } go-dockerclient-1.12.0/container_stats.go000066400000000000000000000373601465717111200204110ustar00rootroot00000000000000package docker import ( "context" "encoding/json" "errors" "fmt" "io" "net/http" "time" ) // Stats represents container statistics, returned by /containers//stats. // // See https://goo.gl/Dk3Xio for more details. type Stats struct { Read time.Time `json:"read,omitempty" yaml:"read,omitempty" toml:"read,omitempty"` PreRead time.Time `json:"preread,omitempty" yaml:"preread,omitempty" toml:"preread,omitempty"` NumProcs uint32 `json:"num_procs" yaml:"num_procs" toml:"num_procs"` PidsStats struct { Current uint64 `json:"current,omitempty" yaml:"current,omitempty"` } `json:"pids_stats,omitempty" yaml:"pids_stats,omitempty" toml:"pids_stats,omitempty"` Network NetworkStats `json:"network,omitempty" yaml:"network,omitempty" toml:"network,omitempty"` Networks map[string]NetworkStats `json:"networks,omitempty" yaml:"networks,omitempty" toml:"networks,omitempty"` MemoryStats struct { Stats struct { TotalPgmafault uint64 `json:"total_pgmafault,omitempty" yaml:"total_pgmafault,omitempty" toml:"total_pgmafault,omitempty"` Cache uint64 `json:"cache,omitempty" yaml:"cache,omitempty" toml:"cache,omitempty"` MappedFile uint64 `json:"mapped_file,omitempty" yaml:"mapped_file,omitempty" toml:"mapped_file,omitempty"` TotalInactiveFile uint64 `json:"total_inactive_file,omitempty" yaml:"total_inactive_file,omitempty" toml:"total_inactive_file,omitempty"` Pgpgout uint64 `json:"pgpgout,omitempty" yaml:"pgpgout,omitempty" toml:"pgpgout,omitempty"` Rss uint64 `json:"rss,omitempty" yaml:"rss,omitempty" toml:"rss,omitempty"` TotalMappedFile uint64 `json:"total_mapped_file,omitempty" yaml:"total_mapped_file,omitempty" toml:"total_mapped_file,omitempty"` Writeback uint64 `json:"writeback,omitempty" yaml:"writeback,omitempty" toml:"writeback,omitempty"` Unevictable uint64 `json:"unevictable,omitempty" yaml:"unevictable,omitempty" toml:"unevictable,omitempty"` Pgpgin uint64 `json:"pgpgin,omitempty" yaml:"pgpgin,omitempty" toml:"pgpgin,omitempty"` TotalUnevictable uint64 `json:"total_unevictable,omitempty" yaml:"total_unevictable,omitempty" toml:"total_unevictable,omitempty"` Pgmajfault uint64 `json:"pgmajfault,omitempty" yaml:"pgmajfault,omitempty" toml:"pgmajfault,omitempty"` TotalRss uint64 `json:"total_rss,omitempty" yaml:"total_rss,omitempty" toml:"total_rss,omitempty"` TotalRssHuge uint64 `json:"total_rss_huge,omitempty" yaml:"total_rss_huge,omitempty" toml:"total_rss_huge,omitempty"` TotalWriteback uint64 `json:"total_writeback,omitempty" yaml:"total_writeback,omitempty" toml:"total_writeback,omitempty"` TotalInactiveAnon uint64 `json:"total_inactive_anon,omitempty" yaml:"total_inactive_anon,omitempty" toml:"total_inactive_anon,omitempty"` RssHuge uint64 `json:"rss_huge,omitempty" yaml:"rss_huge,omitempty" toml:"rss_huge,omitempty"` HierarchicalMemoryLimit uint64 `json:"hierarchical_memory_limit,omitempty" yaml:"hierarchical_memory_limit,omitempty" toml:"hierarchical_memory_limit,omitempty"` TotalPgfault uint64 `json:"total_pgfault,omitempty" yaml:"total_pgfault,omitempty" toml:"total_pgfault,omitempty"` TotalActiveFile uint64 `json:"total_active_file,omitempty" yaml:"total_active_file,omitempty" toml:"total_active_file,omitempty"` ActiveAnon uint64 `json:"active_anon,omitempty" yaml:"active_anon,omitempty" toml:"active_anon,omitempty"` TotalActiveAnon uint64 `json:"total_active_anon,omitempty" yaml:"total_active_anon,omitempty" toml:"total_active_anon,omitempty"` TotalPgpgout uint64 `json:"total_pgpgout,omitempty" yaml:"total_pgpgout,omitempty" toml:"total_pgpgout,omitempty"` TotalCache uint64 `json:"total_cache,omitempty" yaml:"total_cache,omitempty" toml:"total_cache,omitempty"` InactiveAnon uint64 `json:"inactive_anon,omitempty" yaml:"inactive_anon,omitempty" toml:"inactive_anon,omitempty"` ActiveFile uint64 `json:"active_file,omitempty" yaml:"active_file,omitempty" toml:"active_file,omitempty"` Pgfault uint64 `json:"pgfault,omitempty" yaml:"pgfault,omitempty" toml:"pgfault,omitempty"` InactiveFile uint64 `json:"inactive_file,omitempty" yaml:"inactive_file,omitempty" toml:"inactive_file,omitempty"` TotalPgpgin uint64 `json:"total_pgpgin,omitempty" yaml:"total_pgpgin,omitempty" toml:"total_pgpgin,omitempty"` HierarchicalMemswLimit uint64 `json:"hierarchical_memsw_limit,omitempty" yaml:"hierarchical_memsw_limit,omitempty" toml:"hierarchical_memsw_limit,omitempty"` Swap uint64 `json:"swap,omitempty" yaml:"swap,omitempty" toml:"swap,omitempty"` Anon uint64 `json:"anon,omitempty" yaml:"anon,omitempty" toml:"anon,omitempty"` AnonThp uint64 `json:"anon_thp,omitempty" yaml:"anon_thp,omitempty" toml:"anon_thp,omitempty"` File uint64 `json:"file,omitempty" yaml:"file,omitempty" toml:"file,omitempty"` FileDirty uint64 `json:"file_dirty,omitempty" yaml:"file_dirty,omitempty" toml:"file_dirty,omitempty"` FileMapped uint64 `json:"file_mapped,omitempty" yaml:"file_mapped,omitempty" toml:"file_mapped,omitempty"` FileWriteback uint64 `json:"file_writeback,omitempty" yaml:"file_writeback,omitempty" toml:"file_writeback,omitempty"` KernelStack uint64 `json:"kernel_stack,omitempty" yaml:"kernel_stack,omitempty" toml:"kernel_stack,omitempty"` Pgactivate uint64 `json:"pgactivate,omitempty" yaml:"pgactivate,omitempty" toml:"pgactivate,omitempty"` Pgdeactivate uint64 `json:"pgdeactivate,omitempty" yaml:"pgdeactivate,omitempty" toml:"pgdeactivate,omitempty"` Pglazyfree uint64 `json:"pglazyfree,omitempty" yaml:"pglazyfree,omitempty" toml:"pglazyfree,omitempty"` Pglazyfreed uint64 `json:"pglazyfreed,omitempty" yaml:"pglazyfreed,omitempty" toml:"pglazyfreed,omitempty"` Pgrefill uint64 `json:"pgrefill,omitempty" yaml:"pgrefill,omitempty" toml:"pgrefill,omitempty"` Pgscan uint64 `json:"pgscan,omitempty" yaml:"pgscan,omitempty" toml:"pgscan,omitempty"` Pgsteal uint64 `json:"pgsteal,omitempty" yaml:"pgsteal,omitempty" toml:"pgsteal,omitempty"` Shmem uint64 `json:"shmem,omitempty" yaml:"shmem,omitempty" toml:"shmem,omitempty"` Slab uint64 `json:"slab,omitempty" yaml:"slab,omitempty" toml:"slab,omitempty"` SlabReclaimable uint64 `json:"slab_reclaimable,omitempty" yaml:"slab_reclaimable,omitempty" toml:"slab_reclaimable,omitempty"` SlabUnreclaimable uint64 `json:"slab_unreclaimable,omitempty" yaml:"slab_unreclaimable,omitempty" toml:"slab_unreclaimable,omitempty"` Sock uint64 `json:"sock,omitempty" yaml:"sock,omitempty" toml:"sock,omitempty"` ThpCollapseAlloc uint64 `json:"thp_collapse_alloc,omitempty" yaml:"thp_collapse_alloc,omitempty" toml:"thp_collapse_alloc,omitempty"` ThpFaultAlloc uint64 `json:"thp_fault_alloc,omitempty" yaml:"thp_fault_alloc,omitempty" toml:"thp_fault_alloc,omitempty"` WorkingsetActivate uint64 `json:"workingset_activate,omitempty" yaml:"workingset_activate,omitempty" toml:"workingset_activate,omitempty"` WorkingsetNodereclaim uint64 `json:"workingset_nodereclaim,omitempty" yaml:"workingset_nodereclaim,omitempty" toml:"workingset_nodereclaim,omitempty"` WorkingsetRefault uint64 `json:"workingset_refault,omitempty" yaml:"workingset_refault,omitempty" toml:"workingset_refault,omitempty"` } `json:"stats,omitempty" yaml:"stats,omitempty" toml:"stats,omitempty"` MaxUsage uint64 `json:"max_usage,omitempty" yaml:"max_usage,omitempty" toml:"max_usage,omitempty"` Usage uint64 `json:"usage,omitempty" yaml:"usage,omitempty" toml:"usage,omitempty"` Failcnt uint64 `json:"failcnt,omitempty" yaml:"failcnt,omitempty" toml:"failcnt,omitempty"` Limit uint64 `json:"limit,omitempty" yaml:"limit,omitempty" toml:"limit,omitempty"` Commit uint64 `json:"commitbytes,omitempty" yaml:"commitbytes,omitempty" toml:"privateworkingset,omitempty"` CommitPeak uint64 `json:"commitpeakbytes,omitempty" yaml:"commitpeakbytes,omitempty" toml:"commitpeakbytes,omitempty"` PrivateWorkingSet uint64 `json:"privateworkingset,omitempty" yaml:"privateworkingset,omitempty" toml:"privateworkingset,omitempty"` } `json:"memory_stats,omitempty" yaml:"memory_stats,omitempty" toml:"memory_stats,omitempty"` BlkioStats struct { IOServiceBytesRecursive []BlkioStatsEntry `json:"io_service_bytes_recursive,omitempty" yaml:"io_service_bytes_recursive,omitempty" toml:"io_service_bytes_recursive,omitempty"` IOServicedRecursive []BlkioStatsEntry `json:"io_serviced_recursive,omitempty" yaml:"io_serviced_recursive,omitempty" toml:"io_serviced_recursive,omitempty"` IOQueueRecursive []BlkioStatsEntry `json:"io_queue_recursive,omitempty" yaml:"io_queue_recursive,omitempty" toml:"io_queue_recursive,omitempty"` IOServiceTimeRecursive []BlkioStatsEntry `json:"io_service_time_recursive,omitempty" yaml:"io_service_time_recursive,omitempty" toml:"io_service_time_recursive,omitempty"` IOWaitTimeRecursive []BlkioStatsEntry `json:"io_wait_time_recursive,omitempty" yaml:"io_wait_time_recursive,omitempty" toml:"io_wait_time_recursive,omitempty"` IOMergedRecursive []BlkioStatsEntry `json:"io_merged_recursive,omitempty" yaml:"io_merged_recursive,omitempty" toml:"io_merged_recursive,omitempty"` IOTimeRecursive []BlkioStatsEntry `json:"io_time_recursive,omitempty" yaml:"io_time_recursive,omitempty" toml:"io_time_recursive,omitempty"` SectorsRecursive []BlkioStatsEntry `json:"sectors_recursive,omitempty" yaml:"sectors_recursive,omitempty" toml:"sectors_recursive,omitempty"` } `json:"blkio_stats,omitempty" yaml:"blkio_stats,omitempty" toml:"blkio_stats,omitempty"` CPUStats CPUStats `json:"cpu_stats,omitempty" yaml:"cpu_stats,omitempty" toml:"cpu_stats,omitempty"` PreCPUStats CPUStats `json:"precpu_stats,omitempty"` StorageStats struct { ReadCountNormalized uint64 `json:"read_count_normalized,omitempty" yaml:"read_count_normalized,omitempty" toml:"read_count_normalized,omitempty"` ReadSizeBytes uint64 `json:"read_size_bytes,omitempty" yaml:"read_size_bytes,omitempty" toml:"read_size_bytes,omitempty"` WriteCountNormalized uint64 `json:"write_count_normalized,omitempty" yaml:"write_count_normalized,omitempty" toml:"write_count_normalized,omitempty"` WriteSizeBytes uint64 `json:"write_size_bytes,omitempty" yaml:"write_size_bytes,omitempty" toml:"write_size_bytes,omitempty"` } `json:"storage_stats,omitempty" yaml:"storage_stats,omitempty" toml:"storage_stats,omitempty"` } // NetworkStats is a stats entry for network stats type NetworkStats struct { RxDropped uint64 `json:"rx_dropped,omitempty" yaml:"rx_dropped,omitempty" toml:"rx_dropped,omitempty"` RxBytes uint64 `json:"rx_bytes,omitempty" yaml:"rx_bytes,omitempty" toml:"rx_bytes,omitempty"` RxErrors uint64 `json:"rx_errors,omitempty" yaml:"rx_errors,omitempty" toml:"rx_errors,omitempty"` TxPackets uint64 `json:"tx_packets,omitempty" yaml:"tx_packets,omitempty" toml:"tx_packets,omitempty"` TxDropped uint64 `json:"tx_dropped,omitempty" yaml:"tx_dropped,omitempty" toml:"tx_dropped,omitempty"` RxPackets uint64 `json:"rx_packets,omitempty" yaml:"rx_packets,omitempty" toml:"rx_packets,omitempty"` TxErrors uint64 `json:"tx_errors,omitempty" yaml:"tx_errors,omitempty" toml:"tx_errors,omitempty"` TxBytes uint64 `json:"tx_bytes,omitempty" yaml:"tx_bytes,omitempty" toml:"tx_bytes,omitempty"` } // CPUStats is a stats entry for cpu stats type CPUStats struct { CPUUsage struct { PercpuUsage []uint64 `json:"percpu_usage,omitempty" yaml:"percpu_usage,omitempty" toml:"percpu_usage,omitempty"` UsageInUsermode uint64 `json:"usage_in_usermode,omitempty" yaml:"usage_in_usermode,omitempty" toml:"usage_in_usermode,omitempty"` TotalUsage uint64 `json:"total_usage,omitempty" yaml:"total_usage,omitempty" toml:"total_usage,omitempty"` UsageInKernelmode uint64 `json:"usage_in_kernelmode,omitempty" yaml:"usage_in_kernelmode,omitempty" toml:"usage_in_kernelmode,omitempty"` } `json:"cpu_usage,omitempty" yaml:"cpu_usage,omitempty" toml:"cpu_usage,omitempty"` SystemCPUUsage uint64 `json:"system_cpu_usage,omitempty" yaml:"system_cpu_usage,omitempty" toml:"system_cpu_usage,omitempty"` OnlineCPUs uint64 `json:"online_cpus,omitempty" yaml:"online_cpus,omitempty" toml:"online_cpus,omitempty"` ThrottlingData struct { Periods uint64 `json:"periods,omitempty"` ThrottledPeriods uint64 `json:"throttled_periods,omitempty"` ThrottledTime uint64 `json:"throttled_time,omitempty"` } `json:"throttling_data,omitempty" yaml:"throttling_data,omitempty" toml:"throttling_data,omitempty"` } // BlkioStatsEntry is a stats entry for blkio_stats type BlkioStatsEntry struct { Major uint64 `json:"major,omitempty" yaml:"major,omitempty" toml:"major,omitempty"` Minor uint64 `json:"minor,omitempty" yaml:"minor,omitempty" toml:"minor,omitempty"` Op string `json:"op,omitempty" yaml:"op,omitempty" toml:"op,omitempty"` Value uint64 `json:"value,omitempty" yaml:"value,omitempty" toml:"value,omitempty"` } // StatsOptions specify parameters to the Stats function. // // See https://goo.gl/Dk3Xio for more details. type StatsOptions struct { ID string Stats chan<- *Stats Stream bool // A flag that enables stopping the stats operation Done <-chan bool // Initial connection timeout Timeout time.Duration // Timeout with no data is received, it's reset every time new data // arrives InactivityTimeout time.Duration `qs:"-"` Context context.Context } // Stats sends container statistics for the given container to the given channel. // // This function is blocking, similar to a streaming call for logs, and should be run // on a separate goroutine from the caller. Note that this function will block until // the given container is removed, not just exited. When finished, this function // will close the given channel. Alternatively, function can be stopped by // signaling on the Done channel. // // See https://goo.gl/Dk3Xio for more details. func (c *Client) Stats(opts StatsOptions) (retErr error) { errC := make(chan error, 1) readCloser, writeCloser := io.Pipe() defer func() { close(opts.Stats) if err := <-errC; err != nil && retErr == nil { retErr = err } if err := readCloser.Close(); err != nil && retErr == nil { retErr = err } }() reqSent := make(chan struct{}) go func() { defer close(errC) err := c.stream(http.MethodGet, fmt.Sprintf("/containers/%s/stats?stream=%v", opts.ID, opts.Stream), streamOptions{ rawJSONStream: true, useJSONDecoder: true, stdout: writeCloser, timeout: opts.Timeout, inactivityTimeout: opts.InactivityTimeout, context: opts.Context, reqSent: reqSent, }) if err != nil { var dockerError *Error if errors.As(err, &dockerError) { if dockerError.Status == http.StatusNotFound { err = &NoSuchContainer{ID: opts.ID} } } } if closeErr := writeCloser.Close(); closeErr != nil && err == nil { err = closeErr } errC <- err }() quit := make(chan struct{}) defer close(quit) go func() { // block here waiting for the signal to stop function select { case <-opts.Done: readCloser.Close() case <-quit: return } }() decoder := json.NewDecoder(readCloser) stats := new(Stats) <-reqSent for err := decoder.Decode(stats); !errors.Is(err, io.EOF); err = decoder.Decode(stats) { if err != nil { return err } opts.Stats <- stats stats = new(Stats) } return nil } go-dockerclient-1.12.0/container_stats_test.go000066400000000000000000000412161465717111200214430ustar00rootroot00000000000000package docker import ( "encoding/json" "net/http" "net/http/httptest" "net/url" "reflect" "testing" ) // The test for Docker Stat API of the host uses cgroup func TestStatsV1(t *testing.T) { t.Parallel() jsonStats1 := `{ "read" : "2015-01-08T22:57:31.547920715Z", "network" : { "rx_dropped" : 0, "rx_bytes" : 648, "rx_errors" : 0, "tx_packets" : 8, "tx_dropped" : 0, "rx_packets" : 8, "tx_errors" : 0, "tx_bytes" : 648 }, "networks" : { "eth0":{ "rx_dropped" : 0, "rx_bytes" : 648, "rx_errors" : 0, "tx_packets" : 8, "tx_dropped" : 0, "rx_packets" : 8, "tx_errors" : 0, "tx_bytes" : 648 } }, "memory_stats" : { "stats" : { "total_pgmajfault" : 0, "cache" : 0, "mapped_file" : 0, "total_inactive_file" : 0, "pgpgout" : 414, "rss" : 6537216, "total_mapped_file" : 0, "writeback" : 0, "unevictable" : 0, "pgpgin" : 477, "total_unevictable" : 0, "pgmajfault" : 0, "total_rss" : 6537216, "total_rss_huge" : 6291456, "total_writeback" : 0, "total_inactive_anon" : 0, "rss_huge" : 6291456, "hierarchical_memory_limit": 189204833, "total_pgfault" : 964, "total_active_file" : 0, "active_anon" : 6537216, "total_active_anon" : 6537216, "total_pgpgout" : 414, "total_cache" : 0, "inactive_anon" : 0, "active_file" : 0, "pgfault" : 964, "inactive_file" : 0, "total_pgpgin" : 477, "swap" : 47312896, "hierarchical_memsw_limit" : 1610612736 }, "max_usage" : 6651904, "usage" : 6537216, "failcnt" : 0, "limit" : 67108864 }, "blkio_stats": { "io_service_bytes_recursive": [ { "major": 8, "minor": 0, "op": "Read", "value": 428795731968 }, { "major": 8, "minor": 0, "op": "Write", "value": 388177920 } ], "io_serviced_recursive": [ { "major": 8, "minor": 0, "op": "Read", "value": 25994442 }, { "major": 8, "minor": 0, "op": "Write", "value": 1734 } ], "io_queue_recursive": [], "io_service_time_recursive": [], "io_wait_time_recursive": [], "io_merged_recursive": [], "io_time_recursive": [], "sectors_recursive": [] }, "cpu_stats" : { "cpu_usage" : { "percpu_usage" : [ 16970827, 1839451, 7107380, 10571290 ], "usage_in_usermode" : 10000000, "total_usage" : 36488948, "usage_in_kernelmode" : 20000000 }, "system_cpu_usage" : 20091722000000000, "online_cpus": 4 }, "precpu_stats" : { "cpu_usage" : { "percpu_usage" : [ 16970827, 1839451, 7107380, 10571290 ], "usage_in_usermode" : 10000000, "total_usage" : 36488948, "usage_in_kernelmode" : 20000000 }, "system_cpu_usage" : 20091722000000000, "online_cpus": 4 } }` // 1 second later, cache is 100 jsonStats2 := `{ "read" : "2015-01-08T22:57:32.547920715Z", "networks" : { "eth0":{ "rx_dropped" : 0, "rx_bytes" : 648, "rx_errors" : 0, "tx_packets" : 8, "tx_dropped" : 0, "rx_packets" : 8, "tx_errors" : 0, "tx_bytes" : 648 } }, "memory_stats" : { "stats" : { "total_pgmajfault" : 0, "cache" : 100, "mapped_file" : 0, "total_inactive_file" : 0, "pgpgout" : 414, "rss" : 6537216, "total_mapped_file" : 0, "writeback" : 0, "unevictable" : 0, "pgpgin" : 477, "total_unevictable" : 0, "pgmajfault" : 0, "total_rss" : 6537216, "total_rss_huge" : 6291456, "total_writeback" : 0, "total_inactive_anon" : 0, "rss_huge" : 6291456, "total_pgfault" : 964, "total_active_file" : 0, "active_anon" : 6537216, "total_active_anon" : 6537216, "total_pgpgout" : 414, "total_cache" : 0, "inactive_anon" : 0, "active_file" : 0, "pgfault" : 964, "inactive_file" : 0, "total_pgpgin" : 477, "swap" : 47312896, "hierarchical_memsw_limit" : 1610612736 }, "max_usage" : 6651904, "usage" : 6537216, "failcnt" : 0, "limit" : 67108864 }, "blkio_stats": { "io_service_bytes_recursive": [ { "major": 8, "minor": 0, "op": "Read", "value": 428795731968 }, { "major": 8, "minor": 0, "op": "Write", "value": 388177920 } ], "io_serviced_recursive": [ { "major": 8, "minor": 0, "op": "Read", "value": 25994442 }, { "major": 8, "minor": 0, "op": "Write", "value": 1734 } ], "io_queue_recursive": [], "io_service_time_recursive": [], "io_wait_time_recursive": [], "io_merged_recursive": [], "io_time_recursive": [], "sectors_recursive": [] }, "cpu_stats" : { "cpu_usage" : { "percpu_usage" : [ 16970827, 1839451, 7107380, 10571290 ], "usage_in_usermode" : 10000000, "total_usage" : 36488948, "usage_in_kernelmode" : 20000000 }, "system_cpu_usage" : 20091722000000000, "online_cpus": 4 }, "precpu_stats" : { "cpu_usage" : { "percpu_usage" : [ 16970827, 1839451, 7107380, 10571290 ], "usage_in_usermode" : 10000000, "total_usage" : 36488948, "usage_in_kernelmode" : 20000000 }, "system_cpu_usage" : 20091722000000000, "online_cpus": 4 } }` var expected1 Stats var expected2 Stats err := json.Unmarshal([]byte(jsonStats1), &expected1) if err != nil { t.Fatal(err) } err = json.Unmarshal([]byte(jsonStats2), &expected2) if err != nil { t.Fatal(err) } id := "4fa6e0f0" var req http.Request server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") w.Write([]byte(jsonStats1)) w.Write([]byte(jsonStats2)) req = *r })) defer server.Close() client, _ := NewClient(server.URL) client.SkipServerVersionCheck = true errC := make(chan error, 1) statsC := make(chan *Stats) done := make(chan bool) defer close(done) go func() { errC <- client.Stats(StatsOptions{ID: id, Stats: statsC, Stream: true, Done: done}) close(errC) }() var resultStats []*Stats for { stats, ok := <-statsC if !ok { break } resultStats = append(resultStats, stats) } err = <-errC if err != nil { t.Fatal(err) } if len(resultStats) != 2 { t.Fatalf("Stats: Expected 2 results. Got %d.", len(resultStats)) } if !reflect.DeepEqual(resultStats[0], &expected1) { t.Errorf("Stats: Expected:\n%+v\nGot:\n%+v", expected1, resultStats[0]) } if !reflect.DeepEqual(resultStats[1], &expected2) { t.Errorf("Stats: Expected:\n%+v\nGot:\n%+v", expected2, resultStats[1]) } if req.Method != http.MethodGet { t.Errorf("Stats: wrong HTTP method. Want GET. Got %s.", req.Method) } u, _ := url.Parse(client.getURL("/containers/" + id + "/stats")) if req.URL.Path != u.Path { t.Errorf("Stats: wrong HTTP path. Want %q. Got %q.", u.Path, req.URL.Path) } } // The test for Docker Stat API of the host uses cgroup2 func TestStatsV2(t *testing.T) { t.Parallel() jsonStats1 := `{ "read": "2022-05-18T14:37:18.175989615Z", "preread": "2022-05-18T14:37:17.171501052Z", "pids_stats": { "current": 29, "limit": 76948 }, "blkio_stats": { "io_service_bytes_recursive": [ { "major": 259, "minor": 0, "op": "read", "value": 11390976 }, { "major": 259, "minor": 0, "op": "write", "value": 0 } ], "io_serviced_recursive": null, "io_queue_recursive": null, "io_service_time_recursive": null, "io_wait_time_recursive": null, "io_merged_recursive": null, "io_time_recursive": null, "sectors_recursive": null }, "num_procs": 0, "storage_stats": {}, "cpu_stats": { "cpu_usage": { "total_usage": 185266562000, "usage_in_kernelmode": 37912635000, "usage_in_usermode": 147353926000 }, "system_cpu_usage": 26707255190000000, "online_cpus": 24, "throttling_data": { "periods": 0, "throttled_periods": 0, "throttled_time": 0 } }, "precpu_stats": { "cpu_usage": { "total_usage": 185266562000, "usage_in_kernelmode": 37912635000, "usage_in_usermode": 147353926000 }, "system_cpu_usage": 26707231080000000, "online_cpus": 24, "throttling_data": { "periods": 0, "throttled_periods": 0, "throttled_time": 0 } }, "memory_stats": { "usage": 28557312, "stats": { "active_anon": 4096, "active_file": 7446528, "anon": 16572416, "anon_thp": 0, "file": 10829824, "file_dirty": 0, "file_mapped": 9740288, "file_writeback": 0, "inactive_anon": 8069120, "inactive_file": 11882496, "kernel_stack": 475136, "pgactivate": 241, "pgdeactivate": 253, "pgfault": 7714, "pglazyfree": 3042, "pglazyfreed": 967, "pgmajfault": 155, "pgrefill": 301, "pgscan": 1802, "pgsteal": 1100, "shmem": 0, "slab": 488920, "slab_reclaimable": 159664, "slab_unreclaimable": 329256, "sock": 0, "thp_collapse_alloc": 0, "thp_fault_alloc": 0, "unevictable": 0, "workingset_activate": 0, "workingset_nodereclaim": 0, "workingset_refault": 0 }, "limit": 67353382912 }, "networks": { "eth0": { "rx_bytes": 96802652, "rx_packets": 623704, "rx_errors": 0, "rx_dropped": 0, "tx_bytes": 16597749, "tx_packets": 91982, "tx_errors": 0, "tx_dropped": 0 } } }` // 1 second later, shmem is 100 jsonStats2 := `{ "read": "2022-05-18T14:37:18.175989615Z", "preread": "2022-05-18T14:37:17.171501052Z", "pids_stats": { "current": 29, "limit": 76948 }, "blkio_stats": { "io_service_bytes_recursive": [ { "major": 259, "minor": 0, "op": "read", "value": 11390976 }, { "major": 259, "minor": 0, "op": "write", "value": 0 } ], "io_serviced_recursive": null, "io_queue_recursive": null, "io_service_time_recursive": null, "io_wait_time_recursive": null, "io_merged_recursive": null, "io_time_recursive": null, "sectors_recursive": null }, "num_procs": 0, "storage_stats": {}, "cpu_stats": { "cpu_usage": { "total_usage": 185266562000, "usage_in_kernelmode": 37912635000, "usage_in_usermode": 147353926000 }, "system_cpu_usage": 26707255190000000, "online_cpus": 24, "throttling_data": { "periods": 0, "throttled_periods": 0, "throttled_time": 0 } }, "precpu_stats": { "cpu_usage": { "total_usage": 185266562000, "usage_in_kernelmode": 37912635000, "usage_in_usermode": 147353926000 }, "system_cpu_usage": 26707231080000000, "online_cpus": 24, "throttling_data": { "periods": 0, "throttled_periods": 0, "throttled_time": 0 } }, "memory_stats": { "usage": 28557312, "stats": { "active_anon": 4096, "active_file": 7446528, "anon": 16572416, "anon_thp": 0, "file": 10829824, "file_dirty": 0, "file_mapped": 9740288, "file_writeback": 0, "inactive_anon": 8069120, "inactive_file": 11882496, "kernel_stack": 475136, "pgactivate": 241, "pgdeactivate": 253, "pgfault": 7714, "pglazyfree": 3042, "pglazyfreed": 967, "pgmajfault": 155, "pgrefill": 301, "pgscan": 1802, "pgsteal": 1100, "shmem": 100, "slab": 488920, "slab_reclaimable": 159664, "slab_unreclaimable": 329256, "sock": 0, "thp_collapse_alloc": 0, "thp_fault_alloc": 0, "unevictable": 0, "workingset_activate": 0, "workingset_nodereclaim": 0, "workingset_refault": 0 }, "limit": 67353382912 }, "networks": { "eth0": { "rx_bytes": 96802652, "rx_packets": 623704, "rx_errors": 0, "rx_dropped": 0, "tx_bytes": 16597749, "tx_packets": 91982, "tx_errors": 0, "tx_dropped": 0 } } }` var expected1 Stats var expected2 Stats err := json.Unmarshal([]byte(jsonStats1), &expected1) if err != nil { t.Fatal(err) } err = json.Unmarshal([]byte(jsonStats2), &expected2) if err != nil { t.Fatal(err) } id := "4fa6e0f0" var req http.Request server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") w.Write([]byte(jsonStats1)) w.Write([]byte(jsonStats2)) req = *r })) defer server.Close() client, _ := NewClient(server.URL) client.SkipServerVersionCheck = true errC := make(chan error, 1) statsC := make(chan *Stats) done := make(chan bool) defer close(done) go func() { errC <- client.Stats(StatsOptions{ID: id, Stats: statsC, Stream: true, Done: done}) close(errC) }() var resultStats []*Stats for { stats, ok := <-statsC if !ok { break } resultStats = append(resultStats, stats) } err = <-errC if err != nil { t.Fatal(err) } if len(resultStats) != 2 { t.Fatalf("Stats: Expected 2 results. Got %d.", len(resultStats)) } if !reflect.DeepEqual(resultStats[0], &expected1) { t.Errorf("Stats: Expected:\n%+v\nGot:\n%+v", expected1, resultStats[0]) } if !reflect.DeepEqual(resultStats[1], &expected2) { t.Errorf("Stats: Expected:\n%+v\nGot:\n%+v", expected2, resultStats[1]) } if req.Method != http.MethodGet { t.Errorf("Stats: wrong HTTP method. Want GET. Got %s.", req.Method) } u, _ := url.Parse(client.getURL("/containers/" + id + "/stats")) if req.URL.Path != u.Path { t.Errorf("Stats: wrong HTTP path. Want %q. Got %q.", u.Path, req.URL.Path) } } func TestStatsContainerNotFound(t *testing.T) { t.Parallel() client := newTestClient(&FakeRoundTripper{message: "no such container", status: http.StatusNotFound}) statsC := make(chan *Stats) done := make(chan bool) defer close(done) err := client.Stats(StatsOptions{ID: "abef348", Stats: statsC, Stream: true, Done: done}) expectNoSuchContainer(t, "abef348", err) } go-dockerclient-1.12.0/container_stop.go000066400000000000000000000022141465717111200202260ustar00rootroot00000000000000package docker import ( "context" "errors" "fmt" "net/http" ) // StopContainer stops a container, killing it after the given timeout (in // seconds). // // See https://goo.gl/R9dZcV for more details. func (c *Client) StopContainer(id string, timeout uint) error { return c.stopContainer(id, timeout, doOptions{}) } // StopContainerWithContext stops a container, killing it after the given // timeout (in seconds). The context can be used to cancel the stop // container request. // // See https://goo.gl/R9dZcV for more details. func (c *Client) StopContainerWithContext(id string, timeout uint, ctx context.Context) error { return c.stopContainer(id, timeout, doOptions{context: ctx}) } func (c *Client) stopContainer(id string, timeout uint, opts doOptions) error { path := fmt.Sprintf("/containers/%s/stop?t=%d", id, timeout) resp, err := c.do(http.MethodPost, path, opts) if err != nil { var e *Error if errors.As(err, &e) && e.Status == http.StatusNotFound { return &NoSuchContainer{ID: id} } return err } defer resp.Body.Close() if resp.StatusCode == http.StatusNotModified { return &ContainerNotRunning{ID: id} } return nil } go-dockerclient-1.12.0/container_stop_test.go000066400000000000000000000056711465717111200212770ustar00rootroot00000000000000package docker import ( "context" "errors" "net/http" "net/url" "reflect" "testing" "time" ) func TestStopContainer(t *testing.T) { t.Parallel() fakeRT := &FakeRoundTripper{message: "", status: http.StatusNoContent} client := newTestClient(fakeRT) id := "4fa6e0f0c6786287e131c3852c58a2e01cc697a68231826813597e4994f1d6e2" err := client.StopContainer(id, 10) if err != nil { t.Fatal(err) } req := fakeRT.requests[0] if req.Method != http.MethodPost { t.Errorf("StopContainer(%q, 10): wrong HTTP method. Want %q. Got %q.", id, http.MethodPost, req.Method) } expectedURL, _ := url.Parse(client.getURL("/containers/" + id + "/stop")) if gotPath := req.URL.Path; gotPath != expectedURL.Path { t.Errorf("StopContainer(%q, 10): Wrong path in request. Want %q. Got %q.", id, expectedURL.Path, gotPath) } } func TestStopContainerWithContext(t *testing.T) { t.Parallel() fakeRT := &FakeRoundTripper{message: "", status: http.StatusNoContent} client := newTestClient(fakeRT) id := "4fa6e0f0c6786287e131c3852c58a2e01cc697a68231826813597e4994f1d6e2" ctx, cancel := context.WithTimeout(context.TODO(), 1*time.Second) defer cancel() stopError := make(chan error) go func() { stopError <- client.StopContainerWithContext(id, 10, ctx) }() select { case err := <-stopError: if err != nil { t.Fatal(err) } req := fakeRT.requests[0] if req.Method != http.MethodPost { t.Errorf("StopContainer(%q, 10): wrong HTTP method. Want %q. Got %q.", id, http.MethodPost, req.Method) } expectedURL, _ := url.Parse(client.getURL("/containers/" + id + "/stop")) if gotPath := req.URL.Path; gotPath != expectedURL.Path { t.Errorf("StopContainer(%q, 10): Wrong path in request. Want %q. Got %q.", id, expectedURL.Path, gotPath) } case <-ctx.Done(): // Context was canceled unexpectedly. Report the same. t.Fatalf("Context canceled when waiting for stop container response: %v", ctx.Err()) } } func TestStopContainerNotFound(t *testing.T) { t.Parallel() client := newTestClient(&FakeRoundTripper{message: "no such container", status: http.StatusNotFound}) err := client.StopContainer("a2334", 10) expectNoSuchContainer(t, "a2334", err) } func TestStopContainerNotRunning(t *testing.T) { t.Parallel() client := newTestClient(&FakeRoundTripper{message: "container not running", status: http.StatusNotModified}) err := client.StopContainer("a2334", 10) expected := &ContainerNotRunning{ID: "a2334"} if !reflect.DeepEqual(err, expected) { t.Errorf("StopContainer: Wrong error returned. Want %#v. Got %#v.", expected, err) } } func TestStopContainerWhenContextTimesOut(t *testing.T) { t.Parallel() rt := sleepyRoudTripper{sleepDuration: 300 * time.Millisecond} client := newTestClient(&rt) ctx, cancel := context.WithTimeout(context.TODO(), 50*time.Millisecond) defer cancel() err := client.StopContainerWithContext("id", 10, ctx) if !errors.Is(err, context.DeadlineExceeded) { t.Errorf("Expected 'DeadlineExceededError', got: %v", err) } } go-dockerclient-1.12.0/container_test.go000066400000000000000000000055011465717111200202220ustar00rootroot00000000000000// Copyright 2013 go-dockerclient authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package docker import ( "errors" "net/http" "testing" "time" ) func TestStateString(t *testing.T) { t.Parallel() started := time.Now().Add(-3 * time.Hour) tests := []struct { name string input State expected string }{ {"paused", State{Running: true, Paused: true, StartedAt: started}, "Up 3 hours (Paused)"}, {"restarting", State{Running: true, Restarting: true, ExitCode: 7, FinishedAt: started}, "Restarting (7) 3 hours ago"}, {"up", State{Running: true, StartedAt: started}, "Up 3 hours"}, {"being removed", State{RemovalInProgress: true}, "Removal In Progress"}, {"dead", State{Dead: true}, "Dead"}, {"created", State{}, "Created"}, {"no creation info", State{StartedAt: started}, ""}, {"erro code", State{ExitCode: 7, StartedAt: started, FinishedAt: started}, "Exited (7) 3 hours ago"}, } for _, tt := range tests { test := tt t.Run(test.name, func(t *testing.T) { t.Parallel() if got := test.input.String(); got != test.expected { t.Errorf("State.String(): wrong result. Want %q. Got %q.", test.expected, got) } }) } } func TestStateStateString(t *testing.T) { t.Parallel() started := time.Now().Add(-3 * time.Hour) tests := []struct { input State expected string }{ {State{Running: true, Paused: true}, "paused"}, {State{Running: true, Restarting: true}, "restarting"}, {State{Running: true}, "running"}, {State{Dead: true}, "dead"}, {State{}, "created"}, {State{StartedAt: started}, "exited"}, } for _, tt := range tests { test := tt t.Run(test.expected, func(t *testing.T) { t.Parallel() if got := test.input.StateString(); got != test.expected { t.Errorf("State.String(): wrong result. Want %q. Got %q.", test.expected, got) } }) } } // sleepyRoundTripper implements the http.RoundTripper interface. It sleeps // for the 'sleep' duration and then returns an error for RoundTrip method. type sleepyRoudTripper struct { sleepDuration time.Duration } func (rt *sleepyRoudTripper) RoundTrip(r *http.Request) (*http.Response, error) { time.Sleep(rt.sleepDuration) return nil, errors.New("Can't complete round trip") } func TestNoSuchContainerError(t *testing.T) { t.Parallel() err := &NoSuchContainer{ID: "i345"} expected := "No such container: i345" if got := err.Error(); got != expected { t.Errorf("NoSuchContainer: wrong message. Want %q. Got %q.", expected, got) } } func TestNoSuchContainerErrorMessage(t *testing.T) { t.Parallel() err := &NoSuchContainer{ID: "i345", Err: errors.New("some advanced error info")} expected := "some advanced error info" if got := err.Error(); got != expected { t.Errorf("NoSuchContainer: wrong message. Want %q. Got %q.", expected, got) } } go-dockerclient-1.12.0/container_top.go000066400000000000000000000017061465717111200200500ustar00rootroot00000000000000package docker import ( "encoding/json" "errors" "fmt" "net/http" ) // TopResult represents the list of processes running in a container, as // returned by /containers//top. // // See https://goo.gl/FLwpPl for more details. type TopResult struct { Titles []string Processes [][]string } // TopContainer returns processes running inside a container // // See https://goo.gl/FLwpPl for more details. func (c *Client) TopContainer(id string, psArgs string) (TopResult, error) { var args string var result TopResult if psArgs != "" { args = fmt.Sprintf("?ps_args=%s", psArgs) } path := fmt.Sprintf("/containers/%s/top%s", id, args) resp, err := c.do(http.MethodGet, path, doOptions{}) if err != nil { var e *Error if errors.As(err, &e) && e.Status == http.StatusNotFound { return result, &NoSuchContainer{ID: id} } return result, err } defer resp.Body.Close() err = json.NewDecoder(resp.Body).Decode(&result) return result, err } go-dockerclient-1.12.0/container_top_test.go000066400000000000000000000041561465717111200211110ustar00rootroot00000000000000package docker import ( "encoding/json" "net/http" "reflect" "strings" "testing" ) func TestTopContainer(t *testing.T) { t.Parallel() jsonTop := `{ "Processes": [ [ "ubuntu", "3087", "815", "0", "01:44", "?", "00:00:00", "cmd1" ], [ "root", "3158", "3087", "0", "01:44", "?", "00:00:01", "cmd2" ] ], "Titles": [ "UID", "PID", "PPID", "C", "STIME", "TTY", "TIME", "CMD" ] }` var expected TopResult err := json.Unmarshal([]byte(jsonTop), &expected) if err != nil { t.Fatal(err) } id := "4fa6e0f0" fakeRT := &FakeRoundTripper{message: jsonTop, status: http.StatusOK} client := newTestClient(fakeRT) processes, err := client.TopContainer(id, "") if err != nil { t.Fatal(err) } if !reflect.DeepEqual(processes, expected) { t.Errorf("TopContainer: Expected %#v. Got %#v.", expected, processes) } if len(processes.Processes) != 2 || len(processes.Processes[0]) != 8 || processes.Processes[0][7] != "cmd1" { t.Errorf("TopContainer: Process list to include cmd1. Got %#v.", processes) } expectedURI := "/containers/" + id + "/top" if !strings.HasSuffix(fakeRT.requests[0].URL.String(), expectedURI) { t.Errorf("TopContainer: Expected URI to have %q. Got %q.", expectedURI, fakeRT.requests[0].URL.String()) } } func TestTopContainerNotFound(t *testing.T) { t.Parallel() client := newTestClient(&FakeRoundTripper{message: "no such container", status: http.StatusNotFound}) _, err := client.TopContainer("abef348", "") expectNoSuchContainer(t, "abef348", err) } func TestTopContainerWithPsArgs(t *testing.T) { t.Parallel() fakeRT := &FakeRoundTripper{message: "no such container", status: http.StatusNotFound} client := newTestClient(fakeRT) _, err := client.TopContainer("abef348", "aux") expectNoSuchContainer(t, "abef348", err) expectedURI := "/containers/abef348/top?ps_args=aux" if !strings.HasSuffix(fakeRT.requests[0].URL.String(), expectedURI) { t.Errorf("TopContainer: Expected URI to have %q. Got %q.", expectedURI, fakeRT.requests[0].URL.String()) } } go-dockerclient-1.12.0/container_unix_test.go000066400000000000000000000073251465717111200212730ustar00rootroot00000000000000//go:build !windows // Copyright 2016 go-dockerclient authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package docker import ( "bufio" "bytes" "net" "net/http" "os" "path/filepath" "strconv" "testing" "time" ) func TestExportContainerViaUnixSocket(t *testing.T) { t.Parallel() content := "exported container tar content" var buf []byte out := bytes.NewBuffer(buf) tempSocket := tempfile("export_socket") defer os.Remove(tempSocket) endpoint := "unix://" + tempSocket u, _ := parseEndpoint(endpoint, false) client := Client{ HTTPClient: defaultClient(), Dialer: &net.Dialer{}, endpoint: endpoint, endpointURL: u, SkipServerVersionCheck: true, } listening := make(chan string) done := make(chan int) containerID := "4fa6e0f0c678" go runStreamConnServer(t, "unix", tempSocket, listening, done, containerID) <-listening // wait for server to start opts := ExportContainerOptions{ID: containerID, OutputStream: out} err := client.ExportContainer(opts) <-done // make sure server stopped if err != nil { t.Errorf("ExportContainer: caugh error %#v while exporting container, expected nil", err.Error()) } if out.String() != content { t.Errorf("ExportContainer: wrong stdout. Want %#v. Got %#v.", content, out.String()) } } func TestStatsTimeoutUnixSocket(t *testing.T) { t.Parallel() tmpdir, err := os.MkdirTemp("", "socket") if err != nil { t.Fatal(err) } defer os.RemoveAll(tmpdir) socketPath := filepath.Join(tmpdir, "docker_test.sock") t.Logf("socketPath=%s", socketPath) l, err := net.Listen("unix", socketPath) if err != nil { t.Fatal(err) } received := make(chan bool) defer l.Close() go func() { conn, connErr := l.Accept() if connErr != nil { t.Logf("Failed to accept connection: %s", connErr) return } breader := bufio.NewReader(conn) req, connErr := http.ReadRequest(breader) if connErr != nil { t.Logf("Failed to read request: %s", connErr) return } if req.URL.Path != "/containers/c/stats" { t.Logf("Wrong URL path for stats: %q", req.URL.Path) return } received <- true time.Sleep(2 * time.Second) }() client, _ := NewClient("unix://" + socketPath) client.SkipServerVersionCheck = true errC := make(chan error, 1) statsC := make(chan *Stats) done := make(chan bool) defer close(done) go func() { errC <- client.Stats(StatsOptions{ID: "c", Stats: statsC, Stream: true, Done: done, Timeout: time.Millisecond}) close(errC) }() err = <-errC e, ok := err.(net.Error) if !ok || !e.Timeout() { t.Errorf("Failed to receive timeout error, got %#v", err) } recvTimeout := 2 * time.Second select { case <-received: return case <-time.After(recvTimeout): t.Fatalf("Timeout waiting to receive message after %v", recvTimeout) } } func runStreamConnServer(t *testing.T, network, laddr string, listening chan<- string, done chan<- int, containerID string) { defer close(done) l, err := net.Listen(network, laddr) if err != nil { t.Errorf("Listen(%q, %q) failed: %v", network, laddr, err) listening <- "" return } defer l.Close() listening <- l.Addr().String() c, err := l.Accept() if err != nil { t.Logf("Accept failed: %v", err) return } defer c.Close() breader := bufio.NewReader(c) req, err := http.ReadRequest(breader) if err != nil { t.Error(err) return } if path := "/containers/" + containerID + "/export"; req.URL.Path != path { t.Errorf("wrong path. Want %q. Got %q", path, req.URL.Path) return } c.Write([]byte("HTTP/1.1 200 OK\n\nexported container tar content")) } func tempfile(filename string) string { return os.TempDir() + "/" + filename + "." + strconv.Itoa(os.Getpid()) } go-dockerclient-1.12.0/container_unpause.go000066400000000000000000000007611465717111200207260ustar00rootroot00000000000000package docker import ( "errors" "fmt" "net/http" ) // UnpauseContainer unpauses the given container. // // See https://goo.gl/sZ2faO for more details. func (c *Client) UnpauseContainer(id string) error { path := fmt.Sprintf("/containers/%s/unpause", id) resp, err := c.do(http.MethodPost, path, doOptions{}) if err != nil { var e *Error if errors.As(err, &e) && e.Status == http.StatusNotFound { return &NoSuchContainer{ID: id} } return err } resp.Body.Close() return nil } go-dockerclient-1.12.0/container_unpause_test.go000066400000000000000000000020131465717111200217550ustar00rootroot00000000000000package docker import ( "net/http" "net/url" "testing" ) func TestUnpauseContainer(t *testing.T) { t.Parallel() fakeRT := &FakeRoundTripper{message: "", status: http.StatusNoContent} client := newTestClient(fakeRT) id := "4fa6e0f0c6786287e131c3852c58a2e01cc697a68231826813597e4994f1d6e2" err := client.UnpauseContainer(id) if err != nil { t.Fatal(err) } req := fakeRT.requests[0] if req.Method != http.MethodPost { t.Errorf("PauseContainer(%q): wrong HTTP method. Want %q. Got %q.", id, http.MethodPost, req.Method) } expectedURL, _ := url.Parse(client.getURL("/containers/" + id + "/unpause")) if gotPath := req.URL.Path; gotPath != expectedURL.Path { t.Errorf("PauseContainer(%q): Wrong path in request. Want %q. Got %q.", id, expectedURL.Path, gotPath) } } func TestUnpauseContainerNotFound(t *testing.T) { t.Parallel() client := newTestClient(&FakeRoundTripper{message: "no such container", status: http.StatusNotFound}) err := client.UnpauseContainer("a2334") expectNoSuchContainer(t, "a2334", err) } go-dockerclient-1.12.0/container_update.go000066400000000000000000000026131465717111200205260ustar00rootroot00000000000000package docker import ( "context" "fmt" "net/http" ) // UpdateContainerOptions specify parameters to the UpdateContainer function. // // See https://goo.gl/Y6fXUy for more details. type UpdateContainerOptions struct { BlkioWeight int `json:"BlkioWeight"` CPUShares int `json:"CpuShares"` CPUPeriod int `json:"CpuPeriod"` CPURealtimePeriod int64 `json:"CpuRealtimePeriod"` CPURealtimeRuntime int64 `json:"CpuRealtimeRuntime"` CPUQuota int `json:"CpuQuota"` CpusetCpus string `json:"CpusetCpus"` CpusetMems string `json:"CpusetMems"` Memory int `json:"Memory"` MemorySwap int `json:"MemorySwap"` MemoryReservation int `json:"MemoryReservation"` KernelMemory int `json:"KernelMemory"` RestartPolicy RestartPolicy `json:"RestartPolicy,omitempty"` Context context.Context } // UpdateContainer updates the container at ID with the options // // See https://goo.gl/Y6fXUy for more details. func (c *Client) UpdateContainer(id string, opts UpdateContainerOptions) error { resp, err := c.do(http.MethodPost, fmt.Sprintf("/containers/"+id+"/update"), doOptions{ data: opts, forceJSON: true, context: opts.Context, }) if err != nil { return err } defer resp.Body.Close() return nil } go-dockerclient-1.12.0/container_update_test.go000066400000000000000000000024751465717111200215730ustar00rootroot00000000000000package docker import ( "encoding/json" "net/http" "net/url" "reflect" "testing" ) func TestUpdateContainer(t *testing.T) { t.Parallel() fakeRT := &FakeRoundTripper{message: "", status: http.StatusOK} client := newTestClient(fakeRT) id := "4fa6e0f0c6786287e131c3852c58a2e01cc697a68231826813597e4994f1d6e2" update := UpdateContainerOptions{Memory: 12345, CpusetMems: "0,1"} err := client.UpdateContainer(id, update) if err != nil { t.Fatal(err) } req := fakeRT.requests[0] if req.Method != http.MethodPost { t.Errorf("UpdateContainer: wrong HTTP method. Want %q. Got %q.", http.MethodPost, req.Method) } expectedURL, _ := url.Parse(client.getURL("/containers/" + id + "/update")) if gotPath := req.URL.Path; gotPath != expectedURL.Path { t.Errorf("UpdateContainer: Wrong path in request. Want %q. Got %q.", expectedURL.Path, gotPath) } expectedContentType := "application/json" if contentType := req.Header.Get("Content-Type"); contentType != expectedContentType { t.Errorf("UpdateContainer: Wrong content-type in request. Want %q. Got %q.", expectedContentType, contentType) } var out UpdateContainerOptions if err := json.NewDecoder(req.Body).Decode(&out); err != nil { t.Fatal(err) } if !reflect.DeepEqual(out, update) { t.Errorf("UpdateContainer: wrong body, got: %#v, want %#v", out, update) } } go-dockerclient-1.12.0/container_wait.go000066400000000000000000000022271465717111200202110ustar00rootroot00000000000000package docker import ( "context" "encoding/json" "errors" "net/http" ) // WaitContainer blocks until the given container stops, return the exit code // of the container status. // // See https://goo.gl/4AGweZ for more details. func (c *Client) WaitContainer(id string) (int, error) { return c.waitContainer(id, doOptions{}) } // WaitContainerWithContext blocks until the given container stops, return the exit code // of the container status. The context object can be used to cancel the // inspect request. // // See https://goo.gl/4AGweZ for more details. func (c *Client) WaitContainerWithContext(id string, ctx context.Context) (int, error) { return c.waitContainer(id, doOptions{context: ctx}) } func (c *Client) waitContainer(id string, opts doOptions) (int, error) { resp, err := c.do(http.MethodPost, "/containers/"+id+"/wait", opts) if err != nil { var e *Error if errors.As(err, &e) && e.Status == http.StatusNotFound { return 0, &NoSuchContainer{ID: id} } return 0, err } defer resp.Body.Close() var r struct{ StatusCode int } if err := json.NewDecoder(resp.Body).Decode(&r); err != nil { return 0, err } return r.StatusCode, nil } go-dockerclient-1.12.0/container_wait_test.go000066400000000000000000000054451465717111200212550ustar00rootroot00000000000000package docker import ( "context" "errors" "net/http" "net/url" "testing" "time" ) func TestWaitContainer(t *testing.T) { t.Parallel() fakeRT := &FakeRoundTripper{message: `{"StatusCode": 56}`, status: http.StatusOK} client := newTestClient(fakeRT) id := "4fa6e0f0c6786287e131c3852c58a2e01cc697a68231826813597e4994f1d6e2" status, err := client.WaitContainer(id) if err != nil { t.Fatal(err) } if status != 56 { t.Errorf("WaitContainer(%q): wrong return. Want 56. Got %d.", id, status) } req := fakeRT.requests[0] if req.Method != http.MethodPost { t.Errorf("WaitContainer(%q): wrong HTTP method. Want %q. Got %q.", id, http.MethodPost, req.Method) } expectedURL, _ := url.Parse(client.getURL("/containers/" + id + "/wait")) if gotPath := req.URL.Path; gotPath != expectedURL.Path { t.Errorf("WaitContainer(%q): Wrong path in request. Want %q. Got %q.", id, expectedURL.Path, gotPath) } } func TestWaitContainerWithContext(t *testing.T) { t.Parallel() fakeRT := &FakeRoundTripper{message: `{"StatusCode": 56}`, status: http.StatusOK} client := newTestClient(fakeRT) id := "4fa6e0f0c6786287e131c3852c58a2e01cc697a68231826813597e4994f1d6e2" ctx, cancel := context.WithTimeout(context.TODO(), 1*time.Second) defer cancel() var status int waitError := make(chan error) go func() { var err error status, err = client.WaitContainerWithContext(id, ctx) waitError <- err }() select { case err := <-waitError: if err != nil { t.Fatal(err) } if status != 56 { t.Errorf("WaitContainer(%q): wrong return. Want 56. Got %d.", id, status) } req := fakeRT.requests[0] if req.Method != http.MethodPost { t.Errorf("WaitContainer(%q): wrong HTTP method. Want %q. Got %q.", id, http.MethodPost, req.Method) } expectedURL, _ := url.Parse(client.getURL("/containers/" + id + "/wait")) if gotPath := req.URL.Path; gotPath != expectedURL.Path { t.Errorf("WaitContainer(%q): Wrong path in request. Want %q. Got %q.", id, expectedURL.Path, gotPath) } case <-ctx.Done(): // Context was canceled unexpectedly. Report the same. t.Fatalf("Context canceled when waiting for wait container response: %v", ctx.Err()) } } func TestWaitContainerNotFound(t *testing.T) { t.Parallel() client := newTestClient(&FakeRoundTripper{message: "no such container", status: http.StatusNotFound}) _, err := client.WaitContainer("a2334") expectNoSuchContainer(t, "a2334", err) } func TestWaitContainerWhenContextTimesOut(t *testing.T) { t.Parallel() rt := sleepyRoudTripper{sleepDuration: 200 * time.Millisecond} client := newTestClient(&rt) ctx, cancel := context.WithTimeout(context.TODO(), 100*time.Millisecond) defer cancel() _, err := client.WaitContainerWithContext("id", ctx) if !errors.Is(err, context.DeadlineExceeded) { t.Errorf("Expected 'DeadlineExceededError', got: %v", err) } } go-dockerclient-1.12.0/distribution.go000066400000000000000000000014551465717111200177240ustar00rootroot00000000000000// Copyright 2017 go-dockerclient authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package docker import ( "encoding/json" "net/http" "github.com/docker/docker/api/types/registry" ) // InspectDistribution returns image digest and platform information by contacting the registry func (c *Client) InspectDistribution(name string) (*registry.DistributionInspect, error) { path := "/distribution/" + name + "/json" resp, err := c.do(http.MethodGet, path, doOptions{}) if err != nil { return nil, err } defer resp.Body.Close() var distributionInspect registry.DistributionInspect if err := json.NewDecoder(resp.Body).Decode(&distributionInspect); err != nil { return nil, err } return &distributionInspect, nil } go-dockerclient-1.12.0/distribution_test.go000066400000000000000000000026171465717111200207640ustar00rootroot00000000000000// Copyright 2017 go-dockerclient authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package docker import ( "encoding/json" "net/http" "reflect" "testing" "github.com/docker/docker/api/types/registry" ) func TestInspectDistribution(t *testing.T) { t.Parallel() jsonDistribution := `{ "Descriptor": { "MediaType": "application/vnd.docker.distribution.manifest.v2+json", "Digest": "sha256:c0537ff6a5218ef531ece93d4984efc99bbf3f7497c0a7726c88e2bb7584dc96", "Size": 3987495, "URLs": [ "" ] }, "Platforms": [ { "Architecture": "amd64", "OS": "linux", "OSVersion": "", "OSFeatures": [ "" ], "Variant": "", "Features": [ "" ] } ] }` var expected registry.DistributionInspect err := json.Unmarshal([]byte(jsonDistribution), &expected) if err != nil { t.Fatal(err) } fakeRT := &FakeRoundTripper{message: jsonDistribution, status: http.StatusOK} client := newTestClient(fakeRT) // image name/tag is not present in the reply, so it can be omitted for testing purposes distributionInspect, err := client.InspectDistribution("") if err != nil { t.Fatal(err) } if !reflect.DeepEqual(*distributionInspect, expected) { t.Errorf("InspectDistribution(%q): Expected %#v. Got %#v.", "", expected, distributionInspect) } } go-dockerclient-1.12.0/env.go000066400000000000000000000107011465717111200157670ustar00rootroot00000000000000// Copyright 2014 Docker authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the DOCKER-LICENSE file. package docker import ( "encoding/json" "fmt" "io" "strconv" "strings" ) // Env represents a list of key-pair represented in the form KEY=VALUE. type Env []string // Get returns the string value of the given key. func (env *Env) Get(key string) (value string) { return env.Map()[key] } // Exists checks whether the given key is defined in the internal Env // representation. func (env *Env) Exists(key string) bool { _, exists := env.Map()[key] return exists } // GetBool returns a boolean representation of the given key. The key is false // whenever its value if 0, no, false, none or an empty string. Any other value // will be interpreted as true. func (env *Env) GetBool(key string) (value bool) { s := strings.ToLower(strings.Trim(env.Get(key), " \t")) if s == "" || s == "0" || s == "no" || s == "false" || s == "none" { return false } return true } // SetBool defines a boolean value to the given key. func (env *Env) SetBool(key string, value bool) { if value { env.Set(key, "1") } else { env.Set(key, "0") } } // GetInt returns the value of the provided key, converted to int. // // It the value cannot be represented as an integer, it returns -1. func (env *Env) GetInt(key string) int { return int(env.GetInt64(key)) } // SetInt defines an integer value to the given key. func (env *Env) SetInt(key string, value int) { env.Set(key, strconv.Itoa(value)) } // GetInt64 returns the value of the provided key, converted to int64. // // It the value cannot be represented as an integer, it returns -1. func (env *Env) GetInt64(key string) int64 { s := strings.Trim(env.Get(key), " \t") val, err := strconv.ParseInt(s, 10, 64) if err != nil { return -1 } return val } // SetInt64 defines an integer (64-bit wide) value to the given key. func (env *Env) SetInt64(key string, value int64) { env.Set(key, strconv.FormatInt(value, 10)) } // GetJSON unmarshals the value of the provided key in the provided iface. // // iface is a value that can be provided to the json.Unmarshal function. func (env *Env) GetJSON(key string, iface any) error { sval := env.Get(key) if sval == "" { return nil } return json.Unmarshal([]byte(sval), iface) } // SetJSON marshals the given value to JSON format and stores it using the // provided key. func (env *Env) SetJSON(key string, value any) error { sval, err := json.Marshal(value) if err != nil { return err } env.Set(key, string(sval)) return nil } // GetList returns a list of strings matching the provided key. It handles the // list as a JSON representation of a list of strings. // // If the given key matches to a single string, it will return a list // containing only the value that matches the key. func (env *Env) GetList(key string) []string { sval := env.Get(key) if sval == "" { return nil } var l []string if err := json.Unmarshal([]byte(sval), &l); err != nil { l = append(l, sval) } return l } // SetList stores the given list in the provided key, after serializing it to // JSON format. func (env *Env) SetList(key string, value []string) error { return env.SetJSON(key, value) } // Set defines the value of a key to the given string. func (env *Env) Set(key, value string) { *env = append(*env, key+"="+value) } // Decode decodes `src` as a json dictionary, and adds each decoded key-value // pair to the environment. // // If `src` cannot be decoded as a json dictionary, an error is returned. func (env *Env) Decode(src io.Reader) error { m := make(map[string]any) if err := json.NewDecoder(src).Decode(&m); err != nil { return err } for k, v := range m { env.SetAuto(k, v) } return nil } // SetAuto will try to define the Set* method to call based on the given value. func (env *Env) SetAuto(key string, value any) { if fval, ok := value.(float64); ok { env.SetInt64(key, int64(fval)) } else if sval, ok := value.(string); ok { env.Set(key, sval) } else if val, err := json.Marshal(value); err == nil { env.Set(key, string(val)) } else { env.Set(key, fmt.Sprintf("%v", value)) } } // Map returns the map representation of the env. func (env *Env) Map() map[string]string { if env == nil || len(*env) == 0 { return nil } m := make(map[string]string) for _, kv := range *env { parts := strings.SplitN(kv, "=", 2) if len(parts) == 1 { m[parts[0]] = "" } else { m[parts[0]] = parts[1] } } return m } go-dockerclient-1.12.0/env_test.go000066400000000000000000000233671465717111200170420ustar00rootroot00000000000000// Copyright 2014 go-dockerclient authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the DOCKER-LICENSE file. package docker import ( "bytes" "errors" "reflect" "slices" "testing" ) func TestGet(t *testing.T) { t.Parallel() tests := []struct { input []string query string expected string }{ {[]string{"PATH=/usr/bin:/bin", "PYTHONPATH=/usr/local"}, "PATH", "/usr/bin:/bin"}, {[]string{"PATH=/usr/bin:/bin", "PYTHONPATH=/usr/local"}, "PYTHONPATH", "/usr/local"}, {[]string{"PATH=/usr/bin:/bin", "PYTHONPATH=/usr/local"}, "PYTHONPATHI", ""}, {[]string{"WAT="}, "WAT", ""}, } for _, tt := range tests { test := tt t.Run("", func(t *testing.T) { t.Parallel() env := Env(test.input) got := env.Get(test.query) if got != test.expected { t.Errorf("Env.Get(%q): wrong result. Want %q. Got %q", test.query, test.expected, got) } }) } } func TestExists(t *testing.T) { t.Parallel() tests := []struct { input []string query string expected bool }{ {[]string{"WAT=", "PYTHONPATH=/usr/local"}, "WAT", true}, {[]string{"PATH=/usr/bin:/bin", "PYTHONPATH=/usr/local"}, "PYTHONPATH", true}, {[]string{"PATH=/usr/bin:/bin", "PYTHONPATH=/usr/local"}, "PYTHONPATHI", false}, } for _, tt := range tests { test := tt t.Run("", func(t *testing.T) { t.Parallel() env := Env(test.input) got := env.Exists(test.query) if got != test.expected { t.Errorf("Env.Exists(%q): wrong result. Want %v. Got %v", test.query, test.expected, got) } }) } } func TestGetBool(t *testing.T) { t.Parallel() tests := []struct { input string expected bool }{ {"EMPTY_VAR", false}, {"ZERO_VAR", false}, {"NO_VAR", false}, {"FALSE_VAR", false}, {"NONE_VAR", false}, {"TRUE_VAR", true}, {"WAT", true}, {"PATH", true}, {"ONE_VAR", true}, {"NO_VAR_TAB", false}, } env := Env([]string{ "EMPTY_VAR=", "ZERO_VAR=0", "NO_VAR=no", "FALSE_VAR=false", "NONE_VAR=none", "TRUE_VAR=true", "WAT=wat", "PATH=/usr/bin:/bin", "ONE_VAR=1", "NO_VAR_TAB=0 \t\t\t", }) for _, tt := range tests { test := tt t.Run(test.input, func(t *testing.T) { got := env.GetBool(test.input) if got != test.expected { t.Errorf("Env.GetBool(%q): wrong result. Want %v. Got %v.", test.input, test.expected, got) } }) } } func TestSetBool(t *testing.T) { t.Parallel() tests := []struct { input bool expected string }{ {true, "1"}, {false, "0"}, } for _, tt := range tests { var env Env env.SetBool("SOME", tt.input) if got := env.Get("SOME"); got != tt.expected { t.Errorf("Env.SetBool(%v): wrong result. Want %q. Got %q", tt.input, tt.expected, got) } } } func TestGetInt(t *testing.T) { t.Parallel() tests := []struct { input string expected int }{ {"NEGATIVE_INTEGER", -10}, {"NON_INTEGER", -1}, {"ONE", 1}, {"TWO", 2}, } env := Env([]string{"NEGATIVE_INTEGER=-10", "NON_INTEGER=wat", "ONE=1", "TWO=2"}) for _, tt := range tests { test := tt t.Run(test.input, func(t *testing.T) { t.Parallel() got := env.GetInt(test.input) if got != test.expected { t.Errorf("Env.GetInt(%q): wrong result. Want %d. Got %d", test.input, test.expected, got) } }) } } func TestSetInt(t *testing.T) { t.Parallel() tests := []struct { input int expected string }{ {10, "10"}, {13, "13"}, {7, "7"}, {33, "33"}, {0, "0"}, {-34, "-34"}, } for _, tt := range tests { test := tt t.Run(test.expected, func(t *testing.T) { t.Parallel() var env Env env.SetInt("SOME", test.input) if got := env.Get("SOME"); got != test.expected { t.Errorf("Env.SetBool(%d): wrong result. Want %q. Got %q", test.input, test.expected, got) } }) } } func TestGetInt64(t *testing.T) { t.Parallel() tests := []struct { input string expected int64 }{ {"NEGATIVE_INTEGER", -10}, {"NON_INTEGER", -1}, {"ONE", 1}, {"TWO", 2}, } env := Env([]string{"NEGATIVE_INTEGER=-10", "NON_INTEGER=wat", "ONE=1", "TWO=2"}) for _, tt := range tests { test := tt t.Run(test.input, func(t *testing.T) { t.Parallel() got := env.GetInt64(test.input) if got != test.expected { t.Errorf("Env.GetInt64(%q): wrong result. Want %d. Got %d", test.input, test.expected, got) } }) } } func TestSetInt64(t *testing.T) { t.Parallel() tests := []struct { input int64 expected string }{ {10, "10"}, {13, "13"}, {7, "7"}, {33, "33"}, {0, "0"}, {-34, "-34"}, } for _, tt := range tests { test := tt t.Run(test.expected, func(t *testing.T) { t.Parallel() var env Env env.SetInt64("SOME", test.input) if got := env.Get("SOME"); got != test.expected { t.Errorf("Env.SetBool(%d): wrong result. Want %q. Got %q", test.input, test.expected, got) } }) } } func TestGetJSON(t *testing.T) { t.Parallel() var p struct { Name string `json:"name"` Age int `json:"age"` } var env Env env.Set("person", `{"name":"Gopher","age":5}`) err := env.GetJSON("person", &p) if err != nil { t.Error(err) } if p.Name != "Gopher" { t.Errorf("Env.GetJSON(%q): wrong name. Want %q. Got %q", "person", "Gopher", p.Name) } if p.Age != 5 { t.Errorf("Env.GetJSON(%q): wrong age. Want %d. Got %d", "person", 5, p.Age) } } func TestGetJSONAbsent(t *testing.T) { t.Parallel() var l []string var env Env err := env.GetJSON("person", &l) if err != nil { t.Error(err) } if l != nil { t.Errorf("Env.GetJSON(): get unexpected list %v", l) } } func TestGetJSONFailure(t *testing.T) { t.Parallel() var p []string var env Env env.Set("list-person", `{"name":"Gopher","age":5}`) err := env.GetJSON("list-person", &p) if err == nil { t.Errorf("Env.GetJSON(%q): got unexpected error.", "list-person") } } func TestSetJSON(t *testing.T) { t.Parallel() p1 := struct { Name string `json:"name"` Age int `json:"age"` }{Name: "Gopher", Age: 5} var env Env err := env.SetJSON("person", p1) if err != nil { t.Error(err) } var p2 struct { Name string `json:"name"` Age int `json:"age"` } err = env.GetJSON("person", &p2) if err != nil { t.Error(err) } if !reflect.DeepEqual(p1, p2) { t.Errorf("Env.SetJSON(%q): wrong result. Want %v. Got %v", "person", p1, p2) } } func TestSetJSONFailure(t *testing.T) { t.Parallel() var env Env err := env.SetJSON("person", unmarshable{}) if err == nil { t.Error("Env.SetJSON(): got unexpected error") } if env.Exists("person") { t.Errorf("Env.SetJSON(): should not define the key %q, but did", "person") } } func TestGetList(t *testing.T) { t.Parallel() tests := []struct { input string expected []string }{ {"WAT=wat", []string{"wat"}}, {`WAT=["wat","wet","wit","wot","wut"]`, []string{"wat", "wet", "wit", "wot", "wut"}}, {"WAT=", nil}, } for _, tt := range tests { env := Env([]string{tt.input}) got := env.GetList("WAT") if !reflect.DeepEqual(got, tt.expected) { t.Errorf("Env.GetList(%q): wrong result. Want %v. Got %v", "WAT", tt.expected, got) } } } func TestSetList(t *testing.T) { t.Parallel() list := []string{"a", "b", "c"} var env Env if err := env.SetList("SOME", list); err != nil { t.Error(err) } if got := env.GetList("SOME"); !reflect.DeepEqual(got, list) { t.Errorf("Env.SetList(%v): wrong result. Got %v", list, got) } } func TestSet(t *testing.T) { t.Parallel() var env Env env.Set("PATH", "/home/bin:/bin") env.Set("SOMETHING", "/usr/bin") env.Set("PATH", "/bin") if expected, got := "/usr/bin", env.Get("SOMETHING"); got != expected { t.Errorf("Env.Set(%q): wrong result. Want %q. Got %q", expected, expected, got) } if expected, got := "/bin", env.Get("PATH"); got != expected { t.Errorf("Env.Set(%q): wrong result. Want %q. Got %q", expected, expected, got) } } func TestDecode(t *testing.T) { t.Parallel() tests := []struct { input string expectedOut []string expectedErr bool }{ { `{"PATH":"/usr/bin:/bin","containers":54,"wat":["123","345"]}`, []string{"PATH=/usr/bin:/bin", "containers=54", `wat=["123","345"]`}, false, }, {"}}", nil, true}, {`{}`, nil, false}, } for _, tt := range tests { test := tt t.Run(test.input, func(t *testing.T) { t.Parallel() var env Env err := env.Decode(bytes.NewBufferString(test.input)) if !test.expectedErr && err != nil { t.Error(err) } else if test.expectedErr && err == nil { t.Error("Env.Decode(): unexpected error") } got := []string(env) slices.Sort(got) slices.Sort(test.expectedOut) if !reflect.DeepEqual(got, test.expectedOut) { t.Errorf("Env.Decode(): wrong result. Want %v. Got %v.", test.expectedOut, got) } }) } } func TestSetAuto(t *testing.T) { t.Parallel() buf := bytes.NewBufferString("oi") tests := []struct { input any expected string }{ {10, "10"}, {10.3, "10"}, {"oi", "oi"}, {buf, "{}"}, {unmarshable{}, "{}"}, } for _, tt := range tests { test := tt t.Run(test.expected, func(t *testing.T) { t.Parallel() var env Env env.SetAuto("SOME", test.input) if got := env.Get("SOME"); got != test.expected { t.Errorf("Env.SetAuto(%v): wrong result. Want %q. Got %q", test.input, test.expected, got) } }) } } func TestMap(t *testing.T) { t.Parallel() tests := []struct { input []string expected map[string]string }{ {[]string{"PATH=/usr/bin:/bin", "PYTHONPATH=/usr/local"}, map[string]string{"PATH": "/usr/bin:/bin", "PYTHONPATH": "/usr/local"}}, {[]string{"ENABLE_LOGGING", "PYTHONPATH=/usr/local"}, map[string]string{"ENABLE_LOGGING": "", "PYTHONPATH": "/usr/local"}}, {nil, nil}, } for _, tt := range tests { env := Env(tt.input) got := env.Map() if !reflect.DeepEqual(got, tt.expected) { t.Errorf("Env.Map(): wrong result. Want %v. Got %v", tt.expected, got) } } } type unmarshable struct{} func (unmarshable) MarshalJSON() ([]byte, error) { return nil, errors.New("cannot marshal") } go-dockerclient-1.12.0/event.go000066400000000000000000000320041465717111200163200ustar00rootroot00000000000000// Copyright 2014 go-dockerclient authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package docker import ( "encoding/json" "errors" "io" "math" "net" "net/http" "net/http/httputil" "strconv" "sync" "sync/atomic" "time" ) // EventsOptions to filter events // See https://docs.docker.com/engine/api/v1.41/#operation/SystemEvents for more details. type EventsOptions struct { // Show events created since this timestamp then stream new events. Since string // Show events created until this timestamp then stop streaming. Until string // Filter for events. For example: // map[string][]string{"type": {"container"}, "event": {"start", "die"}} // will return events when container was started and stopped or killed // // Available filters: // config= config name or ID // container= container name or ID // daemon= daemon name or ID // event= event type // image= image name or ID // label= image or container label // network= network name or ID // node= node ID // plugin= plugin name or ID // scope= local or swarm // secret= secret name or ID // service= service name or ID // type= container, image, volume, network, daemon, plugin, node, service, secret or config // volume= volume name Filters map[string][]string } // APIEvents represents events coming from the Docker API // The fields in the Docker API changed in API version 1.22, and // events for more than images and containers are now fired off. // To maintain forward and backward compatibility, go-dockerclient // replicates the event in both the new and old format as faithfully as possible. // // For events that only exist in 1.22 in later, `Status` is filled in as // `"Type:Action"` instead of just `Action` to allow for older clients to // differentiate and not break if they rely on the pre-1.22 Status types. // // The transformEvent method can be consulted for more information about how // events are translated from new/old API formats type APIEvents struct { // New API Fields in 1.22 Action string `json:"action,omitempty"` Type string `json:"type,omitempty"` Actor APIActor `json:"actor,omitempty"` // Old API fields for < 1.22 Status string `json:"status,omitempty"` ID string `json:"id,omitempty"` From string `json:"from,omitempty"` // Fields in both Time int64 `json:"time,omitempty"` TimeNano int64 `json:"timeNano,omitempty"` } // APIActor represents an actor that accomplishes something for an event type APIActor struct { ID string `json:"id,omitempty"` Attributes map[string]string `json:"attributes,omitempty"` } type eventMonitoringState struct { // `sync/atomic` expects the first word in an allocated struct to be 64-bit // aligned on both ARM and x86-32. See https://goo.gl/zW7dgq for more details. lastSeen int64 sync.RWMutex sync.WaitGroup enabled bool C chan *APIEvents errC chan error listeners []chan<- *APIEvents closeConn func() } const ( maxMonitorConnRetries = 5 retryInitialWaitTime = 10. ) var ( // ErrNoListeners is the error returned when no listeners are available // to receive an event. ErrNoListeners = errors.New("no listeners present to receive event") // ErrListenerAlreadyExists is the error returned when the listerner already // exists. ErrListenerAlreadyExists = errors.New("listener already exists for docker events") // ErrTLSNotSupported is the error returned when the client does not support // TLS (this applies to the Windows named pipe client). ErrTLSNotSupported = errors.New("tls not supported by this client") // EOFEvent is sent when the event listener receives an EOF error. EOFEvent = &APIEvents{ Type: "EOF", Status: "EOF", } ) // AddEventListener adds a new listener to container events in the Docker API. // // The parameter is a channel through which events will be sent. func (c *Client) AddEventListener(listener chan<- *APIEvents) error { return c.AddEventListenerWithOptions(EventsOptions{}, listener) } // AddEventListener adds a new listener to container events in the Docker API. // See https://docs.docker.com/engine/api/v1.41/#operation/SystemEvents for more details. // // The listener parameter is a channel through which events will be sent. func (c *Client) AddEventListenerWithOptions(options EventsOptions, listener chan<- *APIEvents) error { var err error if !c.eventMonitor.isEnabled() { err = c.eventMonitor.enableEventMonitoring(c, options) if err != nil { return err } } return c.eventMonitor.addListener(listener) } // RemoveEventListener removes a listener from the monitor. func (c *Client) RemoveEventListener(listener chan *APIEvents) error { err := c.eventMonitor.removeListener(listener) if err != nil { return err } if c.eventMonitor.listernersCount() == 0 { c.eventMonitor.disableEventMonitoring() } return nil } func (eventState *eventMonitoringState) addListener(listener chan<- *APIEvents) error { eventState.Lock() defer eventState.Unlock() if listenerExists(listener, &eventState.listeners) { return ErrListenerAlreadyExists } eventState.Add(1) eventState.listeners = append(eventState.listeners, listener) return nil } func (eventState *eventMonitoringState) removeListener(listener chan<- *APIEvents) error { eventState.Lock() defer eventState.Unlock() if listenerExists(listener, &eventState.listeners) { var newListeners []chan<- *APIEvents for _, l := range eventState.listeners { if l != listener { newListeners = append(newListeners, l) } } eventState.listeners = newListeners eventState.Add(-1) } return nil } func (eventState *eventMonitoringState) closeListeners() { for _, l := range eventState.listeners { close(l) eventState.Add(-1) } eventState.listeners = nil } func (eventState *eventMonitoringState) listernersCount() int { eventState.RLock() defer eventState.RUnlock() return len(eventState.listeners) } func listenerExists(a chan<- *APIEvents, list *[]chan<- *APIEvents) bool { for _, b := range *list { if b == a { return true } } return false } func (eventState *eventMonitoringState) enableEventMonitoring(c *Client, opts EventsOptions) error { eventState.Lock() defer eventState.Unlock() if !eventState.enabled { eventState.enabled = true atomic.StoreInt64(&eventState.lastSeen, 0) eventState.C = make(chan *APIEvents, 100) eventState.errC = make(chan error, 1) go eventState.monitorEvents(c, opts) } return nil } func (eventState *eventMonitoringState) disableEventMonitoring() { eventState.Lock() defer eventState.Unlock() eventState.closeListeners() eventState.Wait() if eventState.enabled { eventState.enabled = false close(eventState.C) close(eventState.errC) if eventState.closeConn != nil { eventState.closeConn() eventState.closeConn = nil } } } func (eventState *eventMonitoringState) monitorEvents(c *Client, opts EventsOptions) { const ( noListenersTimeout = 5 * time.Second noListenersInterval = 10 * time.Millisecond noListenersMaxTries = noListenersTimeout / noListenersInterval ) var err error for i := time.Duration(0); i < noListenersMaxTries && eventState.noListeners(); i++ { time.Sleep(10 * time.Millisecond) } if eventState.noListeners() { // terminate if no listener is available after 5 seconds. // Prevents goroutine leak when RemoveEventListener is called // right after AddEventListener. eventState.disableEventMonitoring() return } if err = eventState.connectWithRetry(c, opts); err != nil { // terminate if connect failed eventState.disableEventMonitoring() return } for eventState.isEnabled() { timeout := time.After(100 * time.Millisecond) select { case ev, ok := <-eventState.C: if !ok { return } if ev == EOFEvent { eventState.disableEventMonitoring() return } eventState.updateLastSeen(ev) eventState.sendEvent(ev) case err = <-eventState.errC: if errors.Is(err, ErrNoListeners) { eventState.disableEventMonitoring() return } else if err != nil { defer func() { go eventState.monitorEvents(c, opts) }() return } case <-timeout: continue } } } func (eventState *eventMonitoringState) connectWithRetry(c *Client, opts EventsOptions) error { var retries int eventState.RLock() eventChan := eventState.C errChan := eventState.errC eventState.RUnlock() closeConn, err := c.eventHijack(opts, atomic.LoadInt64(&eventState.lastSeen), eventChan, errChan) for ; err != nil && retries < maxMonitorConnRetries; retries++ { waitTime := int64(retryInitialWaitTime * math.Pow(2, float64(retries))) time.Sleep(time.Duration(waitTime) * time.Millisecond) eventState.RLock() eventChan = eventState.C errChan = eventState.errC eventState.RUnlock() closeConn, err = c.eventHijack(opts, atomic.LoadInt64(&eventState.lastSeen), eventChan, errChan) } eventState.Lock() defer eventState.Unlock() eventState.closeConn = closeConn return err } func (eventState *eventMonitoringState) noListeners() bool { eventState.RLock() defer eventState.RUnlock() return len(eventState.listeners) == 0 } func (eventState *eventMonitoringState) isEnabled() bool { eventState.RLock() defer eventState.RUnlock() return eventState.enabled } func (eventState *eventMonitoringState) sendEvent(event *APIEvents) { eventState.RLock() defer eventState.RUnlock() eventState.Add(1) defer eventState.Done() if eventState.enabled { if len(eventState.listeners) == 0 { eventState.errC <- ErrNoListeners return } for _, listener := range eventState.listeners { select { case listener <- event: default: } } } } func (eventState *eventMonitoringState) updateLastSeen(e *APIEvents) { eventState.Lock() defer eventState.Unlock() if atomic.LoadInt64(&eventState.lastSeen) < e.Time { atomic.StoreInt64(&eventState.lastSeen, e.Time) } } func (c *Client) eventHijack(opts EventsOptions, startTime int64, eventChan chan *APIEvents, errChan chan error) (closeConn func(), err error) { // on reconnect override initial Since with last event seen time if startTime != 0 { opts.Since = strconv.FormatInt(startTime, 10) } uri := "/events?" + queryString(opts) protocol := c.endpointURL.Scheme address := c.endpointURL.Path if protocol != "unix" && protocol != "npipe" { protocol = "tcp" address = c.endpointURL.Host } var dial net.Conn if c.TLSConfig == nil { dial, err = c.Dialer.Dial(protocol, address) } else { netDialer, ok := c.Dialer.(*net.Dialer) if !ok { return nil, ErrTLSNotSupported } dial, err = tlsDialWithDialer(netDialer, protocol, address, c.TLSConfig) } if err != nil { return nil, err } //lint:ignore SA1019 the alternative doesn't quite work, so keep using the deprecated thing. conn := httputil.NewClientConn(dial, nil) req, err := http.NewRequest(http.MethodGet, uri, nil) if err != nil { return nil, err } res, err := conn.Do(req) if err != nil { return nil, err } keepRunning := int32(1) //lint:ignore SA1019 the alternative doesn't quite work, so keep using the deprecated thing. go func(res *http.Response, conn *httputil.ClientConn) { defer conn.Close() defer res.Body.Close() decoder := json.NewDecoder(res.Body) for atomic.LoadInt32(&keepRunning) == 1 { var event APIEvents if err := decoder.Decode(&event); err != nil { if errors.Is(err, io.EOF) || errors.Is(err, io.ErrUnexpectedEOF) { c.eventMonitor.RLock() if c.eventMonitor.enabled && c.eventMonitor.C == eventChan { // Signal that we're exiting. eventChan <- EOFEvent } c.eventMonitor.RUnlock() break } errChan <- err } if event.Time == 0 { continue } transformEvent(&event) c.eventMonitor.RLock() if c.eventMonitor.enabled && c.eventMonitor.C == eventChan { eventChan <- &event } c.eventMonitor.RUnlock() } }(res, conn) return func() { atomic.StoreInt32(&keepRunning, 0) }, nil } // transformEvent takes an event and determines what version it is from // then populates both versions of the event func transformEvent(event *APIEvents) { // if event version is <= 1.21 there will be no Action and no Type if event.Action == "" && event.Type == "" { event.Action = event.Status event.Actor.ID = event.ID event.Actor.Attributes = map[string]string{} switch event.Status { case "delete", "import", "pull", "push", "tag", "untag": event.Type = "image" default: event.Type = "container" if event.From != "" { event.Actor.Attributes["image"] = event.From } } } else { if event.Status == "" { if event.Type == "image" || event.Type == "container" { event.Status = event.Action } else { // Because just the Status has been overloaded with different Types // if an event is not for an image or a container, we prepend the type // to avoid problems for people relying on actions being only for // images and containers event.Status = event.Type + ":" + event.Action } } if event.ID == "" { event.ID = event.Actor.ID } if event.From == "" { event.From = event.Actor.Attributes["image"] } } } go-dockerclient-1.12.0/event_test.go000066400000000000000000000203601465717111200173610ustar00rootroot00000000000000// Copyright 2014 go-dockerclient authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package docker import ( "bufio" "crypto/tls" "crypto/x509" "net/http" "net/http/httptest" "os" "strings" "testing" "time" "github.com/google/go-cmp/cmp" ) func TestEventListeners(t *testing.T) { t.Parallel() testEventListeners("TestEventListeners", t, httptest.NewServer, NewClient) } func TestTLSEventListeners(t *testing.T) { t.Parallel() testEventListeners("TestTLSEventListeners", t, func(handler http.Handler) *httptest.Server { server := httptest.NewUnstartedServer(handler) cert, err := tls.LoadX509KeyPair("testing/data/server.pem", "testing/data/serverkey.pem") if err != nil { t.Fatalf("Error loading server key pair: %s", err) } caCert, err := os.ReadFile("testing/data/ca.pem") if err != nil { t.Fatalf("Error loading ca certificate: %s", err) } caPool := x509.NewCertPool() if !caPool.AppendCertsFromPEM(caCert) { t.Fatalf("Could not add ca certificate") } server.TLS = &tls.Config{ Certificates: []tls.Certificate{cert}, RootCAs: caPool, } server.StartTLS() return server }, func(url string) (*Client, error) { return NewTLSClient(url, "testing/data/cert.pem", "testing/data/key.pem", "testing/data/ca.pem") }) } func testEventListeners(testName string, t *testing.T, buildServer func(http.Handler) *httptest.Server, buildClient func(string) (*Client, error)) { response := `{"action":"pull","type":"image","actor":{"id":"busybox:latest","attributes":{}},"time":1442421700,"timeNano":1442421700598988358} {"action":"create","type":"container","actor":{"id":"5745704abe9caa5","attributes":{"image":"busybox"}},"time":1442421716,"timeNano":1442421716853979870} {"action":"attach","type":"container","actor":{"id":"5745704abe9caa5","attributes":{"image":"busybox"}},"time":1442421716,"timeNano":1442421716894759198} {"action":"start","type":"container","actor":{"id":"5745704abe9caa5","attributes":{"image":"busybox"}},"time":1442421716,"timeNano":1442421716983607193} {"status":"create","id":"dfdf82bd3881","from":"base:latest","time":1374067924} {"status":"start","id":"dfdf82bd3881","from":"base:latest","time":1374067924} {"status":"stop","id":"dfdf82bd3881","from":"base:latest","time":1374067966} {"status":"destroy","id":"dfdf82bd3881","from":"base:latest","time":1374067970} {"Action":"create","Actor":{"Attributes":{"HAProxyMode":"http","HealthCheck":"HttpGet","HealthCheckArgs":"http://127.0.0.1:39051/status/check","ServicePort_8080":"17801","image":"datanerd.us/siteeng/sample-app-go:latest","name":"sample-app-client-go-69818c1223ddb5"},"ID":"a925eaf4084d5c3bcf337b2abb05f566ebb94276dff34f6effb00d8ecd380e16"},"Type":"container","from":"datanerd.us/siteeng/sample-app-go:latest","id":"a925eaf4084d5c3bcf337b2abb05f566ebb94276dff34f6effb00d8ecd380e16","status":"create","time":1459133932,"timeNano":1459133932961735842}` server := buildServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { rsc := bufio.NewScanner(strings.NewReader(response)) for rsc.Scan() { w.Write(rsc.Bytes()) w.(http.Flusher).Flush() time.Sleep(10 * time.Millisecond) } })) defer server.Close() wantedEvents := []APIEvents{ { Action: "pull", Type: "image", Actor: APIActor{ ID: "busybox:latest", Attributes: map[string]string{}, }, Status: "pull", ID: "busybox:latest", Time: 1442421700, TimeNano: 1442421700598988358, }, { Action: "create", Type: "container", Actor: APIActor{ ID: "5745704abe9caa5", Attributes: map[string]string{ "image": "busybox", }, }, Status: "create", ID: "5745704abe9caa5", From: "busybox", Time: 1442421716, TimeNano: 1442421716853979870, }, { Action: "attach", Type: "container", Actor: APIActor{ ID: "5745704abe9caa5", Attributes: map[string]string{ "image": "busybox", }, }, Status: "attach", ID: "5745704abe9caa5", From: "busybox", Time: 1442421716, TimeNano: 1442421716894759198, }, { Action: "start", Type: "container", Actor: APIActor{ ID: "5745704abe9caa5", Attributes: map[string]string{ "image": "busybox", }, }, Status: "start", ID: "5745704abe9caa5", From: "busybox", Time: 1442421716, TimeNano: 1442421716983607193, }, { Action: "create", Type: "container", Actor: APIActor{ ID: "dfdf82bd3881", Attributes: map[string]string{ "image": "base:latest", }, }, Status: "create", ID: "dfdf82bd3881", From: "base:latest", Time: 1374067924, }, { Action: "start", Type: "container", Actor: APIActor{ ID: "dfdf82bd3881", Attributes: map[string]string{ "image": "base:latest", }, }, Status: "start", ID: "dfdf82bd3881", From: "base:latest", Time: 1374067924, }, { Action: "stop", Type: "container", Actor: APIActor{ ID: "dfdf82bd3881", Attributes: map[string]string{ "image": "base:latest", }, }, Status: "stop", ID: "dfdf82bd3881", From: "base:latest", Time: 1374067966, }, { Action: "destroy", Type: "container", Actor: APIActor{ ID: "dfdf82bd3881", Attributes: map[string]string{ "image": "base:latest", }, }, Status: "destroy", ID: "dfdf82bd3881", From: "base:latest", Time: 1374067970, }, { Action: "create", Type: "container", Status: "create", From: "datanerd.us/siteeng/sample-app-go:latest", ID: "a925eaf4084d5c3bcf337b2abb05f566ebb94276dff34f6effb00d8ecd380e16", Time: 1459133932, TimeNano: 1459133932961735842, Actor: APIActor{ ID: "a925eaf4084d5c3bcf337b2abb05f566ebb94276dff34f6effb00d8ecd380e16", Attributes: map[string]string{ "HAProxyMode": "http", "HealthCheck": "HttpGet", "HealthCheckArgs": "http://127.0.0.1:39051/status/check", "ServicePort_8080": "17801", "image": "datanerd.us/siteeng/sample-app-go:latest", "name": "sample-app-client-go-69818c1223ddb5", }, }, }, } client, err := buildClient(server.URL) if err != nil { t.Errorf("Failed to create client: %s", err) } client.SkipServerVersionCheck = true listener := make(chan *APIEvents, len(wantedEvents)+1) defer func() { if err = client.RemoveEventListener(listener); err != nil { t.Error(err) } }() filters := map[string][]string{ "type": {"container"}, "event": {"create", "destroy", "start", "stop", "pull", "attach"}, } opts := EventsOptions{Since: "1374067970", Until: "1442421700", Filters: filters} err = client.AddEventListenerWithOptions(opts, listener) if err != nil { t.Errorf("Failed to add event listener: %s", err) } timeout := time.After(5 * time.Second) events := make([]APIEvents, 0, len(wantedEvents)) loop: for i := range wantedEvents { select { case msg, ok := <-listener: if !ok { break loop } events = append(events, *msg) case <-timeout: t.Fatalf("%s: timed out waiting on events after %d events", testName, i) } } cmpr := cmp.Comparer(func(e1, e2 APIEvents) bool { return e1.Action == e2.Action && e1.Actor.ID == e2.Actor.ID }) if dff := cmp.Diff(events, wantedEvents, cmpr); dff != "" { t.Errorf("wrong events:\n%s", dff) } } func TestEventListenerReAdding(t *testing.T) { t.Parallel() endChan := make(chan bool) server := httptest.NewServer(http.HandlerFunc(func(http.ResponseWriter, *http.Request) { <-endChan })) client, err := NewClient(server.URL) if err != nil { t.Errorf("Failed to create client: %s", err) } listener := make(chan *APIEvents, 10) if err := client.AddEventListener(listener); err != nil { t.Errorf("Failed to add event listener: %s", err) } // Make sure eventHijack() is started with the current eventMonitoringState. time.Sleep(10 * time.Millisecond) if err := client.RemoveEventListener(listener); err != nil { t.Errorf("Failed to remove event listener: %s", err) } if err := client.AddEventListener(listener); err != nil { t.Errorf("Failed to add event listener: %s", err) } endChan <- true // Give the goroutine of the first eventHijack() time to handle the EOF. time.Sleep(10 * time.Millisecond) } go-dockerclient-1.12.0/example_test.go000066400000000000000000000052531465717111200176770ustar00rootroot00000000000000// Copyright 2013 go-dockerclient authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package docker_test import ( "archive/tar" "bytes" "fmt" "log" "time" docker "github.com/fsouza/go-dockerclient" ) func ExampleClient_AttachToContainer() { client, err := docker.NewClient("http://localhost:4243") if err != nil { log.Fatal(err) } client.SkipServerVersionCheck = true // Reading logs from container a84849 and sending them to buf. var buf bytes.Buffer err = client.AttachToContainer(docker.AttachToContainerOptions{ Container: "a84849", OutputStream: &buf, Logs: true, Stdout: true, Stderr: true, }) if err != nil { log.Fatal(err) } log.Println(buf.String()) buf.Reset() err = client.AttachToContainer(docker.AttachToContainerOptions{ Container: "a84849", OutputStream: &buf, Stdout: true, Stream: true, }) if err != nil { log.Fatal(err) } log.Println(buf.String()) } func ExampleClient_BuildImage() { client, err := docker.NewClient("http://localhost:4243") if err != nil { log.Fatal(err) } t := time.Now() inputbuf, outputbuf := bytes.NewBuffer(nil), bytes.NewBuffer(nil) tr := tar.NewWriter(inputbuf) tr.WriteHeader(&tar.Header{Name: "Dockerfile", Size: 10, ModTime: t, AccessTime: t, ChangeTime: t}) tr.Write([]byte("FROM base\n")) tr.Close() opts := docker.BuildImageOptions{ Name: "test", InputStream: inputbuf, OutputStream: outputbuf, } if err := client.BuildImage(opts); err != nil { log.Fatal(err) } } func ExampleClient_AddEventListener() { client, err := docker.NewClient("http://localhost:4243") if err != nil { log.Fatal(err) } listener := make(chan *docker.APIEvents) err = client.AddEventListener(listener) if err != nil { log.Fatal(err) } defer func() { err = client.RemoveEventListener(listener) if err != nil { log.Fatal(err) } }() timeout := time.After(1 * time.Second) for { select { case msg := <-listener: log.Println(msg) case <-timeout: return } } } func ExampleEnv_Map() { e := docker.Env([]string{"A=1", "B=2", "C=3"}) envs := e.Map() for k, v := range envs { fmt.Printf("%s=%q\n", k, v) } } func ExampleEnv_SetJSON() { type Person struct { Name string Age int } p := Person{Name: "Gopher", Age: 4} var e docker.Env err := e.SetJSON("person", p) if err != nil { log.Fatal(err) } } func ExampleEnv_GetJSON() { type Person struct { Name string Age int } p := Person{Name: "Gopher", Age: 4} var e docker.Env e.Set("person", `{"name":"Gopher","age":4}`) err := e.GetJSON("person", &p) if err != nil { log.Fatal(err) } } go-dockerclient-1.12.0/exec.go000066400000000000000000000210131465717111200161210ustar00rootroot00000000000000// Copyright 2014 go-dockerclient authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package docker import ( "context" "encoding/json" "errors" "fmt" "io" "net/http" "net/url" "strconv" ) // Exec is the type representing a `docker exec` instance and containing the // instance ID type Exec struct { ID string `json:"Id,omitempty" yaml:"Id,omitempty"` } // CreateExecOptions specify parameters to the CreateExecContainer function. // // See https://goo.gl/60TeBP for more details type CreateExecOptions struct { Env []string `json:"Env,omitempty" yaml:"Env,omitempty" toml:"Env,omitempty"` Cmd []string `json:"Cmd,omitempty" yaml:"Cmd,omitempty" toml:"Cmd,omitempty"` Container string `json:"Container,omitempty" yaml:"Container,omitempty" toml:"Container,omitempty"` User string `json:"User,omitempty" yaml:"User,omitempty" toml:"User,omitempty"` WorkingDir string `json:"WorkingDir,omitempty" yaml:"WorkingDir,omitempty" toml:"WorkingDir,omitempty"` DetachKeys string `json:"DetachKeys,omitempty" yaml:"DetachKeys,omitempty" toml:"DetachKeys,omitempty"` Context context.Context `json:"-"` AttachStdin bool `json:"AttachStdin,omitempty" yaml:"AttachStdin,omitempty" toml:"AttachStdin,omitempty"` AttachStdout bool `json:"AttachStdout,omitempty" yaml:"AttachStdout,omitempty" toml:"AttachStdout,omitempty"` AttachStderr bool `json:"AttachStderr,omitempty" yaml:"AttachStderr,omitempty" toml:"AttachStderr,omitempty"` Tty bool `json:"Tty,omitempty" yaml:"Tty,omitempty" toml:"Tty,omitempty"` Privileged bool `json:"Privileged,omitempty" yaml:"Privileged,omitempty" toml:"Privileged,omitempty"` } // CreateExec sets up an exec instance in a running container `id`, returning the exec // instance, or an error in case of failure. // // See https://goo.gl/60TeBP for more details func (c *Client) CreateExec(opts CreateExecOptions) (*Exec, error) { if c.serverAPIVersion == nil { c.checkAPIVersion() } if len(opts.Env) > 0 && c.serverAPIVersion.LessThan(apiVersion125) { return nil, errors.New("exec configuration Env is only supported in API#1.25 and above") } if len(opts.WorkingDir) > 0 && c.serverAPIVersion.LessThan(apiVersion135) { return nil, errors.New("exec configuration WorkingDir is only supported in API#1.35 and above") } path := fmt.Sprintf("/containers/%s/exec", opts.Container) resp, err := c.do(http.MethodPost, path, doOptions{data: opts, context: opts.Context}) if err != nil { var e *Error if errors.As(err, &e) && e.Status == http.StatusNotFound { return nil, &NoSuchContainer{ID: opts.Container} } return nil, err } defer resp.Body.Close() var exec Exec if err := json.NewDecoder(resp.Body).Decode(&exec); err != nil { return nil, err } return &exec, nil } // StartExecOptions specify parameters to the StartExecContainer function. // // See https://goo.gl/1EeDWi for more details type StartExecOptions struct { InputStream io.Reader `qs:"-"` OutputStream io.Writer `qs:"-"` ErrorStream io.Writer `qs:"-"` Detach bool `json:"Detach,omitempty" yaml:"Detach,omitempty" toml:"Detach,omitempty"` Tty bool `json:"Tty,omitempty" yaml:"Tty,omitempty" toml:"Tty,omitempty"` // Use raw terminal? Usually true when the container contains a TTY. RawTerminal bool `qs:"-"` // If set, after a successful connect, a sentinel will be sent and then the // client will block on receive before continuing. // // It must be an unbuffered channel. Using a buffered channel can lead // to unexpected behavior. Success chan struct{} `json:"-"` Context context.Context `json:"-"` } // StartExec starts a previously set up exec instance id. If opts.Detach is // true, it returns after starting the exec command. Otherwise, it sets up an // interactive session with the exec command. // // See https://goo.gl/1EeDWi for more details func (c *Client) StartExec(id string, opts StartExecOptions) error { cw, err := c.StartExecNonBlocking(id, opts) if err != nil { return err } if cw != nil { return cw.Wait() } return nil } // StartExecNonBlocking starts a previously set up exec instance id. If opts.Detach is // true, it returns after starting the exec command. Otherwise, it sets up an // interactive session with the exec command. // // See https://goo.gl/1EeDWi for more details func (c *Client) StartExecNonBlocking(id string, opts StartExecOptions) (CloseWaiter, error) { if id == "" { return nil, &NoSuchExec{ID: id} } path := fmt.Sprintf("/exec/%s/start", id) if opts.Detach { resp, err := c.do(http.MethodPost, path, doOptions{data: opts, context: opts.Context}) if err != nil { var e *Error if errors.As(err, &e) && e.Status == http.StatusNotFound { return nil, &NoSuchExec{ID: id} } return nil, err } defer resp.Body.Close() return nil, nil } return c.hijack(http.MethodPost, path, hijackOptions{ success: opts.Success, setRawTerminal: opts.RawTerminal, in: opts.InputStream, stdout: opts.OutputStream, stderr: opts.ErrorStream, data: opts, }) } // ResizeExecTTY resizes the tty session used by the exec command id. This API // is valid only if Tty was specified as part of creating and starting the exec // command. // // See https://goo.gl/Mo5bxx for more details func (c *Client) ResizeExecTTY(id string, height, width int) error { params := make(url.Values) params.Set("h", strconv.Itoa(height)) params.Set("w", strconv.Itoa(width)) path := fmt.Sprintf("/exec/%s/resize?%s", id, params.Encode()) resp, err := c.do(http.MethodPost, path, doOptions{}) if err != nil { return err } resp.Body.Close() return nil } // ExecProcessConfig is a type describing the command associated to a Exec // instance. It's used in the ExecInspect type. type ExecProcessConfig struct { User string `json:"user,omitempty" yaml:"user,omitempty" toml:"user,omitempty"` Privileged bool `json:"privileged,omitempty" yaml:"privileged,omitempty" toml:"privileged,omitempty"` Tty bool `json:"tty,omitempty" yaml:"tty,omitempty" toml:"tty,omitempty"` EntryPoint string `json:"entrypoint,omitempty" yaml:"entrypoint,omitempty" toml:"entrypoint,omitempty"` Arguments []string `json:"arguments,omitempty" yaml:"arguments,omitempty" toml:"arguments,omitempty"` } // ExecInspect is a type with details about a exec instance, including the // exit code if the command has finished running. It's returned by a api // call to /exec/(id)/json // // See https://goo.gl/ctMUiW for more details type ExecInspect struct { ID string `json:"ID,omitempty" yaml:"ID,omitempty" toml:"ID,omitempty"` ExitCode int `json:"ExitCode,omitempty" yaml:"ExitCode,omitempty" toml:"ExitCode,omitempty"` ProcessConfig ExecProcessConfig `json:"ProcessConfig,omitempty" yaml:"ProcessConfig,omitempty" toml:"ProcessConfig,omitempty"` ContainerID string `json:"ContainerID,omitempty" yaml:"ContainerID,omitempty" toml:"ContainerID,omitempty"` DetachKeys string `json:"DetachKeys,omitempty" yaml:"DetachKeys,omitempty" toml:"DetachKeys,omitempty"` Running bool `json:"Running,omitempty" yaml:"Running,omitempty" toml:"Running,omitempty"` OpenStdin bool `json:"OpenStdin,omitempty" yaml:"OpenStdin,omitempty" toml:"OpenStdin,omitempty"` OpenStderr bool `json:"OpenStderr,omitempty" yaml:"OpenStderr,omitempty" toml:"OpenStderr,omitempty"` OpenStdout bool `json:"OpenStdout,omitempty" yaml:"OpenStdout,omitempty" toml:"OpenStdout,omitempty"` CanRemove bool `json:"CanRemove,omitempty" yaml:"CanRemove,omitempty" toml:"CanRemove,omitempty"` } // InspectExec returns low-level information about the exec command id. // // See https://goo.gl/ctMUiW for more details func (c *Client) InspectExec(id string) (*ExecInspect, error) { path := fmt.Sprintf("/exec/%s/json", id) resp, err := c.do(http.MethodGet, path, doOptions{}) if err != nil { var e *Error if errors.As(err, &e) && e.Status == http.StatusNotFound { return nil, &NoSuchExec{ID: id} } return nil, err } defer resp.Body.Close() var exec ExecInspect if err := json.NewDecoder(resp.Body).Decode(&exec); err != nil { return nil, err } return &exec, nil } // NoSuchExec is the error returned when a given exec instance does not exist. type NoSuchExec struct { ID string } func (err *NoSuchExec) Error() string { return "No such exec instance: " + err.ID } go-dockerclient-1.12.0/exec_test.go000066400000000000000000000223451465717111200171710ustar00rootroot00000000000000// Copyright 2014 go-dockerclient authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package docker import ( "bytes" "encoding/json" "net" "net/http" "net/http/httptest" "net/url" "reflect" "strings" "testing" ) func TestExecCreate(t *testing.T) { t.Parallel() jsonContainer := `{"Id": "4fa6e0f0c6786287e131c3852c58a2e01cc697a68231826813597e4994f1d6e2"}` var expected struct{ ID string } err := json.Unmarshal([]byte(jsonContainer), &expected) if err != nil { t.Fatal(err) } fakeRT := &FakeRoundTripper{message: jsonContainer, status: http.StatusOK} client := newTestClient(fakeRT) config := CreateExecOptions{ Container: "test", AttachStdin: true, AttachStdout: true, AttachStderr: false, Tty: false, Cmd: []string{"touch", "/tmp/file"}, User: "a-user", } execObj, err := client.CreateExec(config) if err != nil { t.Fatal(err) } expectedID := "4fa6e0f0c6786287e131c3852c58a2e01cc697a68231826813597e4994f1d6e2" if execObj.ID != expectedID { t.Errorf("ExecCreate: wrong ID. Want %q. Got %q.", expectedID, execObj.ID) } req := fakeRT.requests[0] if req.Method != http.MethodPost { t.Errorf("ExecCreate: wrong HTTP method. Want %q. Got %q.", http.MethodPost, req.Method) } expectedURL, _ := url.Parse(client.getURL("/containers/test/exec")) if gotPath := req.URL.Path; gotPath != expectedURL.Path { t.Errorf("ExecCreate: Wrong path in request. Want %q. Got %q.", expectedURL.Path, gotPath) } var gotBody struct{ ID string } err = json.NewDecoder(req.Body).Decode(&gotBody) if err != nil { t.Fatal(err) } } func TestExecCreateWithEnvErr(t *testing.T) { t.Parallel() jsonContainer := `{"Id": "4fa6e0f0c6786287e131c3852c58a2e01cc697a68231826813597e4994f1d6e2"}` var expected struct{ ID string } err := json.Unmarshal([]byte(jsonContainer), &expected) if err != nil { t.Fatal(err) } fakeRT := &FakeRoundTripper{message: jsonContainer, status: http.StatusOK} client := newTestClient(fakeRT) config := CreateExecOptions{ Container: "test", AttachStdin: true, AttachStdout: true, AttachStderr: false, Tty: false, Env: []string{"foo=bar"}, Cmd: []string{"touch", "/tmp/file"}, User: "a-user", } _, err = client.CreateExec(config) if err == nil || err.Error() != "exec configuration Env is only supported in API#1.25 and above" { t.Error("CreateExec: options contain Env for unsupported api version") } } func TestExecCreateWithEnv(t *testing.T) { t.Parallel() jsonContainer := `{"Id": "4fa6e0f0c6786287e131c3852c58a2e01cc697a68231826813597e4994f1d6e2"}` var expected struct{ ID string } err := json.Unmarshal([]byte(jsonContainer), &expected) if err != nil { t.Fatal(err) } fakeRT := &FakeRoundTripper{message: jsonContainer, status: http.StatusOK} endpoint := "http://localhost:4243" u, _ := parseEndpoint("http://localhost:4243", false) testAPIVersion, _ := NewAPIVersion("1.25") client := Client{ HTTPClient: &http.Client{Transport: fakeRT}, Dialer: &net.Dialer{}, endpoint: endpoint, endpointURL: u, SkipServerVersionCheck: true, serverAPIVersion: testAPIVersion, } config := CreateExecOptions{ Container: "test", AttachStdin: true, AttachStdout: true, AttachStderr: false, Tty: false, Env: []string{"foo=bar"}, Cmd: []string{"touch", "/tmp/file"}, User: "a-user", } _, err = client.CreateExec(config) if err != nil { t.Error(err) } } func TestExecCreateWithWorkingDirErr(t *testing.T) { t.Parallel() jsonContainer := `{"Id": "4fa6e0f0c6786287e131c3852c58a2e01cc697a68231826813597e4994f1d6e2"}` var expected struct{ ID string } err := json.Unmarshal([]byte(jsonContainer), &expected) if err != nil { t.Fatal(err) } fakeRT := &FakeRoundTripper{message: jsonContainer, status: http.StatusOK} client := newTestClient(fakeRT) config := CreateExecOptions{ Container: "test", AttachStdin: true, AttachStdout: true, AttachStderr: false, Tty: false, WorkingDir: "/tmp", Cmd: []string{"touch", "file"}, User: "a-user", } _, err = client.CreateExec(config) if err == nil || err.Error() != "exec configuration WorkingDir is only supported in API#1.35 and above" { t.Error("CreateExec: options contain WorkingDir for unsupported api version") } } func TestExecCreateWithWorkingDir(t *testing.T) { t.Parallel() jsonContainer := `{"Id": "4fa6e0f0c6786287e131c3852c58a2e01cc697a68231826813597e4994f1d6e2"}` var expected struct{ ID string } err := json.Unmarshal([]byte(jsonContainer), &expected) if err != nil { t.Fatal(err) } fakeRT := &FakeRoundTripper{message: jsonContainer, status: http.StatusOK} endpoint := "http://localhost:4243" u, _ := parseEndpoint("http://localhost:4243", false) testAPIVersion, _ := NewAPIVersion("1.35") client := Client{ HTTPClient: &http.Client{Transport: fakeRT}, Dialer: &net.Dialer{}, endpoint: endpoint, endpointURL: u, SkipServerVersionCheck: true, serverAPIVersion: testAPIVersion, } config := CreateExecOptions{ Container: "test", AttachStdin: true, AttachStdout: true, AttachStderr: false, Tty: false, WorkingDir: "/tmp", Cmd: []string{"touch", "file"}, User: "a-user", } _, err = client.CreateExec(config) if err != nil { t.Error(err) } } func TestExecStartDetached(t *testing.T) { t.Parallel() execID := "4fa6e0f0c6786287e131c3852c58a2e01cc697a68231826813597e4994f1d6e2" fakeRT := &FakeRoundTripper{status: http.StatusOK} client := newTestClient(fakeRT) config := StartExecOptions{ Detach: true, } err := client.StartExec(execID, config) if err != nil { t.Fatal(err) } req := fakeRT.requests[0] if req.Method != http.MethodPost { t.Errorf("ExecStart: wrong HTTP method. Want %q. Got %q.", http.MethodPost, req.Method) } expectedURL, _ := url.Parse(client.getURL("/exec/" + execID + "/start")) if gotPath := req.URL.Path; gotPath != expectedURL.Path { t.Errorf("ExecCreate: Wrong path in request. Want %q. Got %q.", expectedURL.Path, gotPath) } t.Log(req.Body) var gotBody struct{ Detach bool } err = json.NewDecoder(req.Body).Decode(&gotBody) if err != nil { t.Fatal(err) } if !gotBody.Detach { t.Fatal("Expected Detach in StartExecOptions to be true") } } func TestExecStartAndAttach(t *testing.T) { reader := strings.NewReader("send value") server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { w.Write([]byte{1, 0, 0, 0, 0, 0, 0, 5}) w.Write([]byte("hello")) })) defer server.Close() client, _ := NewClient(server.URL) client.SkipServerVersionCheck = true var stdout, stderr bytes.Buffer success := make(chan struct{}) execID := "4fa6e0f0c6786287e131c3852c58a2e01cc697a68231826813597e4994f1d6e2" opts := StartExecOptions{ OutputStream: &stdout, ErrorStream: &stderr, InputStream: reader, RawTerminal: true, Success: success, } go func() { if err := client.StartExec(execID, opts); err != nil { t.Error(err) } }() <-success } func TestExecResize(t *testing.T) { t.Parallel() execID := "4fa6e0f0c6786287e131c3852c58a2e01cc697a68231826813597e4994f1d6e2" fakeRT := &FakeRoundTripper{status: http.StatusOK} client := newTestClient(fakeRT) err := client.ResizeExecTTY(execID, 10, 20) if err != nil { t.Fatal(err) } req := fakeRT.requests[0] if req.Method != http.MethodPost { t.Errorf("ExecStart: wrong HTTP method. Want %q. Got %q.", http.MethodPost, req.Method) } expectedURL, _ := url.Parse(client.getURL("/exec/" + execID + "/resize?h=10&w=20")) if gotPath := req.URL.RequestURI(); gotPath != expectedURL.RequestURI() { t.Errorf("ExecCreate: Wrong path in request. Want %q. Got %q.", expectedURL.Path, gotPath) } } func TestExecInspect(t *testing.T) { t.Parallel() jsonExec := `{ "CanRemove": false, "ContainerID": "b53ee82b53a40c7dca428523e34f741f3abc51d9f297a14ff874bf761b995126", "DetachKeys": "", "ExitCode": 2, "ID": "f33bbfb39f5b142420f4759b2348913bd4a8d1a6d7fd56499cb41a1bb91d7b3b", "OpenStderr": true, "OpenStdin": true, "OpenStdout": true, "ProcessConfig": { "arguments": [ "-c", "exit 2" ], "entrypoint": "sh", "privileged": false, "tty": true, "user": "1000" }, "Running": false }` var expected ExecInspect err := json.Unmarshal([]byte(jsonExec), &expected) if err != nil { t.Fatal(err) } fakeRT := &FakeRoundTripper{message: jsonExec, status: http.StatusOK} client := newTestClient(fakeRT) expectedID := "b53ee82b53a40c7dca428523e34f741f3abc51d9f297a14ff874bf761b995126" execObj, err := client.InspectExec(expectedID) if err != nil { t.Fatal(err) } if !reflect.DeepEqual(*execObj, expected) { t.Errorf("ExecInspect: Expected %#v. Got %#v.", expected, *execObj) } req := fakeRT.requests[0] if req.Method != http.MethodGet { t.Errorf("ExecInspect: wrong HTTP method. Want %q. Got %q.", http.MethodGet, req.Method) } expectedURL, _ := url.Parse(client.getURL("/exec/" + expectedID + "/json")) if gotPath := fakeRT.requests[0].URL.Path; gotPath != expectedURL.Path { t.Errorf("ExecInspect: Wrong path in request. Want %q. Got %q.", expectedURL.Path, gotPath) } } go-dockerclient-1.12.0/go.mod000066400000000000000000000025071465717111200157630ustar00rootroot00000000000000module github.com/fsouza/go-dockerclient go 1.22 require ( github.com/Microsoft/go-winio v0.6.2 github.com/docker/docker v27.1.2+incompatible github.com/docker/go-units v0.5.0 github.com/google/go-cmp v0.6.0 github.com/gorilla/mux v1.8.1 github.com/moby/patternmatcher v0.6.0 golang.org/x/term v0.23.0 ) require ( github.com/AdaLogics/go-fuzz-headers v0.0.0-20210715213245-6c3934b029d8 // indirect github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect github.com/containerd/log v0.1.0 // indirect github.com/docker/go-connections v0.4.0 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/klauspost/compress v1.15.9 // indirect github.com/moby/docker-image-spec v1.3.1 // indirect github.com/moby/sys/sequential v0.5.0 // indirect github.com/moby/sys/user v0.1.0 // indirect github.com/moby/sys/userns v0.1.0 // indirect github.com/moby/term v0.0.0-20210619224110-3f7ff695adc6 // indirect github.com/morikuni/aec v1.0.0 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.1.0-rc2.0.20221005185240-3a7f492d3f1b // indirect github.com/pkg/errors v0.9.1 // indirect github.com/sirupsen/logrus v1.9.3 // indirect github.com/stretchr/testify v1.8.4 // indirect golang.org/x/sys v0.23.0 // indirect gotest.tools/v3 v3.5.0 // indirect ) go-dockerclient-1.12.0/go.sum000066400000000000000000000227041465717111200160110ustar00rootroot00000000000000github.com/AdaLogics/go-fuzz-headers v0.0.0-20210715213245-6c3934b029d8 h1:V8krnnfGj4pV65YLUm3C0/8bl7V5Nry2Pwvy3ru/wLc= github.com/AdaLogics/go-fuzz-headers v0.0.0-20210715213245-6c3934b029d8/go.mod h1:CzsSbkDixRphAF5hS6wbMKq0eI6ccJRb7/A0M6JBnwg= github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8= github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= github.com/creack/pty v1.1.11 h1:07n33Z8lZxZ2qwegKbObQohDhXDQxiMMz1NOUGYlesw= github.com/creack/pty v1.1.11/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/docker/docker v27.1.2+incompatible h1:AhGzR1xaQIy53qCkxARaFluI00WPGtXn0AJuoQsVYTY= github.com/docker/docker v27.1.2+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ= github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/klauspost/compress v1.15.9 h1:wKRjX6JRtDdrE9qwa4b/Cip7ACOshUI4smpCQanqjSY= github.com/klauspost/compress v1.15.9/go.mod h1:PhcZ0MbTNciWF3rruxRgKxI5NkcHHrHUDtV4Yw2GlzU= github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0= github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo= github.com/moby/patternmatcher v0.6.0 h1:GmP9lR19aU5GqSSFko+5pRqHi+Ohk1O69aFiKkVGiPk= github.com/moby/patternmatcher v0.6.0/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc= github.com/moby/sys/sequential v0.5.0 h1:OPvI35Lzn9K04PBbCLW0g4LcFAJgHsvXsRyewg5lXtc= github.com/moby/sys/sequential v0.5.0/go.mod h1:tH2cOOs5V9MlPiXcQzRC+eEyab644PWKGRYaaV5ZZlo= github.com/moby/sys/user v0.1.0 h1:WmZ93f5Ux6het5iituh9x2zAG7NFY9Aqi49jjE1PaQg= github.com/moby/sys/user v0.1.0/go.mod h1:fKJhFOnsCN6xZ5gSfbM6zaHGgDJMrqt9/reuj4T7MmU= github.com/moby/sys/userns v0.1.0 h1:tVLXkFOxVu9A64/yh59slHVv9ahO9UIev4JZusOLG/g= github.com/moby/sys/userns v0.1.0/go.mod h1:IHUYgu/kao6N8YZlp9Cf444ySSvCmDlmzUcYfDHOl28= github.com/moby/term v0.0.0-20210619224110-3f7ff695adc6 h1:dcztxKSvZ4Id8iPpHERQBbIJfabdt4wUm5qy3wOL2Zc= github.com/moby/term v0.0.0-20210619224110-3f7ff695adc6/go.mod h1:E2VnQOmVuvZB6UYnnDB0qG5Nq/1tD9acaOpo6xmt0Kw= github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opencontainers/image-spec v1.1.0-rc2.0.20221005185240-3a7f492d3f1b h1:YWuSjZCQAPM8UUBLkYUk1e+rZcvWHJmFb6i6rM44Xs8= github.com/opencontainers/image-spec v1.1.0-rc2.0.20221005185240-3a7f492d3f1b/go.mod h1:3OVijpioIKYWTqjiG0zfF6wvoJ4fAXGbjdZuI2NgsRQ= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.23.0 h1:YfKFowiIMvtgl1UERQoTPPToxltDeZfbj4H7dVUCwmM= golang.org/x/sys v0.23.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.23.0 h1:F6D4vR+EHoL9/sWAWgAR1H2DcHr4PareCbAaCo1RpuU= golang.org/x/term v0.23.0/go.mod h1:DgV24QBUrK6jhZXl+20l6UWznPlwAHm1Q1mGHtydmSk= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190624222133-a101b041ded4/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk= gotest.tools/v3 v3.5.0 h1:Ljk6PdHdOhAb5aDMWXjDLMMhph+BpztA4v1QdqEW2eY= gotest.tools/v3 v3.5.0/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU= go-dockerclient-1.12.0/helpers_test.go000066400000000000000000000030631465717111200177030ustar00rootroot00000000000000// Copyright 2020 go-dockerclient authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package docker import ( "errors" "testing" ) func expectNoSuchContainer(t *testing.T, id string, err error) { t.Helper() var containerErr *NoSuchContainer if !errors.As(err, &containerErr) { t.Fatalf("Container: Wrong error information. Want %#v. Got %#v.", containerErr, err) } if containerErr.ID != id { t.Errorf("Container: wrong container in error\nWant %q\ngot %q", id, containerErr.ID) } } func expectNoSuchNode(t *testing.T, nodeID string, err error) { t.Helper() var nodeErr *NoSuchNode if !errors.As(err, &nodeErr) { t.Fatalf("Node: Wrong error information. Want %#v. Got %#v.", nodeErr, err) } if nodeErr.ID != nodeID { t.Errorf("Node: wrong node in error\nWant %q\ngot %q", nodeID, nodeErr.ID) } } func expectNoSuchSecret(t *testing.T, secretID string, err error) { t.Helper() var nodeErr *NoSuchSecret if !errors.As(err, &nodeErr) { t.Fatalf("Secret: Wrong error information. Want %#v. Got %#v.", nodeErr, err) } if nodeErr.ID != secretID { t.Errorf("Secret: wrong secret in error\nWant %q\ngot %q", secretID, nodeErr.ID) } } func expectNoSuchConfig(t *testing.T, secretID string, err error) { t.Helper() var nodeErr *NoSuchConfig if !errors.As(err, &nodeErr) { t.Fatalf("Config: Wrong error information. Want %#v. Got %#v.", nodeErr, err) } if nodeErr.ID != secretID { t.Errorf("Config: wrong secret in error\nWant %q\ngot %q", secretID, nodeErr.ID) } } go-dockerclient-1.12.0/image.go000066400000000000000000000607501465717111200162720ustar00rootroot00000000000000// Copyright 2013 go-dockerclient authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package docker import ( "context" "encoding/base64" "encoding/json" "errors" "fmt" "io" "net/http" "net/url" "os" "strings" "time" ) // APIImages represent an image returned in the ListImages call. type APIImages struct { ID string `json:"Id" yaml:"Id" toml:"Id"` RepoTags []string `json:"RepoTags,omitempty" yaml:"RepoTags,omitempty" toml:"RepoTags,omitempty"` Created int64 `json:"Created,omitempty" yaml:"Created,omitempty" toml:"Created,omitempty"` Size int64 `json:"Size,omitempty" yaml:"Size,omitempty" toml:"Size,omitempty"` VirtualSize int64 `json:"VirtualSize,omitempty" yaml:"VirtualSize,omitempty" toml:"VirtualSize,omitempty"` ParentID string `json:"ParentId,omitempty" yaml:"ParentId,omitempty" toml:"ParentId,omitempty"` RepoDigests []string `json:"RepoDigests,omitempty" yaml:"RepoDigests,omitempty" toml:"RepoDigests,omitempty"` Labels map[string]string `json:"Labels,omitempty" yaml:"Labels,omitempty" toml:"Labels,omitempty"` } // RootFS represents the underlying layers used by an image type RootFS struct { Type string `json:"Type,omitempty" yaml:"Type,omitempty" toml:"Type,omitempty"` Layers []string `json:"Layers,omitempty" yaml:"Layers,omitempty" toml:"Layers,omitempty"` } // Image is the type representing a docker image and its various properties type Image struct { ID string `json:"Id" yaml:"Id" toml:"Id"` RepoTags []string `json:"RepoTags,omitempty" yaml:"RepoTags,omitempty" toml:"RepoTags,omitempty"` Parent string `json:"Parent,omitempty" yaml:"Parent,omitempty" toml:"Parent,omitempty"` Comment string `json:"Comment,omitempty" yaml:"Comment,omitempty" toml:"Comment,omitempty"` Created time.Time `json:"Created,omitempty" yaml:"Created,omitempty" toml:"Created,omitempty"` Container string `json:"Container,omitempty" yaml:"Container,omitempty" toml:"Container,omitempty"` ContainerConfig Config `json:"ContainerConfig,omitempty" yaml:"ContainerConfig,omitempty" toml:"ContainerConfig,omitempty"` DockerVersion string `json:"DockerVersion,omitempty" yaml:"DockerVersion,omitempty" toml:"DockerVersion,omitempty"` Author string `json:"Author,omitempty" yaml:"Author,omitempty" toml:"Author,omitempty"` Config *Config `json:"Config,omitempty" yaml:"Config,omitempty" toml:"Config,omitempty"` Architecture string `json:"Architecture,omitempty" yaml:"Architecture,omitempty"` Size int64 `json:"Size,omitempty" yaml:"Size,omitempty" toml:"Size,omitempty"` VirtualSize int64 `json:"VirtualSize,omitempty" yaml:"VirtualSize,omitempty" toml:"VirtualSize,omitempty"` RepoDigests []string `json:"RepoDigests,omitempty" yaml:"RepoDigests,omitempty" toml:"RepoDigests,omitempty"` RootFS *RootFS `json:"RootFS,omitempty" yaml:"RootFS,omitempty" toml:"RootFS,omitempty"` OS string `json:"Os,omitempty" yaml:"Os,omitempty" toml:"Os,omitempty"` } // ImagePre012 serves the same purpose as the Image type except that it is for // earlier versions of the Docker API (pre-012 to be specific) type ImagePre012 struct { ID string `json:"id"` Parent string `json:"parent,omitempty"` Comment string `json:"comment,omitempty"` Created time.Time `json:"created"` Container string `json:"container,omitempty"` ContainerConfig Config `json:"container_config,omitempty"` DockerVersion string `json:"docker_version,omitempty"` Author string `json:"author,omitempty"` Config *Config `json:"config,omitempty"` Architecture string `json:"architecture,omitempty"` Size int64 `json:"size,omitempty"` } var ( // ErrNoSuchImage is the error returned when the image does not exist. ErrNoSuchImage = errors.New("no such image") // ErrMissingRepo is the error returned when the remote repository is // missing. ErrMissingRepo = errors.New("missing remote repository e.g. 'github.com/user/repo'") // ErrMissingOutputStream is the error returned when no output stream // is provided to some calls, like BuildImage. ErrMissingOutputStream = errors.New("missing output stream") // ErrMultipleContexts is the error returned when both a ContextDir and // InputStream are provided in BuildImageOptions ErrMultipleContexts = errors.New("image build may not be provided BOTH context dir and input stream") // ErrMustSpecifyNames is the error returned when the Names field on // ExportImagesOptions is nil or empty ErrMustSpecifyNames = errors.New("must specify at least one name to export") ) // ListImagesOptions specify parameters to the ListImages function. // // See https://goo.gl/BVzauZ for more details. type ListImagesOptions struct { Filters map[string][]string All bool Digests bool Filter string Context context.Context } // ListImages returns the list of available images in the server. // // See https://goo.gl/BVzauZ for more details. func (c *Client) ListImages(opts ListImagesOptions) ([]APIImages, error) { path := "/images/json?" + queryString(opts) resp, err := c.do(http.MethodGet, path, doOptions{context: opts.Context}) if err != nil { return nil, err } defer resp.Body.Close() var images []APIImages if err := json.NewDecoder(resp.Body).Decode(&images); err != nil { return nil, err } return images, nil } // ImageHistory represent a layer in an image's history returned by the // ImageHistory call. type ImageHistory struct { ID string `json:"Id" yaml:"Id" toml:"Id"` Tags []string `json:"Tags,omitempty" yaml:"Tags,omitempty" toml:"Tags,omitempty"` Created int64 `json:"Created,omitempty" yaml:"Created,omitempty" toml:"Tags,omitempty"` CreatedBy string `json:"CreatedBy,omitempty" yaml:"CreatedBy,omitempty" toml:"CreatedBy,omitempty"` Size int64 `json:"Size,omitempty" yaml:"Size,omitempty" toml:"Size,omitempty"` Comment string `json:"Comment,omitempty" yaml:"Comment,omitempty" toml:"Comment,omitempty"` } // ImageHistory returns the history of the image by its name or ID. // // See https://goo.gl/fYtxQa for more details. func (c *Client) ImageHistory(name string) ([]ImageHistory, error) { resp, err := c.do(http.MethodGet, "/images/"+name+"/history", doOptions{}) if err != nil { var e *Error if errors.As(err, &e) && e.Status == http.StatusNotFound { return nil, ErrNoSuchImage } return nil, err } defer resp.Body.Close() var history []ImageHistory if err := json.NewDecoder(resp.Body).Decode(&history); err != nil { return nil, err } return history, nil } // RemoveImage removes an image by its name or ID. // // See https://goo.gl/Vd2Pck for more details. func (c *Client) RemoveImage(name string) error { resp, err := c.do(http.MethodDelete, "/images/"+name, doOptions{}) if err != nil { var e *Error if errors.As(err, &e) && e.Status == http.StatusNotFound { return ErrNoSuchImage } return err } resp.Body.Close() return nil } // RemoveImageOptions present the set of options available for removing an image // from a registry. // // See https://goo.gl/Vd2Pck for more details. type RemoveImageOptions struct { Force bool `qs:"force"` NoPrune bool `qs:"noprune"` Context context.Context } // RemoveImageExtended removes an image by its name or ID. // Extra params can be passed, see RemoveImageOptions // // See https://goo.gl/Vd2Pck for more details. func (c *Client) RemoveImageExtended(name string, opts RemoveImageOptions) error { uri := fmt.Sprintf("/images/%s?%s", name, queryString(&opts)) resp, err := c.do(http.MethodDelete, uri, doOptions{context: opts.Context}) if err != nil { var e *Error if errors.As(err, &e) && e.Status == http.StatusNotFound { return ErrNoSuchImage } return err } resp.Body.Close() return nil } // InspectImage returns an image by its name or ID. // // See https://goo.gl/ncLTG8 for more details. func (c *Client) InspectImage(name string) (*Image, error) { resp, err := c.do(http.MethodGet, "/images/"+name+"/json", doOptions{}) if err != nil { var e *Error if errors.As(err, &e) && e.Status == http.StatusNotFound { return nil, ErrNoSuchImage } return nil, err } defer resp.Body.Close() var image Image // if the caller elected to skip checking the server's version, assume it's the latest if c.SkipServerVersionCheck || c.expectedAPIVersion.GreaterThanOrEqualTo(apiVersion112) { if err := json.NewDecoder(resp.Body).Decode(&image); err != nil { return nil, err } } else { var imagePre012 ImagePre012 if err := json.NewDecoder(resp.Body).Decode(&imagePre012); err != nil { return nil, err } image.ID = imagePre012.ID image.Parent = imagePre012.Parent image.Comment = imagePre012.Comment image.Created = imagePre012.Created image.Container = imagePre012.Container image.ContainerConfig = imagePre012.ContainerConfig image.DockerVersion = imagePre012.DockerVersion image.Author = imagePre012.Author image.Config = imagePre012.Config image.Architecture = imagePre012.Architecture image.Size = imagePre012.Size } return &image, nil } // PushImageOptions represents options to use in the PushImage method. // // See https://goo.gl/BZemGg for more details. type PushImageOptions struct { // Name of the image Name string // Tag of the image Tag string // Registry server to push the image Registry string OutputStream io.Writer `qs:"-"` RawJSONStream bool `qs:"-"` InactivityTimeout time.Duration `qs:"-"` Context context.Context } // PushImage pushes an image to a remote registry, logging progress to w. // // An empty instance of AuthConfiguration may be used for unauthenticated // pushes. // // See https://goo.gl/BZemGg for more details. func (c *Client) PushImage(opts PushImageOptions, auth AuthConfiguration) error { if opts.Name == "" { return ErrNoSuchImage } headers, err := headersWithAuth(auth) if err != nil { return err } name := opts.Name opts.Name = "" path := "/images/" + name + "/push?" + queryString(&opts) return c.stream(http.MethodPost, path, streamOptions{ setRawTerminal: true, rawJSONStream: opts.RawJSONStream, headers: headers, stdout: opts.OutputStream, inactivityTimeout: opts.InactivityTimeout, context: opts.Context, }) } // PullImageOptions present the set of options available for pulling an image // from a registry. // // See https://goo.gl/qkoSsn for more details. type PullImageOptions struct { All bool Repository string `qs:"fromImage"` Tag string Platform string `ver:"1.32"` // Only required for Docker Engine 1.9 or 1.10 w/ Remote API < 1.21 // and Docker Engine < 1.9 // This parameter was removed in Docker Engine 1.11 Registry string OutputStream io.Writer `qs:"-"` RawJSONStream bool `qs:"-"` InactivityTimeout time.Duration `qs:"-"` Context context.Context } // PullImage pulls an image from a remote registry, logging progress to // opts.OutputStream. // // See https://goo.gl/qkoSsn for more details. func (c *Client) PullImage(opts PullImageOptions, auth AuthConfiguration) error { if opts.Repository == "" { return ErrNoSuchImage } headers, err := headersWithAuth(auth) if err != nil { return err } if opts.Tag == "" && strings.Contains(opts.Repository, "@") { parts := strings.SplitN(opts.Repository, "@", 2) opts.Repository = parts[0] opts.Tag = parts[1] } return c.createImage(&opts, headers, nil, opts.OutputStream, opts.RawJSONStream, opts.InactivityTimeout, opts.Context) } func (c *Client) createImage(opts any, headers map[string]string, in io.Reader, w io.Writer, rawJSONStream bool, timeout time.Duration, context context.Context) error { url, err := c.getPath("/images/create", opts) if err != nil { return err } return c.streamURL(http.MethodPost, url, streamOptions{ setRawTerminal: true, headers: headers, in: in, stdout: w, rawJSONStream: rawJSONStream, inactivityTimeout: timeout, context: context, }) } // LoadImageOptions represents the options for LoadImage Docker API Call // // See https://goo.gl/rEsBV3 for more details. type LoadImageOptions struct { InputStream io.Reader OutputStream io.Writer Context context.Context } // LoadImage imports a tarball docker image // // See https://goo.gl/rEsBV3 for more details. func (c *Client) LoadImage(opts LoadImageOptions) error { return c.stream(http.MethodPost, "/images/load", streamOptions{ setRawTerminal: true, in: opts.InputStream, stdout: opts.OutputStream, context: opts.Context, }) } // ExportImageOptions represent the options for ExportImage Docker API call. // // See https://goo.gl/AuySaA for more details. type ExportImageOptions struct { Name string OutputStream io.Writer InactivityTimeout time.Duration Context context.Context } // ExportImage exports an image (as a tar file) into the stream. // // See https://goo.gl/AuySaA for more details. func (c *Client) ExportImage(opts ExportImageOptions) error { return c.stream(http.MethodGet, fmt.Sprintf("/images/%s/get", opts.Name), streamOptions{ setRawTerminal: true, stdout: opts.OutputStream, inactivityTimeout: opts.InactivityTimeout, context: opts.Context, }) } // ExportImagesOptions represent the options for ExportImages Docker API call // // See https://goo.gl/N9XlDn for more details. type ExportImagesOptions struct { Names []string OutputStream io.Writer `qs:"-"` InactivityTimeout time.Duration `qs:"-"` Context context.Context } // ExportImages exports one or more images (as a tar file) into the stream // // See https://goo.gl/N9XlDn for more details. func (c *Client) ExportImages(opts ExportImagesOptions) error { if len(opts.Names) == 0 { return ErrMustSpecifyNames } // API < 1.25 allows multiple name values // 1.25 says name must be a comma separated list var err error var exporturl string if c.requestedAPIVersion.GreaterThanOrEqualTo(apiVersion125) { str := opts.Names[0] for _, val := range opts.Names[1:] { str += "," + val } exporturl, err = c.getPath("/images/get", ExportImagesOptions{ Names: []string{str}, OutputStream: opts.OutputStream, InactivityTimeout: opts.InactivityTimeout, Context: opts.Context, }) } else { exporturl, err = c.getPath("/images/get", &opts) } if err != nil { return err } return c.streamURL(http.MethodGet, exporturl, streamOptions{ setRawTerminal: true, stdout: opts.OutputStream, inactivityTimeout: opts.InactivityTimeout, }) } // ImportImageOptions present the set of informations available for importing // an image from a source file or the stdin. // // See https://goo.gl/qkoSsn for more details. type ImportImageOptions struct { Repository string `qs:"repo"` Source string `qs:"fromSrc"` Tag string `qs:"tag"` InputStream io.Reader `qs:"-"` OutputStream io.Writer `qs:"-"` RawJSONStream bool `qs:"-"` InactivityTimeout time.Duration `qs:"-"` Context context.Context } // ImportImage imports an image from a url, a file or stdin // // See https://goo.gl/qkoSsn for more details. func (c *Client) ImportImage(opts ImportImageOptions) error { if opts.Repository == "" { return ErrNoSuchImage } if opts.Source != "-" { opts.InputStream = nil } if opts.Source != "-" && !isURL(opts.Source) { f, err := os.Open(opts.Source) if err != nil { return err } opts.InputStream = f opts.Source = "-" } return c.createImage(&opts, nil, opts.InputStream, opts.OutputStream, opts.RawJSONStream, opts.InactivityTimeout, opts.Context) } // BuilderVersion represents either the BuildKit or V1 ("classic") builder. type BuilderVersion string const ( BuilderV1 BuilderVersion = "1" BuilderBuildKit BuilderVersion = "2" ) // BuildImageOptions present the set of informations available for building an // image from a tarfile with a Dockerfile in it. // // For more details about the Docker building process, see // https://goo.gl/4nYHwV. type BuildImageOptions struct { Context context.Context Name string `qs:"t"` Dockerfile string `ver:"1.25"` ExtraHosts string `ver:"1.28"` CacheFrom []string `qs:"-" ver:"1.25"` Memory int64 Memswap int64 ShmSize int64 CPUShares int64 CPUQuota int64 `ver:"1.21"` CPUPeriod int64 `ver:"1.21"` CPUSetCPUs string Labels map[string]string InputStream io.Reader `qs:"-"` OutputStream io.Writer `qs:"-"` Remote string Auth AuthConfiguration `qs:"-"` // for older docker X-Registry-Auth header AuthConfigs AuthConfigurations `qs:"-"` // for newer docker X-Registry-Config header ContextDir string `qs:"-"` Ulimits []ULimit `qs:"-" ver:"1.18"` BuildArgs []BuildArg `qs:"-" ver:"1.21"` NetworkMode string `ver:"1.25"` Platform string `ver:"1.32"` InactivityTimeout time.Duration `qs:"-"` CgroupParent string SecurityOpt []string Target string Outputs string `ver:"1.40"` NoCache bool SuppressOutput bool `qs:"q"` Pull bool `ver:"1.16"` RmTmpContainer bool `qs:"rm"` ForceRmTmpContainer bool `qs:"forcerm" ver:"1.12"` RawJSONStream bool `qs:"-"` Version BuilderVersion `qs:"version" ver:"1.39"` } // BuildArg represents arguments that can be passed to the image when building // it from a Dockerfile. // // For more details about the Docker building process, see // https://goo.gl/4nYHwV. type BuildArg struct { Name string `json:"Name,omitempty" yaml:"Name,omitempty" toml:"Name,omitempty"` Value string `json:"Value,omitempty" yaml:"Value,omitempty" toml:"Value,omitempty"` } // BuildImage builds an image from a tarball's url or a Dockerfile in the input // stream. // // See https://goo.gl/4nYHwV for more details. func (c *Client) BuildImage(opts BuildImageOptions) error { if opts.OutputStream == nil { return ErrMissingOutputStream } headers, err := headersWithAuth(opts.Auth, c.versionedAuthConfigs(opts.AuthConfigs)) if err != nil { return err } if opts.Remote != "" && opts.Name == "" { opts.Name = opts.Remote } if opts.InputStream != nil || opts.ContextDir != "" { headers["Content-Type"] = "application/tar" } else if opts.Remote == "" { return ErrMissingRepo } if opts.ContextDir != "" { if opts.InputStream != nil { return ErrMultipleContexts } var err error if opts.InputStream, err = createTarStream(opts.ContextDir, opts.Dockerfile); err != nil { return err } } qs, ver := queryStringVersion(&opts) if len(opts.CacheFrom) > 0 { if b, err := json.Marshal(opts.CacheFrom); err == nil { item := url.Values(map[string][]string{}) item.Add("cachefrom", string(b)) qs = fmt.Sprintf("%s&%s", qs, item.Encode()) if ver == nil || apiVersion125.GreaterThan(ver) { ver = apiVersion125 } } } if len(opts.Ulimits) > 0 { if b, err := json.Marshal(opts.Ulimits); err == nil { item := url.Values(map[string][]string{}) item.Add("ulimits", string(b)) qs = fmt.Sprintf("%s&%s", qs, item.Encode()) if ver == nil || apiVersion118.GreaterThan(ver) { ver = apiVersion118 } } } if len(opts.BuildArgs) > 0 { v := make(map[string]string) for _, arg := range opts.BuildArgs { v[arg.Name] = arg.Value } if b, err := json.Marshal(v); err == nil { item := url.Values(map[string][]string{}) item.Add("buildargs", string(b)) qs = fmt.Sprintf("%s&%s", qs, item.Encode()) if ver == nil || apiVersion121.GreaterThan(ver) { ver = apiVersion121 } } } buildURL, err := c.pathVersionCheck("/build", qs, ver) if err != nil { return err } return c.streamURL(http.MethodPost, buildURL, streamOptions{ setRawTerminal: true, rawJSONStream: opts.RawJSONStream, headers: headers, in: opts.InputStream, stdout: opts.OutputStream, inactivityTimeout: opts.InactivityTimeout, context: opts.Context, }) } func (c *Client) versionedAuthConfigs(authConfigs AuthConfigurations) registryAuth { if c.serverAPIVersion == nil { c.checkAPIVersion() } if c.serverAPIVersion != nil && c.serverAPIVersion.GreaterThanOrEqualTo(apiVersion119) { return AuthConfigurations119(authConfigs.Configs) } return authConfigs } // TagImageOptions present the set of options to tag an image. // // See https://goo.gl/prHrvo for more details. type TagImageOptions struct { Repo string Tag string Force bool Context context.Context } // TagImage adds a tag to the image identified by the given name. // // See https://goo.gl/prHrvo for more details. func (c *Client) TagImage(name string, opts TagImageOptions) error { if name == "" { return ErrNoSuchImage } resp, err := c.do(http.MethodPost, "/images/"+name+"/tag?"+queryString(&opts), doOptions{ context: opts.Context, }) if err != nil { return err } defer resp.Body.Close() if resp.StatusCode == http.StatusNotFound { return ErrNoSuchImage } return err } func isURL(u string) bool { p, err := url.Parse(u) if err != nil { return false } return p.Scheme == "http" || p.Scheme == "https" } func headersWithAuth(auths ...registryAuth) (map[string]string, error) { headers := make(map[string]string) for _, auth := range auths { if auth.isEmpty() { continue } data, err := json.Marshal(auth) if err != nil { return nil, err } headers[auth.headerKey()] = base64.URLEncoding.EncodeToString(data) } return headers, nil } // APIImageSearch reflect the result of a search on the Docker Hub. // // See https://goo.gl/KLO9IZ for more details. type APIImageSearch struct { Description string `json:"description,omitempty" yaml:"description,omitempty" toml:"description,omitempty"` IsOfficial bool `json:"is_official,omitempty" yaml:"is_official,omitempty" toml:"is_official,omitempty"` IsAutomated bool `json:"is_automated,omitempty" yaml:"is_automated,omitempty" toml:"is_automated,omitempty"` Name string `json:"name,omitempty" yaml:"name,omitempty" toml:"name,omitempty"` StarCount int `json:"star_count,omitempty" yaml:"star_count,omitempty" toml:"star_count,omitempty"` } // SearchImages search the docker hub with a specific given term. // // See https://goo.gl/KLO9IZ for more details. func (c *Client) SearchImages(term string) ([]APIImageSearch, error) { resp, err := c.do(http.MethodGet, "/images/search?term="+term, doOptions{}) if err != nil { return nil, err } defer resp.Body.Close() var searchResult []APIImageSearch if err := json.NewDecoder(resp.Body).Decode(&searchResult); err != nil { return nil, err } return searchResult, nil } // SearchImagesEx search the docker hub with a specific given term and authentication. // // See https://goo.gl/KLO9IZ for more details. func (c *Client) SearchImagesEx(term string, auth AuthConfiguration) ([]APIImageSearch, error) { headers, err := headersWithAuth(auth) if err != nil { return nil, err } resp, err := c.do(http.MethodGet, "/images/search?term="+term, doOptions{ headers: headers, }) if err != nil { return nil, err } defer resp.Body.Close() var searchResult []APIImageSearch if err := json.NewDecoder(resp.Body).Decode(&searchResult); err != nil { return nil, err } return searchResult, nil } // PruneImagesOptions specify parameters to the PruneImages function. // // See https://goo.gl/qfZlbZ for more details. type PruneImagesOptions struct { Filters map[string][]string Context context.Context } // PruneImagesResults specify results from the PruneImages function. // // See https://goo.gl/qfZlbZ for more details. type PruneImagesResults struct { ImagesDeleted []struct{ Untagged, Deleted string } SpaceReclaimed int64 } // PruneImages deletes images which are unused. // // See https://goo.gl/qfZlbZ for more details. func (c *Client) PruneImages(opts PruneImagesOptions) (*PruneImagesResults, error) { path := "/images/prune?" + queryString(opts) resp, err := c.do(http.MethodPost, path, doOptions{context: opts.Context}) if err != nil { return nil, err } defer resp.Body.Close() var results PruneImagesResults if err := json.NewDecoder(resp.Body).Decode(&results); err != nil { return nil, err } return &results, nil } go-dockerclient-1.12.0/image_test.go000066400000000000000000001052311465717111200173230ustar00rootroot00000000000000// Copyright 2013 go-dockerclient authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package docker import ( "bytes" "encoding/base64" "encoding/json" "errors" "io" "net" "net/http" "net/url" "os" "reflect" "strings" "testing" "time" ) func newTestClient(rt http.RoundTripper) Client { endpoint := "http://localhost:4243" u, _ := parseEndpoint("http://localhost:4243", false) testAPIVersion, _ := NewAPIVersion("1.17") client := Client{ HTTPClient: &http.Client{Transport: rt}, Dialer: &net.Dialer{}, endpoint: endpoint, endpointURL: u, SkipServerVersionCheck: true, serverAPIVersion: testAPIVersion, } return client } type stdoutMock struct { *bytes.Buffer } func (m stdoutMock) Close() error { return nil } type stdinMock struct { *bytes.Buffer } func (m stdinMock) Close() error { return nil } func TestListImages(t *testing.T) { t.Parallel() body := `[ { "Repository":"base", "Tag":"ubuntu-12.10", "Id":"b750fe79269d", "Created":1364102658 }, { "Repository":"base", "Tag":"ubuntu-quantal", "Id":"b750fe79269d", "Created":1364102658 }, { "RepoTag": [ "ubuntu:12.04", "ubuntu:precise", "ubuntu:latest" ], "Id": "8dbd9e392a964c", "Created": 1365714795, "Size": 131506275, "VirtualSize": 131506275 }, { "RepoTag": [ "ubuntu:12.10", "ubuntu:quantal" ], "ParentId": "27cf784147099545", "Id": "b750fe79269d2e", "Created": 1364102658, "Size": 24653, "VirtualSize": 180116135 } ]` var expected []APIImages err := json.Unmarshal([]byte(body), &expected) if err != nil { t.Fatal(err) } client := newTestClient(&FakeRoundTripper{message: body, status: http.StatusOK}) images, err := client.ListImages(ListImagesOptions{}) if err != nil { t.Error(err) } if !reflect.DeepEqual(images, expected) { t.Errorf("ListImages: Wrong return value. Want %#v. Got %#v.", expected, images) } } func TestListImagesParameters(t *testing.T) { t.Parallel() fakeRT := &FakeRoundTripper{message: "null", status: http.StatusOK} client := newTestClient(fakeRT) _, err := client.ListImages(ListImagesOptions{All: false}) if err != nil { t.Fatal(err) } req := fakeRT.requests[0] if req.Method != http.MethodGet { t.Errorf("ListImages({All: false}: Wrong HTTP method. Want GET. Got %s.", req.Method) } if all := req.URL.Query().Get("all"); all != "0" && all != "" { t.Errorf("ListImages({All: false}): Wrong parameter. Want all=0 or not present at all. Got all=%s", all) } fakeRT.Reset() _, err = client.ListImages(ListImagesOptions{All: true}) if err != nil { t.Fatal(err) } req = fakeRT.requests[0] if all := req.URL.Query().Get("all"); all != "1" { t.Errorf("ListImages({All: true}): Wrong parameter. Want all=1. Got all=%s", all) } fakeRT.Reset() _, err = client.ListImages(ListImagesOptions{Filters: map[string][]string{ "dangling": {"true"}, }}) if err != nil { t.Fatal(err) } req = fakeRT.requests[0] body := req.URL.Query().Get("filters") var filters map[string][]string err = json.Unmarshal([]byte(body), &filters) if err != nil { t.Fatal(err) } if len(filters["dangling"]) != 1 || filters["dangling"][0] != "true" { t.Errorf("ListImages(dangling=[true]): Wrong filter map. Want dangling=[true], got dangling=%v", filters["dangling"]) } } func TestImageHistory(t *testing.T) { t.Parallel() body := `[ { "Id": "25daec02219d2d852f7526137213a9b199926b4b24e732eab5b8bc6c49bd470e", "Tags": [ "debian:7.6", "debian:latest", "debian:7", "debian:wheezy" ], "Created": 1409856216, "CreatedBy": "/bin/sh -c #(nop) CMD [/bin/bash]" }, { "Id": "41026a5347fb5be6ed16115bf22df8569697139f246186de9ae8d4f67c335dce", "Created": 1409856213, "CreatedBy": "/bin/sh -c #(nop) ADD file:1ee9e97209d00e3416a4543b23574cc7259684741a46bbcbc755909b8a053a38 in /", "Size": 85178663 }, { "Id": "511136ea3c5a64f264b78b5433614aec563103b4d4702f3ba7d4d2698e22c158", "Tags": [ "scratch:latest" ], "Created": 1371157430 } ]` var expected []ImageHistory err := json.Unmarshal([]byte(body), &expected) if err != nil { t.Fatal(err) } client := newTestClient(&FakeRoundTripper{message: body, status: http.StatusOK}) history, err := client.ImageHistory("debian:latest") if err != nil { t.Error(err) } if !reflect.DeepEqual(history, expected) { t.Errorf("ImageHistory: Wrong return value. Want %#v. Got %#v.", expected, history) } } func TestRemoveImage(t *testing.T) { t.Parallel() name := "test" fakeRT := &FakeRoundTripper{message: "", status: http.StatusNoContent} client := newTestClient(fakeRT) err := client.RemoveImage(name) if err != nil { t.Fatal(err) } req := fakeRT.requests[0] expectedMethod := http.MethodDelete if req.Method != expectedMethod { t.Errorf("RemoveImage(%q): Wrong HTTP method. Want %s. Got %s.", name, expectedMethod, req.Method) } u, _ := url.Parse(client.getURL("/images/" + name)) if req.URL.Path != u.Path { t.Errorf("RemoveImage(%q): Wrong request path. Want %q. Got %q.", name, u.Path, req.URL.Path) } } func TestRemoveImageNotFound(t *testing.T) { t.Parallel() client := newTestClient(&FakeRoundTripper{message: "no such image", status: http.StatusNotFound}) err := client.RemoveImage("test:") if !errors.Is(err, ErrNoSuchImage) { t.Errorf("RemoveImage: wrong error. Want %#v. Got %#v.", ErrNoSuchImage, err) } } func TestRemoveImageExtended(t *testing.T) { t.Parallel() name := "test" fakeRT := &FakeRoundTripper{message: "", status: http.StatusNoContent} client := newTestClient(fakeRT) err := client.RemoveImageExtended(name, RemoveImageOptions{Force: true, NoPrune: true}) if err != nil { t.Fatal(err) } req := fakeRT.requests[0] expectedMethod := http.MethodDelete if req.Method != expectedMethod { t.Errorf("RemoveImage(%q): Wrong HTTP method. Want %s. Got %s.", name, expectedMethod, req.Method) } u, _ := url.Parse(client.getURL("/images/" + name)) if req.URL.Path != u.Path { t.Errorf("RemoveImage(%q): Wrong request path. Want %q. Got %q.", name, u.Path, req.URL.Path) } expectedQuery := "force=1&noprune=1" if query := req.URL.Query().Encode(); query != expectedQuery { t.Errorf("PushImage: Wrong query string. Want %q. Got %q.", expectedQuery, query) } } func TestInspectImage(t *testing.T) { t.Parallel() body := `{ "Id":"b750fe79269d2ec9a3c593ef05b4332b1d1a02a62b4accb2c21d589ff2f5f2dc", "Parent":"27cf784147099545", "Created":"2013-03-23T22:24:18.818426Z", "Container":"3d67245a8d72ecf13f33dffac9f79dcdf70f75acb84d308770391510e0c23ad0", "ContainerConfig":{"Memory":1}, "VirtualSize":12345, "RootFS": { "Type": "layers", "Layers": [ "sha256:05a0deb2e405eb3095ab646dc1695a26bffe8bd4071e3af90efcf16e9d3f6d93", "sha256:4c5db681b9aa9ab1cf666ec969a810c8ff4410e70e06394670dc4f3bf595532f" ] } }` created, err := time.Parse(time.RFC3339Nano, "2013-03-23T22:24:18.818426Z") if err != nil { t.Fatal(err) } expected := Image{ ID: "b750fe79269d2ec9a3c593ef05b4332b1d1a02a62b4accb2c21d589ff2f5f2dc", Parent: "27cf784147099545", Created: created, Container: "3d67245a8d72ecf13f33dffac9f79dcdf70f75acb84d308770391510e0c23ad0", ContainerConfig: Config{ Memory: 1, }, VirtualSize: 12345, RootFS: &RootFS{ Type: "layers", Layers: []string{ "sha256:05a0deb2e405eb3095ab646dc1695a26bffe8bd4071e3af90efcf16e9d3f6d93", "sha256:4c5db681b9aa9ab1cf666ec969a810c8ff4410e70e06394670dc4f3bf595532f", }, }, } fakeRT := &FakeRoundTripper{message: body, status: http.StatusOK} client := newTestClient(fakeRT) image, err := client.InspectImage(expected.ID) if err != nil { t.Fatal(err) } if !reflect.DeepEqual(*image, expected) { t.Errorf("InspectImage(%q): Wrong image returned. Want %#v. Got %#v.", expected.ID, expected, *image) } req := fakeRT.requests[0] if req.Method != http.MethodGet { t.Errorf("InspectImage(%q): Wrong HTTP method. Want GET. Got %s.", expected.ID, req.Method) } u, _ := url.Parse(client.getURL("/images/" + expected.ID + "/json")) if req.URL.Path != u.Path { t.Errorf("InspectImage(%q): Wrong request URL. Want %q. Got %q.", expected.ID, u.Path, req.URL.Path) } } func TestInspectImageNotFound(t *testing.T) { t.Parallel() client := newTestClient(&FakeRoundTripper{message: "no such image", status: http.StatusNotFound}) name := "test" image, err := client.InspectImage(name) if image != nil { t.Errorf("InspectImage(%q): expected image, got %#v.", name, image) } if !errors.Is(err, ErrNoSuchImage) { t.Errorf("InspectImage(%q): wrong error. Want %#v. Got %#v.", name, ErrNoSuchImage, err) } } func TestPushImage(t *testing.T) { t.Parallel() fakeRT := &FakeRoundTripper{message: "Pushing 1/100", status: http.StatusOK} client := newTestClient(fakeRT) var buf bytes.Buffer err := client.PushImage(PushImageOptions{Name: "test", OutputStream: &buf}, AuthConfiguration{}) if err != nil { t.Fatal(err) } expected := "Pushing 1/100" if buf.String() != expected { t.Errorf("PushImage: Wrong output. Want %q. Got %q.", expected, buf.String()) } req := fakeRT.requests[0] if req.Method != http.MethodPost { t.Errorf("PushImage: Wrong HTTP method. Want POST. Got %s.", req.Method) } u, _ := url.Parse(client.getURL("/images/test/push")) if req.URL.Path != u.Path { t.Errorf("PushImage: Wrong request path. Want %q. Got %q.", u.Path, req.URL.Path) } if query := req.URL.Query().Encode(); query != "" { t.Errorf("PushImage: Wrong query string. Want no parameters, got %q.", query) } authHeader, ok := req.Header["X-Registry-Auth"] if ok { t.Errorf("PushImage: unexpected non-empty X-Registry-Auth header: %v", authHeader) } } func TestPushImageWithRawJSON(t *testing.T) { t.Parallel() body := ` {"status":"Pushing..."} {"status":"Pushing", "progress":"1/? (n/a)", "progressDetail":{"current":1}}} {"status":"Image successfully pushed"} ` fakeRT := &FakeRoundTripper{ message: body, status: http.StatusOK, header: map[string]string{ "Content-Type": "application/json", }, } client := newTestClient(fakeRT) var buf bytes.Buffer err := client.PushImage(PushImageOptions{ Name: "test", OutputStream: &buf, RawJSONStream: true, }, AuthConfiguration{}) if err != nil { t.Fatal(err) } if buf.String() != body { t.Errorf("PushImage: Wrong raw output. Want %q. Got %q.", body, buf.String()) } } func TestPushImageWithAuthentication(t *testing.T) { t.Parallel() fakeRT := &FakeRoundTripper{message: "Pushing 1/100", status: http.StatusOK} client := newTestClient(fakeRT) var buf bytes.Buffer inputAuth := AuthConfiguration{ Username: "gopher", Password: "gopher123", Email: "gopher@tsuru.io", } err := client.PushImage(PushImageOptions{Name: "test", OutputStream: &buf}, inputAuth) if err != nil { t.Fatal(err) } req := fakeRT.requests[0] var gotAuth AuthConfiguration auth, err := base64.URLEncoding.DecodeString(req.Header.Get("X-Registry-Auth")) if err != nil { t.Errorf("PushImage: caught error decoding auth. %#v", err.Error()) } err = json.Unmarshal(auth, &gotAuth) if err != nil { t.Fatal(err) } if !reflect.DeepEqual(gotAuth, inputAuth) { t.Errorf("PushImage: wrong auth configuration. Want %#v. Got %#v.", inputAuth, gotAuth) } } func TestPushImageCustomRegistry(t *testing.T) { t.Parallel() fakeRT := &FakeRoundTripper{message: "Pushing 1/100", status: http.StatusOK} client := newTestClient(fakeRT) var authConfig AuthConfiguration var buf bytes.Buffer opts := PushImageOptions{ Name: "test", Registry: "docker.tsuru.io", OutputStream: &buf, } err := client.PushImage(opts, authConfig) if err != nil { t.Fatal(err) } req := fakeRT.requests[0] expectedQuery := "registry=docker.tsuru.io" if query := req.URL.Query().Encode(); query != expectedQuery { t.Errorf("PushImage: Wrong query string. Want %q. Got %q.", expectedQuery, query) } } func TestPushImageNoName(t *testing.T) { t.Parallel() client := Client{} err := client.PushImage(PushImageOptions{}, AuthConfiguration{}) if !errors.Is(err, ErrNoSuchImage) { t.Errorf("PushImage: got wrong error. Want %#v. Got %#v.", ErrNoSuchImage, err) } } func TestPullImage(t *testing.T) { t.Parallel() fakeRT := &FakeRoundTripper{message: "Pulling 1/100", status: http.StatusOK} client := newTestClient(fakeRT) var buf bytes.Buffer err := client.PullImage(PullImageOptions{Repository: "base", OutputStream: &buf}, AuthConfiguration{}) if err != nil { t.Fatal(err) } expected := "Pulling 1/100" if buf.String() != expected { t.Errorf("PullImage: Wrong output. Want %q. Got %q.", expected, buf.String()) } req := fakeRT.requests[0] if req.Method != http.MethodPost { t.Errorf("PullImage: Wrong HTTP method. Want POST. Got %s.", req.Method) } u, _ := url.Parse(client.getURL("/images/create")) if req.URL.Path != u.Path { t.Errorf("PullImage: Wrong request path. Want %q. Got %q.", u.Path, req.URL.Path) } expectedQuery := "fromImage=base" if query := req.URL.Query().Encode(); query != expectedQuery { t.Errorf("PullImage: Wrong query strin. Want %q. Got %q.", expectedQuery, query) } } func TestPullImageWithDigest(t *testing.T) { t.Parallel() fakeRT := &FakeRoundTripper{message: "Pulling 1/100", status: http.StatusOK} client := newTestClient(fakeRT) var buf bytes.Buffer err := client.PullImage(PullImageOptions{ Repository: "tsuru/bs:latest@sha256:504a2f04aa5d07768e4f7467ddd2618b07dd6013cfabca7dc527a3d9fa786580", OutputStream: &buf, }, AuthConfiguration{}) if err != nil { t.Fatal(err) } expected := "Pulling 1/100" if buf.String() != expected { t.Errorf("PullImage: Wrong output. Want %q. Got %q.", expected, buf.String()) } req := fakeRT.requests[0] if req.Method != http.MethodPost { t.Errorf("PullImage: Wrong HTTP method. Want POST. Got %s.", req.Method) } u, _ := url.Parse(client.getURL("/images/create")) if req.URL.Path != u.Path { t.Errorf("PullImage: Wrong request path. Want %q. Got %q.", u.Path, req.URL.Path) } expectedQuery := url.Values{ "fromImage": {"tsuru/bs:latest"}, "tag": {"sha256:504a2f04aa5d07768e4f7467ddd2618b07dd6013cfabca7dc527a3d9fa786580"}, } if !reflect.DeepEqual(req.URL.Query(), expectedQuery) { t.Errorf("PullImage: Wrong query string\nWant %#v\nGot %#v", expectedQuery, req.URL.Query()) } } func TestPullImageWithDigestAndTag(t *testing.T) { // This is probably a wrong use of the Docker API, but let's let users // send the request to the API. And also changing this behavior would // be a breaking change on go-dockerclient. t.Parallel() fakeRT := &FakeRoundTripper{message: "Pulling 1/100", status: http.StatusOK} client := newTestClient(fakeRT) var buf bytes.Buffer err := client.PullImage(PullImageOptions{ Repository: "tsuru/bs:latest@sha256:504a2f04aa5d07768e4f7467ddd2618b07dd6013cfabca7dc527a3d9fa786580", Tag: "latest", OutputStream: &buf, }, AuthConfiguration{}) if err != nil { t.Fatal(err) } expected := "Pulling 1/100" if buf.String() != expected { t.Errorf("PullImage: Wrong output. Want %q. Got %q.", expected, buf.String()) } req := fakeRT.requests[0] if req.Method != http.MethodPost { t.Errorf("PullImage: Wrong HTTP method. Want POST. Got %s.", req.Method) } u, _ := url.Parse(client.getURL("/images/create")) if req.URL.Path != u.Path { t.Errorf("PullImage: Wrong request path. Want %q. Got %q.", u.Path, req.URL.Path) } expectedQuery := url.Values{ "fromImage": {"tsuru/bs:latest@sha256:504a2f04aa5d07768e4f7467ddd2618b07dd6013cfabca7dc527a3d9fa786580"}, "tag": {"latest"}, } if !reflect.DeepEqual(req.URL.Query(), expectedQuery) { t.Errorf("PullImage: Wrong query string\nWant %#vGot %#v", expectedQuery, req.URL.Query()) } } func TestPullImageWithRawJSON(t *testing.T) { t.Parallel() body := ` {"status":"Pulling..."} {"status":"Pulling", "progress":"1 B/ 100 B", "progressDetail":{"current":1, "total":100}} ` fakeRT := &FakeRoundTripper{ message: body, status: http.StatusOK, header: map[string]string{ "Content-Type": "application/json", }, } client := newTestClient(fakeRT) var buf bytes.Buffer err := client.PullImage(PullImageOptions{ Repository: "base", OutputStream: &buf, RawJSONStream: true, }, AuthConfiguration{}) if err != nil { t.Fatal(err) } if buf.String() != body { t.Errorf("PullImage: Wrong raw output. Want %q. Got %q", body, buf.String()) } } func TestPullImageWithoutOutputStream(t *testing.T) { t.Parallel() fakeRT := &FakeRoundTripper{message: "Pulling 1/100", status: http.StatusOK} client := newTestClient(fakeRT) opts := PullImageOptions{ Repository: "base", Registry: "docker.tsuru.io", } err := client.PullImage(opts, AuthConfiguration{}) if err != nil { t.Fatal(err) } req := fakeRT.requests[0] expected := map[string][]string{"fromImage": {"base"}, "registry": {"docker.tsuru.io"}} got := map[string][]string(req.URL.Query()) if !reflect.DeepEqual(got, expected) { t.Errorf("PullImage: wrong query string. Want %#v. Got %#v.", expected, got) } } func TestPullImageCustomRegistry(t *testing.T) { t.Parallel() fakeRT := &FakeRoundTripper{message: "Pulling 1/100", status: http.StatusOK} client := newTestClient(fakeRT) var buf bytes.Buffer opts := PullImageOptions{ Repository: "base", Registry: "docker.tsuru.io", OutputStream: &buf, } err := client.PullImage(opts, AuthConfiguration{}) if err != nil { t.Fatal(err) } req := fakeRT.requests[0] expected := map[string][]string{"fromImage": {"base"}, "registry": {"docker.tsuru.io"}} got := map[string][]string(req.URL.Query()) if !reflect.DeepEqual(got, expected) { t.Errorf("PullImage: wrong query string. Want %#v. Got %#v.", expected, got) } } func TestPullImageTag(t *testing.T) { t.Parallel() fakeRT := &FakeRoundTripper{message: "Pulling 1/100", status: http.StatusOK} client := newTestClient(fakeRT) var buf bytes.Buffer opts := PullImageOptions{ Repository: "base", Registry: "docker.tsuru.io", Tag: "latest", OutputStream: &buf, } err := client.PullImage(opts, AuthConfiguration{}) if err != nil { t.Fatal(err) } req := fakeRT.requests[0] expected := map[string][]string{"fromImage": {"base"}, "registry": {"docker.tsuru.io"}, "tag": {"latest"}} got := map[string][]string(req.URL.Query()) if !reflect.DeepEqual(got, expected) { t.Errorf("PullImage: wrong query string. Want %#v. Got %#v.", expected, got) } } func TestPullImageNoRepository(t *testing.T) { t.Parallel() var opts PullImageOptions client := Client{} err := client.PullImage(opts, AuthConfiguration{}) if !errors.Is(err, ErrNoSuchImage) { t.Errorf("PullImage: got wrong error. Want %#v. Got %#v.", ErrNoSuchImage, err) } } func TestImportImageFromUrl(t *testing.T) { t.Parallel() fakeRT := &FakeRoundTripper{message: "", status: http.StatusOK} client := newTestClient(fakeRT) var buf bytes.Buffer opts := ImportImageOptions{ Source: "http://mycompany.com/file.tar", Repository: "testimage", Tag: "tag", OutputStream: &buf, } err := client.ImportImage(opts) if err != nil { t.Fatal(err) } req := fakeRT.requests[0] expected := map[string][]string{"fromSrc": {opts.Source}, "repo": {opts.Repository}, "tag": {opts.Tag}} got := map[string][]string(req.URL.Query()) if !reflect.DeepEqual(got, expected) { t.Errorf("ImportImage: wrong query string. Want %#v. Got %#v.", expected, got) } } func TestImportImageFromInput(t *testing.T) { t.Parallel() fakeRT := &FakeRoundTripper{message: "", status: http.StatusOK} client := newTestClient(fakeRT) in := bytes.NewBufferString("tar content") var buf bytes.Buffer opts := ImportImageOptions{ Source: "-", Repository: "testimage", InputStream: in, OutputStream: &buf, Tag: "tag", } err := client.ImportImage(opts) if err != nil { t.Fatal(err) } req := fakeRT.requests[0] expected := map[string][]string{"fromSrc": {opts.Source}, "repo": {opts.Repository}, "tag": {opts.Tag}} got := map[string][]string(req.URL.Query()) if !reflect.DeepEqual(got, expected) { t.Errorf("ImportImage: wrong query string. Want %#v. Got %#v.", expected, got) } body, err := io.ReadAll(req.Body) if err != nil { t.Errorf("ImportImage: caugth error while reading body %#v", err.Error()) } e := "tar content" if string(body) != e { t.Errorf("ImportImage: wrong body. Want %#v. Got %#v.", e, string(body)) } } func TestImportImageDoesNotPassInputIfSourceIsNotDash(t *testing.T) { t.Parallel() fakeRT := &FakeRoundTripper{message: "", status: http.StatusOK} client := newTestClient(fakeRT) var buf bytes.Buffer in := bytes.NewBufferString("foo") opts := ImportImageOptions{ Source: "http://test.com/container.tar", Repository: "testimage", InputStream: in, OutputStream: &buf, } err := client.ImportImage(opts) if err != nil { t.Fatal(err) } req := fakeRT.requests[0] expected := map[string][]string{"fromSrc": {opts.Source}, "repo": {opts.Repository}} got := map[string][]string(req.URL.Query()) if !reflect.DeepEqual(got, expected) { t.Errorf("ImportImage: wrong query string. Want %#v. Got %#v.", expected, got) } body, err := io.ReadAll(req.Body) if err != nil { t.Errorf("ImportImage: caugth error while reading body %#v", err.Error()) } if string(body) != "" { t.Errorf("ImportImage: wrong body. Want nothing. Got %#v.", string(body)) } } func TestImportImageShouldPassTarContentToBodyWhenSourceIsFilePath(t *testing.T) { t.Parallel() fakeRT := &FakeRoundTripper{message: "", status: http.StatusOK} client := newTestClient(fakeRT) var buf bytes.Buffer tarPath := "testing/data/container.tar" opts := ImportImageOptions{ Source: tarPath, Repository: "testimage", OutputStream: &buf, } err := client.ImportImage(opts) if err != nil { t.Fatal(err) } tar, err := os.Open(tarPath) if err != nil { t.Fatal(err) } req := fakeRT.requests[0] tarContent, err := io.ReadAll(tar) if err != nil { t.Fatal(err) } body, err := io.ReadAll(req.Body) if err != nil { t.Fatal(err) } if !reflect.DeepEqual(tarContent, body) { t.Errorf("ImportImage: wrong body. Want %#v content. Got %#v.", tarPath, body) } } func TestImportImageShouldChangeSourceToDashWhenItsAFilePath(t *testing.T) { t.Parallel() fakeRT := &FakeRoundTripper{message: "", status: http.StatusOK} client := newTestClient(fakeRT) var buf bytes.Buffer tarPath := "testing/data/container.tar" opts := ImportImageOptions{ Source: tarPath, Repository: "testimage", OutputStream: &buf, } err := client.ImportImage(opts) if err != nil { t.Fatal(err) } req := fakeRT.requests[0] expected := map[string][]string{"fromSrc": {"-"}, "repo": {opts.Repository}} got := map[string][]string(req.URL.Query()) if !reflect.DeepEqual(got, expected) { t.Errorf("ImportImage: wrong query string. Want %#v. Got %#v.", expected, got) } } func TestBuildImageParameters(t *testing.T) { t.Parallel() fakeRT := &FakeRoundTripper{message: "", status: http.StatusOK} client := newTestClient(fakeRT) var buf bytes.Buffer opts := BuildImageOptions{ Name: "testImage", NoCache: true, CacheFrom: []string{"test1", "test2"}, SuppressOutput: true, Pull: true, RmTmpContainer: true, ForceRmTmpContainer: true, Memory: 1024, Memswap: 2048, CPUShares: 10, CPUQuota: 7500, CPUPeriod: 100000, CPUSetCPUs: "0-3", Ulimits: []ULimit{{Name: "nofile", Soft: 100, Hard: 200}}, BuildArgs: []BuildArg{{Name: "SOME_VAR", Value: "some_value"}}, InputStream: &buf, OutputStream: &buf, Labels: map[string]string{"k": "v"}, NetworkMode: "host", CgroupParent: "cgparent", SecurityOpt: []string{"securityoptions"}, } err := client.BuildImage(opts) if err != nil && !strings.Contains(err.Error(), "build image fail") { t.Fatal(err) } req := fakeRT.requests[0] expected := map[string][]string{ "t": {opts.Name}, "nocache": {"1"}, "cachefrom": {`["test1","test2"]`}, "q": {"1"}, "pull": {"1"}, "rm": {"1"}, "forcerm": {"1"}, "memory": {"1024"}, "memswap": {"2048"}, "cpushares": {"10"}, "cpuquota": {"7500"}, "cpuperiod": {"100000"}, "cpusetcpus": {"0-3"}, "labels": {`{"k":"v"}`}, "ulimits": {`[{"Name":"nofile","Soft":100,"Hard":200}]`}, "buildargs": {`{"SOME_VAR":"some_value"}`}, "networkmode": {"host"}, "cgroupparent": {"cgparent"}, "securityopt": {"securityoptions"}, } got := map[string][]string(req.URL.Query()) if !reflect.DeepEqual(got, expected) { t.Errorf("BuildImage: wrong query string. Want %#v.\n Got %#v.", expected, got) } expectedPrefix := "http://localhost:4243/v1.25/" if !strings.HasPrefix(req.URL.String(), expectedPrefix) { t.Errorf("BuildImage: wrong URL version Want Prefix %s.\n Got URL: %s", expectedPrefix, req.URL.String()) } } func TestBuildImageParametersForRemoteBuild(t *testing.T) { t.Parallel() fakeRT := &FakeRoundTripper{message: "", status: http.StatusOK} client := newTestClient(fakeRT) var buf bytes.Buffer opts := BuildImageOptions{ Name: "testImage", Remote: "testing/data/container.tar", SuppressOutput: true, OutputStream: &buf, } err := client.BuildImage(opts) if err != nil { t.Fatal(err) } req := fakeRT.requests[0] expected := map[string][]string{"t": {opts.Name}, "remote": {opts.Remote}, "q": {"1"}} got := map[string][]string(req.URL.Query()) if !reflect.DeepEqual(got, expected) { t.Errorf("BuildImage: wrong query string. Want %#v. Got %#v.", expected, got) } } func TestBuildImageMissingRepoAndNilInput(t *testing.T) { t.Parallel() fakeRT := &FakeRoundTripper{message: "", status: http.StatusOK} client := newTestClient(fakeRT) var buf bytes.Buffer opts := BuildImageOptions{ Name: "testImage", SuppressOutput: true, OutputStream: &buf, } err := client.BuildImage(opts) if !errors.Is(err, ErrMissingRepo) { t.Errorf("BuildImage: wrong error returned. Want %#v. Got %#v.", ErrMissingRepo, err) } } func TestBuildImageMissingOutputStream(t *testing.T) { t.Parallel() fakeRT := &FakeRoundTripper{message: "", status: http.StatusOK} client := newTestClient(fakeRT) opts := BuildImageOptions{Name: "testImage"} err := client.BuildImage(opts) if !errors.Is(err, ErrMissingOutputStream) { t.Errorf("BuildImage: wrong error returned. Want %#v. Got %#v.", ErrMissingOutputStream, err) } } func TestBuildImageWithRawJSON(t *testing.T) { t.Parallel() body := ` {"stream":"Step 0 : FROM ubuntu:latest\n"} {"stream":" ---\u003e 4300eb9d3c8d\n"} {"stream":"Step 1 : MAINTAINER docker \n"} {"stream":" ---\u003e Using cache\n"} {"stream":" ---\u003e 3a3ed758c370\n"} {"stream":"Step 2 : CMD /usr/bin/top\n"} {"stream":" ---\u003e Running in 36b1479cc2e4\n"} {"stream":" ---\u003e 4b6188aebe39\n"} {"stream":"Removing intermediate container 36b1479cc2e4\n"} {"stream":"Successfully built 4b6188aebe39\n"} ` fakeRT := &FakeRoundTripper{ message: body, status: http.StatusOK, header: map[string]string{ "Content-Type": "application/json", }, } client := newTestClient(fakeRT) var buf bytes.Buffer opts := BuildImageOptions{ Name: "testImage", RmTmpContainer: true, InputStream: &buf, OutputStream: &buf, RawJSONStream: true, } err := client.BuildImage(opts) if err != nil { t.Fatal(err) } if buf.String() != body { t.Errorf("BuildImage: Wrong raw output. Want %q. Got %q.", body, buf.String()) } } func TestBuildImageRemoteWithoutName(t *testing.T) { t.Parallel() fakeRT := &FakeRoundTripper{message: "", status: http.StatusOK} client := newTestClient(fakeRT) var buf bytes.Buffer opts := BuildImageOptions{ Remote: "testing/data/container.tar", SuppressOutput: true, OutputStream: &buf, } err := client.BuildImage(opts) if err != nil { t.Fatal(err) } req := fakeRT.requests[0] expected := map[string][]string{"t": {opts.Remote}, "remote": {opts.Remote}, "q": {"1"}} got := map[string][]string(req.URL.Query()) if !reflect.DeepEqual(got, expected) { t.Errorf("BuildImage: wrong query string. Want %#v. Got %#v.", expected, got) } } func TestTagImageParameters(t *testing.T) { t.Parallel() fakeRT := &FakeRoundTripper{message: "", status: http.StatusOK} client := newTestClient(fakeRT) opts := TagImageOptions{Repo: "testImage"} err := client.TagImage("base", opts) if err != nil && !strings.Contains(err.Error(), "tag image fail") { t.Fatal(err) } req := fakeRT.requests[0] expected := "http://localhost:4243/images/base/tag?repo=testImage" got := req.URL.String() if !reflect.DeepEqual(got, expected) { t.Errorf("TagImage: wrong query string. Want %#v. Got %#v.", expected, got) } } func TestTagImageMissingRepo(t *testing.T) { t.Parallel() fakeRT := &FakeRoundTripper{message: "", status: http.StatusOK} client := newTestClient(fakeRT) opts := TagImageOptions{Repo: "testImage"} err := client.TagImage("", opts) if !errors.Is(err, ErrNoSuchImage) { t.Errorf("TestTag: wrong error returned. Want %#v. Got %#v.", ErrNoSuchImage, err) } } func TestIsUrl(t *testing.T) { t.Parallel() url := "http://foo.bar/" result := isURL(url) if !result { t.Errorf("isURL: wrong match. Expected %#v to be a url. Got %#v.", url, result) } url = "/foo/bar.tar" result = isURL(url) if result { t.Errorf("isURL: wrong match. Expected %#v to not be a url. Got %#v", url, result) } } func TestLoadImage(t *testing.T) { t.Parallel() fakeRT := &FakeRoundTripper{message: "", status: http.StatusOK} client := newTestClient(fakeRT) tar, err := os.Open("testing/data/container.tar") if err != nil { t.Fatal(err) } else { defer tar.Close() } opts := LoadImageOptions{InputStream: tar} err = client.LoadImage(opts) if nil != err { t.Error(err) } req := fakeRT.requests[0] if req.Method != http.MethodPost { t.Errorf("LoadImage: wrong method. Expected %q. Got %q.", http.MethodPost, req.Method) } if req.URL.Path != "/images/load" { t.Errorf("LoadImage: wrong URL. Expected %q. Got %q.", "/images/load", req.URL.Path) } } func TestExportImage(t *testing.T) { t.Parallel() var buf bytes.Buffer fakeRT := &FakeRoundTripper{message: "", status: http.StatusOK} client := newTestClient(fakeRT) opts := ExportImageOptions{Name: "testimage", OutputStream: &buf} err := client.ExportImage(opts) if nil != err { t.Error(err) } req := fakeRT.requests[0] if req.Method != http.MethodGet { t.Errorf("ExportImage: wrong method. Expected %q. Got %q.", http.MethodGet, req.Method) } expectedPath := "/images/testimage/get" if req.URL.Path != expectedPath { t.Errorf("ExportIMage: wrong path. Expected %q. Got %q.", expectedPath, req.URL.Path) } } func TestExportImages(t *testing.T) { t.Parallel() var buf bytes.Buffer fakeRT := &FakeRoundTripper{message: "", status: http.StatusOK} client := newTestClient(fakeRT) client.requestedAPIVersion = apiVersion125 opts := ExportImagesOptions{Names: []string{"testimage1", "testimage2:latest"}, OutputStream: &buf} err := client.ExportImages(opts) if nil != err { t.Error(err) } req := fakeRT.requests[0] if req.Method != http.MethodGet { t.Errorf("ExportImages: wrong method. Expected %q. Got %q.", http.MethodGet, req.Method) } expected := "http://localhost:4243/v1.25/images/get?names=testimage1%2Ctestimage2%3Alatest" got := req.URL.String() if !reflect.DeepEqual(got, expected) { t.Errorf("ExportImages: wrong path. Expected %q. Got %q.", expected, got) } client.requestedAPIVersion = apiVersion124 err = client.ExportImages(opts) if nil != err { t.Error(err) } req = fakeRT.requests[1] expected = "http://localhost:4243/v1.24/images/get?names=testimage1&names=testimage2%3Alatest" got = req.URL.String() if !reflect.DeepEqual(got, expected) { t.Errorf("ExportImages: wrong path. Expected %q. Got %q.", expected, got) } } func TestExportImagesNoNames(t *testing.T) { t.Parallel() var buf bytes.Buffer fakeRT := &FakeRoundTripper{message: "", status: http.StatusOK} client := newTestClient(fakeRT) opts := ExportImagesOptions{Names: []string{}, OutputStream: &buf} err := client.ExportImages(opts) if err == nil { t.Error("Expected an error") } if !errors.Is(err, ErrMustSpecifyNames) { t.Error(err) } } func TestSearchImages(t *testing.T) { t.Parallel() body := `[ { "description":"A container with Cassandra 2.0.3", "is_official":true, "is_automated":true, "name":"poklet/cassandra", "star_count":17 }, { "description":"A container with Cassandra 2.0.3", "is_official":true, "is_automated":false, "name":"poklet/cassandra", "star_count":17 } , { "description":"A container with Cassandra 2.0.3", "is_official":false, "is_automated":true, "name":"poklet/cassandra", "star_count":17 } ]` var expected []APIImageSearch err := json.Unmarshal([]byte(body), &expected) if err != nil { t.Fatal(err) } client := newTestClient(&FakeRoundTripper{message: body, status: http.StatusOK}) result, err := client.SearchImages("cassandra") if err != nil { t.Error(err) } if !reflect.DeepEqual(result, expected) { t.Errorf("SearchImages: Wrong return value. Want %#v. Got %#v.", expected, result) } } func TestSearchImagesEx(t *testing.T) { t.Parallel() body := `[ { "description":"A container with Cassandra 2.0.3", "is_official":true, "is_automated":true, "name":"poklet/cassandra", "star_count":17 }, { "description":"A container with Cassandra 2.0.3", "is_official":true, "is_automated":false, "name":"poklet/cassandra", "star_count":17 } , { "description":"A container with Cassandra 2.0.3", "is_official":false, "is_automated":true, "name":"poklet/cassandra", "star_count":17 } ]` var expected []APIImageSearch err := json.Unmarshal([]byte(body), &expected) if err != nil { t.Fatal(err) } client := newTestClient(&FakeRoundTripper{message: body, status: http.StatusOK}) auth := AuthConfiguration{} result, err := client.SearchImagesEx("cassandra", auth) if err != nil { t.Error(err) } if !reflect.DeepEqual(result, expected) { t.Errorf("SearchImages: Wrong return value. Want %#v. Got %#v.", expected, result) } } func TestPruneImages(t *testing.T) { t.Parallel() results := `{ "ImagesDeleted": [ {"Deleted": "a"}, {"Deleted": "b"}, {"Deleted": "c"} ], "SpaceReclaimed": 123 }` expected := &PruneImagesResults{} err := json.Unmarshal([]byte(results), expected) if err != nil { t.Fatal(err) } client := newTestClient(&FakeRoundTripper{message: results, status: http.StatusOK}) got, err := client.PruneImages(PruneImagesOptions{}) if err != nil { t.Fatal(err) } if !reflect.DeepEqual(got, expected) { t.Errorf("PruneImages: Expected %#v. Got %#v.", expected, got) } } go-dockerclient-1.12.0/integration_test.go000066400000000000000000000042461465717111200205700ustar00rootroot00000000000000// Copyright 2015 go-dockerclient authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. //go:build docker_integration package docker import ( "bytes" "reflect" "strings" "testing" ) func TestIntegrationPullCreateStartLogs(t *testing.T) { imageName := pullImage(t) client, err := NewClientFromEnv() if err != nil { t.Fatal(err) } hostConfig := HostConfig{PublishAllPorts: true} createOpts := integrationCreateContainerOpts(imageName, &hostConfig) container, err := client.CreateContainer(createOpts) if err != nil { t.Fatal(err) } err = client.StartContainer(container.ID, &hostConfig) if err != nil { t.Fatal(err) } status, err := client.WaitContainer(container.ID) if err != nil { t.Error(err) } if status != 0 { t.Errorf("WaitContainer(%q): wrong status. Want 0. Got %d", container.ID, status) } var stdout, stderr bytes.Buffer logsOpts := LogsOptions{ Container: container.ID, OutputStream: &stdout, ErrorStream: &stderr, Stdout: true, Stderr: true, } err = client.Logs(logsOpts) if err != nil { t.Error(err) } if stderr.String() != "" { t.Errorf("Got unexpected stderr from logs: %q", stderr.String()) } // split stdout by lines to make sure the test is the same on Windows // and Linux. Life is hard. expected := []string{ "hello hello", } if stdoutLines := getLines(&stdout); !reflect.DeepEqual(stdoutLines, expected) { t.Errorf("Got wrong stdout from logs.\nWant:\n%#v.\n\nGot:\n%#v.", expected, stdoutLines) } } func getLines(buf *bytes.Buffer) []string { var lines []string for _, line := range strings.Split(buf.String(), "\n") { line = strings.TrimSpace(line) if line != "" { lines = append(lines, line) } } return lines } func pullImage(t *testing.T) string { imageName := integrationDockerImage var buf bytes.Buffer pullOpts := PullImageOptions{ Repository: imageName, OutputStream: &buf, } client, err := NewClientFromEnv() if err != nil { t.Fatal(err) } err = client.PullImage(pullOpts, AuthConfiguration{}) if err != nil { t.Logf("Pull output: %s", buf.String()) t.Fatal(err) } return imageName } go-dockerclient-1.12.0/integration_unix_test.go000066400000000000000000000010351465717111200216240ustar00rootroot00000000000000// Copyright 2019 go-dockerclient authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. //go:build docker_integration && !windows package docker const integrationDockerImage = "alpine:latest" func integrationCreateContainerOpts(imageName string, hostConfig *HostConfig) CreateContainerOptions { return CreateContainerOptions{ Config: &Config{ Image: imageName, Cmd: []string{"sh", "-c", `echo "hello hello"`}, }, HostConfig: hostConfig, } } go-dockerclient-1.12.0/integration_windows_test.go000066400000000000000000000011051465717111200223310ustar00rootroot00000000000000// Copyright 2019 go-dockerclient authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. //go:build docker_integration package docker const integrationDockerImage = "mcr.microsoft.com/windows/servercore:ltsc2022" func integrationCreateContainerOpts(imageName string, hostConfig *HostConfig) CreateContainerOptions { return CreateContainerOptions{ Config: &Config{ Image: imageName, Cmd: []string{"powershell", "-Command", `Write-Host "hello hello"`}, }, HostConfig: hostConfig, } } go-dockerclient-1.12.0/misc.go000066400000000000000000000125051465717111200161360ustar00rootroot00000000000000// Copyright 2013 go-dockerclient authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package docker import ( "context" "encoding/json" "net" "net/http" "strings" "github.com/docker/docker/api/types/swarm" ) // Version returns version information about the docker server. // // See https://goo.gl/mU7yje for more details. func (c *Client) Version() (*Env, error) { return c.VersionWithContext(context.TODO()) } // VersionWithContext returns version information about the docker server. func (c *Client) VersionWithContext(ctx context.Context) (*Env, error) { resp, err := c.do(http.MethodGet, "/version", doOptions{context: ctx}) if err != nil { return nil, err } defer resp.Body.Close() var env Env if err := env.Decode(resp.Body); err != nil { return nil, err } return &env, nil } // DockerInfo contains information about the Docker server // // See https://goo.gl/bHUoz9 for more details. type DockerInfo struct { ID string Containers int ContainersRunning int ContainersPaused int ContainersStopped int Images int Driver string DriverStatus [][2]string SystemStatus [][2]string Plugins PluginsInfo NFd int NGoroutines int SystemTime string ExecutionDriver string LoggingDriver string CgroupDriver string NEventsListener int KernelVersion string OperatingSystem string OSType string Architecture string IndexServerAddress string RegistryConfig *ServiceConfig SecurityOptions []string NCPU int MemTotal int64 DockerRootDir string HTTPProxy string `json:"HttpProxy"` HTTPSProxy string `json:"HttpsProxy"` NoProxy string Name string Labels []string ServerVersion string ClusterStore string Runtimes map[string]Runtime ClusterAdvertise string Isolation string InitBinary string DefaultRuntime string Swarm swarm.Info LiveRestoreEnabled bool MemoryLimit bool SwapLimit bool KernelMemory bool CPUCfsPeriod bool `json:"CpuCfsPeriod"` CPUCfsQuota bool `json:"CpuCfsQuota"` CPUShares bool CPUSet bool IPv4Forwarding bool BridgeNfIptables bool BridgeNfIP6tables bool `json:"BridgeNfIp6tables"` Debug bool OomKillDisable bool ExperimentalBuild bool } // Runtime describes an OCI runtime // // for more information, see: https://dockr.ly/2NKM8qq type Runtime struct { Path string Args []string `json:"runtimeArgs"` } // PluginsInfo is a struct with the plugins registered with the docker daemon // // for more information, see: https://goo.gl/bHUoz9 type PluginsInfo struct { // List of Volume plugins registered Volume []string // List of Network plugins registered Network []string // List of Authorization plugins registered Authorization []string } // ServiceConfig stores daemon registry services configuration. // // for more information, see: https://goo.gl/7iFFDz type ServiceConfig struct { InsecureRegistryCIDRs []*NetIPNet IndexConfigs map[string]*IndexInfo Mirrors []string } // NetIPNet is the net.IPNet type, which can be marshalled and // unmarshalled to JSON. // // for more information, see: https://goo.gl/7iFFDz type NetIPNet net.IPNet // MarshalJSON returns the JSON representation of the IPNet. func (ipnet *NetIPNet) MarshalJSON() ([]byte, error) { return json.Marshal((*net.IPNet)(ipnet).String()) } // UnmarshalJSON sets the IPNet from a byte array of JSON. func (ipnet *NetIPNet) UnmarshalJSON(b []byte) (err error) { var ipnetStr string if err = json.Unmarshal(b, &ipnetStr); err == nil { var cidr *net.IPNet if _, cidr, err = net.ParseCIDR(ipnetStr); err == nil { *ipnet = NetIPNet(*cidr) } } return } // IndexInfo contains information about a registry. // // for more information, see: https://goo.gl/7iFFDz type IndexInfo struct { Name string Mirrors []string Secure bool Official bool } // Info returns system-wide information about the Docker server. // // See https://goo.gl/ElTHi2 for more details. func (c *Client) Info() (*DockerInfo, error) { resp, err := c.do(http.MethodGet, "/info", doOptions{}) if err != nil { return nil, err } defer resp.Body.Close() var info DockerInfo if err := json.NewDecoder(resp.Body).Decode(&info); err != nil { return nil, err } return &info, nil } // ParseRepositoryTag gets the name of the repository and returns it splitted // in two parts: the repository and the tag. It ignores the digest when it is // present. // // Some examples: // // localhost.localdomain:5000/samalba/hipache:latest -> localhost.localdomain:5000/samalba/hipache, latest // localhost.localdomain:5000/samalba/hipache -> localhost.localdomain:5000/samalba/hipache, "" // busybox:latest@sha256:4a731fb46adc5cefe3ae374a8b6020fc1b6ad667a279647766e9a3cd89f6fa92 -> busybox, latest func ParseRepositoryTag(repoTag string) (repository string, tag string) { parts := strings.SplitN(repoTag, "@", 2) repoTag = parts[0] n := strings.LastIndex(repoTag, ":") if n < 0 { return repoTag, "" } if tag := repoTag[n+1:]; !strings.Contains(tag, "/") { return repoTag[:n], tag } return repoTag, "" } go-dockerclient-1.12.0/misc_test.go000066400000000000000000000132031465717111200171710ustar00rootroot00000000000000// Copyright 2013 go-dockerclient authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package docker import ( "net" "net/http" "net/url" "reflect" "testing" ) type DockerVersion struct { Version string GitCommit string GoVersion string } func TestVersion(t *testing.T) { t.Parallel() body := `{ "Version":"0.2.2", "GitCommit":"5a2a5cc+CHANGES", "GoVersion":"go1.0.3" }` fakeRT := FakeRoundTripper{message: body, status: http.StatusOK} client := newTestClient(&fakeRT) expected := DockerVersion{ Version: "0.2.2", GitCommit: "5a2a5cc+CHANGES", GoVersion: "go1.0.3", } version, err := client.Version() if err != nil { t.Fatal(err) } if result := version.Get("Version"); result != expected.Version { t.Errorf("Version(): Wrong result. Want %#v. Got %#v.", expected.Version, version.Get("Version")) } if result := version.Get("GitCommit"); result != expected.GitCommit { t.Errorf("GitCommit(): Wrong result. Want %#v. Got %#v.", expected.GitCommit, version.Get("GitCommit")) } if result := version.Get("GoVersion"); result != expected.GoVersion { t.Errorf("GoVersion(): Wrong result. Want %#v. Got %#v.", expected.GoVersion, version.Get("GoVersion")) } req := fakeRT.requests[0] if req.Method != http.MethodGet { t.Errorf("Version(): wrong request method. Want GET. Got %s.", req.Method) } u, _ := url.Parse(client.getURL("/version")) if req.URL.Path != u.Path { t.Errorf("Version(): wrong request path. Want %q. Got %q.", u.Path, req.URL.Path) } } func TestVersionError(t *testing.T) { t.Parallel() fakeRT := &FakeRoundTripper{message: "internal error", status: http.StatusInternalServerError} client := newTestClient(fakeRT) version, err := client.Version() if version != nil { t.Errorf("Version(): expected value, got %#v.", version) } if err == nil { t.Error("Version(): unexpected error") } } func TestInfo(t *testing.T) { t.Parallel() body := `{ "Containers":11, "Images":16, "Debug":false, "NFd":11, "NGoroutines":21, "MemoryLimit":true, "SwapLimit":false, "RegistryConfig":{ "InsecureRegistryCIDRs":["127.0.0.0/8"], "IndexConfigs":{ "docker.io":{ "Name":"docker.io", "Mirrors":null, "Secure":true, "Official":true } }, "Mirrors":null }, "SecurityOptions": [ "name=apparmor", "name=seccomp", "profile=default" ], "Runtimes": { "runc": { "path": "docker-runc" }, "custom": { "path": "/usr/local/bin/my-oci-runtime", "runtimeArgs": [ "--debug", "--systemd-cgroup=false" ] } } }` fakeRT := FakeRoundTripper{message: body, status: http.StatusOK} client := newTestClient(&fakeRT) expected := &DockerInfo{ Containers: 11, Images: 16, Debug: false, NFd: 11, NGoroutines: 21, MemoryLimit: true, SwapLimit: false, RegistryConfig: &ServiceConfig{ InsecureRegistryCIDRs: []*NetIPNet{ { Mask: net.CIDRMask(8, 32), IP: net.ParseIP("127.0.0.0").To4(), }, }, IndexConfigs: map[string]*IndexInfo{ "docker.io": { Name: "docker.io", Secure: true, Official: true, }, }, }, SecurityOptions: []string{ "name=apparmor", "name=seccomp", "profile=default", }, Runtimes: map[string]Runtime{ "runc": { Path: "docker-runc", }, "custom": { Path: "/usr/local/bin/my-oci-runtime", Args: []string{ "--debug", "--systemd-cgroup=false", }, }, }, } info, err := client.Info() if err != nil { t.Fatal(err) } if !reflect.DeepEqual(expected, info) { t.Errorf("Info(): Wrong result.\nWant %#v.\nGot %#v.", expected, info) } req := fakeRT.requests[0] if req.Method != http.MethodGet { t.Errorf("Info(): Wrong HTTP method. Want GET. Got %s.", req.Method) } u, _ := url.Parse(client.getURL("/info")) if req.URL.Path != u.Path { t.Errorf("Info(): Wrong request path. Want %q. Got %q.", u.Path, req.URL.Path) } } func TestInfoError(t *testing.T) { t.Parallel() fakeRT := &FakeRoundTripper{message: "internal error", status: http.StatusInternalServerError} client := newTestClient(fakeRT) version, err := client.Info() if version != nil { t.Errorf("Info(): expected value, got %#v.", version) } if err == nil { t.Error("Info(): unexpected error") } } func TestParseRepositoryTag(t *testing.T) { t.Parallel() tests := []struct { input string expectedRepo string expectedTag string }{ { "localhost.localdomain:5000/samalba/hipache:latest", "localhost.localdomain:5000/samalba/hipache", "latest", }, { "localhost.localdomain:5000/samalba/hipache", "localhost.localdomain:5000/samalba/hipache", "", }, { "tsuru/python", "tsuru/python", "", }, { "tsuru/python:2.7", "tsuru/python", "2.7", }, { "busybox@sha256:4a731fb46adc5cefe3ae374a8b6020fc1b6ad667a279647766e9a3cd89f6fa92", "busybox", "", }, { "localhost.localdomain:5000/samalba/hipache:v1@sha256:4a731fb46adc5cefe3ae374a8b6020fc1b6ad667a279647766e9a3cd89f6fa92", "localhost.localdomain:5000/samalba/hipache", "v1", }, } for _, tt := range tests { test := tt t.Run(test.input, func(t *testing.T) { t.Parallel() repo, tag := ParseRepositoryTag(test.input) if repo != test.expectedRepo { t.Errorf("ParseRepositoryTag(%q): wrong repository. Want %q. Got %q", test.input, test.expectedRepo, repo) } if tag != test.expectedTag { t.Errorf("ParseRepositoryTag(%q): wrong tag. Want %q. Got %q", test.input, test.expectedTag, tag) } }) } } go-dockerclient-1.12.0/network.go000066400000000000000000000262601465717111200166770ustar00rootroot00000000000000// Copyright 2015 go-dockerclient authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package docker import ( "context" "encoding/json" "errors" "fmt" "net/http" "net/url" ) // ErrNetworkAlreadyExists is the error returned by CreateNetwork when the // network already exists. var ErrNetworkAlreadyExists = errors.New("network already exists") // Network represents a network. // // See https://goo.gl/6GugX3 for more details. type Network struct { Name string ID string `json:"Id"` Scope string Driver string IPAM IPAMOptions Containers map[string]Endpoint Options map[string]string Internal bool EnableIPv6 bool `json:"EnableIPv6"` Labels map[string]string } // Endpoint contains network resources allocated and used for a container in a network // // See https://goo.gl/6GugX3 for more details. type Endpoint struct { Name string ID string `json:"EndpointID"` MacAddress string IPv4Address string IPv6Address string } // ListNetworks returns all networks. // // See https://goo.gl/6GugX3 for more details. func (c *Client) ListNetworks() ([]Network, error) { resp, err := c.do(http.MethodGet, "/networks", doOptions{}) if err != nil { return nil, err } defer resp.Body.Close() var networks []Network if err := json.NewDecoder(resp.Body).Decode(&networks); err != nil { return nil, err } return networks, nil } // NetworkFilterOpts is an aggregation of key=value that Docker // uses to filter networks type NetworkFilterOpts map[string]map[string]bool // FilteredListNetworks returns all networks with the filters applied // // See goo.gl/zd2mx4 for more details. func (c *Client) FilteredListNetworks(opts NetworkFilterOpts) ([]Network, error) { params, err := json.Marshal(opts) if err != nil { return nil, err } qs := make(url.Values) qs.Add("filters", string(params)) path := "/networks?" + qs.Encode() resp, err := c.do(http.MethodGet, path, doOptions{}) if err != nil { return nil, err } defer resp.Body.Close() var networks []Network if err := json.NewDecoder(resp.Body).Decode(&networks); err != nil { return nil, err } return networks, nil } // NetworkInfo returns information about a network by its ID. // // See https://goo.gl/6GugX3 for more details. func (c *Client) NetworkInfo(id string) (*Network, error) { path := "/networks/" + id resp, err := c.do(http.MethodGet, path, doOptions{}) if err != nil { var e *Error if errors.As(err, &e) && e.Status == http.StatusNotFound { return nil, &NoSuchNetwork{ID: id} } return nil, err } defer resp.Body.Close() var network Network if err := json.NewDecoder(resp.Body).Decode(&network); err != nil { return nil, err } return &network, nil } // CreateNetworkOptions specify parameters to the CreateNetwork function and // (for now) is the expected body of the "create network" http request message // // See https://goo.gl/6GugX3 for more details. type CreateNetworkOptions struct { Name string `json:"Name" yaml:"Name" toml:"Name"` Driver string `json:"Driver" yaml:"Driver" toml:"Driver"` Scope string `json:"Scope" yaml:"Scope" toml:"Scope"` IPAM *IPAMOptions `json:"IPAM,omitempty" yaml:"IPAM" toml:"IPAM"` ConfigFrom *NetworkConfigFrom `json:"ConfigFrom,omitempty" yaml:"ConfigFrom" toml:"ConfigFrom"` Options map[string]any `json:"Options" yaml:"Options" toml:"Options"` Labels map[string]string `json:"Labels" yaml:"Labels" toml:"Labels"` CheckDuplicate bool `json:"CheckDuplicate" yaml:"CheckDuplicate" toml:"CheckDuplicate"` Internal bool `json:"Internal" yaml:"Internal" toml:"Internal"` EnableIPv6 bool `json:"EnableIPv6" yaml:"EnableIPv6" toml:"EnableIPv6"` Attachable bool `json:"Attachable" yaml:"Attachable" toml:"Attachable"` ConfigOnly bool `json:"ConfigOnly" yaml:"ConfigOnly" toml:"ConfigOnly"` Ingress bool `json:"Ingress" yaml:"Ingress" toml:"Ingress"` Context context.Context `json:"-"` } // NetworkConfigFrom is used in network creation for specifying the source of a // network configuration. type NetworkConfigFrom struct { Network string `json:"Network" yaml:"Network" toml:"Network"` } // IPAMOptions controls IP Address Management when creating a network // // See https://goo.gl/T8kRVH for more details. type IPAMOptions struct { Driver string `json:"Driver" yaml:"Driver" toml:"Driver"` Config []IPAMConfig `json:"Config" yaml:"Config" toml:"Config"` Options map[string]string `json:"Options" yaml:"Options" toml:"Options"` } // IPAMConfig represents IPAM configurations // // See https://goo.gl/T8kRVH for more details. type IPAMConfig struct { Subnet string `json:",omitempty"` IPRange string `json:",omitempty"` Gateway string `json:",omitempty"` AuxAddress map[string]string `json:"AuxiliaryAddresses,omitempty"` } // CreateNetwork creates a new network, returning the network instance, // or an error in case of failure. // // See https://goo.gl/6GugX3 for more details. func (c *Client) CreateNetwork(opts CreateNetworkOptions) (*Network, error) { resp, err := c.do( http.MethodPost, "/networks/create", doOptions{ data: opts, context: opts.Context, }, ) if err != nil { return nil, err } defer resp.Body.Close() type createNetworkResponse struct { ID string } var ( network Network cnr createNetworkResponse ) if err := json.NewDecoder(resp.Body).Decode(&cnr); err != nil { return nil, err } network.Name = opts.Name network.ID = cnr.ID network.Driver = opts.Driver return &network, nil } // RemoveNetwork removes a network or returns an error in case of failure. // // See https://goo.gl/6GugX3 for more details. func (c *Client) RemoveNetwork(id string) error { resp, err := c.do(http.MethodDelete, "/networks/"+id, doOptions{}) if err != nil { var e *Error if errors.As(err, &e) && e.Status == http.StatusNotFound { return &NoSuchNetwork{ID: id} } return err } resp.Body.Close() return nil } // NetworkConnectionOptions specify parameters to the ConnectNetwork and // DisconnectNetwork function. // // See https://goo.gl/RV7BJU for more details. type NetworkConnectionOptions struct { Container string // EndpointConfig is only applicable to the ConnectNetwork call EndpointConfig *EndpointConfig `json:"EndpointConfig,omitempty"` // Force is only applicable to the DisconnectNetwork call Force bool Context context.Context `json:"-"` } // EndpointConfig stores network endpoint details // // See https://goo.gl/RV7BJU for more details. type EndpointConfig struct { IPAMConfig *EndpointIPAMConfig `json:"IPAMConfig,omitempty" yaml:"IPAMConfig,omitempty" toml:"IPAMConfig,omitempty"` Links []string `json:"Links,omitempty" yaml:"Links,omitempty" toml:"Links,omitempty"` Aliases []string `json:"Aliases,omitempty" yaml:"Aliases,omitempty" toml:"Aliases,omitempty"` NetworkID string `json:"NetworkID,omitempty" yaml:"NetworkID,omitempty" toml:"NetworkID,omitempty"` EndpointID string `json:"EndpointID,omitempty" yaml:"EndpointID,omitempty" toml:"EndpointID,omitempty"` Gateway string `json:"Gateway,omitempty" yaml:"Gateway,omitempty" toml:"Gateway,omitempty"` IPAddress string `json:"IPAddress,omitempty" yaml:"IPAddress,omitempty" toml:"IPAddress,omitempty"` IPPrefixLen int `json:"IPPrefixLen,omitempty" yaml:"IPPrefixLen,omitempty" toml:"IPPrefixLen,omitempty"` IPv6Gateway string `json:"IPv6Gateway,omitempty" yaml:"IPv6Gateway,omitempty" toml:"IPv6Gateway,omitempty"` GlobalIPv6Address string `json:"GlobalIPv6Address,omitempty" yaml:"GlobalIPv6Address,omitempty" toml:"GlobalIPv6Address,omitempty"` GlobalIPv6PrefixLen int `json:"GlobalIPv6PrefixLen,omitempty" yaml:"GlobalIPv6PrefixLen,omitempty" toml:"GlobalIPv6PrefixLen,omitempty"` MacAddress string `json:"MacAddress,omitempty" yaml:"MacAddress,omitempty" toml:"MacAddress,omitempty"` DriverOpts map[string]string `json:"DriverOpts,omitempty" yaml:"DriverOpts,omitempty" toml:"DriverOpts,omitempty"` } // EndpointIPAMConfig represents IPAM configurations for an // endpoint // // See https://goo.gl/RV7BJU for more details. type EndpointIPAMConfig struct { IPv4Address string `json:",omitempty" yaml:"IPv4Address,omitempty"` IPv6Address string `json:",omitempty" yaml:"IPv6Address,omitempty"` } // ConnectNetwork adds a container to a network or returns an error in case of // failure. // // See https://goo.gl/6GugX3 for more details. func (c *Client) ConnectNetwork(id string, opts NetworkConnectionOptions) error { resp, err := c.do(http.MethodPost, "/networks/"+id+"/connect", doOptions{ data: opts, context: opts.Context, }) if err != nil { var e *Error if errors.As(err, &e) && e.Status == http.StatusNotFound { return &NoSuchNetworkOrContainer{NetworkID: id, ContainerID: opts.Container} } return err } resp.Body.Close() return nil } // DisconnectNetwork removes a container from a network or returns an error in // case of failure. // // See https://goo.gl/6GugX3 for more details. func (c *Client) DisconnectNetwork(id string, opts NetworkConnectionOptions) error { resp, err := c.do(http.MethodPost, "/networks/"+id+"/disconnect", doOptions{data: opts}) if err != nil { var e *Error if errors.As(err, &e) && e.Status == http.StatusNotFound { return &NoSuchNetworkOrContainer{NetworkID: id, ContainerID: opts.Container} } return err } resp.Body.Close() return nil } // PruneNetworksOptions specify parameters to the PruneNetworks function. // // See https://goo.gl/kX0S9h for more details. type PruneNetworksOptions struct { Filters map[string][]string Context context.Context } // PruneNetworksResults specify results from the PruneNetworks function. // // See https://goo.gl/kX0S9h for more details. type PruneNetworksResults struct { NetworksDeleted []string } // PruneNetworks deletes networks which are unused. // // See https://goo.gl/kX0S9h for more details. func (c *Client) PruneNetworks(opts PruneNetworksOptions) (*PruneNetworksResults, error) { path := "/networks/prune?" + queryString(opts) resp, err := c.do(http.MethodPost, path, doOptions{context: opts.Context}) if err != nil { return nil, err } defer resp.Body.Close() var results PruneNetworksResults if err := json.NewDecoder(resp.Body).Decode(&results); err != nil { return nil, err } return &results, nil } // NoSuchNetwork is the error returned when a given network does not exist. type NoSuchNetwork struct { ID string } func (err *NoSuchNetwork) Error() string { return fmt.Sprintf("No such network: %s", err.ID) } // NoSuchNetworkOrContainer is the error returned when a given network or // container does not exist. type NoSuchNetworkOrContainer struct { NetworkID string ContainerID string } func (err *NoSuchNetworkOrContainer) Error() string { return fmt.Sprintf("No such network (%s) or container (%s)", err.NetworkID, err.ContainerID) } go-dockerclient-1.12.0/network_test.go000066400000000000000000000206651465717111200177410ustar00rootroot00000000000000// Copyright 2015 go-dockerclient authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package docker import ( "bytes" "encoding/json" "errors" "net/http" "net/url" "reflect" "testing" ) func TestListNetworks(t *testing.T) { t.Parallel() jsonNetworks := `[ { "ID": "8dfafdbc3a40", "Name": "blah", "Type": "bridge", "Endpoints":[{"ID": "918c11c8288a", "Name": "dsafdsaf", "Network": "8dfafdbc3a40"}] }, { "ID": "9fb1e39c", "Name": "foo", "Type": "bridge", "Endpoints":[{"ID": "c080be979dda", "Name": "lllll2222", "Network": "9fb1e39c"}] } ]` var expected []Network err := json.Unmarshal([]byte(jsonNetworks), &expected) if err != nil { t.Fatal(err) } client := newTestClient(&FakeRoundTripper{message: jsonNetworks, status: http.StatusOK}) containers, err := client.ListNetworks() if err != nil { t.Fatal(err) } if !reflect.DeepEqual(containers, expected) { t.Errorf("ListNetworks: Expected %#v. Got %#v.", expected, containers) } } func TestFilteredListNetworks(t *testing.T) { t.Parallel() jsonNetworks := `[ { "ID": "9fb1e39c", "Name": "foo", "Type": "bridge", "Endpoints":[{"ID": "c080be979dda", "Name": "lllll2222", "Network": "9fb1e39c"}] } ]` var expected []Network err := json.Unmarshal([]byte(jsonNetworks), &expected) if err != nil { t.Fatal(err) } wantQuery := url.Values{ "filters": []string{`{"name":{"blah":true}}`}, } fakeRT := &FakeRoundTripper{message: jsonNetworks, status: http.StatusOK} client := newTestClient(fakeRT) opts := NetworkFilterOpts{ "name": map[string]bool{"blah": true}, } containers, err := client.FilteredListNetworks(opts) if err != nil { t.Fatal(err) } if !reflect.DeepEqual(containers, expected) { t.Errorf("ListNetworks: Expected %#v. Got %#v.", expected, containers) } query := fakeRT.requests[0].URL.Query() if !reflect.DeepEqual(query, wantQuery) { t.Errorf("FilteredListNetworks: wrong query\nWant %#v\nGot %#v", wantQuery, query) } } func TestNetworkInfo(t *testing.T) { t.Parallel() jsonNetwork := `{ "ID": "8dfafdbc3a40", "Name": "blah", "Type": "bridge", "Endpoints":[{"ID": "918c11c8288a", "Name": "dsafdsaf", "Network": "8dfafdbc3a40"}] }` var expected Network err := json.Unmarshal([]byte(jsonNetwork), &expected) if err != nil { t.Fatal(err) } fakeRT := &FakeRoundTripper{message: jsonNetwork, status: http.StatusOK} client := newTestClient(fakeRT) id := "8dfafdbc3a40" network, err := client.NetworkInfo(id) if err != nil { t.Fatal(err) } if !reflect.DeepEqual(*network, expected) { t.Errorf("NetworkInfo(%q): Expected %#v. Got %#v.", id, expected, network) } expectedURL, _ := url.Parse(client.getURL("/networks/8dfafdbc3a40")) if gotPath := fakeRT.requests[0].URL.Path; gotPath != expectedURL.Path { t.Errorf("NetworkInfo(%q): Wrong path in request. Want %q. Got %q.", id, expectedURL.Path, gotPath) } } func TestNetworkCreate(t *testing.T) { jsonID := `{"ID": "8dfafdbc3a40"}` jsonNetwork := `{ "ID": "8dfafdbc3a40", "Name": "foobar", "Driver": "bridge" }` var expected Network err := json.Unmarshal([]byte(jsonNetwork), &expected) if err != nil { t.Fatal(err) } client := newTestClient(&FakeRoundTripper{message: jsonID, status: http.StatusOK}) opts := CreateNetworkOptions{Name: "foobar", Driver: "bridge"} network, err := client.CreateNetwork(opts) if err != nil { t.Fatal(err) } if !reflect.DeepEqual(*network, expected) { t.Errorf("CreateNetwork: Expected %#v. Got %#v.", expected, network) } } func TestNetworkRemove(t *testing.T) { t.Parallel() id := "8dfafdbc3a40" fakeRT := &FakeRoundTripper{message: "", status: http.StatusNoContent} client := newTestClient(fakeRT) err := client.RemoveNetwork(id) if err != nil { t.Fatal(err) } req := fakeRT.requests[0] expectedMethod := http.MethodDelete if req.Method != expectedMethod { t.Errorf("RemoveNetwork(%q): Wrong HTTP method. Want %s. Got %s.", id, expectedMethod, req.Method) } u, _ := url.Parse(client.getURL("/networks/" + id)) if req.URL.Path != u.Path { t.Errorf("RemoveNetwork(%q): Wrong request path. Want %q. Got %q.", id, u.Path, req.URL.Path) } } func TestNetworkConnect(t *testing.T) { t.Parallel() id := "8dfafdbc3a40" fakeRT := &FakeRoundTripper{message: "", status: http.StatusNoContent} client := newTestClient(fakeRT) opts := NetworkConnectionOptions{Container: "foobar"} err := client.ConnectNetwork(id, opts) if err != nil { t.Fatal(err) } req := fakeRT.requests[0] expectedMethod := http.MethodPost if req.Method != expectedMethod { t.Errorf("ConnectNetwork(%q): Wrong HTTP method. Want %s. Got %s.", id, expectedMethod, req.Method) } u, _ := url.Parse(client.getURL("/networks/" + id + "/connect")) if req.URL.Path != u.Path { t.Errorf("ConnectNetwork(%q): Wrong request path. Want %q. Got %q.", id, u.Path, req.URL.Path) } } func TestNetworkConnectWithEndpoint(t *testing.T) { t.Parallel() wantJSON := `{"Container":"foobar","EndpointConfig":{"IPAMConfig":{"IPv4Address":"8.8.8.8"},"Links":null,"Aliases":null},"Force":false}` var wantObj NetworkConnectionOptions json.NewDecoder(bytes.NewBuffer([]byte(wantJSON))).Decode(&wantObj) id := "8dfafdbc3a40" fakeRT := &FakeRoundTripper{message: "", status: http.StatusNoContent} client := newTestClient(fakeRT) opts := NetworkConnectionOptions{ Container: "foobar", EndpointConfig: &EndpointConfig{ IPAMConfig: &EndpointIPAMConfig{ IPv4Address: "8.8.8.8", }, }, } err := client.ConnectNetwork(id, opts) if err != nil { t.Fatal(err) } req := fakeRT.requests[0] expectedMethod := http.MethodPost if req.Method != expectedMethod { t.Errorf("ConnectNetwork(%q): Wrong HTTP method. Want %s. Got %s.", id, expectedMethod, req.Method) } u, _ := url.Parse(client.getURL("/networks/" + id + "/connect")) if req.URL.Path != u.Path { t.Errorf("ConnectNetwork(%q): Wrong request path. Want %q. Got %q.", id, u.Path, req.URL.Path) } var in NetworkConnectionOptions if err := json.NewDecoder(req.Body).Decode(&in); err != nil { t.Errorf("ConnectNetwork: error parsing JSON data sent: %q", err) } if !reflect.DeepEqual(in, wantObj) { t.Errorf("ConnectNetwork: wanted %#v send, got: %#v", wantObj, in) } } func TestNetworkConnectNotFound(t *testing.T) { t.Parallel() client := newTestClient(&FakeRoundTripper{message: "no such network container", status: http.StatusNotFound}) opts := NetworkConnectionOptions{Container: "foobar"} err := client.ConnectNetwork("8dfafdbc3a40", opts) var serr *NoSuchNetworkOrContainer if !errors.As(err, &serr) { t.Errorf("ConnectNetwork: wrong error type: %s.", serr) } } func TestNetworkDisconnect(t *testing.T) { t.Parallel() id := "8dfafdbc3a40" fakeRT := &FakeRoundTripper{message: "", status: http.StatusNoContent} client := newTestClient(fakeRT) opts := NetworkConnectionOptions{Container: "foobar"} err := client.DisconnectNetwork(id, opts) if err != nil { t.Fatal(err) } req := fakeRT.requests[0] expectedMethod := http.MethodPost if req.Method != expectedMethod { t.Errorf("DisconnectNetwork(%q): Wrong HTTP method. Want %s. Got %s.", id, expectedMethod, req.Method) } u, _ := url.Parse(client.getURL("/networks/" + id + "/disconnect")) if req.URL.Path != u.Path { t.Errorf("DisconnectNetwork(%q): Wrong request path. Want %q. Got %q.", id, u.Path, req.URL.Path) } } func TestNetworkDisconnectNotFound(t *testing.T) { t.Parallel() client := newTestClient(&FakeRoundTripper{message: "no such network container", status: http.StatusNotFound}) opts := NetworkConnectionOptions{Container: "foobar"} err := client.DisconnectNetwork("8dfafdbc3a40", opts) var serr *NoSuchNetworkOrContainer if !errors.As(err, &serr) { t.Errorf("DisconnectNetwork: wrong error type: %s.", serr) } } func TestPruneNetworks(t *testing.T) { t.Parallel() results := `{ "NetworksDeleted": [ "a", "b", "c" ] }` expected := &PruneNetworksResults{} err := json.Unmarshal([]byte(results), expected) if err != nil { t.Fatal(err) } client := newTestClient(&FakeRoundTripper{message: results, status: http.StatusOK}) got, err := client.PruneNetworks(PruneNetworksOptions{}) if err != nil { t.Fatal(err) } if !reflect.DeepEqual(got, expected) { t.Errorf("PruneNetworks: Expected %#v. Got %#v.", expected, got) } } go-dockerclient-1.12.0/plugin.go000066400000000000000000000352021465717111200165000ustar00rootroot00000000000000// Copyright 2018 go-dockerclient authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package docker import ( "context" "encoding/json" "errors" "io" "net/http" ) // PluginPrivilege represents a privilege for a plugin. type PluginPrivilege struct { Name string `json:"Name,omitempty" yaml:"Name,omitempty" toml:"Name,omitempty"` Description string `json:"Description,omitempty" yaml:"Description,omitempty" toml:"Description,omitempty"` Value []string `json:"Value,omitempty" yaml:"Value,omitempty" toml:"Value,omitempty"` } // InstallPluginOptions specify parameters to the InstallPlugins function. // // See https://goo.gl/C4t7Tz for more details. type InstallPluginOptions struct { Remote string Name string Plugins []PluginPrivilege `qs:"-"` Auth AuthConfiguration Context context.Context } // InstallPlugins installs a plugin or returns an error in case of failure. // // See https://goo.gl/C4t7Tz for more details. func (c *Client) InstallPlugins(opts InstallPluginOptions) error { headers, err := headersWithAuth(opts.Auth) if err != nil { return err } path := "/plugins/pull?" + queryString(opts) resp, err := c.do(http.MethodPost, path, doOptions{ data: opts.Plugins, context: opts.Context, headers: headers, }) if err != nil { return err } defer resp.Body.Close() // PullPlugin streams back the progress of the pull, we must consume the whole body // otherwise the pull will be canceled on the engine. if _, err := io.ReadAll(resp.Body); err != nil { return err } return nil } // PluginSettings stores plugin settings. // // See https://goo.gl/C4t7Tz for more details. type PluginSettings struct { Env []string `json:"Env,omitempty" yaml:"Env,omitempty" toml:"Env,omitempty"` Args []string `json:"Args,omitempty" yaml:"Args,omitempty" toml:"Args,omitempty"` Devices []string `json:"Devices,omitempty" yaml:"Devices,omitempty" toml:"Devices,omitempty"` } // PluginInterface stores plugin interface. // // See https://goo.gl/C4t7Tz for more details. type PluginInterface struct { Types []string `json:"Types,omitempty" yaml:"Types,omitempty" toml:"Types,omitempty"` Socket string `json:"Socket,omitempty" yaml:"Socket,omitempty" toml:"Socket,omitempty"` } // PluginNetwork stores plugin network type. // // See https://goo.gl/C4t7Tz for more details. type PluginNetwork struct { Type string `json:"Type,omitempty" yaml:"Type,omitempty" toml:"Type,omitempty"` } // PluginLinux stores plugin linux setting. // // See https://goo.gl/C4t7Tz for more details. type PluginLinux struct { Capabilities []string `json:"Capabilities,omitempty" yaml:"Capabilities,omitempty" toml:"Capabilities,omitempty"` AllowAllDevices bool `json:"AllowAllDevices,omitempty" yaml:"AllowAllDevices,omitempty" toml:"AllowAllDevices,omitempty"` Devices []PluginLinuxDevices `json:"Devices,omitempty" yaml:"Devices,omitempty" toml:"Devices,omitempty"` } // PluginLinuxDevices stores plugin linux device setting. // // See https://goo.gl/C4t7Tz for more details. type PluginLinuxDevices struct { Name string `json:"Name,omitempty" yaml:"Name,omitempty" toml:"Name,omitempty"` Description string `json:"Documentation,omitempty" yaml:"Documentation,omitempty" toml:"Documentation,omitempty"` Settable []string `json:"Settable,omitempty" yaml:"Settable,omitempty" toml:"Settable,omitempty"` Path string `json:"Path,omitempty" yaml:"Path,omitempty" toml:"Path,omitempty"` } // PluginEnv stores plugin environment. // // See https://goo.gl/C4t7Tz for more details. type PluginEnv struct { Name string `json:"Name,omitempty" yaml:"Name,omitempty" toml:"Name,omitempty"` Description string `json:"Description,omitempty" yaml:"Description,omitempty" toml:"Description,omitempty"` Settable []string `json:"Settable,omitempty" yaml:"Settable,omitempty" toml:"Settable,omitempty"` Value string `json:"Value,omitempty" yaml:"Value,omitempty" toml:"Value,omitempty"` } // PluginArgs stores plugin arguments. // // See https://goo.gl/C4t7Tz for more details. type PluginArgs struct { Name string `json:"Name,omitempty" yaml:"Name,omitempty" toml:"Name,omitempty"` Description string `json:"Description,omitempty" yaml:"Description,omitempty" toml:"Description,omitempty"` Settable []string `json:"Settable,omitempty" yaml:"Settable,omitempty" toml:"Settable,omitempty"` Value []string `json:"Value,omitempty" yaml:"Value,omitempty" toml:"Value,omitempty"` } // PluginUser stores plugin user. // // See https://goo.gl/C4t7Tz for more details. type PluginUser struct { UID int32 `json:"UID,omitempty" yaml:"UID,omitempty" toml:"UID,omitempty"` GID int32 `json:"GID,omitempty" yaml:"GID,omitempty" toml:"GID,omitempty"` } // PluginConfig stores plugin config. // // See https://goo.gl/C4t7Tz for more details. type PluginConfig struct { Description string `json:"Description,omitempty" yaml:"Description,omitempty" toml:"Description,omitempty"` Documentation string Interface PluginInterface `json:"Interface,omitempty" yaml:"Interface,omitempty" toml:"Interface,omitempty"` Entrypoint []string `json:"Entrypoint,omitempty" yaml:"Entrypoint,omitempty" toml:"Entrypoint,omitempty"` WorkDir string `json:"WorkDir,omitempty" yaml:"WorkDir,omitempty" toml:"WorkDir,omitempty"` User PluginUser `json:"User,omitempty" yaml:"User,omitempty" toml:"User,omitempty"` Network PluginNetwork `json:"Network,omitempty" yaml:"Network,omitempty" toml:"Network,omitempty"` Linux PluginLinux `json:"Linux,omitempty" yaml:"Linux,omitempty" toml:"Linux,omitempty"` PropagatedMount string `json:"PropagatedMount,omitempty" yaml:"PropagatedMount,omitempty" toml:"PropagatedMount,omitempty"` Mounts []Mount `json:"Mounts,omitempty" yaml:"Mounts,omitempty" toml:"Mounts,omitempty"` Env []PluginEnv `json:"Env,omitempty" yaml:"Env,omitempty" toml:"Env,omitempty"` Args PluginArgs `json:"Args,omitempty" yaml:"Args,omitempty" toml:"Args,omitempty"` } // PluginDetail specify results from the ListPlugins function. // // See https://goo.gl/C4t7Tz for more details. type PluginDetail struct { ID string `json:"Id,omitempty" yaml:"Id,omitempty" toml:"Id,omitempty"` Name string `json:"Name,omitempty" yaml:"Name,omitempty" toml:"Name,omitempty"` Tag string `json:"Tag,omitempty" yaml:"Tag,omitempty" toml:"Tag,omitempty"` Active bool `json:"Enabled,omitempty" yaml:"Active,omitempty" toml:"Active,omitempty"` Settings PluginSettings `json:"Settings,omitempty" yaml:"Settings,omitempty" toml:"Settings,omitempty"` Config PluginConfig `json:"Config,omitempty" yaml:"Config,omitempty" toml:"Config,omitempty"` } // ListPlugins returns pluginDetails or an error. // // See https://goo.gl/C4t7Tz for more details. func (c *Client) ListPlugins(ctx context.Context) ([]PluginDetail, error) { resp, err := c.do(http.MethodGet, "/plugins", doOptions{ context: ctx, }) if err != nil { return nil, err } defer resp.Body.Close() pluginDetails := make([]PluginDetail, 0) if err := json.NewDecoder(resp.Body).Decode(&pluginDetails); err != nil { return nil, err } return pluginDetails, nil } // ListFilteredPluginsOptions specify parameters to the ListFilteredPlugins function. // // See https://goo.gl/C4t7Tz for more details. type ListFilteredPluginsOptions struct { Filters map[string][]string Context context.Context } // ListFilteredPlugins returns pluginDetails or an error. // // See https://goo.gl/rmdmWg for more details. func (c *Client) ListFilteredPlugins(opts ListFilteredPluginsOptions) ([]PluginDetail, error) { path := "/plugins/json?" + queryString(opts) resp, err := c.do(http.MethodGet, path, doOptions{ context: opts.Context, }) if err != nil { return nil, err } defer resp.Body.Close() pluginDetails := make([]PluginDetail, 0) if err := json.NewDecoder(resp.Body).Decode(&pluginDetails); err != nil { return nil, err } return pluginDetails, nil } // GetPluginPrivileges returns pluginPrivileges or an error. // // See https://goo.gl/C4t7Tz for more details. func (c *Client) GetPluginPrivileges(remote string, ctx context.Context) ([]PluginPrivilege, error) { return c.GetPluginPrivilegesWithOptions( GetPluginPrivilegesOptions{ Remote: remote, Context: ctx, }) } // GetPluginPrivilegesOptions specify parameters to the GetPluginPrivilegesWithOptions function. // // See https://goo.gl/C4t7Tz for more details. type GetPluginPrivilegesOptions struct { Remote string Auth AuthConfiguration Context context.Context } // GetPluginPrivilegesWithOptions returns pluginPrivileges or an error. // // See https://goo.gl/C4t7Tz for more details. func (c *Client) GetPluginPrivilegesWithOptions(opts GetPluginPrivilegesOptions) ([]PluginPrivilege, error) { headers, err := headersWithAuth(opts.Auth) if err != nil { return nil, err } path := "/plugins/privileges?" + queryString(opts) resp, err := c.do(http.MethodGet, path, doOptions{ context: opts.Context, headers: headers, }) if err != nil { return nil, err } defer resp.Body.Close() var pluginPrivileges []PluginPrivilege if err := json.NewDecoder(resp.Body).Decode(&pluginPrivileges); err != nil { return nil, err } return pluginPrivileges, nil } // InspectPlugins returns a pluginDetail or an error. // // See https://goo.gl/C4t7Tz for more details. func (c *Client) InspectPlugins(name string, ctx context.Context) (*PluginDetail, error) { resp, err := c.do(http.MethodGet, "/plugins/"+name+"/json", doOptions{ context: ctx, }) if err != nil { var e *Error if errors.As(err, &e) && e.Status == http.StatusNotFound { return nil, &NoSuchPlugin{ID: name} } return nil, err } defer resp.Body.Close() var pluginDetail PluginDetail if err := json.NewDecoder(resp.Body).Decode(&pluginDetail); err != nil { return nil, err } return &pluginDetail, nil } // RemovePluginOptions specify parameters to the RemovePlugin function. // // See https://goo.gl/C4t7Tz for more details. type RemovePluginOptions struct { // The Name of the plugin. Name string `qs:"-"` Force bool `qs:"force"` Context context.Context } // RemovePlugin returns a PluginDetail or an error. // // See https://goo.gl/C4t7Tz for more details. func (c *Client) RemovePlugin(opts RemovePluginOptions) (*PluginDetail, error) { path := "/plugins/" + opts.Name + "?" + queryString(opts) resp, err := c.do(http.MethodDelete, path, doOptions{context: opts.Context}) if err != nil { var e *Error if errors.As(err, &e) && e.Status == http.StatusNotFound { return nil, &NoSuchPlugin{ID: opts.Name} } return nil, err } defer resp.Body.Close() body, err := io.ReadAll(resp.Body) if err != nil { return nil, err } if len(body) == 0 { // Seems like newer docker versions won't return the plugindetail after removal return nil, nil } var pluginDetail PluginDetail if err := json.Unmarshal(body, &pluginDetail); err != nil { return nil, err } return &pluginDetail, nil } // EnablePluginOptions specify parameters to the EnablePlugin function. // // See https://goo.gl/C4t7Tz for more details. type EnablePluginOptions struct { // The Name of the plugin. Name string `qs:"-"` Timeout int64 `qs:"timeout"` Context context.Context } // EnablePlugin enables plugin that opts point or returns an error. // // See https://goo.gl/C4t7Tz for more details. func (c *Client) EnablePlugin(opts EnablePluginOptions) error { path := "/plugins/" + opts.Name + "/enable?" + queryString(opts) resp, err := c.do(http.MethodPost, path, doOptions{context: opts.Context}) if err != nil { return err } resp.Body.Close() return nil } // DisablePluginOptions specify parameters to the DisablePlugin function. // // See https://goo.gl/C4t7Tz for more details. type DisablePluginOptions struct { // The Name of the plugin. Name string `qs:"-"` Context context.Context } // DisablePlugin disables plugin that opts point or returns an error. // // See https://goo.gl/C4t7Tz for more details. func (c *Client) DisablePlugin(opts DisablePluginOptions) error { path := "/plugins/" + opts.Name + "/disable" resp, err := c.do(http.MethodPost, path, doOptions{context: opts.Context}) if err != nil { return err } resp.Body.Close() return nil } // CreatePluginOptions specify parameters to the CreatePlugin function. // // See https://goo.gl/C4t7Tz for more details. type CreatePluginOptions struct { // The Name of the plugin. Name string `qs:"name"` // Path to tar containing plugin Path string `qs:"-"` Context context.Context } // CreatePlugin creates plugin that opts point or returns an error. // // See https://goo.gl/C4t7Tz for more details. func (c *Client) CreatePlugin(opts CreatePluginOptions) (string, error) { path := "/plugins/create?" + queryString(opts) resp, err := c.do(http.MethodPost, path, doOptions{ data: opts.Path, context: opts.Context, }) if err != nil { return "", err } defer resp.Body.Close() containerNameBytes, err := io.ReadAll(resp.Body) if err != nil { return "", err } return string(containerNameBytes), nil } // PushPluginOptions specify parameters to PushPlugin function. // // See https://goo.gl/C4t7Tz for more details. type PushPluginOptions struct { // The Name of the plugin. Name string Context context.Context } // PushPlugin pushes plugin that opts point or returns an error. // // See https://goo.gl/C4t7Tz for more details. func (c *Client) PushPlugin(opts PushPluginOptions) error { path := "/plugins/" + opts.Name + "/push" resp, err := c.do(http.MethodPost, path, doOptions{context: opts.Context}) if err != nil { return err } resp.Body.Close() return nil } // ConfigurePluginOptions specify parameters to the ConfigurePlugin // // See https://goo.gl/C4t7Tz for more details. type ConfigurePluginOptions struct { // The Name of the plugin. Name string `qs:"name"` Envs []string Context context.Context } // ConfigurePlugin configures plugin that opts point or returns an error. // // See https://goo.gl/C4t7Tz for more details. func (c *Client) ConfigurePlugin(opts ConfigurePluginOptions) error { path := "/plugins/" + opts.Name + "/set" resp, err := c.do(http.MethodPost, path, doOptions{ data: opts.Envs, context: opts.Context, }) if err != nil { var e *Error if errors.As(err, &e) && e.Status == http.StatusNotFound { return &NoSuchPlugin{ID: opts.Name} } return err } resp.Body.Close() return nil } // NoSuchPlugin is the error returned when a given plugin does not exist. type NoSuchPlugin struct { ID string Err error } func (err *NoSuchPlugin) Error() string { if err.Err != nil { return err.Err.Error() } return "No such plugin: " + err.ID } go-dockerclient-1.12.0/plugin_test.go000066400000000000000000000231671465717111200175460ustar00rootroot00000000000000// Copyright 2018 go-dockerclient authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package docker import ( "context" "encoding/json" "fmt" "net/http" "reflect" "testing" ) var expectPluginDetail = PluginDetail{ ID: "5724e2c8652da337ab2eedd19fc6fc0ec908e4bd907c7421bf6a8dfc70c4c078", Name: "tiborvass/sample-volume-plugin", Tag: "latest", Active: true, Settings: PluginSettings{ Env: []string{"DEBUG=0"}, Args: nil, Devices: nil, }, Config: PluginConfig{ Description: "A sample volume plugin for Docker", Documentation: "https://docs.docker.com/engine/extend/plugins/", Interface: PluginInterface{ Types: []string{"docker.volumedriver/1.0"}, Socket: "plugins.sock", }, Entrypoint: []string{ "/usr/bin/sample-volume-plugin", "/data", }, WorkDir: "", User: PluginUser{}, Network: PluginNetwork{Type: ""}, Linux: PluginLinux{Capabilities: nil, AllowAllDevices: false, Devices: nil}, Mounts: nil, PropagatedMount: "/data", Env: []PluginEnv{ { Name: "DEBUG", Description: "If set, prints debug messages", Settable: nil, Value: "0", }, }, Args: PluginArgs{ Name: "args", Description: "command line arguments", Settable: nil, Value: []string{}, }, }, } const jsonPluginDetail = `{ "Id": "5724e2c8652da337ab2eedd19fc6fc0ec908e4bd907c7421bf6a8dfc70c4c078", "Name": "tiborvass/sample-volume-plugin", "Tag": "latest", "Enabled": true, "Settings": { "Env": [ "DEBUG=0" ], "Args": null, "Devices": null }, "Config": { "Description": "A sample volume plugin for Docker", "Documentation": "https://docs.docker.com/engine/extend/plugins/", "Interface": { "Types": [ "docker.volumedriver/1.0" ], "Socket": "plugins.sock" }, "Entrypoint": [ "/usr/bin/sample-volume-plugin", "/data" ], "WorkDir": "", "User": {}, "Network": { "Type": "" }, "Linux": { "Capabilities": null, "AllowAllDevices": false, "Devices": null }, "Mounts": null, "PropagatedMount": "/data", "Env": [ { "Name": "DEBUG", "Description": "If set, prints debug messages", "Settable": null, "Value": "0" } ], "Args": { "Name": "args", "Description": "command line arguments", "Settable": null, "Value": [] } } }` func TestListPlugins(t *testing.T) { t.Parallel() jsonPlugins := fmt.Sprintf("[%s]", jsonPluginDetail) var expected []PluginDetail err := json.Unmarshal([]byte(jsonPlugins), &expected) if err != nil { t.Fatal(err) } client := newTestClient(&FakeRoundTripper{message: jsonPlugins, status: http.StatusOK}) pluginDetails, err := client.ListPlugins(context.Background()) if err != nil { t.Fatal(err) } if !reflect.DeepEqual(pluginDetails, expected) { t.Errorf("ListPlugins: Expected %#v. Got %#v.", expected, pluginDetails) } } func TestListFilteredPlugins(t *testing.T) { t.Parallel() jsonPlugins := fmt.Sprintf("[%s]", jsonPluginDetail) var expected []PluginDetail err := json.Unmarshal([]byte(jsonPlugins), &expected) if err != nil { t.Fatal(err) } client := newTestClient(&FakeRoundTripper{message: jsonPlugins, status: http.StatusOK}) pluginDetails, err := client.ListFilteredPlugins( ListFilteredPluginsOptions{ Filters: map[string][]string{ "capability": {"volumedriver"}, "enabled": {"true"}, }, Context: context.Background(), }) if err != nil { t.Fatal(err) } if !reflect.DeepEqual(pluginDetails, expected) { t.Errorf("ListPlugins: Expected %#v. Got %#v.", expected, pluginDetails) } } func TestListFilteredPluginsFailure(t *testing.T) { t.Parallel() tests := []struct { status int message string }{ {400, "bad parameter"}, {500, "internal server error"}, } for _, tt := range tests { client := newTestClient(&FakeRoundTripper{message: tt.message, status: tt.status}) expected := Error{Status: tt.status, Message: tt.message} pluginDetails, err := client.ListFilteredPlugins(ListFilteredPluginsOptions{}) if !reflect.DeepEqual(expected, *err.(*Error)) { t.Errorf("Wrong error in ListFilteredPlugins. Want %#v. Got %#v.", expected, err) } if len(pluginDetails) > 0 { t.Errorf("ListFilteredPlugins failure. Expected empty list. Got %#v.", pluginDetails) } } } func TestGetPluginPrivileges(t *testing.T) { t.Parallel() name := "test_plugin" jsonPluginPrivileges := `[ { "Name": "network", "Description": "", "Value": [ "host" ] }]` fakeRT := &FakeRoundTripper{message: jsonPluginPrivileges, status: http.StatusNoContent} client := newTestClient(fakeRT) expected := []PluginPrivilege{ { Name: "network", Description: "", Value: []string{"host"}, }, } pluginPrivileges, err := client.GetPluginPrivileges(name, context.Background()) if err != nil { t.Fatal(err) } if !reflect.DeepEqual(pluginPrivileges, expected) { t.Errorf("PluginPrivileges: Expected %#v. Got %#v.", expected, pluginPrivileges) } } func TestGetPluginPrivilegesWithOptions(t *testing.T) { t.Parallel() remote := "test_plugin" jsonPluginPrivileges := `[ { "Name": "network", "Description": "", "Value": [ "host" ] }]` fakeRT := &FakeRoundTripper{message: jsonPluginPrivileges, status: http.StatusNoContent} client := newTestClient(fakeRT) expected := []PluginPrivilege{ { Name: "network", Description: "", Value: []string{"host"}, }, } pluginPrivileges, err := client.GetPluginPrivilegesWithOptions(GetPluginPrivilegesOptions{ Remote: remote, Context: context.Background(), Auth: AuthConfiguration{Username: "XY"}, }) if err != nil { t.Fatal(err) } if !reflect.DeepEqual(pluginPrivileges, expected) { t.Errorf("PluginPrivileges: Expected %#v. Got %#v.", expected, pluginPrivileges) } req := fakeRT.requests[0] authHeader := req.Header.Get("X-Registry-Auth") if authHeader == "" { t.Errorf("InstallImage: unexpected empty X-Registry-Auth header: %v", authHeader) } } func TestInstallPlugins(t *testing.T) { opts := InstallPluginOptions{ Remote: "", Name: "test", Plugins: []PluginPrivilege{ { Name: "network", Description: "", Value: []string{"host"}, }, }, Context: context.Background(), Auth: AuthConfiguration{Username: "XY"}, } fakeRT := &FakeRoundTripper{message: "", status: http.StatusOK} client := newTestClient(fakeRT) err := client.InstallPlugins(opts) if err != nil { t.Fatal(err) } req := fakeRT.requests[0] authHeader := req.Header.Get("X-Registry-Auth") if authHeader == "" { t.Errorf("InstallImage: unexpected empty X-Registry-Auth header: %v", authHeader) } } func TestInspectPlugin(t *testing.T) { name := "test_plugin" fakeRT := &FakeRoundTripper{message: jsonPluginDetail, status: http.StatusNoContent} client := newTestClient(fakeRT) pluginPrivileges, err := client.InspectPlugins(name, context.Background()) if err != nil { t.Fatal(err) } if !reflect.DeepEqual(pluginPrivileges, &expectPluginDetail) { t.Errorf("InspectPlugins: Expected %#v. Got %#v.", &expectPluginDetail, pluginPrivileges) } } func TestRemovePlugin(t *testing.T) { opts := RemovePluginOptions{ Name: "test_plugin", Force: false, Context: context.Background(), } fakeRT := &FakeRoundTripper{message: jsonPluginDetail, status: http.StatusNoContent} client := newTestClient(fakeRT) pluginPrivileges, err := client.RemovePlugin(opts) if err != nil { t.Fatal(err) } if !reflect.DeepEqual(pluginPrivileges, &expectPluginDetail) { t.Errorf("RemovePlugin: Expected %#v. Got %#v.", &expectPluginDetail, pluginPrivileges) } } func TestRemovePluginNoResponse(t *testing.T) { opts := RemovePluginOptions{ Name: "test_plugin", Force: false, Context: context.Background(), } fakeRT := &FakeRoundTripper{message: "", status: http.StatusNoContent} client := newTestClient(fakeRT) plugindetails, err := client.RemovePlugin(opts) if err != nil { t.Fatal(err) } if plugindetails != nil { t.Errorf("RemovePlugin: Expected %#v. Got %#v.", nil, plugindetails) } } func TestEnablePlugin(t *testing.T) { opts := EnablePluginOptions{ Name: "test", Timeout: 5, Context: context.Background(), } client := newTestClient(&FakeRoundTripper{message: "", status: http.StatusOK}) err := client.EnablePlugin(opts) if err != nil { t.Fatal(err) } } func TestDisablePlugin(t *testing.T) { opts := DisablePluginOptions{ Name: "test", Context: context.Background(), } client := newTestClient(&FakeRoundTripper{message: "", status: http.StatusOK}) err := client.DisablePlugin(opts) if err != nil { t.Fatal(err) } } func TestCreatePlugin(t *testing.T) { opts := CreatePluginOptions{ Name: "test", Path: "", Context: context.Background(), } client := newTestClient(&FakeRoundTripper{message: "", status: http.StatusOK}) _, err := client.CreatePlugin(opts) if err != nil { t.Fatal(err) } } func TestPushPlugin(t *testing.T) { opts := PushPluginOptions{ Name: "test", Context: context.Background(), } client := newTestClient(&FakeRoundTripper{message: "", status: http.StatusOK}) err := client.PushPlugin(opts) if err != nil { t.Fatal(err) } } func TestConfigurePlugin(t *testing.T) { opts := ConfigurePluginOptions{ Name: "test", Envs: []string{}, Context: context.Background(), } client := newTestClient(&FakeRoundTripper{message: "", status: http.StatusOK}) err := client.ConfigurePlugin(opts) if err != nil { t.Fatal(err) } } go-dockerclient-1.12.0/registry_auth.go000066400000000000000000000003751465717111200200760ustar00rootroot00000000000000// Copyright 2013 go-dockerclient authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package docker type registryAuth interface { isEmpty() bool headerKey() string } go-dockerclient-1.12.0/signal.go000066400000000000000000000024131465717111200164550ustar00rootroot00000000000000// Copyright 2014 go-dockerclient authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package docker // Signal represents a signal that can be send to the container on // KillContainer call. type Signal int // These values represent all signals available on Linux, where containers will // be running. const ( SIGABRT = Signal(0x6) SIGALRM = Signal(0xe) SIGBUS = Signal(0x7) SIGCHLD = Signal(0x11) SIGCLD = Signal(0x11) SIGCONT = Signal(0x12) SIGFPE = Signal(0x8) SIGHUP = Signal(0x1) SIGILL = Signal(0x4) SIGINT = Signal(0x2) SIGIO = Signal(0x1d) SIGIOT = Signal(0x6) SIGKILL = Signal(0x9) SIGPIPE = Signal(0xd) SIGPOLL = Signal(0x1d) SIGPROF = Signal(0x1b) SIGPWR = Signal(0x1e) SIGQUIT = Signal(0x3) SIGSEGV = Signal(0xb) SIGSTKFLT = Signal(0x10) SIGSTOP = Signal(0x13) SIGSYS = Signal(0x1f) SIGTERM = Signal(0xf) SIGTRAP = Signal(0x5) SIGTSTP = Signal(0x14) SIGTTIN = Signal(0x15) SIGTTOU = Signal(0x16) SIGUNUSED = Signal(0x1f) SIGURG = Signal(0x17) SIGUSR1 = Signal(0xa) SIGUSR2 = Signal(0xc) SIGVTALRM = Signal(0x1a) SIGWINCH = Signal(0x1c) SIGXCPU = Signal(0x18) SIGXFSZ = Signal(0x19) ) go-dockerclient-1.12.0/swarm.go000066400000000000000000000110601465717111200163270ustar00rootroot00000000000000// Copyright 2016 go-dockerclient authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package docker import ( "context" "encoding/json" "errors" "net/http" "net/url" "strconv" "github.com/docker/docker/api/types/swarm" ) var ( // ErrNodeAlreadyInSwarm is the error returned by InitSwarm and JoinSwarm // when the node is already part of a Swarm. ErrNodeAlreadyInSwarm = errors.New("node already in a Swarm") // ErrNodeNotInSwarm is the error returned by LeaveSwarm and UpdateSwarm // when the node is not part of a Swarm. ErrNodeNotInSwarm = errors.New("node is not in a Swarm") ) // InitSwarmOptions specify parameters to the InitSwarm function. // See https://goo.gl/hzkgWu for more details. type InitSwarmOptions struct { swarm.InitRequest Context context.Context } // InitSwarm initializes a new Swarm and returns the node ID. // See https://goo.gl/ZWyG1M for more details. func (c *Client) InitSwarm(opts InitSwarmOptions) (string, error) { path := "/swarm/init" resp, err := c.do(http.MethodPost, path, doOptions{ data: opts.InitRequest, forceJSON: true, context: opts.Context, }) if err != nil { var e *Error if errors.As(err, &e) && (e.Status == http.StatusNotAcceptable || e.Status == http.StatusServiceUnavailable) { return "", ErrNodeAlreadyInSwarm } return "", err } defer resp.Body.Close() var response string if err := json.NewDecoder(resp.Body).Decode(&response); err != nil { return "", err } return response, nil } // JoinSwarmOptions specify parameters to the JoinSwarm function. // See https://goo.gl/TdhJWU for more details. type JoinSwarmOptions struct { swarm.JoinRequest Context context.Context } // JoinSwarm joins an existing Swarm. // See https://goo.gl/N59IP1 for more details. func (c *Client) JoinSwarm(opts JoinSwarmOptions) error { path := "/swarm/join" resp, err := c.do(http.MethodPost, path, doOptions{ data: opts.JoinRequest, forceJSON: true, context: opts.Context, }) if err != nil { var e *Error if errors.As(err, &e) && (e.Status == http.StatusNotAcceptable || e.Status == http.StatusServiceUnavailable) { return ErrNodeAlreadyInSwarm } } resp.Body.Close() return err } // LeaveSwarmOptions specify parameters to the LeaveSwarm function. // See https://goo.gl/UWDlLg for more details. type LeaveSwarmOptions struct { Force bool Context context.Context } // LeaveSwarm leaves a Swarm. // See https://goo.gl/FTX1aD for more details. func (c *Client) LeaveSwarm(opts LeaveSwarmOptions) error { params := make(url.Values) params.Set("force", strconv.FormatBool(opts.Force)) path := "/swarm/leave?" + params.Encode() resp, err := c.do(http.MethodPost, path, doOptions{ context: opts.Context, }) if err != nil { var e *Error if errors.As(err, &e) && (e.Status == http.StatusNotAcceptable || e.Status == http.StatusServiceUnavailable) { return ErrNodeNotInSwarm } } resp.Body.Close() return err } // UpdateSwarmOptions specify parameters to the UpdateSwarm function. // See https://goo.gl/vFbq36 for more details. type UpdateSwarmOptions struct { Version int RotateWorkerToken bool RotateManagerToken bool Swarm swarm.Spec Context context.Context } // UpdateSwarm updates a Swarm. // See https://goo.gl/iJFnsw for more details. func (c *Client) UpdateSwarm(opts UpdateSwarmOptions) error { params := make(url.Values) params.Set("version", strconv.Itoa(opts.Version)) params.Set("rotateWorkerToken", strconv.FormatBool(opts.RotateWorkerToken)) params.Set("rotateManagerToken", strconv.FormatBool(opts.RotateManagerToken)) path := "/swarm/update?" + params.Encode() resp, err := c.do(http.MethodPost, path, doOptions{ data: opts.Swarm, forceJSON: true, context: opts.Context, }) if err != nil { var e *Error if errors.As(err, &e) && (e.Status == http.StatusNotAcceptable || e.Status == http.StatusServiceUnavailable) { return ErrNodeNotInSwarm } } resp.Body.Close() return err } // InspectSwarm inspects a Swarm. // See https://goo.gl/MFwgX9 for more details. func (c *Client) InspectSwarm(ctx context.Context) (swarm.Swarm, error) { response := swarm.Swarm{} resp, err := c.do(http.MethodGet, "/swarm", doOptions{ context: ctx, }) if err != nil { var e *Error if errors.As(err, &e) && (e.Status == http.StatusNotAcceptable || e.Status == http.StatusServiceUnavailable) { return response, ErrNodeNotInSwarm } return response, err } defer resp.Body.Close() err = json.NewDecoder(resp.Body).Decode(&response) return response, err } go-dockerclient-1.12.0/swarm_configs.go000066400000000000000000000107561465717111200200520ustar00rootroot00000000000000// Copyright 2017 go-dockerclient authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package docker import ( "context" "encoding/json" "errors" "net/http" "net/url" "strconv" "github.com/docker/docker/api/types/swarm" ) // NoSuchConfig is the error returned when a given config does not exist. type NoSuchConfig struct { ID string Err error } func (err *NoSuchConfig) Error() string { if err.Err != nil { return err.Err.Error() } return "No such config: " + err.ID } // CreateConfigOptions specify parameters to the CreateConfig function. // // See https://goo.gl/KrVjHz for more details. type CreateConfigOptions struct { Auth AuthConfiguration `qs:"-"` swarm.ConfigSpec Context context.Context } // CreateConfig creates a new config, returning the config instance // or an error in case of failure. // // See https://goo.gl/KrVjHz for more details. func (c *Client) CreateConfig(opts CreateConfigOptions) (*swarm.Config, error) { headers, err := headersWithAuth(opts.Auth) if err != nil { return nil, err } path := "/configs/create?" + queryString(opts) resp, err := c.do(http.MethodPost, path, doOptions{ headers: headers, data: opts.ConfigSpec, forceJSON: true, context: opts.Context, }) if err != nil { return nil, err } defer resp.Body.Close() var config swarm.Config if err := json.NewDecoder(resp.Body).Decode(&config); err != nil { return nil, err } return &config, nil } // RemoveConfigOptions encapsulates options to remove a config. // // See https://goo.gl/Tqrtya for more details. type RemoveConfigOptions struct { ID string `qs:"-"` Context context.Context } // RemoveConfig removes a config, returning an error in case of failure. // // See https://goo.gl/Tqrtya for more details. func (c *Client) RemoveConfig(opts RemoveConfigOptions) error { path := "/configs/" + opts.ID resp, err := c.do(http.MethodDelete, path, doOptions{context: opts.Context}) if err != nil { var e *Error if errors.As(err, &e) && e.Status == http.StatusNotFound { return &NoSuchConfig{ID: opts.ID} } return err } resp.Body.Close() return nil } // UpdateConfigOptions specify parameters to the UpdateConfig function. // // See https://goo.gl/wu3MmS for more details. type UpdateConfigOptions struct { Auth AuthConfiguration `qs:"-"` swarm.ConfigSpec Context context.Context Version uint64 } // UpdateConfig updates the config at ID with the options // // Only label can be updated // https://docs.docker.com/engine/api/v1.33/#operation/ConfigUpdate // See https://goo.gl/wu3MmS for more details. func (c *Client) UpdateConfig(id string, opts UpdateConfigOptions) error { headers, err := headersWithAuth(opts.Auth) if err != nil { return err } params := make(url.Values) params.Set("version", strconv.FormatUint(opts.Version, 10)) resp, err := c.do(http.MethodPost, "/configs/"+id+"/update?"+params.Encode(), doOptions{ headers: headers, data: opts.ConfigSpec, forceJSON: true, context: opts.Context, }) if err != nil { var e *Error if errors.As(err, &e) && e.Status == http.StatusNotFound { return &NoSuchConfig{ID: id} } return err } defer resp.Body.Close() return nil } // InspectConfig returns information about a config by its ID. // // See https://goo.gl/dHmr75 for more details. func (c *Client) InspectConfig(id string) (*swarm.Config, error) { path := "/configs/" + id resp, err := c.do(http.MethodGet, path, doOptions{}) if err != nil { var e *Error if errors.As(err, &e) && e.Status == http.StatusNotFound { return nil, &NoSuchConfig{ID: id} } return nil, err } defer resp.Body.Close() var config swarm.Config if err := json.NewDecoder(resp.Body).Decode(&config); err != nil { return nil, err } return &config, nil } // ListConfigsOptions specify parameters to the ListConfigs function. // // See https://goo.gl/DwvNMd for more details. type ListConfigsOptions struct { Filters map[string][]string Context context.Context } // ListConfigs returns a slice of configs matching the given criteria. // // See https://goo.gl/DwvNMd for more details. func (c *Client) ListConfigs(opts ListConfigsOptions) ([]swarm.Config, error) { path := "/configs?" + queryString(opts) resp, err := c.do(http.MethodGet, path, doOptions{context: opts.Context}) if err != nil { return nil, err } defer resp.Body.Close() var configs []swarm.Config if err := json.NewDecoder(resp.Body).Decode(&configs); err != nil { return nil, err } return configs, nil } go-dockerclient-1.12.0/swarm_configs_test.go000066400000000000000000000171011465717111200211000ustar00rootroot00000000000000// Copyright 2017 go-dockerclient authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package docker import ( "encoding/base64" "encoding/json" "net/http" "net/url" "reflect" "testing" "github.com/docker/docker/api/types/swarm" ) func TestCreateConfig(t *testing.T) { t.Parallel() result := `{ "Id": "d1c00f91353ab0fe368363fab76d124cc764f2db8e11832f89f5ce21c2ece675" }` var expected swarm.Config err := json.Unmarshal([]byte(result), &expected) if err != nil { t.Fatal(err) } fakeRT := &FakeRoundTripper{message: result, status: http.StatusOK} client := newTestClient(fakeRT) opts := CreateConfigOptions{} config, err := client.CreateConfig(opts) if err != nil { t.Fatal(err) } id := "d1c00f91353ab0fe368363fab76d124cc764f2db8e11832f89f5ce21c2ece675" if config.ID != id { t.Errorf("CreateConfig: wrong ID. Want %q. Got %q.", id, config.ID) } req := fakeRT.requests[0] if req.Method != http.MethodPost { t.Errorf("CreateConfig: wrong HTTP method. Want %q. Got %q.", http.MethodPost, req.Method) } expectedURL, _ := url.Parse(client.getURL("/configs/create")) if gotPath := req.URL.Path; gotPath != expectedURL.Path { t.Errorf("CreateConfig: Wrong path in request. Want %q. Got %q.", expectedURL.Path, gotPath) } var gotBody Config err = json.NewDecoder(req.Body).Decode(&gotBody) if err != nil { t.Fatal(err) } } func TestRemoveConfig(t *testing.T) { t.Parallel() fakeRT := &FakeRoundTripper{message: "", status: http.StatusOK} client := newTestClient(fakeRT) id := "d1c00f91353ab0fe368363fab76d124cc764f2db8e11832f89f5ce21c2ece675" opts := RemoveConfigOptions{ID: id} err := client.RemoveConfig(opts) if err != nil { t.Fatal(err) } req := fakeRT.requests[0] if req.Method != http.MethodDelete { t.Errorf("RemoveConfig(%q): wrong HTTP method. Want %q. Got %q.", id, http.MethodDelete, req.Method) } expectedURL, _ := url.Parse(client.getURL("/configs/" + id)) if gotPath := req.URL.Path; gotPath != expectedURL.Path { t.Errorf("RemoveConfig(%q): Wrong path in request. Want %q. Got %q.", id, expectedURL.Path, gotPath) } } func TestRemoveConfigNotFound(t *testing.T) { t.Parallel() client := newTestClient(&FakeRoundTripper{message: "no such config", status: http.StatusNotFound}) err := client.RemoveConfig(RemoveConfigOptions{ID: "a2334"}) expectNoSuchConfig(t, "a2334", err) } func TestUpdateConfig(t *testing.T) { t.Parallel() fakeRT := &FakeRoundTripper{message: "", status: http.StatusOK} client := newTestClient(fakeRT) id := "d1c00f91353ab0fe368363fab76d124cc764f2db8e11832f89f5ce21c2ece675" update := UpdateConfigOptions{Version: 23} err := client.UpdateConfig(id, update) if err != nil { t.Fatal(err) } req := fakeRT.requests[0] if req.Method != http.MethodPost { t.Errorf("UpdateConfig: wrong HTTP method. Want %q. Got %q.", http.MethodPost, req.Method) } expectedURL, _ := url.Parse(client.getURL("/configs/" + id + "/update?version=23")) if gotURI := req.URL.RequestURI(); gotURI != expectedURL.RequestURI() { t.Errorf("UpdateConfig: Wrong path in request. Want %q. Got %q.", expectedURL.RequestURI(), gotURI) } expectedContentType := "application/json" if contentType := req.Header.Get("Content-Type"); contentType != expectedContentType { t.Errorf("UpdateConfig: Wrong content-type in request. Want %q. Got %q.", expectedContentType, contentType) } var out UpdateConfigOptions if err := json.NewDecoder(req.Body).Decode(&out); err != nil { t.Fatal(err) } update.Version = 0 if !reflect.DeepEqual(out, update) { t.Errorf("UpdateConfig: wrong body\ngot %#v\nwant %#v", out, update) } } func TestUpdateConfigWithAuthentication(t *testing.T) { t.Parallel() fakeRT := &FakeRoundTripper{message: "", status: http.StatusOK} client := newTestClient(fakeRT) id := "d1c00f91353ab0fe368363fab76d124cc764f2db8e11832f89f5ce21c2ece675" update := UpdateConfigOptions{Version: 23} update.Auth = AuthConfiguration{ Username: "gopher", Password: "gopher123", Email: "gopher@tsuru.io", } err := client.UpdateConfig(id, update) if err != nil { t.Fatal(err) } req := fakeRT.requests[0] if req.Method != http.MethodPost { t.Errorf("UpdateConfig: wrong HTTP method. Want %q. Got %q.", http.MethodPost, req.Method) } expectedURL, _ := url.Parse(client.getURL("/configs/" + id + "/update?version=23")) if gotURI := req.URL.RequestURI(); gotURI != expectedURL.RequestURI() { t.Errorf("UpdateConfig: Wrong path in request. Want %q. Got %q.", expectedURL.RequestURI(), gotURI) } expectedContentType := "application/json" if contentType := req.Header.Get("Content-Type"); contentType != expectedContentType { t.Errorf("UpdateConfig: Wrong content-type in request. Want %q. Got %q.", expectedContentType, contentType) } var out UpdateConfigOptions if err := json.NewDecoder(req.Body).Decode(&out); err != nil { t.Fatal(err) } var updateAuth AuthConfiguration auth, err := base64.URLEncoding.DecodeString(req.Header.Get("X-Registry-Auth")) if err != nil { t.Errorf("UpdateConfig: caught error decoding auth. %#v", err.Error()) } err = json.Unmarshal(auth, &updateAuth) if err != nil { t.Fatal(err) } if !reflect.DeepEqual(updateAuth, update.Auth) { t.Errorf("UpdateConfig: wrong auth configuration. Want %#v. Got %#v", update.Auth, updateAuth) } } func TestUpdateConfigNotFound(t *testing.T) { t.Parallel() client := newTestClient(&FakeRoundTripper{message: "no such Config", status: http.StatusNotFound}) update := UpdateConfigOptions{} err := client.UpdateConfig("notfound", update) expectNoSuchConfig(t, "notfound", err) } func TestInspectConfigNotFound(t *testing.T) { t.Parallel() client := newTestClient(&FakeRoundTripper{message: "no such config", status: http.StatusNotFound}) config, err := client.InspectConfig("notfound") if config != nil { t.Errorf("InspectConfig: Expected Config, got %#v", config) } expectNoSuchConfig(t, "notfound", err) } func TestInspectConfig(t *testing.T) { t.Parallel() jsonConfig := `{ "ID": "ktnbjxoalbkvbvedmg1urrz8h", "Version": { "Index": 11 }, "CreatedAt": "2016-11-05T01:20:17.327670065Z", "UpdatedAt": "2016-11-05T01:20:17.327670065Z", "Spec": { "Name": "app-dev.crt" } }` var expected swarm.Config err := json.Unmarshal([]byte(jsonConfig), &expected) if err != nil { t.Fatal(err) } fakeRT := &FakeRoundTripper{message: jsonConfig, status: http.StatusOK} client := newTestClient(fakeRT) id := "ktnbjxoalbkvbvedmg1urrz8h" config, err := client.InspectConfig(id) if err != nil { t.Fatal(err) } if !reflect.DeepEqual(*config, expected) { t.Errorf("InspectConfig(%q): Expected %#v. Got %#v.", id, expected, config) } expectedURL, _ := url.Parse(client.getURL("/configs/ktnbjxoalbkvbvedmg1urrz8h")) if gotPath := fakeRT.requests[0].URL.Path; gotPath != expectedURL.Path { t.Errorf("InspectConfig(%q): Wrong path in request. Want %q. Got %q.", id, expectedURL.Path, gotPath) } } func TestListConfigs(t *testing.T) { t.Parallel() jsonConfigs := `[ { "ID": "ktnbjxoalbkvbvedmg1urrz8h", "Version": { "Index": 11 }, "CreatedAt": "2016-11-05T01:20:17.327670065Z", "UpdatedAt": "2016-11-05T01:20:17.327670065Z", "Spec": { "Name": "server.conf" } } ]` var expected []swarm.Config err := json.Unmarshal([]byte(jsonConfigs), &expected) if err != nil { t.Fatal(err) } client := newTestClient(&FakeRoundTripper{message: jsonConfigs, status: http.StatusOK}) configs, err := client.ListConfigs(ListConfigsOptions{}) if err != nil { t.Fatal(err) } if !reflect.DeepEqual(configs, expected) { t.Errorf("ListConfigs: Expected %#v. Got %#v.", expected, configs) } } go-dockerclient-1.12.0/swarm_node.go000066400000000000000000000064341465717111200173450ustar00rootroot00000000000000// Copyright 2016 go-dockerclient authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package docker import ( "context" "encoding/json" "errors" "net/http" "net/url" "strconv" "github.com/docker/docker/api/types/swarm" ) // NoSuchNode is the error returned when a given node does not exist. type NoSuchNode struct { ID string Err error } func (err *NoSuchNode) Error() string { if err.Err != nil { return err.Err.Error() } return "No such node: " + err.ID } // ListNodesOptions specify parameters to the ListNodes function. // // See http://goo.gl/3K4GwU for more details. type ListNodesOptions struct { Filters map[string][]string Context context.Context } // ListNodes returns a slice of nodes matching the given criteria. // // See http://goo.gl/3K4GwU for more details. func (c *Client) ListNodes(opts ListNodesOptions) ([]swarm.Node, error) { path := "/nodes?" + queryString(opts) resp, err := c.do(http.MethodGet, path, doOptions{context: opts.Context}) if err != nil { return nil, err } defer resp.Body.Close() var nodes []swarm.Node if err := json.NewDecoder(resp.Body).Decode(&nodes); err != nil { return nil, err } return nodes, nil } // InspectNode returns information about a node by its ID. // // See http://goo.gl/WjkTOk for more details. func (c *Client) InspectNode(id string) (*swarm.Node, error) { resp, err := c.do(http.MethodGet, "/nodes/"+id, doOptions{}) if err != nil { var e *Error if errors.As(err, &e) && e.Status == http.StatusNotFound { return nil, &NoSuchNode{ID: id} } return nil, err } defer resp.Body.Close() var node swarm.Node if err := json.NewDecoder(resp.Body).Decode(&node); err != nil { return nil, err } return &node, nil } // UpdateNodeOptions specify parameters to the NodeUpdate function. // // See http://goo.gl/VPBFgA for more details. type UpdateNodeOptions struct { swarm.NodeSpec Version uint64 Context context.Context } // UpdateNode updates a node. // // See http://goo.gl/VPBFgA for more details. func (c *Client) UpdateNode(id string, opts UpdateNodeOptions) error { params := make(url.Values) params.Set("version", strconv.FormatUint(opts.Version, 10)) path := "/nodes/" + id + "/update?" + params.Encode() resp, err := c.do(http.MethodPost, path, doOptions{ context: opts.Context, forceJSON: true, data: opts.NodeSpec, }) if err != nil { var e *Error if errors.As(err, &e) && e.Status == http.StatusNotFound { return &NoSuchNode{ID: id} } return err } resp.Body.Close() return nil } // RemoveNodeOptions specify parameters to the RemoveNode function. // // See http://goo.gl/0SNvYg for more details. type RemoveNodeOptions struct { ID string Force bool Context context.Context } // RemoveNode removes a node. // // See http://goo.gl/0SNvYg for more details. func (c *Client) RemoveNode(opts RemoveNodeOptions) error { params := make(url.Values) params.Set("force", strconv.FormatBool(opts.Force)) path := "/nodes/" + opts.ID + "?" + params.Encode() resp, err := c.do(http.MethodDelete, path, doOptions{context: opts.Context}) if err != nil { var e *Error if errors.As(err, &e) && e.Status == http.StatusNotFound { return &NoSuchNode{ID: opts.ID} } return err } resp.Body.Close() return nil } go-dockerclient-1.12.0/swarm_node_test.go000066400000000000000000000146451465717111200204070ustar00rootroot00000000000000// Copyright 2016 go-dockerclient authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package docker import ( "encoding/json" "net/http" "net/url" "reflect" "testing" "github.com/docker/docker/api/types/swarm" ) func TestListNodes(t *testing.T) { t.Parallel() jsonNodes := `[ { "ID": "24ifsmvkjbyhk", "Version": { "Index": 8 }, "CreatedAt": "2016-06-07T20:31:11.853781916Z", "UpdatedAt": "2016-06-07T20:31:11.999868824Z", "Spec": { "Name": "my-node", "Role": "manager", "Availability": "active", "Labels": { "foo": "bar" } }, "Description": { "Hostname": "bf3067039e47", "Platform": { "Architecture": "x86_64", "OS": "linux" }, "Resources": { "NanoCPUs": 4000000000, "MemoryBytes": 8272408576 }, "Engine": { "EngineVersion": "1.12.0-dev", "Labels": { "foo": "bar" }, "Plugins": [ { "Type": "Volume", "Name": "local" }, { "Type": "Network", "Name": "bridge" }, { "Type": "Network", "Name": "null" }, { "Type": "Network", "Name": "overlay" } ] } }, "Status": { "State": "ready" }, "ManagerStatus": { "Leader": true, "Reachability": "reachable", "Addr": "172.17.0.2:2377" } } ]` var expected []swarm.Node err := json.Unmarshal([]byte(jsonNodes), &expected) if err != nil { t.Fatal(err) } client := newTestClient(&FakeRoundTripper{message: jsonNodes, status: http.StatusOK}) nodes, err := client.ListNodes(ListNodesOptions{}) if err != nil { t.Fatal(err) } if !reflect.DeepEqual(nodes, expected) { t.Errorf("ListNodes: Expected %#v. Got %#v.", expected, nodes) } } func TestInspectNode(t *testing.T) { t.Parallel() jsonNode := `{ "ID": "24ifsmvkjbyhk", "Version": { "Index": 8 }, "CreatedAt": "2016-06-07T20:31:11.853781916Z", "UpdatedAt": "2016-06-07T20:31:11.999868824Z", "Spec": { "Name": "my-node", "Role": "manager", "Availability": "active", "Labels": { "foo": "bar" } }, "Description": { "Hostname": "bf3067039e47", "Platform": { "Architecture": "x86_64", "OS": "linux" }, "Resources": { "NanoCPUs": 4000000000, "MemoryBytes": 8272408576 }, "Engine": { "EngineVersion": "1.12.0-dev", "Labels": { "foo": "bar" }, "Plugins": [ { "Type": "Volume", "Name": "local" }, { "Type": "Network", "Name": "bridge" }, { "Type": "Network", "Name": "null" }, { "Type": "Network", "Name": "overlay" } ] } }, "Status": { "State": "ready" }, "ManagerStatus": { "Leader": true, "Reachability": "reachable", "Addr": "172.17.0.2:2377" } }` var expected swarm.Node err := json.Unmarshal([]byte(jsonNode), &expected) if err != nil { t.Fatal(err) } fakeRT := &FakeRoundTripper{message: jsonNode, status: http.StatusOK} client := newTestClient(fakeRT) id := "24ifsmvkjbyhk" node, err := client.InspectNode(id) if err != nil { t.Fatal(err) } if !reflect.DeepEqual(*node, expected) { t.Errorf("InspectNode(%q): Expected %#v. Got %#v.", id, expected, node) } expectedURL, _ := url.Parse(client.getURL("/nodes/24ifsmvkjbyhk")) if gotPath := fakeRT.requests[0].URL.Path; gotPath != expectedURL.Path { t.Errorf("InspectNode(%q): Wrong path in request. Want %q. Got %q.", id, expectedURL.Path, gotPath) } } func TestInspectNodeNotFound(t *testing.T) { t.Parallel() client := newTestClient(&FakeRoundTripper{message: "no such node", status: http.StatusNotFound}) node, err := client.InspectNode("notfound") if node != nil { t.Errorf("InspectNode: Expected task, got %#v", node) } expectNoSuchNode(t, "notfound", err) } func TestUpdateNode(t *testing.T) { t.Parallel() fakeRT := &FakeRoundTripper{message: "", status: http.StatusOK} client := newTestClient(fakeRT) id := "4fa6e0f0c6786287e131c3852c58a2e01cc697a68231826813597e4994f1d6e2" opts := UpdateNodeOptions{} err := client.UpdateNode(id, opts) if err != nil { t.Fatal(err) } req := fakeRT.requests[0] if req.Method != http.MethodPost { t.Errorf("UpdateNode: wrong HTTP method. Want %q. Got %q.", http.MethodPost, req.Method) } expectedURL, _ := url.Parse(client.getURL("/nodes/" + id + "/update")) if gotPath := req.URL.Path; gotPath != expectedURL.Path { t.Errorf("UpdateNode: Wrong path in request. Want %q. Got %q.", expectedURL.Path, gotPath) } expectedContentType := "application/json" if contentType := req.Header.Get("Content-Type"); contentType != expectedContentType { t.Errorf("UpdateNode: Wrong content-type in request. Want %q. Got %q.", expectedContentType, contentType) } var out UpdateNodeOptions if err := json.NewDecoder(req.Body).Decode(&out); err != nil { t.Fatal(err) } if !reflect.DeepEqual(out, opts) { t.Errorf("UpdateNode: wrong body, got: %#v, want %#v", out, opts) } } func TestUpdateNodeNotFound(t *testing.T) { t.Parallel() client := newTestClient(&FakeRoundTripper{message: "no such node", status: http.StatusNotFound}) err := client.UpdateNode("notfound", UpdateNodeOptions{}) expectNoSuchNode(t, "notfound", err) } func TestRemoveNode(t *testing.T) { t.Parallel() fakeRT := &FakeRoundTripper{message: "", status: http.StatusOK} client := newTestClient(fakeRT) id := "4fa6e0f0c6786287e131c3852c58a2e01cc697a68231826813597e4994f1d6e2" err := client.RemoveNode(RemoveNodeOptions{ID: id}) if err != nil { t.Fatal(err) } req := fakeRT.requests[0] if req.Method != http.MethodDelete { t.Errorf("RemoveNode(%q): wrong HTTP method. Want %q. Got %q.", id, http.MethodDelete, req.Method) } expectedURL, _ := url.Parse(client.getURL("/nodes/" + id)) if gotPath := req.URL.Path; gotPath != expectedURL.Path { t.Errorf("RemoveNode(%q): Wrong path in request. Want %q. Got %q.", id, expectedURL.Path, gotPath) } } func TestRemoveNodeNotFound(t *testing.T) { t.Parallel() client := newTestClient(&FakeRoundTripper{message: "no such node", status: http.StatusNotFound}) err := client.RemoveNode(RemoveNodeOptions{ID: "notfound"}) expectNoSuchNode(t, "notfound", err) } go-dockerclient-1.12.0/swarm_secrets.go000066400000000000000000000107621465717111200200670ustar00rootroot00000000000000// Copyright 2016 go-dockerclient authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package docker import ( "context" "encoding/json" "errors" "net/http" "net/url" "strconv" "github.com/docker/docker/api/types/swarm" ) // NoSuchSecret is the error returned when a given secret does not exist. type NoSuchSecret struct { ID string Err error } func (err *NoSuchSecret) Error() string { if err.Err != nil { return err.Err.Error() } return "No such secret: " + err.ID } // CreateSecretOptions specify parameters to the CreateSecret function. // // See https://goo.gl/KrVjHz for more details. type CreateSecretOptions struct { Auth AuthConfiguration `qs:"-"` swarm.SecretSpec Context context.Context } // CreateSecret creates a new secret, returning the secret instance // or an error in case of failure. // // See https://goo.gl/KrVjHz for more details. func (c *Client) CreateSecret(opts CreateSecretOptions) (*swarm.Secret, error) { headers, err := headersWithAuth(opts.Auth) if err != nil { return nil, err } path := "/secrets/create?" + queryString(opts) resp, err := c.do(http.MethodPost, path, doOptions{ headers: headers, data: opts.SecretSpec, forceJSON: true, context: opts.Context, }) if err != nil { return nil, err } defer resp.Body.Close() var secret swarm.Secret if err := json.NewDecoder(resp.Body).Decode(&secret); err != nil { return nil, err } return &secret, nil } // RemoveSecretOptions encapsulates options to remove a secret. // // See https://goo.gl/Tqrtya for more details. type RemoveSecretOptions struct { ID string `qs:"-"` Context context.Context } // RemoveSecret removes a secret, returning an error in case of failure. // // See https://goo.gl/Tqrtya for more details. func (c *Client) RemoveSecret(opts RemoveSecretOptions) error { path := "/secrets/" + opts.ID resp, err := c.do(http.MethodDelete, path, doOptions{context: opts.Context}) if err != nil { var e *Error if errors.As(err, &e) && e.Status == http.StatusNotFound { return &NoSuchSecret{ID: opts.ID} } return err } resp.Body.Close() return nil } // UpdateSecretOptions specify parameters to the UpdateSecret function. // // Only label can be updated // See https://docs.docker.com/engine/api/v1.33/#operation/SecretUpdate // See https://goo.gl/wu3MmS for more details. type UpdateSecretOptions struct { Auth AuthConfiguration `qs:"-"` swarm.SecretSpec Context context.Context Version uint64 } // UpdateSecret updates the secret at ID with the options // // See https://goo.gl/wu3MmS for more details. func (c *Client) UpdateSecret(id string, opts UpdateSecretOptions) error { headers, err := headersWithAuth(opts.Auth) if err != nil { return err } params := make(url.Values) params.Set("version", strconv.FormatUint(opts.Version, 10)) resp, err := c.do(http.MethodPost, "/secrets/"+id+"/update?"+params.Encode(), doOptions{ headers: headers, data: opts.SecretSpec, forceJSON: true, context: opts.Context, }) if err != nil { var e *Error if errors.As(err, &e) && e.Status == http.StatusNotFound { return &NoSuchSecret{ID: id} } return err } defer resp.Body.Close() return nil } // InspectSecret returns information about a secret by its ID. // // See https://goo.gl/dHmr75 for more details. func (c *Client) InspectSecret(id string) (*swarm.Secret, error) { path := "/secrets/" + id resp, err := c.do(http.MethodGet, path, doOptions{}) if err != nil { var e *Error if errors.As(err, &e) && e.Status == http.StatusNotFound { return nil, &NoSuchSecret{ID: id} } return nil, err } defer resp.Body.Close() var secret swarm.Secret if err := json.NewDecoder(resp.Body).Decode(&secret); err != nil { return nil, err } return &secret, nil } // ListSecretsOptions specify parameters to the ListSecrets function. // // See https://goo.gl/DwvNMd for more details. type ListSecretsOptions struct { Filters map[string][]string Context context.Context } // ListSecrets returns a slice of secrets matching the given criteria. // // See https://goo.gl/DwvNMd for more details. func (c *Client) ListSecrets(opts ListSecretsOptions) ([]swarm.Secret, error) { path := "/secrets?" + queryString(opts) resp, err := c.do(http.MethodGet, path, doOptions{context: opts.Context}) if err != nil { return nil, err } defer resp.Body.Close() var secrets []swarm.Secret if err := json.NewDecoder(resp.Body).Decode(&secrets); err != nil { return nil, err } return secrets, nil } go-dockerclient-1.12.0/swarm_secrets_test.go000066400000000000000000000204331465717111200211220ustar00rootroot00000000000000// Copyright 2017 go-dockerclient authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package docker import ( "encoding/base64" "encoding/json" "net/http" "net/url" "reflect" "testing" "github.com/docker/docker/api/types/swarm" ) func TestCreateSecret(t *testing.T) { t.Parallel() result := `{ "Id": "13417726f7654bc286201f7c9accc98ccbd190efcc4753bf8ecfc0b61ef3dde8" }` var expected swarm.Secret err := json.Unmarshal([]byte(result), &expected) if err != nil { t.Fatal(err) } fakeRT := &FakeRoundTripper{message: result, status: http.StatusOK} client := newTestClient(fakeRT) opts := CreateSecretOptions{} secret, err := client.CreateSecret(opts) if err != nil { t.Fatal(err) } id := "13417726f7654bc286201f7c9accc98ccbd190efcc4753bf8ecfc0b61ef3dde8" if secret.ID != id { t.Errorf("CreateSecret: wrong ID. Want %q. Got %q.", id, secret.ID) } req := fakeRT.requests[0] if req.Method != http.MethodPost { t.Errorf("CreateSecret: wrong HTTP method. Want %q. Got %q.", http.MethodPost, req.Method) } expectedURL, _ := url.Parse(client.getURL("/secrets/create")) if gotPath := req.URL.Path; gotPath != expectedURL.Path { t.Errorf("CreateSecret: Wrong path in request. Want %q. Got %q.", expectedURL.Path, gotPath) } var gotBody Config err = json.NewDecoder(req.Body).Decode(&gotBody) if err != nil { t.Fatal(err) } } func TestRemoveSecret(t *testing.T) { t.Parallel() fakeRT := &FakeRoundTripper{message: "", status: http.StatusOK} client := newTestClient(fakeRT) id := "13417726f7654bc286201f7c9accc98ccbd190efcc4753bf8ecfc0b61ef3dde8" opts := RemoveSecretOptions{ID: id} err := client.RemoveSecret(opts) if err != nil { t.Fatal(err) } req := fakeRT.requests[0] if req.Method != http.MethodDelete { t.Errorf("RemoveSecret(%q): wrong HTTP method. Want %q. Got %q.", id, http.MethodDelete, req.Method) } expectedURL, _ := url.Parse(client.getURL("/secrets/" + id)) if gotPath := req.URL.Path; gotPath != expectedURL.Path { t.Errorf("RemoveSecret(%q): Wrong path in request. Want %q. Got %q.", id, expectedURL.Path, gotPath) } } func TestRemoveSecretNotFound(t *testing.T) { t.Parallel() client := newTestClient(&FakeRoundTripper{message: "no such secret", status: http.StatusNotFound}) err := client.RemoveSecret(RemoveSecretOptions{ID: "a2334"}) expectNoSuchSecret(t, "a2334", err) } func TestUpdateSecret(t *testing.T) { t.Parallel() fakeRT := &FakeRoundTripper{message: "", status: http.StatusOK} client := newTestClient(fakeRT) id := "13417726f7654bc286201f7c9accc98ccbd190efcc4753bf8ecfc0b61ef3dde8" update := UpdateSecretOptions{Version: 23} err := client.UpdateSecret(id, update) if err != nil { t.Fatal(err) } req := fakeRT.requests[0] if req.Method != http.MethodPost { t.Errorf("UpdateSecret: wrong HTTP method. Want %q. Got %q.", http.MethodPost, req.Method) } expectedURL, _ := url.Parse(client.getURL("/secrets/" + id + "/update?version=23")) if gotURI := req.URL.RequestURI(); gotURI != expectedURL.RequestURI() { t.Errorf("UpdateSecret: Wrong path in request. Want %q. Got %q.", expectedURL.RequestURI(), gotURI) } expectedContentType := "application/json" if contentType := req.Header.Get("Content-Type"); contentType != expectedContentType { t.Errorf("UpdateSecret: Wrong content-type in request. Want %q. Got %q.", expectedContentType, contentType) } var out UpdateSecretOptions if err := json.NewDecoder(req.Body).Decode(&out); err != nil { t.Fatal(err) } update.Version = 0 if !reflect.DeepEqual(out, update) { t.Errorf("UpdateSecret: wrong body\ngot %#v\nwant %#v", out, update) } } func TestUpdateSecretWithAuthentication(t *testing.T) { t.Parallel() fakeRT := &FakeRoundTripper{message: "", status: http.StatusOK} client := newTestClient(fakeRT) id := "13417726f7654bc286201f7c9accc98ccbd190efcc4753bf8ecfc0b61ef3dde8" update := UpdateSecretOptions{Version: 23} update.Auth = AuthConfiguration{ Username: "gopher", Password: "gopher123", Email: "gopher@tsuru.io", } err := client.UpdateSecret(id, update) if err != nil { t.Fatal(err) } req := fakeRT.requests[0] if req.Method != http.MethodPost { t.Errorf("UpdateSecret: wrong HTTP method. Want %q. Got %q.", http.MethodPost, req.Method) } expectedURL, _ := url.Parse(client.getURL("/secrets/" + id + "/update?version=23")) if gotURI := req.URL.RequestURI(); gotURI != expectedURL.RequestURI() { t.Errorf("UpdateSecret: Wrong path in request. Want %q. Got %q.", expectedURL.RequestURI(), gotURI) } expectedContentType := "application/json" if contentType := req.Header.Get("Content-Type"); contentType != expectedContentType { t.Errorf("UpdateSecret: Wrong content-type in request. Want %q. Got %q.", expectedContentType, contentType) } var out UpdateSecretOptions if err := json.NewDecoder(req.Body).Decode(&out); err != nil { t.Fatal(err) } var updateAuth AuthConfiguration auth, err := base64.URLEncoding.DecodeString(req.Header.Get("X-Registry-Auth")) if err != nil { t.Errorf("UpdateSecret: caught error decoding auth. %#v", err.Error()) } err = json.Unmarshal(auth, &updateAuth) if err != nil { t.Fatal(err) } if !reflect.DeepEqual(updateAuth, update.Auth) { t.Errorf("UpdateSecret: wrong auth configuration. Want %#v. Got %#v", update.Auth, updateAuth) } } func TestUpdateSecretNotFound(t *testing.T) { t.Parallel() client := newTestClient(&FakeRoundTripper{message: "no such Secret", status: http.StatusNotFound}) update := UpdateSecretOptions{} err := client.UpdateSecret("notfound", update) expectNoSuchSecret(t, "notfound", err) } func TestInspectSecretNotFound(t *testing.T) { t.Parallel() client := newTestClient(&FakeRoundTripper{message: "no such secret", status: http.StatusNotFound}) secret, err := client.InspectSecret("notfound") if secret != nil { t.Errorf("InspectSecret: Expected Secret, got %#v", secret) } expectNoSuchSecret(t, "notfound", err) } func TestInspectSecret(t *testing.T) { t.Parallel() jsonSecret := `{ "ID": "ak7w3gjqoa3kuz8xcpnyy0pvl", "Version": { "Index": 11 }, "CreatedAt": "2016-11-05T01:20:17.327670065Z", "UpdatedAt": "2016-11-05T01:20:17.327670065Z", "Spec": { "Name": "app-dev.crt", "Labels": { "foo": "bar" }, "Driver": { "Name": "secret-bucket", "Options": { "OptionA": "value for driver option A", "OptionB": "value for driver option B" } } } }` var expected swarm.Secret err := json.Unmarshal([]byte(jsonSecret), &expected) if err != nil { t.Fatal(err) } fakeRT := &FakeRoundTripper{message: jsonSecret, status: http.StatusOK} client := newTestClient(fakeRT) id := "ak7w3gjqoa3kuz8xcpnyy0pvl" secret, err := client.InspectSecret(id) if err != nil { t.Fatal(err) } if !reflect.DeepEqual(*secret, expected) { t.Errorf("InspectSecret(%q): Expected %#v. Got %#v.", id, expected, secret) } expectedURL, _ := url.Parse(client.getURL("/secrets/ak7w3gjqoa3kuz8xcpnyy0pvl")) if gotPath := fakeRT.requests[0].URL.Path; gotPath != expectedURL.Path { t.Errorf("InspectSecret(%q): Wrong path in request. Want %q. Got %q.", id, expectedURL.Path, gotPath) } } func TestListSecrets(t *testing.T) { t.Parallel() jsonSecrets := `[ { "ID": "blt1owaxmitz71s9v5zh81zun", "Version": { "Index": 85 }, "CreatedAt": "2017-07-20T13:55:28.678958722Z", "UpdatedAt": "2017-07-20T13:55:28.678958722Z", "Spec": { "Name": "mysql-passwd", "Labels": { "some.label": "some.value" }, "Driver": { "Name": "secret-bucket", "Options": { "OptionA": "value for driver option A", "OptionB": "value for driver option B" } } } }, { "ID": "ktnbjxoalbkvbvedmg1urrz8h", "Version": { "Index": 11 }, "CreatedAt": "2016-11-05T01:20:17.327670065Z", "UpdatedAt": "2016-11-05T01:20:17.327670065Z", "Spec": { "Name": "app-dev.crt", "Labels": { "foo": "bar" } } } ]` var expected []swarm.Secret err := json.Unmarshal([]byte(jsonSecrets), &expected) if err != nil { t.Fatal(err) } client := newTestClient(&FakeRoundTripper{message: jsonSecrets, status: http.StatusOK}) secrets, err := client.ListSecrets(ListSecretsOptions{}) if err != nil { t.Fatal(err) } if !reflect.DeepEqual(secrets, expected) { t.Errorf("ListSecrets: Expected %#v. Got %#v.", expected, secrets) } } go-dockerclient-1.12.0/swarm_service.go000066400000000000000000000136301465717111200200540ustar00rootroot00000000000000// Copyright 2016 go-dockerclient authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package docker import ( "context" "encoding/json" "errors" "io" "net/http" "time" "github.com/docker/docker/api/types/swarm" ) // NoSuchService is the error returned when a given service does not exist. type NoSuchService struct { ID string Err error } func (err *NoSuchService) Error() string { if err.Err != nil { return err.Err.Error() } return "No such service: " + err.ID } // CreateServiceOptions specify parameters to the CreateService function. // // See https://goo.gl/KrVjHz for more details. type CreateServiceOptions struct { Auth AuthConfiguration `qs:"-"` swarm.ServiceSpec Context context.Context } // CreateService creates a new service, returning the service instance // or an error in case of failure. // // See https://goo.gl/KrVjHz for more details. func (c *Client) CreateService(opts CreateServiceOptions) (*swarm.Service, error) { headers, err := headersWithAuth(opts.Auth) if err != nil { return nil, err } path := "/services/create?" + queryString(opts) resp, err := c.do(http.MethodPost, path, doOptions{ headers: headers, data: opts.ServiceSpec, forceJSON: true, context: opts.Context, }) if err != nil { return nil, err } defer resp.Body.Close() var service swarm.Service if err := json.NewDecoder(resp.Body).Decode(&service); err != nil { return nil, err } return &service, nil } // RemoveServiceOptions encapsulates options to remove a service. // // See https://goo.gl/Tqrtya for more details. type RemoveServiceOptions struct { ID string `qs:"-"` Context context.Context } // RemoveService removes a service, returning an error in case of failure. // // See https://goo.gl/Tqrtya for more details. func (c *Client) RemoveService(opts RemoveServiceOptions) error { path := "/services/" + opts.ID resp, err := c.do(http.MethodDelete, path, doOptions{context: opts.Context}) if err != nil { var e *Error if errors.As(err, &e) && e.Status == http.StatusNotFound { return &NoSuchService{ID: opts.ID} } return err } resp.Body.Close() return nil } // UpdateServiceOptions specify parameters to the UpdateService function. // // See https://goo.gl/wu3MmS for more details. type UpdateServiceOptions struct { Auth AuthConfiguration `qs:"-"` swarm.ServiceSpec `qs:"-"` Context context.Context Version uint64 Rollback string } // UpdateService updates the service at ID with the options // // See https://goo.gl/wu3MmS for more details. func (c *Client) UpdateService(id string, opts UpdateServiceOptions) error { headers, err := headersWithAuth(opts.Auth) if err != nil { return err } resp, err := c.do(http.MethodPost, "/services/"+id+"/update?"+queryString(opts), doOptions{ headers: headers, data: opts.ServiceSpec, forceJSON: true, context: opts.Context, }) if err != nil { var e *Error if errors.As(err, &e) && e.Status == http.StatusNotFound { return &NoSuchService{ID: id} } return err } defer resp.Body.Close() return nil } // InspectService returns information about a service by its ID. // // See https://goo.gl/dHmr75 for more details. func (c *Client) InspectService(id string) (*swarm.Service, error) { path := "/services/" + id resp, err := c.do(http.MethodGet, path, doOptions{}) if err != nil { var e *Error if errors.As(err, &e) && e.Status == http.StatusNotFound { return nil, &NoSuchService{ID: id} } return nil, err } defer resp.Body.Close() var service swarm.Service if err := json.NewDecoder(resp.Body).Decode(&service); err != nil { return nil, err } return &service, nil } // ListServicesOptions specify parameters to the ListServices function. // // See https://goo.gl/DwvNMd for more details. type ListServicesOptions struct { Filters map[string][]string Status bool Context context.Context } // ListServices returns a slice of services matching the given criteria. // // See https://goo.gl/DwvNMd for more details. func (c *Client) ListServices(opts ListServicesOptions) ([]swarm.Service, error) { path := "/services?" + queryString(opts) resp, err := c.do(http.MethodGet, path, doOptions{context: opts.Context}) if err != nil { return nil, err } defer resp.Body.Close() var services []swarm.Service if err := json.NewDecoder(resp.Body).Decode(&services); err != nil { return nil, err } return services, nil } // LogsServiceOptions represents the set of options used when getting logs from a // service. type LogsServiceOptions struct { Context context.Context Service string `qs:"-"` OutputStream io.Writer `qs:"-"` ErrorStream io.Writer `qs:"-"` InactivityTimeout time.Duration `qs:"-"` Tail string Since int64 // Use raw terminal? Usually true when the container contains a TTY. RawTerminal bool `qs:"-"` Follow bool Stdout bool Stderr bool Timestamps bool Details bool } // GetServiceLogs gets stdout and stderr logs from the specified service. // // When LogsServiceOptions.RawTerminal is set to false, go-dockerclient will multiplex // the streams and send the containers stdout to LogsServiceOptions.OutputStream, and // stderr to LogsServiceOptions.ErrorStream. // // When LogsServiceOptions.RawTerminal is true, callers will get the raw stream on // LogsServiceOptions.OutputStream. func (c *Client) GetServiceLogs(opts LogsServiceOptions) error { if opts.Service == "" { return &NoSuchService{ID: opts.Service} } if opts.Tail == "" { opts.Tail = "all" } path := "/services/" + opts.Service + "/logs?" + queryString(opts) return c.stream(http.MethodGet, path, streamOptions{ setRawTerminal: opts.RawTerminal, stdout: opts.OutputStream, stderr: opts.ErrorStream, inactivityTimeout: opts.InactivityTimeout, context: opts.Context, }) } go-dockerclient-1.12.0/swarm_service_test.go000066400000000000000000000432171465717111200211170ustar00rootroot00000000000000// Copyright 2016 go-dockerclient authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package docker import ( "bytes" "encoding/base64" "encoding/json" "errors" "net/http" "net/http/httptest" "net/url" "reflect" "testing" "github.com/docker/docker/api/types/swarm" ) func TestCreateService(t *testing.T) { t.Parallel() result := `{ "Id": "4fa6e0f0c6786287e131c3852c58a2e01cc697a68231826813597e4994f1d6e2" }` var expected swarm.Service err := json.Unmarshal([]byte(result), &expected) if err != nil { t.Fatal(err) } fakeRT := &FakeRoundTripper{message: result, status: http.StatusOK} client := newTestClient(fakeRT) opts := CreateServiceOptions{} service, err := client.CreateService(opts) if err != nil { t.Fatal(err) } id := "4fa6e0f0c6786287e131c3852c58a2e01cc697a68231826813597e4994f1d6e2" if service.ID != id { t.Errorf("CreateServce: wrong ID. Want %q. Got %q.", id, service.ID) } req := fakeRT.requests[0] if req.Method != http.MethodPost { t.Errorf("CreateService: wrong HTTP method. Want %q. Got %q.", http.MethodPost, req.Method) } expectedURL, _ := url.Parse(client.getURL("/services/create")) if gotPath := req.URL.Path; gotPath != expectedURL.Path { t.Errorf("CreateService: Wrong path in request. Want %q. Got %q.", expectedURL.Path, gotPath) } var gotBody Config err = json.NewDecoder(req.Body).Decode(&gotBody) if err != nil { t.Fatal(err) } authHeader, ok := req.Header["X-Registry-Auth"] if ok { t.Errorf("CreateService: unexpected non-empty X-Registry-Auth header: %v", authHeader) } } func TestCreateServiceWithAuthentication(t *testing.T) { t.Parallel() result := `{ "Id": "4fa6e0f0c6786287e131c3852c58a2e01cc697a68231826813597e4994f1d6e2" }` var expected swarm.Service err := json.Unmarshal([]byte(result), &expected) if err != nil { t.Fatal(err) } fakeRT := &FakeRoundTripper{message: result, status: http.StatusOK} client := newTestClient(fakeRT) opts := CreateServiceOptions{} opts.Auth = AuthConfiguration{ Username: "gopher", Password: "gopher123", Email: "gopher@tsuru.io", } service, err := client.CreateService(opts) if err != nil { t.Fatal(err) } id := "4fa6e0f0c6786287e131c3852c58a2e01cc697a68231826813597e4994f1d6e2" if service.ID != id { t.Errorf("CreateServce: wrong ID. Want %q. Got %q.", id, service.ID) } req := fakeRT.requests[0] if req.Method != http.MethodPost { t.Errorf("CreateService: wrong HTTP method. Want %q. Got %q.", http.MethodPost, req.Method) } expectedURL, _ := url.Parse(client.getURL("/services/create")) if gotPath := req.URL.Path; gotPath != expectedURL.Path { t.Errorf("CreateService: Wrong path in request. Want %q. Got %q.", expectedURL.Path, gotPath) } var gotBody Config err = json.NewDecoder(req.Body).Decode(&gotBody) if err != nil { t.Fatal(err) } var gotAuth AuthConfiguration auth, err := base64.URLEncoding.DecodeString(req.Header.Get("X-Registry-Auth")) if err != nil { t.Errorf("CreateService: caught error decoding auth. %#v", err.Error()) } err = json.Unmarshal(auth, &gotAuth) if err != nil { t.Fatal(err) } if !reflect.DeepEqual(gotAuth, opts.Auth) { t.Errorf("CreateService: wrong auth configuration. Want %#v. Got %#v.", opts.Auth, gotAuth) } } func TestRemoveService(t *testing.T) { t.Parallel() fakeRT := &FakeRoundTripper{message: "", status: http.StatusOK} client := newTestClient(fakeRT) id := "4fa6e0f0c6786287e131c3852c58a2e01cc697a68231826813597e4994f1d6e2" opts := RemoveServiceOptions{ID: id} err := client.RemoveService(opts) if err != nil { t.Fatal(err) } req := fakeRT.requests[0] if req.Method != http.MethodDelete { t.Errorf("RemoveService(%q): wrong HTTP method. Want %q. Got %q.", id, http.MethodDelete, req.Method) } expectedURL, _ := url.Parse(client.getURL("/services/" + id)) if gotPath := req.URL.Path; gotPath != expectedURL.Path { t.Errorf("RemoveService(%q): Wrong path in request. Want %q. Got %q.", id, expectedURL.Path, gotPath) } } func TestRemoveServiceNotFound(t *testing.T) { t.Parallel() client := newTestClient(&FakeRoundTripper{message: "no such service", status: http.StatusNotFound}) err := client.RemoveService(RemoveServiceOptions{ID: "a2334"}) expected := &NoSuchService{ID: "a2334"} var e *NoSuchService if errors.As(err, &e) && e.ID != expected.ID { t.Errorf("RemoveService: Wrong error returned. Want %#v. Got %#v.", expected, err) } } func TestUpdateService(t *testing.T) { t.Parallel() fakeRT := &FakeRoundTripper{message: "", status: http.StatusOK} client := newTestClient(fakeRT) id := "4fa6e0f0c6786287e131c3852c58a2e01cc697a68231826813597e4994f1d6e2" update := UpdateServiceOptions{Version: 23} err := client.UpdateService(id, update) if err != nil { t.Fatal(err) } req := fakeRT.requests[0] if req.Method != http.MethodPost { t.Errorf("UpdateService: wrong HTTP method. Want %q. Got %q.", http.MethodPost, req.Method) } expectedURL, _ := url.Parse(client.getURL("/services/" + id + "/update?version=23")) if gotURI := req.URL.RequestURI(); gotURI != expectedURL.RequestURI() { t.Errorf("UpdateService: Wrong path in request. Want %q. Got %q.", expectedURL.RequestURI(), gotURI) } expectedContentType := "application/json" if contentType := req.Header.Get("Content-Type"); contentType != expectedContentType { t.Errorf("UpdateService: Wrong content-type in request. Want %q. Got %q.", expectedContentType, contentType) } var out UpdateServiceOptions if err := json.NewDecoder(req.Body).Decode(&out); err != nil { t.Fatal(err) } update.Version = 0 if !reflect.DeepEqual(out, update) { t.Errorf("UpdateService: wrong body\ngot %#v\nwant %#v", out, update) } } func TestUpdateServiceRollback(t *testing.T) { t.Parallel() fakeRT := &FakeRoundTripper{message: "", status: http.StatusOK} client := newTestClient(fakeRT) id := "4fa6e0f0c6786287e131c3852c58a2e01cc697a68231826813597e4994f1d6e2" update := UpdateServiceOptions{Version: 23, Rollback: "previous"} err := client.UpdateService(id, update) if err != nil { t.Fatal(err) } req := fakeRT.requests[0] if req.Method != http.MethodPost { t.Errorf("UpdateService: wrong HTTP method. Want %q. Got %q.", http.MethodPost, req.Method) } expectedURL, _ := url.Parse(client.getURL("/services/" + id + "/update?version=23&rollback=previous")) if req.URL.Path != expectedURL.Path { t.Errorf("UpdateService: Wrong path in request. Want %q. Got %q.", expectedURL.Path, req.URL.Path) } if !reflect.DeepEqual(req.URL.Query(), expectedURL.Query()) { t.Errorf("UpdateService: Wrong querystring in request. Want %v. Got %v.", expectedURL.Query(), req.URL.Query()) } } func TestUpdateServiceWithAuthentication(t *testing.T) { t.Parallel() fakeRT := &FakeRoundTripper{message: "", status: http.StatusOK} client := newTestClient(fakeRT) id := "4fa6e0f0c6786287e131c3852c58a2e01cc697a68231826813597e4994f1d6e2" update := UpdateServiceOptions{Version: 23} update.Auth = AuthConfiguration{ Username: "gopher", Password: "gopher123", Email: "gopher@tsuru.io", } err := client.UpdateService(id, update) if err != nil { t.Fatal(err) } req := fakeRT.requests[0] if req.Method != http.MethodPost { t.Errorf("UpdateService: wrong HTTP method. Want %q. Got %q.", http.MethodPost, req.Method) } expectedURL, _ := url.Parse(client.getURL("/services/" + id + "/update?version=23")) if gotURI := req.URL.RequestURI(); gotURI != expectedURL.RequestURI() { t.Errorf("UpdateService: Wrong path in request. Want %q. Got %q.", expectedURL.RequestURI(), gotURI) } expectedContentType := "application/json" if contentType := req.Header.Get("Content-Type"); contentType != expectedContentType { t.Errorf("UpdateService: Wrong content-type in request. Want %q. Got %q.", expectedContentType, contentType) } var out UpdateServiceOptions if err := json.NewDecoder(req.Body).Decode(&out); err != nil { t.Fatal(err) } var updateAuth AuthConfiguration auth, err := base64.URLEncoding.DecodeString(req.Header.Get("X-Registry-Auth")) if err != nil { t.Errorf("UpdateService: caught error decoding auth. %#v", err.Error()) } err = json.Unmarshal(auth, &updateAuth) if err != nil { t.Fatal(err) } if !reflect.DeepEqual(updateAuth, update.Auth) { t.Errorf("UpdateService: wrong auth configuration. Want %#v. Got %#v", update.Auth, updateAuth) } } func TestUpdateServiceNotFound(t *testing.T) { t.Parallel() client := newTestClient(&FakeRoundTripper{message: "no such service", status: http.StatusNotFound}) update := UpdateServiceOptions{} err := client.UpdateService("notfound", update) expected := &NoSuchService{ID: "notfound"} var e *NoSuchService if errors.As(err, &e) && e.ID != expected.ID { t.Errorf("UpdateService: Wrong error returned. Want %#v. Got %#v.", expected, err) } } func TestInspectServiceNotFound(t *testing.T) { t.Parallel() client := newTestClient(&FakeRoundTripper{message: "no such service", status: http.StatusNotFound}) service, err := client.InspectService("notfound") if service != nil { t.Errorf("InspectService: Expected service, got %#v", service) } expected := &NoSuchService{ID: "notfound"} var e *NoSuchService if errors.As(err, &e) && e.ID != expected.ID { t.Errorf("InspectService: Wrong error returned. Want %#v. Got %#v.", expected, err) } } func TestInspectService(t *testing.T) { t.Parallel() jsonService := `{ "ID": "ak7w3gjqoa3kuz8xcpnyy0pvl", "Version": { "Index": 95 }, "CreatedAt": "2016-06-07T21:10:20.269723157Z", "UpdatedAt": "2016-06-07T21:10:20.276301259Z", "Spec": { "Name": "redis", "Task": { "ContainerSpec": { "Image": "redis" }, "Resources": { "Limits": {}, "Reservations": {} }, "RestartPolicy": { "Condition": "ANY" }, "Placement": {} }, "Mode": { "Replicated": { "Replicas": 1 } }, "UpdateConfig": { "Parallelism": 1 }, "EndpointSpec": { "Mode": "VIP", "Ingress": "PUBLICPORT", "ExposedPorts": [ { "Protocol": "tcp", "Port": 6379 } ] } }, "Endpoint": { "Spec": {}, "ExposedPorts": [ { "Protocol": "tcp", "Port": 6379, "PublicPort": 30001 } ], "VirtualIPs": [ { "NetworkID": "4qvuz4ko70xaltuqbt8956gd1", "Addr": "10.255.0.4/16" } ] } }` var expected swarm.Service err := json.Unmarshal([]byte(jsonService), &expected) if err != nil { t.Fatal(err) } fakeRT := &FakeRoundTripper{message: jsonService, status: http.StatusOK} client := newTestClient(fakeRT) id := "ak7w3gjqoa3kuz8xcpnyy0pvl" service, err := client.InspectService(id) if err != nil { t.Fatal(err) } if !reflect.DeepEqual(*service, expected) { t.Errorf("InspectService(%q): Expected %#v. Got %#v.", id, expected, service) } expectedURL, _ := url.Parse(client.getURL("/services/ak7w3gjqoa3kuz8xcpnyy0pvl")) if gotPath := fakeRT.requests[0].URL.Path; gotPath != expectedURL.Path { t.Errorf("InspectService(%q): Wrong path in request. Want %q. Got %q.", id, expectedURL.Path, gotPath) } } func TestListServices(t *testing.T) { t.Parallel() jsonServices := `[ { "ID": "9mnpnzenvg8p8tdbtq4wvbkcz", "Version": { "Index": 19 }, "CreatedAt": "2016-06-07T21:05:51.880065305Z", "UpdatedAt": "2016-06-07T21:07:29.962229872Z", "Spec": { "Name": "hopeful_cori", "TaskTemplate": { "ContainerSpec": { "Image": "redis" }, "Resources": { "Limits": {}, "Reservations": {} }, "RestartPolicy": { "Condition": "ANY" }, "Placement": {} }, "Mode": { "Replicated": { "Replicas": 1 } }, "UpdateConfig": { "Parallelism": 1 }, "EndpointSpec": { "Mode": "VIP", "Ingress": "PUBLICPORT", "ExposedPorts": [ { "Protocol": "tcp", "Port": 6379 } ] } }, "Endpoint": { "Spec": {}, "ExposedPorts": [ { "Protocol": "tcp", "Port": 6379, "PublicPort": 30000 } ], "VirtualIPs": [ { "NetworkID": "4qvuz4ko70xaltuqbt8956gd1", "Addr": "10.255.0.2/16" }, { "NetworkID": "4qvuz4ko70xaltuqbt8956gd1", "Addr": "10.255.0.3/16" } ] } } ]` var expected []swarm.Service err := json.Unmarshal([]byte(jsonServices), &expected) if err != nil { t.Fatal(err) } client := newTestClient(&FakeRoundTripper{message: jsonServices, status: http.StatusOK}) services, err := client.ListServices(ListServicesOptions{}) if err != nil { t.Fatal(err) } if !reflect.DeepEqual(services, expected) { t.Errorf("ListServices: Expected %#v. Got %#v.", expected, services) } } func TestGetServiceLogs(t *testing.T) { var req http.Request server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { prefix := []byte{1, 0, 0, 0, 0, 0, 0, 19} w.Write(prefix) w.Write([]byte("something happened!")) req = *r })) defer server.Close() client, _ := NewClient(server.URL) client.SkipServerVersionCheck = true var buf bytes.Buffer opts := LogsServiceOptions{ Service: "a123456", OutputStream: &buf, Follow: true, Stdout: true, Stderr: true, Timestamps: true, } err := client.GetServiceLogs(opts) if err != nil { t.Fatal(err) } expected := "something happened!" if buf.String() != expected { t.Errorf("Logs: wrong output. Want %q. Got %q.", expected, buf.String()) } if req.Method != http.MethodGet { t.Errorf("Logs: wrong HTTP method. Want GET. Got %s.", req.Method) } u, _ := url.Parse(client.getURL("/services/a123456/logs")) if req.URL.Path != u.Path { t.Errorf("AttachToContainer for logs: wrong HTTP path. Want %q. Got %q.", u.Path, req.URL.Path) } expectedQs := map[string][]string{ "follow": {"1"}, "stdout": {"1"}, "stderr": {"1"}, "timestamps": {"1"}, "tail": {"all"}, } got := map[string][]string(req.URL.Query()) if !reflect.DeepEqual(got, expectedQs) { t.Errorf("Logs: wrong query string. Want %#v. Got %#v.", expectedQs, got) } } func TestGetServicetLogsNilStdoutDoesntFail(t *testing.T) { server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { prefix := []byte{1, 0, 0, 0, 0, 0, 0, 19} w.Write(prefix) w.Write([]byte("something happened!")) })) defer server.Close() client, _ := NewClient(server.URL) client.SkipServerVersionCheck = true opts := LogsServiceOptions{ Service: "a123456", Follow: true, Stdout: true, Stderr: true, Timestamps: true, } err := client.GetServiceLogs(opts) if err != nil { t.Fatal(err) } } func TestGetServiceLogsNilStderrDoesntFail(t *testing.T) { server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { prefix := []byte{2, 0, 0, 0, 0, 0, 0, 19} w.Write(prefix) w.Write([]byte("something happened!")) })) defer server.Close() client, _ := NewClient(server.URL) client.SkipServerVersionCheck = true opts := LogsServiceOptions{ Service: "a123456", Follow: true, Stdout: true, Stderr: true, Timestamps: true, } err := client.GetServiceLogs(opts) if err != nil { t.Fatal(err) } } func TestGetServiceLogsSpecifyingTail(t *testing.T) { var req http.Request server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { prefix := []byte{1, 0, 0, 0, 0, 0, 0, 19} w.Write(prefix) w.Write([]byte("something happened!")) req = *r })) defer server.Close() client, _ := NewClient(server.URL) client.SkipServerVersionCheck = true var buf bytes.Buffer opts := LogsServiceOptions{ Service: "a123456", OutputStream: &buf, Follow: true, Stdout: true, Stderr: true, Timestamps: true, Tail: "100", } err := client.GetServiceLogs(opts) if err != nil { t.Fatal(err) } expected := "something happened!" if buf.String() != expected { t.Errorf("Logs: wrong output. Want %q. Got %q.", expected, buf.String()) } if req.Method != http.MethodGet { t.Errorf("Logs: wrong HTTP method. Want GET. Got %s.", req.Method) } u, _ := url.Parse(client.getURL("/services/a123456/logs")) if req.URL.Path != u.Path { t.Errorf("AttachToContainer for logs: wrong HTTP path. Want %q. Got %q.", u.Path, req.URL.Path) } expectedQs := map[string][]string{ "follow": {"1"}, "stdout": {"1"}, "stderr": {"1"}, "timestamps": {"1"}, "tail": {"100"}, } got := map[string][]string(req.URL.Query()) if !reflect.DeepEqual(got, expectedQs) { t.Errorf("Logs: wrong query string. Want %#v. Got %#v.", expectedQs, got) } } func TestGetServiceLogsRawTerminal(t *testing.T) { server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { w.Write([]byte("something happened!")) })) defer server.Close() client, _ := NewClient(server.URL) client.SkipServerVersionCheck = true var buf bytes.Buffer opts := LogsServiceOptions{ Service: "a123456", OutputStream: &buf, Follow: true, RawTerminal: true, Stdout: true, Stderr: true, Timestamps: true, Tail: "100", } err := client.GetServiceLogs(opts) if err != nil { t.Fatal(err) } expected := "something happened!" if buf.String() != expected { t.Errorf("Logs: wrong output. Want %q. Got %q.", expected, buf.String()) } } func TestGetServiceLogsNoContainer(t *testing.T) { var client Client err := client.GetServiceLogs(LogsServiceOptions{}) expected := &NoSuchService{ID: ""} var e *NoSuchService if errors.As(err, &e) && e.ID != expected.ID { t.Errorf("AttachToContainer: wrong error. Want %#v. Got %#v.", expected, err) } } go-dockerclient-1.12.0/swarm_task.go000066400000000000000000000033671465717111200173640ustar00rootroot00000000000000// Copyright 2016 go-dockerclient authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package docker import ( "context" "encoding/json" "errors" "net/http" "github.com/docker/docker/api/types/swarm" ) // NoSuchTask is the error returned when a given task does not exist. type NoSuchTask struct { ID string Err error } func (err *NoSuchTask) Error() string { if err.Err != nil { return err.Err.Error() } return "No such task: " + err.ID } // ListTasksOptions specify parameters to the ListTasks function. // // See http://goo.gl/rByLzw for more details. type ListTasksOptions struct { Filters map[string][]string Context context.Context } // ListTasks returns a slice of tasks matching the given criteria. // // See http://goo.gl/rByLzw for more details. func (c *Client) ListTasks(opts ListTasksOptions) ([]swarm.Task, error) { path := "/tasks?" + queryString(opts) resp, err := c.do(http.MethodGet, path, doOptions{context: opts.Context}) if err != nil { return nil, err } defer resp.Body.Close() var tasks []swarm.Task if err := json.NewDecoder(resp.Body).Decode(&tasks); err != nil { return nil, err } return tasks, nil } // InspectTask returns information about a task by its ID. // // See http://goo.gl/kyziuq for more details. func (c *Client) InspectTask(id string) (*swarm.Task, error) { resp, err := c.do(http.MethodGet, "/tasks/"+id, doOptions{}) if err != nil { var e *Error if errors.As(err, &e) && e.Status == http.StatusNotFound { return nil, &NoSuchTask{ID: id} } return nil, err } defer resp.Body.Close() var task swarm.Task if err := json.NewDecoder(resp.Body).Decode(&task); err != nil { return nil, err } return &task, nil } go-dockerclient-1.12.0/swarm_task_test.go000066400000000000000000000217171465717111200204220ustar00rootroot00000000000000// Copyright 2016 go-dockerclient authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package docker import ( "encoding/json" "errors" "net/http" "net/url" "reflect" "testing" "github.com/docker/docker/api/types/swarm" ) func TestListTasks(t *testing.T) { t.Parallel() jsonTasks := `[ { "ID": "0kzzo1i0y4jz6027t0k7aezc7", "Version": { "Index": 71 }, "CreatedAt": "2016-06-07T21:07:31.171892745Z", "UpdatedAt": "2016-06-07T21:07:31.376370513Z", "Name": "hopeful_cori", "Spec": { "ContainerSpec": { "Image": "redis" }, "Resources": { "Limits": {}, "Reservations": {} }, "RestartPolicy": { "Condition": "ANY" }, "Placement": {} }, "ServiceID": "9mnpnzenvg8p8tdbtq4wvbkcz", "Instance": 1, "NodeID": "24ifsmvkjbyhk", "ServiceAnnotations": {}, "Status": { "Timestamp": "2016-06-07T21:07:31.290032978Z", "State": "FAILED", "Message": "execution failed", "ContainerStatus": {} }, "DesiredState": "SHUTDOWN", "NetworksAttachments": [ { "Network": { "ID": "4qvuz4ko70xaltuqbt8956gd1", "Version": { "Index": 18 }, "CreatedAt": "2016-06-07T20:31:11.912919752Z", "UpdatedAt": "2016-06-07T21:07:29.955277358Z", "Spec": { "Name": "ingress", "Labels": { "com.docker.swarm.internal": "true" }, "DriverConfiguration": {}, "IPAM": { "Driver": {}, "Configs": [ { "Family": "UNKNOWN", "Subnet": "10.255.0.0/16" } ] } }, "DriverState": { "Name": "overlay", "Options": { "com.docker.network.driver.overlay.vxlanid_list": "256" } }, "IPAM": { "Driver": { "Name": "default" }, "Configs": [ { "Family": "UNKNOWN", "Subnet": "10.255.0.0/16" } ] } }, "Addresses": [ "10.255.0.10/16" ] } ], "Endpoint": { "Spec": {}, "ExposedPorts": [ { "Protocol": "tcp", "Port": 6379, "PublicPort": 30000 } ], "VirtualIPs": [ { "NetworkID": "4qvuz4ko70xaltuqbt8956gd1", "Addr": "10.255.0.2/16" }, { "NetworkID": "4qvuz4ko70xaltuqbt8956gd1", "Addr": "10.255.0.3/16" } ] } }, { "ID": "1yljwbmlr8er2waf8orvqpwms", "Version": { "Index": 30 }, "CreatedAt": "2016-06-07T21:07:30.019104782Z", "UpdatedAt": "2016-06-07T21:07:30.231958098Z", "Name": "hopeful_cori", "Spec": { "ContainerSpec": { "Image": "redis" }, "Resources": { "Limits": {}, "Reservations": {} }, "RestartPolicy": { "Condition": "ANY" }, "Placement": {} }, "ServiceID": "9mnpnzenvg8p8tdbtq4wvbkcz", "Instance": 1, "NodeID": "24ifsmvkjbyhk", "ServiceAnnotations": {}, "Status": { "Timestamp": "2016-06-07T21:07:30.202183143Z", "State": "FAILED", "Message": "execution failed", "ContainerStatus": {} }, "DesiredState": "SHUTDOWN", "NetworksAttachments": [ { "Network": { "ID": "4qvuz4ko70xaltuqbt8956gd1", "Version": { "Index": 18 }, "CreatedAt": "2016-06-07T20:31:11.912919752Z", "UpdatedAt": "2016-06-07T21:07:29.955277358Z", "Spec": { "Name": "ingress", "Labels": { "com.docker.swarm.internal": "true" }, "DriverConfiguration": {}, "IPAM": { "Driver": {}, "Configs": [ { "Family": "UNKNOWN", "Subnet": "10.255.0.0/16" } ] } }, "DriverState": { "Name": "overlay", "Options": { "com.docker.network.driver.overlay.vxlanid_list": "256" } }, "IPAM": { "Driver": { "Name": "default" }, "Configs": [ { "Family": "UNKNOWN", "Subnet": "10.255.0.0/16" } ] } }, "Addresses": [ "10.255.0.5/16" ] } ], "Endpoint": { "Spec": {}, "ExposedPorts": [ { "Protocol": "tcp", "Port": 6379, "PublicPort": 30000 } ], "VirtualIPs": [ { "NetworkID": "4qvuz4ko70xaltuqbt8956gd1", "Addr": "10.255.0.2/16" }, { "NetworkID": "4qvuz4ko70xaltuqbt8956gd1", "Addr": "10.255.0.3/16" } ] } } ]` var expected []swarm.Task err := json.Unmarshal([]byte(jsonTasks), &expected) if err != nil { t.Fatal(err) } client := newTestClient(&FakeRoundTripper{message: jsonTasks, status: http.StatusOK}) tasks, err := client.ListTasks(ListTasksOptions{}) if err != nil { t.Fatal(err) } if !reflect.DeepEqual(tasks, expected) { t.Errorf("ListTasks: Expected %#v. Got %#v.", expected, tasks) } } func TestInspectTask(t *testing.T) { t.Parallel() jsonTask := `{ "ID": "0kzzo1i0y4jz6027t0k7aezc7", "Version": { "Index": 71 }, "CreatedAt": "2016-06-07T21:07:31.171892745Z", "UpdatedAt": "2016-06-07T21:07:31.376370513Z", "Name": "hopeful_cori", "Spec": { "ContainerSpec": { "Image": "redis" }, "Resources": { "Limits": {}, "Reservations": {} }, "RestartPolicy": { "Condition": "ANY" }, "Placement": {} }, "ServiceID": "9mnpnzenvg8p8tdbtq4wvbkcz", "Instance": 1, "NodeID": "24ifsmvkjbyhk", "ServiceAnnotations": {}, "Status": { "Timestamp": "2016-06-07T21:07:31.290032978Z", "State": "FAILED", "Message": "execution failed", "ContainerStatus": {} }, "DesiredState": "SHUTDOWN", "NetworksAttachments": [ { "Network": { "ID": "4qvuz4ko70xaltuqbt8956gd1", "Version": { "Index": 18 }, "CreatedAt": "2016-06-07T20:31:11.912919752Z", "UpdatedAt": "2016-06-07T21:07:29.955277358Z", "Spec": { "Name": "ingress", "Labels": { "com.docker.swarm.internal": "true" }, "DriverConfiguration": {}, "IPAM": { "Driver": {}, "Configs": [ { "Family": "UNKNOWN", "Subnet": "10.255.0.0/16" } ] } }, "DriverState": { "Name": "overlay", "Options": { "com.docker.network.driver.overlay.vxlanid_list": "256" } }, "IPAM": { "Driver": { "Name": "default" }, "Configs": [ { "Family": "UNKNOWN", "Subnet": "10.255.0.0/16" } ] } }, "Addresses": [ "10.255.0.10/16" ] } ], "Endpoint": { "Spec": {}, "ExposedPorts": [ { "Protocol": "tcp", "Port": 6379, "PublicPort": 30000 } ], "VirtualIPs": [ { "NetworkID": "4qvuz4ko70xaltuqbt8956gd1", "Addr": "10.255.0.2/16" }, { "NetworkID": "4qvuz4ko70xaltuqbt8956gd1", "Addr": "10.255.0.3/16" } ] } }` var expected swarm.Task err := json.Unmarshal([]byte(jsonTask), &expected) if err != nil { t.Fatal(err) } fakeRT := &FakeRoundTripper{message: jsonTask, status: http.StatusOK} client := newTestClient(fakeRT) id := "0kzzo1i0y4jz6027t0k7aezc7" task, err := client.InspectTask(id) if err != nil { t.Fatal(err) } if !reflect.DeepEqual(*task, expected) { t.Errorf("InspectTask(%q): Expected %#v. Got %#v.", id, expected, task) } expectedURL, _ := url.Parse(client.getURL("/tasks/0kzzo1i0y4jz6027t0k7aezc7")) if gotPath := fakeRT.requests[0].URL.Path; gotPath != expectedURL.Path { t.Errorf("InspectTask(%q): Wrong path in request. Want %q. Got %q.", id, expectedURL.Path, gotPath) } } func TestInspectTaskNotFound(t *testing.T) { t.Parallel() const taskID = "notfound" client := newTestClient(&FakeRoundTripper{message: "no such task", status: http.StatusNotFound}) task, err := client.InspectTask(taskID) if task != nil { t.Errorf("InspectTask: Expected task, got %#v", task) } var taskErr *NoSuchTask if !errors.As(err, &taskErr) { t.Fatalf("InspectTask: wrong error tyope returned. Want %#v. Got %#v.", taskErr, err) } if taskErr.ID != taskID { t.Errorf("wrong taskID\nwant %q\ngot %q", taskID, taskErr.ID) } } go-dockerclient-1.12.0/swarm_test.go000066400000000000000000000162221465717111200173730ustar00rootroot00000000000000// Copyright 2016 go-dockerclient authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package docker import ( "context" "errors" "net/http" "net/url" "reflect" "testing" "github.com/docker/docker/api/types/swarm" ) func TestInitSwarm(t *testing.T) { t.Parallel() fakeRT := &FakeRoundTripper{message: `"body"`, status: http.StatusOK} client := newTestClient(fakeRT) response, err := client.InitSwarm(InitSwarmOptions{}) if err != nil { t.Fatal(err) } req := fakeRT.requests[0] expectedMethod := http.MethodPost if req.Method != expectedMethod { t.Errorf("InitSwarm: Wrong HTTP method. Want %s. Got %s.", expectedMethod, req.Method) } u, _ := url.Parse(client.getURL("/swarm/init")) if req.URL.Path != u.Path { t.Errorf("InitSwarm: Wrong request path. Want %q. Got %q.", u.Path, req.URL.Path) } expected := "body" if response != expected { t.Errorf("InitSwarm: Wrong response. Want %q. Got %q.", expected, response) } } func TestInitSwarmAlreadyInSwarm(t *testing.T) { t.Parallel() client := newTestClient(&FakeRoundTripper{message: "", status: http.StatusNotAcceptable}) _, err := client.InitSwarm(InitSwarmOptions{}) if !errors.Is(err, ErrNodeAlreadyInSwarm) { t.Errorf("InitSwarm: Wrong error type. Want %#v. Got %#v", ErrNodeAlreadyInSwarm, err) } client = newTestClient(&FakeRoundTripper{message: "", status: http.StatusServiceUnavailable}) _, err = client.InitSwarm(InitSwarmOptions{}) if !errors.Is(err, ErrNodeAlreadyInSwarm) { t.Errorf("InitSwarm: Wrong error type. Want %#v. Got %#v", ErrNodeAlreadyInSwarm, err) } } func TestJoinSwarm(t *testing.T) { t.Parallel() fakeRT := &FakeRoundTripper{message: "", status: http.StatusOK} client := newTestClient(fakeRT) err := client.JoinSwarm(JoinSwarmOptions{}) if err != nil { t.Fatal(err) } req := fakeRT.requests[0] expectedMethod := http.MethodPost if req.Method != expectedMethod { t.Errorf("JoinSwarm: Wrong HTTP method. Want %s. Got %s.", expectedMethod, req.Method) } u, _ := url.Parse(client.getURL("/swarm/join")) if req.URL.Path != u.Path { t.Errorf("JoinSwarm: Wrong request path. Want %q. Got %q.", u.Path, req.URL.Path) } } func TestJoinSwarmAlreadyInSwarm(t *testing.T) { t.Parallel() client := newTestClient(&FakeRoundTripper{message: "", status: http.StatusNotAcceptable}) err := client.JoinSwarm(JoinSwarmOptions{}) if !errors.Is(err, ErrNodeAlreadyInSwarm) { t.Errorf("JoinSwarm: Wrong error type. Want %#v. Got %#v", ErrNodeAlreadyInSwarm, err) } client = newTestClient(&FakeRoundTripper{message: "", status: http.StatusServiceUnavailable}) err = client.JoinSwarm(JoinSwarmOptions{}) if !errors.Is(err, ErrNodeAlreadyInSwarm) { t.Errorf("JoinSwarm: Wrong error type. Want %#v. Got %#v", ErrNodeAlreadyInSwarm, err) } } func TestLeaveSwarm(t *testing.T) { t.Parallel() fakeRT := &FakeRoundTripper{message: "", status: http.StatusOK} client := newTestClient(fakeRT) testData := []struct { force bool expectedURI string }{ {false, "/swarm/leave?force=false"}, {true, "/swarm/leave?force=true"}, } for i, tt := range testData { err := client.LeaveSwarm(LeaveSwarmOptions{Force: tt.force}) if err != nil { t.Fatal(err) } expectedMethod := http.MethodPost req := fakeRT.requests[i] if req.Method != expectedMethod { t.Errorf("LeaveSwarm: Wrong HTTP method. Want %s. Got %s.", expectedMethod, req.Method) } expected, _ := url.Parse(client.getURL(tt.expectedURI)) if req.URL.String() != expected.String() { t.Errorf("LeaveSwarm: Wrong request string. Want %q. Got %q.", expected.String(), req.URL.String()) } } } func TestLeaveSwarmNotInSwarm(t *testing.T) { t.Parallel() client := newTestClient(&FakeRoundTripper{message: "", status: http.StatusNotAcceptable}) err := client.LeaveSwarm(LeaveSwarmOptions{}) if !errors.Is(err, ErrNodeNotInSwarm) { t.Errorf("LeaveSwarm: Wrong error type. Want %#v. Got %#v", ErrNodeNotInSwarm, err) } client = newTestClient(&FakeRoundTripper{message: "", status: http.StatusServiceUnavailable}) err = client.LeaveSwarm(LeaveSwarmOptions{}) if !errors.Is(err, ErrNodeNotInSwarm) { t.Errorf("LeaveSwarm: Wrong error type. Want %#v. Got %#v", ErrNodeNotInSwarm, err) } } func TestUpdateSwarm(t *testing.T) { t.Parallel() fakeRT := &FakeRoundTripper{message: "", status: http.StatusOK} client := newTestClient(fakeRT) opts := UpdateSwarmOptions{ Version: 10, RotateManagerToken: true, RotateWorkerToken: false, } err := client.UpdateSwarm(opts) if err != nil { t.Fatal(err) } req := fakeRT.requests[0] expectedMethod := http.MethodPost if req.Method != expectedMethod { t.Errorf("UpdateSwarm: Wrong HTTP method. Want %s. Got %s.", expectedMethod, req.Method) } expectedPath := "/swarm/update" if req.URL.Path != expectedPath { t.Errorf("UpdateSwarm: Wrong request path. Want %q. Got %q.", expectedPath, req.URL.Path) } expected := map[string][]string{ "version": {"10"}, "rotateManagerToken": {"true"}, "rotateWorkerToken": {"false"}, } got := map[string][]string(req.URL.Query()) if !reflect.DeepEqual(got, expected) { t.Errorf("UpdateSwarm: Wrong request query. Want %v. Got %v", expected, got) } } func TestUpdateSwarmNotInSwarm(t *testing.T) { t.Parallel() client := newTestClient(&FakeRoundTripper{message: "", status: http.StatusNotAcceptable}) err := client.UpdateSwarm(UpdateSwarmOptions{}) if !errors.Is(err, ErrNodeNotInSwarm) { t.Errorf("UpdateSwarm: Wrong error type. Want %#v. Got %#v", ErrNodeNotInSwarm, err) } client = newTestClient(&FakeRoundTripper{message: "", status: http.StatusServiceUnavailable}) err = client.UpdateSwarm(UpdateSwarmOptions{}) if !errors.Is(err, ErrNodeNotInSwarm) { t.Errorf("UpdateSwarm: Wrong error type. Want %#v. Got %#v", ErrNodeNotInSwarm, err) } } func TestInspectSwarm(t *testing.T) { t.Parallel() fakeRT := &FakeRoundTripper{message: `{"ID": "123"}`, status: http.StatusOK} client := newTestClient(fakeRT) response, err := client.InspectSwarm(context.TODO()) if err != nil { t.Fatal(err) } req := fakeRT.requests[0] expectedMethod := http.MethodGet if req.Method != expectedMethod { t.Errorf("InspectSwarm: Wrong HTTP method. Want %s. Got %s.", expectedMethod, req.Method) } u, _ := url.Parse(client.getURL("/swarm")) if req.URL.Path != u.Path { t.Errorf("InspectSwarm: Wrong request path. Want %q. Got %q.", u.Path, req.URL.Path) } expected := swarm.Swarm{ClusterInfo: swarm.ClusterInfo{ID: "123"}} if !reflect.DeepEqual(expected, response) { t.Errorf("InspectSwarm: Wrong response. Want %#v. Got %#v.", expected, response) } } func TestInspectSwarmNotInSwarm(t *testing.T) { t.Parallel() client := newTestClient(&FakeRoundTripper{message: "", status: http.StatusNotAcceptable}) _, err := client.InspectSwarm(context.TODO()) if !errors.Is(err, ErrNodeNotInSwarm) { t.Errorf("InspectSwarm: Wrong error type. Want %#v. Got %#v", ErrNodeNotInSwarm, err) } client = newTestClient(&FakeRoundTripper{message: "", status: http.StatusServiceUnavailable}) _, err = client.InspectSwarm(context.TODO()) if !errors.Is(err, ErrNodeNotInSwarm) { t.Errorf("InspectSwarm: Wrong error type. Want %#v. Got %#v", ErrNodeNotInSwarm, err) } } go-dockerclient-1.12.0/system.go000066400000000000000000000041701465717111200165260ustar00rootroot00000000000000package docker import ( "context" "encoding/json" "net/http" ) // VolumeUsageData represents usage data from the docker system api // More Info Here https://dockr.ly/2PNzQyO type VolumeUsageData struct { // The number of containers referencing this volume. This field // is set to `-1` if the reference-count is not available. // // Required: true RefCount int64 `json:"RefCount"` // Amount of disk space used by the volume (in bytes). This information // is only available for volumes created with the `"local"` volume // driver. For volumes created with other volume drivers, this field // is set to `-1` ("not available") // // Required: true Size int64 `json:"Size"` } // ImageSummary represents data about what images are // currently known to docker // More Info Here https://dockr.ly/2PNzQyO type ImageSummary struct { Containers int64 `json:"Containers"` Created int64 `json:"Created"` ID string `json:"Id"` Labels map[string]string `json:"Labels"` ParentID string `json:"ParentId"` RepoDigests []string `json:"RepoDigests"` RepoTags []string `json:"RepoTags"` SharedSize int64 `json:"SharedSize"` Size int64 `json:"Size"` VirtualSize int64 `json:"VirtualSize"` } // DiskUsage holds information about what docker is using disk space on. // More Info Here https://dockr.ly/2PNzQyO type DiskUsage struct { LayersSize int64 Images []*ImageSummary Containers []*APIContainers Volumes []*Volume } // DiskUsageOptions only contains a context for canceling. type DiskUsageOptions struct { Context context.Context } // DiskUsage returns a *DiskUsage describing what docker is using disk on. // // More Info Here https://dockr.ly/2PNzQyO func (c *Client) DiskUsage(opts DiskUsageOptions) (*DiskUsage, error) { path := "/system/df" resp, err := c.do(http.MethodGet, path, doOptions{context: opts.Context}) if err != nil { return nil, err } defer resp.Body.Close() var du *DiskUsage if err := json.NewDecoder(resp.Body).Decode(&du); err != nil { return nil, err } return du, nil } go-dockerclient-1.12.0/system_test.go000066400000000000000000000054631465717111200175730ustar00rootroot00000000000000// Copyright 2015 go-dockerclient authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package docker import ( "encoding/json" "net/http" "reflect" "testing" ) func TestDiskUsage(t *testing.T) { t.Parallel() duData := ` { "LayersSize": 17667551166, "Images": [ { "Containers": 7, "Created": 1536130571, "Id": "sha256:056f6f1952204e38bd67cd2901c0cb2fc4cc8b640d1264814a9916b33eb34794", "Labels": null, "ParentId": "", "RepoDigests": [ "fnproject/fn-test-utils@sha256:2ce83a86519d48b4f0deec062887c8aebf483708f4b87c0756a7cb108ecc98f8" ], "RepoTags": [ "fnproject/fn-test-utils:latest" ], "SharedSize": 4413370, "Size": 10861179, "VirtualSize": 10861179 } ], "Containers": [ { "Id": "52bd62a82a72d8db8162eeef45a15dbec0a9066903631bff99a02c5e8dafcb3c", "Names": [ "/0_prefork_01CP3AMNDS0000000000000001" ], "Image": "busybox", "ImageID": "sha256:e1ddd7948a1c31709a23cc5b7dfe96e55fc364f90e1cebcde0773a1b5a30dcda", "Command": "tail -f /dev/null", "Created": 1535562634, "Ports": [], "SizeRootFs": 1162769, "Labels": {}, "State": "running", "Status": "Up 2 weeks", "HostConfig": { "NetworkMode": "default" }, "NetworkSettings": { "Networks": { "bridge": { "IPAMConfig": null, "Links": null, "Aliases": null, "NetworkID": "2e879f7f3faba9c4970920e31b1185cadccb8a5c564a8393871c5ae114c49b39", "EndpointID": "853c2b7bc4e7bd47834a45d0c93465ffaecea09103fcf4caa098c88b974f4124", "Gateway": "172.17.0.1", "IPAddress": "172.17.0.5", "IPPrefixLen": 16, "IPv6Gateway": "", "GlobalIPv6Address": "", "GlobalIPv6PrefixLen": 0, "MacAddress": "02:42:ac:11:00:05", "DriverOpts": null } } }, "Mounts": [] } ], "Volumes": [ { "CreatedAt": "2018-07-18T11:17:34-07:00", "Driver": "local", "Labels": null, "Mountpoint": "", "Name": "1284e17abce1d43818d7136849095c6a449a8dcfbaa859c2ff7c40abc75653eb", "Options": {}, "Scope": "local", "UsageData": { "RefCount": 0, "Size": 0 } } ], "BuilderSize": 0 } ` var expected *DiskUsage if err := json.Unmarshal([]byte(duData), &expected); err != nil { t.Fatal(err) } client := newTestClient(&FakeRoundTripper{message: duData, status: http.StatusOK}) du, err := client.DiskUsage(DiskUsageOptions{}) if err != nil { t.Error(err) } if !reflect.DeepEqual(du, expected) { t.Errorf("DiskUsage: Wrong return value. Want %#v. Got %#v.", expected, du) } } go-dockerclient-1.12.0/tar.go000066400000000000000000000065061465717111200157750ustar00rootroot00000000000000// Copyright 2014 go-dockerclient authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package docker import ( "fmt" "io" "os" "path" "path/filepath" "strings" "github.com/docker/docker/pkg/archive" "github.com/moby/patternmatcher" ) func createTarStream(srcPath, dockerfilePath string) (io.ReadCloser, error) { srcPath, err := filepath.Abs(srcPath) if err != nil { return nil, err } excludes, err := parseDockerignore(srcPath) if err != nil { return nil, err } includes := []string{"."} // If .dockerignore mentions .dockerignore or the Dockerfile // then make sure we send both files over to the daemon // because Dockerfile is, obviously, needed no matter what, and // .dockerignore is needed to know if either one needs to be // removed. The deamon will remove them for us, if needed, after it // parses the Dockerfile. // // https://github.com/docker/docker/issues/8330 // forceIncludeFiles := []string{".dockerignore", dockerfilePath} for _, includeFile := range forceIncludeFiles { if includeFile == "" { continue } keepThem, err := patternmatcher.Matches(includeFile, excludes) if err != nil { return nil, fmt.Errorf("cannot match .dockerfileignore: '%s', error: %w", includeFile, err) } if keepThem { includes = append(includes, includeFile) } } if err := validateContextDirectory(srcPath, excludes); err != nil { return nil, err } tarOpts := &archive.TarOptions{ ExcludePatterns: excludes, IncludeFiles: includes, Compression: archive.Uncompressed, NoLchown: true, } return archive.TarWithOptions(srcPath, tarOpts) } // validateContextDirectory checks if all the contents of the directory // can be read and returns an error if some files can't be read. // Symlinks which point to non-existing files don't trigger an error func validateContextDirectory(srcPath string, excludes []string) error { return filepath.Walk(filepath.Join(srcPath, "."), func(filePath string, f os.FileInfo, err error) error { // skip this directory/file if it's not in the path, it won't get added to the context if relFilePath, relErr := filepath.Rel(srcPath, filePath); relErr != nil { return relErr } else if skip, matchErr := patternmatcher.Matches(relFilePath, excludes); matchErr != nil { return matchErr } else if skip { if f.IsDir() { return filepath.SkipDir } return nil } if err != nil { if os.IsPermission(err) { return fmt.Errorf("cannot stat %q: %w", filePath, err) } if os.IsNotExist(err) { return nil } return err } // skip checking if symlinks point to non-existing files, such symlinks can be useful // also skip named pipes, because they hanging on open if f.Mode()&(os.ModeSymlink|os.ModeNamedPipe) != 0 { return nil } if !f.IsDir() { currentFile, err := os.Open(filePath) if err != nil { return fmt.Errorf("cannot open %q for reading: %w", filePath, err) } currentFile.Close() } return nil }) } func parseDockerignore(root string) ([]string, error) { var excludes []string ignore, err := os.ReadFile(path.Join(root, ".dockerignore")) if err != nil && !os.IsNotExist(err) { return excludes, fmt.Errorf("error reading .dockerignore: %w", err) } excludes = strings.Split(string(ignore), "\n") return excludes, nil } go-dockerclient-1.12.0/testing/000077500000000000000000000000001465717111200163265ustar00rootroot00000000000000go-dockerclient-1.12.0/testing/data/000077500000000000000000000000001465717111200172375ustar00rootroot00000000000000go-dockerclient-1.12.0/testing/data/.dockerignore000066400000000000000000000000451465717111200217120ustar00rootroot00000000000000container.tar dockerfile.tar foofile go-dockerclient-1.12.0/testing/data/Dockerfile000066400000000000000000000000641465717111200212310ustar00rootroot00000000000000from ubuntu run apt-get install wget -y --force-yes go-dockerclient-1.12.0/testing/data/barfile000066400000000000000000000000001465717111200205540ustar00rootroot00000000000000go-dockerclient-1.12.0/testing/data/ca.pem000066400000000000000000000025371465717111200203340ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- MIIDyzCCArOgAwIBAgIJAMqHPLMzLU8eMA0GCSqGSIb3DQEBCwUAMHwxCzAJBgNV BAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBX aWRnaXRzIFB0eSBMdGQxFDASBgNVBAMMC2Jvb3QyZG9ja2VyMR8wHQYJKoZIhvcN AQkBFhB1c2VyQGV4YW1wbGUuY29tMB4XDTE3MTAwNDA1MzkyMFoXDTI3MTAwMjA1 MzkyMFowfDELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNV BAoMGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDEUMBIGA1UEAwwLYm9vdDJkb2Nr ZXIxHzAdBgkqhkiG9w0BCQEWEHVzZXJAZXhhbXBsZS5jb20wggEiMA0GCSqGSIb3 DQEBAQUAA4IBDwAwggEKAoIBAQDGpWLvwBuD74o0XERYcoHAwN4FqzwKSuwVI0e6 7R+YCWDRBRLmrJIaqTEEMO/CbXA7eSYr8ak/pXxrgy7cf3eDIEMQIjyK11PSdVV2 ezMabxBzmyo0fESvBVPTOKY5HBo0+c0Q1pjPZOKFWjr3/xSnTQLHxztw9+yFo1vY /DGYK9Zb/gQly1rKpgMKiYzoYxLFSzL7EgGMDK9zVWs8MQthjsqR0wLKK4MBQXmr cOwNRRgo//bTgp+wf4NiCuOHw9DzvxVS3gFifCzab8QtBgurB2e0Ej5IjcHKsXvQ lkPFpX/5YEcRb71Ns+Wo3NsP7aFkdGYiRkyudbShxX+fp8ErAgMBAAGjUDBOMB0G A1UdDgQWBBRCxuobp2cZVi0FOlv2Cikv+yeleDAfBgNVHSMEGDAWgBRCxuobp2cZ Vi0FOlv2Cikv+yeleDAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQBj BuAJS3GbQJbm1c8x9OjKm/3EPwouxEDmbPI5xhjCfgHFiLqE8p60XAA8AH/l3Y8j r5ARH0NNmKf1fQ0AXO8Z7ctuHFYsVN8XN09se3F2o5sxkfDjQr5rLsxVJwwSfB5n 7FA+pv+6gWEqwa9P/mx+7YjRBNpRi2IZ2DMYNt5T1c2JXlcoDms99oMcpElKU0Vs VygGN86wDDrdUNcQotvtnNRhVxdNCyQGNxak/QolJQL57fdIkEvKLLhmCxT+IIyu bPHH6DiYI6ZxsHQb3CL6qfWO2MvIWYdW70/Wpzz/y2SSaUEo4LjUqLUw/Dd0+juv GSr4a8LUxiG7bkHa4xNS -----END CERTIFICATE----- go-dockerclient-1.12.0/testing/data/cert.pem000066400000000000000000000022701465717111200207000ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- MIIDUDCCAjigAwIBAgIJALw2I+++VX++MA0GCSqGSIb3DQEBCwUAMHwxCzAJBgNV BAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBX aWRnaXRzIFB0eSBMdGQxFDASBgNVBAMMC2Jvb3QyZG9ja2VyMR8wHQYJKoZIhvcN AQkBFhB1c2VyQGV4YW1wbGUuY29tMB4XDTE3MTAwNDA1NDEzNFoXDTI3MTAwMjA1 NDEzNFowETEPMA0GA1UEAwwGY2xpZW50MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A MIIBCgKCAQEAs1bJPjF06hdKcX47c2q24lYmTCJUd3lPaG83MjyZb16jQaZ8J1En ckULNbZaArYQgjAPxUVDpp1WVfRqqsXa6rT/BjTSp+Abtq+lcL2ABa5Rrf9XI8qB xs4EiiE45N9w0GxG2+WCUulu5ldDW7E9VeQ/yCwMc8cKYv+zZRxHj0fVRtAtk1ea idqhL0zmShfklPDa5EyNm03kfqVQcV3WpPRBfEbbek7sLbAXmJUdyp+uMeo8a147 pKbv0njRMfj0TA/ATzKF+Dq3fmpb+lCM0wuPJXwDgpWrKfO+50aRuax2sod3yHBU Vs33+qmzLBrq5OzTyRfk+ALQ+5md+l3F4wIDAQABo0AwPjAnBgNVHREEIDAeggti b290MmRvY2tlcoIJbG9jYWxob3N0hwR/AAABMBMGA1UdJQQMMAoGCCsGAQUFBwMC MA0GCSqGSIb3DQEBCwUAA4IBAQAtlDz0QjW//j0NMKnNZv5IDLhomXMFzou2FUD1 j93H5grBQVWtUfHCTZgr92LL5qnxSEGnGLX+f1e4hkkOnSLqenJORJDwFYuShj13 GmQSGfKLndH5+0uLLjIcDqO9wMlQ+Z0sHF0lEi5CN5Y7JlBsgZY/ucRzZc8FP+J2 63MqgvV/BQBPNzr8X14MU9F2lR4RhimS5HgMofNpDHjlVDnl+/N3EQX4JoZAAEht CfMchLmdVU6fnMIyxOP5FnxILBFhXalt/qJjrbMQ/++fiR82zedB8gM609G1HFb1 RC9W6rTdDChmKzvBHv0ZgPRYnrE30zmdAixfwYs0GrLrgZoI -----END CERTIFICATE----- go-dockerclient-1.12.0/testing/data/container.tar000066400000000000000000000040001465717111200217230ustar00rootroot00000000000000fakecontainer000644 000766 000024 00000000065 12210121775 016021 0ustar00flavia.carlettestaff000000 000000 this is supposed to be a fake container in a tar. xD go-dockerclient-1.12.0/testing/data/dockerfile.tar000066400000000000000000000040001465717111200220500ustar00rootroot00000000000000Dockerfile000644 000766 000024 00000000064 13673224546 013537 0ustar00fsouzastaff000000 000000 from ubuntu run apt-get install wget -y --force-yes go-dockerclient-1.12.0/testing/data/foofile000066400000000000000000000000001465717111200205730ustar00rootroot00000000000000go-dockerclient-1.12.0/testing/data/key.pem000066400000000000000000000032171465717111200205350ustar00rootroot00000000000000-----BEGIN RSA PRIVATE KEY----- MIIEpAIBAAKCAQEAs1bJPjF06hdKcX47c2q24lYmTCJUd3lPaG83MjyZb16jQaZ8 J1EnckULNbZaArYQgjAPxUVDpp1WVfRqqsXa6rT/BjTSp+Abtq+lcL2ABa5Rrf9X I8qBxs4EiiE45N9w0GxG2+WCUulu5ldDW7E9VeQ/yCwMc8cKYv+zZRxHj0fVRtAt k1eaidqhL0zmShfklPDa5EyNm03kfqVQcV3WpPRBfEbbek7sLbAXmJUdyp+uMeo8 a147pKbv0njRMfj0TA/ATzKF+Dq3fmpb+lCM0wuPJXwDgpWrKfO+50aRuax2sod3 yHBUVs33+qmzLBrq5OzTyRfk+ALQ+5md+l3F4wIDAQABAoIBAAHQc/K8H1mq6Kbj Rwiw4K7Dflmw0zfuMz50OZO5hzfYNkCYxZLEQGjoh+eAM8LpQpt+jvpI7BlIaDIw ac/WdpN/R0Ex0Zu/KaiceEj+scfc7pibB6+mAdAYD3WyRlgSZprFLaZV2Q6rq+KK qXTJAzzWNUr6HxogvlsoCRGRyquJYynltZH7zKNsKkMn+bUmWHQoBp/y16xGF7zN Xi4iO5L5fBqK0jw+mv2Dgqfavr7NTlMUIJwzOut7NpYNGh/cnQh+G/gStOkbJD9E N+jeja7tio9vEUtBbX76eM+qIqvAkhCbwLy5v07X5z/cobZRvm+SGiby6yOwik7k yEsWuekCgYEA2qVoebE6WMbcrnNJQQ5GJ34ITJGrUPeqHUflzHeax81do3Plc1I0 eiBa2Pie8UsfJTpyAxRNdB8xvan+OMq1ZcXrhGIZL1MoE0s/qhsQWrLrHZmPeKe3 E5BMr0QAzwWKAN94SINqQvYsVDWH5oamrJfJ8tUYSsEwU1tpN/b562cCgYEA0fpD OfydNbTIAza1s3jOaqa/U0F1wEMzBpQmOzDRdRW7RNPljApu6noGy84kRu3p5d+9 2K7G+8Jp+OjjjZsbDedjxChNFdkt63mlzN3p4HewqlD9ow5U14o4Uygeoe0wC5JC yrFWXl/HDtEl0zq1NOmx2fuxkkHB42UTwM1TQCUCgYEAzEU0slytkjtX+XOjwK/B QywwPLjrQR3hRLHjM3+aj4iCLy8b8v2eIWsQ9RxyAiSY2IMd+VqCcjailfu3kv9v McITIv8zYE9kZYIr3JSkVthJaMIKlEK8e/6YZjY2OzOFlwMwsS51nFG5YIMSkIDE PwMgnUoRA+Yxa+8/AhYvr4kCgYEAuBo6GTxwxmb0O0UbuPkB2qbK939cc683rNv2 TU13SLY+FJHJIKkBKNrG3KioS/xLaL5soPBVDOYfrhrbcQv+g4lOgu1m8PITffOR uHcVmTa4Egyi5fUp1DWfPuHdQdJJnsulfjxQYryNNmOQS4yc1oDT7DDvNzIqgmrc D9620HUCgYAQo4xjNtFFH8/enegUNxyxytMrAYTie33acHXbq0Xmv7S1x5bo8BuS iHd/Hi33kqFfwFe9FcM/qcHagfMu76cFC7PdTRVDxhFXu9qoQs9u7ZlnfuYNRAzs jv3+xXKcBmwVc2+8wHbGAuz3oIg886Atlyx8b0nBcAPFwvgimpgKcg== -----END RSA PRIVATE KEY----- go-dockerclient-1.12.0/testing/data/server.pem000066400000000000000000000023001465717111200212430ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- MIIDVTCCAj2gAwIBAgIJALw2I+++VX+9MA0GCSqGSIb3DQEBCwUAMHwxCzAJBgNV BAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBX aWRnaXRzIFB0eSBMdGQxFDASBgNVBAMMC2Jvb3QyZG9ja2VyMR8wHQYJKoZIhvcN AQkBFhB1c2VyQGV4YW1wbGUuY29tMB4XDTE3MTAwNDA1NDA0MFoXDTI3MTAwMjA1 NDA0MFowFjEUMBIGA1UEAwwLYm9vdDJkb2NrZXIwggEiMA0GCSqGSIb3DQEBAQUA A4IBDwAwggEKAoIBAQDNtyVIOyXw/1IjgnnSnZW2fWGYHMs0K06PnPQc1ckXXI43 yKjSjeSjQQaKBmz1N9Akf3W7tP0WAXazIGRRaV5qGdTdPDR3ckUDWOjXOKy2XYB3 JaQ7hi6i5tcxxZbPmlKMxZLAU4Qq/jOgybFxZ89ZZ8IH4sifV3HkNoPE7+ijX+T9 5oS6C6d7HHdILedf6se7trNqPEnXrfzWwm/B+ZJMJpqDD9N6sXILjcAL6fGVivuk OvuwSfwxoAX57EM4QS6McZccKUxnfY55v5BKY+20q0gkkwXC594jPLDQkQ1FEaoA iB0gUen66gOEs9zrg43lih0fjZr+KET9E10u6dZXAgMBAAGjQDA+MCcGA1UdEQQg MB6CC2Jvb3QyZG9ja2Vygglsb2NhbGhvc3SHBH8AAAEwEwYDVR0lBAwwCgYIKwYB BQUHAwEwDQYJKoZIhvcNAQELBQADggEBACeORdlxL1Rr1YOrAoRa7QJ7WG6x5AC3 ivucKEKdGd3x/L9snsR65OPJOi3zBf/YvktS8iIWNtUrTIQ+Umf5dix8LDRr7CDi 3319eTawnSc4Tk5ML5TtMTQHcarKA3VXwxpuhoY7GpxhH7+4hrBYPWWxqJ2c0i/f b7tiun5phWVUjgUSYRyaZ3Fcb8rUKPWidrI8hSgAB1xnwGB/hNWxvxXGUZMgOT/w fQ+k22FS+I8DPY15k1sPCURJx5hL6gro9N5ykuIUyKBRGRY3eRMX+GBey0PCSsZF qc7M1kMAPTvIxDgE8N9x/3GqzZzc5ZVHLzh1Y+06X2+8Q13XDAm1nx0= -----END CERTIFICATE----- go-dockerclient-1.12.0/testing/data/serverkey.pem000066400000000000000000000032171465717111200217640ustar00rootroot00000000000000-----BEGIN RSA PRIVATE KEY----- MIIEpAIBAAKCAQEAzbclSDsl8P9SI4J50p2Vtn1hmBzLNCtOj5z0HNXJF1yON8io 0o3ko0EGigZs9TfQJH91u7T9FgF2syBkUWleahnU3Tw0d3JFA1jo1zistl2AdyWk O4YuoubXMcWWz5pSjMWSwFOEKv4zoMmxcWfPWWfCB+LIn1dx5DaDxO/oo1/k/eaE ugunexx3SC3nX+rHu7azajxJ16381sJvwfmSTCaagw/TerFyC43AC+nxlYr7pDr7 sEn8MaAF+exDOEEujHGXHClMZ32Oeb+QSmPttKtIJJMFwufeIzyw0JENRRGqAIgd IFHp+uoDhLPc64ON5YodH42a/ihE/RNdLunWVwIDAQABAoIBAAx8k2zA+lqYhNnW 76ITIqVDEwtyo/r0rf4VntOpPl2GprNIIMc3CavHJKh4H2D7FE1C11ifccVGLXhN /QjcBnkWPE95eg3OB9ZeLTPzIrinEEcrJ77dNDN7I5DH15GeYmhdDb1S1HDvoR6f X3/sty9MFwD3iQny+tzJWlI35b0U1xypz+qnyeb9/+3ymW21FEYIgt4N989Kbd2i Tnafc53WpNgBecpnpqrzbb+FWIVSaa/4jhUHgY4MUs+hmFGfqUtP0NWmU9LzvOdm iSHjuCqa8jYfegOZEECq3SNIgGjLpbSrp+NcGcwTp/rXZsTkPmPLCPkYyOxwHkwx 47GWH4ECgYEA9MmUGEyb4Hp7AkmE6QPgeEgKvksixPe2G50KyULWToTaGl0hU20b IHL+m6qx3Dl0RmL6c7yicno9fSImnCN7ykkvfWeWTrn7G33+a134jJ66dErB+4Pm p/7HMU5YRQijoswOS1eccss0OoZSVLA7zYE0UtjBirYNAdEyn0mfjSECgYEA1yNm 2cySG0GBemdk8n+o4TTPzx3CQ4n53VwzNCTrQuxZlbqyiRH/ykkC7ymL/5CrjuAP 4EWq+biNFOFFNJTWMX076btSJ/C5KMY/gEdccg3jpZNu9K7IVQy0cuLGo1eVYWik xZ7AM9xXsi4r4tK0TGx7EO2/MQYDJYxlk1O7vHcCgYEAwKvpZTbnekjtiV9UvhPP Gt2Zly6Mr3xKWnHBi6iQBj2LbjTfhkrajy/0N5KjbDPM0ZoWiAJx2qlcvKGkwkEr rozsVEi0hkLc40bIern8aLKHEAHHrbCkXJatMqH32F50bi8vER5khfZrpaUUKJSO fbK0/ICf1g51Lnj632WTsAECgYBc7hDeYcx284vPaNwlF9nGNf7R84PXksEYylvf 2Va6WMe1sqiuJtU79t/KmUK9CSviEo0pDk71ga+HZmtzdBs102fO5bhuQkBpCYGi krCvH6rG+fGCxgEJI+zKd4TgR9Ph+Ma8Cn/xputGr0ULvIJfRLjALkJZFcTEMmoq ApBkrwKBgQCraE3Mt6cLvNGCc+TPtDOmd0fYDvrKWBCTNfWvhaK3HKoGAd0yS8EO oOogyhqYEiSK6oYWsGmKvnUiHmbtvLL3Mtth/WRxTZiLxv28b1aE5OL35ha6Q1pI poax/riDPoEehVujXSVCTRT/Mxi8UhkL2At3QzOo/3DTK8igPDymYw== -----END RSA PRIVATE KEY----- go-dockerclient-1.12.0/testing/server.go000066400000000000000000001375011465717111200201720ustar00rootroot00000000000000// Copyright 2013 go-dockerclient authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. // Package testing provides a fake implementation of the Docker API, useful for // testing purpose. package testing import ( "archive/tar" "crypto/rand" "crypto/tls" "crypto/x509" "encoding/json" "errors" "fmt" "io" mathrand "math/rand" "net" "net/http" "os" libpath "path" "regexp" "strconv" "strings" "sync" "time" "github.com/docker/docker/api/types/swarm" "github.com/docker/docker/pkg/stdcopy" docker "github.com/fsouza/go-dockerclient" "github.com/gorilla/mux" ) var nameRegexp = regexp.MustCompile(`^[a-zA-Z0-9][a-zA-Z0-9_.-]+$`) // DockerServer represents a programmable, concurrent (not much), HTTP server // implementing a fake version of the Docker remote API. // // It can used in standalone mode, listening for connections or as an arbitrary // HTTP handler. // // For more details on the remote API, check http://goo.gl/G3plxW. type DockerServer struct { containers map[string]*docker.Container contNameToID map[string]string uploadedFiles map[string]string execs []*docker.ExecInspect execMut sync.RWMutex cMut sync.RWMutex images map[string]docker.Image iMut sync.RWMutex imgIDs map[string]string networks []*docker.Network netMut sync.RWMutex listener net.Listener mux *mux.Router hook func(*http.Request) failures map[string]string multiFailures []map[string]string execCallbacks map[string]func() statsCallbacks map[string]func(string) docker.Stats customHandlers map[string]http.Handler handlerMutex sync.RWMutex cChan chan<- *docker.Container volStore map[string]*volumeCounter volMut sync.RWMutex swarmMut sync.RWMutex swarm *swarm.Swarm swarmServer *swarmServer nodes []swarm.Node nodeID string tasks []*swarm.Task services []*swarm.Service nodeRR int servicePorts int } type volumeCounter struct { volume docker.Volume count int } func baseDockerServer() DockerServer { return DockerServer{ containers: make(map[string]*docker.Container), contNameToID: make(map[string]string), imgIDs: make(map[string]string), images: make(map[string]docker.Image), failures: make(map[string]string), execCallbacks: make(map[string]func()), statsCallbacks: make(map[string]func(string) docker.Stats), customHandlers: make(map[string]http.Handler), uploadedFiles: make(map[string]string), } } func buildDockerServer(listener net.Listener, containerChan chan<- *docker.Container, hook func(*http.Request)) *DockerServer { server := baseDockerServer() server.listener = listener server.hook = hook server.cChan = containerChan server.buildMuxer() return &server } // NewServer returns a new instance of the fake server, in standalone mode. Use // the method URL to get the URL of the server. // // It receives the bind address (use 127.0.0.1:0 for getting an available port // on the host), a channel of containers and a hook function, that will be // called on every request. // // The fake server will send containers in the channel whenever the container // changes its state, via the HTTP API (i.e.: create, start and stop). This // channel may be nil, which means that the server won't notify on state // changes. func NewServer(bind string, containerChan chan<- *docker.Container, hook func(*http.Request)) (*DockerServer, error) { listener, err := net.Listen("tcp", bind) if err != nil { return nil, err } server := buildDockerServer(listener, containerChan, hook) go http.Serve(listener, server) return server, nil } // TLSConfig is the set of options to start the TLS-enabled testing server. type TLSConfig struct { CertPath string CertKeyPath string RootCAPath string } // NewTLSServer creates and starts a TLS-enabled testing server. func NewTLSServer(bind string, containerChan chan<- *docker.Container, hook func(*http.Request), tlsConfig TLSConfig) (*DockerServer, error) { listener, err := net.Listen("tcp", bind) if err != nil { return nil, err } defaultCertificate, err := tls.LoadX509KeyPair(tlsConfig.CertPath, tlsConfig.CertKeyPath) if err != nil { return nil, err } tlsServerConfig := new(tls.Config) tlsServerConfig.Certificates = []tls.Certificate{defaultCertificate} if tlsConfig.RootCAPath != "" { rootCertPEM, err := os.ReadFile(tlsConfig.RootCAPath) if err != nil { return nil, err } certsPool := x509.NewCertPool() certsPool.AppendCertsFromPEM(rootCertPEM) tlsServerConfig.RootCAs = certsPool } tlsListener := tls.NewListener(listener, tlsServerConfig) server := buildDockerServer(tlsListener, containerChan, hook) go http.Serve(tlsListener, server) return server, nil } func (s *DockerServer) notify(container *docker.Container) { if s.cChan != nil { s.cChan <- container } } func (s *DockerServer) buildMuxer() { s.mux = mux.NewRouter() s.addMuxerRoutes(s.mux) sub := s.mux.PathPrefix("/{version:v[0-9]+\\.[0-9]+}").Subrouter() s.addMuxerRoutes(sub) } func (s *DockerServer) addMuxerRoutes(m *mux.Router) { m.Path("/commit").Methods(http.MethodPost).HandlerFunc(s.handlerWrapper(s.commitContainer)) m.Path("/containers/json").Methods(http.MethodGet).HandlerFunc(s.handlerWrapper(s.listContainers)) m.Path("/containers/create").Methods(http.MethodPost).HandlerFunc(s.handlerWrapper(s.createContainer)) m.Path("/containers/{id:.*}/json").Methods(http.MethodGet).HandlerFunc(s.handlerWrapper(s.inspectContainer)) m.Path("/containers/{id:.*}/rename").Methods(http.MethodPost).HandlerFunc(s.handlerWrapper(s.renameContainer)) m.Path("/containers/{id:.*}/top").Methods(http.MethodGet).HandlerFunc(s.handlerWrapper(s.topContainer)) m.Path("/containers/{id:.*}/start").Methods(http.MethodPost).HandlerFunc(s.handlerWrapper(s.startContainer)) m.Path("/containers/{id:.*}/kill").Methods(http.MethodPost).HandlerFunc(s.handlerWrapper(s.stopContainer)) m.Path("/containers/{id:.*}/stop").Methods(http.MethodPost).HandlerFunc(s.handlerWrapper(s.stopContainer)) m.Path("/containers/{id:.*}/pause").Methods(http.MethodPost).HandlerFunc(s.handlerWrapper(s.pauseContainer)) m.Path("/containers/{id:.*}/unpause").Methods(http.MethodPost).HandlerFunc(s.handlerWrapper(s.unpauseContainer)) m.Path("/containers/{id:.*}/wait").Methods(http.MethodPost).HandlerFunc(s.handlerWrapper(s.waitContainer)) m.Path("/containers/{id:.*}/attach").Methods(http.MethodPost).HandlerFunc(s.handlerWrapper(s.attachContainer)) m.Path("/containers/{id:.*}").Methods(http.MethodDelete).HandlerFunc(s.handlerWrapper(s.removeContainer)) m.Path("/containers/{id:.*}/exec").Methods(http.MethodPost).HandlerFunc(s.handlerWrapper(s.createExecContainer)) m.Path("/containers/{id:.*}/stats").Methods(http.MethodGet).HandlerFunc(s.handlerWrapper(s.statsContainer)) m.Path("/containers/{id:.*}/archive").Methods(http.MethodPut).HandlerFunc(s.handlerWrapper(s.uploadToContainer)) m.Path("/containers/{id:.*}/archive").Methods(http.MethodGet).HandlerFunc(s.handlerWrapper(s.downloadFromContainer)) m.Path("/containers/{id:.*}/logs").Methods(http.MethodGet).HandlerFunc(s.handlerWrapper(s.logContainer)) m.Path("/exec/{id:.*}/resize").Methods(http.MethodPost).HandlerFunc(s.handlerWrapper(s.resizeExecContainer)) m.Path("/exec/{id:.*}/start").Methods(http.MethodPost).HandlerFunc(s.handlerWrapper(s.startExecContainer)) m.Path("/exec/{id:.*}/json").Methods(http.MethodGet).HandlerFunc(s.handlerWrapper(s.inspectExecContainer)) m.Path("/images/create").Methods(http.MethodPost).HandlerFunc(s.handlerWrapper(s.pullImage)) m.Path("/build").Methods(http.MethodPost).HandlerFunc(s.handlerWrapper(s.buildImage)) m.Path("/images/json").Methods(http.MethodGet).HandlerFunc(s.handlerWrapper(s.listImages)) m.Path("/images/{id:.*}").Methods(http.MethodDelete).HandlerFunc(s.handlerWrapper(s.removeImage)) m.Path("/images/{name:.*}/json").Methods(http.MethodGet).HandlerFunc(s.handlerWrapper(s.inspectImage)) m.Path("/images/{name:.*}/push").Methods(http.MethodPost).HandlerFunc(s.handlerWrapper(s.pushImage)) m.Path("/images/{name:.*}/tag").Methods(http.MethodPost).HandlerFunc(s.handlerWrapper(s.tagImage)) m.Path("/events").Methods(http.MethodGet).HandlerFunc(s.listEvents) m.Path("/_ping").Methods(http.MethodGet).HandlerFunc(s.handlerWrapper(s.pingDocker)) m.Path("/images/load").Methods(http.MethodPost).HandlerFunc(s.handlerWrapper(s.loadImage)) m.Path("/images/{id:.*}/get").Methods(http.MethodGet).HandlerFunc(s.handlerWrapper(s.getImage)) m.Path("/networks").Methods(http.MethodGet).HandlerFunc(s.handlerWrapper(s.listNetworks)) m.Path("/networks/{id:.*}").Methods(http.MethodGet).HandlerFunc(s.handlerWrapper(s.networkInfo)) m.Path("/networks/{id:.*}").Methods(http.MethodDelete).HandlerFunc(s.handlerWrapper(s.removeNetwork)) m.Path("/networks/create").Methods(http.MethodPost).HandlerFunc(s.handlerWrapper(s.createNetwork)) m.Path("/networks/{id:.*}/connect").Methods(http.MethodPost).HandlerFunc(s.handlerWrapper(s.networksConnect)) m.Path("/volumes").Methods(http.MethodGet).HandlerFunc(s.handlerWrapper(s.listVolumes)) m.Path("/volumes/create").Methods(http.MethodPost).HandlerFunc(s.handlerWrapper(s.createVolume)) m.Path("/volumes/{name:.*}").Methods(http.MethodGet).HandlerFunc(s.handlerWrapper(s.inspectVolume)) m.Path("/volumes/{name:.*}").Methods(http.MethodDelete).HandlerFunc(s.handlerWrapper(s.removeVolume)) m.Path("/info").Methods(http.MethodGet).HandlerFunc(s.handlerWrapper(s.infoDocker)) m.Path("/version").Methods(http.MethodGet).HandlerFunc(s.handlerWrapper(s.versionDocker)) m.Path("/swarm/init").Methods(http.MethodPost).HandlerFunc(s.handlerWrapper(s.swarmInit)) m.Path("/swarm").Methods(http.MethodGet).HandlerFunc(s.handlerWrapper(s.swarmInspect)) m.Path("/swarm/join").Methods(http.MethodPost).HandlerFunc(s.handlerWrapper(s.swarmJoin)) m.Path("/swarm/leave").Methods(http.MethodPost).HandlerFunc(s.handlerWrapper(s.swarmLeave)) m.Path("/nodes/{id:.+}/update").Methods(http.MethodPost).HandlerFunc(s.handlerWrapper(s.nodeUpdate)) m.Path("/nodes/{id:.+}").Methods(http.MethodGet).HandlerFunc(s.handlerWrapper(s.nodeInspect)) m.Path("/nodes/{id:.+}").Methods(http.MethodDelete).HandlerFunc(s.handlerWrapper(s.nodeDelete)) m.Path("/nodes").Methods(http.MethodGet).HandlerFunc(s.handlerWrapper(s.nodeList)) m.Path("/services/create").Methods(http.MethodPost).HandlerFunc(s.handlerWrapper(s.serviceCreate)) m.Path("/services/{id:.+}").Methods(http.MethodGet).HandlerFunc(s.handlerWrapper(s.serviceInspect)) m.Path("/services").Methods(http.MethodGet).HandlerFunc(s.handlerWrapper(s.serviceList)) m.Path("/services/{id:.+}").Methods(http.MethodDelete).HandlerFunc(s.handlerWrapper(s.serviceDelete)) m.Path("/services/{id:.+}/update").Methods(http.MethodPost).HandlerFunc(s.handlerWrapper(s.serviceUpdate)) m.Path("/tasks").Methods(http.MethodGet).HandlerFunc(s.handlerWrapper(s.taskList)) m.Path("/tasks/{id:.+}").Methods(http.MethodGet).HandlerFunc(s.handlerWrapper(s.taskInspect)) } // SetHook changes the hook function used by the server. // // The hook function is a function called on every request. func (s *DockerServer) SetHook(hook func(*http.Request)) { s.hook = hook } // PrepareExec adds a callback to a container exec in the fake server. // // This function will be called whenever the given exec id is started, and the // given exec id will remain in the "Running" start while the function is // running, so it's useful for emulating an exec that runs for two seconds, for // example: // // opts := docker.CreateExecOptions{ // AttachStdin: true, // AttachStdout: true, // AttachStderr: true, // Tty: true, // Cmd: []string{"/bin/bash", "-l"}, // } // // Client points to a fake server. // exec, err := client.CreateExec(opts) // // handle error // server.PrepareExec(exec.ID, func() {time.Sleep(2 * time.Second)}) // err = client.StartExec(exec.ID, docker.StartExecOptions{Tty: true}) // will block for 2 seconds // // handle error func (s *DockerServer) PrepareExec(id string, callback func()) { s.execCallbacks[id] = callback } // PrepareStats adds a callback that will be called for each container stats // call. // // This callback function will be called multiple times if stream is set to // true when stats is called. func (s *DockerServer) PrepareStats(id string, callback func(string) docker.Stats) { s.statsCallbacks[id] = callback } // PrepareFailure adds a new expected failure based on a URL regexp it receives // an id for the failure. func (s *DockerServer) PrepareFailure(id string, urlRegexp string) { s.failures[id] = urlRegexp } // PrepareMultiFailures enqueues a new expected failure based on a URL regexp // it receives an id for the failure. func (s *DockerServer) PrepareMultiFailures(id string, urlRegexp string) { s.multiFailures = append(s.multiFailures, map[string]string{"error": id, "url": urlRegexp}) } // ResetFailure removes an expected failure identified by the given id. func (s *DockerServer) ResetFailure(id string) { delete(s.failures, id) } // ResetMultiFailures removes all enqueued failures. func (s *DockerServer) ResetMultiFailures() { s.multiFailures = []map[string]string{} } // CustomHandler registers a custom handler for a specific path. // // For example: // // server.CustomHandler("/containers/json", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { // http.Error(w, "Something wrong is not right", http.StatusInternalServerError) // })) func (s *DockerServer) CustomHandler(path string, handler http.Handler) { s.handlerMutex.Lock() s.customHandlers[path] = handler s.handlerMutex.Unlock() } // MutateContainer changes the state of a container, returning an error if the // given id does not match to any container "running" in the server. func (s *DockerServer) MutateContainer(id string, state docker.State) error { s.cMut.Lock() defer s.cMut.Unlock() if container, ok := s.containers[id]; ok { container.State = state return nil } return errors.New("container not found") } // Stop stops the server. func (s *DockerServer) Stop() { if s.listener != nil { s.listener.Close() } if s.swarmServer != nil { s.swarmServer.listener.Close() } } // URL returns the HTTP URL of the server. func (s *DockerServer) URL() string { if s.listener == nil { return "" } return "http://" + s.listener.Addr().String() + "/" } // ServeHTTP handles HTTP requests sent to the server. func (s *DockerServer) ServeHTTP(w http.ResponseWriter, r *http.Request) { s.handlerMutex.RLock() defer s.handlerMutex.RUnlock() for re, handler := range s.customHandlers { if m, _ := regexp.MatchString(re, r.URL.Path); m { handler.ServeHTTP(w, r) return } } s.mux.ServeHTTP(w, r) if s.hook != nil { s.hook(r) } } // DefaultHandler returns default http.Handler mux, it allows customHandlers to // call the default behavior if wanted. func (s *DockerServer) DefaultHandler() http.Handler { return s.mux } func (s *DockerServer) handlerWrapper(f http.HandlerFunc) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { for errorID, urlRegexp := range s.failures { matched, err := regexp.MatchString(urlRegexp, r.URL.Path) if err != nil { http.Error(w, err.Error(), http.StatusBadRequest) return } if !matched { continue } http.Error(w, errorID, http.StatusBadRequest) return } for i, failure := range s.multiFailures { matched, err := regexp.MatchString(failure["url"], r.URL.Path) if err != nil { http.Error(w, err.Error(), http.StatusBadRequest) return } if !matched { continue } http.Error(w, failure["error"], http.StatusBadRequest) s.multiFailures = append(s.multiFailures[:i], s.multiFailures[i+1:]...) return } f(w, r) } } func (s *DockerServer) listContainers(w http.ResponseWriter, r *http.Request) { all := r.URL.Query().Get("all") filtersRaw := r.FormValue("filters") filters := make(map[string][]string) json.Unmarshal([]byte(filtersRaw), &filters) labelFilters := make(map[string]*string) for _, f := range filters["label"] { parts := strings.Split(f, "=") if len(parts) == 2 { labelFilters[parts[0]] = &parts[1] continue } labelFilters[parts[0]] = nil } s.cMut.RLock() result := make([]docker.APIContainers, 0, len(s.containers)) loop: for _, container := range s.containers { if all == "1" || container.State.Running { var ports []docker.APIPort if container.NetworkSettings != nil { ports = container.NetworkSettings.PortMappingAPI() } for l, fv := range labelFilters { lv, ok := container.Config.Labels[l] if !ok { continue loop } if fv != nil && lv != *fv { continue loop } } result = append(result, docker.APIContainers{ ID: container.ID, Image: container.Image, Command: fmt.Sprintf("%s %s", container.Path, strings.Join(container.Args, " ")), Created: container.Created.Unix(), Status: container.State.String(), State: container.State.StateString(), Ports: ports, Names: []string{fmt.Sprintf("/%s", container.Name)}, Labels: container.Config.Labels, }) } } s.cMut.RUnlock() w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) json.NewEncoder(w).Encode(result) } func (s *DockerServer) listImages(w http.ResponseWriter, r *http.Request) { s.cMut.RLock() result := make([]docker.APIImages, len(s.images)) i := 0 for _, image := range s.images { result[i] = docker.APIImages{ ID: image.ID, Created: image.Created.Unix(), } for tag, id := range s.imgIDs { if id == image.ID { result[i].RepoTags = append(result[i].RepoTags, tag) } } i++ } s.cMut.RUnlock() w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) json.NewEncoder(w).Encode(result) } func (s *DockerServer) findImage(id string) (string, error) { s.iMut.RLock() defer s.iMut.RUnlock() image, ok := s.imgIDs[id] if ok { return image, nil } if _, ok := s.images[id]; ok { return id, nil } return "", errors.New("no such image") } func (s *DockerServer) createContainer(w http.ResponseWriter, r *http.Request) { var config struct { *docker.Config HostConfig *docker.HostConfig } defer r.Body.Close() err := json.NewDecoder(r.Body).Decode(&config) if err != nil { http.Error(w, err.Error(), http.StatusBadRequest) return } name := r.URL.Query().Get("name") if name != "" && !nameRegexp.MatchString(name) { http.Error(w, "Invalid container name", http.StatusInternalServerError) return } imageID, err := s.findImage(config.Image) if err != nil { http.Error(w, err.Error(), http.StatusNotFound) return } ports := map[docker.Port][]docker.PortBinding{} for port := range config.ExposedPorts { ports[port] = []docker.PortBinding{{ HostIP: "0.0.0.0", HostPort: strconv.Itoa(mathrand.Int() % 0xffff), }} } // the container may not have cmd when using a Dockerfile var path string var args []string if len(config.Cmd) == 1 { path = config.Cmd[0] } else if len(config.Cmd) > 1 { path = config.Cmd[0] args = config.Cmd[1:] } generatedID := s.generateID() config.Hostname = generatedID[:12] container := docker.Container{ Name: name, ID: generatedID, Created: time.Now(), Path: path, Args: args, Config: config.Config, HostConfig: config.HostConfig, State: docker.State{ Running: false, Pid: mathrand.Int() % 50000, ExitCode: 0, }, Image: config.Image, NetworkSettings: &docker.NetworkSettings{ IPAddress: fmt.Sprintf("172.16.42.%d", mathrand.Int()%250+2), IPPrefixLen: 24, Gateway: "172.16.42.1", Bridge: "docker0", Ports: ports, }, } s.cMut.Lock() if val, ok := s.uploadedFiles[imageID]; ok { s.uploadedFiles[container.ID] = val } if container.Name != "" { _, err = s.findContainerWithLock(container.Name, false) if err == nil { defer s.cMut.Unlock() http.Error(w, "there's already a container with this name", http.StatusConflict) return } } s.addContainer(&container) s.cMut.Unlock() w.WriteHeader(http.StatusCreated) s.notify(&container) json.NewEncoder(w).Encode(container) } func (s *DockerServer) addContainer(container *docker.Container) { s.containers[container.ID] = container if container.Name != "" { s.contNameToID[container.Name] = container.ID } } func (s *DockerServer) generateID() string { var buf [16]byte rand.Read(buf[:]) return fmt.Sprintf("%x", buf) } func (s *DockerServer) renameContainer(w http.ResponseWriter, r *http.Request) { id := mux.Vars(r)["id"] s.cMut.Lock() defer s.cMut.Unlock() container, err := s.findContainerWithLock(id, false) if err != nil { http.Error(w, err.Error(), http.StatusNotFound) return } delete(s.contNameToID, container.Name) container.Name = r.URL.Query().Get("name") s.contNameToID[container.Name] = container.ID w.WriteHeader(http.StatusNoContent) } func (s *DockerServer) inspectContainer(w http.ResponseWriter, r *http.Request) { id := mux.Vars(r)["id"] container, err := s.findContainer(id) if err != nil { http.Error(w, err.Error(), http.StatusNotFound) return } w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) s.cMut.RLock() defer s.cMut.RUnlock() json.NewEncoder(w).Encode(container) } func (s *DockerServer) statsContainer(w http.ResponseWriter, r *http.Request) { id := mux.Vars(r)["id"] _, err := s.findContainer(id) if err != nil { http.Error(w, err.Error(), http.StatusNotFound) return } stream, _ := strconv.ParseBool(r.URL.Query().Get("stream")) callback := s.statsCallbacks[id] w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) encoder := json.NewEncoder(w) for { var stats docker.Stats if callback != nil { stats = callback(id) } encoder.Encode(stats) if !stream { break } } } func (s *DockerServer) uploadToContainer(w http.ResponseWriter, r *http.Request) { id := mux.Vars(r)["id"] _, err := s.findContainer(id) if err != nil { http.Error(w, err.Error(), http.StatusNotFound) return } path := r.URL.Query().Get("path") if r.Body != nil { tr := tar.NewReader(r.Body) if hdr, _ := tr.Next(); hdr != nil { path = libpath.Join(path, hdr.Name) } } s.cMut.Lock() s.uploadedFiles[id] = path s.cMut.Unlock() w.WriteHeader(http.StatusOK) } func (s *DockerServer) downloadFromContainer(w http.ResponseWriter, r *http.Request) { id := mux.Vars(r)["id"] _, err := s.findContainer(id) if err != nil { http.Error(w, err.Error(), http.StatusNotFound) return } path := r.URL.Query().Get("path") s.cMut.RLock() val, ok := s.uploadedFiles[id] s.cMut.RUnlock() if !ok || val != path { w.WriteHeader(http.StatusNotFound) fmt.Fprintf(w, "Path %s not found", path) return } w.Header().Set("Content-Type", "application/x-tar") w.WriteHeader(http.StatusOK) } func (s *DockerServer) topContainer(w http.ResponseWriter, r *http.Request) { id := mux.Vars(r)["id"] container, err := s.findContainer(id) if err != nil { http.Error(w, err.Error(), http.StatusNotFound) return } s.cMut.RLock() defer s.cMut.RUnlock() if !container.State.Running { w.WriteHeader(http.StatusInternalServerError) fmt.Fprintf(w, "Container %s is not running", id) return } w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) result := docker.TopResult{ Titles: []string{"UID", "PID", "PPID", "C", "STIME", "TTY", "TIME", "CMD"}, Processes: [][]string{ {"root", "7535", "7516", "0", "03:20", "?", "00:00:00", container.Path + " " + strings.Join(container.Args, " ")}, }, } json.NewEncoder(w).Encode(result) } func (s *DockerServer) startContainer(w http.ResponseWriter, r *http.Request) { id := mux.Vars(r)["id"] container, err := s.findContainer(id) if err != nil { http.Error(w, err.Error(), http.StatusNotFound) return } s.cMut.Lock() defer s.cMut.Unlock() defer r.Body.Close() if container.State.Running { http.Error(w, "", http.StatusNotModified) return } var hostConfig *docker.HostConfig err = json.NewDecoder(r.Body).Decode(&hostConfig) if err != nil && !errors.Is(err, io.EOF) { http.Error(w, err.Error(), http.StatusInternalServerError) return } if hostConfig == nil { hostConfig = container.HostConfig } else { container.HostConfig = hostConfig } if hostConfig != nil && len(hostConfig.PortBindings) > 0 { ports := map[docker.Port][]docker.PortBinding{} for key, items := range hostConfig.PortBindings { bindings := make([]docker.PortBinding, len(items)) for i := range items { binding := docker.PortBinding{ HostIP: items[i].HostIP, HostPort: items[i].HostPort, } if binding.HostIP == "" { binding.HostIP = "0.0.0.0" } if binding.HostPort == "" { binding.HostPort = strconv.Itoa(mathrand.Int() % 0xffff) } bindings[i] = binding } ports[key] = bindings } container.NetworkSettings.Ports = ports } container.State.Running = true container.State.StartedAt = time.Now() s.notify(container) } func (s *DockerServer) stopContainer(w http.ResponseWriter, r *http.Request) { id := mux.Vars(r)["id"] container, err := s.findContainer(id) if err != nil { http.Error(w, err.Error(), http.StatusNotFound) return } s.cMut.Lock() defer s.cMut.Unlock() if !container.State.Running { http.Error(w, "Container not running", http.StatusBadRequest) return } w.WriteHeader(http.StatusNoContent) container.State.Running = false s.notify(container) } func (s *DockerServer) pauseContainer(w http.ResponseWriter, r *http.Request) { id := mux.Vars(r)["id"] container, err := s.findContainer(id) if err != nil { http.Error(w, err.Error(), http.StatusNotFound) return } s.cMut.Lock() defer s.cMut.Unlock() if container.State.Paused { http.Error(w, "Container already paused", http.StatusBadRequest) return } w.WriteHeader(http.StatusNoContent) container.State.Paused = true } func (s *DockerServer) unpauseContainer(w http.ResponseWriter, r *http.Request) { id := mux.Vars(r)["id"] container, err := s.findContainer(id) if err != nil { http.Error(w, err.Error(), http.StatusNotFound) return } s.cMut.Lock() defer s.cMut.Unlock() if !container.State.Paused { http.Error(w, "Container not paused", http.StatusBadRequest) return } w.WriteHeader(http.StatusNoContent) container.State.Paused = false } func (s *DockerServer) attachContainer(w http.ResponseWriter, r *http.Request) { id := mux.Vars(r)["id"] container, err := s.findContainer(id) if err != nil { http.Error(w, err.Error(), http.StatusNotFound) return } hijacker, ok := w.(http.Hijacker) if !ok { http.Error(w, "cannot hijack connection", http.StatusInternalServerError) return } w.Header().Set("Content-Type", "application/vnd.docker.raw-stream") w.WriteHeader(http.StatusOK) conn, _, err := hijacker.Hijack() if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } wg := sync.WaitGroup{} if r.URL.Query().Get("stdin") == "1" { wg.Add(1) go func() { io.ReadAll(conn) wg.Done() }() } outStream := stdcopy.NewStdWriter(conn, stdcopy.Stdout) s.cMut.RLock() if container.State.Running { fmt.Fprintf(outStream, "Container is running\n") } else { fmt.Fprintf(outStream, "Container is not running\n") } s.cMut.RUnlock() fmt.Fprintln(outStream, "What happened?") fmt.Fprintln(outStream, "Something happened") wg.Wait() if r.URL.Query().Get("stream") == "1" { for { time.Sleep(1e6) s.cMut.RLock() if !container.State.StartedAt.IsZero() && !container.State.Running { s.cMut.RUnlock() break } s.cMut.RUnlock() } } conn.Close() } func (s *DockerServer) waitContainer(w http.ResponseWriter, r *http.Request) { id := mux.Vars(r)["id"] container, err := s.findContainer(id) if err != nil { http.Error(w, err.Error(), http.StatusNotFound) return } var exitCode int for { time.Sleep(1e6) s.cMut.RLock() if !container.State.Running { exitCode = container.State.ExitCode s.cMut.RUnlock() break } s.cMut.RUnlock() } result := map[string]int{"StatusCode": exitCode} json.NewEncoder(w).Encode(result) } func (s *DockerServer) removeContainer(w http.ResponseWriter, r *http.Request) { id := mux.Vars(r)["id"] force := r.URL.Query().Get("force") s.cMut.Lock() defer s.cMut.Unlock() container, err := s.findContainerWithLock(id, false) if err != nil { http.Error(w, err.Error(), http.StatusNotFound) return } if container.State.Running && force != "1" { msg := "Error: API error (406): Impossible to remove a running container, please stop it first" http.Error(w, msg, http.StatusInternalServerError) return } w.WriteHeader(http.StatusNoContent) delete(s.containers, container.ID) delete(s.contNameToID, container.Name) } func (s *DockerServer) commitContainer(w http.ResponseWriter, r *http.Request) { id := r.URL.Query().Get("container") container, err := s.findContainer(id) if err != nil { http.Error(w, err.Error(), http.StatusNotFound) return } config := new(docker.Config) runConfig := r.URL.Query().Get("run") if runConfig != "" { err = json.Unmarshal([]byte(runConfig), config) if err != nil { http.Error(w, err.Error(), http.StatusBadRequest) return } } w.WriteHeader(http.StatusOK) image := docker.Image{ ID: "img-" + container.ID, Parent: container.Image, Container: container.ID, Comment: r.URL.Query().Get("m"), Author: r.URL.Query().Get("author"), Config: config, } repository := r.URL.Query().Get("repo") tag := r.URL.Query().Get("tag") s.iMut.Lock() s.images[image.ID] = image if repository != "" { if tag != "" { repository += ":" + tag } s.imgIDs[repository] = image.ID } s.iMut.Unlock() s.cMut.Lock() if val, ok := s.uploadedFiles[container.ID]; ok { s.uploadedFiles[image.ID] = val } s.cMut.Unlock() fmt.Fprintf(w, `{"ID":%q}`, image.ID) } func (s *DockerServer) findContainer(idOrName string) (*docker.Container, error) { return s.findContainerWithLock(idOrName, true) } func (s *DockerServer) findContainerWithLock(idOrName string, shouldLock bool) (*docker.Container, error) { if shouldLock { s.cMut.RLock() defer s.cMut.RUnlock() } if contID, ok := s.contNameToID[idOrName]; ok { idOrName = contID } if cont, ok := s.containers[idOrName]; ok { return cont, nil } return nil, errors.New("no such container") } func (s *DockerServer) logContainer(w http.ResponseWriter, r *http.Request) { id := mux.Vars(r)["id"] container, err := s.findContainer(id) if err != nil { http.Error(w, err.Error(), http.StatusNotFound) return } w.Header().Set("Content-Type", "application/vnd.docker.raw-stream") w.WriteHeader(http.StatusOK) s.cMut.RLock() if container.State.Running { fmt.Fprintf(w, "Container is running\n") } else { fmt.Fprintf(w, "Container is not running\n") } s.cMut.RUnlock() fmt.Fprintln(w, "What happened?") fmt.Fprintln(w, "Something happened") if r.URL.Query().Get("follow") == "1" { for { time.Sleep(1e6) s.cMut.RLock() if !container.State.StartedAt.IsZero() && !container.State.Running { s.cMut.RUnlock() break } s.cMut.RUnlock() } } } func (s *DockerServer) buildImage(w http.ResponseWriter, r *http.Request) { if ct := r.Header.Get("Content-Type"); ct == "application/tar" { gotDockerFile := false tr := tar.NewReader(r.Body) for { header, err := tr.Next() if err != nil { break } if header.Name == "Dockerfile" { gotDockerFile = true } } if !gotDockerFile { w.WriteHeader(http.StatusBadRequest) w.Write([]byte("miss Dockerfile")) return } } // we did not use that Dockerfile to build image cause we are a fake Docker daemon image := docker.Image{ ID: s.generateID(), Created: time.Now(), } query := r.URL.Query() repository := image.ID if t := query.Get("t"); t != "" { repository = t } s.iMut.Lock() s.images[image.ID] = image s.imgIDs[repository] = image.ID s.iMut.Unlock() fmt.Fprintf(w, "Successfully built %s", image.ID) } func (s *DockerServer) pullImage(w http.ResponseWriter, r *http.Request) { fromImageName := r.URL.Query().Get("fromImage") tag := r.URL.Query().Get("tag") if fromImageName != "" { if tag != "" { separator := ":" if strings.HasPrefix(tag, "sha256") { separator = "@" } fromImageName = fmt.Sprintf("%s%s%s", fromImageName, separator, tag) } } image := docker.Image{ ID: s.generateID(), Config: &docker.Config{}, } s.iMut.Lock() if _, exists := s.imgIDs[fromImageName]; fromImageName == "" || !exists { s.images[image.ID] = image if fromImageName != "" { s.imgIDs[fromImageName] = image.ID } } s.iMut.Unlock() } func (s *DockerServer) pushImage(w http.ResponseWriter, r *http.Request) { name := mux.Vars(r)["name"] tag := r.URL.Query().Get("tag") if tag != "" { name += ":" + tag } s.iMut.RLock() if _, ok := s.imgIDs[name]; !ok { s.iMut.RUnlock() http.Error(w, "No such image", http.StatusNotFound) return } s.iMut.RUnlock() fmt.Fprintln(w, "Pushing...") fmt.Fprintln(w, "Pushed") } func (s *DockerServer) tagImage(w http.ResponseWriter, r *http.Request) { name := mux.Vars(r)["name"] id, err := s.findImage(name) if err != nil { http.Error(w, "No such image", http.StatusNotFound) return } s.iMut.Lock() defer s.iMut.Unlock() newRepo := r.URL.Query().Get("repo") newTag := r.URL.Query().Get("tag") if newTag != "" { newRepo += ":" + newTag } s.imgIDs[newRepo] = id w.WriteHeader(http.StatusCreated) } func (s *DockerServer) removeImage(w http.ResponseWriter, r *http.Request) { id := mux.Vars(r)["id"] s.iMut.Lock() defer s.iMut.Unlock() var tag string if img, ok := s.imgIDs[id]; ok { id, tag = img, id } var tags []string for tag, taggedID := range s.imgIDs { if taggedID == id { tags = append(tags, tag) } } _, ok := s.images[id] if !ok { http.Error(w, "No such image", http.StatusNotFound) return } if tag == "" && len(tags) > 1 { http.Error(w, "image is referenced in multiple repositories", http.StatusConflict) return } w.WriteHeader(http.StatusNoContent) if tag == "" { // delete called with image ID for _, t := range tags { delete(s.imgIDs, t) } delete(s.images, id) } else { // delete called with image repository name delete(s.imgIDs, tag) if len(tags) == 1 { delete(s.images, id) } } } func (s *DockerServer) inspectImage(w http.ResponseWriter, r *http.Request) { name := mux.Vars(r)["name"] s.iMut.RLock() defer s.iMut.RUnlock() if id, ok := s.imgIDs[name]; ok { name = id } img, ok := s.images[name] if !ok { http.Error(w, "not found", http.StatusNotFound) return } w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) json.NewEncoder(w).Encode(img) } func (s *DockerServer) listEvents(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") var events [][]byte count := mathrand.Intn(20) for i := 0; i < count; i++ { data, err := json.Marshal(s.generateEvent()) if err != nil { w.WriteHeader(http.StatusInternalServerError) return } events = append(events, data) } w.WriteHeader(http.StatusOK) for _, d := range events { fmt.Fprintln(w, string(d)) time.Sleep(time.Duration(mathrand.Intn(200)) * time.Millisecond) } } func (s *DockerServer) pingDocker(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) } func (s *DockerServer) generateEvent() *docker.APIEvents { var eventType string switch mathrand.Intn(4) { case 0: eventType = "create" case 1: eventType = "start" case 2: eventType = "stop" case 3: eventType = "destroy" } return &docker.APIEvents{ ID: s.generateID(), Status: eventType, From: "mybase:latest", Time: time.Now().Unix(), } } func (s *DockerServer) loadImage(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) } func (s *DockerServer) getImage(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) w.Header().Set("Content-Type", "application/tar") } func (s *DockerServer) createExecContainer(w http.ResponseWriter, r *http.Request) { id := mux.Vars(r)["id"] container, err := s.findContainer(id) if err != nil { http.Error(w, err.Error(), http.StatusNotFound) return } execID := s.generateID() s.cMut.Lock() container.ExecIDs = append(container.ExecIDs, execID) s.cMut.Unlock() exec := docker.ExecInspect{ ID: execID, ContainerID: container.ID, } var params docker.CreateExecOptions err = json.NewDecoder(r.Body).Decode(¶ms) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } if len(params.Cmd) > 0 { exec.ProcessConfig.EntryPoint = params.Cmd[0] if len(params.Cmd) > 1 { exec.ProcessConfig.Arguments = params.Cmd[1:] } } exec.ProcessConfig.User = params.User exec.ProcessConfig.Tty = params.Tty s.execMut.Lock() s.execs = append(s.execs, &exec) s.execMut.Unlock() w.WriteHeader(http.StatusOK) w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(map[string]string{"Id": exec.ID}) } func (s *DockerServer) startExecContainer(w http.ResponseWriter, r *http.Request) { id := mux.Vars(r)["id"] if exec, err := s.getExec(id, false); err == nil { s.execMut.Lock() exec.Running = true s.execMut.Unlock() if callback, ok := s.execCallbacks[id]; ok { callback() delete(s.execCallbacks, id) } else if callback, ok := s.execCallbacks["*"]; ok { callback() delete(s.execCallbacks, "*") } s.execMut.Lock() exec.Running = false s.execMut.Unlock() w.WriteHeader(http.StatusOK) return } w.WriteHeader(http.StatusNotFound) } func (s *DockerServer) resizeExecContainer(w http.ResponseWriter, r *http.Request) { id := mux.Vars(r)["id"] if _, err := s.getExec(id, false); err == nil { w.WriteHeader(http.StatusOK) return } w.WriteHeader(http.StatusNotFound) } func (s *DockerServer) inspectExecContainer(w http.ResponseWriter, r *http.Request) { id := mux.Vars(r)["id"] if exec, err := s.getExec(id, true); err == nil { w.WriteHeader(http.StatusOK) w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(exec) return } w.WriteHeader(http.StatusNotFound) } func (s *DockerServer) getExec(id string, copy bool) (*docker.ExecInspect, error) { s.execMut.RLock() defer s.execMut.RUnlock() for _, exec := range s.execs { if exec.ID == id { if copy { cp := *exec exec = &cp } return exec, nil } } return nil, errors.New("exec not found") } func (s *DockerServer) findNetwork(idOrName string) (*docker.Network, int, error) { s.netMut.RLock() defer s.netMut.RUnlock() for i, network := range s.networks { if network.ID == idOrName || network.Name == idOrName { return network, i, nil } } return nil, -1, errors.New("no such network") } func (s *DockerServer) listNetworks(w http.ResponseWriter, r *http.Request) { s.netMut.RLock() result := make([]docker.Network, 0, len(s.networks)) for _, network := range s.networks { result = append(result, *network) } s.netMut.RUnlock() w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) json.NewEncoder(w).Encode(result) } func (s *DockerServer) networkInfo(w http.ResponseWriter, r *http.Request) { id := mux.Vars(r)["id"] network, _, err := s.findNetwork(id) if err != nil { http.Error(w, err.Error(), http.StatusNotFound) return } w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) json.NewEncoder(w).Encode(network) } // isValidName validates configuration objects supported by libnetwork func isValidName(name string) bool { if name == "" || strings.Contains(name, ".") { return false } return true } func (s *DockerServer) createNetwork(w http.ResponseWriter, r *http.Request) { var config *docker.CreateNetworkOptions defer r.Body.Close() err := json.NewDecoder(r.Body).Decode(&config) if err != nil { http.Error(w, err.Error(), http.StatusBadRequest) return } if !isValidName(config.Name) { http.Error(w, "Invalid network name", http.StatusBadRequest) return } if n, _, _ := s.findNetwork(config.Name); n != nil { http.Error(w, "network already exists", http.StatusForbidden) return } generatedID := s.generateID() network := docker.Network{ Name: config.Name, ID: generatedID, Driver: config.Driver, Containers: map[string]docker.Endpoint{}, } s.netMut.Lock() s.networks = append(s.networks, &network) s.netMut.Unlock() w.WriteHeader(http.StatusCreated) c := struct{ ID string }{ID: network.ID} json.NewEncoder(w).Encode(c) } func (s *DockerServer) removeNetwork(w http.ResponseWriter, r *http.Request) { id := mux.Vars(r)["id"] _, index, err := s.findNetwork(id) if err != nil { http.Error(w, err.Error(), http.StatusNotFound) return } s.netMut.Lock() defer s.netMut.Unlock() s.networks[index] = s.networks[len(s.networks)-1] s.networks = s.networks[:len(s.networks)-1] w.WriteHeader(http.StatusNoContent) } func (s *DockerServer) networksConnect(w http.ResponseWriter, r *http.Request) { id := mux.Vars(r)["id"] var config *docker.NetworkConnectionOptions defer r.Body.Close() err := json.NewDecoder(r.Body).Decode(&config) if err != nil { http.Error(w, err.Error(), http.StatusBadRequest) return } network, index, _ := s.findNetwork(id) container, _ := s.findContainer(config.Container) if network == nil || container == nil { http.Error(w, "network or container not found", http.StatusNotFound) return } if _, found := network.Containers[container.ID]; found { http.Error(w, "endpoint already exists in network", http.StatusBadRequest) return } s.netMut.Lock() s.networks[index].Containers[config.Container] = docker.Endpoint{} s.netMut.Unlock() w.WriteHeader(http.StatusOK) } func (s *DockerServer) listVolumes(w http.ResponseWriter, r *http.Request) { s.volMut.RLock() result := make([]docker.Volume, 0, len(s.volStore)) for _, volumeCounter := range s.volStore { result = append(result, volumeCounter.volume) } s.volMut.RUnlock() w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) json.NewEncoder(w).Encode(map[string][]docker.Volume{"Volumes": result}) } func (s *DockerServer) createVolume(w http.ResponseWriter, r *http.Request) { var data struct { *docker.CreateVolumeOptions } defer r.Body.Close() err := json.NewDecoder(r.Body).Decode(&data) if err != nil { http.Error(w, err.Error(), http.StatusBadRequest) return } volume := &docker.Volume{ Name: data.Name, Driver: data.Driver, } // If the name is not specified, generate one. Just using generateID for now if len(volume.Name) == 0 { volume.Name = s.generateID() } // If driver is not specified, use local if len(volume.Driver) == 0 { volume.Driver = "local" } // Mount point is a default one with name volume.Mountpoint = "/var/lib/docker/volumes/" + volume.Name // If the volume already exists, don't re-add it. exists := false s.volMut.Lock() if s.volStore != nil { _, exists = s.volStore[volume.Name] } else { // No volumes, create volStore s.volStore = make(map[string]*volumeCounter) } if !exists { s.volStore[volume.Name] = &volumeCounter{ volume: *volume, count: 0, } } s.volMut.Unlock() w.WriteHeader(http.StatusCreated) json.NewEncoder(w).Encode(volume) } func (s *DockerServer) inspectVolume(w http.ResponseWriter, r *http.Request) { s.volMut.RLock() defer s.volMut.RUnlock() name := mux.Vars(r)["name"] vol, err := s.findVolume(name) if err != nil { http.Error(w, err.Error(), http.StatusNotFound) return } w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) json.NewEncoder(w).Encode(vol.volume) } func (s *DockerServer) findVolume(name string) (*volumeCounter, error) { vol, ok := s.volStore[name] if !ok { return nil, errors.New("no such volume") } return vol, nil } func (s *DockerServer) removeVolume(w http.ResponseWriter, r *http.Request) { s.volMut.Lock() defer s.volMut.Unlock() name := mux.Vars(r)["name"] vol, err := s.findVolume(name) if err != nil { http.Error(w, err.Error(), http.StatusNotFound) return } if vol.count != 0 { http.Error(w, "volume in use and cannot be removed", http.StatusConflict) return } delete(s.volStore, vol.volume.Name) w.WriteHeader(http.StatusNoContent) } func (s *DockerServer) infoDocker(w http.ResponseWriter, r *http.Request) { s.cMut.RLock() defer s.cMut.RUnlock() s.iMut.RLock() defer s.iMut.RUnlock() var running, stopped, paused int for _, c := range s.containers { if c.State.Running { running++ } else { stopped++ } if c.State.Paused { paused++ } } var swarmInfo *swarm.Info if s.swarm != nil { swarmInfo = &swarm.Info{ NodeID: s.nodeID, } for _, n := range s.nodes { swarmInfo.RemoteManagers = append(swarmInfo.RemoteManagers, swarm.Peer{ NodeID: n.ID, Addr: n.ManagerStatus.Addr, }) } } envs := map[string]any{ "ID": "AAAA:XXXX:0000:BBBB:AAAA:XXXX:0000:BBBB:AAAA:XXXX:0000:BBBB", "Containers": len(s.containers), "ContainersRunning": running, "ContainersPaused": paused, "ContainersStopped": stopped, "Images": len(s.images), "Driver": "aufs", "DriverStatus": [][]string{}, "SystemStatus": nil, "Plugins": map[string]any{ "Volume": []string{ "local", }, "Network": []string{ "bridge", "null", "host", }, "Authorization": nil, }, "MemoryLimit": true, "SwapLimit": false, "CpuCfsPeriod": true, "CpuCfsQuota": true, "CPUShares": true, "CPUSet": true, "IPv4Forwarding": true, "BridgeNfIptables": true, "BridgeNfIp6tables": true, "Debug": false, "NFd": 79, "OomKillDisable": true, "NGoroutines": 101, "SystemTime": "2016-02-25T18:13:10.25870078Z", "ExecutionDriver": "native-0.2", "LoggingDriver": "json-file", "NEventsListener": 0, "KernelVersion": "3.13.0-77-generic", "OperatingSystem": "Ubuntu 14.04.3 LTS", "OSType": "linux", "Architecture": "x86_64", "IndexServerAddress": "https://index.docker.io/v1/", "RegistryConfig": map[string]any{ "InsecureRegistryCIDRs": []string{}, "IndexConfigs": map[string]any{}, "Mirrors": nil, }, "InitSha1": "e2042dbb0fcf49bb9da199186d9a5063cda92a01", "InitPath": "/usr/lib/docker/dockerinit", "NCPU": 1, "MemTotal": 2099204096, "DockerRootDir": "/var/lib/docker", "HttpProxy": "", "HttpsProxy": "", "NoProxy": "", "Name": "vagrant-ubuntu-trusty-64", "Labels": nil, "ExperimentalBuild": false, "ServerVersion": "1.10.1", "ClusterStore": "", "ClusterAdvertise": "", "Swarm": swarmInfo, } w.WriteHeader(http.StatusOK) json.NewEncoder(w).Encode(envs) } func (s *DockerServer) versionDocker(w http.ResponseWriter, r *http.Request) { envs := map[string]any{ "Version": "1.10.1", "Os": "linux", "KernelVersion": "3.13.0-77-generic", "GoVersion": "go1.4.2", "GitCommit": "9e83765", "Arch": "amd64", "ApiVersion": "1.22", "BuildTime": "2015-12-01T07:09:13.444803460+00:00", "Experimental": false, } w.WriteHeader(http.StatusOK) json.NewEncoder(w).Encode(envs) } // SwarmAddress returns the address if there's a fake swarm server enabled. func (s *DockerServer) SwarmAddress() string { if s.swarmServer == nil { return "" } return s.swarmServer.listener.Addr().String() } func (s *DockerServer) initSwarmNode(listenAddr, advertiseAddr string) (swarm.Node, error) { _, portPart, _ := net.SplitHostPort(listenAddr) if portPart == "" { portPart = "0" } var err error s.swarmServer, err = newSwarmServer(s, fmt.Sprintf("127.0.0.1:%s", portPart)) if err != nil { return swarm.Node{}, err } if advertiseAddr == "" { advertiseAddr = s.SwarmAddress() } hostPart, portPart, err := net.SplitHostPort(advertiseAddr) if err != nil { hostPart = advertiseAddr } if portPart == "" || portPart == "0" { _, portPart, _ = net.SplitHostPort(s.SwarmAddress()) } s.nodeID = s.generateID() return swarm.Node{ ID: s.nodeID, Status: swarm.NodeStatus{ Addr: hostPart, State: swarm.NodeStateReady, }, ManagerStatus: &swarm.ManagerStatus{ Addr: fmt.Sprintf("%s:%s", hostPart, portPart), }, }, nil } go-dockerclient-1.12.0/testing/server_test.go000066400000000000000000002743351465717111200212400ustar00rootroot00000000000000// Copyright 2013 go-dockerclient authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package testing import ( "archive/tar" "bufio" "bytes" "encoding/json" "fmt" "io" "math/rand" "net" "net/http" "net/http/httptest" "net/url" "os" "reflect" "slices" "sort" "strings" "sync" "testing" "time" "github.com/docker/docker/api/types/swarm" docker "github.com/fsouza/go-dockerclient" ) func TestNewServer(t *testing.T) { t.Parallel() server, err := NewServer("127.0.0.1:0", nil, nil) if err != nil { t.Fatal(err) } defer server.listener.Close() conn, err := net.Dial("tcp", server.listener.Addr().String()) if err != nil { t.Fatal(err) } conn.Close() } func TestNewTLSServer(t *testing.T) { t.Parallel() tlsConfig := TLSConfig{ CertPath: "./data/server.pem", CertKeyPath: "./data/serverkey.pem", RootCAPath: "./data/ca.pem", } server, err := NewTLSServer("127.0.0.1:0", nil, nil, tlsConfig) if err != nil { t.Fatal(err) } defer server.listener.Close() conn, err := net.Dial("tcp", server.listener.Addr().String()) if err != nil { t.Fatal(err) } conn.Close() client, err := docker.NewTLSClient(server.URL(), "./data/cert.pem", "./data/key.pem", "./data/ca.pem") if err != nil { t.Fatal(err) } err = client.Ping() if err != nil { t.Fatal(err) } } func TestServerStop(t *testing.T) { t.Parallel() const retries = 3 server, err := NewServer("127.0.0.1:0", nil, nil) if err != nil { t.Fatal(err) } server.Stop() _, err = net.Dial("tcp", server.listener.Addr().String()) for i := 0; i < retries && err == nil; i++ { time.Sleep(100 * time.Millisecond) _, err = net.Dial("tcp", server.listener.Addr().String()) } if err == nil { t.Error("Unexpected error when dialing to stopped server") } } func TestServerStopNoListener(t *testing.T) { t.Parallel() server := baseDockerServer() server.Stop() } func TestServerURL(t *testing.T) { t.Parallel() server, err := NewServer("127.0.0.1:0", nil, nil) if err != nil { t.Fatal(err) } defer server.Stop() url := server.URL() if expected := "http://" + server.listener.Addr().String() + "/"; url != expected { t.Errorf("DockerServer.URL(): Want %q. Got %q.", expected, url) } } func TestServerURLNoListener(t *testing.T) { t.Parallel() server := baseDockerServer() url := server.URL() if url != "" { t.Errorf("DockerServer.URL(): Expected empty URL on handler mode, got %q.", url) } } func TestHandleWithHook(t *testing.T) { t.Parallel() var called bool server, _ := NewServer("127.0.0.1:0", nil, func(*http.Request) { called = true }) defer server.Stop() recorder := httptest.NewRecorder() request, _ := http.NewRequest(http.MethodGet, "/containers/json?all=1", nil) server.ServeHTTP(recorder, request) if !called { t.Error("ServeHTTP did not call the hook function.") } } func TestSetHook(t *testing.T) { t.Parallel() var called bool server, _ := NewServer("127.0.0.1:0", nil, nil) defer server.Stop() server.SetHook(func(*http.Request) { called = true }) recorder := httptest.NewRecorder() request, _ := http.NewRequest(http.MethodGet, "/containers/json?all=1", nil) server.ServeHTTP(recorder, request) if !called { t.Error("ServeHTTP did not call the hook function.") } } func TestCustomHandler(t *testing.T) { t.Parallel() var called bool server, _ := NewServer("127.0.0.1:0", nil, nil) defer server.Stop() addContainers(server, 2) server.CustomHandler("/containers/json", http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { called = true fmt.Fprint(w, "Hello world") })) recorder := httptest.NewRecorder() request, _ := http.NewRequest(http.MethodGet, "/containers/json?all=1", nil) server.ServeHTTP(recorder, request) if !called { t.Error("Did not call the custom handler") } if got := recorder.Body.String(); got != "Hello world" { t.Errorf("Wrong output for custom handler: want %q. Got %q.", "Hello world", got) } } func TestCustomHandlerRegexp(t *testing.T) { t.Parallel() var called bool server, _ := NewServer("127.0.0.1:0", nil, nil) defer server.Stop() addContainers(server, 2) server.CustomHandler("/containers/.*/json", http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { called = true fmt.Fprint(w, "Hello world") })) recorder := httptest.NewRecorder() request, _ := http.NewRequest(http.MethodGet, "/containers/.*/json?all=1", nil) server.ServeHTTP(recorder, request) if !called { t.Error("Did not call the custom handler") } if got := recorder.Body.String(); got != "Hello world" { t.Errorf("Wrong output for custom handler: want %q. Got %q.", "Hello world", got) } } func TestListContainers(t *testing.T) { t.Parallel() server := baseDockerServer() containers := addContainers(&server, 2) server.buildMuxer() recorder := httptest.NewRecorder() request, _ := http.NewRequest(http.MethodGet, "/containers/json?all=1", nil) server.ServeHTTP(recorder, request) if recorder.Code != http.StatusOK { t.Errorf("ListContainers: wrong status. Want %d. Got %d.", http.StatusOK, recorder.Code) } expected := make([]docker.APIContainers, 2) for i, container := range containers { expected[i] = docker.APIContainers{ ID: container.ID, Image: container.Image, Command: strings.Join(container.Config.Cmd, " "), Created: container.Created.Unix(), Status: container.State.String(), Ports: container.NetworkSettings.PortMappingAPI(), Names: []string{"/" + container.Name}, State: container.State.StateString(), Labels: map[string]string{"key": fmt.Sprintf("val-%d", i)}, } } sortFn := func(left, right docker.APIContainers) int { return strings.Compare(left.ID, right.ID) } slices.SortFunc(expected, sortFn) var got []docker.APIContainers err := json.NewDecoder(recorder.Body).Decode(&got) if err != nil { t.Fatal(err) } slices.SortFunc(got, sortFn) if !reflect.DeepEqual(got, expected) { t.Errorf("ListContainers.\nWant %#v.\nGot %#v.", expected, got) } } func TestListRunningContainers(t *testing.T) { t.Parallel() server := baseDockerServer() addContainers(&server, 2) server.buildMuxer() recorder := httptest.NewRecorder() request, _ := http.NewRequest(http.MethodGet, "/containers/json?all=0", nil) server.ServeHTTP(recorder, request) if recorder.Code != http.StatusOK { t.Errorf("ListRunningContainers: wrong status. Want %d. Got %d.", http.StatusOK, recorder.Code) } var got []docker.APIContainers err := json.NewDecoder(recorder.Body).Decode(&got) if err != nil { t.Fatal(err) } if len(got) != 0 { t.Errorf("ListRunningContainers: Want 0. Got %d.", len(got)) } } func TestListContainersFilterLabels(t *testing.T) { t.Parallel() server := baseDockerServer() addContainers(&server, 3) server.buildMuxer() recorder := httptest.NewRecorder() filters := url.QueryEscape(`{"label": ["key=val-1"]}`) request, _ := http.NewRequest(http.MethodGet, "/containers/json?all=1&filters="+filters, nil) server.ServeHTTP(recorder, request) if recorder.Code != http.StatusOK { t.Errorf("TestListContainersFilterLabels: wrong status. Want %d. Got %d.", http.StatusOK, recorder.Code) } var got []docker.APIContainers err := json.NewDecoder(recorder.Body).Decode(&got) if err != nil { t.Fatal(err) } if len(got) != 1 { t.Errorf("TestListContainersFilterLabels: Want 1. Got %d.", len(got)) } filters = url.QueryEscape(`{"label": ["key="]}`) request, _ = http.NewRequest(http.MethodGet, "/containers/json?all=1&filters="+filters, nil) server.ServeHTTP(recorder, request) if recorder.Code != http.StatusOK { t.Errorf("TestListContainersFilterLabels: wrong status. Want %d. Got %d.", http.StatusOK, recorder.Code) } err = json.NewDecoder(recorder.Body).Decode(&got) if err != nil { t.Fatal(err) } if len(got) != 0 { t.Errorf("TestListContainersFilterLabels: Want 0. Got %d.", len(got)) } filters = url.QueryEscape(`{"label": ["key"]}`) request, _ = http.NewRequest(http.MethodGet, "/containers/json?all=1&filters="+filters, nil) server.ServeHTTP(recorder, request) if recorder.Code != http.StatusOK { t.Errorf("TestListContainersFilterLabels: wrong status. Want %d. Got %d.", http.StatusOK, recorder.Code) } err = json.NewDecoder(recorder.Body).Decode(&got) if err != nil { t.Fatal(err) } if len(got) != 3 { t.Errorf("TestListContainersFilterLabels: Want 3. Got %d.", len(got)) } } func TestCreateContainer(t *testing.T) { t.Parallel() server := baseDockerServer() server.imgIDs = map[string]string{"base": "a1234"} server.uploadedFiles = map[string]string{"a1234": "/abcd"} server.buildMuxer() recorder := httptest.NewRecorder() body := `{"Hostname":"", "User":"ubuntu", "Memory":0, "MemorySwap":0, "AttachStdin":false, "AttachStdout":true, "AttachStderr":true, "PortSpecs":null, "Tty":false, "OpenStdin":false, "StdinOnce":false, "Env":null, "Cmd":["date"], "Image":"base", "Volumes":{}, "VolumesFrom":"","HostConfig":{"Binds":["/var/run/docker.sock:/var/run/docker.sock:rw"]}}` request, _ := http.NewRequest(http.MethodPost, "/containers/create", strings.NewReader(body)) server.ServeHTTP(recorder, request) if recorder.Code != http.StatusCreated { t.Errorf("CreateContainer: wrong status. Want %d. Got %d.", http.StatusCreated, recorder.Code) } var returned docker.Container err := json.NewDecoder(recorder.Body).Decode(&returned) if err != nil { t.Fatal(err) } stored := getContainer(&server) if returned.ID != stored.ID { t.Errorf("CreateContainer: ID mismatch. Stored: %q. Returned: %q.", stored.ID, returned.ID) } if stored.State.Running { t.Errorf("CreateContainer should not set container to running state.") } if !stored.State.StartedAt.IsZero() { t.Errorf("CreateContainer should not set startedAt in container state.") } if stored.Config.User != "ubuntu" { t.Errorf("CreateContainer: wrong config. Expected: %q. Returned: %q.", "ubuntu", stored.Config.User) } if stored.Config.Hostname != returned.ID[:12] { t.Errorf("CreateContainer: wrong hostname. Expected: %q. Returned: %q.", returned.ID[:12], stored.Config.Hostname) } expectedBind := []string{"/var/run/docker.sock:/var/run/docker.sock:rw"} if !reflect.DeepEqual(stored.HostConfig.Binds, expectedBind) { t.Errorf("CreateContainer: wrong host config. Expected: %v. Returned %v.", expectedBind, stored.HostConfig.Binds) } if val, ok := server.uploadedFiles[stored.ID]; !ok { t.Error("CreateContainer: uploadedFiles should exist.") } else if val != "/abcd" { t.Errorf("CreateContainer: wrong uploadedFile. Want '/abcd', got %s.", val) } } func getContainer(server *DockerServer) *docker.Container { var cont *docker.Container for _, cont = range server.containers { } return cont } func TestCreateContainerWithNotifyChannel(t *testing.T) { t.Parallel() ch := make(chan *docker.Container, 1) server := baseDockerServer() server.imgIDs = map[string]string{"base": "a1234"} server.cChan = ch server.buildMuxer() recorder := httptest.NewRecorder() body := `{"Hostname":"", "User":"", "Memory":0, "MemorySwap":0, "AttachStdin":false, "AttachStdout":true, "AttachStderr":true, "PortSpecs":null, "Tty":false, "OpenStdin":false, "StdinOnce":false, "Env":null, "Cmd":["date"], "Image":"base", "Volumes":{}, "VolumesFrom":""}` request, _ := http.NewRequest(http.MethodPost, "/containers/create", strings.NewReader(body)) server.ServeHTTP(recorder, request) if recorder.Code != http.StatusCreated { t.Errorf("CreateContainer: wrong status. Want %d. Got %d.", http.StatusCreated, recorder.Code) } if notified := <-ch; notified != getContainer(&server) { t.Errorf("CreateContainer: did not notify the proper container. Want %q. Got %q.", getContainer(&server).ID, notified.ID) } } func TestCreateContainerInvalidBody(t *testing.T) { t.Parallel() server := baseDockerServer() server.buildMuxer() recorder := httptest.NewRecorder() request, _ := http.NewRequest(http.MethodPost, "/containers/create", strings.NewReader("whaaaaaat---")) server.ServeHTTP(recorder, request) if recorder.Code != http.StatusBadRequest { t.Errorf("CreateContainer: wrong status. Want %d. Got %d.", http.StatusBadRequest, recorder.Code) } } func TestCreateContainerDuplicateName(t *testing.T) { t.Parallel() server := baseDockerServer() server.buildMuxer() server.imgIDs = map[string]string{"base": "a1234"} containers := addContainers(&server, 1) containers[0].Name = "mycontainer" server.contNameToID[containers[0].Name] = containers[0].ID recorder := httptest.NewRecorder() body := `{"Hostname":"", "User":"ubuntu", "Memory":0, "MemorySwap":0, "AttachStdin":false, "AttachStdout":true, "AttachStderr":true, "PortSpecs":null, "Tty":false, "OpenStdin":false, "StdinOnce":false, "Env":null, "Cmd":["date"], "Image":"base", "Volumes":{}, "VolumesFrom":"","HostConfig":{"Binds":["/var/run/docker.sock:/var/run/docker.sock:rw"]}}` request, _ := http.NewRequest(http.MethodPost, "/containers/create?name=mycontainer", strings.NewReader(body)) server.ServeHTTP(recorder, request) if recorder.Code != http.StatusConflict { t.Errorf("CreateContainer: wrong status. Want %d. Got %d.", http.StatusConflict, recorder.Code) } } func TestCreateMultipleContainersEmptyName(t *testing.T) { t.Parallel() server := baseDockerServer() server.buildMuxer() server.imgIDs = map[string]string{"base": "a1234"} addContainers(&server, 1) getContainer(&server).Name = "" recorder := httptest.NewRecorder() body := `{"Hostname":"", "User":"ubuntu", "Memory":0, "MemorySwap":0, "AttachStdin":false, "AttachStdout":true, "AttachStderr":true, "PortSpecs":null, "Tty":false, "OpenStdin":false, "StdinOnce":false, "Env":null, "Cmd":["date"], "Image":"base", "Volumes":{}, "VolumesFrom":"","HostConfig":{"Binds":["/var/run/docker.sock:/var/run/docker.sock:rw"]}}` request, _ := http.NewRequest(http.MethodPost, "/containers/create", strings.NewReader(body)) server.ServeHTTP(recorder, request) if recorder.Code != http.StatusCreated { t.Errorf("CreateContainer: wrong status. Want %d. Got %d.", http.StatusCreated, recorder.Code) } var returned docker.Container err := json.NewDecoder(recorder.Body).Decode(&returned) if err != nil { t.Fatal(err) } stored, err := server.findContainer(returned.ID) if err != nil { t.Fatal(err) } if returned.ID != stored.ID { t.Errorf("CreateContainer: ID mismatch. Stored: %q. Returned: %q.", stored.ID, returned.ID) } if stored.State.Running { t.Errorf("CreateContainer should not set container to running state.") } if stored.Config.User != "ubuntu" { t.Errorf("CreateContainer: wrong config. Expected: %q. Returned: %q.", "ubuntu", stored.Config.User) } expectedBind := []string{"/var/run/docker.sock:/var/run/docker.sock:rw"} if !reflect.DeepEqual(stored.HostConfig.Binds, expectedBind) { t.Errorf("CreateContainer: wrong host config. Expected: %v. Returned %v.", expectedBind, stored.HostConfig.Binds) } } func TestCreateContainerInvalidName(t *testing.T) { t.Parallel() server := baseDockerServer() server.buildMuxer() recorder := httptest.NewRecorder() body := `{"Hostname":"", "User":"", "Memory":0, "MemorySwap":0, "AttachStdin":false, "AttachStdout":true, "AttachStderr":true, "PortSpecs":null, "Tty":false, "OpenStdin":false, "StdinOnce":false, "Env":null, "Cmd":["date"], "Image":"base", "Volumes":{}, "VolumesFrom":""}` request, _ := http.NewRequest(http.MethodPost, "/containers/create?name=myapp/container1", strings.NewReader(body)) server.ServeHTTP(recorder, request) if recorder.Code != http.StatusInternalServerError { t.Errorf("CreateContainer: wrong status. Want %d. Got %d.", http.StatusInternalServerError, recorder.Code) } expectedBody := "Invalid container name\n" if got := recorder.Body.String(); got != expectedBody { t.Errorf("CreateContainer: wrong body. Want %q. Got %q.", expectedBody, got) } } func TestCreateContainerImageNotFound(t *testing.T) { t.Parallel() server := baseDockerServer() server.buildMuxer() recorder := httptest.NewRecorder() body := `{"Hostname":"", "User":"", "Memory":0, "MemorySwap":0, "AttachStdin":false, "AttachStdout":true, "AttachStderr":true, "PortSpecs":null, "Tty":false, "OpenStdin":false, "StdinOnce":false, "Env":null, "Cmd":["date"], "Image":"base", "Volumes":{}, "VolumesFrom":""}` request, _ := http.NewRequest(http.MethodPost, "/containers/create", strings.NewReader(body)) server.ServeHTTP(recorder, request) if recorder.Code != http.StatusNotFound { t.Errorf("CreateContainer: wrong status. Want %d. Got %d.", http.StatusNotFound, recorder.Code) } } func TestRenameContainer(t *testing.T) { t.Parallel() server := baseDockerServer() containers := addContainers(&server, 2) server.buildMuxer() recorder := httptest.NewRecorder() newName := containers[0].Name + "abc" path := fmt.Sprintf("/containers/%s/rename?name=%s", containers[0].ID, newName) request, _ := http.NewRequest(http.MethodPost, path, nil) server.ServeHTTP(recorder, request) if recorder.Code != http.StatusNoContent { t.Errorf("RenameContainer: wrong status. Want %d. Got %d.", http.StatusNoContent, recorder.Code) } container := containers[0] if container.Name != newName { t.Errorf("RenameContainer: did not rename the container. Want %q. Got %q.", newName, container.Name) } } func TestRenameContainerNotFound(t *testing.T) { t.Parallel() server := baseDockerServer() server.buildMuxer() recorder := httptest.NewRecorder() request, _ := http.NewRequest(http.MethodPost, "/containers/blabla/rename?name=something", nil) server.ServeHTTP(recorder, request) if recorder.Code != http.StatusNotFound { t.Errorf("RenameContainer: wrong status. Want %d. Got %d.", http.StatusNotFound, recorder.Code) } } func TestCommitContainer(t *testing.T) { t.Parallel() server := baseDockerServer() containers := addContainers(&server, 2) server.uploadedFiles = map[string]string{containers[0].ID: "/abcd"} server.buildMuxer() recorder := httptest.NewRecorder() request, _ := http.NewRequest(http.MethodPost, "/commit?container="+containers[0].ID, nil) server.ServeHTTP(recorder, request) if recorder.Code != http.StatusOK { t.Errorf("CommitContainer: wrong status. Want %d. Got %d.", http.StatusOK, recorder.Code) } if len(server.images) != 1 { t.Errorf("CommitContainer: wrong images len in server. Want 1. Got %q.", len(server.images)) } imgID := fmt.Sprintf("img-%s", containers[0].ID) expected := fmt.Sprintf(`{"ID":"%s"}`, imgID) if got := recorder.Body.String(); got != expected { t.Errorf("CommitContainer: wrong response body. Want %q. Got %q.", expected, got) } if server.images[imgID].Config == nil { t.Error("CommitContainer: image Config should not be nil.") } if val, ok := server.uploadedFiles[server.images[imgID].ID]; !ok { t.Error("CommitContainer: uploadedFiles should exist.") } else if val != "/abcd" { t.Errorf("CommitContainer: wrong uploadedFile. Want '/abcd', got %s.", val) } } func TestCommitContainerComplete(t *testing.T) { t.Parallel() server := baseDockerServer() containers := addContainers(&server, 2) server.buildMuxer() recorder := httptest.NewRecorder() qs := make(url.Values) qs.Add("container", containers[0].ID) qs.Add("repo", "tsuru/python") qs.Add("m", "saving") qs.Add("author", "developers") qs.Add("run", `{"Cmd": ["cat", "/world"],"PortSpecs":["22"]}`) request, _ := http.NewRequest(http.MethodPost, "/commit?"+qs.Encode(), nil) server.ServeHTTP(recorder, request) imgID := fmt.Sprintf("img-%s", containers[0].ID) image := server.images[imgID] if image.Parent != containers[0].Image { t.Errorf("CommitContainer: wrong parent image. Want %q. Got %q.", containers[0].Image, image.Parent) } if image.Container != containers[0].ID { t.Errorf("CommitContainer: wrong container. Want %q. Got %q.", containers[0].ID, image.Container) } message := "saving" if image.Comment != message { t.Errorf("CommitContainer: wrong comment (commit message). Want %q. Got %q.", message, image.Comment) } author := "developers" if image.Author != author { t.Errorf("CommitContainer: wrong author. Want %q. Got %q.", author, image.Author) } if id := server.imgIDs["tsuru/python"]; id != image.ID { t.Errorf("CommitContainer: wrong ID saved for repository. Want %q. Got %q.", image.ID, id) } portSpecs := []string{"22"} if !reflect.DeepEqual(image.Config.PortSpecs, portSpecs) { t.Errorf("CommitContainer: wrong port spec in config. Want %#v. Got %#v.", portSpecs, image.Config.PortSpecs) } cmd := []string{"cat", "/world"} if !reflect.DeepEqual(image.Config.Cmd, cmd) { t.Errorf("CommitContainer: wrong cmd in config. Want %#v. Got %#v.", cmd, image.Config.Cmd) } } func TestCommitContainerWithTag(t *testing.T) { t.Parallel() server := baseDockerServer() containers := addContainers(&server, 2) server.buildMuxer() recorder := httptest.NewRecorder() queryString := "container=" + containers[0].ID + "&repo=tsuru/python&tag=v1" request, _ := http.NewRequest(http.MethodPost, "/commit?"+queryString, nil) server.ServeHTTP(recorder, request) imgID := fmt.Sprintf("img-%s", containers[0].ID) image := server.images[imgID] if image.Parent != containers[0].Image { t.Errorf("CommitContainer: wrong parent image. Want %q. Got %q.", containers[0].Image, image.Parent) } if image.Container != containers[0].ID { t.Errorf("CommitContainer: wrong container. Want %q. Got %q.", containers[0].ID, image.Container) } if id := server.imgIDs["tsuru/python:v1"]; id != image.ID { t.Errorf("CommitContainer: wrong ID saved for repository. Want %q. Got %q.", image.ID, id) } } func TestCommitContainerInvalidRun(t *testing.T) { t.Parallel() server := baseDockerServer() addContainers(&server, 1) server.buildMuxer() recorder := httptest.NewRecorder() request, _ := http.NewRequest(http.MethodPost, "/commit?container="+getContainer(&server).ID+"&run=abc---", nil) server.ServeHTTP(recorder, request) if recorder.Code != http.StatusBadRequest { t.Errorf("CommitContainer. Wrong status. Want %d. Got %d.", http.StatusBadRequest, recorder.Code) } } func TestCommitContainerNotFound(t *testing.T) { t.Parallel() server := baseDockerServer() server.buildMuxer() recorder := httptest.NewRecorder() request, _ := http.NewRequest(http.MethodPost, "/commit?container=abc123", nil) server.ServeHTTP(recorder, request) if recorder.Code != http.StatusNotFound { t.Errorf("CommitContainer. Wrong status. Want %d. Got %d.", http.StatusNotFound, recorder.Code) } } func TestInspectContainer(t *testing.T) { t.Parallel() server := baseDockerServer() containers := addContainers(&server, 2) server.buildMuxer() recorder := httptest.NewRecorder() path := fmt.Sprintf("/containers/%s/json", containers[0].ID) request, _ := http.NewRequest(http.MethodGet, path, nil) server.ServeHTTP(recorder, request) if recorder.Code != http.StatusOK { t.Errorf("InspectContainer: wrong status. Want %d. Got %d.", http.StatusOK, recorder.Code) } expected := containers[0] var got docker.Container err := json.NewDecoder(recorder.Body).Decode(&got) if err != nil { t.Fatal(err) } if !reflect.DeepEqual(got.Config, expected.Config) { t.Errorf("InspectContainer: wrong value. Want %#v. Got %#v.", *expected, got) } if !reflect.DeepEqual(got.NetworkSettings, expected.NetworkSettings) { t.Errorf("InspectContainer: wrong value. Want %#v. Got %#v.", *expected, got) } got.State.StartedAt = expected.State.StartedAt got.State.FinishedAt = expected.State.FinishedAt got.Config = expected.Config got.Created = expected.Created got.NetworkSettings = expected.NetworkSettings if !reflect.DeepEqual(got, *expected) { t.Errorf("InspectContainer: wrong value. Want %#v. Got %#v.", *expected, got) } } func TestInspectContainerNotFound(t *testing.T) { t.Parallel() server := baseDockerServer() server.buildMuxer() recorder := httptest.NewRecorder() request, _ := http.NewRequest(http.MethodGet, "/containers/abc123/json", nil) server.ServeHTTP(recorder, request) if recorder.Code != http.StatusNotFound { t.Errorf("InspectContainer: wrong status code. Want %d. Got %d.", http.StatusNotFound, recorder.Code) } } func TestTopContainer(t *testing.T) { t.Parallel() server := baseDockerServer() addContainers(&server, 1) getContainer(&server).State.Running = true server.buildMuxer() recorder := httptest.NewRecorder() path := fmt.Sprintf("/containers/%s/top", getContainer(&server).ID) request, _ := http.NewRequest(http.MethodGet, path, nil) server.ServeHTTP(recorder, request) if recorder.Code != http.StatusOK { t.Errorf("TopContainer: wrong status. Want %d. Got %d.", http.StatusOK, recorder.Code) } var got docker.TopResult err := json.NewDecoder(recorder.Body).Decode(&got) if err != nil { t.Fatal(err) } if !reflect.DeepEqual(got.Titles, []string{"UID", "PID", "PPID", "C", "STIME", "TTY", "TIME", "CMD"}) { t.Fatalf("TopContainer: Unexpected titles, got: %#v", got.Titles) } if len(got.Processes) != 1 { t.Fatalf("TopContainer: Unexpected process len, got: %d", len(got.Processes)) } if got.Processes[0][len(got.Processes[0])-1] != "ls -la .." { t.Fatalf("TopContainer: Unexpected command name, got: %s", got.Processes[0][len(got.Processes[0])-1]) } } func TestTopContainerNotFound(t *testing.T) { t.Parallel() server := baseDockerServer() server.buildMuxer() recorder := httptest.NewRecorder() request, _ := http.NewRequest(http.MethodGet, "/containers/xyz/top", nil) server.ServeHTTP(recorder, request) if recorder.Code != http.StatusNotFound { t.Errorf("TopContainer: wrong status. Want %d. Got %d.", http.StatusNotFound, recorder.Code) } } func TestTopContainerStopped(t *testing.T) { t.Parallel() server := baseDockerServer() addContainers(&server, 1) server.buildMuxer() recorder := httptest.NewRecorder() path := fmt.Sprintf("/containers/%s/top", getContainer(&server).ID) request, _ := http.NewRequest(http.MethodGet, path, nil) server.ServeHTTP(recorder, request) if recorder.Code != http.StatusInternalServerError { t.Errorf("TopContainer: wrong status. Want %d. Got %d.", http.StatusInternalServerError, recorder.Code) } } func TestStartContainer(t *testing.T) { t.Parallel() server := baseDockerServer() addContainers(&server, 1) server.buildMuxer() memory := int64(536870912) hostConfig := docker.HostConfig{Memory: memory} configBytes, err := json.Marshal(hostConfig) if err != nil { t.Fatal(err) } recorder := httptest.NewRecorder() path := fmt.Sprintf("/containers/%s/start", getContainer(&server).ID) request, _ := http.NewRequest(http.MethodPost, path, bytes.NewBuffer(configBytes)) server.ServeHTTP(recorder, request) if recorder.Code != http.StatusOK { t.Errorf("StartContainer: wrong status code. Want %d. Got %d.", http.StatusOK, recorder.Code) } if !getContainer(&server).State.Running { t.Error("StartContainer: did not set the container to running state") } if getContainer(&server).State.StartedAt.IsZero() { t.Error("StartContainer: did not set the startedAt container state") } if gotMemory := getContainer(&server).HostConfig.Memory; gotMemory != memory { t.Errorf("StartContainer: wrong HostConfig. Wants %d of memory. Got %d", memory, gotMemory) } } func TestStartContainerNoHostConfig(t *testing.T) { t.Parallel() server := baseDockerServer() addContainers(&server, 1) server.buildMuxer() memory := int64(536870912) hostConfig := docker.HostConfig{Memory: memory} getContainer(&server).HostConfig = &hostConfig recorder := httptest.NewRecorder() path := fmt.Sprintf("/containers/%s/start", getContainer(&server).ID) request, _ := http.NewRequest(http.MethodPost, path, strings.NewReader("")) server.ServeHTTP(recorder, request) if recorder.Code != http.StatusOK { t.Errorf("StartContainer: wrong status code. Want %d. Got %d.", http.StatusOK, recorder.Code) } if !getContainer(&server).State.Running { t.Error("StartContainer: did not set the container to running state") } if gotMemory := getContainer(&server).HostConfig.Memory; gotMemory != memory { t.Errorf("StartContainer: wrong HostConfig. Wants %d of memory. Got %d", memory, gotMemory) } } func TestStartContainerChangeNetwork(t *testing.T) { t.Parallel() server := baseDockerServer() addContainers(&server, 1) server.buildMuxer() hostConfig := docker.HostConfig{ PortBindings: map[docker.Port][]docker.PortBinding{ "8888/tcp": {{HostIP: "", HostPort: "12345"}}, }, } configBytes, err := json.Marshal(hostConfig) if err != nil { t.Fatal(err) } recorder := httptest.NewRecorder() path := fmt.Sprintf("/containers/%s/start", getContainer(&server).ID) request, _ := http.NewRequest(http.MethodPost, path, bytes.NewBuffer(configBytes)) server.ServeHTTP(recorder, request) if recorder.Code != http.StatusOK { t.Errorf("StartContainer: wrong status code. Want %d. Got %d.", http.StatusOK, recorder.Code) } if !getContainer(&server).State.Running { t.Error("StartContainer: did not set the container to running state") } portMapping := getContainer(&server).NetworkSettings.Ports["8888/tcp"] expected := []docker.PortBinding{{HostIP: "0.0.0.0", HostPort: "12345"}} if !reflect.DeepEqual(portMapping, expected) { t.Errorf("StartContainer: network not updated. Wants %#v ports. Got %#v", expected, portMapping) } } func TestStartContainerWithNotifyChannel(t *testing.T) { t.Parallel() ch := make(chan *docker.Container, 1) server := baseDockerServer() server.cChan = ch containers := addContainers(&server, 2) server.buildMuxer() recorder := httptest.NewRecorder() path := fmt.Sprintf("/containers/%s/start", containers[1].ID) request, _ := http.NewRequest(http.MethodPost, path, bytes.NewBuffer([]byte("{}"))) server.ServeHTTP(recorder, request) if recorder.Code != http.StatusOK { t.Errorf("StartContainer: wrong status code. Want %d. Got %d.", http.StatusOK, recorder.Code) } if notified := <-ch; notified != containers[1] { t.Errorf("StartContainer: did not notify the proper container. Want %q. Got %q.", containers[1].ID, notified.ID) } } func TestStartContainerNotFound(t *testing.T) { t.Parallel() server := baseDockerServer() server.buildMuxer() recorder := httptest.NewRecorder() path := "/containers/abc123/start" request, _ := http.NewRequest(http.MethodPost, path, bytes.NewBuffer([]byte("null"))) server.ServeHTTP(recorder, request) if recorder.Code != http.StatusNotFound { t.Errorf("StartContainer: wrong status code. Want %d. Got %d.", http.StatusNotFound, recorder.Code) } } func TestStartContainerAlreadyRunning(t *testing.T) { t.Parallel() server := baseDockerServer() addContainers(&server, 1) getContainer(&server).State.Running = true server.buildMuxer() recorder := httptest.NewRecorder() path := fmt.Sprintf("/containers/%s/start", getContainer(&server).ID) request, _ := http.NewRequest(http.MethodPost, path, bytes.NewBuffer([]byte("null"))) server.ServeHTTP(recorder, request) if recorder.Code != http.StatusNotModified { t.Errorf("StartContainer: wrong status code. Want %d. Got %d.", http.StatusNotModified, recorder.Code) } } func TestStopContainer(t *testing.T) { t.Parallel() server := baseDockerServer() addContainers(&server, 1) getContainer(&server).State.Running = true server.buildMuxer() recorder := httptest.NewRecorder() path := fmt.Sprintf("/containers/%s/stop", getContainer(&server).ID) request, _ := http.NewRequest(http.MethodPost, path, nil) server.ServeHTTP(recorder, request) if recorder.Code != http.StatusNoContent { t.Errorf("StopContainer: wrong status code. Want %d. Got %d.", http.StatusNoContent, recorder.Code) } if getContainer(&server).State.Running { t.Error("StopContainer: did not stop the container") } } func TestKillContainer(t *testing.T) { t.Parallel() server := baseDockerServer() addContainers(&server, 1) getContainer(&server).State.Running = true server.buildMuxer() recorder := httptest.NewRecorder() path := fmt.Sprintf("/containers/%s/kill", getContainer(&server).ID) request, _ := http.NewRequest(http.MethodPost, path, nil) server.ServeHTTP(recorder, request) if recorder.Code != http.StatusNoContent { t.Errorf("KillContainer: wrong status code. Want %d. Got %d.", http.StatusNoContent, recorder.Code) } if getContainer(&server).State.Running { t.Error("KillContainer: did not stop the container") } } func TestStopContainerWithNotifyChannel(t *testing.T) { t.Parallel() ch := make(chan *docker.Container, 1) server := baseDockerServer() server.cChan = ch containers := addContainers(&server, 2) containers[1].State.Running = true server.buildMuxer() recorder := httptest.NewRecorder() path := fmt.Sprintf("/containers/%s/stop", containers[1].ID) request, _ := http.NewRequest(http.MethodPost, path, nil) server.ServeHTTP(recorder, request) if recorder.Code != http.StatusNoContent { t.Errorf("StopContainer: wrong status code. Want %d. Got %d.", http.StatusNoContent, recorder.Code) } if notified := <-ch; notified != containers[1] { t.Errorf("StopContainer: did not notify the proper container. Want %q. Got %q.", containers[1].ID, notified.ID) } } func TestStopContainerNotFound(t *testing.T) { t.Parallel() server := baseDockerServer() server.buildMuxer() recorder := httptest.NewRecorder() path := "/containers/abc123/stop" request, _ := http.NewRequest(http.MethodPost, path, nil) server.ServeHTTP(recorder, request) if recorder.Code != http.StatusNotFound { t.Errorf("StopContainer: wrong status code. Want %d. Got %d.", http.StatusNotFound, recorder.Code) } } func TestStopContainerNotRunning(t *testing.T) { t.Parallel() server := baseDockerServer() addContainers(&server, 1) server.buildMuxer() recorder := httptest.NewRecorder() path := fmt.Sprintf("/containers/%s/stop", getContainer(&server).ID) request, _ := http.NewRequest(http.MethodPost, path, nil) server.ServeHTTP(recorder, request) if recorder.Code != http.StatusBadRequest { t.Errorf("StopContainer: wrong status code. Want %d. Got %d.", http.StatusBadRequest, recorder.Code) } } func TestPauseContainer(t *testing.T) { t.Parallel() server := baseDockerServer() addContainers(&server, 1) server.buildMuxer() recorder := httptest.NewRecorder() path := fmt.Sprintf("/containers/%s/pause", getContainer(&server).ID) request, _ := http.NewRequest(http.MethodPost, path, nil) server.ServeHTTP(recorder, request) if recorder.Code != http.StatusNoContent { t.Errorf("PauseContainer: wrong status code. Want %d. Got %d.", http.StatusNoContent, recorder.Code) } if !getContainer(&server).State.Paused { t.Error("PauseContainer: did not pause the container") } } func TestPauseContainerAlreadyPaused(t *testing.T) { t.Parallel() server := baseDockerServer() addContainers(&server, 1) getContainer(&server).State.Paused = true server.buildMuxer() recorder := httptest.NewRecorder() path := fmt.Sprintf("/containers/%s/pause", getContainer(&server).ID) request, _ := http.NewRequest(http.MethodPost, path, nil) server.ServeHTTP(recorder, request) if recorder.Code != http.StatusBadRequest { t.Errorf("PauseContainer: wrong status code. Want %d. Got %d.", http.StatusBadRequest, recorder.Code) } } func TestPauseContainerNotFound(t *testing.T) { t.Parallel() server := baseDockerServer() server.buildMuxer() recorder := httptest.NewRecorder() path := "/containers/abc123/pause" request, _ := http.NewRequest(http.MethodPost, path, nil) server.ServeHTTP(recorder, request) if recorder.Code != http.StatusNotFound { t.Errorf("PauseContainer: wrong status code. Want %d. Got %d.", http.StatusNotFound, recorder.Code) } } func TestUnpauseContainer(t *testing.T) { t.Parallel() server := baseDockerServer() addContainers(&server, 1) getContainer(&server).State.Paused = true server.buildMuxer() recorder := httptest.NewRecorder() path := fmt.Sprintf("/containers/%s/unpause", getContainer(&server).ID) request, _ := http.NewRequest(http.MethodPost, path, nil) server.ServeHTTP(recorder, request) if recorder.Code != http.StatusNoContent { t.Errorf("UnpauseContainer: wrong status code. Want %d. Got %d.", http.StatusNoContent, recorder.Code) } if getContainer(&server).State.Paused { t.Error("UnpauseContainer: did not unpause the container") } } func TestUnpauseContainerNotPaused(t *testing.T) { t.Parallel() server := baseDockerServer() addContainers(&server, 1) server.buildMuxer() recorder := httptest.NewRecorder() path := fmt.Sprintf("/containers/%s/unpause", getContainer(&server).ID) request, _ := http.NewRequest(http.MethodPost, path, nil) server.ServeHTTP(recorder, request) if recorder.Code != http.StatusBadRequest { t.Errorf("UnpauseContainer: wrong status code. Want %d. Got %d.", http.StatusBadRequest, recorder.Code) } } func TestUnpauseContainerNotFound(t *testing.T) { t.Parallel() server := baseDockerServer() server.buildMuxer() recorder := httptest.NewRecorder() path := "/containers/abc123/unpause" request, _ := http.NewRequest(http.MethodPost, path, nil) server.ServeHTTP(recorder, request) if recorder.Code != http.StatusNotFound { t.Errorf("UnpauseContainer: wrong status code. Want %d. Got %d.", http.StatusNotFound, recorder.Code) } } func TestWaitContainer(t *testing.T) { t.Parallel() server := baseDockerServer() addContainers(&server, 1) getContainer(&server).State.Running = true server.buildMuxer() recorder := httptest.NewRecorder() path := fmt.Sprintf("/containers/%s/wait", getContainer(&server).ID) request, _ := http.NewRequest(http.MethodPost, path, nil) go func() { server.cMut.Lock() getContainer(&server).State.Running = false server.cMut.Unlock() }() server.ServeHTTP(recorder, request) if recorder.Code != http.StatusOK { t.Errorf("WaitContainer: wrong status. Want %d. Got %d.", http.StatusOK, recorder.Code) } expected := `{"StatusCode":0}` + "\n" if body := recorder.Body.String(); body != expected { t.Errorf("WaitContainer: wrong body. Want %q. Got %q.", expected, body) } } func TestWaitContainerStatus(t *testing.T) { t.Parallel() server := baseDockerServer() addContainers(&server, 1) server.buildMuxer() getContainer(&server).State.ExitCode = 63 recorder := httptest.NewRecorder() path := fmt.Sprintf("/containers/%s/wait", getContainer(&server).ID) request, _ := http.NewRequest(http.MethodPost, path, nil) server.ServeHTTP(recorder, request) if recorder.Code != http.StatusOK { t.Errorf("WaitContainer: wrong status. Want %d. Got %d.", http.StatusOK, recorder.Code) } expected := `{"StatusCode":63}` + "\n" if body := recorder.Body.String(); body != expected { t.Errorf("WaitContainer: wrong body. Want %q. Got %q.", expected, body) } } func TestWaitContainerNotFound(t *testing.T) { t.Parallel() server := baseDockerServer() server.buildMuxer() recorder := httptest.NewRecorder() path := "/containers/abc123/wait" request, _ := http.NewRequest(http.MethodPost, path, nil) server.ServeHTTP(recorder, request) if recorder.Code != http.StatusNotFound { t.Errorf("WaitContainer: wrong status code. Want %d. Got %d.", http.StatusNotFound, recorder.Code) } } type HijackableResponseRecorder struct { httptest.ResponseRecorder readCh chan []byte } func (r *HijackableResponseRecorder) Hijack() (net.Conn, *bufio.ReadWriter, error) { myConn, otherConn := net.Pipe() r.readCh = make(chan []byte) go func() { data, _ := io.ReadAll(myConn) r.readCh <- data }() return otherConn, nil, nil } func (r *HijackableResponseRecorder) HijackBuffer() string { return string(<-r.readCh) } func TestLogContainer(t *testing.T) { t.Parallel() server := baseDockerServer() addContainers(&server, 1) getContainer(&server).State.Running = true server.buildMuxer() recorder := httptest.NewRecorder() path := fmt.Sprintf("/containers/%s/logs", getContainer(&server).ID) request, _ := http.NewRequest(http.MethodGet, path, nil) server.ServeHTTP(recorder, request) if recorder.Code != http.StatusOK { t.Errorf("LogContainer: wrong status. Want %d. Got %d.", http.StatusOK, recorder.Code) } } func TestLogContainerNotFound(t *testing.T) { t.Parallel() server := baseDockerServer() server.buildMuxer() recorder := httptest.NewRecorder() path := "/containers/abc123/logs" request, _ := http.NewRequest(http.MethodGet, path, nil) server.ServeHTTP(recorder, request) if recorder.Code != http.StatusNotFound { t.Errorf("LogContainer: wrong status. Want %d. Got %d.", http.StatusNotFound, recorder.Code) } } func TestAttachContainer(t *testing.T) { t.Parallel() server := baseDockerServer() addContainers(&server, 1) getContainer(&server).State.Running = true server.buildMuxer() recorder := &HijackableResponseRecorder{} path := fmt.Sprintf("/containers/%s/attach?logs=1", getContainer(&server).ID) request, _ := http.NewRequest(http.MethodPost, path, nil) server.ServeHTTP(recorder, request) lines := []string{ "\x01\x00\x00\x00\x00\x00\x00\x15Container is running", "\x01\x00\x00\x00\x00\x00\x00\x0fWhat happened?", "\x01\x00\x00\x00\x00\x00\x00\x13Something happened", } expected := strings.Join(lines, "\n") + "\n" if body := recorder.HijackBuffer(); body != expected { t.Errorf("AttachContainer: wrong body. Want %q. Got %q.", expected, body) } } func TestAttachContainerNotFound(t *testing.T) { t.Parallel() server := baseDockerServer() server.buildMuxer() recorder := &HijackableResponseRecorder{} path := "/containers/abc123/attach?logs=1" request, _ := http.NewRequest(http.MethodPost, path, nil) server.ServeHTTP(recorder, request) if recorder.Code != http.StatusNotFound { t.Errorf("AttachContainer: wrong status. Want %d. Got %d.", http.StatusNotFound, recorder.Code) } } func TestAttachContainerWithStreamBlocks(t *testing.T) { t.Parallel() server := baseDockerServer() addContainers(&server, 1) getContainer(&server).State.Running = true server.buildMuxer() path := fmt.Sprintf("/containers/%s/attach?logs=1&stdout=1&stream=1", getContainer(&server).ID) request, _ := http.NewRequest(http.MethodPost, path, nil) done := make(chan string) go func() { recorder := &HijackableResponseRecorder{} server.ServeHTTP(recorder, request) done <- recorder.HijackBuffer() }() select { case <-done: t.Fatalf("attach stream returned before container is stopped") case <-time.After(500 * time.Millisecond): } server.cMut.Lock() getContainer(&server).State.Running = false server.cMut.Unlock() var body string select { case body = <-done: case <-time.After(5 * time.Second): t.Fatalf("timed out waiting for attach to finish") } lines := []string{ "\x01\x00\x00\x00\x00\x00\x00\x15Container is running", "\x01\x00\x00\x00\x00\x00\x00\x0fWhat happened?", "\x01\x00\x00\x00\x00\x00\x00\x13Something happened", } expected := strings.Join(lines, "\n") + "\n" if body != expected { t.Errorf("AttachContainer: wrong body. Want %q. Got %q.", expected, body) } } func TestAttachContainerWithStreamBlocksOnCreatedContainers(t *testing.T) { t.Parallel() server := baseDockerServer() addContainers(&server, 1) getContainer(&server).State.Running = false getContainer(&server).State.StartedAt = time.Time{} server.buildMuxer() path := fmt.Sprintf("/containers/%s/attach?logs=1&stdout=1&stream=1", getContainer(&server).ID) request, _ := http.NewRequest(http.MethodPost, path, nil) done := make(chan string) go func() { recorder := &HijackableResponseRecorder{} server.ServeHTTP(recorder, request) done <- recorder.HijackBuffer() }() select { case <-done: t.Fatalf("attach stream returned before container is stopped") case <-time.After(500 * time.Millisecond): } server.cMut.Lock() getContainer(&server).State.StartedAt = time.Now() server.cMut.Unlock() var body string select { case body = <-done: case <-time.After(5 * time.Second): t.Fatalf("timed out waiting for attach to finish") } lines := []string{ "\x01\x00\x00\x00\x00\x00\x00\x19Container is not running", "\x01\x00\x00\x00\x00\x00\x00\x0fWhat happened?", "\x01\x00\x00\x00\x00\x00\x00\x13Something happened", } expected := strings.Join(lines, "\n") + "\n" if body != expected { t.Errorf("AttachContainer: wrong body. Want %q. Got %q.", expected, body) } } func TestRemoveContainer(t *testing.T) { t.Parallel() server := baseDockerServer() addContainers(&server, 1) server.buildMuxer() recorder := httptest.NewRecorder() path := fmt.Sprintf("/containers/%s", getContainer(&server).ID) request, _ := http.NewRequest(http.MethodDelete, path, nil) server.ServeHTTP(recorder, request) if recorder.Code != http.StatusNoContent { t.Errorf("RemoveContainer: wrong status. Want %d. Got %d.", http.StatusNoContent, recorder.Code) } if len(server.containers) > 0 { t.Error("RemoveContainer: did not remove the container.") } } func TestRemoveContainerByName(t *testing.T) { t.Parallel() server := baseDockerServer() addContainers(&server, 1) server.buildMuxer() recorder := httptest.NewRecorder() path := fmt.Sprintf("/containers/%s", getContainer(&server).Name) request, _ := http.NewRequest(http.MethodDelete, path, nil) server.ServeHTTP(recorder, request) if recorder.Code != http.StatusNoContent { t.Errorf("RemoveContainer: wrong status. Want %d. Got %d.", http.StatusNoContent, recorder.Code) } if len(server.containers) > 0 { t.Error("RemoveContainer: did not remove the container.") } } func TestRemoveContainerNotFound(t *testing.T) { t.Parallel() server := baseDockerServer() server.buildMuxer() recorder := httptest.NewRecorder() request, _ := http.NewRequest(http.MethodDelete, "/containers/abc123", nil) server.ServeHTTP(recorder, request) if recorder.Code != http.StatusNotFound { t.Errorf("RemoveContainer: wrong status. Want %d. Got %d.", http.StatusNotFound, recorder.Code) } } func TestRemoveContainerRunning(t *testing.T) { t.Parallel() server := baseDockerServer() addContainers(&server, 1) getContainer(&server).State.Running = true server.buildMuxer() recorder := httptest.NewRecorder() path := fmt.Sprintf("/containers/%s", getContainer(&server).ID) request, _ := http.NewRequest(http.MethodDelete, path, nil) server.ServeHTTP(recorder, request) if recorder.Code != http.StatusInternalServerError { t.Errorf("RemoveContainer: wrong status. Want %d. Got %d.", http.StatusInternalServerError, recorder.Code) } if len(server.containers) < 1 { t.Error("RemoveContainer: should not remove the container.") } } func TestRemoveContainerRunningForce(t *testing.T) { t.Parallel() server := baseDockerServer() addContainers(&server, 1) getContainer(&server).State.Running = true server.buildMuxer() recorder := httptest.NewRecorder() path := fmt.Sprintf("/containers/%s?%s", getContainer(&server).ID, "force=1") request, _ := http.NewRequest(http.MethodDelete, path, nil) server.ServeHTTP(recorder, request) if recorder.Code != http.StatusNoContent { t.Errorf("RemoveContainer: wrong status. Want %d. Got %d.", http.StatusNoContent, recorder.Code) } if len(server.containers) > 0 { t.Error("RemoveContainer: did not remove the container.") } } func TestPullImage(t *testing.T) { t.Parallel() server := baseDockerServer() server.buildMuxer() recorder := httptest.NewRecorder() request, _ := http.NewRequest(http.MethodPost, "/images/create?fromImage=base", nil) server.ServeHTTP(recorder, request) if recorder.Code != http.StatusOK { t.Errorf("PullImage: wrong status. Want %d. Got %d.", http.StatusOK, recorder.Code) } if len(server.images) != 1 { t.Errorf("PullImage: Want 1 image. Got %d.", len(server.images)) } if _, ok := server.imgIDs["base"]; !ok { t.Error("PullImage: Repository should not be empty.") } var image docker.Image for _, image = range server.images { } if image.Config == nil { t.Error("PullImage: Image Config should not be nil.") } } func TestPullImageWithTag(t *testing.T) { t.Parallel() server := baseDockerServer() server.buildMuxer() recorder := httptest.NewRecorder() request, _ := http.NewRequest(http.MethodPost, "/images/create?fromImage=base&tag=tag", nil) server.ServeHTTP(recorder, request) if recorder.Code != http.StatusOK { t.Errorf("PullImage: wrong status. Want %d. Got %d.", http.StatusOK, recorder.Code) } if len(server.images) != 1 { t.Errorf("PullImage: Want 1 image. Got %d.", len(server.images)) } if _, ok := server.imgIDs["base:tag"]; !ok { t.Error("PullImage: Repository should not be empty.") } } func TestPullImageWithShaTag(t *testing.T) { t.Parallel() server := baseDockerServer() server.buildMuxer() recorder := httptest.NewRecorder() request, _ := http.NewRequest(http.MethodPost, "/images/create?fromImage=base&tag=sha256:deadc0de", nil) server.ServeHTTP(recorder, request) if recorder.Code != http.StatusOK { t.Errorf("PullImage: wrong status. Want %d. Got %d.", http.StatusOK, recorder.Code) } if len(server.images) != 1 { t.Errorf("PullImage: Want 1 image. Got %d.", len(server.images)) } if _, ok := server.imgIDs["base@sha256:deadc0de"]; !ok { t.Error("PullImage: Repository should not be empty.") } } func TestPullImageExisting(t *testing.T) { t.Parallel() server := baseDockerServer() server.buildMuxer() recorder := httptest.NewRecorder() request, _ := http.NewRequest(http.MethodPost, "/images/create?fromImage=base", nil) server.ServeHTTP(recorder, request) if recorder.Code != http.StatusOK { t.Errorf("PullImage: wrong status. Want %d. Got %d.", http.StatusOK, recorder.Code) } if len(server.images) != 1 { t.Errorf("PullImage: Want 1 image. Got %d.", len(server.images)) } if _, ok := server.imgIDs["base"]; !ok { t.Error("PullImage: Repository should not be empty.") } oldID := server.imgIDs["base"] recorder = httptest.NewRecorder() request, _ = http.NewRequest(http.MethodPost, "/images/create?fromImage=base", nil) server.ServeHTTP(recorder, request) if recorder.Code != http.StatusOK { t.Errorf("PullImage: wrong status. Want %d. Got %d.", http.StatusOK, recorder.Code) } if len(server.images) != 1 { t.Errorf("PullImage: Want 1 image. Got %d.", len(server.images)) } if _, ok := server.imgIDs["base"]; !ok { t.Error("PullImage: Repository should not be empty.") } newID := server.imgIDs["base"] if oldID != newID { t.Error("PullImage: Image ID should be the same after second pull.") } } func TestPushImage(t *testing.T) { t.Parallel() server := baseDockerServer() server.imgIDs = map[string]string{"tsuru/python": "a123"} server.buildMuxer() recorder := httptest.NewRecorder() request, _ := http.NewRequest(http.MethodPost, "/images/tsuru/python/push", nil) server.ServeHTTP(recorder, request) if recorder.Code != http.StatusOK { t.Errorf("PushImage: wrong status. Want %d. Got %d.", http.StatusOK, recorder.Code) } } func TestPushImageWithTag(t *testing.T) { t.Parallel() server := baseDockerServer() server.imgIDs = map[string]string{"tsuru/python:v1": "a123"} server.buildMuxer() recorder := httptest.NewRecorder() request, _ := http.NewRequest(http.MethodPost, "/images/tsuru/python/push?tag=v1", nil) server.ServeHTTP(recorder, request) if recorder.Code != http.StatusOK { t.Errorf("PushImage: wrong status. Want %d. Got %d.", http.StatusOK, recorder.Code) } } func TestPushImageNotFound(t *testing.T) { t.Parallel() server := baseDockerServer() server.buildMuxer() recorder := httptest.NewRecorder() request, _ := http.NewRequest(http.MethodPost, "/images/tsuru/python/push", nil) server.ServeHTTP(recorder, request) if recorder.Code != http.StatusNotFound { t.Errorf("PushImage: wrong status. Want %d. Got %d.", http.StatusNotFound, recorder.Code) } } func TestTagImage(t *testing.T) { t.Parallel() server := baseDockerServer() server.imgIDs = map[string]string{"tsuru/python": "a123"} server.buildMuxer() recorder := httptest.NewRecorder() request, _ := http.NewRequest(http.MethodPost, "/images/tsuru/python/tag?repo=tsuru/new-python", nil) server.ServeHTTP(recorder, request) if recorder.Code != http.StatusCreated { t.Errorf("TagImage: wrong status. Want %d. Got %d.", http.StatusCreated, recorder.Code) } if server.imgIDs["tsuru/python"] != server.imgIDs["tsuru/new-python"] { t.Errorf("TagImage: did not tag the image") } } func TestTagImageWithRepoAndTag(t *testing.T) { t.Parallel() server := baseDockerServer() server.imgIDs = map[string]string{"tsuru/python": "a123"} server.buildMuxer() recorder := httptest.NewRecorder() request, _ := http.NewRequest(http.MethodPost, "/images/tsuru/python/tag?repo=tsuru/new-python&tag=v1", nil) server.ServeHTTP(recorder, request) if recorder.Code != http.StatusCreated { t.Errorf("TagImage: wrong status. Want %d. Got %d.", http.StatusCreated, recorder.Code) } if server.imgIDs["tsuru/python"] != server.imgIDs["tsuru/new-python:v1"] { t.Errorf("TagImage: did not tag the image") } } func TestTagImageWithID(t *testing.T) { t.Parallel() server := baseDockerServer() server.images = map[string]docker.Image{"myimgid": {ID: "myimgid"}} server.buildMuxer() recorder := httptest.NewRecorder() request, _ := http.NewRequest(http.MethodPost, "/images/myimgid/tag?repo=tsuru/new-python", nil) server.ServeHTTP(recorder, request) if recorder.Code != http.StatusCreated { t.Errorf("TagImage: wrong status. Want %d. Got %d.", http.StatusCreated, recorder.Code) } if server.imgIDs["tsuru/new-python"] != "myimgid" { t.Errorf("TagImage: did not tag the image") } } func TestTagImageNotFound(t *testing.T) { t.Parallel() server := baseDockerServer() server.buildMuxer() recorder := httptest.NewRecorder() request, _ := http.NewRequest(http.MethodPost, "/images/tsuru/python/tag", nil) server.ServeHTTP(recorder, request) if recorder.Code != http.StatusNotFound { t.Errorf("TagImage: wrong status. Want %d. Got %d.", http.StatusNotFound, recorder.Code) } } func TestInspectImage(t *testing.T) { t.Parallel() server := baseDockerServer() server.imgIDs = map[string]string{"tsuru/python": "a123"} server.images = map[string]docker.Image{"a123": {ID: "a123", Author: "me"}} server.buildMuxer() recorder := httptest.NewRecorder() request, _ := http.NewRequest(http.MethodGet, "/images/tsuru/python/json", nil) server.ServeHTTP(recorder, request) if recorder.Code != http.StatusOK { t.Errorf("InspectImage: wrong status. Want %d. Got %d.", http.StatusOK, recorder.Code) } var img docker.Image err := json.NewDecoder(recorder.Body).Decode(&img) if err != nil { t.Fatal(err) } expected := docker.Image{ ID: "a123", Author: "me", } if !reflect.DeepEqual(img, expected) { t.Errorf("InspectImage: wrong image returned, expected %#v, got: %#v", expected, img) } } func TestInspectImageWithID(t *testing.T) { t.Parallel() server := baseDockerServer() server.images = map[string]docker.Image{"myimgid": {ID: "myimgid", Author: "me"}} server.buildMuxer() recorder := httptest.NewRecorder() request, _ := http.NewRequest(http.MethodGet, "/images/myimgid/json", nil) server.ServeHTTP(recorder, request) if recorder.Code != http.StatusOK { t.Errorf("InspectImage: wrong status. Want %d. Got %d.", http.StatusOK, recorder.Code) } var img docker.Image err := json.NewDecoder(recorder.Body).Decode(&img) if err != nil { t.Fatal(err) } expected := docker.Image{ ID: "myimgid", Author: "me", } if !reflect.DeepEqual(img, expected) { t.Errorf("InspectImage: wrong image returned, expected %#v, got: %#v", expected, img) } } func TestInspectImageNotFound(t *testing.T) { t.Parallel() server := baseDockerServer() server.buildMuxer() recorder := httptest.NewRecorder() request, _ := http.NewRequest(http.MethodGet, "/images/tsuru/python/json", nil) server.ServeHTTP(recorder, request) if recorder.Code != http.StatusNotFound { t.Errorf("InspectImage: wrong status. Want %d. Got %d.", http.StatusNotFound, recorder.Code) } } func addContainers(server *DockerServer, n int) []*docker.Container { server.cMut.Lock() defer server.cMut.Unlock() var addedContainers []*docker.Container for i := 0; i < n; i++ { date := time.Now().Add(time.Duration((rand.Int() % (i + 1))) * time.Hour) container := docker.Container{ Name: fmt.Sprintf("%x", rand.Int()%10000), ID: fmt.Sprintf("%x", rand.Int()%10000), Created: date, Path: "ls", Args: []string{"-la", ".."}, Config: &docker.Config{ Hostname: fmt.Sprintf("docker-%d", i), AttachStdout: true, AttachStderr: true, Env: []string{"ME=you", fmt.Sprintf("NUMBER=%d", i)}, Cmd: []string{"ls", "-la", ".."}, Image: "base", Labels: map[string]string{"key": fmt.Sprintf("val-%d", i)}, }, State: docker.State{ Running: false, Pid: 400 + i, ExitCode: 0, StartedAt: date, }, Image: "b750fe79269d2ec9a3c593ef05b4332b1d1a02a62b4accb2c21d589ff2f5f2dc", NetworkSettings: &docker.NetworkSettings{ IPAddress: fmt.Sprintf("10.10.10.%d", i+2), IPPrefixLen: 24, Gateway: "10.10.10.1", Bridge: "docker0", PortMapping: map[string]docker.PortMapping{ "Tcp": {"8888": fmt.Sprintf("%d", 49600+i)}, }, Ports: map[docker.Port][]docker.PortBinding{ "8888/tcp": { {HostIP: "0.0.0.0", HostPort: fmt.Sprintf("%d", 49600+i)}, }, }, }, ResolvConfPath: "/etc/resolv.conf", } server.addContainer(&container) addedContainers = append(addedContainers, &container) } return addedContainers } func addImages(server *DockerServer, n int, repo bool) []docker.Image { server.iMut.Lock() defer server.iMut.Unlock() if server.imgIDs == nil { server.imgIDs = make(map[string]string) } if server.images == nil { server.images = make(map[string]docker.Image) } var addedImages []docker.Image for i := 0; i < n; i++ { date := time.Now().Add(time.Duration((rand.Int() % (i + 1))) * time.Hour) image := docker.Image{ ID: fmt.Sprintf("%x", rand.Int()%10000), Created: date, } addedImages = append(addedImages, image) server.images[image.ID] = image if repo { repo := "docker/python-" + image.ID server.imgIDs[repo] = image.ID } } return addedImages } func TestListImages(t *testing.T) { t.Parallel() server := baseDockerServer() addImages(&server, 2, true) server.buildMuxer() recorder := httptest.NewRecorder() request, _ := http.NewRequest(http.MethodGet, "/images/json?all=1", nil) server.ServeHTTP(recorder, request) if recorder.Code != http.StatusOK { t.Errorf("ListImages: wrong status. Want %d. Got %d.", http.StatusOK, recorder.Code) } expected := make([]docker.APIImages, 2) i := 0 for _, image := range server.images { expected[i] = docker.APIImages{ ID: image.ID, Created: image.Created.Unix(), RepoTags: []string{"docker/python-" + image.ID}, } i++ } sort.Slice(expected, func(i, j int) bool { return expected[i].ID < expected[j].ID }) var got []docker.APIImages err := json.NewDecoder(recorder.Body).Decode(&got) if err != nil { t.Fatal(err) } sort.Slice(got, func(i, j int) bool { return got[i].ID < got[j].ID }) if !reflect.DeepEqual(got, expected) { t.Errorf("ListImages. Want %#v. Got %#v.", expected, got) } } func TestRemoveImage(t *testing.T) { t.Parallel() server := baseDockerServer() images := addImages(&server, 1, false) server.buildMuxer() recorder := httptest.NewRecorder() path := fmt.Sprintf("/images/%s", images[0].ID) request, _ := http.NewRequest(http.MethodDelete, path, nil) server.ServeHTTP(recorder, request) if recorder.Code != http.StatusNoContent { t.Errorf("RemoveImage: wrong status. Want %d. Got %d.", http.StatusNoContent, recorder.Code) } if len(server.images) > 0 { t.Error("RemoveImage: did not remove the image.") } } func TestRemoveImageByName(t *testing.T) { t.Parallel() server := baseDockerServer() images := addImages(&server, 1, true) server.buildMuxer() recorder := httptest.NewRecorder() imgName := "docker/python-" + images[0].ID path := "/images/" + imgName request, _ := http.NewRequest(http.MethodDelete, path, nil) server.ServeHTTP(recorder, request) if recorder.Code != http.StatusNoContent { t.Errorf("RemoveImage: wrong status. Want %d. Got %d.", http.StatusNoContent, recorder.Code) } if len(server.images) > 0 { t.Error("RemoveImage: did not remove the image.") } _, ok := server.imgIDs[imgName] if ok { t.Error("RemoveImage: did not remove image tag name.") } } func TestRemoveImageWithMultipleTags(t *testing.T) { t.Parallel() server := baseDockerServer() images := addImages(&server, 1, true) server.buildMuxer() imgID := images[0].ID imgName := "docker/python-" + imgID server.imgIDs["docker/python-wat"] = imgID recorder := httptest.NewRecorder() path := fmt.Sprintf("/images/%s", imgName) request, _ := http.NewRequest(http.MethodDelete, path, nil) server.ServeHTTP(recorder, request) _, ok := server.imgIDs[imgName] if ok { t.Error("RemoveImage: did not remove image tag name.") } id, ok := server.imgIDs["docker/python-wat"] if !ok { t.Error("RemoveImage: removed the wrong tag name.") } if id != imgID { t.Error("RemoveImage: disassociated the wrong ID from the tag") } if len(server.images) < 1 { t.Fatal("RemoveImage: removed the image, but should keep it") } if server.images[imgID].ID != imgID { t.Error("RemoveImage: changed the ID of the image!") } } func TestRemoveImageByIDWithMultipleTags(t *testing.T) { t.Parallel() server := baseDockerServer() images := addImages(&server, 1, true) server.buildMuxer() imgID := images[0].ID server.imgIDs["docker/python-wat"] = imgID recorder := httptest.NewRecorder() path := fmt.Sprintf("/images/%s", imgID) request, _ := http.NewRequest(http.MethodDelete, path, nil) server.ServeHTTP(recorder, request) if recorder.Code != http.StatusConflict { t.Errorf("RemoveImage: wrong status. Want %d. Got %d.", http.StatusConflict, recorder.Code) } } func TestRemoveImageByIDWithSingleTag(t *testing.T) { t.Parallel() server := baseDockerServer() images := addImages(&server, 1, true) server.buildMuxer() imgID := images[0].ID recorder := httptest.NewRecorder() path := fmt.Sprintf("/images/%s", imgID) request, _ := http.NewRequest(http.MethodDelete, path, nil) server.ServeHTTP(recorder, request) if recorder.Code != http.StatusNoContent { t.Errorf("RemoveImage: wrong status. Want %d. Got %d.", http.StatusNoContent, recorder.Code) } if len(server.images) > 0 { t.Error("RemoveImage: did not remove the image.") } imgName := "docker/python-" + imgID _, ok := server.imgIDs[imgName] if ok { t.Error("RemoveImage: did not remove image tag name.") } } func TestPrepareFailure(t *testing.T) { t.Parallel() server := baseDockerServer() server.buildMuxer() errorID := "my_error" server.PrepareFailure(errorID, "containers/json") recorder := httptest.NewRecorder() request, _ := http.NewRequest(http.MethodGet, "/containers/json?all=1", nil) server.ServeHTTP(recorder, request) if recorder.Code != http.StatusBadRequest { t.Errorf("PrepareFailure: wrong status. Want %d. Got %d.", http.StatusBadRequest, recorder.Code) } if recorder.Body.String() != errorID+"\n" { t.Errorf("PrepareFailure: wrong message. Want %s. Got %s.", errorID, recorder.Body.String()) } } func TestPrepareMultiFailures(t *testing.T) { t.Parallel() server := baseDockerServer() server.buildMuxer() errorID := "multi error" server.PrepareMultiFailures(errorID, "containers/json") server.PrepareMultiFailures(errorID, "containers/json") recorder := httptest.NewRecorder() request, _ := http.NewRequest(http.MethodGet, "/containers/json?all=1", nil) server.ServeHTTP(recorder, request) if recorder.Code != http.StatusBadRequest { t.Errorf("PrepareFailure: wrong status. Want %d. Got %d.", http.StatusBadRequest, recorder.Code) } if recorder.Body.String() != errorID+"\n" { t.Errorf("PrepareFailure: wrong message. Want %s. Got %s.", errorID, recorder.Body.String()) } recorder = httptest.NewRecorder() request, _ = http.NewRequest(http.MethodGet, "/containers/json?all=1", nil) server.ServeHTTP(recorder, request) if recorder.Code != http.StatusBadRequest { t.Errorf("PrepareFailure: wrong status. Want %d. Got %d.", http.StatusBadRequest, recorder.Code) } if recorder.Body.String() != errorID+"\n" { t.Errorf("PrepareFailure: wrong message. Want %s. Got %s.", errorID, recorder.Body.String()) } recorder = httptest.NewRecorder() request, _ = http.NewRequest(http.MethodGet, "/containers/json?all=1", nil) server.ServeHTTP(recorder, request) if recorder.Code != http.StatusOK { t.Errorf("PrepareFailure: wrong status. Want %d. Got %d.", http.StatusOK, recorder.Code) } if recorder.Body.String() == errorID+"\n" { t.Errorf("PrepareFailure: wrong message. Want %s. Got %s.", errorID, recorder.Body.String()) } } func TestRemoveFailure(t *testing.T) { t.Parallel() server := baseDockerServer() server.buildMuxer() errorID := "my_error" server.PrepareFailure(errorID, "containers/json") recorder := httptest.NewRecorder() request, _ := http.NewRequest(http.MethodGet, "/containers/json?all=1", nil) server.ServeHTTP(recorder, request) if recorder.Code != http.StatusBadRequest { t.Errorf("PrepareFailure: wrong status. Want %d. Got %d.", http.StatusBadRequest, recorder.Code) } server.ResetFailure(errorID) recorder = httptest.NewRecorder() request, _ = http.NewRequest(http.MethodGet, "/containers/json?all=1", nil) server.ServeHTTP(recorder, request) if recorder.Code != http.StatusOK { t.Errorf("RemoveFailure: wrong status. Want %d. Got %d.", http.StatusOK, recorder.Code) } } func TestResetMultiFailures(t *testing.T) { t.Parallel() server := baseDockerServer() server.buildMuxer() errorID := "multi error" server.PrepareMultiFailures(errorID, "containers/json") server.PrepareMultiFailures(errorID, "containers/json") if len(server.multiFailures) != 2 { t.Errorf("PrepareMultiFailures: error adding multi failures.") } server.ResetMultiFailures() if len(server.multiFailures) != 0 { t.Errorf("ResetMultiFailures: error reseting multi failures.") } } func TestMutateContainer(t *testing.T) { t.Parallel() server := baseDockerServer() server.buildMuxer() server.addContainer(&docker.Container{ID: "id123"}) state := docker.State{Running: false, ExitCode: 1} err := server.MutateContainer("id123", state) if err != nil { t.Fatal(err) } if !reflect.DeepEqual(getContainer(&server).State, state) { t.Errorf("Wrong state after mutation.\nWant %#v.\nGot %#v.", state, getContainer(&server).State) } } func TestMutateContainerNotFound(t *testing.T) { t.Parallel() server := baseDockerServer() server.buildMuxer() state := docker.State{Running: false, ExitCode: 1} err := server.MutateContainer("id123", state) if err == nil { t.Error("Unexpected error") } if err.Error() != "container not found" { t.Errorf("wrong error message. Want %q. Got %q.", "container not found", err) } } func TestBuildImageWithContentTypeTar(t *testing.T) { t.Parallel() server := baseDockerServer() imageName := "teste" recorder := httptest.NewRecorder() tarFile, err := os.Open("data/dockerfile.tar") if err != nil { t.Fatal(err) } defer tarFile.Close() request, _ := http.NewRequest(http.MethodPost, "/build?t=teste", tarFile) request.Header.Add("Content-Type", "application/tar") server.buildImage(recorder, request) if recorder.Body.String() == "miss Dockerfile" { t.Errorf("BuildImage: miss Dockerfile") return } if _, ok := server.imgIDs[imageName]; !ok { t.Errorf("BuildImage: image %s not builded", imageName) } } func TestBuildImageWithRemoteDockerfile(t *testing.T) { t.Parallel() server := baseDockerServer() imageName := "teste" recorder := httptest.NewRecorder() request, _ := http.NewRequest(http.MethodPost, "/build?t=teste&remote=http://localhost/Dockerfile", nil) server.buildImage(recorder, request) if _, ok := server.imgIDs[imageName]; !ok { t.Errorf("BuildImage: image %s not builded", imageName) } } func TestPing(t *testing.T) { t.Parallel() server := baseDockerServer() recorder := httptest.NewRecorder() request, _ := http.NewRequest(http.MethodGet, "/_ping", nil) server.pingDocker(recorder, request) if recorder.Body.String() != "" { t.Errorf("Ping: Unexpected body: %s", recorder.Body.String()) } if recorder.Code != http.StatusOK { t.Errorf("Ping: Expected code %d, got: %d", http.StatusOK, recorder.Code) } } func TestDefaultHandler(t *testing.T) { t.Parallel() server, err := NewServer("127.0.0.1:0", nil, nil) if err != nil { t.Fatal(err) } defer server.listener.Close() if server.mux != server.DefaultHandler() { t.Fatalf("DefaultHandler: Expected to return server.mux, got: %#v", server.DefaultHandler()) } } func TestCreateExecContainer(t *testing.T) { t.Parallel() server := baseDockerServer() containers := addContainers(&server, 2) server.buildMuxer() recorder := httptest.NewRecorder() body := `{"Cmd": ["bash", "-c", "ls"]}` path := fmt.Sprintf("/containers/%s/exec", containers[0].ID) request, _ := http.NewRequest(http.MethodPost, path, strings.NewReader(body)) server.ServeHTTP(recorder, request) if recorder.Code != http.StatusOK { t.Fatalf("CreateExec: wrong status. Want %d. Got %d.", http.StatusOK, recorder.Code) } serverExec := server.execs[0] var got docker.Exec err := json.NewDecoder(recorder.Body).Decode(&got) if err != nil { t.Fatal(err) } if got.ID != serverExec.ID { t.Errorf("CreateExec: wrong value. Want %#v. Got %#v.", serverExec.ID, got.ID) } expected := docker.ExecInspect{ ID: got.ID, ProcessConfig: docker.ExecProcessConfig{ EntryPoint: "bash", Arguments: []string{"-c", "ls"}, }, ContainerID: containers[0].ID, } if !reflect.DeepEqual(*serverExec, expected) { t.Errorf("InspectContainer: wrong value. Want:\n%#v\nGot:\n%#v\n", expected, *serverExec) } } func TestInspectExecContainer(t *testing.T) { t.Parallel() server := baseDockerServer() addContainers(&server, 1) server.buildMuxer() recorder := httptest.NewRecorder() body := `{"Cmd": ["bash", "-c", "ls"]}` path := fmt.Sprintf("/containers/%s/exec", getContainer(&server).ID) request, _ := http.NewRequest(http.MethodPost, path, strings.NewReader(body)) server.ServeHTTP(recorder, request) if recorder.Code != http.StatusOK { t.Fatalf("CreateExec: wrong status. Want %d. Got %d.", http.StatusOK, recorder.Code) } var got docker.Exec err := json.NewDecoder(recorder.Body).Decode(&got) if err != nil { t.Fatal(err) } path = fmt.Sprintf("/exec/%s/json", got.ID) request, _ = http.NewRequest(http.MethodGet, path, nil) server.ServeHTTP(recorder, request) if recorder.Code != http.StatusOK { t.Fatalf("CreateExec: wrong status. Want %d. Got %d.", http.StatusOK, recorder.Code) } var got2 docker.ExecInspect err = json.NewDecoder(recorder.Body).Decode(&got2) if err != nil { t.Fatal(err) } expected := docker.ExecInspect{ ID: got.ID, ProcessConfig: docker.ExecProcessConfig{ EntryPoint: "bash", Arguments: []string{"-c", "ls"}, }, ContainerID: getContainer(&server).ID, } if !reflect.DeepEqual(got2, expected) { t.Errorf("InspectContainer: wrong value. Want:\n%#v\nGot:\n%#v\n", expected, got2) } } func TestStartExecContainer(t *testing.T) { t.Parallel() server, _ := NewServer("127.0.0.1:0", nil, nil) defer server.Stop() addContainers(server, 1) server.buildMuxer() recorder := httptest.NewRecorder() body := `{"Cmd": ["bash", "-c", "ls"]}` path := fmt.Sprintf("/containers/%s/exec", getContainer(server).ID) request, _ := http.NewRequest(http.MethodPost, path, strings.NewReader(body)) server.ServeHTTP(recorder, request) if recorder.Code != http.StatusOK { t.Fatalf("CreateExec: wrong status. Want %d. Got %d.", http.StatusOK, recorder.Code) } var exec docker.Exec err := json.NewDecoder(recorder.Body).Decode(&exec) if err != nil { t.Fatal(err) } unleash := make(chan bool) server.PrepareExec(exec.ID, func() { <-unleash }) codes := make(chan int, 1) sent := make(chan bool) go func() { recorder := httptest.NewRecorder() path := fmt.Sprintf("/exec/%s/start", exec.ID) body := `{"Tty":true}` request, _ := http.NewRequest(http.MethodPost, path, strings.NewReader(body)) close(sent) server.ServeHTTP(recorder, request) codes <- recorder.Code }() <-sent execInfo, err := waitExec(server.URL(), exec.ID, true) if err != nil { t.Fatal(err) } if !execInfo.Running { t.Error("StartExec: expected exec to be running, but it's not running") } close(unleash) if code := <-codes; code != http.StatusOK { t.Errorf("StartExec: wrong status. Want %d. Got %d.", http.StatusOK, code) } execInfo, err = waitExec(server.URL(), exec.ID, false) if err != nil { t.Fatal(err) } if execInfo.Running { t.Error("StartExec: expected exec to be not running after start returns, but it's running") } } func TestStartExecContainerWildcardCallback(t *testing.T) { t.Parallel() server, _ := NewServer("127.0.0.1:0", nil, nil) defer server.Stop() addContainers(server, 1) server.buildMuxer() recorder := httptest.NewRecorder() body := `{"Cmd": ["bash", "-c", "ls"]}` path := fmt.Sprintf("/containers/%s/exec", getContainer(server).ID) request, _ := http.NewRequest(http.MethodPost, path, strings.NewReader(body)) server.ServeHTTP(recorder, request) if recorder.Code != http.StatusOK { t.Fatalf("CreateExec: wrong status. Want %d. Got %d.", http.StatusOK, recorder.Code) } unleash := make(chan bool) server.PrepareExec("*", func() { <-unleash }) var exec docker.Exec err := json.NewDecoder(recorder.Body).Decode(&exec) if err != nil { t.Fatal(err) } codes := make(chan int, 1) sent := make(chan bool) go func() { recorder := httptest.NewRecorder() path := fmt.Sprintf("/exec/%s/start", exec.ID) body := `{"Tty":true}` request, _ := http.NewRequest(http.MethodPost, path, strings.NewReader(body)) close(sent) server.ServeHTTP(recorder, request) codes <- recorder.Code }() <-sent execInfo, err := waitExec(server.URL(), exec.ID, true) if err != nil { t.Fatal(err) } if !execInfo.Running { t.Error("StartExec: expected exec to be running, but it's not running") } close(unleash) if code := <-codes; code != http.StatusOK { t.Errorf("StartExec: wrong status. Want %d. Got %d.", http.StatusOK, code) } execInfo, err = waitExec(server.URL(), exec.ID, false) if err != nil { t.Fatal(err) } if execInfo.Running { t.Error("StartExec: expected exec to be not running after start returns, but it's running") } } func TestStartExecContainerNotFound(t *testing.T) { t.Parallel() server, _ := NewServer("127.0.0.1:0", nil, nil) defer server.Stop() addContainers(server, 1) server.buildMuxer() recorder := httptest.NewRecorder() body := `{"Tty":true}` request, _ := http.NewRequest(http.MethodPost, "/exec/something-wat/start", strings.NewReader(body)) server.ServeHTTP(recorder, request) } func waitExec(url, execID string, running bool) (*docker.ExecInspect, error) { const maxTry = 5 client, err := docker.NewClient(url) if err != nil { return nil, err } exec, err := client.InspectExec(execID) for i := 0; i < maxTry && exec.Running != running && err == nil; i++ { time.Sleep(100e6) exec, err = client.InspectExec(exec.ID) } return exec, err } func TestStatsContainer(t *testing.T) { t.Parallel() server, err := NewServer("127.0.0.1:0", nil, nil) if err != nil { t.Fatal(err) } defer server.Stop() containers := addContainers(server, 2) server.buildMuxer() expected := docker.Stats{} expected.CPUStats.CPUUsage.TotalUsage = 20 server.PrepareStats(containers[0].ID, func(id string) docker.Stats { return expected }) recorder := httptest.NewRecorder() path := fmt.Sprintf("/containers/%s/stats?stream=false", containers[0].ID) request, _ := http.NewRequest(http.MethodGet, path, nil) server.ServeHTTP(recorder, request) if recorder.Code != http.StatusOK { t.Errorf("StatsContainer: wrong status. Want %d. Got %d.", http.StatusOK, recorder.Code) } body := recorder.Body.Bytes() var got docker.Stats err = json.Unmarshal(body, &got) if err != nil { t.Fatal(err) } got.Read = time.Time{} got.PreRead = time.Time{} if !reflect.DeepEqual(got, expected) { t.Errorf("StatsContainer: wrong value. Want %#v. Got %#v.", expected, got) } } type safeWriter struct { sync.Mutex *httptest.ResponseRecorder } func (w *safeWriter) Write(buf []byte) (int, error) { w.Lock() defer w.Unlock() return w.ResponseRecorder.Write(buf) } func TestStatsContainerStream(t *testing.T) { t.Parallel() server, err := NewServer("127.0.0.1:0", nil, nil) if err != nil { t.Fatal(err) } defer server.Stop() containers := addContainers(server, 2) server.buildMuxer() expected := docker.Stats{} expected.CPUStats.CPUUsage.TotalUsage = 20 server.PrepareStats(containers[0].ID, func(string) docker.Stats { time.Sleep(50 * time.Millisecond) return expected }) recorder := &safeWriter{ ResponseRecorder: httptest.NewRecorder(), } path := fmt.Sprintf("/containers/%s/stats?stream=true", containers[0].ID) request, _ := http.NewRequest(http.MethodGet, path, nil) go func() { server.ServeHTTP(recorder, request) }() time.Sleep(200 * time.Millisecond) recorder.Lock() defer recorder.Unlock() body := recorder.Body.Bytes() parts := bytes.Split(body, []byte("\n")) if len(parts) < 2 { t.Errorf("StatsContainer: wrong number of parts. Want at least 2. Got %#v.", len(parts)) } var got docker.Stats err = json.Unmarshal(parts[0], &got) if err != nil { t.Fatal(err) } got.Read = time.Time{} got.PreRead = time.Time{} if !reflect.DeepEqual(got, expected) { t.Errorf("StatsContainer: wrong value. Want %#v. Got %#v.", expected, got) } } func addNetworks(server *DockerServer, n int) { server.netMut.Lock() defer server.netMut.Unlock() for i := 0; i < n; i++ { netid := fmt.Sprintf("%x", rand.Int()%10000) network := docker.Network{ Name: netid, ID: fmt.Sprintf("%x", rand.Int()%10000), Driver: "bridge", Containers: map[string]docker.Endpoint{ "blah": { Name: "blah", ID: fmt.Sprintf("%x", rand.Int()%10000), }, }, } server.networks = append(server.networks, &network) } } func TestListNetworks(t *testing.T) { t.Parallel() server := baseDockerServer() addNetworks(&server, 2) server.buildMuxer() recorder := httptest.NewRecorder() request, _ := http.NewRequest(http.MethodGet, "/networks", nil) server.ServeHTTP(recorder, request) if recorder.Code != http.StatusOK { t.Errorf("ListNetworks: wrong status. Want %d. Got %d.", http.StatusOK, recorder.Code) } expected := make([]docker.Network, 2) for i, network := range server.networks { expected[i] = docker.Network{ ID: network.ID, Name: network.Name, Driver: network.Driver, Containers: network.Containers, } } var got []docker.Network err := json.NewDecoder(recorder.Body).Decode(&got) if err != nil { t.Fatal(err) } if !reflect.DeepEqual(got, expected) { t.Errorf("ListNetworks. Want %#v. Got %#v.", expected, got) } } type createNetworkResponse struct { ID string `json:"ID"` } func TestCreateNetwork(t *testing.T) { t.Parallel() server := baseDockerServer() server.buildMuxer() recorder := httptest.NewRecorder() netid := fmt.Sprintf("%x", rand.Int()%10000) netname := fmt.Sprintf("%x", rand.Int()%10000) body := fmt.Sprintf(`{"ID": "%s", "Name": "%s", "Type": "bridge" }`, netid, netname) request, _ := http.NewRequest(http.MethodPost, "/networks/create", strings.NewReader(body)) server.ServeHTTP(recorder, request) if recorder.Code != http.StatusCreated { t.Errorf("CreateNetwork: wrong status. Want %d. Got %d.", http.StatusCreated, recorder.Code) } var returned createNetworkResponse err := json.NewDecoder(recorder.Body).Decode(&returned) if err != nil { t.Fatal(err) } stored := server.networks[0] if returned.ID != stored.ID { t.Errorf("CreateNetwork: ID mismatch. Stored: %q. Returned: %q.", stored.ID, returned) } } func TestCreateNetworkInvalidBody(t *testing.T) { t.Parallel() server := baseDockerServer() server.buildMuxer() recorder := httptest.NewRecorder() request, _ := http.NewRequest(http.MethodPost, "/networks/create", strings.NewReader("whaaaaaat---")) server.ServeHTTP(recorder, request) if recorder.Code != http.StatusBadRequest { t.Errorf("CreateNetwork: wrong status. Want %d. Got %d.", http.StatusBadRequest, recorder.Code) } } func TestCreateNetworkDuplicateName(t *testing.T) { t.Parallel() server := baseDockerServer() server.buildMuxer() addNetworks(&server, 1) server.networks[0].Name = "mynetwork" recorder := httptest.NewRecorder() body := fmt.Sprintf(`{"ID": "%s", "Name": "mynetwork", "Type": "bridge" }`, fmt.Sprintf("%x", rand.Int()%10000)) request, _ := http.NewRequest(http.MethodPost, "/networks/create", strings.NewReader(body)) server.ServeHTTP(recorder, request) if recorder.Code != http.StatusForbidden { t.Errorf("CreateNetwork: wrong status. Want %d. Got %d.", http.StatusForbidden, recorder.Code) } } func TestRemoveNetwork(t *testing.T) { t.Parallel() server := baseDockerServer() server.buildMuxer() recorder := httptest.NewRecorder() server.networks = []*docker.Network{ {ID: "id1", Name: "name1"}, {ID: "id2", Name: "name2"}, } request, _ := http.NewRequest(http.MethodDelete, "/networks/id1", nil) server.ServeHTTP(recorder, request) if recorder.Code != http.StatusNoContent { t.Errorf("RemoveNetwork: wrong status. Want %d. Got %d.", http.StatusNoContent, recorder.Code) } expected := []*docker.Network{{ID: "id2", Name: "name2"}} if !reflect.DeepEqual(server.networks, expected) { t.Errorf("RemoveNetwork: expected networks to be %#v, got %#v", expected, server.networks) } request, _ = http.NewRequest(http.MethodDelete, "/networks/name2", nil) server.ServeHTTP(recorder, request) if recorder.Code != http.StatusNoContent { t.Errorf("RemoveNetwork: wrong status. Want %d. Got %d.", http.StatusNoContent, recorder.Code) } expected = []*docker.Network{} if !reflect.DeepEqual(server.networks, expected) { t.Errorf("RemoveNetwork: expected networks to be %#v, got %#v", expected, server.networks) } } func TestNetworkConnect(t *testing.T) { t.Parallel() server := baseDockerServer() server.buildMuxer() addNetworks(&server, 1) server.networks[0].ID = fmt.Sprintf("%x", rand.Int()%10000) server.imgIDs = map[string]string{"base": "a1234"} containers := addContainers(&server, 1) containers[0].ID = fmt.Sprintf("%x", rand.Int()%10000) server.addContainer(containers[0]) recorder := httptest.NewRecorder() body := fmt.Sprintf(`{"Container": "%s" }`, containers[0].ID) request, _ := http.NewRequest(http.MethodPost, fmt.Sprintf("/networks/%s/connect", server.networks[0].ID), strings.NewReader(body)) server.ServeHTTP(recorder, request) if recorder.Code != http.StatusOK { t.Errorf("NetworkConnect: wrong status. Want %d. Got %d.", http.StatusOK, recorder.Code) } } func TestListVolumes(t *testing.T) { t.Parallel() server := baseDockerServer() server.buildMuxer() expected := []docker.Volume{{ Name: "test-vol-1", Driver: "local", Mountpoint: "/var/lib/docker/volumes/test-vol-1", }} server.volStore = make(map[string]*volumeCounter) for _, vol := range expected { server.volStore[vol.Name] = &volumeCounter{ volume: vol, count: 0, } } recorder := httptest.NewRecorder() request, _ := http.NewRequest(http.MethodGet, "/volumes", nil) server.ServeHTTP(recorder, request) if recorder.Code != http.StatusOK { t.Errorf("ListVolumes: wrong status. Want %d. Got %d.", http.StatusCreated, recorder.Code) } var got map[string][]docker.Volume err := json.NewDecoder(recorder.Body).Decode(&got) if err != nil { t.Fatal(err) } gotVolumes, ok := got["Volumes"] if !ok { t.Fatal("ListVolumes failed can not find Volumes") } if !reflect.DeepEqual(gotVolumes, expected) { t.Errorf("ListVolumes. Want %#v. Got %#v.", expected, got) } } func TestCreateVolume(t *testing.T) { t.Parallel() server := baseDockerServer() server.buildMuxer() recorder := httptest.NewRecorder() body := `{"Name":"test-volume"}` request, _ := http.NewRequest(http.MethodPost, "/volumes/create", strings.NewReader(body)) server.ServeHTTP(recorder, request) if recorder.Code != http.StatusCreated { t.Errorf("CreateVolume: wrong status. Want %d. Got %d.", http.StatusCreated, recorder.Code) } var returned docker.Volume err := json.NewDecoder(recorder.Body).Decode(&returned) if err != nil { t.Error(err) } if returned.Name != "test-volume" { t.Errorf("CreateVolume: Name mismatch. Expected: test-volume. Returned %q.", returned.Name) } if returned.Driver != "local" { t.Errorf("CreateVolume: Driver mismatch. Expected: local. Returned: %q", returned.Driver) } if returned.Mountpoint != "/var/lib/docker/volumes/test-volume" { t.Errorf("CreateVolume: Mountpoint mismatch. Expected: /var/lib/docker/volumes/test-volume. Returned: %q.", returned.Mountpoint) } } func TestCreateVolumeAlreadExists(t *testing.T) { t.Parallel() server := baseDockerServer() server.buildMuxer() server.volStore = make(map[string]*volumeCounter) server.volStore["test-volume"] = &volumeCounter{ volume: docker.Volume{ Name: "test-volume", Driver: "local", Mountpoint: "/var/lib/docker/volumes/test-volume", }, count: 0, } body := `{"Name":"test-volume"}` recorder := httptest.NewRecorder() request, _ := http.NewRequest(http.MethodPost, "/volumes/create", strings.NewReader(body)) server.ServeHTTP(recorder, request) if recorder.Code != http.StatusCreated { t.Errorf("CreateVolumeAlreadExists: wrong status. Want %d. Got %d.", http.StatusCreated, recorder.Code) } var returned docker.Volume err := json.NewDecoder(recorder.Body).Decode(&returned) if err != nil { t.Error(err) } if returned.Name != "test-volume" { t.Errorf("CreateVolumeAlreadExists: Name mismatch. Expected: test-volume. Returned %q.", returned.Name) } if returned.Driver != "local" { t.Errorf("CreateVolumeAlreadExists: Driver mismatch. Expected: local. Returned: %q", returned.Driver) } if returned.Mountpoint != "/var/lib/docker/volumes/test-volume" { t.Errorf("CreateVolumeAlreadExists: Mountpoint mismatch. Expected: /var/lib/docker/volumes/test-volume. Returned: %q.", returned.Mountpoint) } } func TestInspectVolume(t *testing.T) { t.Parallel() server := baseDockerServer() server.buildMuxer() recorder := httptest.NewRecorder() expected := docker.Volume{ Name: "test-volume", Driver: "local", Mountpoint: "/var/lib/docker/volumes/test-volume", } volC := &volumeCounter{ volume: expected, count: 0, } volStore := make(map[string]*volumeCounter) volStore["test-volume"] = volC server.volStore = volStore request, _ := http.NewRequest(http.MethodGet, "/volumes/test-volume", nil) server.ServeHTTP(recorder, request) if recorder.Code != http.StatusOK { t.Errorf("InspectVolume: wrong status. Want %d. God %d.", http.StatusOK, recorder.Code) } var returned docker.Volume err := json.NewDecoder(recorder.Body).Decode(&returned) if err != nil { t.Error(err) } if returned.Name != "test-volume" { t.Errorf("InspectVolume: Name mismatch. Expected: test-volume. Returned %q.", returned.Name) } if returned.Driver != "local" { t.Errorf("InspectVolume: Driver mismatch. Expected: local. Returned: %q", returned.Driver) } if returned.Mountpoint != "/var/lib/docker/volumes/test-volume" { t.Errorf("InspectVolume: Mountpoint mismatch. Expected: /var/lib/docker/volumes/test-volume. Returned: %q.", returned.Mountpoint) } } func TestInspectVolumeNotFound(t *testing.T) { t.Parallel() server := baseDockerServer() server.buildMuxer() recorder := httptest.NewRecorder() request, _ := http.NewRequest(http.MethodGet, "/volumes/test-volume", nil) server.ServeHTTP(recorder, request) if recorder.Code != http.StatusNotFound { t.Errorf("RemoveMissingVolume: wrong status. Want %d. Got %d.", http.StatusNotFound, recorder.Code) } } func TestRemoveVolume(t *testing.T) { t.Parallel() server := baseDockerServer() server.buildMuxer() server.volStore = make(map[string]*volumeCounter) server.volStore["test-volume"] = &volumeCounter{ volume: docker.Volume{ Name: "test-volume", Driver: "local", Mountpoint: "/var/lib/docker/volumes/test-volume", }, count: 0, } recorder := httptest.NewRecorder() request, _ := http.NewRequest(http.MethodDelete, "/volumes/test-volume", nil) server.ServeHTTP(recorder, request) if recorder.Code != http.StatusNoContent { t.Errorf("RemoveVolume: wrong status. Want %d. Got %d.", http.StatusNoContent, recorder.Code) } } func TestRemoveMissingVolume(t *testing.T) { t.Parallel() server := baseDockerServer() server.buildMuxer() recorder := httptest.NewRecorder() request, _ := http.NewRequest(http.MethodDelete, "/volumes/test-volume", nil) server.ServeHTTP(recorder, request) if recorder.Code != http.StatusNotFound { t.Errorf("RemoveMissingVolume: wrong status. Want %d. Got %d.", http.StatusNotFound, recorder.Code) } } func TestRemoveVolumeInuse(t *testing.T) { t.Parallel() server := baseDockerServer() server.buildMuxer() server.volStore = make(map[string]*volumeCounter) server.volStore["test-volume"] = &volumeCounter{ volume: docker.Volume{ Name: "test-volume", Driver: "local", Mountpoint: "/var/lib/docker/volumes/test-volume", }, count: 1, } recorder := httptest.NewRecorder() request, _ := http.NewRequest(http.MethodDelete, "/volumes/test-volume", nil) server.ServeHTTP(recorder, request) if recorder.Code != http.StatusConflict { t.Errorf("RemoveVolume: wrong status. Want %d. Got %d.", http.StatusConflict, recorder.Code) } } func TestUploadToContainer(t *testing.T) { t.Parallel() server := baseDockerServer() server.buildMuxer() cont := &docker.Container{ ID: "id123", State: docker.State{ Running: true, ExitCode: 0, }, } server.addContainer(cont) server.uploadedFiles = make(map[string]string) recorder := httptest.NewRecorder() request, _ := http.NewRequest(http.MethodPut, fmt.Sprintf("/containers/%s/archive?path=abcd", cont.ID), nil) server.ServeHTTP(recorder, request) if recorder.Code != http.StatusOK { t.Errorf("UploadToContainer: wrong status. Want %d. Got %d.", http.StatusOK, recorder.Code) } if val, ok := server.uploadedFiles[cont.ID]; !ok { t.Errorf("UploadToContainer: uploadedFiles should exist.") } else if val != "abcd" { t.Errorf("UploadToContainer: wrong uploadedFiles. Want 'abcd'. Got %s.", val) } } func TestUploadToContainerWithBodyTarFile(t *testing.T) { t.Parallel() server := baseDockerServer() server.buildMuxer() cont := &docker.Container{ ID: "id123", State: docker.State{ Running: true, ExitCode: 0, }, } buf := new(bytes.Buffer) tw := tar.NewWriter(buf) defer tw.Close() hdr := &tar.Header{ Name: "test.tar.gz", Mode: 0o600, Size: int64(buf.Len()), } tw.WriteHeader(hdr) tw.Write([]byte("something")) tw.Close() server.addContainer(cont) server.uploadedFiles = make(map[string]string) recorder := httptest.NewRecorder() request, _ := http.NewRequest(http.MethodPut, fmt.Sprintf("/containers/%s/archive?path=abcd", cont.ID), buf) server.ServeHTTP(recorder, request) if recorder.Code != http.StatusOK { t.Errorf("UploadToContainer: wrong status. Want %d. Got %d.", http.StatusOK, recorder.Code) } if val, ok := server.uploadedFiles[cont.ID]; !ok { t.Errorf("UploadToContainer: uploadedFiles should exist.") } else if val != "abcd/test.tar.gz" { t.Errorf("UploadToContainer: wrong uploadedFiles. Want 'abcd/test.tar.gz'. Got %s.", val) } } func TestUploadToContainerBodyNotTarFile(t *testing.T) { t.Parallel() server := baseDockerServer() server.buildMuxer() cont := &docker.Container{ ID: "id123", State: docker.State{ Running: true, ExitCode: 0, }, } buf := bytes.NewBufferString("something") server.addContainer(cont) server.uploadedFiles = make(map[string]string) recorder := httptest.NewRecorder() request, _ := http.NewRequest(http.MethodPut, fmt.Sprintf("/containers/%s/archive?path=abcd", cont.ID), buf) server.ServeHTTP(recorder, request) if recorder.Code != http.StatusOK { t.Errorf("UploadToContainer: wrong status. Want %d. Got %d.", http.StatusOK, recorder.Code) } if val, ok := server.uploadedFiles[cont.ID]; !ok { t.Errorf("UploadToContainer: uploadedFiles should exist.") } else if val != "abcd" { t.Errorf("UploadToContainer: wrong uploadedFiles. Want 'abcd'. Got %s.", val) } } func TestUploadToContainerMissingContainer(t *testing.T) { t.Parallel() server := baseDockerServer() server.buildMuxer() recorder := httptest.NewRecorder() request, _ := http.NewRequest(http.MethodPut, "/containers/missing-container/archive?path=abcd", nil) server.ServeHTTP(recorder, request) if recorder.Code != http.StatusNotFound { t.Errorf("UploadToContainer: wrong status. Want %d. Got %d.", http.StatusNotFound, recorder.Code) } } func TestInfoDocker(t *testing.T) { t.Parallel() server, _ := NewServer("127.0.0.1:0", nil, nil) defer server.Stop() addContainers(server, 1) server.buildMuxer() recorder := httptest.NewRecorder() request, _ := http.NewRequest(http.MethodGet, "/info", nil) server.ServeHTTP(recorder, request) if recorder.Code != http.StatusOK { t.Fatalf("InfoDocker: wrong status. Want %d. Got %d.", http.StatusOK, recorder.Code) } var infoData map[string]any err := json.Unmarshal(recorder.Body.Bytes(), &infoData) if err != nil { t.Fatal(err) } if infoData["Containers"].(float64) != 1.0 { t.Fatalf("InfoDocker: wrong containers count. Want %f. Got %f.", 1.0, infoData["Containers"]) } if infoData["DockerRootDir"].(string) != "/var/lib/docker" { t.Fatalf("InfoDocker: wrong docker root. Want /var/lib/docker. Got %s.", infoData["DockerRootDir"]) } } func TestInfoDockerWithSwarm(t *testing.T) { t.Parallel() srv1, srv2 := setUpSwarm(t) defer srv1.Stop() defer srv2.Stop() recorder := httptest.NewRecorder() request, _ := http.NewRequest(http.MethodGet, "/info", nil) srv1.ServeHTTP(recorder, request) if recorder.Code != http.StatusOK { t.Fatalf("InfoDocker: wrong status. Want %d. Got %d.", http.StatusOK, recorder.Code) } var infoData docker.DockerInfo err := json.Unmarshal(recorder.Body.Bytes(), &infoData) if err != nil { t.Fatal(err) } expectedSwarm := swarm.Info{ NodeID: srv1.nodeID, RemoteManagers: []swarm.Peer{ {NodeID: srv1.nodeID, Addr: srv1.SwarmAddress()}, {NodeID: srv2.nodeID, Addr: srv2.SwarmAddress()}, }, } if !reflect.DeepEqual(infoData.Swarm, expectedSwarm) { t.Fatalf("InfoDocker: wrong swarm info. Want:\n%#v\nGot:\n%#v", expectedSwarm, infoData.Swarm) } } func TestVersionDocker(t *testing.T) { t.Parallel() server, _ := NewServer("127.0.0.1:0", nil, nil) defer server.Stop() server.buildMuxer() recorder := httptest.NewRecorder() request, _ := http.NewRequest(http.MethodGet, "/version", nil) server.ServeHTTP(recorder, request) if recorder.Code != http.StatusOK { t.Fatalf("VersionDocker: wrong status. Want %d. Got %d.", http.StatusOK, recorder.Code) } } func TestDownloadFromContainer(t *testing.T) { t.Parallel() server := baseDockerServer() server.buildMuxer() cont := &docker.Container{ ID: "id123", State: docker.State{ Running: true, ExitCode: 0, }, } server.addContainer(cont) server.uploadedFiles = make(map[string]string) server.uploadedFiles[cont.ID] = "abcd" recorder := httptest.NewRecorder() request, _ := http.NewRequest(http.MethodGet, fmt.Sprintf("/containers/%s/archive?path=abcd", cont.ID), nil) server.ServeHTTP(recorder, request) resp := recorder.Result() if resp.StatusCode != http.StatusOK { t.Errorf("DownloadFromContainer: wrong status. Want %d. Got %d.", http.StatusOK, resp.StatusCode) } if resp.Header.Get("Content-Type") != "application/x-tar" { t.Errorf("DownloadFromContainer: wrong Content-Type. Want 'application/x-tar'. Got %s.", resp.Header.Get("Content-Type")) } } func TestSupportVersionPathPrefix(t *testing.T) { t.Parallel() server, _ := NewServer("127.0.0.1:0", nil, nil) defer server.Stop() server.buildMuxer() recorder := httptest.NewRecorder() request, _ := http.NewRequest(http.MethodGet, "/v1.16/version", nil) server.ServeHTTP(recorder, request) if recorder.Code != http.StatusOK { t.Fatalf("VersionDocker: wrong status. Want %d. Got %d.", http.StatusOK, recorder.Code) } } go-dockerclient-1.12.0/testing/swarm.go000066400000000000000000000417301465717111200200130ustar00rootroot00000000000000// Copyright 2016 go-dockerclient authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package testing import ( "bytes" "encoding/json" "errors" "fmt" "io" "math/rand" "net" "net/http" "strings" "time" "github.com/docker/docker/api/types/swarm" docker "github.com/fsouza/go-dockerclient" "github.com/gorilla/mux" ) type swarmServer struct { srv *DockerServer mux *mux.Router listener net.Listener } func newSwarmServer(srv *DockerServer, bind string) (*swarmServer, error) { listener, err := net.Listen("tcp", bind) if err != nil { return nil, err } router := mux.NewRouter() router.Path("/internal/updatenodes").Methods(http.MethodPost).HandlerFunc(srv.handlerWrapper(srv.internalUpdateNodes)) server := &swarmServer{ listener: listener, mux: router, srv: srv, } go http.Serve(listener, router) return server, nil } func (s *swarmServer) URL() string { if s.listener == nil { return "" } return "http://" + s.listener.Addr().String() + "/" } // MutateTask changes a task, returning an error if the given id does not match // to any task in the server. func (s *DockerServer) MutateTask(id string, newTask swarm.Task) error { s.swarmMut.Lock() defer s.swarmMut.Unlock() for i, task := range s.tasks { if task.ID == id { s.tasks[i] = &newTask return nil } } return errors.New("task not found") } func (s *DockerServer) swarmInit(w http.ResponseWriter, r *http.Request) { s.swarmMut.Lock() defer s.swarmMut.Unlock() if s.swarm != nil { w.WriteHeader(http.StatusNotAcceptable) return } var req swarm.InitRequest err := json.NewDecoder(r.Body).Decode(&req) if err != nil && !errors.Is(err, io.EOF) { http.Error(w, err.Error(), http.StatusInternalServerError) return } node, err := s.initSwarmNode(req.ListenAddr, req.AdvertiseAddr) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } node.ManagerStatus.Leader = true err = s.runNodeOperation(s.swarmServer.URL(), nodeOperation{ Op: "add", Node: node, }) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } s.swarm = &swarm.Swarm{ JoinTokens: swarm.JoinTokens{ Manager: s.generateID(), Worker: s.generateID(), }, } w.Header().Set("Content-Type", "application/json") err = json.NewEncoder(w).Encode(s.nodeID) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) } } func (s *DockerServer) swarmInspect(w http.ResponseWriter, r *http.Request) { s.swarmMut.Lock() defer s.swarmMut.Unlock() if s.swarm == nil { w.WriteHeader(http.StatusNotAcceptable) } else { w.WriteHeader(http.StatusOK) w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(s.swarm) } } func (s *DockerServer) swarmJoin(w http.ResponseWriter, r *http.Request) { s.swarmMut.Lock() defer s.swarmMut.Unlock() if s.swarm != nil { w.WriteHeader(http.StatusNotAcceptable) return } var req swarm.JoinRequest err := json.NewDecoder(r.Body).Decode(&req) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } if len(req.RemoteAddrs) == 0 { w.WriteHeader(http.StatusBadRequest) return } node, err := s.initSwarmNode(req.ListenAddr, req.AdvertiseAddr) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } s.swarm = &swarm.Swarm{ JoinTokens: swarm.JoinTokens{ Manager: s.generateID(), Worker: s.generateID(), }, } s.swarmMut.Unlock() err = s.runNodeOperation(fmt.Sprintf("http://%s", req.RemoteAddrs[0]), nodeOperation{ Op: "add", Node: node, forceLock: true, }) s.swarmMut.Lock() if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } w.WriteHeader(http.StatusOK) } func (s *DockerServer) swarmLeave(w http.ResponseWriter, r *http.Request) { s.swarmMut.Lock() defer s.swarmMut.Unlock() if s.swarm == nil { w.WriteHeader(http.StatusNotAcceptable) } else { s.swarmServer.listener.Close() s.swarm = nil s.nodes = nil s.swarmServer = nil s.nodeID = "" w.WriteHeader(http.StatusOK) } } func (s *DockerServer) containerForService(srv *swarm.Service, name string) *docker.Container { hostConfig := docker.HostConfig{} dockerConfig := docker.Config{ Entrypoint: srv.Spec.TaskTemplate.ContainerSpec.Command, Cmd: srv.Spec.TaskTemplate.ContainerSpec.Args, Env: srv.Spec.TaskTemplate.ContainerSpec.Env, } return &docker.Container{ ID: s.generateID(), Name: name, Image: srv.Spec.TaskTemplate.ContainerSpec.Image, Created: time.Now(), Config: &dockerConfig, HostConfig: &hostConfig, State: docker.State{ Running: true, StartedAt: time.Now(), Pid: rand.Int() % 50000, ExitCode: 0, }, } } func (s *DockerServer) serviceCreate(w http.ResponseWriter, r *http.Request) { var config swarm.ServiceSpec defer r.Body.Close() err := json.NewDecoder(r.Body).Decode(&config) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } s.cMut.Lock() defer s.cMut.Unlock() s.swarmMut.Lock() defer s.swarmMut.Unlock() if len(s.nodes) == 0 || s.swarm == nil { http.Error(w, "no swarm nodes available", http.StatusNotAcceptable) return } if config.Name == "" { config.Name = s.generateID() } for _, s := range s.services { if s.Spec.Name == config.Name { http.Error(w, "there's already a service with this name", http.StatusConflict) return } } service := swarm.Service{ ID: s.generateID(), Spec: config, } s.setServiceEndpoint(&service) s.addTasks(&service, false) s.services = append(s.services, &service) err = s.runNodeOperation(s.swarmServer.URL(), nodeOperation{}) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } w.WriteHeader(http.StatusOK) json.NewEncoder(w).Encode(service) } func (s *DockerServer) setServiceEndpoint(service *swarm.Service) { if service.Spec.EndpointSpec == nil { return } service.Endpoint = swarm.Endpoint{ Spec: *service.Spec.EndpointSpec, } for _, port := range service.Spec.EndpointSpec.Ports { if port.PublishedPort == 0 { port.PublishedPort = uint32(30000 + s.servicePorts) s.servicePorts++ } service.Endpoint.Ports = append(service.Endpoint.Ports, port) } } func (s *DockerServer) addTasks(service *swarm.Service, update bool) { if service.Spec.TaskTemplate.ContainerSpec == nil { return } containerCount := 1 if service.Spec.Mode.Global != nil { containerCount = len(s.nodes) } else if repl := service.Spec.Mode.Replicated; repl != nil { if repl.Replicas != nil { containerCount = int(*repl.Replicas) } } for i := 0; i < containerCount; i++ { name := fmt.Sprintf("%s-%d", service.Spec.Name, i) if update { name = fmt.Sprintf("%s-%d-updated", service.Spec.Name, i) } container := s.containerForService(service, name) chosenNode := s.nodes[s.nodeRR] s.nodeRR = (s.nodeRR + 1) % len(s.nodes) task := swarm.Task{ ID: s.generateID(), ServiceID: service.ID, NodeID: chosenNode.ID, Status: swarm.TaskStatus{ State: swarm.TaskStateReady, ContainerStatus: &swarm.ContainerStatus{ ContainerID: container.ID, }, }, DesiredState: swarm.TaskStateReady, Spec: service.Spec.TaskTemplate, } s.tasks = append(s.tasks, &task) s.addContainer(container) s.notify(container) } } func (s *DockerServer) serviceInspect(w http.ResponseWriter, r *http.Request) { s.swarmMut.Lock() defer s.swarmMut.Unlock() if s.swarm == nil { w.WriteHeader(http.StatusNotAcceptable) return } id := mux.Vars(r)["id"] for _, srv := range s.services { if srv.ID == id || srv.Spec.Name == id { json.NewEncoder(w).Encode(srv) return } } http.Error(w, "service not found", http.StatusNotFound) } func (s *DockerServer) taskInspect(w http.ResponseWriter, r *http.Request) { s.swarmMut.Lock() defer s.swarmMut.Unlock() if s.swarm == nil { w.WriteHeader(http.StatusNotAcceptable) return } id := mux.Vars(r)["id"] for _, task := range s.tasks { if task.ID == id { json.NewEncoder(w).Encode(task) return } } http.Error(w, "task not found", http.StatusNotFound) } func (s *DockerServer) serviceList(w http.ResponseWriter, r *http.Request) { s.swarmMut.Lock() defer s.swarmMut.Unlock() if s.swarm == nil { w.WriteHeader(http.StatusNotAcceptable) return } filtersRaw := r.FormValue("filters") var filters map[string][]string json.Unmarshal([]byte(filtersRaw), &filters) if filters == nil { json.NewEncoder(w).Encode(s.services) return } var ret []*swarm.Service for i, srv := range s.services { if inFilter(filters["id"], srv.ID) && inFilter(filters["name"], srv.Spec.Name) { ret = append(ret, s.services[i]) } } json.NewEncoder(w).Encode(ret) } func (s *DockerServer) taskList(w http.ResponseWriter, r *http.Request) { s.swarmMut.Lock() defer s.swarmMut.Unlock() if s.swarm == nil { w.WriteHeader(http.StatusNotAcceptable) return } filtersRaw := r.FormValue("filters") var filters map[string][]string json.Unmarshal([]byte(filtersRaw), &filters) if filters == nil { json.NewEncoder(w).Encode(s.tasks) return } var ret []*swarm.Task for i, task := range s.tasks { var srv *swarm.Service for _, srv = range s.services { if task.ServiceID == srv.ID { break } } if srv == nil { http.Error(w, "service not found", http.StatusNotFound) return } if inFilter(filters["id"], task.ID) && (inFilter(filters["service"], task.ServiceID) || inFilter(filters["service"], srv.Spec.Name)) && inFilter(filters["node"], task.NodeID) && inFilter(filters["desired-state"], string(task.DesiredState)) && inLabelFilter(filters["label"], srv.Spec.Labels) { ret = append(ret, s.tasks[i]) } } json.NewEncoder(w).Encode(ret) } func inLabelFilter(list []string, labels map[string]string) bool { if len(list) == 0 { return true } for _, item := range list { parts := strings.Split(item, "=") key := parts[0] if val, ok := labels[key]; ok { if len(parts) > 1 && val != parts[1] { continue } return true } } return false } func inFilter(list []string, wanted string) bool { if len(list) == 0 { return true } for _, item := range list { if item == wanted { return true } } return false } func (s *DockerServer) serviceDelete(w http.ResponseWriter, r *http.Request) { s.swarmMut.Lock() defer s.swarmMut.Unlock() s.cMut.Lock() defer s.cMut.Unlock() if s.swarm == nil { w.WriteHeader(http.StatusNotAcceptable) return } id := mux.Vars(r)["id"] var i int var toDelete *swarm.Service for i = range s.services { if s.services[i].ID == id || s.services[i].Spec.Name == id { toDelete = s.services[i] break } } if toDelete == nil { http.Error(w, "service not found", http.StatusNotFound) return } s.services[i] = s.services[len(s.services)-1] s.services = s.services[:len(s.services)-1] for i := 0; i < len(s.tasks); i++ { if s.tasks[i].ServiceID == toDelete.ID { cont, _ := s.findContainerWithLock(s.tasks[i].Status.ContainerStatus.ContainerID, false) if cont != nil { delete(s.containers, cont.ID) delete(s.contNameToID, cont.Name) } s.tasks = append(s.tasks[:i], s.tasks[i+1:]...) i-- } } err := s.runNodeOperation(s.swarmServer.URL(), nodeOperation{}) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } } func (s *DockerServer) serviceUpdate(w http.ResponseWriter, r *http.Request) { start := time.Now() s.swarmMut.Lock() defer s.swarmMut.Unlock() s.cMut.Lock() defer s.cMut.Unlock() if s.swarm == nil { w.WriteHeader(http.StatusNotAcceptable) return } id := mux.Vars(r)["id"] var toUpdate *swarm.Service for i := range s.services { if s.services[i].ID == id || s.services[i].Spec.Name == id { toUpdate = s.services[i] break } } if toUpdate == nil { http.Error(w, "service not found", http.StatusNotFound) return } var newSpec swarm.ServiceSpec err := json.NewDecoder(r.Body).Decode(&newSpec) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } toUpdate.Spec = newSpec end := time.Now() toUpdate.UpdateStatus = &swarm.UpdateStatus{ State: swarm.UpdateStateCompleted, CompletedAt: &end, StartedAt: &start, } s.setServiceEndpoint(toUpdate) for i := 0; i < len(s.tasks); i++ { if s.tasks[i].ServiceID != toUpdate.ID { continue } cont, _ := s.findContainerWithLock(s.tasks[i].Status.ContainerStatus.ContainerID, false) if cont != nil { delete(s.containers, cont.ID) delete(s.contNameToID, cont.Name) } s.tasks = append(s.tasks[:i], s.tasks[i+1:]...) i-- } s.addTasks(toUpdate, true) err = s.runNodeOperation(s.swarmServer.URL(), nodeOperation{}) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } } func (s *DockerServer) nodeUpdate(w http.ResponseWriter, r *http.Request) { s.swarmMut.Lock() defer s.swarmMut.Unlock() if s.swarm == nil { w.WriteHeader(http.StatusNotAcceptable) return } id := mux.Vars(r)["id"] var n *swarm.Node for i := range s.nodes { if s.nodes[i].ID == id { n = &s.nodes[i] break } } if n == nil { w.WriteHeader(http.StatusNotFound) return } var spec swarm.NodeSpec err := json.NewDecoder(r.Body).Decode(&spec) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } n.Spec = spec err = s.runNodeOperation(s.swarmServer.URL(), nodeOperation{ Op: "update", Node: *n, }) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } } func (s *DockerServer) nodeDelete(w http.ResponseWriter, r *http.Request) { s.swarmMut.Lock() defer s.swarmMut.Unlock() if s.swarm == nil { w.WriteHeader(http.StatusNotAcceptable) return } id := mux.Vars(r)["id"] err := s.runNodeOperation(s.swarmServer.URL(), nodeOperation{ Op: "delete", Node: swarm.Node{ ID: id, }, }) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } } func (s *DockerServer) nodeInspect(w http.ResponseWriter, r *http.Request) { s.swarmMut.Lock() defer s.swarmMut.Unlock() if s.swarm == nil { w.WriteHeader(http.StatusNotAcceptable) return } id := mux.Vars(r)["id"] for _, n := range s.nodes { if n.ID == id { err := json.NewEncoder(w).Encode(n) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) } return } } w.WriteHeader(http.StatusNotFound) } func (s *DockerServer) nodeList(w http.ResponseWriter, r *http.Request) { s.swarmMut.Lock() defer s.swarmMut.Unlock() if s.swarm == nil { w.WriteHeader(http.StatusNotAcceptable) return } err := json.NewEncoder(w).Encode(s.nodes) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) } } type nodeOperation struct { Op string Node swarm.Node Tasks []*swarm.Task Services []*swarm.Service forceLock bool } func (s *DockerServer) runNodeOperation(dst string, nodeOp nodeOperation) error { data, err := json.Marshal(nodeOp) if err != nil { return err } url := fmt.Sprintf("%s/internal/updatenodes", strings.TrimRight(dst, "/")) if nodeOp.forceLock { url += "?forcelock=1" } rsp, err := http.Post(url, "application/json", bytes.NewReader(data)) if err != nil { return err } defer rsp.Body.Close() if rsp.StatusCode != http.StatusOK { return fmt.Errorf("unexpected status code in updatenodes: %d", rsp.StatusCode) } return json.NewDecoder(rsp.Body).Decode(&s.nodes) } func (s *DockerServer) internalUpdateNodes(w http.ResponseWriter, r *http.Request) { propagate := r.URL.Query().Get("propagate") != "0" if !propagate || r.URL.Query().Get("forcelock") != "" { s.swarmMut.Lock() defer s.swarmMut.Unlock() } data, err := io.ReadAll(r.Body) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } var nodeOp nodeOperation err = json.Unmarshal(data, &nodeOp) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } switch nodeOp.Op { case "add": s.nodes = append(s.nodes, nodeOp.Node) case "update": for i, n := range s.nodes { if n.ID == nodeOp.Node.ID { s.nodes[i] = nodeOp.Node break } } case "delete": for i, n := range s.nodes { if n.ID == nodeOp.Node.ID { s.nodes = append(s.nodes[:i], s.nodes[i+1:]...) break } } } if propagate { nodeOp.Services = s.services nodeOp.Tasks = s.tasks data, _ = json.Marshal(nodeOp) for _, node := range s.nodes { if s.nodeID == node.ID { continue } url := fmt.Sprintf("http://%s/internal/updatenodes?propagate=0", node.ManagerStatus.Addr) var resp *http.Response resp, err = http.Post(url, "application/json", bytes.NewReader(data)) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } resp.Body.Close() } } if nodeOp.Services != nil { s.services = nodeOp.Services } if nodeOp.Tasks != nil { s.tasks = nodeOp.Tasks } w.Header().Set("Content-Type", "application/json") err = json.NewEncoder(w).Encode(s.nodes) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) } } go-dockerclient-1.12.0/testing/swarm_test.go000066400000000000000000001321261465717111200210520ustar00rootroot00000000000000// Copyright 2016 go-dockerclient authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package testing import ( "bytes" "encoding/json" "errors" "fmt" "io" "net" "net/http" "net/http/httptest" "net/url" "reflect" "strings" "testing" "github.com/docker/docker/api/types/swarm" docker "github.com/fsouza/go-dockerclient" ) func TestSwarmInit(t *testing.T) { t.Parallel() server, err := NewServer("127.0.0.1:0", nil, nil) if err != nil { t.Fatal(err) } defer server.Stop() server.buildMuxer() recorder := httptest.NewRecorder() request, _ := http.NewRequest(http.MethodPost, "/swarm/init", bytes.NewReader(nil)) server.ServeHTTP(recorder, request) if recorder.Code != http.StatusOK { t.Fatalf("SwarmInit: wrong status. Want %d. Got %d.", http.StatusOK, recorder.Code) } var id string err = json.Unmarshal(recorder.Body.Bytes(), &id) if err != nil { t.Fatalf("SwarmInit: got error. %s", err.Error()) } if id == "" { t.Fatal("SwarmInit: id not found") } if server.swarm == nil { t.Fatalf("SwarmInit: expected swarm to be set.") } if len(server.nodes) != 1 { t.Fatalf("SwarmInit: expected node len to be 1, got: %d", len(server.nodes)) } if server.nodes[0].ManagerStatus.Addr != server.SwarmAddress() { t.Fatalf("SwarmInit: expected current node to have addr %q, got: %q", server.SwarmAddress(), server.nodes[0].ManagerStatus.Addr) } if !server.nodes[0].ManagerStatus.Leader { t.Fatalf("SwarmInit: expected current node to be leader") } } func TestSwarmInitDynamicAdvertiseAddrPort(t *testing.T) { t.Parallel() server, err := NewServer("127.0.0.1:0", nil, nil) if err != nil { t.Fatal(err) } defer server.Stop() server.buildMuxer() data := `{"ListenAddr": "127.0.0.1:0", "AdvertiseAddr": "localhost"}` recorder := httptest.NewRecorder() request, _ := http.NewRequest(http.MethodPost, "/swarm/init", strings.NewReader(data)) server.ServeHTTP(recorder, request) if recorder.Code != http.StatusOK { t.Fatalf("SwarmInit: wrong status. Want %d. Got %d.", http.StatusOK, recorder.Code) } if len(server.nodes) != 1 { t.Fatalf("SwarmInit: expected node len to be 1, got: %d", len(server.nodes)) } _, port, _ := net.SplitHostPort(server.SwarmAddress()) expectedAddr := fmt.Sprintf("localhost:%s", port) if server.nodes[0].ManagerStatus.Addr != expectedAddr { t.Fatalf("SwarmInit: expected current node to have addr %q, got: %q", expectedAddr, server.nodes[0].ManagerStatus.Addr) } } func TestSwarmInitAlreadyInSwarm(t *testing.T) { t.Parallel() server, err := NewServer("127.0.0.1:0", nil, nil) if err != nil { t.Fatal(err) } defer server.Stop() server.buildMuxer() server.swarm = &swarm.Swarm{} recorder := httptest.NewRecorder() request, _ := http.NewRequest(http.MethodPost, "/swarm/init", nil) server.ServeHTTP(recorder, request) if recorder.Code != http.StatusNotAcceptable { t.Fatalf("SwarmInit: wrong status. Want %d. Got %d.", http.StatusNotAcceptable, recorder.Code) } } func TestSwarmJoinNoBody(t *testing.T) { t.Parallel() server, err := NewServer("127.0.0.1:0", nil, nil) if err != nil { t.Fatal(err) } defer server.Stop() server.buildMuxer() recorder := httptest.NewRecorder() request, _ := http.NewRequest(http.MethodPost, "/swarm/join", bytes.NewReader(nil)) server.ServeHTTP(recorder, request) if recorder.Code != http.StatusInternalServerError { t.Fatalf("SwarmJoin: wrong status. Want %d. Got %d.", http.StatusInternalServerError, recorder.Code) } if server.swarm != nil { t.Fatalf("SwarmJoin: expected swarm not to be set.") } } func TestSwarmJoin(t *testing.T) { t.Parallel() server1, err := NewServer("127.0.0.1:0", nil, nil) if err != nil { t.Fatal(err) } defer server1.Stop() server2, err := NewServer("127.0.0.1:0", nil, nil) if err != nil { t.Fatal(err) } defer server2.Stop() data, err := json.Marshal(swarm.InitRequest{}) if err != nil { t.Fatal(err) } recorder := httptest.NewRecorder() request, _ := http.NewRequest(http.MethodPost, "/swarm/init", bytes.NewReader(data)) server1.ServeHTTP(recorder, request) if recorder.Code != http.StatusOK { t.Fatalf("SwarmJoin: wrong status. Want %d. Got %d.", http.StatusOK, recorder.Code) } data, err = json.Marshal(swarm.JoinRequest{ RemoteAddrs: []string{server1.SwarmAddress()}, }) if err != nil { t.Fatal(err) } recorder = httptest.NewRecorder() request, _ = http.NewRequest(http.MethodPost, "/swarm/join", bytes.NewReader(data)) server2.ServeHTTP(recorder, request) if recorder.Code != http.StatusOK { t.Fatalf("SwarmJoin: wrong status. Want %d. Got %d.", http.StatusOK, recorder.Code) } if server1.swarm == nil || server2.swarm == nil { t.Fatalf("SwarmJoin: expected swarm to be set.") } if len(server1.nodes) != 2 { t.Fatalf("SwarmJoin: expected node len to be 2, got: %d", len(server1.nodes)) } if server1.nodes[0].ManagerStatus.Addr != server1.SwarmAddress() { t.Fatalf("SwarmJoin: expected nodes[0] to have addr %q, got: %q", server1.SwarmAddress(), server1.nodes[0].ManagerStatus.Addr) } if server1.nodes[1].ManagerStatus.Leader { t.Fatalf("SwarmInit: expected nodes[1] not to be leader") } if server1.nodes[1].ManagerStatus.Addr != server2.SwarmAddress() { t.Fatalf("SwarmJoin: expected nodes[1] to have addr %q, got: %q", server2.SwarmAddress(), server1.nodes[1].ManagerStatus.Addr) } if !reflect.DeepEqual(server1.nodes, server2.nodes) { t.Fatalf("SwarmJoin: expected nodes to be equal in server1 and server2, got:\n%#v\n%#v", server1.nodes, server2.nodes) } } func TestSwarmJoinWithService(t *testing.T) { t.Parallel() server1, err := NewServer("127.0.0.1:0", nil, nil) if err != nil { t.Fatal(err) } defer server1.Stop() server2, err := NewServer("127.0.0.1:0", nil, nil) if err != nil { t.Fatal(err) } defer server2.Stop() data, err := json.Marshal(swarm.InitRequest{}) if err != nil { t.Fatal(err) } recorder := httptest.NewRecorder() request, _ := http.NewRequest(http.MethodPost, "/swarm/init", bytes.NewReader(data)) server1.ServeHTTP(recorder, request) if recorder.Code != http.StatusOK { t.Fatalf("SwarmJoin: wrong status. Want %d. Got %d.", http.StatusOK, recorder.Code) } serviceCreateOpts := docker.CreateServiceOptions{ ServiceSpec: swarm.ServiceSpec{ TaskTemplate: swarm.TaskSpec{ ContainerSpec: &swarm.ContainerSpec{ Image: "test/test", }, }, }, } buf, err := json.Marshal(serviceCreateOpts) if err != nil { t.Fatalf("ServiceCreate error: %s", err.Error()) } recorder = httptest.NewRecorder() request, _ = http.NewRequest(http.MethodPost, "/services/create", bytes.NewBuffer(buf)) server1.ServeHTTP(recorder, request) if recorder.Code != http.StatusOK { t.Fatalf("SwarmJoin: wrong status code. Want %d. Got %d.", http.StatusOK, recorder.Code) } data, err = json.Marshal(swarm.JoinRequest{ RemoteAddrs: []string{server1.SwarmAddress()}, }) if err != nil { t.Fatal(err) } recorder = httptest.NewRecorder() request, _ = http.NewRequest(http.MethodPost, "/swarm/join", bytes.NewReader(data)) server2.ServeHTTP(recorder, request) if recorder.Code != http.StatusOK { t.Fatalf("SwarmJoin: wrong status. Want %d. Got %d.", http.StatusOK, recorder.Code) } if len(server1.services) != len(server2.services) { t.Fatalf("SwarmJoin: expected len services to be equal in server1 and server2, got:\n%#v\n%#v", len(server1.services), len(server2.services)) } if !compareServices(server1.services[0], server2.services[0]) { t.Fatalf("SwarmJoin: expected services to be equal in server1 and server2, got:\n%#v\n%#v", server1.services[0], server2.services[0]) } if len(server1.tasks) != len(server2.tasks) { t.Fatalf("SwarmJoin: expected len tasks to be equal in server1 and server2, got:\n%#v\n%#v", len(server1.tasks), len(server2.tasks)) } if !compareTasks(server1.tasks[0], server2.tasks[0]) { t.Fatalf("SwarmJoin: expected tasks to be equal in server1 and server2, got:\n%#v\n%#v", server1.tasks[0], server2.tasks[0]) } } func TestSwarmJoinAlreadyInSwarm(t *testing.T) { server, err := NewServer("127.0.0.1:0", nil, nil) if err != nil { t.Fatal(err) } defer server.Stop() server.buildMuxer() server.swarm = &swarm.Swarm{} recorder := httptest.NewRecorder() request, _ := http.NewRequest(http.MethodPost, "/swarm/join", nil) server.ServeHTTP(recorder, request) if recorder.Code != http.StatusNotAcceptable { t.Fatalf("SwarmJoin: wrong status. Want %d. Got %d.", http.StatusNotAcceptable, recorder.Code) } } func TestSwarmLeave(t *testing.T) { t.Parallel() server, err := NewServer("127.0.0.1:0", nil, nil) if err != nil { t.Fatal(err) } defer server.Stop() server.buildMuxer() server.swarm = &swarm.Swarm{} server.swarmServer, _ = newSwarmServer(server, "127.0.0.1:0") recorder := httptest.NewRecorder() request, _ := http.NewRequest(http.MethodPost, "/swarm/leave", nil) server.ServeHTTP(recorder, request) if recorder.Code != http.StatusOK { t.Fatalf("SwarmLeave: wrong status. Want %d. Got %d.", http.StatusOK, recorder.Code) } if server.swarm != nil { t.Fatalf("SwarmLeave: expected swarm to be nil. Got %+v.", server.swarm) } } func TestSwarmLeaveNotInSwarm(t *testing.T) { t.Parallel() server, err := NewServer("127.0.0.1:0", nil, nil) if err != nil { t.Fatal(err) } defer server.Stop() server.buildMuxer() recorder := httptest.NewRecorder() request, _ := http.NewRequest(http.MethodPost, "/swarm/leave", nil) server.ServeHTTP(recorder, request) if recorder.Code != http.StatusNotAcceptable { t.Fatalf("SwarmLeave: wrong status. Want %d. Got %d.", http.StatusNotAcceptable, recorder.Code) } if server.swarm != nil { t.Fatalf("SwarmLeave: expected swarm to be nil. Got %+v.", server.swarm) } } func TestSwarmInspect(t *testing.T) { t.Parallel() server, err := NewServer("127.0.0.1:0", nil, nil) if err != nil { t.Fatal(err) } defer server.Stop() server.buildMuxer() expected := &swarm.Swarm{ ClusterInfo: swarm.ClusterInfo{ ID: "swarm-id", }, } server.swarm = expected recorder := httptest.NewRecorder() request, _ := http.NewRequest(http.MethodGet, "/swarm", nil) server.ServeHTTP(recorder, request) if recorder.Code != http.StatusOK { t.Fatalf("SwarmInspect: wrong status. Want %d. Got %d.", http.StatusOK, recorder.Code) } var swarmInspect *swarm.Swarm err = json.Unmarshal(recorder.Body.Bytes(), &swarmInspect) if err != nil { t.Fatalf("SwarmInspect: got error. %s", err.Error()) } if expected.ID != swarmInspect.ID { t.Fatalf("SwarmInspect: wrong response. Want %+v. Got %+v.", expected, swarmInspect) } } func TestSwarmInspectNotInSwarm(t *testing.T) { t.Parallel() server, err := NewServer("127.0.0.1:0", nil, nil) if err != nil { t.Fatal(err) } defer server.Stop() server.buildMuxer() recorder := httptest.NewRecorder() request, _ := http.NewRequest(http.MethodGet, "/swarm", nil) server.ServeHTTP(recorder, request) if recorder.Code != http.StatusNotAcceptable { t.Fatalf("SwarmInspect: wrong status. Want %d. Got %d.", http.StatusNotAcceptable, recorder.Code) } } func TestServiceCreate(t *testing.T) { t.Parallel() server, unused := setUpSwarm(t) defer server.Stop() defer unused.Stop() recorder := httptest.NewRecorder() serviceCreateOpts := docker.CreateServiceOptions{ ServiceSpec: swarm.ServiceSpec{ Annotations: swarm.Annotations{ Name: "test", }, TaskTemplate: swarm.TaskSpec{ ContainerSpec: &swarm.ContainerSpec{ Image: "test/test", Command: []string{"sh"}, Args: []string{"--test"}, Env: []string{"ENV=1"}, User: "test", }, }, EndpointSpec: &swarm.EndpointSpec{ Mode: swarm.ResolutionModeVIP, Ports: []swarm.PortConfig{{ Protocol: swarm.PortConfigProtocolTCP, TargetPort: uint32(80), PublishedPort: uint32(80), }}, }, }, } buf, err := json.Marshal(serviceCreateOpts) if err != nil { t.Fatalf("ServiceCreate error: %s", err.Error()) } var params io.Reader = bytes.NewBuffer(buf) request, _ := http.NewRequest(http.MethodPost, "/services/create", params) server.ServeHTTP(recorder, request) if recorder.Code != http.StatusOK { t.Fatalf("ServiceCreate: wrong status code. Want %d. Got %d.", http.StatusOK, recorder.Code) } if len(server.services) != 1 || len(server.tasks) != 1 || len(server.containers) != 1 { t.Fatalf("ServiceCreate: wrong item count. Want 1. Got services: %d, tasks: %d, containers: %d.", len(server.services), len(server.tasks), len(server.containers)) } cont := getContainer(server) expectedContainer := &docker.Container{ ID: cont.ID, Created: cont.Created, Image: "test/test", Name: "test-0", Config: &docker.Config{ Entrypoint: []string{"sh"}, Cmd: []string{"--test"}, Env: []string{"ENV=1"}, }, HostConfig: &docker.HostConfig{}, State: docker.State{ Running: true, StartedAt: cont.State.StartedAt, Pid: cont.State.Pid, ExitCode: 0, }, } if !reflect.DeepEqual(cont, expectedContainer) { t.Fatalf("ServiceCreate: wrong cont. Want\n%#v\nGot\n%#v", expectedContainer, cont) } srv := server.services[0] expectedService := &swarm.Service{ ID: srv.ID, Spec: serviceCreateOpts.ServiceSpec, Endpoint: swarm.Endpoint{ Spec: *serviceCreateOpts.EndpointSpec, Ports: []swarm.PortConfig{{Protocol: "tcp", TargetPort: 80, PublishedPort: 80}}, }, } if !reflect.DeepEqual(srv, expectedService) { t.Fatalf("ServiceCreate: wrong service. Want\n%#v\nGot\n%#v", expectedService, srv) } task := server.tasks[0] expectedTask := &swarm.Task{ ID: task.ID, ServiceID: srv.ID, NodeID: server.nodes[0].ID, Status: swarm.TaskStatus{ State: swarm.TaskStateReady, ContainerStatus: &swarm.ContainerStatus{ ContainerID: cont.ID, }, }, DesiredState: swarm.TaskStateReady, Spec: serviceCreateOpts.TaskTemplate, } if !reflect.DeepEqual(task, expectedTask) { t.Fatalf("ServiceCreate: wrong task. Want\n%#v\nGot\n%#v", expectedTask, task) } } func TestServiceCreateDynamicPort(t *testing.T) { t.Parallel() server, unused := setUpSwarm(t) defer server.Stop() defer unused.Stop() recorder := httptest.NewRecorder() serviceCreateOpts := docker.CreateServiceOptions{ ServiceSpec: swarm.ServiceSpec{ Annotations: swarm.Annotations{ Name: "test", }, TaskTemplate: swarm.TaskSpec{ ContainerSpec: &swarm.ContainerSpec{ Image: "test/test", Command: []string{"sh"}, Args: []string{"--test"}, Env: []string{"ENV=1"}, User: "test", }, }, EndpointSpec: &swarm.EndpointSpec{ Mode: swarm.ResolutionModeVIP, Ports: []swarm.PortConfig{{ Protocol: swarm.PortConfigProtocolTCP, TargetPort: uint32(80), PublishedPort: uint32(0), }}, }, }, } buf, err := json.Marshal(serviceCreateOpts) if err != nil { t.Fatalf("ServiceCreate error: %s", err.Error()) } var params io.Reader = bytes.NewBuffer(buf) request, _ := http.NewRequest(http.MethodPost, "/services/create", params) server.ServeHTTP(recorder, request) if recorder.Code != http.StatusOK { t.Fatalf("ServiceCreate: wrong status code. Want %d. Got %d.", http.StatusOK, recorder.Code) } if len(server.services) != 1 || len(server.tasks) != 1 || len(server.containers) != 1 { t.Fatalf("ServiceCreate: wrong item count. Want 1. Got services: %d, tasks: %d, containers: %d.", len(server.services), len(server.tasks), len(server.containers)) } srv := server.services[0] expectedService := &swarm.Service{ ID: srv.ID, Spec: serviceCreateOpts.ServiceSpec, Endpoint: swarm.Endpoint{ Spec: *serviceCreateOpts.EndpointSpec, Ports: []swarm.PortConfig{{Protocol: "tcp", TargetPort: 80, PublishedPort: 30000}}, }, } if !reflect.DeepEqual(srv, expectedService) { t.Fatalf("ServiceCreate: wrong service. Want\n%#v\nGot\n%#v", expectedService, srv) } } func TestServiceCreateMultipleServers(t *testing.T) { t.Parallel() server1, server2 := setUpSwarm(t) defer server1.Stop() defer server2.Stop() _, err := addTestService(server1) if err != nil { t.Fatal(err) } if len(server1.services) != 1 { t.Fatalf("ServiceCreate: expected services to have len 1, got: %d", len(server1.services)) } if len(server1.services) != len(server2.services) { t.Fatalf("ServiceCreate: expected len services to be equal in server1 and server2, got:\n%#v\n%#v", len(server1.services), len(server2.services)) } if !compareServices(server1.services[0], server2.services[0]) { t.Fatalf("ServiceCreate: expected services to be equal in server1 and server2, got:\n%#v\n%#v", server1.services[0], server2.services[0]) } if len(server1.tasks) != len(server2.tasks) { t.Fatalf("ServiceCreate: expected len tasks to be equal in server1 and server2, got:\n%#v\n%#v", len(server1.tasks), len(server2.tasks)) } if !compareTasks(server1.tasks[0], server2.tasks[0]) { t.Fatalf("ServiceCreate: expected tasks to be equal in server1 and server2, got:\n%#v\n%#v", server1.tasks[0], server2.tasks[0]) } } func TestServiceCreateNoContainers(t *testing.T) { t.Parallel() server, unused := setUpSwarm(t) defer server.Stop() defer unused.Stop() recorder := httptest.NewRecorder() serviceCreateOpts := docker.CreateServiceOptions{ ServiceSpec: swarm.ServiceSpec{ Annotations: swarm.Annotations{ Name: "test", }, }, } buf, err := json.Marshal(serviceCreateOpts) if err != nil { t.Fatalf("ServiceCreate error: %s", err.Error()) } var params io.Reader = bytes.NewBuffer(buf) request, _ := http.NewRequest(http.MethodPost, "/services/create", params) server.ServeHTTP(recorder, request) if recorder.Code != http.StatusOK { t.Fatalf("ServiceCreate: wrong status code. Want %d. Got %d.", http.StatusOK, recorder.Code) } if len(server.services) != 1 || len(server.tasks) != 0 || len(server.containers) != 0 { t.Fatalf("ServiceCreate: wrong item count. Want 1 service and 0 tasks. Got services: %d, tasks: %d, containers: %d.", len(server.services), len(server.tasks), len(server.containers)) } } func compareServices(srv1 *swarm.Service, srv2 *swarm.Service) bool { srv1.CreatedAt = srv2.CreatedAt srv1.UpdatedAt = srv2.UpdatedAt if srv1.UpdateStatus != nil { srv1.UpdateStatus.StartedAt = srv2.UpdateStatus.StartedAt srv1.UpdateStatus.CompletedAt = srv2.UpdateStatus.CompletedAt } return reflect.DeepEqual(srv1, srv2) } func compareTasks(task1 *swarm.Task, task2 *swarm.Task) bool { task1.CreatedAt = task2.CreatedAt task1.UpdatedAt = task2.UpdatedAt task1.Status.Timestamp = task2.Status.Timestamp return reflect.DeepEqual(task1, task2) } func TestServiceInspect(t *testing.T) { t.Parallel() server, unused := setUpSwarm(t) defer server.Stop() defer unused.Stop() srv, err := addTestService(server) if err != nil { t.Fatal(err) } recorder := httptest.NewRecorder() request, _ := http.NewRequest(http.MethodGet, "/services/"+srv.ID, nil) server.ServeHTTP(recorder, request) if recorder.Code != http.StatusOK { t.Fatalf("ServiceInspect: wrong status code. Want %d. Got %d.", http.StatusOK, recorder.Code) } var srvInspect swarm.Service err = json.Unmarshal(recorder.Body.Bytes(), &srvInspect) if err != nil { t.Fatalf("ServiceInspect: unable to unmarshal response body: %s", err) } if !compareServices(srv, &srvInspect) { t.Fatalf("ServiceInspect: wrong service. Want\n%#v\nGot\n%#v", srv, &srvInspect) } } func TestServiceInspectByName(t *testing.T) { t.Parallel() server, unused := setUpSwarm(t) defer server.Stop() defer unused.Stop() srv, err := addTestService(server) if err != nil { t.Fatal(err) } recorder := httptest.NewRecorder() request, _ := http.NewRequest(http.MethodGet, "/services/"+srv.Spec.Name, nil) server.ServeHTTP(recorder, request) if recorder.Code != http.StatusOK { t.Fatalf("ServiceInspect: wrong status code. Want %d. Got %d.", http.StatusOK, recorder.Code) } var srvInspect swarm.Service err = json.Unmarshal(recorder.Body.Bytes(), &srvInspect) if err != nil { t.Fatalf("ServiceInspect: unable to unmarshal response body: %s", err) } if !compareServices(srv, &srvInspect) { t.Fatalf("ServiceInspect: wrong service. Want\n%#v\nGot\n%#v", srv, &srvInspect) } } func TestServiceInspectNotFound(t *testing.T) { t.Parallel() server, unused := setUpSwarm(t) defer server.Stop() defer unused.Stop() recorder := httptest.NewRecorder() request, _ := http.NewRequest(http.MethodGet, "/services/abcd", nil) server.ServeHTTP(recorder, request) if recorder.Code != http.StatusNotFound { t.Fatalf("ServiceInspect: wrong status code. Want %d. Got %d.", http.StatusNotFound, recorder.Code) } } func TestTaskInspect(t *testing.T) { t.Parallel() server, unused := setUpSwarm(t) defer server.Stop() defer unused.Stop() _, err := addTestService(server) if err != nil { t.Fatal(err) } task := server.tasks[0] recorder := httptest.NewRecorder() request, _ := http.NewRequest(http.MethodGet, "/tasks/"+task.ID, nil) server.ServeHTTP(recorder, request) if recorder.Code != http.StatusOK { t.Fatalf("TaskInspect: wrong status code. Want %d. Got %d.", http.StatusOK, recorder.Code) } var taskInspect swarm.Task err = json.Unmarshal(recorder.Body.Bytes(), &taskInspect) if err != nil { t.Fatalf("TaskInspect: unable to unmarshal response body: %s", err) } if !compareTasks(task, &taskInspect) { t.Fatalf("TaskInspect: wrong task. Want\n%#v\nGot\n%#v", task, &taskInspect) } } func TestTaskInspectNotFound(t *testing.T) { t.Parallel() server, unused := setUpSwarm(t) defer server.Stop() defer unused.Stop() recorder := httptest.NewRecorder() request, _ := http.NewRequest(http.MethodGet, "/tasks/abcd", nil) server.ServeHTTP(recorder, request) if recorder.Code != http.StatusNotFound { t.Fatalf("TaskInspect: wrong status code. Want %d. Got %d.", http.StatusNotFound, recorder.Code) } } func TestServiceList(t *testing.T) { t.Parallel() server, unused := setUpSwarm(t) defer server.Stop() defer unused.Stop() srv, err := addTestService(server) if err != nil { t.Fatal(err) } recorder := httptest.NewRecorder() request, _ := http.NewRequest(http.MethodGet, "/services", nil) server.ServeHTTP(recorder, request) if recorder.Code != http.StatusOK { t.Fatalf("ServiceList: wrong status code. Want %d. Got %d.", http.StatusOK, recorder.Code) } var srvInspect []swarm.Service err = json.Unmarshal(recorder.Body.Bytes(), &srvInspect) if err != nil { t.Fatalf("ServiceList: unable to unmarshal response body: %s", err) } if !compareServices(srv, &srvInspect[0]) { t.Fatalf("ServiceList: wrong service. Want\n%#v\nGot\n%#v", srv, &srvInspect) } } func TestServiceListFilterID(t *testing.T) { t.Parallel() server, unused := setUpSwarm(t) defer server.Stop() defer unused.Stop() srv, err := addTestService(server) if err != nil { t.Fatal(err) } recorder := httptest.NewRecorder() request, _ := http.NewRequest(http.MethodGet, fmt.Sprintf(`/services?filters={"id":[%q]}`, srv.ID), nil) server.ServeHTTP(recorder, request) if recorder.Code != http.StatusOK { t.Fatalf("ServiceList: wrong status code. Want %d. Got %d.", http.StatusOK, recorder.Code) } var srvInspect []swarm.Service err = json.Unmarshal(recorder.Body.Bytes(), &srvInspect) if err != nil { t.Fatalf("ServiceList: unable to unmarshal response body: %s", err) } if !compareServices(srv, &srvInspect[0]) { t.Fatalf("ServiceList: wrong service. Want\n%#v\nGot\n%#v", srv, &srvInspect) } } func TestServiceListFilterName(t *testing.T) { t.Parallel() server, unused := setUpSwarm(t) defer server.Stop() defer unused.Stop() srv, err := addTestService(server) if err != nil { t.Fatal(err) } recorder := httptest.NewRecorder() request, _ := http.NewRequest(http.MethodGet, fmt.Sprintf(`/services?filters={"name":[%q]}`, srv.Spec.Name), nil) server.ServeHTTP(recorder, request) if recorder.Code != http.StatusOK { t.Fatalf("ServiceList: wrong status code. Want %d. Got %d.", http.StatusOK, recorder.Code) } var srvInspect []swarm.Service err = json.Unmarshal(recorder.Body.Bytes(), &srvInspect) if err != nil { t.Fatalf("ServiceList: unable to unmarshal response body: %s", err) } if !compareServices(srv, &srvInspect[0]) { t.Fatalf("ServiceList: wrong service. Want\n%#v\nGot\n%#v", srv, &srvInspect) } } func TestServiceListFilterEmpty(t *testing.T) { t.Parallel() server, unused := setUpSwarm(t) defer server.Stop() defer unused.Stop() _, err := addTestService(server) if err != nil { t.Fatal(err) } recorder := httptest.NewRecorder() request, _ := http.NewRequest(http.MethodGet, "/services?filters="+url.QueryEscape(`{"id":["something"]}`), nil) server.ServeHTTP(recorder, request) if recorder.Code != http.StatusOK { t.Fatalf("ServiceList: wrong status code. Want %d. Got %d.", http.StatusOK, recorder.Code) } var srvInspect []swarm.Service err = json.Unmarshal(recorder.Body.Bytes(), &srvInspect) if err != nil { t.Fatalf("ServiceList: unable to unmarshal response body: %s", err) } if len(srvInspect) != 0 { t.Fatalf("ServiceList: expected empty list got %d.", len(srvInspect)) } } func TestTaskList(t *testing.T) { t.Parallel() server, unused := setUpSwarm(t) defer server.Stop() defer unused.Stop() _, err := addTestService(server) if err != nil { t.Fatal(err) } task := server.tasks[0] recorder := httptest.NewRecorder() request, _ := http.NewRequest(http.MethodGet, "/tasks", nil) server.ServeHTTP(recorder, request) if recorder.Code != http.StatusOK { t.Fatalf("TaskList: wrong status code. Want %d. Got %d.", http.StatusOK, recorder.Code) } var taskInspect []swarm.Task err = json.Unmarshal(recorder.Body.Bytes(), &taskInspect) if err != nil { t.Fatalf("TaskList: unable to unmarshal response body: %s", err) } if !compareTasks(task, &taskInspect[0]) { t.Fatalf("TaskList: wrong task. Want\n%#v\nGot\n%#v", task, &taskInspect) } } func TestTaskListFilterID(t *testing.T) { t.Parallel() server, unused := setUpSwarm(t) defer server.Stop() defer unused.Stop() _, err := addTestService(server) if err != nil { t.Fatal(err) } task := server.tasks[0] recorder := httptest.NewRecorder() request, _ := http.NewRequest(http.MethodGet, fmt.Sprintf(`/tasks?filters={"id":[%q]}`, task.ID), nil) server.ServeHTTP(recorder, request) if recorder.Code != http.StatusOK { t.Fatalf("TaskList: wrong status code. Want %d. Got %d.", http.StatusOK, recorder.Code) } var taskInspect []swarm.Task err = json.Unmarshal(recorder.Body.Bytes(), &taskInspect) if err != nil { t.Fatalf("TaskList: unable to unmarshal response body: %s", err) } if !compareTasks(task, &taskInspect[0]) { t.Fatalf("TaskList: wrong task. Want\n%#v\nGot\n%#v", task, &taskInspect) } } func TestTaskListFilterServiceID(t *testing.T) { t.Parallel() server, unused := setUpSwarm(t) defer server.Stop() defer unused.Stop() _, err := addTestService(server) if err != nil { t.Fatal(err) } task := server.tasks[0] recorder := httptest.NewRecorder() request, _ := http.NewRequest(http.MethodGet, fmt.Sprintf(`/tasks?filters={"service":[%q]}`, task.ServiceID), nil) server.ServeHTTP(recorder, request) if recorder.Code != http.StatusOK { t.Fatalf("TaskList: wrong status code. Want %d. Got %d.", http.StatusOK, recorder.Code) } var taskInspect []swarm.Task err = json.Unmarshal(recorder.Body.Bytes(), &taskInspect) if err != nil { t.Fatalf("TaskList: unable to unmarshal response body: %s", err) } if !compareTasks(task, &taskInspect[0]) { t.Fatalf("TaskList: wrong task. Want\n%#v\nGot\n%#v", task, &taskInspect) } } func TestTaskListFilterServiceName(t *testing.T) { t.Parallel() server, unused := setUpSwarm(t) defer server.Stop() defer unused.Stop() srv, err := addTestService(server) if err != nil { t.Fatal(err) } task := server.tasks[0] recorder := httptest.NewRecorder() request, _ := http.NewRequest(http.MethodGet, fmt.Sprintf(`/tasks?filters={"service":[%q]}`, srv.Spec.Name), nil) server.ServeHTTP(recorder, request) if recorder.Code != http.StatusOK { t.Fatalf("TaskList: wrong status code. Want %d. Got %d.", http.StatusOK, recorder.Code) } var taskInspect []swarm.Task err = json.Unmarshal(recorder.Body.Bytes(), &taskInspect) if err != nil { t.Fatalf("TaskList: unable to unmarshal response body: %s", err) } if !compareTasks(task, &taskInspect[0]) { t.Fatalf("TaskList: wrong task. Want\n%#v\nGot\n%#v", task, &taskInspect) } } func TestTaskListFilterMultipleFields(t *testing.T) { t.Parallel() server, unused := setUpSwarm(t) defer server.Stop() defer unused.Stop() srv, err := addTestService(server) if err != nil { t.Fatal(err) } task := server.tasks[0] recorder := httptest.NewRecorder() request, _ := http.NewRequest(http.MethodGet, fmt.Sprintf(`/tasks?filters={"service":[%q], "id":[%q]}`, srv.Spec.Name, task.ID), nil) server.ServeHTTP(recorder, request) if recorder.Code != http.StatusOK { t.Fatalf("TaskList: wrong status code. Want %d. Got %d.", http.StatusOK, recorder.Code) } var taskInspect []swarm.Task err = json.Unmarshal(recorder.Body.Bytes(), &taskInspect) if err != nil { t.Fatalf("TaskList: unable to unmarshal response body: %s", err) } if !compareTasks(task, &taskInspect[0]) { t.Fatalf("TaskList: wrong task. Want\n%#v\nGot\n%#v", task, &taskInspect) } } func TestTaskListFilterMultipleFieldsNotFound(t *testing.T) { t.Parallel() server, unused := setUpSwarm(t) defer server.Stop() defer unused.Stop() srv, err := addTestService(server) if err != nil { t.Fatal(err) } recorder := httptest.NewRecorder() filterParam := url.QueryEscape(fmt.Sprintf(`{"service":[%q], "id":["abc"]}`, srv.Spec.Name)) request, _ := http.NewRequest(http.MethodGet, "/tasks?filters="+filterParam, nil) server.ServeHTTP(recorder, request) if recorder.Code != http.StatusOK { t.Fatalf("TaskList: wrong status code. Want %d. Got %d.", http.StatusOK, recorder.Code) } var taskInspect []swarm.Task err = json.Unmarshal(recorder.Body.Bytes(), &taskInspect) if err != nil { t.Fatalf("TaskList: unable to unmarshal response body: %s", err) } if len(taskInspect) != 0 { t.Fatalf("TaskList: Want\nempty task list\nGot\n%#v", &taskInspect) } } func TestTaskListFilterNotFound(t *testing.T) { t.Parallel() server, unused := setUpSwarm(t) defer server.Stop() defer unused.Stop() _, err := addTestService(server) if err != nil { t.Fatal(err) } recorder := httptest.NewRecorder() filter := url.QueryEscape(`{"id":["something"]}`) request, _ := http.NewRequest(http.MethodGet, "/tasks?filters="+filter, nil) server.ServeHTTP(recorder, request) if recorder.Code != http.StatusOK { t.Fatalf("TaskList: wrong status code. Want %d. Got %d.", http.StatusOK, recorder.Code) } var taskInspect []swarm.Task err = json.Unmarshal(recorder.Body.Bytes(), &taskInspect) if err != nil { t.Fatalf("TaskList: unable to unmarshal response body: %s", err) } if len(taskInspect) != 0 { t.Fatalf("TaskList: expected empty list got %d.", len(taskInspect)) } } func TestTaskListFilterLabel(t *testing.T) { t.Parallel() server, unused := setUpSwarm(t) defer server.Stop() defer unused.Stop() _, err := addTestService(server) if err != nil { t.Fatal(err) } task := server.tasks[0] recorder := httptest.NewRecorder() filter := url.QueryEscape(`{"label":["mykey=myvalue"]}`) request, _ := http.NewRequest(http.MethodGet, "/tasks?filters="+filter, nil) server.ServeHTTP(recorder, request) if recorder.Code != http.StatusOK { t.Fatalf("TaskList: wrong status code. Want %d. Got %d.", http.StatusOK, recorder.Code) } var taskInspect []swarm.Task err = json.Unmarshal(recorder.Body.Bytes(), &taskInspect) if err != nil { t.Fatalf("TaskList: unable to unmarshal response body: %s", err) } if !compareTasks(task, &taskInspect[0]) { t.Fatalf("TaskList: wrong task. Want\n%#v\nGot\n%#v", task, &taskInspect) } filter = url.QueryEscape(`{"label":["mykey"]}`) request, _ = http.NewRequest(http.MethodGet, "/tasks?filters="+filter, nil) recorder = httptest.NewRecorder() server.ServeHTTP(recorder, request) if recorder.Code != http.StatusOK { t.Fatalf("TaskList: wrong status code. Want %d. Got %d.", http.StatusOK, recorder.Code) } err = json.Unmarshal(recorder.Body.Bytes(), &taskInspect) if err != nil { t.Fatalf("TaskList: unable to unmarshal response body: %s", err) } if !compareTasks(task, &taskInspect[0]) { t.Fatalf("TaskList: wrong task. Want\n%#v\nGot\n%#v", task, &taskInspect) } filter = url.QueryEscape(`{"label":["otherkey"]}`) request, _ = http.NewRequest(http.MethodGet, "/tasks?filters="+filter, nil) recorder = httptest.NewRecorder() server.ServeHTTP(recorder, request) if recorder.Code != http.StatusOK { t.Fatalf("TaskList: wrong status code. Want %d. Got %d.", http.StatusOK, recorder.Code) } err = json.Unmarshal(recorder.Body.Bytes(), &taskInspect) if err != nil { t.Fatalf("TaskList: unable to unmarshal response body: %s", err) } if len(taskInspect) != 0 { t.Fatalf("TaskList: wrong size. Want 0, Got %d", len(taskInspect)) } } func TestServiceDelete(t *testing.T) { t.Parallel() server, unused := setUpSwarm(t) defer server.Stop() defer unused.Stop() srv, err := addTestService(server) if err != nil { t.Fatal(err) } recorder := httptest.NewRecorder() request, _ := http.NewRequest(http.MethodDelete, "/services/"+srv.ID, nil) server.ServeHTTP(recorder, request) if recorder.Code != http.StatusOK { t.Fatalf("ServiceDelete: wrong status code. Want %d. Got %d.", http.StatusOK, recorder.Code) } if len(server.services) != 0 { t.Fatalf("ServiceDelete: expected empty services, got %d", len(server.services)) } if len(server.tasks) != 0 { t.Fatalf("ServiceDelete: expected empty tasks, got %d", len(server.tasks)) } if len(server.containers) != 0 { t.Fatalf("ServiceDelete: expected empty containers, got %d", len(server.containers)) } } func TestServiceDeleteNotFound(t *testing.T) { t.Parallel() server, unused := setUpSwarm(t) defer server.Stop() defer unused.Stop() recorder := httptest.NewRecorder() request, _ := http.NewRequest(http.MethodDelete, "/services/blahblah", nil) server.ServeHTTP(recorder, request) if recorder.Code != http.StatusNotFound { t.Fatalf("ServiceDelete: wrong status code. Want %d. Got %d.", http.StatusNotFound, recorder.Code) } } func TestServiceUpdate(t *testing.T) { t.Parallel() server, unused := setUpSwarm(t) defer server.Stop() defer unused.Stop() srv, err := addTestService(server) if err != nil { t.Fatal(err) } recorder := httptest.NewRecorder() updateOpts := swarm.ServiceSpec{ Annotations: swarm.Annotations{ Name: "test", }, TaskTemplate: swarm.TaskSpec{ ContainerSpec: &swarm.ContainerSpec{ Image: "test/test2", Args: []string{"--test2"}, Env: []string{"ENV=2"}, User: "test", }, }, EndpointSpec: &swarm.EndpointSpec{ Mode: swarm.ResolutionModeVIP, Ports: []swarm.PortConfig{{ Protocol: swarm.PortConfigProtocolTCP, TargetPort: uint32(80), PublishedPort: uint32(80), }}, }, } buf, err := json.Marshal(updateOpts) if err != nil { t.Fatalf("ServiceUpdate error: %s", err.Error()) } request, _ := http.NewRequest(http.MethodPost, fmt.Sprintf("/services/%s/update", srv.ID), bytes.NewReader(buf)) server.ServeHTTP(recorder, request) if recorder.Code != http.StatusOK { t.Fatalf("ServiceUpdate: wrong status code. Want %d. Got %d.", http.StatusOK, recorder.Code) } if len(server.services) != 1 || len(server.tasks) != 1 || len(server.containers) != 1 { t.Fatalf("ServiceUpdate: wrong item count. Want 1. Got services: %d, tasks: %d, containers: %d.", len(server.services), len(server.tasks), len(server.containers)) } cont := getContainer(server) expectedContainer := &docker.Container{ ID: cont.ID, Created: cont.Created, Image: "test/test2", Name: "test-0-updated", Config: &docker.Config{ Cmd: []string{"--test2"}, Env: []string{"ENV=2"}, }, HostConfig: &docker.HostConfig{}, State: docker.State{ Running: true, StartedAt: cont.State.StartedAt, Pid: cont.State.Pid, ExitCode: 0, }, } if !reflect.DeepEqual(cont, expectedContainer) { t.Fatalf("ServiceUpdate: wrong cont. Want\n%#v\nGot\n%#v", expectedContainer, cont) } srv = server.services[0] expectedService := &swarm.Service{ ID: srv.ID, Spec: updateOpts, Endpoint: swarm.Endpoint{ Spec: *updateOpts.EndpointSpec, Ports: []swarm.PortConfig{{Protocol: "tcp", TargetPort: 80, PublishedPort: 80}}, }, UpdateStatus: &swarm.UpdateStatus{ State: swarm.UpdateStateCompleted, }, } srv.UpdateStatus.CompletedAt = nil srv.UpdateStatus.StartedAt = nil if !reflect.DeepEqual(srv, expectedService) { t.Fatalf("ServiceUpdate: wrong service. Want\n%#v\nGot\n%#v", expectedService, srv) } task := server.tasks[0] expectedTask := &swarm.Task{ ID: task.ID, ServiceID: srv.ID, NodeID: server.nodes[1].ID, Status: swarm.TaskStatus{ State: swarm.TaskStateReady, ContainerStatus: &swarm.ContainerStatus{ ContainerID: cont.ID, }, }, DesiredState: swarm.TaskStateReady, Spec: updateOpts.TaskTemplate, } if !reflect.DeepEqual(task, expectedTask) { t.Fatalf("ServiceUpdate: wrong task. Want\n%#v\nGot\n%#v", expectedTask, task) } } func TestServiceUpdateMoreReplicas(t *testing.T) { t.Parallel() server, unused := setUpSwarm(t) defer server.Stop() defer unused.Stop() srv, err := addTestService(server) if err != nil { t.Fatal(err) } recorder := httptest.NewRecorder() replicas := uint64(3) updateOpts := swarm.ServiceSpec{ Annotations: swarm.Annotations{ Name: "test", }, TaskTemplate: swarm.TaskSpec{ ContainerSpec: &swarm.ContainerSpec{ Image: "test/test2", Args: []string{"--test2"}, Env: []string{"ENV=2"}, User: "test", }, }, EndpointSpec: &swarm.EndpointSpec{ Mode: swarm.ResolutionModeVIP, Ports: []swarm.PortConfig{{ Protocol: swarm.PortConfigProtocolTCP, TargetPort: uint32(80), PublishedPort: uint32(80), }}, }, Mode: swarm.ServiceMode{ Replicated: &swarm.ReplicatedService{ Replicas: &replicas, }, }, } buf, err := json.Marshal(updateOpts) if err != nil { t.Fatalf("ServiceUpdate error: %s", err.Error()) } request, _ := http.NewRequest(http.MethodPost, fmt.Sprintf("/services/%s/update", srv.ID), bytes.NewReader(buf)) server.ServeHTTP(recorder, request) if recorder.Code != http.StatusOK { t.Fatalf("ServiceUpdate: wrong status code. Want %d. Got %d.", http.StatusOK, recorder.Code) } if len(server.services) != 1 || len(server.tasks) != 3 || len(server.containers) != 3 { t.Fatalf("ServiceUpdate: wrong item count. Want 1 service and 3 replicas. Got services: %d, tasks: %d, containers: %d.", len(server.services), len(server.tasks), len(server.containers)) } } func TestServiceUpdateNotFound(t *testing.T) { t.Parallel() server, unused := setUpSwarm(t) defer server.Stop() defer unused.Stop() recorder := httptest.NewRecorder() updateOpts := swarm.ServiceSpec{ Annotations: swarm.Annotations{ Name: "test", }, TaskTemplate: swarm.TaskSpec{ ContainerSpec: &swarm.ContainerSpec{ Image: "test/test2", Args: []string{"--test2"}, Env: []string{"ENV=2"}, User: "test", }, }, EndpointSpec: &swarm.EndpointSpec{ Mode: swarm.ResolutionModeVIP, Ports: []swarm.PortConfig{{ Protocol: swarm.PortConfigProtocolTCP, TargetPort: uint32(80), PublishedPort: uint32(80), }}, }, } buf, err := json.Marshal(updateOpts) if err != nil { t.Fatalf("ServiceUpdate error: %s", err.Error()) } request, _ := http.NewRequest(http.MethodPost, "/services/pale/update", bytes.NewReader(buf)) server.ServeHTTP(recorder, request) if recorder.Code != http.StatusNotFound { t.Fatalf("ServiceUpdate: wrong status code. Want %d. Got %d.", http.StatusNotFound, recorder.Code) } } func TestNodeList(t *testing.T) { t.Parallel() srv1, srv2 := setUpSwarm(t) defer srv1.Stop() defer srv2.Stop() for _, srv := range []*DockerServer{srv1, srv2} { recorder := httptest.NewRecorder() request, _ := http.NewRequest(http.MethodGet, "/nodes", nil) srv.ServeHTTP(recorder, request) if recorder.Code != http.StatusOK { t.Fatalf("invalid status code: %d", recorder.Code) } var nodes []swarm.Node err := json.NewDecoder(recorder.Body).Decode(&nodes) if err != nil { t.Fatal(err) } if !reflect.DeepEqual(nodes, srv1.nodes) { t.Fatalf("expected nodes to equal %#v, got: %#v", srv1.nodes, nodes) } if !reflect.DeepEqual(nodes, srv2.nodes) { t.Fatalf("expected nodes to equal %#v, got: %#v", srv2.nodes, nodes) } } } func TestNodeInfo(t *testing.T) { t.Parallel() srv1, srv2 := setUpSwarm(t) defer srv1.Stop() defer srv2.Stop() for _, srv := range []*DockerServer{srv1, srv2} { recorder := httptest.NewRecorder() request, _ := http.NewRequest(http.MethodGet, "/nodes/"+srv.nodes[0].ID, nil) srv.ServeHTTP(recorder, request) if recorder.Code != http.StatusOK { t.Fatalf("invalid status code: %d", recorder.Code) } var node swarm.Node err := json.NewDecoder(recorder.Body).Decode(&node) if err != nil { t.Fatal(err) } if !reflect.DeepEqual(node, srv1.nodes[0]) { t.Fatalf("expected node to equal %#v, got: %#v", srv1.nodes[0], node) } if !reflect.DeepEqual(node, srv2.nodes[0]) { t.Fatalf("expected node to equal %#v, got: %#v", srv2.nodes[0], node) } } } func TestNodeUpdate(t *testing.T) { t.Parallel() srv1, srv2 := setUpSwarm(t) defer srv1.Stop() defer srv2.Stop() recorder := httptest.NewRecorder() for i, srv := range []*DockerServer{srv1, srv2} { key := fmt.Sprintf("l%d", i) data, err := json.Marshal(swarm.NodeSpec{ Annotations: swarm.Annotations{Labels: map[string]string{key: "value"}}, }) if err != nil { t.Fatal(err) } body := bytes.NewReader(data) request, _ := http.NewRequest(http.MethodPost, "/nodes/"+srv.nodes[0].ID+"/update", body) srv.ServeHTTP(recorder, request) if recorder.Code != http.StatusOK { t.Fatalf("invalid status code: %d", recorder.Code) } if srv1.nodes[0].Spec.Labels[key] != "value" { t.Fatalf("expected node to have label %s", key) } if srv2.nodes[0].Spec.Labels[key] != "value" { t.Fatalf("expected node to have label %s", key) } } } func TestNodeDelete(t *testing.T) { t.Parallel() srv1, srv2 := setUpSwarm(t) defer srv1.Stop() defer srv2.Stop() recorder := httptest.NewRecorder() request, _ := http.NewRequest(http.MethodDelete, "/nodes/"+srv1.nodes[0].ID, nil) srv1.ServeHTTP(recorder, request) if recorder.Code != http.StatusOK { t.Fatalf("invalid status code: %d", recorder.Code) } if len(srv1.nodes) != 1 { t.Fatalf("expected len(nodes) to be 1, got %d", len(srv1.nodes)) } if len(srv2.nodes) != 1 { t.Fatalf("expected len(nodes) to be 1, got %d", len(srv2.nodes)) } } func setUpSwarm(t *testing.T) (*DockerServer, *DockerServer) { server1, err := NewServer("127.0.0.1:0", nil, nil) if err != nil { t.Fatal(err) } server2, err := NewServer("127.0.0.1:0", nil, nil) if err != nil { t.Fatal(err) } recorder := httptest.NewRecorder() request, _ := http.NewRequest(http.MethodPost, "/swarm/init", bytes.NewReader(nil)) server1.ServeHTTP(recorder, request) if recorder.Code != http.StatusOK { t.Fatalf("setUpSwarm: invalid status code swarm init %d", recorder.Code) } data, err := json.Marshal(swarm.JoinRequest{ RemoteAddrs: []string{server1.SwarmAddress()}, }) if err != nil { t.Fatal(err) } recorder = httptest.NewRecorder() request, _ = http.NewRequest(http.MethodPost, "/swarm/join", bytes.NewReader(data)) server2.ServeHTTP(recorder, request) if recorder.Code != http.StatusOK { t.Fatalf("setUpSwarm: invalid status code swarm join %d", recorder.Code) } return server1, server2 } func addTestService(server *DockerServer) (*swarm.Service, error) { recorder := httptest.NewRecorder() serviceCreateOpts := docker.CreateServiceOptions{ ServiceSpec: swarm.ServiceSpec{ Annotations: swarm.Annotations{ Name: "test", Labels: map[string]string{ "mykey": "myvalue", }, }, TaskTemplate: swarm.TaskSpec{ ContainerSpec: &swarm.ContainerSpec{ Image: "test/test", Args: []string{"--test"}, Env: []string{"ENV=1"}, User: "test", }, }, EndpointSpec: &swarm.EndpointSpec{ Mode: swarm.ResolutionModeVIP, Ports: []swarm.PortConfig{{ Protocol: swarm.PortConfigProtocolTCP, TargetPort: uint32(80), PublishedPort: uint32(80), }}, }, }, } buf, err := json.Marshal(serviceCreateOpts) if err != nil { return nil, err } var params io.Reader = bytes.NewBuffer(buf) request, _ := http.NewRequest(http.MethodPost, "/services/create", params) server.ServeHTTP(recorder, request) if recorder.Code != http.StatusOK { return nil, fmt.Errorf("unexpected status %d", recorder.Code) } if len(server.services) == 0 { return nil, errors.New("no service created on server") } if len(server.tasks) == 0 { return nil, errors.New("no tasks created on server") } return server.services[0], nil } func TestMutateTask(t *testing.T) { t.Parallel() server := DockerServer{failures: make(map[string]string)} server.buildMuxer() server.tasks = append(server.tasks, &swarm.Task{ID: "id123"}) newTask := swarm.Task{Status: swarm.TaskStatus{State: swarm.TaskStateFailed}} err := server.MutateTask("id123", newTask) if err != nil { t.Fatal(err) } if !reflect.DeepEqual(server.tasks[0], &newTask) { t.Errorf("Wrong task after mutation.\nWant %#v.\nGot %#v.", newTask, server.tasks[0]) } } func TestMutateTaskNotFound(t *testing.T) { t.Parallel() server := DockerServer{failures: make(map[string]string)} server.buildMuxer() newTask := swarm.Task{Status: swarm.TaskStatus{State: swarm.TaskStateFailed}} err := server.MutateTask("id123", newTask) if err == nil { t.Error("Unexpected error") } if err.Error() != "task not found" { t.Errorf("wrong error message. Want %q. Got %q.", "task not found", err) } } go-dockerclient-1.12.0/tls.go000066400000000000000000000055471465717111200160150ustar00rootroot00000000000000// Copyright 2014 go-dockerclient authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. // // The content is borrowed from Docker's own source code to provide a simple // tls based dialer package docker import ( "crypto/tls" "errors" "net" "strings" "time" ) type tlsClientCon struct { *tls.Conn rawConn net.Conn } func (c *tlsClientCon) CloseWrite() error { // Go standard tls.Conn doesn't provide the CloseWrite() method so we do it // on its underlying connection. if cwc, ok := c.rawConn.(interface { CloseWrite() error }); ok { return cwc.CloseWrite() } return nil } func tlsDialWithDialer(dialer *net.Dialer, network, addr string, config *tls.Config) (net.Conn, error) { // We want the Timeout and Deadline values from dialer to cover the // whole process: TCP connection and TLS handshake. This means that we // also need to start our own timers now. timeout := dialer.Timeout if !dialer.Deadline.IsZero() { deadlineTimeout := time.Until(dialer.Deadline) if timeout == 0 || deadlineTimeout < timeout { timeout = deadlineTimeout } } var errChannel chan error if timeout != 0 { errChannel = make(chan error, 2) time.AfterFunc(timeout, func() { errChannel <- errors.New("") }) } rawConn, err := dialer.Dial(network, addr) if err != nil { return nil, err } colonPos := strings.LastIndex(addr, ":") if colonPos == -1 { colonPos = len(addr) } hostname := addr[:colonPos] // If no ServerName is set, infer the ServerName // from the hostname we're connecting to. if config.ServerName == "" { // Make a copy to avoid polluting argument or default. config = copyTLSConfig(config) config.ServerName = hostname } conn := tls.Client(rawConn, config) if timeout == 0 { err = conn.Handshake() } else { go func() { errChannel <- conn.Handshake() }() err = <-errChannel } if err != nil { rawConn.Close() return nil, err } // This is Docker difference with standard's crypto/tls package: returned a // wrapper which holds both the TLS and raw connections. return &tlsClientCon{conn, rawConn}, nil } // this exists to silent an error message in go vet func copyTLSConfig(cfg *tls.Config) *tls.Config { return &tls.Config{ Certificates: cfg.Certificates, CipherSuites: cfg.CipherSuites, ClientAuth: cfg.ClientAuth, ClientCAs: cfg.ClientCAs, ClientSessionCache: cfg.ClientSessionCache, CurvePreferences: cfg.CurvePreferences, InsecureSkipVerify: cfg.InsecureSkipVerify, MaxVersion: cfg.MaxVersion, MinVersion: cfg.MinVersion, NextProtos: cfg.NextProtos, Rand: cfg.Rand, RootCAs: cfg.RootCAs, ServerName: cfg.ServerName, SessionTicketsDisabled: cfg.SessionTicketsDisabled, } } go-dockerclient-1.12.0/volume.go000066400000000000000000000126321465717111200165130ustar00rootroot00000000000000// Copyright 2015 go-dockerclient authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package docker import ( "context" "encoding/json" "errors" "net/http" "time" ) var ( // ErrNoSuchVolume is the error returned when the volume does not exist. ErrNoSuchVolume = errors.New("no such volume") // ErrVolumeInUse is the error returned when the volume requested to be removed is still in use. ErrVolumeInUse = errors.New("volume in use and cannot be removed") ) // Volume represents a volume. // // See https://goo.gl/3wgTsd for more details. type Volume struct { Name string `json:"Name" yaml:"Name" toml:"Name"` Driver string `json:"Driver,omitempty" yaml:"Driver,omitempty" toml:"Driver,omitempty"` Mountpoint string `json:"Mountpoint,omitempty" yaml:"Mountpoint,omitempty" toml:"Mountpoint,omitempty"` Labels map[string]string `json:"Labels,omitempty" yaml:"Labels,omitempty" toml:"Labels,omitempty"` Options map[string]string `json:"Options,omitempty" yaml:"Options,omitempty" toml:"Options,omitempty"` CreatedAt time.Time `json:"CreatedAt,omitempty" yaml:"CreatedAt,omitempty" toml:"CreatedAt,omitempty"` } // ListVolumesOptions specify parameters to the ListVolumes function. // // See https://goo.gl/3wgTsd for more details. type ListVolumesOptions struct { Filters map[string][]string Context context.Context } // ListVolumes returns a list of available volumes in the server. // // See https://goo.gl/3wgTsd for more details. func (c *Client) ListVolumes(opts ListVolumesOptions) ([]Volume, error) { resp, err := c.do(http.MethodGet, "/volumes?"+queryString(opts), doOptions{ context: opts.Context, }) if err != nil { return nil, err } defer resp.Body.Close() m := make(map[string]any) if err = json.NewDecoder(resp.Body).Decode(&m); err != nil { return nil, err } var volumes []Volume volumesJSON, ok := m["Volumes"] if !ok { return volumes, nil } data, err := json.Marshal(volumesJSON) if err != nil { return nil, err } if err := json.Unmarshal(data, &volumes); err != nil { return nil, err } return volumes, nil } // CreateVolumeOptions specify parameters to the CreateVolume function. // // See https://goo.gl/qEhmEC for more details. type CreateVolumeOptions struct { Name string Driver string DriverOpts map[string]string Context context.Context `json:"-"` Labels map[string]string } // CreateVolume creates a volume on the server. // // See https://goo.gl/qEhmEC for more details. func (c *Client) CreateVolume(opts CreateVolumeOptions) (*Volume, error) { resp, err := c.do(http.MethodPost, "/volumes/create", doOptions{ data: opts, context: opts.Context, }) if err != nil { return nil, err } defer resp.Body.Close() var volume Volume if err := json.NewDecoder(resp.Body).Decode(&volume); err != nil { return nil, err } return &volume, nil } // InspectVolume returns a volume by its name. // // See https://goo.gl/GMjsMc for more details. func (c *Client) InspectVolume(name string) (*Volume, error) { resp, err := c.do(http.MethodGet, "/volumes/"+name, doOptions{}) if err != nil { var e *Error if errors.As(err, &e) && e.Status == http.StatusNotFound { return nil, ErrNoSuchVolume } return nil, err } defer resp.Body.Close() var volume Volume if err := json.NewDecoder(resp.Body).Decode(&volume); err != nil { return nil, err } return &volume, nil } // RemoveVolume removes a volume by its name. // // Deprecated: Use RemoveVolumeWithOptions instead. func (c *Client) RemoveVolume(name string) error { return c.RemoveVolumeWithOptions(RemoveVolumeOptions{Name: name}) } // RemoveVolumeOptions specify parameters to the RemoveVolumeWithOptions // function. // // See https://goo.gl/nvd6qj for more details. type RemoveVolumeOptions struct { Context context.Context Name string `qs:"-"` Force bool } // RemoveVolumeWithOptions removes a volume by its name and takes extra // parameters. // // See https://goo.gl/nvd6qj for more details. func (c *Client) RemoveVolumeWithOptions(opts RemoveVolumeOptions) error { path := "/volumes/" + opts.Name resp, err := c.do(http.MethodDelete, path+"?"+queryString(opts), doOptions{context: opts.Context}) if err != nil { var e *Error if errors.As(err, &e) { if e.Status == http.StatusNotFound { return ErrNoSuchVolume } if e.Status == http.StatusConflict { return ErrVolumeInUse } } return err } defer resp.Body.Close() return nil } // PruneVolumesOptions specify parameters to the PruneVolumes function. // // See https://goo.gl/f9XDem for more details. type PruneVolumesOptions struct { Filters map[string][]string Context context.Context } // PruneVolumesResults specify results from the PruneVolumes function. // // See https://goo.gl/f9XDem for more details. type PruneVolumesResults struct { VolumesDeleted []string SpaceReclaimed int64 } // PruneVolumes deletes volumes which are unused. // // See https://goo.gl/f9XDem for more details. func (c *Client) PruneVolumes(opts PruneVolumesOptions) (*PruneVolumesResults, error) { path := "/volumes/prune?" + queryString(opts) resp, err := c.do(http.MethodPost, path, doOptions{context: opts.Context}) if err != nil { return nil, err } defer resp.Body.Close() var results PruneVolumesResults if err := json.NewDecoder(resp.Body).Decode(&results); err != nil { return nil, err } return &results, nil } go-dockerclient-1.12.0/volume_test.go000066400000000000000000000136401465717111200175520ustar00rootroot00000000000000// Copyright 2015 go-dockerclient authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package docker import ( "encoding/json" "errors" "net/http" "net/url" "reflect" "testing" ) func TestListVolumes(t *testing.T) { t.Parallel() volumesData := `[ { "Name": "tardis", "Driver": "local", "Mountpoint": "/var/lib/docker/volumes/tardis", "CreatedAt": "2017-07-19T12:00:26Z" }, { "Name": "foo", "Driver": "bar", "Mountpoint": "/var/lib/docker/volumes/bar", "CreatedAt": "2017-07-19T12:01:26Z" } ]` body := `{ "Volumes": ` + volumesData + ` }` var expected []Volume if err := json.Unmarshal([]byte(volumesData), &expected); err != nil { t.Fatal(err) } client := newTestClient(&FakeRoundTripper{message: body, status: http.StatusOK}) volumes, err := client.ListVolumes(ListVolumesOptions{}) if err != nil { t.Error(err) } if !reflect.DeepEqual(volumes, expected) { t.Errorf("ListVolumes: Wrong return value. Want %#v. Got %#v.", expected, volumes) } } func TestCreateVolume(t *testing.T) { t.Parallel() body := `{ "Name": "tardis", "Driver": "local", "Mountpoint": "/var/lib/docker/volumes/tardis" }` var expected Volume if err := json.Unmarshal([]byte(body), &expected); err != nil { t.Fatal(err) } fakeRT := &FakeRoundTripper{message: body, status: http.StatusOK} client := newTestClient(fakeRT) volume, err := client.CreateVolume( CreateVolumeOptions{ Name: "tardis", Driver: "local", DriverOpts: map[string]string{ "foo": "bar", }, }, ) if err != nil { t.Fatal(err) } if !reflect.DeepEqual(volume, &expected) { t.Errorf("CreateVolume: Wrong return value. Want %#v. Got %#v.", expected, volume) } req := fakeRT.requests[0] expectedMethod := http.MethodPost if req.Method != expectedMethod { t.Errorf("CreateVolume(): Wrong HTTP method. Want %s. Got %s.", expectedMethod, req.Method) } u, _ := url.Parse(client.getURL("/volumes/create")) if req.URL.Path != u.Path { t.Errorf("CreateVolume(): Wrong request path. Want %q. Got %q.", u.Path, req.URL.Path) } } func TestInspectVolume(t *testing.T) { t.Parallel() body := `{ "Name": "tardis", "Driver": "local", "Mountpoint": "/var/lib/docker/volumes/tardis", "Options": { "foo": "bar" } }` var expected Volume if err := json.Unmarshal([]byte(body), &expected); err != nil { t.Fatal(err) } fakeRT := &FakeRoundTripper{message: body, status: http.StatusOK} client := newTestClient(fakeRT) name := "tardis" volume, err := client.InspectVolume(name) if err != nil { t.Fatal(err) } if !reflect.DeepEqual(volume, &expected) { t.Errorf("InspectVolume: Wrong return value. Want %#v. Got %#v.", expected, volume) } req := fakeRT.requests[0] expectedMethod := http.MethodGet if req.Method != expectedMethod { t.Errorf("InspectVolume(%q): Wrong HTTP method. Want %s. Got %s.", name, expectedMethod, req.Method) } u, _ := url.Parse(client.getURL("/volumes/" + name)) if req.URL.Path != u.Path { t.Errorf("CreateVolume(%q): Wrong request path. Want %q. Got %q.", name, u.Path, req.URL.Path) } } func TestRemoveVolume(t *testing.T) { t.Parallel() name := "test" fakeRT := &FakeRoundTripper{message: "", status: http.StatusNoContent} client := newTestClient(fakeRT) if err := client.RemoveVolume(name); err != nil { t.Fatal(err) } req := fakeRT.requests[0] expectedMethod := http.MethodDelete if req.Method != expectedMethod { t.Errorf("RemoveVolume(%q): Wrong HTTP method. Want %s. Got %s.", name, expectedMethod, req.Method) } u, _ := url.Parse(client.getURL("/volumes/" + name)) if req.URL.Path != u.Path { t.Errorf("RemoveVolume(%q): Wrong request path. Want %q. Got %q.", name, u.Path, req.URL.Path) } } func TestRemoveVolumeWithOptions(t *testing.T) { t.Parallel() name := "test" fakeRT := &FakeRoundTripper{message: "", status: http.StatusNoContent} client := newTestClient(fakeRT) if err := client.RemoveVolumeWithOptions(RemoveVolumeOptions{ Name: name, Force: true, }); err != nil { t.Fatal(err) } req := fakeRT.requests[0] expectedMethod := http.MethodDelete if req.Method != expectedMethod { t.Errorf("RemoveVolume(%q): Wrong HTTP method. Want %s. Got %s.", name, expectedMethod, req.Method) } u, _ := url.Parse(client.getURL("/volumes/" + name + "?force=1")) if req.URL.RequestURI() != u.RequestURI() { t.Errorf("RemoveVolume(%q): Wrong request path. Want %q. Got %q.", name, u.RequestURI(), req.URL.RequestURI()) } } func TestRemoveVolumeNotFound(t *testing.T) { t.Parallel() client := newTestClient(&FakeRoundTripper{message: "no such volume", status: http.StatusNotFound}) if err := client.RemoveVolume("test:"); !errors.Is(err, ErrNoSuchVolume) { t.Errorf("RemoveVolume: wrong error. Want %#v. Got %#v.", ErrNoSuchVolume, err) } } func TestRemoveVolumeInternalError(t *testing.T) { t.Parallel() client := newTestClient(&FakeRoundTripper{message: "something went wrong", status: http.StatusInternalServerError}) if err := client.RemoveVolume("test:test"); err == nil { t.Error("RemoveVolume: unexpected error") } } func TestRemoveVolumeInUse(t *testing.T) { t.Parallel() client := newTestClient(&FakeRoundTripper{message: "volume in use and cannot be removed", status: http.StatusConflict}) if err := client.RemoveVolume("test:"); !errors.Is(err, ErrVolumeInUse) { t.Errorf("RemoveVolume: wrong error. Want %#v. Got %#v.", ErrVolumeInUse, err) } } func TestPruneVolumes(t *testing.T) { t.Parallel() results := `{ "VolumesDeleted": [ "a", "b", "c" ], "SpaceReclaimed": 123 }` expected := &PruneVolumesResults{} err := json.Unmarshal([]byte(results), expected) if err != nil { t.Fatal(err) } client := newTestClient(&FakeRoundTripper{message: results, status: http.StatusOK}) got, err := client.PruneVolumes(PruneVolumesOptions{}) if err != nil { t.Fatal(err) } if !reflect.DeepEqual(got, expected) { t.Errorf("PruneContainers: Expected %#v. Got %#v.", expected, got) } }