community.general icon indicating copy to clipboard operation
community.general copied to clipboard

Homebrew module incorrectly fails executing task when package is already installed

Open mpereira opened this issue 5 years ago • 27 comments

SUMMARY
- name: Install wkhtmltopdf
  homebrew:
    name: homebrew/cask/wkhtmltopdf 
    state: present

results in

PLAY [localhost] ***************************************************************

TASK [Gathering Facts] *********************************************************
ok: [localhost]

TASK [Install wkhtmltopdf] *****************************************************
fatal: [localhost]: FAILED! => changed=false 
  msg: |-
    Updating Homebrew...
    Warning: Cask 'wkhtmltopdf' is already installed.
  
    To re-install wkhtmltopdf, run:
      brew cask reinstall wkhtmltopdf

PLAY RECAP *********************************************************************
localhost                  : ok=1    changed=0    unreachable=0    failed=1    skipped=0    rescued=0    ignored=0   
ISSUE TYPE
  • Bug Report
COMPONENT NAME

Homebrew module

ANSIBLE VERSION
$ ansible --version
ansible 2.9.13
  config file = /Users/mpereira/git/macbook-playbook/ansible.cfg
  configured module search path = ['/Users/mpereira/.ansible/plugins/modules', '/usr/share/ansible/plugins/modules']
  ansible python module location = /Users/mpereira/Library/Python/3.8/lib/python/site-packages/ansible
  executable location = /Users/mpereira/Library/Python/3.8/bin/ansible
  python version = 3.8.5 (v3.8.5:580fbb018f, Jul 20 2020, 12:11:27) [Clang 6.0 (clang-600.0.57)]
CONFIGURATION
$ ansible-config dump --only-changed
DEFAULT_STDOUT_CALLBACK(/Users/mpereira/git/macbook-playbook/ansible.cfg) = yaml
OS / ENVIRONMENT
$ sw_vers
ProductName:	Mac OS X
ProductVersion:	10.15.5
BuildVersion:	19F101
STEPS TO REPRODUCE

Refer to summary.

EXPECTED RESULTS

I expect the task to complete successfully when a package is already installed and a homebrew task is executed with either state: present or state: latest.

ACTUAL RESULTS

The task fails, as shown in the summary.

mpereira avatar Sep 04 '20 17:09 mpereira

Files identified in the description:

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

ansibullbot avatar Sep 04 '20 17:09 ansibullbot

cc @akasurde @andrew-d @danieljaouen @indrajitr @kyleabenson @martinm82 click here for bot help

ansibullbot avatar Sep 04 '20 17:09 ansibullbot

The issue seems to only happen with casks. e.g., the following works:

- name: Install Hugo
  homebrew:
    name: hugo
PLAY [localhost] ***************************************************************

TASK [Gathering Facts] *********************************************************
ok: [localhost]

TASK [hugo : Install Hugo] *****************************************************
ok: [localhost] => changed=false 
  msg: 'Package already installed: hugo'

PLAY RECAP *********************************************************************
localhost                  : ok=2    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   

mpereira avatar Sep 04 '20 17:09 mpereira

/assign

MichaelWasher avatar Oct 21 '20 11:10 MichaelWasher

It depends on the expected usage. For installing casks homebrew_cask should be used. Testing with homebrew_cask as below works fine:

    - name: Install Packages through Homebrew
      homebrew_cask:
        name: visual-studio-code
        state: present
ok: [localhost] => {
    "changed": false,
    "invocation": {
        "module_args": {
            "accept_external_apps": false,
            "greedy": false,
            "install_options": [],
            "name": [
                "visual-studio-code"
            ],
            "path": "/usr/local/bin",
            "state": "present",
            "sudo_password": null,
            "update_homebrew": false,
            "upgrade_all": false
        }
    },
    "msg": "Cask already installed: visual-studio-code"
}

However when using the homebrew package to install casks fails for me in general.

    - name: Remove visual-studio-code
      homebrew_cask:
        name: visual-studio-code
        state: absent
    - name: Install Packages through Homebrew
      homebrew:
        name: visual-studio-code
        state: present
fatal: [localhost]: FAILED! => {
    "changed": false,
    "invocation": {
        "module_args": {
            "install_options": [],
            "name": [
                "visual-studio-code"
            ],
            "path": "/usr/local/bin",
            "state": "present",
            "update_homebrew": false,
            "upgrade_all": false,
            "upgrade_options": []
        }
    },
    "msg": ""
}

Seems like we should deal with the output message better but not sure if the homebrew module should manage cask installs

MichaelWasher avatar Oct 21 '20 22:10 MichaelWasher

@felixfontein Do you want the homebrew module to be more friendly with failing on cask issues; Error something like "This Homebrew package is a cask and requires using the homebrew_cask module" or do you think this case should just be closed?

MichaelWasher avatar Oct 22 '20 09:10 MichaelWasher

@MichaelWasher I don't use or maintain the module, so I'm not the right person to ask. In general, having better error messages is always a good thing though :)

felixfontein avatar Oct 22 '20 12:10 felixfontein

I think this would be preferred behaviour, seems unnecessary to have to split package installs between two separate modules when it could/should stay in homebrew / community.general.homebrew.

TASK [Ensure apps are installed] *********************************************************************************************************************************************************************************************
failed: [localhost] (item=cheatsheet) => {"ansible_loop_var": "item", "changed": false, "item": "cheatsheet", "msg": "Warning: Cask 'cheatsheet' is already installed.\n\nTo re-install cheatsheet, run:\n  brew reinstall cheatsheet"}
ok: [localhost] => (item=docker)
failed: [localhost] (item=firefox) => {"ansible_loop_var": "item", "changed": false, "item": "firefox", "msg": "Error: It seems there is already an App at '/Applications/Firefox.app'."}
failed: [localhost] (item=gimp) => {"ansible_loop_var": "item", "changed": false, "item": "gimp", "msg": "Warning: Cask 'gimp' is already installed.\n\nTo re-install gimp, run:\n  brew reinstall gimp"}
ok: [localhost] => (item=gnupg)
failed: [localhost] (item=google-chrome) => {"ansible_loop_var": "item", "changed": false, "item": "google-chrome", "msg": "Error: It seems there is already an App at '/Applications/Google Chrome.app'."}
failed: [localhost] (item=gpg-suite) => {"ansible_loop_var": "item", "changed": false, "item": "gpg-suite", "msg": "Warning: Cask 'gpg-suite' is already installed.\n\nTo re-install gpg-suite, run:\n  brew reinstall gpg-suite"}
failed: [localhost] (item=inkscape) => {"ansible_loop_var": "item", "changed": false, "item": "inkscape", "msg": "Warning: Cask 'inkscape' is already installed.\n\nTo re-install inkscape, run:\n  brew reinstall inkscape"}
failed: [localhost] (item=iterm2) => {"ansible_loop_var": "item", "changed": false, "item": "iterm2", "msg": "Warning: Cask 'iterm2' is already installed.\n\nTo re-install iterm2, run:\n  brew reinstall iterm2"}
ok: [localhost] => (item=jq)
ok: [localhost] => (item=maven)
ok: [localhost] => (item=md5sha1sum)
ok: [localhost] => (item=openjdk)
ok: [localhost] => (item=pass)
failed: [localhost] (item=pgadmin3) => {"ansible_loop_var": "item", "changed": false, "item": "pgadmin3", "msg": "Warning: Cask 'pgadmin3' is already installed.\n\nTo re-install pgadmin3, run:\n  brew reinstall pgadmin3"}
failed: [localhost] (item=pgp2) => {"ansible_loop_var": "item", "changed": false, "item": "pgp2", "msg": "Error: No available formula or cask with the name \"pgp2\".\nError: No previously deleted formula found.\nError: No similarly named formulae found.\n==> Searching taps on GitHub...\nError: No formulae found in taps."}
failed: [localhost] (item=sequel pro) => {"ansible_loop_var": "item", "changed": false, "item": "sequel pro", "msg": "Invalid package: sequel pro."}
ok: [localhost] => (item=terraform)
ok: [localhost] => (item=tree)
failed: [localhost] (item=vagrant) => {"ansible_loop_var": "item", "changed": false, "item": "vagrant", "msg": "Warning: Cask 'vagrant' is already installed.\n\nTo re-install vagrant, run:\n  brew reinstall vagrant"}
failed: [localhost] (item=virtualbox) => {"ansible_loop_var": "item", "changed": false, "item": "virtualbox", "msg": "Warning: Cask 'virtualbox' is already installed.\n\nTo re-install virtualbox, run:\n  brew reinstall virtualbox"}
failed: [localhost] (item=virtualbox-extension-pack) => {"ansible_loop_var": "item", "changed": false, "item": "virtualbox-extension-pack", "msg": "Warning: Cask 'virtualbox-extension-pack' is already installed.\n\nTo re-install virtualbox-extension-pack, run:\n  brew reinstall virtualbox-extension-pack"}
failed: [localhost] (item=visual-studio-code) => {"ansible_loop_var": "item", "changed": false, "item": "visual-studio-code", "msg": "Warning: Cask 'visual-studio-code' is already installed.\n\nTo re-install visual-studio-code, run:\n  brew reinstall visual-studio-code"}
ok: [localhost] => (item=watch)
ok: [localhost] => (item=wireshark)
failed: [localhost] (item=xquartz) => {"ansible_loop_var": "item", "changed": false, "item": "xquartz", "msg": "Warning: Cask 'xquartz' is already installed.\n\nTo re-install xquartz, run:\n  brew reinstall xquartz"}

PLAY RECAP *******************************************************************************************************************************************************************************************************************
localhost                  : ok=2    changed=0    unreachable=0    failed=1    skipped=0    rescued=0    ignored=0 

dansholds avatar Oct 29 '20 15:10 dansholds

It all really depends on what everyone wants.

But the issue for failing on already installed is due to the below lines, and could be resolved with the snippet below: https://github.com/ansible-collections/community.general/blob/main/plugins/modules/packaging/os/homebrew.py#L462-L481

    # NOTE: This command will output both installed packages and casks
        cmd = [
            "{brew_path}".format(brew_path=self.brew_path), "list", "-1"
        ]
        rc, out, err = self.module.run_command(cmd)
        for package in out.split('\n'):
            if package == self.current_package:
                return True

        return False

MichaelWasher avatar Oct 31 '20 23:10 MichaelWasher

Actually looks like HomeBrew doesn't provode the same output depending on TTY and pipes... 👎 https://github.com/Homebrew/brew/blob/master/Library/Homebrew/cmd/list.rb#L90-L96

Something more like this would fix:

        cmd0 = ["{brew_path}".format(brew_path=self.brew_path), "list", "--casks"]
        cmd1 = ["{brew_path}".format(brew_path=self.brew_path), "list", "--formula"]

        rc, out, err = self.module.run_command(cmd0)
        rc1, out1, err1 = self.module.run_command(cmd1)

        out += '\n' + out1
        for package in out.split('\n'):
            if package == self.current_package:
                return True

MichaelWasher avatar Nov 01 '20 00:11 MichaelWasher

The latest updates to the homebrew & homebrew_cask modules that caught the regex issue for catching dashes is enough for me, it appears that was an underlying issues I hadn't noticed and it was forcing me to try to install some casks using homebrew which then wasn't idempotent.

dansholds avatar Nov 02 '20 11:11 dansholds

I ran into this problem as well and the issue is the following:

At time X I ran brew install python3 and brew itself got updated. At that moment the formula was pointing to Python 3.8 ([email protected]). The output of brew info python3 was:

brew info python3
[email protected]: stable 3.8.5 (bottled)
Interpreted, interactive, object-oriented programming language
https://www.python.org/
/usr/local/Cellar/[email protected]/3.8.5 (4,372 files, 67.6MB) *
  Poured from bottle on 2020-09-22 at 09:17:06
From: https://github.com/Homebrew/homebrew-core/blob/HEAD/Formula/[email protected]
License: Python-2.0

[...]

At time X+n I ran again brew install python3 and brew updated itself again fetching a new formula Python pointing to [email protected]. The output of brew info python3 is suddenly this:

brew info python3
[email protected]: stable 3.9.0 (bottled)
Interpreted, interactive, object-oriented programming language
https://www.python.org/
Not installed
From: https://github.com/Homebrew/homebrew-core/blob/HEAD/Formula/[email protected]
License: Python-2.0

[...]

As you can see it says that the packge is not installed.

If I use brew info [email protected] one receives the expected output:

brew info [email protected]
[email protected]: stable 3.8.6 (bottled) [keg-only]
Interpreted, interactive, object-oriented programming language
https://www.python.org/
/usr/local/Cellar/[email protected]/3.8.5 (4,372 files, 67.6MB) *
  Poured from bottle on 2020-09-22 at 09:17:06
From: https://github.com/Homebrew/homebrew-core/blob/HEAD/Formula/[email protected]
License: Python-2.0

[...]

@MichaelWasher the fix you provided would not solve my issue in case I would use python3 instead [email protected].

Of course one could argue whether I should actually use python3 as it is clearly not pinning to a Major.Minor version of a package. I am also thinking to use always update_homebrew: no in each call to avoid updating brew itself.

But still I thing this is an issue on brew side that it returns information about a package that it actually does not have locally.

martinm82 avatar Nov 09 '20 08:11 martinm82

It would seem the homebrew_cask no longer works and is failing with the following error: Error: Calling brew cask install is disabled! Use brew install [--cask] instead. My target host is running macOS 11.1 and Homebrew version2.7.1.

Installing a cask with homebrew fails with the following: failed: [192.168.122.242] (item=sublime-text) => {"ansible_loop_var": "item", "changed": false, "item": "sublime-text", "msg": ""} but actually does install the package.

It looks like the module(s) require(s) updating to support Homebrew version 2.7.1 correctly.

Sergong avatar Dec 29 '20 21:12 Sergong

@Sergong your problem is a duplicate of #1524.

felixfontein avatar Dec 29 '20 21:12 felixfontein

I'm having similar problem.

My task

---
- name: install cask
  when: ansible_distribution == 'MacOSX'
  homebrew_cask:
    name:
      - docker
      - firefox
      - google-chrome
      - password-gorilla
      - slack
      - spotify
      - visual-studio
    state: present

Error I see:

TASK [osx : install cask] ************************************************************************************
task path: /Users/denis.costa/projects/dotfiles/roles/osx/tasks/main.yml:2
<127.0.0.1> ESTABLISH LOCAL CONNECTION FOR USER: denis.costa
<127.0.0.1> EXEC /bin/sh -c 'echo ~denis.costa && sleep 0'
<127.0.0.1> EXEC /bin/sh -c '( umask 77 && mkdir -p "` echo /Users/denis.costa/.ansible/tmp `"&& mkdir "` echo /Users/denis.costa/.ansible/tmp/ansible-tmp-1624454349.499657-26582-53145583098702 `" && echo ansible-tmp-1624454349.499657-26582-53145583098702="` echo /Users/denis.costa/.ansible/tmp/ansible-tmp-1624454349.499657-26582-53145583098702 `" ) && sleep 0'
redirecting (type: modules) ansible.builtin.homebrew_cask to community.general.homebrew_cask
Using module file /Users/denis.costa/.pyenv/versions/3.9.1/lib/python3.9/site-packages/ansible_collections/community/general/plugins/modules/homebrew_cask.py
<127.0.0.1> PUT /Users/denis.costa/.ansible/tmp/ansible-local-24467fsn_s279/tmp0g39rtcq TO /Users/denis.costa/.ansible/tmp/ansible-tmp-1624454349.499657-26582-53145583098702/AnsiballZ_homebrew_cask.py
<127.0.0.1> EXEC /bin/sh -c 'chmod u+x /Users/denis.costa/.ansible/tmp/ansible-tmp-1624454349.499657-26582-53145583098702/ /Users/denis.costa/.ansible/tmp/ansible-tmp-1624454349.499657-26582-53145583098702/AnsiballZ_homebrew_cask.py && sleep 0'
<127.0.0.1> EXEC /bin/sh -c '/usr/bin/python /Users/denis.costa/.ansible/tmp/ansible-tmp-1624454349.499657-26582-53145583098702/AnsiballZ_homebrew_cask.py && sleep 0'
<127.0.0.1> EXEC /bin/sh -c 'rm -f -r /Users/denis.costa/.ansible/tmp/ansible-tmp-1624454349.499657-26582-53145583098702/ > /dev/null 2>&1 && sleep 0'
fatal: [127.0.0.1]: FAILED! => {
    "changed": false,
    "invocation": {
        "module_args": {
            "accept_external_apps": false,
            "greedy": false,
            "install_options": [],
            "name": [
                "docker",
                "firefox",
                "google-chrome",
                "password-gorilla",
                "slack",
                "spotify",
                "visual-studio"
            ],
            "path": "/usr/local/bin:/opt/homebrew/bin",
            "state": "present",
            "sudo_password": null,
            "update_homebrew": false,
            "upgrade_all": false
        }
    },
    "msg": "Updating Homebrew...\n==> Auto-updated Homebrew!\nUpdated 1 tap (homebrew/core).\n==> Updated Formulae\nUpdated 1 formula.\n\nError: It seems there is already an App at '/Applications/Firefox.app'."
}

Ansible version:

$ ansible --version
ansible 2.10.9
  config file = /Users/denis.costa/projects/dotfiles/ansible.cfg
  configured module search path = ['/Users/denis.costa/.ansible/plugins/modules', '/usr/share/ansible/plugins/modules']
  ansible python module location = /Users/denis.costa/.pyenv/versions/3.9.1/lib/python3.9/site-packages/ansible
  executable location = /Users/denis.costa/.pyenv/versions/3.9.1/bin/ansible
  python version = 3.9.1 (default, May  6 2021, 10:38:39) [Clang 12.0.0 (clang-1200.0.32.29)]

My OS info: Screen Shot 2021-06-23 at 10 16 11

deniscostadsc avatar Jun 23 '21 13:06 deniscostadsc

I would also expect using ansible.builtin.package to invoke the homebrew_cask and not the homebrew module. Right now this doesn't happen and I have to explicitly use homebrew_cask for my play to work.

ansible [core 2.11.2]
  config file = None
  configured module search path = ['~/.ansible/plugins/modules', '/usr/share/ansible/plugins/modules']
  ansible python module location = /usr/local/Cellar/ansible/4.2.0/libexec/lib/python3.9/site-packages/ansible
  ansible collection location = ~/.ansible/collections:/usr/share/ansible/collections
  executable location = /usr/local/bin/ansible
  python version = 3.9.6 (default, Jun 29 2021, 05:25:02) [Clang 12.0.5 (clang-1205.0.22.9)]
  jinja version = 3.0.1
  libyaml = True

lockejan avatar Jul 13 '21 13:07 lockejan

@lockejan not sure how that is related to this bug report? Also we have no influence on what the package module does, that module is part of ansible-core.

felixfontein avatar Jul 14 '21 06:07 felixfontein

@felixfontein my bad. In my case it failed when invoking the wrong variant of the homebrew module, but worked when explicitily calling the "correct" one, which is somewhat different on second sight to what the initial issuer seems to experience.

My initial thought was: why is it not possible to have one homebrew module for both variants (casks and non-casks)? Such as one is used to when using any linux package manager. Sorry for the noise. I'll might open a new issue then.

lockejan avatar Jul 17 '21 14:07 lockejan

My initial thought was: why is it not possible to have one homebrew module for both variants (casks and non-casks)?

I guess that would be great. I know too little about homebrew and casks to be able to say whether this is possible in a backwards-compatible way for the homebrew module. (I don't use homebrew at all.)

felixfontein avatar Jul 17 '21 16:07 felixfontein

Facing the same issue. If an application is already installed without using homebrew, ansible install fails. Example: - name: Install slack homebrew_cask: name: slack state: present

Error: fatal: [localhost]: FAILED! => { "changed": false, "invocation": { "module_args": { "accept_external_apps": false, "greedy": false, "install_options": [], "name": [ "slack" ], "path": "/usr/local/bin:/opt/homebrew/bin", "state": "present", "sudo_password": null, "update_homebrew": false, "upgrade_all": false } }, "msg": "Running brew update --preinstall...\n==> Auto-updated Homebrew!\nUpdated 1 tap (homebrew/core).\n==> Updated Formulae\nUpdated 6 formulae.\n\nError: It seems there is already an App at '/Applications/Slack.app'." }

Specs: Mac OS Monterey M1 MacBook Pro

prajain12 avatar Jan 07 '22 11:01 prajain12

@prajain12 this issue is about apps installed by homebrew. If you install an app another way and try to install it with homebrew, that error is expected as far as I can tell. This is not a bug in the module, but expected behavior from homebrew.

felixfontein avatar Jan 07 '22 12:01 felixfontein

I am facing this issue as well. Since Homebrew removed the cask install option, I have added my cask install to the regular package mgmt module, which causes brew to fail if the package is already installed. Are there any updates to this, or is there a new issue tracking this problem?

jackfarzan avatar Feb 08 '22 20:02 jackfarzan

@jackfarzan You can add logic to check whether the app already exists at the destination or not. If it does not already exist, then you can move ahead with installing using homebrew. As this is not an ansible problem, this can be addressed in homebrew.

prajain12 avatar Feb 09 '22 09:02 prajain12

Hey folks, sorry for the delay. Could you please check if #4177 works for you and let me know? Thanks in advance.

Akasurde avatar Feb 09 '22 13:02 Akasurde

resolved_by_pr #4177

Akasurde avatar Feb 09 '22 13:02 Akasurde

I applied the changes in https://github.com/ansible-collections/community.general/pull/4177/files (homebrew.py) and that fixes the issue with detecting already installed casks.

+1 for not having to separate between homebrew and homebrew_cask from me.

FredrikWendt avatar Apr 13 '22 07:04 FredrikWendt

I applied the last PR #4177 only the homebrew.py file and I still get that message

Warning: Cask 'alfred' is already installed. To re-install Alfred, run: brew reinstall - - cask alfred

I'm using the task as community.general.homebrew

I don't know if I've to to a force reinstall with ansible first or what.

killua99 avatar Aug 09 '22 06:08 killua99

I also ran into this issue with several package installations. The only package that works is zoxide because on brew info zoxide Poured from bottle on... is returned. The condition on line https://github.com/ansible-collections/community.general/blob/main/plugins/modules/packaging/os/homebrew.py#L480 checks if Built from source or Poured from bottle is returned and maybe I'm to naive but couldn't we just check if Not installed is not returned in the info command? In my short test for packages that are not installed the brew info package command always had the Not installed line in it and all packages that are installed don't have this line in their output.

BaldFabi avatar Oct 24 '22 19:10 BaldFabi

Files identified in the description:

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

click here for bot help

ansibullbot avatar Nov 09 '22 19:11 ansibullbot

@prajain12 i solved it with following work around

- name: Check the list of installed apps
  stat: path=/Applications/{{item.path}}
  with_items:
    - { path: "GoLand.app", name: "goland" }
    - { path: "pgAdmin 4.app", name: "pgadmin4" }
    - { path: "Google Chrome.app", name: "google-chrome" }
    - { path: "Postman.app", name: "postman" }
    - { path: "Slack.app", name: "slack" }
    - { path: "Visual Studio Code.app", name: "visual-studio-code" }
  register: apps_install_status

- name: Install Apps not in the applications folder
  homebrew_cask: name={{ item.item.name }} state=present
  with_items: "{{ apps_install_status.results }}"
  when: not item.stat.exists

sacgov avatar Jan 25 '23 20:01 sacgov