Repository: ansible-collections/community.general

Issue:
docker stack ls - functionality
_From @jifox on Jun 19, 2020 09:55_

<!--- Verify first that your feature was not already discussed on GitHub -->
<!--- Complete *all* sections as described, this form is processed automatically -->

##### SUMMARY
<!--- Describe the new feature/improvement briefly below -->
I would like to list all stacks like `docker stack ls` in order to run some setup containers if a particular stack is not present.

##### ISSUE TYPE
- Feature Idea

##### COMPONENT NAME
<!--- Write the short name of the module, plugin, task or feature below, use your best guess if unsure -->
docker_stack

##### ADDITIONAL INFORMATION
<!--- Describe how the feature would be used, why it is needed and what it would solve -->

<!--- Paste example playbooks or commands between quotes below -->
```yaml
   - name: List all docker stacks
     docker_stack_info:
         stacks: yes      # return list of known stacks
         ...
```

<!--- HINT: You can also paste gist.github.com links for larger files -->


_Copied from original issue: ansible/ansible#70165_


Discussion:
Files identified in the description:
* [`plugins/modules/cloud/docker/docker_stack.py`](https://github.com/ansible-collections/community.general/blob/master/plugins/modules/cloud/docker/docker_stack.py)

If these files are inaccurate, please update the `component name` section of the description or use the `!component` bot command.

[click here for bot help](https://github.com/ansible/ansibullbot/blob/master/ISSUE_HELP.md)
<!--- boilerplate: components_banner --->
cc @DBendit @WojciechowskiPiotr @akshay196 @chouseknecht @danihodovic @dariko @felixfontein @jwitko @kassiansun @tbouvet
[click here for bot help](https://github.com/ansible/ansibullbot/blob/master/ISSUE_HELP.md)
<!--- boilerplate: notify --->
```yaml
- name: Get list of all stacks with shell command
  command: "docker stack ls --format {{ ('{{ '  ~ '.Name' ~ '}}')|quote }}"
  register: res_stacks
```
This should rather go into a separate module, `docker_stack_info` (or `docker_stack_list`?).

Resolving patch:
diff --git a/plugins/modules/cloud/docker/docker_stack_info.py b/plugins/modules/cloud/docker/docker_stack_info.py
new file mode 100644
--- /dev/null
+++ b/plugins/modules/cloud/docker/docker_stack_info.py
@@ -0,0 +1,84 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright (c) 2020 Jose Angel Munoz (@imjoseangel)
+# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+from __future__ import (absolute_import, division, print_function)
+
+__metaclass__ = type
+
+DOCUMENTATION = '''
+---
+module: docker_stack_info
+author: "Jose Angel Munoz (@imjoseangel)"
+short_description: Return information on a docker stack
+description:
+  - Retrieve information on docker stacks using the C(docker stack) command
+    on the target node (see examples).
+version_added: "1.0.0"
+'''
+
+RETURN = '''
+results:
+    description: |
+        List of dictionaries containing the list of stacks or tasks associated
+        to a stack name.
+    sample: >
+        "results": [{"name":"grafana","namespace":"default","orchestrator":"Kubernetes","services":"2"}]
+    returned: always
+    type: list
+'''
+
+EXAMPLES = '''
+  - name: Shows stack info
+    community.general.docker_stack_info:
+    register: result
+
+  - name: Show results
+    ansible.builtin.debug:
+      var: result.results
+'''
+
+import json
+from ansible.module_utils.basic import AnsibleModule
+
+
+def docker_stack_list(module):
+    docker_bin = module.get_bin_path('docker', required=True)
+    rc, out, err = module.run_command(
+        [docker_bin, "stack", "ls", "--format={{json .}}"])
+
+    return rc, out.strip(), err.strip()
+
+
+def main():
+    module = AnsibleModule(
+        argument_spec={
+        },
+        supports_check_mode=False
+    )
+
+    rc, out, err = docker_stack_list(module)
+
+    if rc != 0:
+        module.fail_json(msg="Error running docker stack. {0}".format(err),
+                         rc=rc, stdout=out, stderr=err)
+    else:
+        if out:
+            ret = list(
+                json.loads(outitem)
+                for outitem in out.splitlines())
+
+        else:
+            ret = []
+
+        module.exit_json(changed=False,
+                         rc=rc,
+                         stdout=out,
+                         stderr=err,
+                         results=ret)
+
+
+if __name__ == "__main__":
+    main()
diff --git a/plugins/modules/docker_stack_info.py b/plugins/modules/docker_stack_info.py
new file mode 120000
--- /dev/null
+++ b/plugins/modules/docker_stack_info.py
@@ -0,0 +1 @@
+./cloud/docker/docker_stack_info.py
\ No newline at end of file


Test patch:
diff --git a/tests/integration/targets/docker_stack_info/aliases b/tests/integration/targets/docker_stack_info/aliases
new file mode 100644
--- /dev/null
+++ b/tests/integration/targets/docker_stack_info/aliases
@@ -0,0 +1,8 @@
+shippable/posix/group1
+skip/aix
+skip/osx
+skip/freebsd
+destructive
+skip/docker  # The tests sometimes make docker daemon unstable; hence,
+             # we skip all docker-based CI runs to avoid disrupting
+             # the whole CI system.
diff --git a/tests/integration/targets/docker_stack_info/files/stack_compose_base.yml b/tests/integration/targets/docker_stack_info/files/stack_compose_base.yml
new file mode 100644
--- /dev/null
+++ b/tests/integration/targets/docker_stack_info/files/stack_compose_base.yml
@@ -0,0 +1,5 @@
+version: '3'
+services:
+    busybox:
+        image: busybox:latest
+        command: sleep 3600
diff --git a/tests/integration/targets/docker_stack_info/files/stack_compose_overrides.yml b/tests/integration/targets/docker_stack_info/files/stack_compose_overrides.yml
new file mode 100644
--- /dev/null
+++ b/tests/integration/targets/docker_stack_info/files/stack_compose_overrides.yml
@@ -0,0 +1,5 @@
+version: '3'
+services:
+    busybox:
+        environment:
+            envvar: value
diff --git a/tests/integration/targets/docker_stack_info/meta/main.yml b/tests/integration/targets/docker_stack_info/meta/main.yml
new file mode 100644
--- /dev/null
+++ b/tests/integration/targets/docker_stack_info/meta/main.yml
@@ -0,0 +1,3 @@
+---
+dependencies:
+  - setup_docker
diff --git a/tests/integration/targets/docker_stack_info/tasks/main.yml b/tests/integration/targets/docker_stack_info/tasks/main.yml
new file mode 100644
--- /dev/null
+++ b/tests/integration/targets/docker_stack_info/tasks/main.yml
@@ -0,0 +1,5 @@
+- include_tasks: test_stack_info.yml
+  when: docker_api_version is version('1.25', '>=')
+
+- fail: msg="Too old docker / docker-py version to run docker_stack tests!"
+  when: not(docker_api_version is version('1.25', '>=')) and (ansible_distribution != 'CentOS' or ansible_distribution_major_version|int > 6)
diff --git a/tests/integration/targets/docker_stack_info/tasks/test_stack_info.yml b/tests/integration/targets/docker_stack_info/tasks/test_stack_info.yml
new file mode 100644
--- /dev/null
+++ b/tests/integration/targets/docker_stack_info/tasks/test_stack_info.yml
@@ -0,0 +1,75 @@
+---
+- block:
+  - name: Make sure we're not already using Docker swarm
+    docker_swarm:
+      state: absent
+      force: true
+
+  - name: Get docker_stack_info when docker is not running in swarm mode
+    docker_stack_info:
+    ignore_errors: true
+    register: output
+
+  - name: Assert failure when called when swarm is not running
+    assert:
+      that:
+        - 'output is failed'
+        - '"Error running docker stack" in output.msg'
+
+  - name: Create a swarm cluster
+    docker_swarm:
+      state: present
+      advertise_addr: "{{ansible_default_ipv4.address}}"
+
+  - name: Get docker_stack_info when docker is running and not stack available
+    docker_stack_info:
+    register: output
+
+  - name: Assert stack facts
+    assert:
+      that:
+        - 'output.results | type_debug == "list"'
+        - 'output.results | length == 0'
+
+  - name: Copy compose files
+    copy:
+      src: "{{ item }}"
+      dest: "{{ output_dir }}/"
+    with_items:
+    - stack_compose_base.yml
+    - stack_compose_overrides.yml
+
+  - name: Install docker_stack python requirements
+    pip:
+      name: jsondiff,pyyaml
+
+  - name: Create stack with compose file
+    register: output
+    docker_stack:
+      state: present
+      name: test_stack
+      compose:
+      - "{{ output_dir }}/stack_compose_base.yml"
+
+  - name: Assert test_stack changed on stack creation with compose file
+    assert:
+      that:
+      - output is changed
+
+  - name: Get docker_stack_info when docker is running
+    docker_stack_info:
+    register: output
+
+  - name: assert stack facts
+    assert:
+      that:
+        - 'output.results | type_debug == "list"'
+        - 'output.results[0].Name == "test_stack"'
+        - 'output.results[0].Orchestrator == "Swarm"'
+        - 'output.results[0].Services == "1"'
+
+  always:
+  - name: Cleanup
+    docker_swarm:
+      state: absent
+      force: true
diff --git a/tests/integration/targets/docker_stack_info/vars/main.yml b/tests/integration/targets/docker_stack_info/vars/main.yml
new file mode 100644
--- /dev/null
+++ b/tests/integration/targets/docker_stack_info/vars/main.yml
@@ -0,0 +1,15 @@
+stack_compose_base:
+    version: '3'
+    services:
+        busybox:
+            image: busybox:latest
+            command: sleep 3600
+
+stack_compose_overrides:
+    version: '3'
+    services:
+        busybox:
+            environment:
+                envvar: value
+
+stack_update_expected_diff: '{"test_stack_busybox": {"TaskTemplate": {"ContainerSpec": {"Env": ["envvar=value"]}}}}'