morgangraphics / ansible-role-nvm

Installs NVM & Node.js on Debian/Ubuntu and RHEL/CentOS
MIT License
99 stars 29 forks source link

Role fails on nvm commands if connection=local #41

Closed neutralalice closed 1 year ago

neutralalice commented 1 year ago

Describe the bug The role fails on the first nvm command it comes across if the role is run with a local connection rather than ssh. It doesn't have access to the nvm commands as they aren't being sourced.

Expected behavior nvm.sh needs to be sourced in the tasks that utilize nvm so that the shell module can be run locally successfully. Normally this would be fine when connection method is ssh because they get sourced via .bashrc (although this role generates it's own in the playbooks directory in this scenario as well), but running locally doesn't seem to exhibit this behavior

To Reproduce Ubuntu 22.04 host image In a directory with the playbook, and relevant cloned /roles/ansible-role-nvm folder (or .ansible/roles etc)

- name: nvm test playbook
  hosts: 127.0.0.1
  connection: local
  roles:
    - role: ansible-role-nvm

Shell [e.g. Bash, Dash, ksh, tcsh, zsh] bash

Desktop (please complete the following information):

Debugging output

TASK [ansible-role-nvm : Check NVM Version] *********************************************************************************************************************************************************
task path: /home/pickles/ubuntu2204-wsl/roles/ansible-role-nvm/tasks/nvm.yml:157
<127.0.0.1> ESTABLISH LOCAL CONNECTION FOR USER: pickles
<127.0.0.1> EXEC /bin/sh -c 'echo ~pickles && sleep 0'
<127.0.0.1> EXEC /bin/sh -c '( umask 77 && mkdir -p "` echo /home/pickles/.ansible/tmp `"&& mkdir "` echo /home/pickles/.ansible/tmp/ansible-tmp-1690297067.3967338-22793-207818345201238 `" && echo ansible-tmp-1690297067.3967338-22793-207818345201238="` echo /home/pickles/.ansible/tmp/ansible-tmp-1690297067.3967338-22793-207818345201238 `" ) && sleep 0'                         Using module file /usr/lib/python3/dist-packages/ansible/modules/command.py
<127.0.0.1> PUT /home/pickles/.ansible/tmp/ansible-local-21568_10qrebw/tmpt0rp4hlp TO /home/pickles/.ansible/tmp/ansible-tmp-1690297067.3967338-22793-207818345201238/AnsiballZ_command.py
<127.0.0.1> EXEC /bin/sh -c 'chmod u+x /home/pickles/.ansible/tmp/ansible-tmp-1690297067.3967338-22793-207818345201238/ /home/pickles/.ansible/tmp/ansible-tmp-1690297067.3967338-22793-207818345201238/AnsiballZ_command.py && sleep 0'
<127.0.0.1> EXEC /bin/sh -c '/usr/bin/python3 /home/pickles/.ansible/tmp/ansible-tmp-1690297067.3967338-22793-207818345201238/AnsiballZ_command.py && sleep 0'
<127.0.0.1> EXEC /bin/sh -c 'rm -f -r /home/pickles/.ansible/tmp/ansible-tmp-1690297067.3967338-22793-207818345201238/ > /dev/null 2>&1 && sleep 0'
fatal: [localhost]: FAILED! => {
"changed": true,
"cmd": "/usr/bin/bash -ic \"nvm --version\"",
"delta": "0:00:00.107722",
"end": "2023-07-25 15:57:47.708570",
"invocation": {
     "module_args": {
            "_raw_params": "/usr/bin/bash -ic \"nvm --version\"",
            "_uses_shell": true, 
            "argv": null,
            "chdir": null,
            "creates": null,
            "executable": null,
            "removes": null,
            "stdin": null,
            "stdin_add_newline": true,
            "strip_empty_ends": true,
            "warn": false
        }
    },
    "msg": "non-zero return code",
    "rc": 127,
    "start": "2023-07-25 15:57:47.600848",
    "stderr": "Command 'nvm' not found, did you mean:\n  command 'gvm' from snap gvm (1.1.0)\n  command 'nsm' from snap nift (3.0.2)\n  command 'nvim' from snap nvim (v0.9.1)\n  command 'nam' from deb nam (1.15-5.2)\n  command 'npm' from deb npm (8.5.1~ds-1)\n  command 'nvme' from deb nvme-cli (1.16-3ubuntu0.1)\n  command 'nsm' from deb linuxptp (3.1.1-3)\n  command 'nvi' from deb nvi (1.81.6-17)\n  command 'nvim' from deb neovim (0.6.1-3)\n  command 'num' from deb quickcal (2.4-1)\n  command 'lvm' from deb lvm2 (2.03.11-2.1ubuntu4)\n  command 'nm' from deb binutils (2.38-4ubuntu2.2)\n  command 'kvm' from deb qemu-system-x86 (1:6.2+dfsg-2ubuntu6.12)\n  command 'vm' from deb mgetty-voice (1.2.1-1.1)\n  command 'pvm' from deb pvm (3.4.6-3.2)\nSee 'snap info <snapname>' for additional versions.",
    "stderr_lines": [
        "Command 'nvm' not found, did you mean:",
        "  command 'gvm' from snap gvm (1.1.0)",
        "  command 'nsm' from snap nift (3.0.2)",
        "  command 'nvim' from snap nvim (v0.9.1)",
        "  command 'nam' from deb nam (1.15-5.2)",
        "  command 'npm' from deb npm (8.5.1~ds-1)",
        "  command 'nvme' from deb nvme-cli (1.16-3ubuntu0.1)",
        "  command 'nsm' from deb linuxptp (3.1.1-3)",
        "  command 'nvi' from deb nvi (1.81.6-17)",
        "  command 'nvim' from deb neovim (0.6.1-3)",
        "  command 'num' from deb quickcal (2.4-1)",
        "  command 'lvm' from deb lvm2 (2.03.11-2.1ubuntu4)",
        "  command 'nm' from deb binutils (2.38-4ubuntu2.2)",
        "  command 'kvm' from deb qemu-system-x86 (1:6.2+dfsg-2ubuntu6.12)",
        "  command 'vm' from deb mgetty-voice (1.2.1-1.1)",
        "  command 'pvm' from deb pvm (3.4.6-3.2)",
        "See 'snap info <snapname>' for additional versions."
    ],
    "stdout": "",
    "stdout_lines": []
}

Additional context I question if this is the same as #15 but since they didn't give enough information, it is hard to tell. You can test whether or not the local (sub)shell will have access to nvm by entering bash in a terminal and trying any nvm command.

morgangraphics commented 1 year ago

i'll take a look.

neutralalice commented 1 year ago

The more I thought about it today I'm wondering if it actually has to do with it being run locally, and in the non-home directory; I wondered if my statement in the op "although this role generates it's own in the playbooks directory in this scenario as well" with respect to .bashrc is the real core of the problem. Since the generated .bashrc will be in a nonstandard directory(i.e. where the playbook lives /home/pickles/ubuntu2204-wsl), and not be sourced by the shell when it sets up the local connection.

If I set nvm_profile "~/.bashrc" instead of letting it default nvm_profile: ".bashrc" as well as remove the becomes from the nvm tasks(there's 3 of them) so it doesn't expand out to /root/.bashrc then it runs fine locally.

I think the removing the become arguments may be reasonable for the same reasons as described in the readme. Unless I'm missing something, I don't think those specific tasks should actually need to be elevated? In the "best" example for the role import, it would context switch to the user anyway, and so I'm not sure what purpose those would be serving; The become for those tasks is either defaulting to root, or to the user which has already been context switched to in the play itself.

morgangraphics commented 1 year ago

The ansible-role-nvm runs in the context of the user currently running the role.

So, if you are running the role and have become: true or become: yes or become: 1 at the top of your playbook (scoped globally) the ansible-role-nvm will run as root user

OR

have any of those declarations as part of the ansible-role-nvm role itself, the ansible-role-nvm will run as root user

OR

are running the role as the mustard user, the ansible-role-nvm will run as mustard user and install in /home/mustard/ directory

:thumbsdown: This is likely NOT what you want

- hosts: all
  become: true           # THIS RUNS ALL TASKS, FOR ALL HOSTS, AS ROOT_USER
  become_method: sudo    # THIS RUNS ALL TASKS, FOR ALL HOSTS, AS ROOT_USER

  roles:
    - role: ansible-role-nvm     # installs nvm in the root user directory /root/.bashrc
      nodejs_version: "8.16.0"
      nvm_commands:
       - "nvm exec default npm install"

    - role: some-other-role
      ...

Secondly, if the user pickles is not a real user on the machine, the role will not work as expected. You will need to generate the user and home directory first, then install the role as that user.

- hosts: host1

  pre_tasks:

    - name: add new user
      user:
        name: "pickles"
      become: true

  roles:

    - role: ansible-role-nvm
      nodejs_version: "8.16.0"
      nvm_profile: "/home/pickles/.bashrc"
      nvm_commands:
        - "whoami"
        - "node --version"
        - "nvm --version"
        - "npm --version"
      become_user: pickles
      become: true

In the example above, the nvm_profile key is optional, I use it for illustrative purposes in case people have renamed or have different .bashrc file.

It also scopes the install of NVM to the user it is destined for (in this case pickles). This is particularly useful when:

  1. You are running the role as the mustard user
  2. You have no other option because other roles need become: true or become: yes or become: 1 to work properly
neutralalice commented 1 year ago

pickles is a real user (it was a test vm), and the playbook listed was the playbook utilized with zero changes; In other words, I did not set become in the playbook. Because it's run locally without a "become_user: pickles" in the role include, when it gets to the become commands in the actual tasks, it becomes as root by default. The example playbooks listed in the readme (super simple, simple, more complex) will also exhibit this behavior.

If run locally as the user scoped in the playbook with become commands, there would be no problem. the tasks themselves don't need to attempt to become anything since "become: true" is set your examples in the above.

The problem is that the tasks which utilize the defaults variable generate relative ".bashrc". When you run a playbook locally it scopes to the directory that the playbook is run in, not the home directory of the user context. So in my case, with no changes to the role it will generate a .bashrc in the playbook directory (i.e. /home/pickles/ubuntu2204-wsl/.bashrc with root:root owner/group) and not adjust the actual sourced .bashrc (i.e. /home/pickles/.bashrc) for the user.

morgangraphics commented 1 year ago

Ahh, ok

The problem is that the tasks which utilize the defaults variable generate relative ".bashrc". When you run a playbook locally it scopes to the directory that the playbook is run in, not the home directory of the user context.

That was helpful.

Putting this all together, I see a few fixes here:

  1. The default .bashrc file path should be ~/.bashrc instead of .bashrc as you pointed out
  2. When creating/modifying the .bashrc file, I should add the owner:group attributes so the file is scoped to the user appropriately
morgangraphics commented 1 year ago

Would you mind testing this branch to see if this addresses your issue?

https://github.com/morgangraphics/ansible-role-nvm/tree/bugfix/41-connection-local

Or provide me with your playbook, so I can test

neutralalice commented 1 year ago

I will give it a go shortly.

I did find a different issue, but I'll open up a separate issue for the role if I can figure out exactly what's triggering it( I believe it's related to the interactive shell usage option).

neutralalice commented 1 year ago

The branch worked using the same playbook as listed in the OP (and also setting become as a local user, but not one running the playbook)

morgangraphics commented 1 year ago

I believe the original issue has been addressed. If you are still having issues, please open another ticket. Thanks for participating in the process and helping with the project.