diff --git a/changelogs/fragments/203-docker_secret-config-data_src.yml b/changelogs/fragments/203-docker_secret-config-data_src.yml new file mode 100644 index 000000000..46ef106e8 --- /dev/null +++ b/changelogs/fragments/203-docker_secret-config-data_src.yml @@ -0,0 +1,3 @@ +minor_changes: +- "docker_config - add option ``data_src`` to read configuration data from target (https://github.com/ansible-collections/community.docker/issues/64, https://github.com/ansible-collections/community.docker/pull/203)." +- "docker_secret - add option ``data_src`` to read secret data from target (https://github.com/ansible-collections/community.docker/issues/64, https://github.com/ansible-collections/community.docker/pull/203)." diff --git a/plugins/modules/docker_config.py b/plugins/modules/docker_config.py index aad982797..c0d40d7b0 100644 --- a/plugins/modules/docker_config.py +++ b/plugins/modules/docker_config.py @@ -23,7 +23,8 @@ options: data: description: - - The value of the config. Required when state is C(present). + - The value of the config. + - Mutually exclusive with I(data_src). One of I(data) and I(data_src) is required if I(state=present). type: str data_is_b64: description: @@ -33,6 +34,12 @@ be decoded by this option. type: bool default: no + data_src: + description: + - The file on the target from which to read the config. + - Mutually exclusive with I(data). One of I(data) and I(data_src) is required if I(state=present). + type: path + version_added: 1.10.0 labels: description: - "A map of key:value meta data, where both the I(key) and I(value) are expected to be a string." @@ -84,6 +91,12 @@ data_is_b64: true state: present +- name: Create config foo (from a file on the target machine) + community.docker.docker_config: + name: foo + data_src: /path/to/config/file + state: present + - name: Change the config data community.docker.docker_config: name: foo @@ -183,6 +196,13 @@ def __init__(self, client, results): self.data = base64.b64decode(self.data) else: self.data = to_bytes(self.data) + data_src = parameters.get('data_src') + if data_src is not None: + try: + with open(data_src, 'rb') as f: + self.data = f.read() + except Exception as exc: + self.client.fail('Error while reading {src}: {error}'.format(src=data_src, error=to_native(exc))) self.labels = parameters.get('labels') self.force = parameters.get('force') self.data_key = None @@ -266,18 +286,24 @@ def main(): state=dict(type='str', default='present', choices=['absent', 'present']), data=dict(type='str'), data_is_b64=dict(type='bool', default=False), + data_src=dict(type='path'), labels=dict(type='dict'), force=dict(type='bool', default=False) ) required_if = [ - ('state', 'present', ['data']) + ('state', 'present', ['data', 'data_src'], True), + ] + + mutually_exclusive = [ + ('data', 'data_src'), ] client = AnsibleDockerClient( argument_spec=argument_spec, supports_check_mode=True, required_if=required_if, + mutually_exclusive=mutually_exclusive, min_docker_version='2.6.0', min_docker_api_version='1.30', ) diff --git a/plugins/modules/docker_secret.py b/plugins/modules/docker_secret.py index 888ec69ae..f5f25aa59 100644 --- a/plugins/modules/docker_secret.py +++ b/plugins/modules/docker_secret.py @@ -23,7 +23,8 @@ options: data: description: - - The value of the secret. Required when state is C(present). + - The value of the secret. + - Mutually exclusive with I(data_src). One of I(data) and I(data_src) is required if I(state=present). type: str data_is_b64: description: @@ -33,6 +34,12 @@ be decoded by this option. type: bool default: no + data_src: + description: + - The file on the target from which to read the secret. + - Mutually exclusive with I(data). One of I(data) and I(data_src) is required if I(state=present). + type: path + version_added: 1.10.0 labels: description: - "A map of key:value meta data, where both key and value are expected to be strings." @@ -83,6 +90,12 @@ data_is_b64: true state: present +- name: Create secret foo (from a file on the target machine) + community.docker.docker_secret: + name: foo + data_src: /path/to/secret/file + state: present + - name: Change the secret data community.docker.docker_secret: name: foo @@ -182,6 +195,13 @@ def __init__(self, client, results): self.data = base64.b64decode(self.data) else: self.data = to_bytes(self.data) + data_src = parameters.get('data_src') + if data_src is not None: + try: + with open(data_src, 'rb') as f: + self.data = f.read() + except Exception as exc: + self.client.fail('Error while reading {src}: {error}'.format(src=data_src, error=to_native(exc))) self.labels = parameters.get('labels') self.force = parameters.get('force') self.data_key = None @@ -268,18 +288,24 @@ def main(): state=dict(type='str', default='present', choices=['absent', 'present']), data=dict(type='str', no_log=True), data_is_b64=dict(type='bool', default=False), + data_src=dict(type='path'), labels=dict(type='dict'), force=dict(type='bool', default=False) ) required_if = [ - ('state', 'present', ['data']) + ('state', 'present', ['data', 'data_src'], True), + ] + + mutually_exclusive = [ + ('data', 'data_src'), ] client = AnsibleDockerClient( argument_spec=argument_spec, supports_check_mode=True, required_if=required_if, + mutually_exclusive=mutually_exclusive, min_docker_version='2.1.0', min_docker_api_version='1.25', ) diff --git a/tests/integration/targets/docker_config/meta/main.yml b/tests/integration/targets/docker_config/meta/main.yml index 07da8c6dd..a2419b9ad 100644 --- a/tests/integration/targets/docker_config/meta/main.yml +++ b/tests/integration/targets/docker_config/meta/main.yml @@ -1,3 +1,4 @@ --- dependencies: - setup_docker + - setup_remote_tmp_dir diff --git a/tests/integration/targets/docker_config/tasks/test_docker_config.yml b/tests/integration/targets/docker_config/tasks/test_docker_config.yml index 8220e8f54..7cd30c19f 100644 --- a/tests/integration/targets/docker_config/tasks/test_docker_config.yml +++ b/tests/integration/targets/docker_config/tasks/test_docker_config.yml @@ -37,7 +37,7 @@ assert: that: - 'output.failed' - - 'output.msg == "state is present but all of the following are missing: data"' + - 'output.msg == "state is present but any of the following are missing: data, data_src"' - name: Create config docker_config: @@ -80,6 +80,24 @@ that: - not output.changed + - name: Write config into file + copy: + dest: "{{ remote_tmp_dir }}/data" + content: |- + opensesame! + + - name: Create config again (from file) + docker_config: + name: db_password + data_src: "{{ remote_tmp_dir }}/data" + state: present + register: output + + - name: assert create config is idempotent + assert: + that: + - not output.changed + - name: Create config again (base64) docker_config: name: db_password diff --git a/tests/integration/targets/docker_secret/meta/main.yml b/tests/integration/targets/docker_secret/meta/main.yml index 07da8c6dd..a2419b9ad 100644 --- a/tests/integration/targets/docker_secret/meta/main.yml +++ b/tests/integration/targets/docker_secret/meta/main.yml @@ -1,3 +1,4 @@ --- dependencies: - setup_docker + - setup_remote_tmp_dir diff --git a/tests/integration/targets/docker_secret/tasks/test_secrets.yml b/tests/integration/targets/docker_secret/tasks/test_secrets.yml index 2c0784889..7a6509562 100644 --- a/tests/integration/targets/docker_secret/tasks/test_secrets.yml +++ b/tests/integration/targets/docker_secret/tasks/test_secrets.yml @@ -33,7 +33,7 @@ assert: that: - 'output.failed' - - 'output.msg == "state is present but all of the following are missing: data"' + - 'output.msg == "state is present but any of the following are missing: data, data_src"' - name: Create secret docker_secret: @@ -76,6 +76,24 @@ that: - not output.changed + - name: Write secret into file + copy: + dest: "{{ remote_tmp_dir }}/data" + content: |- + opensesame! + + - name: Create secret again (from file) + docker_secret: + name: db_password + data_src: "{{ remote_tmp_dir }}/data" + state: present + register: output + + - name: assert create secret is idempotent + assert: + that: + - not output.changed + - name: Create secret again (base64) docker_secret: name: db_password