Merge pull request #45923 from thaJeztah/client_header

client: remove custom "headers" type (use http.Header), and omit "version" header on API >= 1.30
This commit is contained in:
Bjorn Neergaard 2023-07-11 14:30:20 -06:00 committed by GitHub
commit ebcb230cff
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 83 additions and 62 deletions

View file

@ -2,6 +2,7 @@ package client // import "github.com/docker/docker/client"
import ( import (
"context" "context"
"net/http"
"net/url" "net/url"
"github.com/docker/docker/api/types" "github.com/docker/docker/api/types"
@ -52,8 +53,7 @@ func (cli *Client) ContainerAttach(ctx context.Context, container string, option
query.Set("logs", "1") query.Set("logs", "1")
} }
headers := map[string][]string{ return cli.postHijacked(ctx, "/containers/"+container+"/attach", query, nil, http.Header{
"Content-Type": {"text/plain"}, "Content-Type": {"text/plain"},
} })
return cli.postHijacked(ctx, "/containers/"+container+"/attach", query, nil, headers)
} }

View file

@ -3,6 +3,7 @@ package client // import "github.com/docker/docker/client"
import ( import (
"context" "context"
"encoding/json" "encoding/json"
"net/http"
"github.com/docker/docker/api/types" "github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/versions" "github.com/docker/docker/api/types/versions"
@ -46,10 +47,9 @@ func (cli *Client) ContainerExecAttach(ctx context.Context, execID string, confi
if versions.LessThan(cli.ClientVersion(), "1.42") { if versions.LessThan(cli.ClientVersion(), "1.42") {
config.ConsoleSize = nil config.ConsoleSize = nil
} }
headers := map[string][]string{ return cli.postHijacked(ctx, "/exec/"+execID+"/start", nil, config, http.Header{
"Content-Type": {"application/json"}, "Content-Type": {"application/json"},
} })
return cli.postHijacked(ctx, "/exec/"+execID+"/start", nil, config, headers)
} }
// ContainerExecInspect returns information about a specific exec process on the docker host. // ContainerExecInspect returns information about a specific exec process on the docker host.

View file

@ -3,6 +3,7 @@ package client // import "github.com/docker/docker/client"
import ( import (
"context" "context"
"encoding/json" "encoding/json"
"net/http"
"net/url" "net/url"
"github.com/docker/docker/api/types/registry" "github.com/docker/docker/api/types/registry"
@ -19,10 +20,10 @@ func (cli *Client) DistributionInspect(ctx context.Context, image, encodedRegist
if err := cli.NewVersionError("1.30", "distribution inspect"); err != nil { if err := cli.NewVersionError("1.30", "distribution inspect"); err != nil {
return distributionInspect, err return distributionInspect, err
} }
var headers map[string][]string
var headers http.Header
if encodedRegistryAuth != "" { if encodedRegistryAuth != "" {
headers = map[string][]string{ headers = http.Header{
registry.AuthHeader: {encodedRegistryAuth}, registry.AuthHeader: {encodedRegistryAuth},
} }
} }

View file

@ -23,13 +23,13 @@ func (cli *Client) ImageBuild(ctx context.Context, buildContext io.Reader, optio
return types.ImageBuildResponse{}, err return types.ImageBuildResponse{}, err
} }
headers := http.Header(make(map[string][]string))
buf, err := json.Marshal(options.AuthConfigs) buf, err := json.Marshal(options.AuthConfigs)
if err != nil { if err != nil {
return types.ImageBuildResponse{}, err return types.ImageBuildResponse{}, err
} }
headers.Add("X-Registry-Config", base64.URLEncoding.EncodeToString(buf))
headers := http.Header{}
headers.Add("X-Registry-Config", base64.URLEncoding.EncodeToString(buf))
headers.Set("Content-Type", "application/x-tar") headers.Set("Content-Type", "application/x-tar")
serverResp, err := cli.postRaw(ctx, "/build", query, buildContext, headers) serverResp, err := cli.postRaw(ctx, "/build", query, buildContext, headers)
@ -37,11 +37,9 @@ func (cli *Client) ImageBuild(ctx context.Context, buildContext io.Reader, optio
return types.ImageBuildResponse{}, err return types.ImageBuildResponse{}, err
} }
osType := getDockerOS(serverResp.header.Get("Server"))
return types.ImageBuildResponse{ return types.ImageBuildResponse{
Body: serverResp.body, Body: serverResp.body,
OSType: osType, OSType: getDockerOS(serverResp.header.Get("Server")),
}, nil }, nil
} }

View file

@ -3,6 +3,7 @@ package client // import "github.com/docker/docker/client"
import ( import (
"context" "context"
"io" "io"
"net/http"
"net/url" "net/url"
"strings" "strings"
@ -33,6 +34,7 @@ func (cli *Client) ImageCreate(ctx context.Context, parentReference string, opti
} }
func (cli *Client) tryImageCreate(ctx context.Context, query url.Values, registryAuth string) (serverResponse, error) { func (cli *Client) tryImageCreate(ctx context.Context, query url.Values, registryAuth string) (serverResponse, error) {
headers := map[string][]string{registry.AuthHeader: {registryAuth}} return cli.post(ctx, "/images/create", query, nil, http.Header{
return cli.post(ctx, "/images/create", query, nil, headers) registry.AuthHeader: {registryAuth},
})
} }

View file

@ -3,6 +3,7 @@ package client // import "github.com/docker/docker/client"
import ( import (
"context" "context"
"io" "io"
"net/http"
"net/url" "net/url"
"github.com/docker/docker/api/types" "github.com/docker/docker/api/types"
@ -17,8 +18,9 @@ func (cli *Client) ImageLoad(ctx context.Context, input io.Reader, quiet bool) (
if quiet { if quiet {
v.Set("quiet", "1") v.Set("quiet", "1")
} }
headers := map[string][]string{"Content-Type": {"application/x-tar"}} resp, err := cli.postRaw(ctx, "/images/load", v, input, http.Header{
resp, err := cli.postRaw(ctx, "/images/load", v, input, headers) "Content-Type": {"application/x-tar"},
})
if err != nil { if err != nil {
return types.ImageLoadResponse{}, err return types.ImageLoadResponse{}, err
} }

View file

@ -4,6 +4,7 @@ import (
"context" "context"
"errors" "errors"
"io" "io"
"net/http"
"net/url" "net/url"
"github.com/docker/distribution/reference" "github.com/docker/distribution/reference"
@ -50,6 +51,7 @@ func (cli *Client) ImagePush(ctx context.Context, image string, options types.Im
} }
func (cli *Client) tryImagePush(ctx context.Context, imageID string, query url.Values, registryAuth string) (serverResponse, error) { func (cli *Client) tryImagePush(ctx context.Context, imageID string, query url.Values, registryAuth string) (serverResponse, error) {
headers := map[string][]string{registry.AuthHeader: {registryAuth}} return cli.post(ctx, "/images/"+imageID+"/push", query, nil, http.Header{
return cli.post(ctx, "/images/"+imageID+"/push", query, nil, headers) registry.AuthHeader: {registryAuth},
})
} }

View file

@ -3,6 +3,7 @@ package client // import "github.com/docker/docker/client"
import ( import (
"context" "context"
"encoding/json" "encoding/json"
"net/http"
"net/url" "net/url"
"strconv" "strconv"
@ -48,6 +49,7 @@ func (cli *Client) ImageSearch(ctx context.Context, term string, options types.I
} }
func (cli *Client) tryImageSearch(ctx context.Context, query url.Values, registryAuth string) (serverResponse, error) { func (cli *Client) tryImageSearch(ctx context.Context, query url.Values, registryAuth string) (serverResponse, error) {
headers := map[string][]string{registry.AuthHeader: {registryAuth}} return cli.get(ctx, "/images/search", query, http.Header{
return cli.get(ctx, "/images/search", query, headers) registry.AuthHeader: {registryAuth},
})
} }

View file

@ -4,6 +4,7 @@ import (
"context" "context"
"encoding/json" "encoding/json"
"io" "io"
"net/http"
"net/url" "net/url"
"github.com/docker/distribution/reference" "github.com/docker/distribution/reference"
@ -68,13 +69,15 @@ func (cli *Client) PluginInstall(ctx context.Context, name string, options types
} }
func (cli *Client) tryPluginPrivileges(ctx context.Context, query url.Values, registryAuth string) (serverResponse, error) { func (cli *Client) tryPluginPrivileges(ctx context.Context, query url.Values, registryAuth string) (serverResponse, error) {
headers := map[string][]string{registry.AuthHeader: {registryAuth}} return cli.get(ctx, "/plugins/privileges", query, http.Header{
return cli.get(ctx, "/plugins/privileges", query, headers) registry.AuthHeader: {registryAuth},
})
} }
func (cli *Client) tryPluginPull(ctx context.Context, query url.Values, privileges types.PluginPrivileges, registryAuth string) (serverResponse, error) { func (cli *Client) tryPluginPull(ctx context.Context, query url.Values, privileges types.PluginPrivileges, registryAuth string) (serverResponse, error) {
headers := map[string][]string{registry.AuthHeader: {registryAuth}} return cli.post(ctx, "/plugins/pull", query, privileges, http.Header{
return cli.post(ctx, "/plugins/pull", query, privileges, headers) registry.AuthHeader: {registryAuth},
})
} }
func (cli *Client) checkPluginPermissions(ctx context.Context, query url.Values, options types.PluginInstallOptions) (types.PluginPrivileges, error) { func (cli *Client) checkPluginPermissions(ctx context.Context, query url.Values, options types.PluginInstallOptions) (types.PluginPrivileges, error) {

View file

@ -3,14 +3,16 @@ package client // import "github.com/docker/docker/client"
import ( import (
"context" "context"
"io" "io"
"net/http"
"github.com/docker/docker/api/types/registry" "github.com/docker/docker/api/types/registry"
) )
// PluginPush pushes a plugin to a registry // PluginPush pushes a plugin to a registry
func (cli *Client) PluginPush(ctx context.Context, name string, registryAuth string) (io.ReadCloser, error) { func (cli *Client) PluginPush(ctx context.Context, name string, registryAuth string) (io.ReadCloser, error) {
headers := map[string][]string{registry.AuthHeader: {registryAuth}} resp, err := cli.post(ctx, "/plugins/"+name+"/push", nil, nil, http.Header{
resp, err := cli.post(ctx, "/plugins/"+name+"/push", nil, nil, headers) registry.AuthHeader: {registryAuth},
})
if err != nil { if err != nil {
return nil, err return nil, err
} }

View file

@ -3,6 +3,7 @@ package client // import "github.com/docker/docker/client"
import ( import (
"context" "context"
"io" "io"
"net/http"
"net/url" "net/url"
"github.com/docker/distribution/reference" "github.com/docker/distribution/reference"
@ -35,6 +36,7 @@ func (cli *Client) PluginUpgrade(ctx context.Context, name string, options types
} }
func (cli *Client) tryPluginUpgrade(ctx context.Context, query url.Values, privileges types.PluginPrivileges, name, registryAuth string) (serverResponse, error) { func (cli *Client) tryPluginUpgrade(ctx context.Context, query url.Values, privileges types.PluginPrivileges, name, registryAuth string) (serverResponse, error) {
headers := map[string][]string{registry.AuthHeader: {registryAuth}} return cli.post(ctx, "/plugins/"+name+"/upgrade", query, privileges, http.Header{
return cli.post(ctx, "/plugins/"+name+"/upgrade", query, privileges, headers) registry.AuthHeader: {registryAuth},
})
} }

View file

@ -28,17 +28,17 @@ type serverResponse struct {
} }
// head sends an http request to the docker API using the method HEAD. // head sends an http request to the docker API using the method HEAD.
func (cli *Client) head(ctx context.Context, path string, query url.Values, headers map[string][]string) (serverResponse, error) { func (cli *Client) head(ctx context.Context, path string, query url.Values, headers http.Header) (serverResponse, error) {
return cli.sendRequest(ctx, http.MethodHead, path, query, nil, headers) return cli.sendRequest(ctx, http.MethodHead, path, query, nil, headers)
} }
// get sends an http request to the docker API using the method GET with a specific Go context. // get sends an http request to the docker API using the method GET with a specific Go context.
func (cli *Client) get(ctx context.Context, path string, query url.Values, headers map[string][]string) (serverResponse, error) { func (cli *Client) get(ctx context.Context, path string, query url.Values, headers http.Header) (serverResponse, error) {
return cli.sendRequest(ctx, http.MethodGet, path, query, nil, headers) return cli.sendRequest(ctx, http.MethodGet, path, query, nil, headers)
} }
// post sends an http request to the docker API using the method POST with a specific Go context. // post sends an http request to the docker API using the method POST with a specific Go context.
func (cli *Client) post(ctx context.Context, path string, query url.Values, obj interface{}, headers map[string][]string) (serverResponse, error) { func (cli *Client) post(ctx context.Context, path string, query url.Values, obj interface{}, headers http.Header) (serverResponse, error) {
body, headers, err := encodeBody(obj, headers) body, headers, err := encodeBody(obj, headers)
if err != nil { if err != nil {
return serverResponse{}, err return serverResponse{}, err
@ -46,11 +46,11 @@ func (cli *Client) post(ctx context.Context, path string, query url.Values, obj
return cli.sendRequest(ctx, http.MethodPost, path, query, body, headers) return cli.sendRequest(ctx, http.MethodPost, path, query, body, headers)
} }
func (cli *Client) postRaw(ctx context.Context, path string, query url.Values, body io.Reader, headers map[string][]string) (serverResponse, error) { func (cli *Client) postRaw(ctx context.Context, path string, query url.Values, body io.Reader, headers http.Header) (serverResponse, error) {
return cli.sendRequest(ctx, http.MethodPost, path, query, body, headers) return cli.sendRequest(ctx, http.MethodPost, path, query, body, headers)
} }
func (cli *Client) put(ctx context.Context, path string, query url.Values, obj interface{}, headers map[string][]string) (serverResponse, error) { func (cli *Client) put(ctx context.Context, path string, query url.Values, obj interface{}, headers http.Header) (serverResponse, error) {
body, headers, err := encodeBody(obj, headers) body, headers, err := encodeBody(obj, headers)
if err != nil { if err != nil {
return serverResponse{}, err return serverResponse{}, err
@ -59,7 +59,7 @@ func (cli *Client) put(ctx context.Context, path string, query url.Values, obj i
} }
// putRaw sends an http request to the docker API using the method PUT. // putRaw sends an http request to the docker API using the method PUT.
func (cli *Client) putRaw(ctx context.Context, path string, query url.Values, body io.Reader, headers map[string][]string) (serverResponse, error) { func (cli *Client) putRaw(ctx context.Context, path string, query url.Values, body io.Reader, headers http.Header) (serverResponse, error) {
// PUT requests are expected to always have a body (apparently) // PUT requests are expected to always have a body (apparently)
// so explicitly pass an empty body to sendRequest to signal that // so explicitly pass an empty body to sendRequest to signal that
// it should set the Content-Type header if not already present. // it should set the Content-Type header if not already present.
@ -70,13 +70,11 @@ func (cli *Client) putRaw(ctx context.Context, path string, query url.Values, bo
} }
// delete sends an http request to the docker API using the method DELETE. // delete sends an http request to the docker API using the method DELETE.
func (cli *Client) delete(ctx context.Context, path string, query url.Values, headers map[string][]string) (serverResponse, error) { func (cli *Client) delete(ctx context.Context, path string, query url.Values, headers http.Header) (serverResponse, error) {
return cli.sendRequest(ctx, http.MethodDelete, path, query, nil, headers) return cli.sendRequest(ctx, http.MethodDelete, path, query, nil, headers)
} }
type headers map[string][]string func encodeBody(obj interface{}, headers http.Header) (io.Reader, http.Header, error) {
func encodeBody(obj interface{}, headers headers) (io.Reader, headers, error) {
if obj == nil { if obj == nil {
return nil, headers, nil return nil, headers, nil
} }
@ -98,7 +96,7 @@ func encodeBody(obj interface{}, headers headers) (io.Reader, headers, error) {
return body, headers, nil return body, headers, nil
} }
func (cli *Client) buildRequest(method, path string, body io.Reader, headers headers) (*http.Request, error) { func (cli *Client) buildRequest(method, path string, body io.Reader, headers http.Header) (*http.Request, error) {
req, err := http.NewRequest(method, path, body) req, err := http.NewRequest(method, path, body)
if err != nil { if err != nil {
return nil, err return nil, err
@ -123,7 +121,7 @@ func (cli *Client) buildRequest(method, path string, body io.Reader, headers hea
return req, nil return req, nil
} }
func (cli *Client) sendRequest(ctx context.Context, method, path string, query url.Values, body io.Reader, headers headers) (serverResponse, error) { func (cli *Client) sendRequest(ctx context.Context, method, path string, query url.Values, body io.Reader, headers http.Header) (serverResponse, error) {
req, err := cli.buildRequest(method, cli.getAPIPath(ctx, path, query), body, headers) req, err := cli.buildRequest(method, cli.getAPIPath(ctx, path, query), body, headers)
if err != nil { if err != nil {
return serverResponse{}, err return serverResponse{}, err
@ -161,19 +159,19 @@ func (cli *Client) doRequest(ctx context.Context, req *http.Request) (serverResp
return serverResp, err return serverResp, err
} }
if nErr, ok := err.(*url.Error); ok { if uErr, ok := err.(*url.Error); ok {
if nErr, ok := nErr.Err.(*net.OpError); ok { if nErr, ok := uErr.Err.(*net.OpError); ok {
if os.IsPermission(nErr.Err) { if os.IsPermission(nErr.Err) {
return serverResp, errors.Wrapf(err, "permission denied while trying to connect to the Docker daemon socket at %v", cli.host) return serverResp, errors.Wrapf(err, "permission denied while trying to connect to the Docker daemon socket at %v", cli.host)
} }
} }
} }
if err, ok := err.(net.Error); ok { if nErr, ok := err.(net.Error); ok {
if err.Timeout() { if nErr.Timeout() {
return serverResp, ErrorConnectionFailed(cli.host) return serverResp, ErrorConnectionFailed(cli.host)
} }
if strings.Contains(err.Error(), "connection refused") || strings.Contains(err.Error(), "dial unix") { if strings.Contains(nErr.Error(), "connection refused") || strings.Contains(nErr.Error(), "dial unix") {
return serverResp, ErrorConnectionFailed(cli.host) return serverResp, ErrorConnectionFailed(cli.host)
} }
} }
@ -253,7 +251,7 @@ func (cli *Client) checkResponseErr(serverResp serverResponse) error {
return errors.Wrap(errors.New(errorMessage), "Error response from daemon") return errors.Wrap(errors.New(errorMessage), "Error response from daemon")
} }
func (cli *Client) addHeaders(req *http.Request, headers headers) *http.Request { func (cli *Client) addHeaders(req *http.Request, headers http.Header) *http.Request {
// Add CLI Config's HTTP Headers BEFORE we set the Docker headers // Add CLI Config's HTTP Headers BEFORE we set the Docker headers
// then the user can't change OUR headers // then the user can't change OUR headers
for k, v := range cli.customHTTPHeaders { for k, v := range cli.customHTTPHeaders {

View file

@ -4,12 +4,14 @@ import (
"context" "context"
"encoding/json" "encoding/json"
"fmt" "fmt"
"net/http"
"strings" "strings"
"github.com/docker/distribution/reference" "github.com/docker/distribution/reference"
"github.com/docker/docker/api/types" "github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/registry" "github.com/docker/docker/api/types/registry"
"github.com/docker/docker/api/types/swarm" "github.com/docker/docker/api/types/swarm"
"github.com/docker/docker/api/types/versions"
"github.com/opencontainers/go-digest" "github.com/opencontainers/go-digest"
"github.com/pkg/errors" "github.com/pkg/errors"
) )
@ -17,13 +19,6 @@ import (
// ServiceCreate creates a new service. // ServiceCreate creates a new service.
func (cli *Client) ServiceCreate(ctx context.Context, service swarm.ServiceSpec, options types.ServiceCreateOptions) (types.ServiceCreateResponse, error) { func (cli *Client) ServiceCreate(ctx context.Context, service swarm.ServiceSpec, options types.ServiceCreateOptions) (types.ServiceCreateResponse, error) {
var response types.ServiceCreateResponse var response types.ServiceCreateResponse
headers := map[string][]string{
"version": {cli.version},
}
if options.EncodedRegistryAuth != "" {
headers[registry.AuthHeader] = []string{options.EncodedRegistryAuth}
}
// Make sure containerSpec is not nil when no runtime is set or the runtime is set to container // Make sure containerSpec is not nil when no runtime is set or the runtime is set to container
if service.TaskTemplate.ContainerSpec == nil && (service.TaskTemplate.Runtime == "" || service.TaskTemplate.Runtime == swarm.RuntimeContainer) { if service.TaskTemplate.ContainerSpec == nil && (service.TaskTemplate.Runtime == "" || service.TaskTemplate.Runtime == swarm.RuntimeContainer) {
@ -53,6 +48,16 @@ func (cli *Client) ServiceCreate(ctx context.Context, service swarm.ServiceSpec,
} }
} }
headers := http.Header{}
if versions.LessThan(cli.version, "1.30") {
// the custom "version" header was used by engine API before 20.10
// (API 1.30) to switch between client- and server-side lookup of
// image digests.
headers["version"] = []string{cli.version}
}
if options.EncodedRegistryAuth != "" {
headers[registry.AuthHeader] = []string{options.EncodedRegistryAuth}
}
resp, err := cli.post(ctx, "/services/create", nil, service, headers) resp, err := cli.post(ctx, "/services/create", nil, service, headers)
defer ensureReaderClosed(resp) defer ensureReaderClosed(resp)
if err != nil { if err != nil {

View file

@ -3,11 +3,13 @@ package client // import "github.com/docker/docker/client"
import ( import (
"context" "context"
"encoding/json" "encoding/json"
"net/http"
"net/url" "net/url"
"github.com/docker/docker/api/types" "github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/registry" "github.com/docker/docker/api/types/registry"
"github.com/docker/docker/api/types/swarm" "github.com/docker/docker/api/types/swarm"
"github.com/docker/docker/api/types/versions"
) )
// ServiceUpdate updates a Service. The version number is required to avoid conflicting writes. // ServiceUpdate updates a Service. The version number is required to avoid conflicting writes.
@ -19,14 +21,6 @@ func (cli *Client) ServiceUpdate(ctx context.Context, serviceID string, version
response = types.ServiceUpdateResponse{} response = types.ServiceUpdateResponse{}
) )
headers := map[string][]string{
"version": {cli.version},
}
if options.EncodedRegistryAuth != "" {
headers[registry.AuthHeader] = []string{options.EncodedRegistryAuth}
}
if options.RegistryAuthFrom != "" { if options.RegistryAuthFrom != "" {
query.Set("registryAuthFrom", options.RegistryAuthFrom) query.Set("registryAuthFrom", options.RegistryAuthFrom)
} }
@ -60,6 +54,16 @@ func (cli *Client) ServiceUpdate(ctx context.Context, serviceID string, version
} }
} }
headers := http.Header{}
if versions.LessThan(cli.version, "1.30") {
// the custom "version" header was used by engine API before 20.10
// (API 1.30) to switch between client- and server-side lookup of
// image digests.
headers["version"] = []string{cli.version}
}
if options.EncodedRegistryAuth != "" {
headers[registry.AuthHeader] = []string{options.EncodedRegistryAuth}
}
resp, err := cli.post(ctx, "/services/"+serviceID+"/update", query, service, headers) resp, err := cli.post(ctx, "/services/"+serviceID+"/update", query, service, headers)
defer ensureReaderClosed(resp) defer ensureReaderClosed(resp)
if err != nil { if err != nil {