Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Issue #327 Creating Docker client from env settings #328

Merged
merged 1 commit into from
Nov 6, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 54 additions & 0 deletions docker/daemon/client.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
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) {
httpClient, err := tlsConfig(ctx)
if err != nil {
return nil, err
}

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

return dockerclient.NewClient(host, defaultAPIVersion, httpClient, nil)
}

func tlsConfig(ctx *types.SystemContext) (*http.Client, error) {
options := tlsconfig.Options{}
if ctx != nil && ctx.DockerDaemonInsecureSkipTLSVerify {
options.InsecureSkipVerify = true
}

if ctx != nil && ctx.DockerDaemonCertPath != "" {
options.CAFile = filepath.Join(ctx.DockerDaemonCertPath, "ca.pem")
options.CertFile = filepath.Join(ctx.DockerDaemonCertPath, "cert.pem")
options.KeyFile = filepath.Join(ctx.DockerDaemonCertPath, "key.pem")
}

tlsc, err := tlsconfig.Client(options)
if err != nil {
return nil, err
}

return &http.Client{
Transport: &http.Transport{
TLSClientConfig: tlsc,
},
CheckRedirect: dockerclient.CheckRedirect,
}, nil
}
93 changes: 93 additions & 0 deletions docker/daemon/client_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
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 := testDir(t)

host := "tcp://127.0.0.1:2376"
systemCtx := &types.SystemContext{
DockerDaemonCertPath: filepath.Join(testDir, "testdata", "certs"),
DockerDaemonHost: host,
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, "1.22", client.ClientVersion())
}

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 := testDir(t)

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

httpClient, err := tlsConfig(ctx)

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

tlsConfig := httpClient.Transport.(*http.Transport).TLSClientConfig
assert.True(t, tlsConfig.InsecureSkipVerify, "TLS verification should be skipped")
assert.Len(t, tlsConfig.Certificates, 1, "There should be one certificate")
}

func TestSkipTLSVerifyOnly(t *testing.T) {
//testDir := testDir(t)

ctx := &types.SystemContext{
DockerDaemonInsecureSkipTLSVerify: true,
}

httpClient, err := tlsConfig(ctx)

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

tlsConfig := httpClient.Transport.(*http.Transport).TLSClientConfig
assert.True(t, tlsConfig.InsecureSkipVerify, "TLS verification should be skipped")
assert.Len(t, tlsConfig.Certificates, 0, "There should be no certificate")
}

func testDir(t *testing.T) string {
testDir, err := os.Getwd()
if err != nil {
t.Fatal("Unable to determine the current test directory")
}
return testDir
}
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-----
17 changes: 14 additions & 3 deletions types/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -285,7 +285,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 @@ -324,8 +324,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 @@ -336,6 +337,16 @@ 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 certificate 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
// Used to skip TLS verification, off by default. To take effect DockerDaemonCertPath needs to be specified as well.
DockerDaemonInsecureSkipTLSVerify bool
}

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