- Remove ParseLogDetails, this is not part of the client. Moved to docker/cli
- Deprecate ParseHost and replace with ParseHostURL
- Deprecate redundant IsErr helpers
Signed-off-by: Daniel Nephin <dnephin@docker.com>
| ... | ... |
@@ -1,10 +1,6 @@ |
| 1 | 1 |
/* |
| 2 | 2 |
Package client is a Go client for the Docker Engine API. |
| 3 | 3 |
|
| 4 |
-The "docker" command uses this package to communicate with the daemon. It can also |
|
| 5 |
-be used by your own Go applications to do anything the command-line interface does |
|
| 6 |
-- running containers, pulling images, managing swarms, etc. |
|
| 7 |
- |
|
| 8 | 4 |
For more information about the Engine API, see the documentation: |
| 9 | 5 |
https://docs.docker.com/engine/reference/api/ |
| 10 | 6 |
|
| ... | ... |
@@ -160,7 +156,7 @@ func NewEnvClient() (*Client, error) {
|
| 160 | 160 |
// highly recommended that you set a version or your client may break if the |
| 161 | 161 |
// server is upgraded. |
| 162 | 162 |
func NewClient(host string, version string, client *http.Client, httpHeaders map[string]string) (*Client, error) {
|
| 163 |
- proto, addr, basePath, err := ParseHost(host) |
|
| 163 |
+ hostURL, err := ParseHostURL(host) |
|
| 164 | 164 |
if err != nil {
|
| 165 | 165 |
return nil, err |
| 166 | 166 |
} |
| ... | ... |
@@ -171,7 +167,7 @@ func NewClient(host string, version string, client *http.Client, httpHeaders map |
| 171 | 171 |
} |
| 172 | 172 |
} else {
|
| 173 | 173 |
transport := new(http.Transport) |
| 174 |
- sockets.ConfigureTransport(transport, proto, addr) |
|
| 174 |
+ sockets.ConfigureTransport(transport, hostURL.Scheme, hostURL.Host) |
|
| 175 | 175 |
client = &http.Client{
|
| 176 | 176 |
Transport: transport, |
| 177 | 177 |
CheckRedirect: CheckRedirect, |
| ... | ... |
@@ -189,28 +185,24 @@ func NewClient(host string, version string, client *http.Client, httpHeaders map |
| 189 | 189 |
scheme = "https" |
| 190 | 190 |
} |
| 191 | 191 |
|
| 192 |
+ // TODO: store URL instead of proto/addr/basePath |
|
| 192 | 193 |
return &Client{
|
| 193 | 194 |
scheme: scheme, |
| 194 | 195 |
host: host, |
| 195 |
- proto: proto, |
|
| 196 |
- addr: addr, |
|
| 197 |
- basePath: basePath, |
|
| 196 |
+ proto: hostURL.Scheme, |
|
| 197 |
+ addr: hostURL.Host, |
|
| 198 |
+ basePath: hostURL.Path, |
|
| 198 | 199 |
client: client, |
| 199 | 200 |
version: version, |
| 200 | 201 |
customHTTPHeaders: httpHeaders, |
| 201 | 202 |
}, nil |
| 202 | 203 |
} |
| 203 | 204 |
|
| 204 |
-// Close ensures that transport.Client is closed |
|
| 205 |
-// especially needed while using NewClient with *http.Client = nil |
|
| 206 |
-// for example |
|
| 207 |
-// client.NewClient("unix:///var/run/docker.sock", nil, "v1.18", map[string]string{"User-Agent": "engine-api-cli-1.0"})
|
|
| 205 |
+// Close the transport used by the client |
|
| 208 | 206 |
func (cli *Client) Close() error {
|
| 209 |
- |
|
| 210 | 207 |
if t, ok := cli.client.Transport.(*http.Transport); ok {
|
| 211 | 208 |
t.CloseIdleConnections() |
| 212 | 209 |
} |
| 213 |
- |
|
| 214 | 210 |
return nil |
| 215 | 211 |
} |
| 216 | 212 |
|
| ... | ... |
@@ -234,23 +226,20 @@ func (cli *Client) getAPIPath(p string, query url.Values) string {
|
| 234 | 234 |
return u.String() |
| 235 | 235 |
} |
| 236 | 236 |
|
| 237 |
-// ClientVersion returns the version string associated with this |
|
| 238 |
-// instance of the Client. Note that this value can be changed |
|
| 239 |
-// via the DOCKER_API_VERSION env var. |
|
| 240 |
-// This operation doesn't acquire a mutex. |
|
| 237 |
+// ClientVersion returns the API version used by this client. |
|
| 241 | 238 |
func (cli *Client) ClientVersion() string {
|
| 242 | 239 |
return cli.version |
| 243 | 240 |
} |
| 244 | 241 |
|
| 245 |
-// NegotiateAPIVersion updates the version string associated with this |
|
| 246 |
-// instance of the Client to match the latest version the server supports |
|
| 242 |
+// NegotiateAPIVersion queries the API and updates the version to match the |
|
| 243 |
+// API version. Any errors are silently ignored. |
|
| 247 | 244 |
func (cli *Client) NegotiateAPIVersion(ctx context.Context) {
|
| 248 | 245 |
ping, _ := cli.Ping(ctx) |
| 249 | 246 |
cli.NegotiateAPIVersionPing(ping) |
| 250 | 247 |
} |
| 251 | 248 |
|
| 252 |
-// NegotiateAPIVersionPing updates the version string associated with this |
|
| 253 |
-// instance of the Client to match the latest version the server supports |
|
| 249 |
+// NegotiateAPIVersionPing updates the client version to match the Ping.APIVersion |
|
| 250 |
+// if the ping version is less than the default version. |
|
| 254 | 251 |
func (cli *Client) NegotiateAPIVersionPing(p types.Ping) {
|
| 255 | 252 |
if cli.manualOverride {
|
| 256 | 253 |
return |
| ... | ... |
@@ -272,17 +261,28 @@ func (cli *Client) NegotiateAPIVersionPing(p types.Ping) {
|
| 272 | 272 |
} |
| 273 | 273 |
} |
| 274 | 274 |
|
| 275 |
-// DaemonHost returns the host associated with this instance of the Client. |
|
| 276 |
-// This operation doesn't acquire a mutex. |
|
| 275 |
+// DaemonHost returns the host address used by the client |
|
| 277 | 276 |
func (cli *Client) DaemonHost() string {
|
| 278 | 277 |
return cli.host |
| 279 | 278 |
} |
| 280 | 279 |
|
| 281 |
-// ParseHost verifies that the given host strings is valid. |
|
| 280 |
+// ParseHost parses a url string, validates the strings is a host url, and returns |
|
| 281 |
+// the parsed host as: protocol, address, and base path |
|
| 282 |
+// Deprecated: use ParseHostURL |
|
| 282 | 283 |
func ParseHost(host string) (string, string, string, error) {
|
| 284 |
+ hostURL, err := ParseHostURL(host) |
|
| 285 |
+ if err != nil {
|
|
| 286 |
+ return "", "", "", err |
|
| 287 |
+ } |
|
| 288 |
+ return hostURL.Scheme, hostURL.Host, hostURL.Path, nil |
|
| 289 |
+} |
|
| 290 |
+ |
|
| 291 |
+// ParseHostURL parses a url string, validates the string is a host url, and |
|
| 292 |
+// returns the parsed URL |
|
| 293 |
+func ParseHostURL(host string) (*url.URL, error) {
|
|
| 283 | 294 |
protoAddrParts := strings.SplitN(host, "://", 2) |
| 284 | 295 |
if len(protoAddrParts) == 1 {
|
| 285 |
- return "", "", "", fmt.Errorf("unable to parse docker host `%s`", host)
|
|
| 296 |
+ return nil, fmt.Errorf("unable to parse docker host `%s`", host)
|
|
| 286 | 297 |
} |
| 287 | 298 |
|
| 288 | 299 |
var basePath string |
| ... | ... |
@@ -290,16 +290,19 @@ func ParseHost(host string) (string, string, string, error) {
|
| 290 | 290 |
if proto == "tcp" {
|
| 291 | 291 |
parsed, err := url.Parse("tcp://" + addr)
|
| 292 | 292 |
if err != nil {
|
| 293 |
- return "", "", "", err |
|
| 293 |
+ return nil, err |
|
| 294 | 294 |
} |
| 295 | 295 |
addr = parsed.Host |
| 296 | 296 |
basePath = parsed.Path |
| 297 | 297 |
} |
| 298 |
- return proto, addr, basePath, nil |
|
| 298 |
+ return &url.URL{
|
|
| 299 |
+ Scheme: proto, |
|
| 300 |
+ Host: addr, |
|
| 301 |
+ Path: basePath, |
|
| 302 |
+ }, nil |
|
| 299 | 303 |
} |
| 300 | 304 |
|
| 301 |
-// CustomHTTPHeaders returns the custom http headers associated with this |
|
| 302 |
-// instance of the Client. This operation doesn't acquire a mutex. |
|
| 305 |
+// CustomHTTPHeaders returns the custom http headers stored by the client. |
|
| 303 | 306 |
func (cli *Client) CustomHTTPHeaders() map[string]string {
|
| 304 | 307 |
m := make(map[string]string) |
| 305 | 308 |
for k, v := range cli.customHTTPHeaders {
|
| ... | ... |
@@ -308,8 +311,7 @@ func (cli *Client) CustomHTTPHeaders() map[string]string {
|
| 308 | 308 |
return m |
| 309 | 309 |
} |
| 310 | 310 |
|
| 311 |
-// SetCustomHTTPHeaders updates the custom http headers associated with this |
|
| 312 |
-// instance of the Client. This operation doesn't acquire a mutex. |
|
| 311 |
+// SetCustomHTTPHeaders that will be set on every HTTP request made by the client. |
|
| 313 | 312 |
func (cli *Client) SetCustomHTTPHeaders(headers map[string]string) {
|
| 314 | 313 |
cli.customHTTPHeaders = headers |
| 315 | 314 |
} |
| ... | ... |
@@ -11,6 +11,7 @@ import ( |
| 11 | 11 |
|
| 12 | 12 |
"github.com/docker/docker/api" |
| 13 | 13 |
"github.com/docker/docker/api/types" |
| 14 |
+ "github.com/docker/docker/internal/testutil" |
|
| 14 | 15 |
"github.com/stretchr/testify/assert" |
| 15 | 16 |
) |
| 16 | 17 |
|
| ... | ... |
@@ -152,7 +153,6 @@ func TestParseHost(t *testing.T) {
|
| 152 | 152 |
|
| 153 | 153 |
for _, cs := range cases {
|
| 154 | 154 |
p, a, b, e := ParseHost(cs.host) |
| 155 |
- // if we expected an error to be returned... |
|
| 156 | 155 |
if cs.err {
|
| 157 | 156 |
assert.Error(t, e) |
| 158 | 157 |
} |
| ... | ... |
@@ -162,6 +162,43 @@ func TestParseHost(t *testing.T) {
|
| 162 | 162 |
} |
| 163 | 163 |
} |
| 164 | 164 |
|
| 165 |
+func TestParseHostURL(t *testing.T) {
|
|
| 166 |
+ testcases := []struct {
|
|
| 167 |
+ host string |
|
| 168 |
+ expected *url.URL |
|
| 169 |
+ expectedErr string |
|
| 170 |
+ }{
|
|
| 171 |
+ {
|
|
| 172 |
+ host: "", |
|
| 173 |
+ expectedErr: "unable to parse docker host", |
|
| 174 |
+ }, |
|
| 175 |
+ {
|
|
| 176 |
+ host: "foobar", |
|
| 177 |
+ expectedErr: "unable to parse docker host", |
|
| 178 |
+ }, |
|
| 179 |
+ {
|
|
| 180 |
+ host: "foo://bar", |
|
| 181 |
+ expected: &url.URL{Scheme: "foo", Host: "bar"},
|
|
| 182 |
+ }, |
|
| 183 |
+ {
|
|
| 184 |
+ host: "tcp://localhost:2476", |
|
| 185 |
+ expected: &url.URL{Scheme: "tcp", Host: "localhost:2476"},
|
|
| 186 |
+ }, |
|
| 187 |
+ {
|
|
| 188 |
+ host: "tcp://localhost:2476/path", |
|
| 189 |
+ expected: &url.URL{Scheme: "tcp", Host: "localhost:2476", Path: "/path"},
|
|
| 190 |
+ }, |
|
| 191 |
+ } |
|
| 192 |
+ |
|
| 193 |
+ for _, testcase := range testcases {
|
|
| 194 |
+ actual, err := ParseHostURL(testcase.host) |
|
| 195 |
+ if testcase.expectedErr != "" {
|
|
| 196 |
+ testutil.ErrorContains(t, err, testcase.expectedErr) |
|
| 197 |
+ } |
|
| 198 |
+ assert.Equal(t, testcase.expected, actual) |
|
| 199 |
+ } |
|
| 200 |
+} |
|
| 201 |
+ |
|
| 165 | 202 |
func TestNewEnvClientSetsDefaultVersion(t *testing.T) {
|
| 166 | 203 |
env := envToMap() |
| 167 | 204 |
defer mapToEnv(env) |
| ... | ... |
@@ -36,8 +36,8 @@ type notFound interface {
|
| 36 | 36 |
NotFound() bool // Is the error a NotFound error |
| 37 | 37 |
} |
| 38 | 38 |
|
| 39 |
-// IsErrNotFound returns true if the error is caused with an |
|
| 40 |
-// object (image, container, network, volume, …) is not found in the docker host. |
|
| 39 |
+// IsErrNotFound returns true if the error is a NotFound error, which is returned |
|
| 40 |
+// by the API when some object is not found. |
|
| 41 | 41 |
func IsErrNotFound(err error) bool {
|
| 42 | 42 |
te, ok := err.(notFound) |
| 43 | 43 |
return ok && te.NotFound() |
| ... | ... |
@@ -60,6 +60,8 @@ func (e imageNotFoundError) Error() string {
|
| 60 | 60 |
|
| 61 | 61 |
// IsErrImageNotFound returns true if the error is caused |
| 62 | 62 |
// when an image is not found in the docker host. |
| 63 |
+// |
|
| 64 |
+// Deprecated: Use IsErrNotFound |
|
| 63 | 65 |
func IsErrImageNotFound(err error) bool {
|
| 64 | 66 |
return IsErrNotFound(err) |
| 65 | 67 |
} |
| ... | ... |
@@ -81,6 +83,8 @@ func (e containerNotFoundError) Error() string {
|
| 81 | 81 |
|
| 82 | 82 |
// IsErrContainerNotFound returns true if the error is caused |
| 83 | 83 |
// when a container is not found in the docker host. |
| 84 |
+// |
|
| 85 |
+// Deprecated: Use IsErrNotFound |
|
| 84 | 86 |
func IsErrContainerNotFound(err error) bool {
|
| 85 | 87 |
return IsErrNotFound(err) |
| 86 | 88 |
} |
| ... | ... |
@@ -102,6 +106,8 @@ func (e networkNotFoundError) Error() string {
|
| 102 | 102 |
|
| 103 | 103 |
// IsErrNetworkNotFound returns true if the error is caused |
| 104 | 104 |
// when a network is not found in the docker host. |
| 105 |
+// |
|
| 106 |
+// Deprecated: Use IsErrNotFound |
|
| 105 | 107 |
func IsErrNetworkNotFound(err error) bool {
|
| 106 | 108 |
return IsErrNotFound(err) |
| 107 | 109 |
} |
| ... | ... |
@@ -123,6 +129,8 @@ func (e volumeNotFoundError) Error() string {
|
| 123 | 123 |
|
| 124 | 124 |
// IsErrVolumeNotFound returns true if the error is caused |
| 125 | 125 |
// when a volume is not found in the docker host. |
| 126 |
+// |
|
| 127 |
+// Deprecated: Use IsErrNotFound |
|
| 126 | 128 |
func IsErrVolumeNotFound(err error) bool {
|
| 127 | 129 |
return IsErrNotFound(err) |
| 128 | 130 |
} |
| ... | ... |
@@ -161,6 +169,8 @@ func (e nodeNotFoundError) NotFound() bool {
|
| 161 | 161 |
|
| 162 | 162 |
// IsErrNodeNotFound returns true if the error is caused |
| 163 | 163 |
// when a node is not found. |
| 164 |
+// |
|
| 165 |
+// Deprecated: Use IsErrNotFound |
|
| 164 | 166 |
func IsErrNodeNotFound(err error) bool {
|
| 165 | 167 |
_, ok := err.(nodeNotFoundError) |
| 166 | 168 |
return ok |
| ... | ... |
@@ -183,6 +193,8 @@ func (e serviceNotFoundError) NotFound() bool {
|
| 183 | 183 |
|
| 184 | 184 |
// IsErrServiceNotFound returns true if the error is caused |
| 185 | 185 |
// when a service is not found. |
| 186 |
+// |
|
| 187 |
+// Deprecated: Use IsErrNotFound |
|
| 186 | 188 |
func IsErrServiceNotFound(err error) bool {
|
| 187 | 189 |
_, ok := err.(serviceNotFoundError) |
| 188 | 190 |
return ok |
| ... | ... |
@@ -205,6 +217,8 @@ func (e taskNotFoundError) NotFound() bool {
|
| 205 | 205 |
|
| 206 | 206 |
// IsErrTaskNotFound returns true if the error is caused |
| 207 | 207 |
// when a task is not found. |
| 208 |
+// |
|
| 209 |
+// Deprecated: Use IsErrNotFound |
|
| 208 | 210 |
func IsErrTaskNotFound(err error) bool {
|
| 209 | 211 |
_, ok := err.(taskNotFoundError) |
| 210 | 212 |
return ok |
| ... | ... |
@@ -251,6 +265,8 @@ func (e secretNotFoundError) NotFound() bool {
|
| 251 | 251 |
|
| 252 | 252 |
// IsErrSecretNotFound returns true if the error is caused |
| 253 | 253 |
// when a secret is not found. |
| 254 |
+// |
|
| 255 |
+// Deprecated: Use IsErrNotFound |
|
| 254 | 256 |
func IsErrSecretNotFound(err error) bool {
|
| 255 | 257 |
_, ok := err.(secretNotFoundError) |
| 256 | 258 |
return ok |
| ... | ... |
@@ -273,6 +289,8 @@ func (e configNotFoundError) NotFound() bool {
|
| 273 | 273 |
|
| 274 | 274 |
// IsErrConfigNotFound returns true if the error is caused |
| 275 | 275 |
// when a config is not found. |
| 276 |
+// |
|
| 277 |
+// Deprecated: Use IsErrNotFound |
|
| 276 | 278 |
func IsErrConfigNotFound(err error) bool {
|
| 277 | 279 |
_, ok := err.(configNotFoundError) |
| 278 | 280 |
return ok |
| ... | ... |
@@ -295,6 +313,8 @@ func (e pluginNotFoundError) Error() string {
|
| 295 | 295 |
|
| 296 | 296 |
// IsErrPluginNotFound returns true if the error is caused |
| 297 | 297 |
// when a plugin is not found in the docker host. |
| 298 |
+// |
|
| 299 |
+// Deprecated: Use IsErrNotFound |
|
| 298 | 300 |
func IsErrPluginNotFound(err error) bool {
|
| 299 | 301 |
return IsErrNotFound(err) |
| 300 | 302 |
} |
| 301 | 303 |
deleted file mode 100644 |
| ... | ... |
@@ -1,41 +0,0 @@ |
| 1 |
-package client |
|
| 2 |
- |
|
| 3 |
-// parse_logs.go contains utility helpers for getting information out of docker |
|
| 4 |
-// log lines. really, it only contains ParseDetails right now. maybe in the |
|
| 5 |
-// future there will be some desire to parse log messages back into a struct? |
|
| 6 |
-// that would go here if we did |
|
| 7 |
- |
|
| 8 |
-import ( |
|
| 9 |
- "net/url" |
|
| 10 |
- "strings" |
|
| 11 |
- |
|
| 12 |
- "github.com/pkg/errors" |
|
| 13 |
-) |
|
| 14 |
- |
|
| 15 |
-// ParseLogDetails takes a details string of key value pairs in the form |
|
| 16 |
-// "k=v,l=w", where the keys and values are url query escaped, and each pair |
|
| 17 |
-// is separated by a comma, returns a map. returns an error if the details |
|
| 18 |
-// string is not in a valid format |
|
| 19 |
-// the exact form of details encoding is implemented in |
|
| 20 |
-// api/server/httputils/write_log_stream.go |
|
| 21 |
-func ParseLogDetails(details string) (map[string]string, error) {
|
|
| 22 |
- pairs := strings.Split(details, ",") |
|
| 23 |
- detailsMap := make(map[string]string, len(pairs)) |
|
| 24 |
- for _, pair := range pairs {
|
|
| 25 |
- p := strings.SplitN(pair, "=", 2) |
|
| 26 |
- // if there is no equals sign, we will only get 1 part back |
|
| 27 |
- if len(p) != 2 {
|
|
| 28 |
- return nil, errors.New("invalid details format")
|
|
| 29 |
- } |
|
| 30 |
- k, err := url.QueryUnescape(p[0]) |
|
| 31 |
- if err != nil {
|
|
| 32 |
- return nil, err |
|
| 33 |
- } |
|
| 34 |
- v, err := url.QueryUnescape(p[1]) |
|
| 35 |
- if err != nil {
|
|
| 36 |
- return nil, err |
|
| 37 |
- } |
|
| 38 |
- detailsMap[k] = v |
|
| 39 |
- } |
|
| 40 |
- return detailsMap, nil |
|
| 41 |
-} |
| 42 | 1 |
deleted file mode 100644 |
| ... | ... |
@@ -1,36 +0,0 @@ |
| 1 |
-package client |
|
| 2 |
- |
|
| 3 |
-import ( |
|
| 4 |
- "reflect" |
|
| 5 |
- "testing" |
|
| 6 |
- |
|
| 7 |
- "github.com/pkg/errors" |
|
| 8 |
-) |
|
| 9 |
- |
|
| 10 |
-func TestParseLogDetails(t *testing.T) {
|
|
| 11 |
- testCases := []struct {
|
|
| 12 |
- line string |
|
| 13 |
- expected map[string]string |
|
| 14 |
- err error |
|
| 15 |
- }{
|
|
| 16 |
- {"key=value", map[string]string{"key": "value"}, nil},
|
|
| 17 |
- {"key1=value1,key2=value2", map[string]string{"key1": "value1", "key2": "value2"}, nil},
|
|
| 18 |
- {"key+with+spaces=value%3Dequals,asdf%2C=", map[string]string{"key with spaces": "value=equals", "asdf,": ""}, nil},
|
|
| 19 |
- {"key=,=nothing", map[string]string{"key": "", "": "nothing"}, nil},
|
|
| 20 |
- {"=", map[string]string{"": ""}, nil},
|
|
| 21 |
- {"errors", nil, errors.New("invalid details format")},
|
|
| 22 |
- } |
|
| 23 |
- for _, tc := range testCases {
|
|
| 24 |
- tc := tc // capture range variable |
|
| 25 |
- t.Run(tc.line, func(t *testing.T) {
|
|
| 26 |
- t.Parallel() |
|
| 27 |
- res, err := ParseLogDetails(tc.line) |
|
| 28 |
- if err != nil && (err.Error() != tc.err.Error()) {
|
|
| 29 |
- t.Fatalf("unexpected error parsing logs:\nExpected:\n\t%v\nActual:\n\t%v", tc.err, err)
|
|
| 30 |
- } |
|
| 31 |
- if !reflect.DeepEqual(tc.expected, res) {
|
|
| 32 |
- t.Errorf("result does not match expected:\nExpected:\n\t%#v\nActual:\n\t%#v", tc.expected, res)
|
|
| 33 |
- } |
|
| 34 |
- }) |
|
| 35 |
- } |
|
| 36 |
-} |