Repository: ansible-collections/community.general

Issue:
pids_module list pids by regexp
_From @AlexeyPerevalov on Sep 24, 2019 08:15_

<!--- 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 -->
Sometimes it's necessary to work with pids of the processes which match the particular regular expression.

For example: ".*" can match all pids on the system and these pids can be placed to cgroup or taskset can be executed for all pids. Or another operations with the group of pids could proceeded.

pids_module

<!--- Paste example playbooks or commands between quotes below -->
```yaml
    - pids:
        name: .*
      register: pids_of_the_system

    - name: Place pids on appropriate CPU
      shell: taskset -p 7 {{ item }}
      loop: "{{ pids_of_the_system.pids | flatten(1) }}"

```




_Copied from original issue: ansible/ansible#62776_


Discussion:
_From @AlexeyPerevalov on Sep 24, 2019 08:18_

/cc @saranyasridharan
_From @sivel on Oct 03, 2019 19:13_

!component =lib/ansible/modules/system/pids.py
_From @Akasurde on Nov 18, 2019 13:32_

cc @kamathe 

Resolving patch:
diff --git a/plugins/modules/system/pids.py b/plugins/modules/system/pids.py
--- a/plugins/modules/system/pids.py
+++ b/plugins/modules/system/pids.py
@@ -2,6 +2,7 @@
 # Copyright: (c) 2019, Saranya Sridharan
 # 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 = '''
@@ -14,12 +15,20 @@
   - psutil(python module)
 options:
   name:
-    description: the name of the process you want to get PID for.
-    required: true
+    description: The name of the process(es) you want to get PID(s) for.
+    type: str
+  pattern:
+    description: The pattern (regular expression) to match the process(es) you want to get PID(s) for.
     type: str
+    version_added: 3.0.0
+  ignore_case:
+    description: Ignore case in pattern if using the I(pattern) option.
+    type: bool
+    default: false
+    version_added: 3.0.0
 '''
 
-EXAMPLES = '''
+EXAMPLES = r'''
 # Pass the process name
 - name: Getting process IDs of the process
   community.general.pids:
@@ -29,6 +38,11 @@
 - name: Printing the process IDs obtained
   ansible.builtin.debug:
     msg: "PIDS of python:{{pids_of_python.pids|join(',')}}"
+
+- name: Getting process IDs of processes matching pattern
+  community.general.pids:
+    pattern: python(2(\.7)?|3(\.6)?)?\s+myapp\.py
+  register: myapp_pids
 '''
 
 RETURN = '''
@@ -39,9 +53,15 @@
   sample: [100,200]
 '''
 
-from ansible.module_utils.basic import AnsibleModule
+import re
+from os.path import basename
+
+from ansible.module_utils.basic import AnsibleModule, missing_required_lib
+from ansible.module_utils.common.text.converters import to_native
+
 try:
     import psutil
+
     HAS_PSUTIL = True
 except ImportError:
     HAS_PSUTIL = False
@@ -66,17 +86,51 @@ def get_pid(name):
     return pids
 
 
+def get_matching_command_pids(pattern, ignore_case):
+    flags = 0
+    if ignore_case:
+        flags |= re.I
+
+    regex = re.compile(pattern, flags)
+    # See https://psutil.readthedocs.io/en/latest/#find-process-by-name for more information
+    return [p.pid for p in psutil.process_iter(["name", "exe", "cmdline"])
+            if regex.search(to_native(p.info["name"]))
+            or (p.info["exe"] and regex.search(basename(to_native(p.info["exe"]))))
+            or (p.info["cmdline"] and regex.search(to_native(' '.join(p.cmdline()))))
+            ]
+
+
 def main():
     module = AnsibleModule(
         argument_spec=dict(
-            name=dict(required=True, type="str"),
+            name=dict(type="str"),
+            pattern=dict(type="str"),
+            ignore_case=dict(type="bool", default=False),
         ),
+        required_one_of=[
+            ('name', 'pattern')
+        ],
+        mutually_exclusive=[
+            ('name', 'pattern')
+        ],
         supports_check_mode=True,
     )
+
     if not HAS_PSUTIL:
-        module.fail_json(msg="Missing required 'psutil' python module. Try installing it with: pip install psutil")
+        module.fail_json(msg=missing_required_lib('psutil'))
+
     name = module.params["name"]
-    response = dict(pids=get_pid(name))
+    pattern = module.params["pattern"]
+    ignore_case = module.params["ignore_case"]
+
+    if name:
+        response = dict(pids=get_pid(name))
+    else:
+        try:
+            response = dict(pids=get_matching_command_pids(pattern, ignore_case))
+        except re.error as e:
+            module.fail_json(msg="'%s' is not a valid regular expression: %s" % (pattern, to_native(e)))
+
     module.exit_json(**response)
 
 


Test patch:
diff --git a/tests/integration/targets/pids/tasks/main.yml b/tests/integration/targets/pids/tasks/main.yml
--- a/tests/integration/targets/pids/tasks/main.yml
+++ b/tests/integration/targets/pids/tasks/main.yml
@@ -56,6 +56,22 @@
     name: "{{ random_name[0:5] }}"
   register: exactpidmatch
 
+- name: "Checking that patterns can be used with the pattern option"
+  pids:
+    pattern: "{{ random_name[0:5] }}"
+  register: pattern_pid_match
+
+- name: "Checking that case-insensitive patterns can be used with the pattern option"
+  pids:
+    pattern: "{{ random_name[0:5] | upper }}"
+    ignore_case: true
+  register: caseinsensitive_pattern_pid_match
+
+- name: "Checking that .* includes test pid"
+  pids:
+    pattern: .*
+  register: match_all
+
 - name: "Reading pid from the file"
   slurp:
     src:  "{{ output_dir }}/obtainpid.txt"
@@ -67,3 +83,17 @@
     - "pids.pids | join(' ')  == newpid.content | b64decode | trim"
     - "pids.pids | length > 0"
     - "exactpidmatch.pids == []"
+    - "pattern_pid_match.pids | join(' ')  == newpid.content | b64decode | trim"
+    - "caseinsensitive_pattern_pid_match.pids | join(' ')  == newpid.content | b64decode | trim"
+    - newpid.content | b64decode | trim | int in match_all.pids
+
+- name: "Register output of bad input pattern"
+  pids:
+    pattern: (unterminated
+  register: bad_pattern_result
+  ignore_errors: true
+
+- name: "Verify that bad input pattern result is failed"
+  assert:
+    that:
+      - bad_pattern_result is failed
\ No newline at end of file