Merge pull request #4272 from dmcgowan/update-registry-host-config

Registry config header support and fixes
This commit is contained in:
Wei Fu 2020-07-03 18:47:46 +08:00 committed by GitHub
commit f821b77151
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 97 additions and 40 deletions

View File

@ -48,6 +48,8 @@ type hostConfig struct {
clientPairs [][2]string clientPairs [][2]string
skipVerify *bool skipVerify *bool
header http.Header
// TODO: API ("docker" or "oci") // TODO: API ("docker" or "oci")
// TODO: API Version ("v1", "v2") // TODO: API Version ("v1", "v2")
// TODO: Add credential configuration (domain alias, username) // TODO: Add credential configuration (domain alias, username)
@ -105,6 +107,13 @@ func ConfigureHosts(ctx context.Context, options HostOptions) docker.RegistryHos
hosts[len(hosts)-1].capabilities = docker.HostCapabilityPull | docker.HostCapabilityResolve | docker.HostCapabilityPush hosts[len(hosts)-1].capabilities = docker.HostCapabilityPull | docker.HostCapabilityResolve | docker.HostCapabilityPush
} }
var defaultTLSConfig *tls.Config
if options.DefaultTLS != nil {
defaultTLSConfig = options.DefaultTLS
} else {
defaultTLSConfig = &tls.Config{}
}
defaultTransport := &http.Transport{ defaultTransport := &http.Transport{
Proxy: http.ProxyFromEnvironment, Proxy: http.ProxyFromEnvironment,
DialContext: (&net.Dialer{ DialContext: (&net.Dialer{
@ -115,7 +124,7 @@ func ConfigureHosts(ctx context.Context, options HostOptions) docker.RegistryHos
MaxIdleConns: 10, MaxIdleConns: 10,
IdleConnTimeout: 30 * time.Second, IdleConnTimeout: 30 * time.Second,
TLSHandshakeTimeout: 10 * time.Second, TLSHandshakeTimeout: 10 * time.Second,
TLSClientConfig: options.DefaultTLS, TLSClientConfig: defaultTLSConfig,
ExpectContinueTimeout: 5 * time.Second, ExpectContinueTimeout: 5 * time.Second,
} }
@ -136,14 +145,11 @@ func ConfigureHosts(ctx context.Context, options HostOptions) docker.RegistryHos
rhosts[i].Host = host.host rhosts[i].Host = host.host
rhosts[i].Path = host.path rhosts[i].Path = host.path
rhosts[i].Capabilities = host.capabilities rhosts[i].Capabilities = host.capabilities
rhosts[i].Header = host.header
if host.caCerts != nil || host.clientPairs != nil || host.skipVerify != nil { if host.caCerts != nil || host.clientPairs != nil || host.skipVerify != nil {
var tlsConfig *tls.Config tr := defaultTransport.Clone()
if options.DefaultTLS != nil { tlsConfig := tr.TLSClientConfig
tlsConfig = options.DefaultTLS.Clone()
} else {
tlsConfig = &tls.Config{}
}
if host.skipVerify != nil { if host.skipVerify != nil {
tlsConfig.InsecureSkipVerify = *host.skipVerify tlsConfig.InsecureSkipVerify = *host.skipVerify
} }
@ -190,8 +196,7 @@ func ConfigureHosts(ctx context.Context, options HostOptions) docker.RegistryHos
tlsConfig.Certificates = append(tlsConfig.Certificates, cert) tlsConfig.Certificates = append(tlsConfig.Certificates, cert)
} }
} }
tr := defaultTransport.Clone()
tr.TLSClientConfig = tlsConfig
c := *client c := *client
c.Transport = tr c.Transport = tr
@ -201,7 +206,6 @@ func ConfigureHosts(ctx context.Context, options HostOptions) docker.RegistryHos
rhosts[i].Client = client rhosts[i].Client = client
rhosts[i].Authorizer = authorizer rhosts[i].Authorizer = authorizer
} }
} }
return rhosts, nil return rhosts, nil
@ -270,7 +274,9 @@ type hostFileConfig struct {
// TODO: Make this an array (two key types, one for pairs (multiple files), one for single file?) // TODO: Make this an array (two key types, one for pairs (multiple files), one for single file?)
Client toml.Primitive `toml:"client"` Client toml.Primitive `toml:"client"`
SkipVerify bool `toml:"skip_verify"` SkipVerify *bool `toml:"skip_verify"`
Header map[string]toml.Primitive `toml:"header"`
// API (default: "docker") // API (default: "docker")
// API Version (default: "v2") // API Version (default: "v2")
@ -306,6 +312,9 @@ func parseHostsFile(ctx context.Context, baseDir string, b []byte) ([]hostConfig
} }
} }
if c.HostConfigs == nil {
c.HostConfigs = map[string]hostFileConfig{}
}
if c.Server != "" { if c.Server != "" {
c.HostConfigs[c.Server] = c.hostFileConfig c.HostConfigs[c.Server] = c.hostFileConfig
orderedHosts = append(orderedHosts, c.Server) orderedHosts = append(orderedHosts, c.Server)
@ -317,6 +326,7 @@ func parseHostsFile(ctx context.Context, baseDir string, b []byte) ([]hostConfig
for i, server := range orderedHosts { for i, server := range orderedHosts {
hostConfig := c.HostConfigs[server] hostConfig := c.HostConfigs[server]
if server != "" {
if !strings.HasPrefix(server, "http") { if !strings.HasPrefix(server, "http") {
server = "https://" + server server = "https://" + server
} }
@ -340,10 +350,8 @@ func parseHostsFile(ctx context.Context, baseDir string, b []byte) ([]hostConfig
u.Path = "/v2" u.Path = "/v2"
} }
hosts[i].path = u.Path hosts[i].path = u.Path
if hosts[i].scheme == "https" {
hosts[i].skipVerify = &hostConfig.SkipVerify
} }
hosts[i].skipVerify = hostConfig.SkipVerify
if len(hostConfig.Capabilities) > 0 { if len(hostConfig.Capabilities) > 0 {
for _, c := range hostConfig.Capabilities { for _, c := range hostConfig.Capabilities {
@ -363,7 +371,7 @@ func parseHostsFile(ctx context.Context, baseDir string, b []byte) ([]hostConfig
} }
baseKey := []string{} baseKey := []string{}
if server != "" { if server != "" && server != c.Server {
baseKey = append(baseKey, "host", server) baseKey = append(baseKey, "host", server)
} }
caKey := append(baseKey, "ca") caKey := append(baseKey, "ca")
@ -429,6 +437,31 @@ func parseHostsFile(ctx context.Context, baseDir string, b []byte) ([]hostConfig
return nil, errors.Errorf("invalid type %v for \"client\"", t) return nil, errors.Errorf("invalid type %v for \"client\"", t)
} }
} }
headerKey := append(baseKey, "header")
if md.IsDefined(headerKey...) {
header := http.Header{}
for key, prim := range hostConfig.Header {
switch t := md.Type(append(headerKey, key)...); t {
case "String":
var value string
if err := md.PrimitiveDecode(prim, &value); err != nil {
return nil, errors.Wrapf(err, "failed to decode header %q", key)
}
header[key] = []string{value}
case "Array":
var value []string
if err := md.PrimitiveDecode(prim, &value); err != nil {
return nil, errors.Wrapf(err, "failed to decode header %q", key)
}
header[key] = value
default:
return nil, errors.Errorf("invalid type %v for header %q", t, key)
}
}
hosts[i].header = header
}
} }
return hosts, nil return hosts, nil

View File

@ -20,6 +20,7 @@ import (
"bytes" "bytes"
"context" "context"
"fmt" "fmt"
"net/http"
"path/filepath" "path/filepath"
"testing" "testing"
@ -76,10 +77,15 @@ func TestParseHostFile(t *testing.T) {
const testtoml = ` const testtoml = `
server = "https://test-default.registry" server = "https://test-default.registry"
ca = "/etc/path/default" ca = "/etc/path/default"
[header]
x-custom-1 = "custom header"
[host."https://mirror.registry"] [host."https://mirror.registry"]
capabilities = ["pull"] capabilities = ["pull"]
ca = "/etc/certs/mirror.pem" ca = "/etc/certs/mirror.pem"
skip_verify = false
[host."https://mirror.registry".header]
x-custom-2 = ["value1", "value2"]
[host."https://mirror-bak.registry/us"] [host."https://mirror-bak.registry/us"]
capabilities = ["pull"] capabilities = ["pull"]
@ -108,6 +114,7 @@ ca = "/etc/path/default"
capabilities: docker.HostCapabilityPull, capabilities: docker.HostCapabilityPull,
caCerts: []string{filepath.FromSlash("/etc/certs/mirror.pem")}, caCerts: []string{filepath.FromSlash("/etc/certs/mirror.pem")},
skipVerify: &fb, skipVerify: &fb,
header: http.Header{"x-custom-2": {"value1", "value2"}},
}, },
{ {
scheme: "https", scheme: "https",
@ -132,7 +139,6 @@ ca = "/etc/path/default"
{filepath.FromSlash("/etc/certs/client.cert"), filepath.FromSlash("/etc/certs/client.key")}, {filepath.FromSlash("/etc/certs/client.cert"), filepath.FromSlash("/etc/certs/client.key")},
{filepath.FromSlash("/etc/certs/client.pem"), ""}, {filepath.FromSlash("/etc/certs/client.pem"), ""},
}, },
skipVerify: &fb,
}, },
{ {
scheme: "https", scheme: "https",
@ -142,7 +148,6 @@ ca = "/etc/path/default"
clientPairs: [][2]string{ clientPairs: [][2]string{
{filepath.FromSlash("/etc/certs/client.pem")}, {filepath.FromSlash("/etc/certs/client.pem")},
}, },
skipVerify: &fb,
}, },
{ {
scheme: "https", scheme: "https",
@ -153,14 +158,14 @@ ca = "/etc/path/default"
{filepath.FromSlash("/etc/certs/client-1.pem")}, {filepath.FromSlash("/etc/certs/client-1.pem")},
{filepath.FromSlash("/etc/certs/client-2.pem")}, {filepath.FromSlash("/etc/certs/client-2.pem")},
}, },
skipVerify: &fb,
}, },
{ {
scheme: "https", scheme: "https",
host: "test-default.registry", host: "test-default.registry",
path: "/v2", path: "/v2",
capabilities: allCaps, capabilities: allCaps,
skipVerify: &fb, caCerts: []string{filepath.FromSlash("/etc/path/default")},
header: http.Header{"x-custom-1": {"custom header"}},
}, },
} }
hosts, err := parseHostsFile(ctx, "", []byte(testtoml)) hosts, err := parseHostsFile(ctx, "", []byte(testtoml))
@ -243,6 +248,20 @@ func compareHostConfig(j, k hostConfig) bool {
return false return false
} }
if len(j.header) != len(k.header) {
return false
}
for key := range j.header {
if len(j.header[key]) != len(k.header[key]) {
return false
}
for i := range j.header[key] {
if j.header[key][i] != k.header[key][i] {
return false
}
}
}
return true return true
} }
@ -260,6 +279,7 @@ func printHostConfig(hc []hostConfig) string {
} else { } else {
fmt.Fprintf(b, "\t\tskip-verify: %t\n", *hc[i].skipVerify) fmt.Fprintf(b, "\t\tskip-verify: %t\n", *hc[i].skipVerify)
} }
fmt.Fprintf(b, "\t\theader: %#v\n", hc[i].header)
} }
return b.String() return b.String()
} }

View File

@ -70,6 +70,7 @@ type RegistryHost struct {
Scheme string Scheme string
Path string Path string
Capabilities HostCapabilities Capabilities HostCapabilities
Header http.Header
} }
// RegistryHosts fetches the registry hosts for a given namespace, // RegistryHosts fetches the registry hosts for a given namespace,

View File

@ -450,6 +450,9 @@ func (r *dockerBase) request(host RegistryHost, method string, ps ...string) *re
for key, value := range r.header { for key, value := range r.header {
header[key] = append(header[key], value...) header[key] = append(header[key], value...)
} }
for key, value := range host.Header {
header[key] = append(header[key], value...)
}
parts := append([]string{"/", host.Path, r.namespace}, ps...) parts := append([]string{"/", host.Path, r.namespace}, ps...)
p := path.Join(parts...) p := path.Join(parts...)
// Join strips trailing slash, re-add ending "/" if included // Join strips trailing slash, re-add ending "/" if included