-
Notifications
You must be signed in to change notification settings - Fork 601
/
dockerclientfactory.go
206 lines (186 loc) · 7.5 KB
/
dockerclientfactory.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
// Copyright 2014-2017 Amazon.com, Inc. or its affiliates. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License"). You may
// not use this file except in compliance with the License. A copy of the
// License is located at
//
// http://aws.amazon.com/apache2.0/
//
// or in the "license" file accompanying this file. This file is distributed
// on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
// express or implied. See the License for the specific language governing
// permissions and limitations under the License.
package dockerclient
import (
"github.com/aws/amazon-ecs-agent/agent/engine/dockeriface"
"github.com/aws/amazon-ecs-agent/agent/utils"
log "github.com/cihub/seelog"
docker "github.com/fsouza/go-dockerclient"
"github.com/pkg/errors"
)
const (
// minAPIVersionKey is the docker.Env key for min API version
// This is supported in Docker API versions >=1.25
// https://docs.docker.com/engine/api/version-history/#v125-api-changes
minAPIVersionKey = "MinAPIVersion"
// apiVersionKey is the docker.Env key for API version
apiVersionKey = "ApiVersion"
// zeroPatch is a string to append patch number zero if the major minor version lacks it
zeroPatch = ".0"
)
// Factory provides a collection of docker remote clients that include a
// recommended client version as well as a set of alternative supported
// docker clients.
type Factory interface {
// GetDefaultClient returns a versioned client for the default version
GetDefaultClient() (dockeriface.Client, error)
// GetClient returns a client with the specified version or an error
// if the client doesn't exist.
GetClient(version DockerVersion) (dockeriface.Client, error)
// FindSupportedAPIVersions returns a slice of agent-supported Docker API
// versions. Versions are tested by making calls against the Docker daemon
// and may occur either at Factory creation time or lazily upon invocation
// of this function. The slice represents the intersection of
// agent-supported versions and daemon-supported versions.
FindSupportedAPIVersions() []DockerVersion
// FindKnownAPIVersions returns a slice of Docker API versions that are
// known to the Docker daemon. Versions are tested by making calls against
// the Docker daemon and may occur either at Factory creation time or
// lazily upon invocation of this function. The slice represents the
// intersection of the API versions that the agent knows exist (but does
// not necessarily fully support) and the versions that result in
// successful responses by the Docker daemon.
FindKnownAPIVersions() []DockerVersion
// FindClientAPIVersion returns the client api version
FindClientAPIVersion(dockeriface.Client) DockerVersion
}
type factory struct {
endpoint string
clients map[DockerVersion]dockeriface.Client
}
// newVersionedClient is a variable such that the implementation can be
// swapped out for unit tests
var newVersionedClient = func(endpoint, version string) (dockeriface.Client, error) {
return docker.NewVersionedClient(endpoint, version)
}
// NewFactory initializes a client factory using a specified endpoint.
func NewFactory(endpoint string) Factory {
return &factory{
endpoint: endpoint,
clients: findDockerVersions(endpoint),
}
}
func (f *factory) GetDefaultClient() (dockeriface.Client, error) {
return f.GetClient(getDefaultVersion())
}
func (f *factory) FindSupportedAPIVersions() []DockerVersion {
var supportedVersions []DockerVersion
for _, testVersion := range getAgentVersions() {
_, err := f.GetClient(testVersion)
if err != nil {
continue
}
supportedVersions = append(supportedVersions, testVersion)
}
return supportedVersions
}
func (f *factory) FindKnownAPIVersions() []DockerVersion {
var knownVersions []DockerVersion
for _, testVersion := range getKnownAPIVersions() {
_, err := f.GetClient(testVersion)
if err != nil {
continue
}
knownVersions = append(knownVersions, testVersion)
}
return knownVersions
}
// FindClientAPIVersion returns the version of the client from the map
// TODO we should let go docker client return this version information
func (f *factory) FindClientAPIVersion(client dockeriface.Client) DockerVersion {
for k, v := range f.clients {
if v == client {
return k
}
}
return getDefaultVersion()
}
// getClient returns a client specified by the docker version. Its wrapped
// by GetClient so that it can do platform-specific magic
func (f *factory) getClient(version DockerVersion) (dockeriface.Client, error) {
client, ok := f.clients[version]
if ok {
return client, nil
} else {
return nil, errors.New("docker client factory: client not found for docker version: " + string(version))
}
}
// findDockerVersions loops over all known API versions and finds which ones
// are supported by the docker daemon on the host
func findDockerVersions(endpoint string) map[DockerVersion]dockeriface.Client {
// if the client version returns a MinAPIVersion and APIVersion, then use it to return
// all the Docker clients between MinAPIVersion and APIVersion, else try pinging
// the clients in getKnownAPIVersions
var minAPIVersion, apiVersion string
// get a Docker client with the default supported version
client, err := newVersionedClient(endpoint, string(minDockerAPIVersion))
if err == nil {
clientVersion, err := client.Version()
if err == nil {
// check if the docker.Env obj has MinAPIVersion key
if clientVersion.Exists(minAPIVersionKey) {
minAPIVersion = clientVersion.Get(minAPIVersionKey)
}
// check if the docker.Env obj has APIVersion key
if clientVersion.Exists(apiVersionKey) {
apiVersion = clientVersion.Get(apiVersionKey)
}
}
}
clients := make(map[DockerVersion]dockeriface.Client)
for _, version := range getKnownAPIVersions() {
dockerClient, err := getDockerClientForVersion(endpoint, string(version), minAPIVersion, apiVersion)
if err != nil {
log.Infof("Unable to get Docker client for version %s: %v", version, err)
continue
}
clients[version] = dockerClient
}
return clients
}
func getDockerClientForVersion(
endpoint string,
version string,
minAPIVersion string,
apiVersion string) (dockeriface.Client, error) {
if minAPIVersion != "" && apiVersion != "" {
// Adding patch number zero to Docker versions to reuse the existing semver
// comparator
// TODO: remove this logic later when non-semver comparator is implemented
versionWithPatch := version + zeroPatch
lessThanMinCheck := "<" + minAPIVersion + zeroPatch
moreThanMaxCheck := ">" + apiVersion + zeroPatch
minVersionCheck, err := utils.Version(versionWithPatch).Matches(lessThanMinCheck)
if err != nil {
return nil, errors.Wrapf(err, "version detection using MinAPIVersion: unable to get min version: %s", minAPIVersion)
}
maxVersionCheck, err := utils.Version(versionWithPatch).Matches(moreThanMaxCheck)
if err != nil {
return nil, errors.Wrapf(err, "version detection using MinAPIVersion: unable to get max version: %s", apiVersion)
}
// do not add the version when it is less than min api version or greater
// than api version
if minVersionCheck || maxVersionCheck {
return nil, errors.Errorf("version detection using MinAPIVersion: unsupported version: %s", version)
}
}
client, err := newVersionedClient(endpoint, string(version))
if err != nil {
return nil, errors.Wrapf(err, "version detection check: unable to create Docker client for version: %s", version)
}
err = client.Ping()
if err != nil {
return nil, errors.Wrapf(err, "version detection check: failed to ping with Docker version: %s", string(version))
}
return client, nil
}