Merge pull request #4272 from dmcgowan/update-registry-host-config
Registry config header support and fixes
This commit is contained in:
commit
f821b77151
@ -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,33 +326,32 @@ 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 !strings.HasPrefix(server, "http") {
|
if server != "" {
|
||||||
server = "https://" + server
|
if !strings.HasPrefix(server, "http") {
|
||||||
}
|
server = "https://" + server
|
||||||
u, err := url.Parse(server)
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.Errorf("unable to parse server %v", server)
|
|
||||||
}
|
|
||||||
hosts[i].scheme = u.Scheme
|
|
||||||
hosts[i].host = u.Host
|
|
||||||
|
|
||||||
// TODO: Handle path based on registry protocol
|
|
||||||
// Define a registry protocol type
|
|
||||||
// OCI v1 - Always use given path as is
|
|
||||||
// Docker v2 - Always ensure ends with /v2/
|
|
||||||
if len(u.Path) > 0 {
|
|
||||||
u.Path = path.Clean(u.Path)
|
|
||||||
if !strings.HasSuffix(u.Path, "/v2") {
|
|
||||||
u.Path = u.Path + "/v2"
|
|
||||||
}
|
}
|
||||||
} else {
|
u, err := url.Parse(server)
|
||||||
u.Path = "/v2"
|
if err != nil {
|
||||||
}
|
return nil, errors.Errorf("unable to parse server %v", server)
|
||||||
hosts[i].path = u.Path
|
}
|
||||||
|
hosts[i].scheme = u.Scheme
|
||||||
|
hosts[i].host = u.Host
|
||||||
|
|
||||||
if hosts[i].scheme == "https" {
|
// TODO: Handle path based on registry protocol
|
||||||
hosts[i].skipVerify = &hostConfig.SkipVerify
|
// Define a registry protocol type
|
||||||
|
// OCI v1 - Always use given path as is
|
||||||
|
// Docker v2 - Always ensure ends with /v2/
|
||||||
|
if len(u.Path) > 0 {
|
||||||
|
u.Path = path.Clean(u.Path)
|
||||||
|
if !strings.HasSuffix(u.Path, "/v2") {
|
||||||
|
u.Path = u.Path + "/v2"
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
u.Path = "/v2"
|
||||||
|
}
|
||||||
|
hosts[i].path = u.Path
|
||||||
}
|
}
|
||||||
|
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
|
||||||
|
@ -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()
|
||||||
}
|
}
|
||||||
|
@ -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,
|
||||||
|
@ -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
|
||||||
|
Loading…
Reference in New Issue
Block a user