diff options
author | Joffrey F <joffrey@docker.com> | 2017-11-02 18:46:25 -0700 |
---|---|---|
committer | Joffrey F <joffrey@docker.com> | 2017-11-03 15:33:45 -0700 |
commit | 37c135060d71020ed095efdd2206548786bd2563 (patch) | |
tree | 797ac66d4c9314a550578c76cf06212d86844b2c | |
parent | 1ce93ac6e75f0aaf83aededcc165e1e9fe49c52e (diff) | |
download | docker-py-37c135060d71020ed095efdd2206548786bd2563.tar.gz |
Update SwarmSpec to include new parametersupdate_swarm_spec
Signed-off-by: Joffrey F <joffrey@docker.com>
-rw-r--r-- | docker/api/swarm.py | 29 | ||||
-rw-r--r-- | docker/models/swarm.py | 17 | ||||
-rw-r--r-- | docker/types/swarm.py | 92 | ||||
-rw-r--r-- | docs/api.rst | 2 | ||||
-rw-r--r-- | tests/integration/api_swarm_test.py | 38 |
5 files changed, 160 insertions, 18 deletions
diff --git a/docker/api/swarm.py b/docker/api/swarm.py index 4fa0c4a..576fd79 100644 --- a/docker/api/swarm.py +++ b/docker/api/swarm.py @@ -9,8 +9,8 @@ class SwarmApiMixin(object): def create_swarm_spec(self, *args, **kwargs): """ - Create a ``docker.types.SwarmSpec`` instance that can be used as the - ``swarm_spec`` argument in + Create a :py:class:`docker.types.SwarmSpec` instance that can be used + as the ``swarm_spec`` argument in :py:meth:`~docker.api.swarm.SwarmApiMixin.init_swarm`. Args: @@ -29,13 +29,25 @@ class SwarmApiMixin(object): dispatcher_heartbeat_period (int): The delay for an agent to send a heartbeat to the dispatcher. node_cert_expiry (int): Automatic expiry for nodes certificates. - external_ca (dict): Configuration for forwarding signing requests - to an external certificate authority. Use - ``docker.types.SwarmExternalCA``. + external_cas (:py:class:`list`): Configuration for forwarding + signing requests to an external certificate authority. Use + a list of :py:class:`docker.types.SwarmExternalCA`. name (string): Swarm's name + labels (dict): User-defined key/value metadata. + signing_ca_cert (str): The desired signing CA certificate for all + swarm node TLS leaf certificates, in PEM format. + signing_ca_key (str): The desired signing CA key for all swarm + node TLS leaf certificates, in PEM format. + ca_force_rotate (int): An integer whose purpose is to force swarm + to generate a new signing CA certificate and key, if none have + been specified. + autolock_managers (boolean): If set, generate a key and use it to + lock data stored on the managers. + log_driver (DriverConfig): The default log driver to use for tasks + created in the orchestrator. Returns: - ``docker.types.SwarmSpec`` instance. + :py:class:`docker.types.SwarmSpec` Raises: :py:class:`docker.errors.APIError` @@ -51,7 +63,10 @@ class SwarmApiMixin(object): force_new_cluster=False, swarm_spec=spec ) """ - return types.SwarmSpec(*args, **kwargs) + ext_ca = kwargs.pop('external_ca', None) + if ext_ca: + kwargs['external_cas'] = [ext_ca] + return types.SwarmSpec(self._version, *args, **kwargs) @utils.minimum_version('1.24') def init_swarm(self, advertise_addr=None, listen_addr='0.0.0.0:2377', diff --git a/docker/models/swarm.py b/docker/models/swarm.py index df3afd3..5a253c5 100644 --- a/docker/models/swarm.py +++ b/docker/models/swarm.py @@ -1,6 +1,5 @@ from docker.api import APIClient from docker.errors import APIError -from docker.types import SwarmSpec from .resource import Model @@ -72,6 +71,18 @@ class Swarm(Model): to an external certificate authority. Use ``docker.types.SwarmExternalCA``. name (string): Swarm's name + labels (dict): User-defined key/value metadata. + signing_ca_cert (str): The desired signing CA certificate for all + swarm node TLS leaf certificates, in PEM format. + signing_ca_key (str): The desired signing CA key for all swarm + node TLS leaf certificates, in PEM format. + ca_force_rotate (int): An integer whose purpose is to force swarm + to generate a new signing CA certificate and key, if none have + been specified. + autolock_managers (boolean): If set, generate a key and use it to + lock data stored on the managers. + log_driver (DriverConfig): The default log driver to use for tasks + created in the orchestrator. Returns: ``True`` if the request went through. @@ -94,7 +105,7 @@ class Swarm(Model): 'listen_addr': listen_addr, 'force_new_cluster': force_new_cluster } - init_kwargs['swarm_spec'] = SwarmSpec(**kwargs) + init_kwargs['swarm_spec'] = self.client.api.create_swarm_spec(**kwargs) self.client.api.init_swarm(**init_kwargs) self.reload() @@ -143,7 +154,7 @@ class Swarm(Model): return self.client.api.update_swarm( version=self.version, - swarm_spec=SwarmSpec(**kwargs), + swarm_spec=self.client.api.create_swarm_spec(**kwargs), rotate_worker_token=rotate_worker_token, rotate_manager_token=rotate_manager_token ) diff --git a/docker/types/swarm.py b/docker/types/swarm.py index 49beaa1..9687a82 100644 --- a/docker/types/swarm.py +++ b/docker/types/swarm.py @@ -1,9 +1,21 @@ +from ..errors import InvalidVersion +from ..utils import version_lt + + class SwarmSpec(dict): - def __init__(self, task_history_retention_limit=None, + """ + Describe a Swarm's configuration and options. Use + :py:meth:`~docker.api.swarm.SwarmApiMixin.create_swarm_spec` + to instantiate. + """ + def __init__(self, version, task_history_retention_limit=None, snapshot_interval=None, keep_old_snapshots=None, log_entries_for_slow_followers=None, heartbeat_tick=None, election_tick=None, dispatcher_heartbeat_period=None, - node_cert_expiry=None, external_ca=None, name=None): + node_cert_expiry=None, external_cas=None, name=None, + labels=None, signing_ca_cert=None, signing_ca_key=None, + ca_force_rotate=None, autolock_managers=None, + log_driver=None): if task_history_retention_limit is not None: self['Orchestration'] = { 'TaskHistoryRetentionLimit': task_history_retention_limit @@ -26,18 +38,82 @@ class SwarmSpec(dict): 'HeartbeatPeriod': dispatcher_heartbeat_period } - if node_cert_expiry or external_ca: - self['CAConfig'] = { - 'NodeCertExpiry': node_cert_expiry, - 'ExternalCA': external_ca - } + ca_config = {} + if node_cert_expiry is not None: + ca_config['NodeCertExpiry'] = node_cert_expiry + if external_cas: + if version_lt(version, '1.25'): + if len(external_cas) > 1: + raise InvalidVersion( + 'Support for multiple external CAs is not available ' + 'for API version < 1.25' + ) + ca_config['ExternalCA'] = external_cas[0] + else: + ca_config['ExternalCAs'] = external_cas + if signing_ca_key: + if version_lt(version, '1.30'): + raise InvalidVersion( + 'signing_ca_key is not supported in API version < 1.30' + ) + ca_config['SigningCAKey'] = signing_ca_key + if signing_ca_cert: + if version_lt(version, '1.30'): + raise InvalidVersion( + 'signing_ca_cert is not supported in API version < 1.30' + ) + ca_config['SigningCACert'] = signing_ca_cert + if ca_force_rotate is not None: + if version_lt(version, '1.30'): + raise InvalidVersion( + 'force_rotate is not supported in API version < 1.30' + ) + ca_config['ForceRotate'] = ca_force_rotate + if ca_config: + self['CAConfig'] = ca_config + + if autolock_managers is not None: + if version_lt(version, '1.25'): + raise InvalidVersion( + 'autolock_managers is not supported in API version < 1.25' + ) + + self['EncryptionConfig'] = {'AutoLockManagers': autolock_managers} + + if log_driver is not None: + if version_lt(version, '1.25'): + raise InvalidVersion( + 'log_driver is not supported in API version < 1.25' + ) + + self['TaskDefaults'] = {'LogDriver': log_driver} if name is not None: self['Name'] = name + if labels is not None: + self['Labels'] = labels class SwarmExternalCA(dict): - def __init__(self, url, protocol=None, options=None): + """ + Configuration for forwarding signing requests to an external + certificate authority. + + Args: + url (string): URL where certificate signing requests should be + sent. + protocol (string): Protocol for communication with the external CA. + options (dict): An object with key/value pairs that are interpreted + as protocol-specific options for the external CA driver. + ca_cert (string): The root CA certificate (in PEM format) this + external CA uses to issue TLS certificates (assumed to be to + the current swarm root CA certificate if not provided). + + + + """ + def __init__(self, url, protocol=None, options=None, ca_cert=None): self['URL'] = url self['Protocol'] = protocol self['Options'] = options + self['CACert'] = ca_cert diff --git a/docs/api.rst b/docs/api.rst index 18993ad..ff466a1 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -147,5 +147,7 @@ Configuration types .. autoclass:: RestartPolicy .. autoclass:: SecretReference .. autoclass:: ServiceMode +.. autoclass:: SwarmExternalCA +.. autoclass:: SwarmSpec(*args, **kwargs) .. autoclass:: TaskTemplate .. autoclass:: UpdateConfig diff --git a/tests/integration/api_swarm_test.py b/tests/integration/api_swarm_test.py index 666c689..34b0879 100644 --- a/tests/integration/api_swarm_test.py +++ b/tests/integration/api_swarm_test.py @@ -45,6 +45,44 @@ class SwarmTest(BaseAPIIntegrationTest): assert swarm_info['Spec']['Raft']['SnapshotInterval'] == 5000 assert swarm_info['Spec']['Raft']['LogEntriesForSlowFollowers'] == 1200 + @requires_api_version('1.30') + def test_init_swarm_with_ca_config(self): + spec = self.client.create_swarm_spec( + node_cert_expiry=7776000000000000, ca_force_rotate=6000000000000 + ) + + assert self.init_swarm(swarm_spec=spec) + swarm_info = self.client.inspect_swarm() + assert swarm_info['Spec']['CAConfig']['NodeCertExpiry'] == ( + spec['CAConfig']['NodeCertExpiry'] + ) + assert swarm_info['Spec']['CAConfig']['ForceRotate'] == ( + spec['CAConfig']['ForceRotate'] + ) + + @requires_api_version('1.25') + def test_init_swarm_with_autolock_managers(self): + spec = self.client.create_swarm_spec(autolock_managers=True) + assert self.init_swarm(swarm_spec=spec) + swarm_info = self.client.inspect_swarm() + + assert ( + swarm_info['Spec']['EncryptionConfig']['AutoLockManagers'] is True + ) + + @requires_api_version('1.25') + @pytest.mark.xfail( + reason="This doesn't seem to be taken into account by the engine" + ) + def test_init_swarm_with_log_driver(self): + spec = {'TaskDefaults': {'LogDriver': {'Name': 'syslog'}}} + assert self.init_swarm(swarm_spec=spec) + swarm_info = self.client.inspect_swarm() + + assert swarm_info['Spec']['TaskDefaults']['LogDriver']['Name'] == ( + 'syslog' + ) + @requires_api_version('1.24') def test_leave_swarm(self): assert self.init_swarm() |