Skip to content

Commit 206c184

Browse files
authored
Merge pull request #1386 from docker/2.0.1-release
2.0.1 release
2 parents dbed962 + 359c4cd commit 206c184

15 files changed

+251
-39
lines changed

Jenkinsfile

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
#!groovy
2+
3+
def imageNameBase = "dockerbuildbot/docker-py"
4+
def imageNamePy2
5+
def imageNamePy3
6+
def images = [:]
7+
def dockerVersions = ["1.12.0", "1.13.0-rc3"]
8+
9+
def buildImage = { name, buildargs, pyTag ->
10+
img = docker.image(name)
11+
try {
12+
img.pull()
13+
} catch (Exception exc) {
14+
img = docker.build(name, buildargs)
15+
img.push()
16+
}
17+
images[pyTag] = img.id
18+
}
19+
20+
def buildImages = { ->
21+
wrappedNode(label: "ubuntu && !zfs && amd64", cleanWorkspace: true) {
22+
stage("build image") {
23+
checkout(scm)
24+
25+
imageNamePy2 = "${imageNameBase}:py2-${gitCommit()}"
26+
imageNamePy3 = "${imageNameBase}:py3-${gitCommit()}"
27+
28+
buildImage(imageNamePy2, ".", "py2.7")
29+
buildImage(imageNamePy3, "-f Dockerfile-py3 .", "py3.5")
30+
}
31+
}
32+
}
33+
34+
def runTests = { Map settings ->
35+
def dockerVersion = settings.get("dockerVersion", null)
36+
def pythonVersion = settings.get("pythonVersion", null)
37+
def testImage = settings.get("testImage", null)
38+
39+
if (!testImage) {
40+
throw new Exception("Need test image object, e.g.: `runTests(testImage: img)`")
41+
}
42+
if (!dockerVersion) {
43+
throw new Exception("Need Docker version to test, e.g.: `runTests(dockerVersion: '1.12.3')`")
44+
}
45+
if (!pythonVersion) {
46+
throw new Exception("Need Python version being tested, e.g.: `runTests(pythonVersion: 'py2.7')`")
47+
}
48+
49+
{ ->
50+
wrappedNode(label: "ubuntu && !zfs && amd64", cleanWorkspace: true) {
51+
stage("test python=${pythonVersion} / docker=${dockerVersion}") {
52+
checkout(scm)
53+
def dindContainerName = "dpy-dind-\$BUILD_NUMBER-\$EXECUTOR_NUMBER"
54+
def testContainerName = "dpy-tests-\$BUILD_NUMBER-\$EXECUTOR_NUMBER"
55+
try {
56+
sh """docker run -d --name ${dindContainerName} -v /tmp --privileged \\
57+
dockerswarm/dind:${dockerVersion} docker daemon -H tcp://0.0.0.0:2375
58+
"""
59+
sh """docker run \\
60+
--name ${testContainerName} --volumes-from ${dindContainerName} \\
61+
-e 'DOCKER_HOST=tcp://docker:2375' \\
62+
--link=${dindContainerName}:docker \\
63+
${testImage} \\
64+
py.test -v -rxs tests/integration
65+
"""
66+
} finally {
67+
sh """
68+
docker stop ${dindContainerName} ${testContainerName}
69+
docker rm -vf ${dindContainerName} ${testContainerName}
70+
"""
71+
}
72+
}
73+
}
74+
}
75+
}
76+
77+
78+
buildImages()
79+
80+
def testMatrix = [failFast: false]
81+
82+
for (imgKey in new ArrayList(images.keySet())) {
83+
for (version in dockerVersions) {
84+
testMatrix["${imgKey}_${version}"] = runTests([testImage: images[imgKey], dockerVersion: version, pythonVersion: imgKey])
85+
}
86+
}
87+
88+
parallel(testMatrix)

docker/errors.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,10 @@ class InvalidConfigFile(DockerException):
9393
pass
9494

9595

96+
class InvalidArgument(DockerException):
97+
pass
98+
99+
96100
class DeprecatedMethod(DockerException):
97101
pass
98102

docker/types/services.py

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import six
22

33
from .. import errors
4+
from ..constants import IS_WINDOWS_PLATFORM
45
from ..utils import format_environment, split_command
56

67

@@ -131,18 +132,19 @@ def __init__(self, target, source, type='volume', read_only=False,
131132
self['Target'] = target
132133
self['Source'] = source
133134
if type not in ('bind', 'volume'):
134-
raise errors.DockerError(
135+
raise errors.InvalidArgument(
135136
'Only acceptable mount types are `bind` and `volume`.'
136137
)
137138
self['Type'] = type
139+
self['ReadOnly'] = read_only
138140

139141
if type == 'bind':
140142
if propagation is not None:
141143
self['BindOptions'] = {
142144
'Propagation': propagation
143145
}
144146
if any([labels, driver_config, no_copy]):
145-
raise errors.DockerError(
147+
raise errors.InvalidArgument(
146148
'Mount type is binding but volume options have been '
147149
'provided.'
148150
)
@@ -157,7 +159,7 @@ def __init__(self, target, source, type='volume', read_only=False,
157159
if volume_opts:
158160
self['VolumeOptions'] = volume_opts
159161
if propagation:
160-
raise errors.DockerError(
162+
raise errors.InvalidArgument(
161163
'Mount type is volume but `propagation` argument has been '
162164
'provided.'
163165
)
@@ -166,16 +168,25 @@ def __init__(self, target, source, type='volume', read_only=False,
166168
def parse_mount_string(cls, string):
167169
parts = string.split(':')
168170
if len(parts) > 3:
169-
raise errors.DockerError(
171+
raise errors.InvalidArgument(
170172
'Invalid mount format "{0}"'.format(string)
171173
)
172174
if len(parts) == 1:
173-
return cls(target=parts[0])
175+
return cls(target=parts[0], source=None)
174176
else:
175177
target = parts[1]
176178
source = parts[0]
177-
read_only = not (len(parts) == 3 or parts[2] == 'ro')
178-
return cls(target, source, read_only=read_only)
179+
mount_type = 'volume'
180+
if source.startswith('/') or (
181+
IS_WINDOWS_PLATFORM and source[0].isalpha() and
182+
source[1] == ':'
183+
):
184+
# FIXME: That windows condition will fail earlier since we
185+
# split on ':'. We should look into doing a smarter split
186+
# if we detect we are on Windows.
187+
mount_type = 'bind'
188+
read_only = not (len(parts) == 2 or parts[2] == 'rw')
189+
return cls(target, source, read_only=read_only, type=mount_type)
179190

180191

181192
class Resources(dict):
@@ -228,7 +239,7 @@ def __init__(self, parallelism=0, delay=None, failure_action='continue'):
228239
if delay is not None:
229240
self['Delay'] = delay
230241
if failure_action not in ('pause', 'continue'):
231-
raise errors.DockerError(
242+
raise errors.InvalidArgument(
232243
'failure_action must be either `pause` or `continue`.'
233244
)
234245
self['FailureAction'] = failure_action

docker/utils/utils.py

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -175,11 +175,17 @@ def should_check_directory(directory_path, exclude_patterns, include_patterns):
175175
# docker logic (2016-10-27):
176176
# https://github.com/docker/docker/blob/bc52939b0455116ab8e0da67869ec81c1a1c3e2c/pkg/archive/archive.go#L640-L671
177177

178-
path_with_slash = directory_path + os.sep
179-
possible_child_patterns = [pattern for pattern in include_patterns if
180-
(pattern + os.sep).startswith(path_with_slash)]
181-
directory_included = should_include(directory_path, exclude_patterns,
182-
include_patterns)
178+
def normalize_path(path):
179+
return path.replace(os.path.sep, '/')
180+
181+
path_with_slash = normalize_path(directory_path) + '/'
182+
possible_child_patterns = [
183+
pattern for pattern in map(normalize_path, include_patterns)
184+
if (pattern + '/').startswith(path_with_slash)
185+
]
186+
directory_included = should_include(
187+
directory_path, exclude_patterns, include_patterns
188+
)
183189
return directory_included or len(possible_child_patterns) > 0
184190

185191

@@ -195,9 +201,11 @@ def get_paths(root, exclude_patterns, include_patterns, has_exceptions=False):
195201
# by mutating the dirs we're iterating over.
196202
# This looks strange, but is considered the correct way to skip
197203
# traversal. See https://docs.python.org/2/library/os.html#os.walk
198-
dirs[:] = [d for d in dirs if
199-
should_check_directory(os.path.join(parent, d),
200-
exclude_patterns, include_patterns)]
204+
dirs[:] = [
205+
d for d in dirs if should_check_directory(
206+
os.path.join(parent, d), exclude_patterns, include_patterns
207+
)
208+
]
201209

202210
for path in dirs:
203211
if should_include(os.path.join(parent, path),
@@ -213,7 +221,7 @@ def get_paths(root, exclude_patterns, include_patterns, has_exceptions=False):
213221

214222

215223
def match_path(path, pattern):
216-
pattern = pattern.rstrip('/')
224+
pattern = pattern.rstrip('/' + os.path.sep)
217225
if pattern:
218226
pattern = os.path.relpath(pattern)
219227

docker/version.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
1-
version = "2.0.0"
1+
version = "2.0.1"
22
version_info = tuple([int(d) for d in version.split("-")[0].split(".")])

docs/change-log.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,20 @@
11
Change log
22
==========
33

4+
2.0.1
5+
-----
6+
7+
[List of PRs / issues for this release](https://github.com/docker/docker-py/milestone/28?closed=1)
8+
9+
### Bugfixes
10+
11+
* Fixed a bug where forward slashes in some .dockerignore patterns weren't
12+
being parsed correctly on Windows
13+
* Fixed a bug where `Mount.parse_mount_string` would never set the read_only
14+
parameter on the resulting `Mount`.
15+
* Fixed a bug where `Mount.parse_mount_string` would incorrectly mark host
16+
binds as being of `volume` type.
17+
418
2.0.0
519
-----
620

tests/helpers.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,3 +74,7 @@ def force_leave_swarm(client):
7474
continue
7575
else:
7676
return
77+
78+
79+
def swarm_listen_addr():
80+
return '0.0.0.0:{0}'.format(random.randrange(10000, 25000))

tests/integration/api_service_test.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ class ServiceTest(BaseAPIIntegrationTest):
1010
def setUp(self):
1111
super(ServiceTest, self).setUp()
1212
self.client.leave_swarm(force=True)
13-
self.client.init_swarm('eth0')
13+
self.init_swarm()
1414

1515
def tearDown(self):
1616
super(ServiceTest, self).tearDown()

tests/integration/api_swarm_test.py

Lines changed: 13 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -17,39 +17,37 @@ def tearDown(self):
1717

1818
@requires_api_version('1.24')
1919
def test_init_swarm_simple(self):
20-
assert self.client.init_swarm('eth0')
20+
assert self.init_swarm()
2121

2222
@requires_api_version('1.24')
2323
def test_init_swarm_force_new_cluster(self):
2424
pytest.skip('Test stalls the engine on 1.12.0')
2525

26-
assert self.client.init_swarm('eth0')
26+
assert self.init_swarm()
2727
version_1 = self.client.inspect_swarm()['Version']['Index']
28-
assert self.client.init_swarm('eth0', force_new_cluster=True)
28+
assert self.client.init_swarm(force_new_cluster=True)
2929
version_2 = self.client.inspect_swarm()['Version']['Index']
3030
assert version_2 != version_1
3131

3232
@requires_api_version('1.24')
3333
def test_init_already_in_cluster(self):
34-
assert self.client.init_swarm('eth0')
34+
assert self.init_swarm()
3535
with pytest.raises(docker.errors.APIError):
36-
self.client.init_swarm('eth0')
36+
self.init_swarm()
3737

3838
@requires_api_version('1.24')
3939
def test_init_swarm_custom_raft_spec(self):
4040
spec = self.client.create_swarm_spec(
4141
snapshot_interval=5000, log_entries_for_slow_followers=1200
4242
)
43-
assert self.client.init_swarm(
44-
advertise_addr='eth0', swarm_spec=spec
45-
)
43+
assert self.init_swarm(swarm_spec=spec)
4644
swarm_info = self.client.inspect_swarm()
4745
assert swarm_info['Spec']['Raft']['SnapshotInterval'] == 5000
4846
assert swarm_info['Spec']['Raft']['LogEntriesForSlowFollowers'] == 1200
4947

5048
@requires_api_version('1.24')
5149
def test_leave_swarm(self):
52-
assert self.client.init_swarm('eth0')
50+
assert self.init_swarm()
5351
with pytest.raises(docker.errors.APIError) as exc_info:
5452
self.client.leave_swarm()
5553
exc_info.value.response.status_code == 500
@@ -61,7 +59,7 @@ def test_leave_swarm(self):
6159

6260
@requires_api_version('1.24')
6361
def test_update_swarm(self):
64-
assert self.client.init_swarm('eth0')
62+
assert self.init_swarm()
6563
swarm_info_1 = self.client.inspect_swarm()
6664
spec = self.client.create_swarm_spec(
6765
snapshot_interval=5000, log_entries_for_slow_followers=1200,
@@ -92,7 +90,7 @@ def test_update_swarm(self):
9290

9391
@requires_api_version('1.24')
9492
def test_update_swarm_name(self):
95-
assert self.client.init_swarm('eth0')
93+
assert self.init_swarm()
9694
swarm_info_1 = self.client.inspect_swarm()
9795
spec = self.client.create_swarm_spec(
9896
node_cert_expiry=7776000000000000, name='reimuhakurei'
@@ -110,7 +108,7 @@ def test_update_swarm_name(self):
110108

111109
@requires_api_version('1.24')
112110
def test_list_nodes(self):
113-
assert self.client.init_swarm('eth0')
111+
assert self.init_swarm()
114112
nodes_list = self.client.nodes()
115113
assert len(nodes_list) == 1
116114
node = nodes_list[0]
@@ -129,7 +127,7 @@ def test_list_nodes(self):
129127

130128
@requires_api_version('1.24')
131129
def test_inspect_node(self):
132-
assert self.client.init_swarm('eth0')
130+
assert self.init_swarm()
133131
nodes_list = self.client.nodes()
134132
assert len(nodes_list) == 1
135133
node = nodes_list[0]
@@ -139,7 +137,7 @@ def test_inspect_node(self):
139137

140138
@requires_api_version('1.24')
141139
def test_update_node(self):
142-
assert self.client.init_swarm('eth0')
140+
assert self.init_swarm()
143141
nodes_list = self.client.nodes()
144142
node = nodes_list[0]
145143
orig_spec = node['Spec']
@@ -162,7 +160,7 @@ def test_update_node(self):
162160

163161
@requires_api_version('1.24')
164162
def test_remove_main_node(self):
165-
assert self.client.init_swarm('eth0')
163+
assert self.init_swarm()
166164
nodes_list = self.client.nodes()
167165
node_id = nodes_list[0]['ID']
168166
with pytest.raises(docker.errors.NotFound):

tests/integration/base.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
from docker.utils import kwargs_from_env
66
import six
77

8+
from .. import helpers
89

910
BUSYBOX = 'busybox:buildroot-2014.02'
1011

@@ -90,3 +91,8 @@ def execute(self, container, cmd, exit_code=0, **kwargs):
9091
msg = "Expected `{}` to exit with code {} but returned {}:\n{}".format(
9192
" ".join(cmd), exit_code, actual_exit_code, output)
9293
assert actual_exit_code == exit_code, msg
94+
95+
def init_swarm(self, **kwargs):
96+
return self.client.init_swarm(
97+
'eth0', listen_addr=helpers.swarm_listen_addr(), **kwargs
98+
)

0 commit comments

Comments
 (0)