Skip to content

Commit

Permalink
Issue #327 Creating Docker client from settings passed in SystemContext
Browse files Browse the repository at this point in the history
Signed-off-by: Hardy Ferentschik <[email protected]>
  • Loading branch information
hferentschik committed Oct 11, 2017
1 parent 0638527 commit 9346479
Show file tree
Hide file tree
Showing 8 changed files with 236 additions and 9 deletions.
69 changes: 69 additions & 0 deletions docker/daemon/client.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package daemon

import (
"github.com/containers/image/types"
dockerclient "github.com/docker/docker/client"
"github.com/docker/go-connections/tlsconfig"
"net/http"
"path/filepath"
)

const (
// The default API version to be used in case none is explicitly specified
defaultAPIVersion = "1.22"
)

// NewDockerClient initializes a new API client based on the passed SystemContext.
func newDockerClient(ctx *types.SystemContext) (*dockerclient.Client, error) {
if ctx == nil {
return dockerclient.NewClient(dockerclient.DefaultDockerHost, defaultAPIVersion, nil, nil)
}

httpClient, err := tlsConfig(ctx)
if err != nil {
return nil, err
}

host := ctx.DockerDaemonHost
if host == "" {
host = dockerclient.DefaultDockerHost
}

version := ctx.DockerDaemonAPIVersion
if version == "" {
version = defaultAPIVersion
}

cli, err := dockerclient.NewClient(host, version, httpClient, nil)
if err != nil {
return cli, err
}

return cli, nil
}

func tlsConfig(ctx *types.SystemContext) (*http.Client, error) {
if ctx == nil || ctx.DockerDaemonCertPath == "" {
return nil, nil
}

options := tlsconfig.Options{
CAFile: filepath.Join(ctx.DockerDaemonCertPath, "ca.pem"),
CertFile: filepath.Join(ctx.DockerDaemonCertPath, "cert.pem"),
KeyFile: filepath.Join(ctx.DockerDaemonCertPath, "key.pem"),
InsecureSkipVerify: ctx.DockerDaemonInsecureSkipTLSVerify,
}
tlsc, err := tlsconfig.Client(options)
if err != nil {
return nil, err
}

httpClient := &http.Client{
Transport: &http.Transport{
TLSClientConfig: tlsc,
},
CheckRedirect: dockerclient.CheckRedirect,
}

return httpClient, nil
}
83 changes: 83 additions & 0 deletions docker/daemon/client_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
package daemon

import "testing"
import (
"github.com/containers/image/types"
dockerclient "github.com/docker/docker/client"
"github.com/stretchr/testify/assert"
"net/http"
"os"
"path/filepath"
)

func TestDockerClientFromNilSystemContext(t *testing.T) {
client, err := newDockerClient(nil)

assert.Nil(t, err, "There should be no error creating the Docker client")
assert.NotNil(t, client, "A Docker client reference should have been returned")

assert.Equal(t, dockerclient.DefaultDockerHost, client.DaemonHost(), "The default docker host should have been used")
assert.Equal(t, defaultAPIVersion, client.ClientVersion(), "The default api version should have been used")
}

func TestDockerClientFromCertContext(t *testing.T) {
testDir, err := os.Getwd()
if err != nil {
t.Fatal("Unable to determine the current test directory")
}

host := "tcp://127.0.0.1:2376"
version := "1.11"
systemCtx := &types.SystemContext{
DockerDaemonCertPath: filepath.Join(testDir, "testdata", "certs"),
DockerDaemonHost: host,
DockerDaemonAPIVersion: version,
DockerDaemonInsecureSkipTLSVerify: true,
}

client, err := newDockerClient(systemCtx)

assert.Nil(t, err, "There should be no error creating the Docker client")
assert.NotNil(t, client, "A Docker client reference should have been returned")

assert.Equal(t, host, client.DaemonHost())
assert.Equal(t, version, client.ClientVersion())
}

func TestTlsConfigFromNilSystemContext(t *testing.T) {
httpClient, err := tlsConfig(nil)

assert.Nil(t, err, "There should be no error creating the HTTP client")
assert.Nil(t, httpClient, "With no explicit TLS configutation, there should be no HTTP client")
}

func TestTlsConfigFromInvalidCertPath(t *testing.T) {
ctx := &types.SystemContext{
DockerDaemonCertPath: "/foo/bar",
}

_, err := tlsConfig(ctx)

if assert.Error(t, err, "An error was expected") {
assert.Regexp(t, "could not read CA certificate", err.Error())
}
}

func TestTlsConfigFromCertPath(t *testing.T) {
testDir, err := os.Getwd()
if err != nil {
t.Fatal("Unable to determine the current test directory")
}

ctx := &types.SystemContext{
DockerDaemonCertPath: filepath.Join(testDir, "testdata", "certs"),
DockerDaemonInsecureSkipTLSVerify: true,
}

httpClient, err := tlsConfig(ctx)

assert.Nil(t, err, "There should be no error creating the HTTP client")

transport := httpClient.Transport.(*http.Transport)
assert.True(t, transport.TLSClientConfig.InsecureSkipVerify, "TLS verification should be skipped")
}
8 changes: 4 additions & 4 deletions docker/daemon/daemon_dest.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ type daemonImageDestination struct {
}

// newImageDestination returns a types.ImageDestination for the specified image reference.
func newImageDestination(systemCtx *types.SystemContext, ref daemonReference) (types.ImageDestination, error) {
func newImageDestination(ctx *types.SystemContext, ref daemonReference) (types.ImageDestination, error) {
if ref.ref == nil {
return nil, errors.Errorf("Invalid destination docker-daemon:%s: a destination must be a name:tag", ref.StringWithinTransport())
}
Expand All @@ -33,7 +33,7 @@ func newImageDestination(systemCtx *types.SystemContext, ref daemonReference) (t
return nil, errors.Errorf("Invalid destination docker-daemon:%s: a destination must be a name:tag", ref.StringWithinTransport())
}

c, err := client.NewClient(client.DefaultDockerHost, "1.22", nil, nil) // FIXME: overridable host
c, err := newDockerClient(ctx)
if err != nil {
return nil, errors.Wrap(err, "Error initializing docker engine client")
}
Expand All @@ -42,8 +42,8 @@ func newImageDestination(systemCtx *types.SystemContext, ref daemonReference) (t
// Commit() may never be called, so we may never read from this channel; so, make this buffered to allow imageLoadGoroutine to write status and terminate even if we never read it.
statusChannel := make(chan error, 1)

ctx, goroutineCancel := context.WithCancel(context.Background())
go imageLoadGoroutine(ctx, c, reader, statusChannel)
goroutineContext, goroutineCancel := context.WithCancel(context.Background())
go imageLoadGoroutine(goroutineContext, c, reader, statusChannel)

return &daemonImageDestination{
ref: ref,
Expand Down
3 changes: 1 addition & 2 deletions docker/daemon/daemon_src.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import (

"github.com/containers/image/docker/tarfile"
"github.com/containers/image/types"
"github.com/docker/docker/client"
"github.com/pkg/errors"
"golang.org/x/net/context"
)
Expand Down Expand Up @@ -35,7 +34,7 @@ type layerInfo struct {
// is the config, and that the following len(RootFS) files are the layers, but that feels
// way too brittle.)
func newImageSource(ctx *types.SystemContext, ref daemonReference) (types.ImageSource, error) {
c, err := client.NewClient(client.DefaultDockerHost, "1.22", nil, nil) // FIXME: overridable host
c, err := newDockerClient(ctx)
if err != nil {
return nil, errors.Wrap(err, "Error initializing docker engine client")
}
Expand Down
18 changes: 18 additions & 0 deletions docker/daemon/testdata/certs/ca.pem
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
-----BEGIN CERTIFICATE-----
MIICzjCCAbagAwIBAgIRAIGgYBNZse0EqRVzxe7aQGIwDQYJKoZIhvcNAQELBQAw
EDEOMAwGA1UEChMFaGFyZHkwHhcNMTcxMDA0MDgzNDAwWhcNMjAwOTE4MDgzNDAw
WjAQMQ4wDAYDVQQKEwVoYXJkeTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC
ggEBAMlrdtoXWlZMPFwgeKZHrGxjVe4KXkQy5MFBUfO48htyIe2OlZAd3HGyap41
7L4YciFhw0bp7wHnYtSTiCHQrnA4SLzNuaU2NM5nJw+E4c5kNrkvhLJqpTNCaYCy
Xbh3H8REW+5UJIgnyeKLx//kvlDm6p4O55+OLlGgzxNaTIgldKLPmx543VVt6VDT
qgFlaYsRz8hZ12+qAqu5am/Wpfal2+Df7Pmmn5M90UBTUwY8CLc/ZiWbv6hihDWV
I28JoM0onEqAx7phRd0SwwK4mYfEe/u614r3bZaI36e9ojU9/St4nbMoMeyZP96t
DOdX9A1SMbsqLOYKXBKM+jXPEaECAwEAAaMjMCEwDgYDVR0PAQH/BAQDAgKsMA8G
A1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBALah7CjpwbEY6yjA2KDv
VaAHEgz4Xd8USW/L2292EiQLmFdIaEJiiWRjtKFiF427TXfAPXvxHA2q9OElDW4d
G6XAcBJg5mcBh8WRTHwfLQ8llfj7dH1/sfazSUZeat6lTIyhQfkF99LAJTqlfYAF
aNqIQio7FAjGyJqIPYLa1FKmfLdZr9azb9IjTZLhBGBWdLF0+JOn+JBsl7g9BvUp
ArCI0Wib/vsr368xkzWzKjij1exZdfw0TmsieNYvViFoFJGNCB5XLPo0bHrmMVVe
25EGam+xPkG/JQP5Eb3iikSEn8y5SIeJ0nS0EQE6uXPv+lQj1LmVv8OYzjXqpoJT
n6g=
-----END CERTIFICATE-----
18 changes: 18 additions & 0 deletions docker/daemon/testdata/certs/cert.pem
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
-----BEGIN CERTIFICATE-----
MIIC6zCCAdOgAwIBAgIQEh1UsPL20u9KnyOByuhYWDANBgkqhkiG9w0BAQsFADAQ
MQ4wDAYDVQQKEwVoYXJkeTAeFw0xNzEwMDQwODM0MDBaFw0yMDA5MTgwODM0MDBa
MBwxGjAYBgNVBAoMEWhhcmR5Ljxib290c3RyYXA+MIIBIjANBgkqhkiG9w0BAQEF
AAOCAQ8AMIIBCgKCAQEAyJm29vB/urzreEwF012iAAWW3fgE1VEeNLTP/sZTYV3z
UNGKao5x7dUIiah8rptkK3+FN4TID8Z2c1DpKzMTisdpRF3UoRWmjm1UTbxEENhk
EptkFwGFM6BcZSyiLlyCBVM+wGsqzHAASe833S/yiu8miNc2S+jd0FIluKWe0yzG
u2oaJfA28dBfqWyn9hh6msqBVYK6sDle9t0ditNubCyD+vrnoK8825LOIPV6QafL
kVyW0/mj4GJutPOVop37HyQMcuQnDWBA+934l3tpeaJ93d3u8XjU7dXuOobKMohw
+33/pTALu9P0WtDbEeo/xcEICgimqpir92KMSXxUbwIDAQABozUwMzAOBgNVHQ8B
Af8EBAMCB4AwEwYDVR0lBAwwCgYIKwYBBQUHAwIwDAYDVR0TAQH/BAIwADANBgkq
hkiG9w0BAQsFAAOCAQEAnYffv9ipGQVW/t3sFxKu9LXQ7ZkhUSgoxPIA51goaYop
YM9QR3ZBf2tMJwjKXuOLEkxxweNjP3dMKh2gykFory+jv6OQYIiLf9M82ty8rOPi
mWLMDAIWWagkj5Yy6b+/aLkpXQ+lEsxLyi6po+D+I+AwRUYvfSc74a7XxkJk77JF
/0SVgNdDtL08zVNOGDgepP/95e1pKMKgsOiCDnFCOAY+l6HcvizwBH+EI+XtdLVb
qBmOAYiwYObBaRuyhVbbDKqKRGFUNkmmDv6vCQoTL1C9wrBnAiJe2khbLm1ix9Re
3MW15CLuipneSgRAWXSdMbDIv9+KQE8fo2TWqikrCw==
-----END CERTIFICATE-----
27 changes: 27 additions & 0 deletions docker/daemon/testdata/certs/key.pem
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
-----BEGIN RSA PRIVATE KEY-----
MIIEpQIBAAKCAQEAyJm29vB/urzreEwF012iAAWW3fgE1VEeNLTP/sZTYV3zUNGK
ao5x7dUIiah8rptkK3+FN4TID8Z2c1DpKzMTisdpRF3UoRWmjm1UTbxEENhkEptk
FwGFM6BcZSyiLlyCBVM+wGsqzHAASe833S/yiu8miNc2S+jd0FIluKWe0yzGu2oa
JfA28dBfqWyn9hh6msqBVYK6sDle9t0ditNubCyD+vrnoK8825LOIPV6QafLkVyW
0/mj4GJutPOVop37HyQMcuQnDWBA+934l3tpeaJ93d3u8XjU7dXuOobKMohw+33/
pTALu9P0WtDbEeo/xcEICgimqpir92KMSXxUbwIDAQABAoIBAQCyuKjXR5w1Ll4I
FotWLmTH6jLo3jDIMPZddP6e+emNpRvD1HyixPhiMdvicXdsRUuwqXNx7F4mF+au
hNbIwz/U9CcoXwSy48w5ttRWUba+31wBa+p3yMX5IhVPmr1/2rGItwsAejpuXBcV
yAiYi0BnYfyODFf2t6jwElBDO2POtdEoYVYwgtMTMy5pmDA2QA3mKkjCcJviectZ
9yFb8DFiwIYkryErWrGWaKls/oYV2O0A0mCaIqgw3HfhIl6F1pk+9oYnmsq6IzF5
wSIg2evd4GMm/L2sqlVFqb4Kj54fbyfdOFK0bQso6VQZvB5tZ6NLHfv2f3BBFHVu
jO+On/ixAoGBAOJkPHavnAb/lLDnMJjjXYNUQqyxxSlwOwNifG4evf/KAezIIalJ
kC7jZoFsUkARVbRKQag0T2Xvxw/dDqmNastR1NxsBkhOWjYiQbALYP3u2f06Nhf8
YlX6hyEje/3bb838//sH5jnaN8GcZnDBrAoPzW+V87pQoCyVrjs2t8qXAoGBAOLV
+PviAUWFjUO//dYk9H9IWotr6rdkzmpLbrj+NoLNSGeoZbByPmT5BuNswXvNyk+9
smOQ8yqBiMpjxKwR4WQnS6Ydh6HTT33IWLLVazDFMf7ACmXWoScFhCAW6qGfdrYQ
hkCSbwgun8jbL2D477jJl6ZyQG48lVnnZDjkFbfpAoGAUOqCsekSW23+Nzxqojqh
sc7sBc2EKstyTENnNfTG9CW/imH9pgQlBJ1Chf+xZjTL7SSdUwFfX4/UFldsZi2l
fgZBjocNt8pJdA/KaqGmiRxVzayAqRIME673nWCRcKp9y6Ih3Bd2sjbMtuavtp2C
YBZF1xxBgNZQaZ8WJxPnnQECgYEAzLgGJPWc5iyZCJsesQTbMICRTyEPTYKKFD6N
6CFt+vDgNsUxOWRx0Vk6kUhW+rAItZzjgZ6RBzyuwtH17sGYZHZefMZL4Y2/QSru
ej/IpNRjwaF6AN0KxhfhXcCw8zrivX/+WgqOcJj7lh/TC7a/S0uNNSgJ5DODKwd9
WSboPvkCgYEAzqdWfetko7hEI4076pufJrHPnnCJSHkkQ1QnfVl71mq7UmKXLDxD
L5oWtU53+dswzvxGrzkOWsRJC5nN30BYJuYlwKzo3+MCKlUzJSuIMVTbTPlwKudh
AF19s4GFZVo29FlgIQhA5dfIkZgFXAlVxYcGTLUixEmPwrc6yguULPs=
-----END RSA PRIVATE KEY-----
19 changes: 16 additions & 3 deletions types/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -283,7 +283,7 @@ type DockerAuthConfig struct {
Password string
}

// SystemContext allows parametrizing access to implicitly-accessed resources,
// SystemContext allows parameterizing access to implicitly-accessed resources,
// like configuration files in /etc and users' login state in their home directory.
// Various components can share the same field only if their semantics is exactly
// the same; if in doubt, add a new field.
Expand Down Expand Up @@ -320,8 +320,9 @@ type SystemContext struct {
DockerCertPath string
// If not "", overrides the system’s default path for a directory containing host[:port] subdirectories with the same structure as DockerCertPath above.
// Ignored if DockerCertPath is non-empty.
DockerPerHostCertDirPath string
DockerInsecureSkipTLSVerify bool // Allow contacting docker registries over HTTP, or HTTPS with failed TLS verification. Note that this does not affect other TLS connections.
DockerPerHostCertDirPath string
// Allow contacting docker registries over HTTP, or HTTPS with failed TLS verification. Note that this does not affect other TLS connections.
DockerInsecureSkipTLSVerify bool
// if nil, the library tries to parse ~/.docker/config.json to retrieve credentials
DockerAuthConfig *DockerAuthConfig
// if not "", an User-Agent header is added to each request when contacting a registry.
Expand All @@ -332,6 +333,18 @@ type SystemContext struct {
DockerDisableV1Ping bool
// Directory to use for OSTree temporary files
OSTreeTmpDirPath string

// === docker/daemon.Transport overrides ===
// A directory containing a CA certificate (ending with ".crt"),
// a client certificate (ending with ".cert") and a client ceritificate key
// (ending with ".key") used when talking to a Docker daemon.
DockerDaemonCertPath string
// The hostname or IP to the Docker daemon. If not set (aka ""), client.DefaultDockerHost is assumed.
DockerDaemonHost string
// API version of the Docker daemon to talk to.
DockerDaemonAPIVersion string
// Used to skip TLS verification, off by default.
DockerDaemonInsecureSkipTLSVerify bool
}

// ProgressProperties is used to pass information from the copy code to a monitor which
Expand Down

0 comments on commit 9346479

Please sign in to comment.