From 1b90e10cf0c77e770cb217d99f6e9fe3b4833408 Mon Sep 17 00:00:00 2001 From: Hannes Ljungberg Date: Thu, 27 Jun 2019 22:24:35 +0200 Subject: [PATCH] docker_swarm_service: Make secret_id and config_id optional (#58299) * Lookup secret id by name if not set * Lookup config id by name if not set * Add changelog fragment * Remove usage of secret/config_id in examples * Python 2.6 compat * Extend secrets and configs tests --- ...rvice-remove-required-config-secret-id.yml | 2 + .../cloud/docker/docker_swarm_service.py | 88 +++++++++++++++---- .../tasks/tests/configs.yml | 41 +++++++-- .../tasks/tests/secrets.yml | 41 +++++++-- 4 files changed, 143 insertions(+), 29 deletions(-) create mode 100644 changelogs/fragments/58299-docker_swarm_service-remove-required-config-secret-id.yml diff --git a/changelogs/fragments/58299-docker_swarm_service-remove-required-config-secret-id.yml b/changelogs/fragments/58299-docker_swarm_service-remove-required-config-secret-id.yml new file mode 100644 index 00000000000..c9c965e369e --- /dev/null +++ b/changelogs/fragments/58299-docker_swarm_service-remove-required-config-secret-id.yml @@ -0,0 +1,2 @@ +minor_changes: +- "docker_swarm_service - Remove requirement of ``secret_id`` on ``secrets`` and ``config_id`` on ``configs``." diff --git a/lib/ansible/modules/cloud/docker/docker_swarm_service.py b/lib/ansible/modules/cloud/docker/docker_swarm_service.py index e33fba7a314..2825c4cf473 100644 --- a/lib/ansible/modules/cloud/docker/docker_swarm_service.py +++ b/lib/ansible/modules/cloud/docker/docker_swarm_service.py @@ -44,7 +44,6 @@ options: description: - Config's ID. type: str - required: yes config_name: description: - Config's name as defined at its creation. @@ -598,7 +597,6 @@ options: description: - Secret's ID. type: str - required: yes secret_name: description: - Secret's name as defined at its creation. @@ -993,8 +991,7 @@ EXAMPLES = ''' name: myservice image: alpine:edge configs: - - config_id: myconfig_id - config_name: myconfig_name + - config_name: myconfig_name filename: "/tmp/config.txt" - name: Set networks @@ -1009,8 +1006,7 @@ EXAMPLES = ''' name: myservice image: alpine:edge secrets: - - secret_id: mysecret_id - secret_name: mysecret_name + - secret_name: mysecret_name filename: "/run/secrets/secret.txt" - name: Start service with healthcheck @@ -1480,7 +1476,9 @@ class DockerService(DockerBaseClass): } @classmethod - def from_ansible_params(cls, ap, old_service, image_digest, can_update_networks): + def from_ansible_params( + cls, ap, old_service, image_digest, can_update_networks, secret_ids, config_ids + ): s = DockerService() s.image = image_digest s.can_update_networks = can_update_networks @@ -1616,9 +1614,10 @@ class DockerService(DockerBaseClass): s.configs = [] for param_m in ap['configs']: service_c = {} - service_c['config_id'] = param_m['config_id'] - service_c['config_name'] = param_m['config_name'] - service_c['filename'] = param_m['filename'] or service_c['config_name'] + config_name = param_m['config_name'] + service_c['config_id'] = param_m['config_id'] or config_ids[config_name] + service_c['config_name'] = config_name + service_c['filename'] = param_m['filename'] or config_name service_c['uid'] = param_m['uid'] service_c['gid'] = param_m['gid'] service_c['mode'] = param_m['mode'] @@ -1628,9 +1627,10 @@ class DockerService(DockerBaseClass): s.secrets = [] for param_m in ap['secrets']: service_s = {} - service_s['secret_id'] = param_m['secret_id'] - service_s['secret_name'] = param_m['secret_name'] - service_s['filename'] = param_m['filename'] or service_s['secret_name'] + secret_name = param_m['secret_name'] + service_s['secret_id'] = param_m['secret_id'] or secret_ids[secret_name] + service_s['secret_name'] = secret_name + service_s['filename'] = param_m['filename'] or secret_name service_s['uid'] = param_m['uid'] service_s['gid'] = param_m['gid'] service_s['mode'] = param_m['mode'] @@ -2326,6 +2326,54 @@ class DockerServiceManager(object): self.client.docker_py_version >= LooseVersion('2.7') ) + def get_missing_secret_ids(self): + """ + Resolve missing secret ids by looking them up by name + """ + secret_names = [ + secret['secret_name'] + for secret in self.client.module.params.get('secrets') or [] + if secret['secret_id'] is None + ] + if not secret_names: + return {} + secrets = self.client.secrets(filters={'name': secret_names}) + secrets = dict( + (secret['Spec']['Name'], secret['ID']) + for secret in secrets + if secret['Spec']['Name'] in secret_names + ) + for secret_name in secret_names: + if secret_name not in secrets: + self.client.fail( + 'Could not find a secret named "%s"' % secret_name + ) + return secrets + + def get_missing_config_ids(self): + """ + Resolve missing config ids by looking them up by name + """ + config_names = [ + config['config_name'] + for config in self.client.module.params.get('configs') or [] + if config['config_id'] is None + ] + if not config_names: + return {} + configs = self.client.configs(filters={'name': config_names}) + configs = dict( + (config['Spec']['Name'], config['ID']) + for config in configs + if config['Spec']['Name'] in config_names + ) + for config_name in config_names: + if config_name not in configs: + self.client.fail( + 'Could not find a config named "%s"' % config_name + ) + return configs + def run(self): self.diff_tracker = DifferenceTracker() module = self.client.module @@ -2351,11 +2399,15 @@ class DockerServiceManager(object): ) try: can_update_networks = self.can_update_networks() + secret_ids = self.get_missing_secret_ids() + config_ids = self.get_missing_config_ids() new_service = DockerService.from_ansible_params( module.params, current_service, image_digest, - can_update_networks + can_update_networks, + secret_ids, + config_ids ) except Exception as e: return self.client.fail( @@ -2375,7 +2427,9 @@ class DockerServiceManager(object): msg = 'Service removed' changed = True else: - changed, differences, need_rebuild, force_update = new_service.compare(current_service) + changed, differences, need_rebuild, force_update = new_service.compare( + current_service + ) if changed: self.diff_tracker.merge(differences) if need_rebuild: @@ -2504,7 +2558,7 @@ def main(): tmpfs_mode=dict(type='int') )), configs=dict(type='list', elements='dict', options=dict( - config_id=dict(type='str', required=True), + config_id=dict(type='str'), config_name=dict(type='str', required=True), filename=dict(type='str'), uid=dict(type='str'), @@ -2512,7 +2566,7 @@ def main(): mode=dict(type='int'), )), secrets=dict(type='list', elements='dict', options=dict( - secret_id=dict(type='str', required=True), + secret_id=dict(type='str'), secret_name=dict(type='str', required=True), filename=dict(type='str'), uid=dict(type='str'), diff --git a/test/integration/targets/docker_swarm_service/tasks/tests/configs.yml b/test/integration/targets/docker_swarm_service/tasks/tests/configs.yml index e995841d188..7962cf11b0a 100644 --- a/test/integration/targets/docker_swarm_service/tasks/tests/configs.yml +++ b/test/integration/targets/docker_swarm_service/tasks/tests/configs.yml @@ -48,8 +48,7 @@ resolve_image: no command: '/bin/sh -v -c "sleep 10m"' configs: - - config_id: "{{ config_result_1.config_id|default('') }}" - config_name: "{{ config_name_1 }}" + - config_name: "{{ config_name_1 }}" filename: "/tmp/{{ config_name_1 }}.txt" register: configs_2 ignore_errors: yes @@ -64,10 +63,38 @@ - config_id: "{{ config_result_1.config_id|default('') }}" config_name: "{{ config_name_1 }}" filename: "/tmp/{{ config_name_1 }}.txt" + - config_name: "{{ config_name_2 }}" + filename: "/tmp/{{ config_name_2 }}.txt" + register: configs_3 + ignore_errors: yes + +- name: configs (add idempotency) + docker_swarm_service: + name: "{{ service_name }}" + image: alpine:3.8 + resolve_image: no + command: '/bin/sh -v -c "sleep 10m"' + configs: + - config_name: "{{ config_name_1 }}" + filename: "/tmp/{{ config_name_1 }}.txt" - config_id: "{{ config_result_2.config_id|default('') }}" config_name: "{{ config_name_2 }}" filename: "/tmp/{{ config_name_2 }}.txt" - register: configs_3 + register: configs_4 + ignore_errors: yes + +- name: configs (add idempotency no id) + docker_swarm_service: + name: "{{ service_name }}" + image: alpine:3.8 + resolve_image: no + command: '/bin/sh -v -c "sleep 10m"' + configs: + - config_name: "{{ config_name_1 }}" + filename: "/tmp/{{ config_name_1 }}.txt" + - config_name: "{{ config_name_2 }}" + filename: "/tmp/{{ config_name_2 }}.txt" + register: configs_5 ignore_errors: yes - name: configs (empty) @@ -77,7 +104,7 @@ resolve_image: no command: '/bin/sh -v -c "sleep 10m"' configs: [] - register: configs_4 + register: configs_6 ignore_errors: yes - name: configs (empty idempotency) @@ -87,7 +114,7 @@ resolve_image: no command: '/bin/sh -v -c "sleep 10m"' configs: [] - register: configs_5 + register: configs_7 ignore_errors: yes - name: cleanup @@ -101,8 +128,10 @@ - configs_1 is changed - configs_2 is not changed - configs_3 is changed - - configs_4 is changed + - configs_4 is not changed - configs_5 is not changed + - configs_6 is changed + - configs_7 is not changed when: docker_api_version is version('1.30', '>=') and docker_py_version is version('2.6.0', '>=') - assert: diff --git a/test/integration/targets/docker_swarm_service/tasks/tests/secrets.yml b/test/integration/targets/docker_swarm_service/tasks/tests/secrets.yml index 7ac2b4aa6ef..5d23ca50c06 100644 --- a/test/integration/targets/docker_swarm_service/tasks/tests/secrets.yml +++ b/test/integration/targets/docker_swarm_service/tasks/tests/secrets.yml @@ -48,8 +48,7 @@ resolve_image: no command: '/bin/sh -v -c "sleep 10m"' secrets: - - secret_id: "{{ secret_result_1.secret_id|default('') }}" - secret_name: "{{ secret_name_1 }}" + - secret_name: "{{ secret_name_1 }}" filename: "/run/secrets/{{ secret_name_1 }}.txt" register: secrets_2 ignore_errors: yes @@ -64,10 +63,38 @@ - secret_id: "{{ secret_result_1.secret_id|default('') }}" secret_name: "{{ secret_name_1 }}" filename: "/run/secrets/{{ secret_name_1 }}.txt" + - secret_name: "{{ secret_name_2 }}" + filename: "/run/secrets/{{ secret_name_2 }}.txt" + register: secrets_3 + ignore_errors: yes + +- name: secrets (add idempotency) + docker_swarm_service: + name: "{{ service_name }}" + image: alpine:3.8 + resolve_image: no + command: '/bin/sh -v -c "sleep 10m"' + secrets: + - secret_name: "{{ secret_name_1 }}" + filename: "/run/secrets/{{ secret_name_1 }}.txt" - secret_id: "{{ secret_result_2.secret_id|default('') }}" secret_name: "{{ secret_name_2 }}" filename: "/run/secrets/{{ secret_name_2 }}.txt" - register: secrets_3 + register: secrets_4 + ignore_errors: yes + +- name: secrets (add idempotency no id) + docker_swarm_service: + name: "{{ service_name }}" + image: alpine:3.8 + resolve_image: no + command: '/bin/sh -v -c "sleep 10m"' + secrets: + - secret_name: "{{ secret_name_1 }}" + filename: "/run/secrets/{{ secret_name_1 }}.txt" + - secret_name: "{{ secret_name_2 }}" + filename: "/run/secrets/{{ secret_name_2 }}.txt" + register: secrets_5 ignore_errors: yes - name: secrets (empty) @@ -77,7 +104,7 @@ resolve_image: no command: '/bin/sh -v -c "sleep 10m"' secrets: [] - register: secrets_4 + register: secrets_6 ignore_errors: yes - name: secrets (empty idempotency) @@ -87,7 +114,7 @@ resolve_image: no command: '/bin/sh -v -c "sleep 10m"' secrets: [] - register: secrets_5 + register: secrets_7 ignore_errors: yes - name: cleanup @@ -101,8 +128,10 @@ - secrets_1 is changed - secrets_2 is not changed - secrets_3 is changed - - secrets_4 is changed + - secrets_4 is not changed - secrets_5 is not changed + - secrets_6 is changed + - secrets_7 is not changed when: docker_api_version is version('1.25', '>=') and docker_py_version is version('2.4.0', '>=') - assert: that: