Skip to content

Commit ecca6e0

Browse files
committed
Update SwarmSpec to include new parameters
Signed-off-by: Joffrey F <[email protected]>
1 parent a0f6758 commit ecca6e0

File tree

5 files changed

+160
-18
lines changed

5 files changed

+160
-18
lines changed

docker/api/swarm.py

Lines changed: 22 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@ class SwarmApiMixin(object):
99

1010
def create_swarm_spec(self, *args, **kwargs):
1111
"""
12-
Create a ``docker.types.SwarmSpec`` instance that can be used as the
13-
``swarm_spec`` argument in
12+
Create a :py:class:`docker.types.SwarmSpec` instance that can be used
13+
as the ``swarm_spec`` argument in
1414
:py:meth:`~docker.api.swarm.SwarmApiMixin.init_swarm`.
1515
1616
Args:
@@ -29,13 +29,25 @@ def create_swarm_spec(self, *args, **kwargs):
2929
dispatcher_heartbeat_period (int): The delay for an agent to send
3030
a heartbeat to the dispatcher.
3131
node_cert_expiry (int): Automatic expiry for nodes certificates.
32-
external_ca (dict): Configuration for forwarding signing requests
33-
to an external certificate authority. Use
34-
``docker.types.SwarmExternalCA``.
32+
external_cas (:py:class:`list`): Configuration for forwarding
33+
signing requests to an external certificate authority. Use
34+
a list of :py:class:`docker.types.SwarmExternalCA`.
3535
name (string): Swarm's name
36+
labels (dict): User-defined key/value metadata.
37+
signing_ca_cert (str): The desired signing CA certificate for all
38+
swarm node TLS leaf certificates, in PEM format.
39+
signing_ca_key (str): The desired signing CA key for all swarm
40+
node TLS leaf certificates, in PEM format.
41+
ca_force_rotate (int): An integer whose purpose is to force swarm
42+
to generate a new signing CA certificate and key, if none have
43+
been specified.
44+
autolock_managers (boolean): If set, generate a key and use it to
45+
lock data stored on the managers.
46+
log_driver (DriverConfig): The default log driver to use for tasks
47+
created in the orchestrator.
3648
3749
Returns:
38-
``docker.types.SwarmSpec`` instance.
50+
:py:class:`docker.types.SwarmSpec`
3951
4052
Raises:
4153
:py:class:`docker.errors.APIError`
@@ -51,7 +63,10 @@ def create_swarm_spec(self, *args, **kwargs):
5163
force_new_cluster=False, swarm_spec=spec
5264
)
5365
"""
54-
return types.SwarmSpec(*args, **kwargs)
66+
ext_ca = kwargs.pop('external_ca', None)
67+
if ext_ca:
68+
kwargs['external_cas'] = [ext_ca]
69+
return types.SwarmSpec(self._version, *args, **kwargs)
5570

5671
@utils.minimum_version('1.24')
5772
def init_swarm(self, advertise_addr=None, listen_addr='0.0.0.0:2377',

docker/models/swarm.py

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
from docker.api import APIClient
22
from docker.errors import APIError
3-
from docker.types import SwarmSpec
43
from .resource import Model
54

65

@@ -72,6 +71,18 @@ def init(self, advertise_addr=None, listen_addr='0.0.0.0:2377',
7271
to an external certificate authority. Use
7372
``docker.types.SwarmExternalCA``.
7473
name (string): Swarm's name
74+
labels (dict): User-defined key/value metadata.
75+
signing_ca_cert (str): The desired signing CA certificate for all
76+
swarm node TLS leaf certificates, in PEM format.
77+
signing_ca_key (str): The desired signing CA key for all swarm
78+
node TLS leaf certificates, in PEM format.
79+
ca_force_rotate (int): An integer whose purpose is to force swarm
80+
to generate a new signing CA certificate and key, if none have
81+
been specified.
82+
autolock_managers (boolean): If set, generate a key and use it to
83+
lock data stored on the managers.
84+
log_driver (DriverConfig): The default log driver to use for tasks
85+
created in the orchestrator.
7586
7687
Returns:
7788
``True`` if the request went through.
@@ -94,7 +105,7 @@ def init(self, advertise_addr=None, listen_addr='0.0.0.0:2377',
94105
'listen_addr': listen_addr,
95106
'force_new_cluster': force_new_cluster
96107
}
97-
init_kwargs['swarm_spec'] = SwarmSpec(**kwargs)
108+
init_kwargs['swarm_spec'] = self.client.api.create_swarm_spec(**kwargs)
98109
self.client.api.init_swarm(**init_kwargs)
99110
self.reload()
100111

@@ -143,7 +154,7 @@ def update(self, rotate_worker_token=False, rotate_manager_token=False,
143154

144155
return self.client.api.update_swarm(
145156
version=self.version,
146-
swarm_spec=SwarmSpec(**kwargs),
157+
swarm_spec=self.client.api.create_swarm_spec(**kwargs),
147158
rotate_worker_token=rotate_worker_token,
148159
rotate_manager_token=rotate_manager_token
149160
)

docker/types/swarm.py

Lines changed: 84 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,21 @@
1+
from ..errors import InvalidVersion
2+
from ..utils import version_lt
3+
4+
15
class SwarmSpec(dict):
2-
def __init__(self, task_history_retention_limit=None,
6+
"""
7+
Describe a Swarm's configuration and options. Use
8+
:py:meth:`~docker.api.swarm.SwarmApiMixin.create_swarm_spec`
9+
to instantiate.
10+
"""
11+
def __init__(self, version, task_history_retention_limit=None,
312
snapshot_interval=None, keep_old_snapshots=None,
413
log_entries_for_slow_followers=None, heartbeat_tick=None,
514
election_tick=None, dispatcher_heartbeat_period=None,
6-
node_cert_expiry=None, external_ca=None, name=None):
15+
node_cert_expiry=None, external_cas=None, name=None,
16+
labels=None, signing_ca_cert=None, signing_ca_key=None,
17+
ca_force_rotate=None, autolock_managers=None,
18+
log_driver=None):
719
if task_history_retention_limit is not None:
820
self['Orchestration'] = {
921
'TaskHistoryRetentionLimit': task_history_retention_limit
@@ -26,18 +38,82 @@ def __init__(self, task_history_retention_limit=None,
2638
'HeartbeatPeriod': dispatcher_heartbeat_period
2739
}
2840

29-
if node_cert_expiry or external_ca:
30-
self['CAConfig'] = {
31-
'NodeCertExpiry': node_cert_expiry,
32-
'ExternalCA': external_ca
33-
}
41+
ca_config = {}
42+
if node_cert_expiry is not None:
43+
ca_config['NodeCertExpiry'] = node_cert_expiry
44+
if external_cas:
45+
if version_lt(version, '1.25'):
46+
if len(external_cas) > 1:
47+
raise InvalidVersion(
48+
'Support for multiple external CAs is not available '
49+
'for API version < 1.25'
50+
)
51+
ca_config['ExternalCA'] = external_cas[0]
52+
else:
53+
ca_config['ExternalCAs'] = external_cas
54+
if signing_ca_key:
55+
if version_lt(version, '1.30'):
56+
raise InvalidVersion(
57+
'signing_ca_key is not supported in API version < 1.30'
58+
)
59+
ca_config['SigningCAKey'] = signing_ca_key
60+
if signing_ca_cert:
61+
if version_lt(version, '1.30'):
62+
raise InvalidVersion(
63+
'signing_ca_cert is not supported in API version < 1.30'
64+
)
65+
ca_config['SigningCACert'] = signing_ca_cert
66+
if ca_force_rotate is not None:
67+
if version_lt(version, '1.30'):
68+
raise InvalidVersion(
69+
'force_rotate is not supported in API version < 1.30'
70+
)
71+
ca_config['ForceRotate'] = ca_force_rotate
72+
if ca_config:
73+
self['CAConfig'] = ca_config
74+
75+
if autolock_managers is not None:
76+
if version_lt(version, '1.25'):
77+
raise InvalidVersion(
78+
'autolock_managers is not supported in API version < 1.25'
79+
)
80+
81+
self['EncryptionConfig'] = {'AutoLockManagers': autolock_managers}
82+
83+
if log_driver is not None:
84+
if version_lt(version, '1.25'):
85+
raise InvalidVersion(
86+
'log_driver is not supported in API version < 1.25'
87+
)
88+
89+
self['TaskDefaults'] = {'LogDriver': log_driver}
3490

3591
if name is not None:
3692
self['Name'] = name
93+
if labels is not None:
94+
self['Labels'] = labels
3795

3896

3997
class SwarmExternalCA(dict):
40-
def __init__(self, url, protocol=None, options=None):
98+
"""
99+
Configuration for forwarding signing requests to an external
100+
certificate authority.
101+
102+
Args:
103+
url (string): URL where certificate signing requests should be
104+
sent.
105+
protocol (string): Protocol for communication with the external CA.
106+
options (dict): An object with key/value pairs that are interpreted
107+
as protocol-specific options for the external CA driver.
108+
ca_cert (string): The root CA certificate (in PEM format) this
109+
external CA uses to issue TLS certificates (assumed to be to
110+
the current swarm root CA certificate if not provided).
111+
112+
113+
114+
"""
115+
def __init__(self, url, protocol=None, options=None, ca_cert=None):
41116
self['URL'] = url
42117
self['Protocol'] = protocol
43118
self['Options'] = options
119+
self['CACert'] = ca_cert

docs/api.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,5 +147,7 @@ Configuration types
147147
.. autoclass:: RestartPolicy
148148
.. autoclass:: SecretReference
149149
.. autoclass:: ServiceMode
150+
.. autoclass:: SwarmExternalCA
151+
.. autoclass:: SwarmSpec(*args, **kwargs)
150152
.. autoclass:: TaskTemplate
151153
.. autoclass:: UpdateConfig

tests/integration/api_swarm_test.py

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,44 @@ def test_init_swarm_custom_raft_spec(self):
4545
assert swarm_info['Spec']['Raft']['SnapshotInterval'] == 5000
4646
assert swarm_info['Spec']['Raft']['LogEntriesForSlowFollowers'] == 1200
4747

48+
@requires_api_version('1.30')
49+
def test_init_swarm_with_ca_config(self):
50+
spec = self.client.create_swarm_spec(
51+
node_cert_expiry=7776000000000000, ca_force_rotate=6000000000000
52+
)
53+
54+
assert self.init_swarm(swarm_spec=spec)
55+
swarm_info = self.client.inspect_swarm()
56+
assert swarm_info['Spec']['CAConfig']['NodeCertExpiry'] == (
57+
spec['CAConfig']['NodeCertExpiry']
58+
)
59+
assert swarm_info['Spec']['CAConfig']['ForceRotate'] == (
60+
spec['CAConfig']['ForceRotate']
61+
)
62+
63+
@requires_api_version('1.25')
64+
def test_init_swarm_with_autolock_managers(self):
65+
spec = self.client.create_swarm_spec(autolock_managers=True)
66+
assert self.init_swarm(swarm_spec=spec)
67+
swarm_info = self.client.inspect_swarm()
68+
69+
assert (
70+
swarm_info['Spec']['EncryptionConfig']['AutoLockManagers'] is True
71+
)
72+
73+
@requires_api_version('1.25')
74+
@pytest.mark.xfail(
75+
reason="This doesn't seem to be taken into account by the engine"
76+
)
77+
def test_init_swarm_with_log_driver(self):
78+
spec = {'TaskDefaults': {'LogDriver': {'Name': 'syslog'}}}
79+
assert self.init_swarm(swarm_spec=spec)
80+
swarm_info = self.client.inspect_swarm()
81+
82+
assert swarm_info['Spec']['TaskDefaults']['LogDriver']['Name'] == (
83+
'syslog'
84+
)
85+
4886
@requires_api_version('1.24')
4987
def test_leave_swarm(self):
5088
assert self.init_swarm()

0 commit comments

Comments
 (0)