Repository: ansible-collections/community.general

Issue:
homebrew: Task fails on warning message when formula has cask of same name
### Summary

I'd like to add a `force_formula` parameter to the brew role so that the brew command includes `--formula`.

### Rationale

When trying to install docker with homebrew,
```yml
- name: Install docker via homebrew
  homebrew:
    name: 'docker'
    state: present
```
it fails with the following message:
```
FAILED! => {"changed": false, "msg": "Warning: Treating docker as a formula. For the cask, use homebrew/cask/docker or specify the `--cask` flag."}
```

I can verify that running `brew install docker` on my machine prints the same warning, and returns a non-zero exit code.
But I can avoid this if I run `brew install --formula docker` since it disambiguates the name.

### Issue Type

Feature Idea

### Component Name

homebrew

### Additional Information

<!--- Paste example playbooks or commands between quotes below -->
```yaml
- name: Install docker via homebrew
  homebrew:
    name: 'docker'
    state: present
    force_formula: true
```


### Code of Conduct

- [X] I agree to follow the Ansible Code of Conduct


Discussion:

Resolving patch:
diff --git a/plugins/modules/homebrew.py b/plugins/modules/homebrew.py
--- a/plugins/modules/homebrew.py
+++ b/plugins/modules/homebrew.py
@@ -76,6 +76,13 @@
         type: list
         elements: str
         version_added: '0.2.0'
+    force_formula:
+        description:
+            - Force the package(s) to be treated as a formula (equivalent to C(brew --formula)).
+            - To install a cask, use the M(community.general.homebrew_cask) module.
+        type: bool
+        default: false
+        version_added: 9.0.0
 notes:
   - When used with a C(loop:) each package will be processed individually,
     it is much more efficient to pass the list directly to the O(name) option.
@@ -141,6 +148,12 @@
   community.general.homebrew:
     upgrade_all: true
     upgrade_options: ignore-pinned
+
+- name: Force installing a formula whose name is also a cask name
+  community.general.homebrew:
+    name: ambiguous_formula
+    state: present
+    force_formula: true
 '''
 
 RETURN = '''
@@ -404,7 +417,8 @@ def current_package(self, package):
 
     def __init__(self, module, path, packages=None, state=None,
                  update_homebrew=False, upgrade_all=False,
-                 install_options=None, upgrade_options=None):
+                 install_options=None, upgrade_options=None,
+                 force_formula=False):
         if not install_options:
             install_options = list()
         if not upgrade_options:
@@ -414,7 +428,8 @@ def __init__(self, module, path, packages=None, state=None,
                                   state=state, update_homebrew=update_homebrew,
                                   upgrade_all=upgrade_all,
                                   install_options=install_options,
-                                  upgrade_options=upgrade_options,)
+                                  upgrade_options=upgrade_options,
+                                  force_formula=force_formula)
 
         self._prep()
 
@@ -487,6 +502,8 @@ def _current_package_is_installed(self):
             "--json=v2",
             self.current_package,
         ]
+        if self.force_formula:
+            cmd.append("--formula")
         rc, out, err = self.module.run_command(cmd)
         if err:
             self.failed = True
@@ -632,10 +649,15 @@ def _install_current_package(self):
         else:
             head = None
 
+        if self.force_formula:
+            formula = '--formula'
+        else:
+            formula = None
+
         opts = (
             [self.brew_path, 'install']
             + self.install_options
-            + [self.current_package, head]
+            + [self.current_package, head, formula]
         )
         cmd = [opt for opt in opts if opt]
         rc, out, err = self.module.run_command(cmd)
@@ -919,7 +941,11 @@ def main():
                 default=None,
                 type='list',
                 elements='str',
-            )
+            ),
+            force_formula=dict(
+                default=False,
+                type='bool',
+            ),
         ),
         supports_check_mode=True,
     )
@@ -951,6 +977,7 @@ def main():
     if state in ('absent', 'removed', 'uninstalled'):
         state = 'absent'
 
+    force_formula = p['force_formula']
     update_homebrew = p['update_homebrew']
     if not update_homebrew:
         module.run_command_environ_update.update(
@@ -967,7 +994,7 @@ def main():
     brew = Homebrew(module=module, path=path, packages=packages,
                     state=state, update_homebrew=update_homebrew,
                     upgrade_all=upgrade_all, install_options=install_options,
-                    upgrade_options=upgrade_options)
+                    upgrade_options=upgrade_options, force_formula=force_formula)
     (failed, changed, message) = brew.run()
     changed_pkgs = brew.changed_pkgs
     unchanged_pkgs = brew.unchanged_pkgs


Test patch:
diff --git a/tests/integration/targets/docker/handlers/main.yml b/tests/integration/targets/docker/handlers/main.yml
new file mode 100644
--- /dev/null
+++ b/tests/integration/targets/docker/handlers/main.yml
@@ -0,0 +1,11 @@
+---
+# Copyright (c) Ansible Project
+# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
+# SPDX-License-Identifier: GPL-3.0-or-later
+
+- name: uninstall docker
+  community.general.homebrew:
+    name: docker
+    state: absent
+  become: true
+  become_user: "{{ brew_stat.stat.pw_name }}"
diff --git a/tests/integration/targets/docker/tasks/main.yml b/tests/integration/targets/docker/tasks/main.yml
new file mode 100644
--- /dev/null
+++ b/tests/integration/targets/docker/tasks/main.yml
@@ -0,0 +1,39 @@
+---
+# Copyright (c) Ansible Project
+# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
+# SPDX-License-Identifier: GPL-3.0-or-later
+
+- when: ansible_facts.distribution == 'MacOSX'
+  block:
+    - name: MACOS | Find brew binary
+      command: which brew
+      register: brew_which
+
+    - name: MACOS | Get owner of brew binary
+      stat:
+        path: "{{ brew_which.stdout }}"
+      register: brew_stat
+
+    - name: MACOS | Install docker without --formula
+      community.general.homebrew:
+        name: docker
+        state: present
+      become: true
+      become_user: "{{ brew_stat.stat.pw_name }}"
+      ignore_errors: true
+      register: result
+
+    - name: Check that installing docker without --formula raises warning
+      assert:
+        that:
+          - result is failed
+
+    - name: MACOS | Install docker
+      community.general.homebrew:
+        name: docker
+        state: present
+        force_formula: true
+      become: true
+      become_user: "{{ brew_stat.stat.pw_name }}"
+      notify:
+        - uninstall docker