Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

yarn: Fix state=latest not working with global=true #5829

Merged
merged 11 commits into from
Feb 13, 2023
4 changes: 4 additions & 0 deletions changelogs/fragments/5829-fix-yarn-global.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
bugfixes:
- yarn - fix ``state=latest`` not working with ``global=true`` (https://github.com/ansible-collections/community.general/issues/5712).
- yarn - fix ``global=true`` to check for the configured global folder instead of assuming the default (https://github.com/ansible-collections/community.general/pull/5829)
- yarn - fix ``state=absent`` not working with ``global=true`` when the package does not include a binary (https://github.com/ansible-collections/community.general/pull/5829)
43 changes: 22 additions & 21 deletions plugins/modules/yarn.py
Original file line number Diff line number Diff line change
Expand Up @@ -163,8 +163,6 @@

class Yarn(object):

DEFAULT_GLOBAL_INSTALLATION_PATH = os.path.expanduser('~/.config/yarn/global')

def __init__(self, module, **kwargs):
self.module = module
self.globally = kwargs['globally']
Expand All @@ -188,10 +186,12 @@ def __init__(self, module, **kwargs):
elif self.name is not None:
self.name_version = self.name

def _exec(self, args, run_in_check_mode=False, check_rc=True):
def _exec(self, args, run_in_check_mode=False, check_rc=True, unsupported_with_global=False):
if not self.module.check_mode or (self.module.check_mode and run_in_check_mode):

if self.globally:
with_global_arg = self.globally and not unsupported_with_global

if with_global_arg:
# Yarn global arg is inserted before the command (e.g. `yarn global {some-command}`)
args.insert(0, 'global')

Expand All @@ -207,7 +207,7 @@ def _exec(self, args, run_in_check_mode=False, check_rc=True):

# If path is specified, cd into that path and run the command.
cwd = None
if self.path and not self.globally:
if self.path and not with_global_arg:
if not os.path.exists(self.path):
# Module will make directory if not exists.
os.makedirs(self.path)
Expand All @@ -233,24 +233,21 @@ def list(self):
missing.append(self.name)
return installed, missing

result, error = self._exec(cmd, True, False)
# `yarn global list` should be treated as "unsupported with global" even though it exists,
# because it only only lists binaries, but `yarn global add` can install libraries too.
result, error = self._exec(cmd, run_in_check_mode=True, check_rc=False, unsupported_with_global=True)

if error:
self.module.fail_json(msg=error)

for json_line in result.strip().split('\n'):
data = json.loads(json_line)
if self.globally:
if data['type'] == 'list' and data['data']['type'].startswith('bins-'):
# This is a string in format: 'bins-<PACKAGE_NAME>'
installed.append(data['data']['type'][5:])
else:
if data['type'] == 'tree':
dependencies = data['data']['trees']

for dep in dependencies:
name, version = dep['name'].rsplit('@', 1)
installed.append(name)
if data['type'] == 'tree':
dependencies = data['data']['trees']

for dep in dependencies:
name, version = dep['name'].rsplit('@', 1)
installed.append(name)

if self.name not in installed:
missing.append(self.name)
Expand All @@ -276,9 +273,12 @@ def list_outdated(self):
if not os.path.isfile(os.path.join(self.path, 'yarn.lock')):
return outdated

cmd_result, err = self._exec(['outdated', '--json'], True, False)
if err:
self.module.fail_json(msg=err)
cmd_result, err = self._exec(['outdated', '--json'], True, False, unsupported_with_global=True)

# the package.json in the global dir is missing a license field, so warnings are expected on stderr
for line in err.splitlines():
if json.loads(line)['type'] == 'error':
self.module.fail_json(msg=err)

if not cmd_result:
return outdated
Expand Down Expand Up @@ -340,7 +340,8 @@ def main():

# When installing globally, use the defined path for global node_modules
if globally:
path = Yarn.DEFAULT_GLOBAL_INSTALLATION_PATH
_rc, out, _err = module.run_command([executable, 'global', 'dir'], check_rc=True)
path = out.strip()

yarn = Yarn(module,
name=name,
Expand Down
87 changes: 87 additions & 0 deletions tests/integration/targets/yarn/tasks/run.yml
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
package: 'iconv-lite'
environment:
PATH: "{{ node_bin_path }}:{{ansible_env.PATH}}"
YARN_IGNORE_ENGINES: true
block:

# Get the version of Yarn and register to a variable
Expand Down Expand Up @@ -135,3 +136,89 @@
assert:
that:
- yarn_uninstall_package is changed

- name: 'Global install binary with explicit version (older version of package)'
yarn:
global: true
executable: '{{ yarn_bin_path }}/yarn'
name: prettier
version: 2.0.0
state: present
environment:
PATH: '{{ node_bin_path }}:{{ ansible_env.PATH }}'
register: yarn_global_install_old_binary

- assert:
that:
- yarn_global_install_old_binary is changed

- name: 'Global upgrade old binary'
yarn:
global: true
executable: '{{ yarn_bin_path }}/yarn'
name: prettier
state: latest
environment:
PATH: '{{ node_bin_path }}:{{ ansible_env.PATH }}'
register: yarn_global_update_old_binary

- assert:
that:
- yarn_global_update_old_binary is changed

- name: 'Global remove a binary'
yarn:
global: true
executable: '{{ yarn_bin_path }}/yarn'
name: prettier
state: absent
environment:
PATH: '{{ node_bin_path }}:{{ ansible_env.PATH }}'
register: yarn_global_uninstall_binary

- assert:
that:
- yarn_global_uninstall_binary is changed

- name: 'Global install package with no binary with explicit version (older version of package)'
yarn:
global: true
executable: '{{ yarn_bin_path }}/yarn'
name: left-pad
version: 1.1.0
state: present
environment:
PATH: '{{ node_bin_path }}:{{ ansible_env.PATH }}'
register: yarn_global_install_old_package

- assert:
that:
- yarn_global_install_old_package is changed

- name: 'Global upgrade old package with no binary'
yarn:
global: true
executable: '{{ yarn_bin_path }}/yarn'
name: left-pad
state: latest
environment:
PATH: '{{ node_bin_path }}:{{ ansible_env.PATH }}'
register: yarn_global_update_old_package

- assert:
that:
- yarn_global_update_old_package is changed

- name: 'Global remove a package with no binary'
yarn:
global: true
executable: '{{ yarn_bin_path }}/yarn'
name: left-pad
state: absent
environment:
PATH: '{{ node_bin_path }}:{{ ansible_env.PATH }}'
register: yarn_global_uninstall_package

- assert:
that:
- yarn_global_uninstall_package is changed
1 change: 0 additions & 1 deletion tests/sanity/ignore-2.11.txt
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,5 @@ plugins/modules/rax_files.py validate-modules:parameter-state-invalid-choice
plugins/modules/rax.py use-argspec-type-path # fix needed
plugins/modules/rhevm.py validate-modules:parameter-state-invalid-choice
plugins/modules/xfconf.py validate-modules:return-syntax-error
plugins/modules/yarn.py use-argspec-type-path
tests/integration/targets/django_manage/files/base_test/simple_project/p1/manage.py compile-2.6 # django generated code
tests/integration/targets/django_manage/files/base_test/simple_project/p1/manage.py compile-2.7 # django generated code
1 change: 0 additions & 1 deletion tests/sanity/ignore-2.12.txt
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,3 @@ plugins/modules/rax_files.py validate-modules:parameter-state-invalid-choice
plugins/modules/rax.py use-argspec-type-path # fix needed
plugins/modules/rhevm.py validate-modules:parameter-state-invalid-choice
plugins/modules/xfconf.py validate-modules:return-syntax-error
plugins/modules/yarn.py use-argspec-type-path
1 change: 0 additions & 1 deletion tests/sanity/ignore-2.13.txt
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,3 @@ plugins/modules/rax_files.py validate-modules:parameter-state-invalid-choice
plugins/modules/rax.py use-argspec-type-path # fix needed
plugins/modules/rhevm.py validate-modules:parameter-state-invalid-choice
plugins/modules/xfconf.py validate-modules:return-syntax-error
plugins/modules/yarn.py use-argspec-type-path
1 change: 0 additions & 1 deletion tests/sanity/ignore-2.14.txt
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,3 @@ plugins/modules/rax.py use-argspec-type-path # fix needed
plugins/modules/rhevm.py validate-modules:parameter-state-invalid-choice
plugins/modules/udm_user.py import-3.11 # Uses deprecated stdlib library 'crypt'
plugins/modules/xfconf.py validate-modules:return-syntax-error
plugins/modules/yarn.py use-argspec-type-path
1 change: 0 additions & 1 deletion tests/sanity/ignore-2.15.txt
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,3 @@ plugins/modules/rax.py use-argspec-type-path # fix needed
plugins/modules/rhevm.py validate-modules:parameter-state-invalid-choice
plugins/modules/udm_user.py import-3.11 # Uses deprecated stdlib library 'crypt'
plugins/modules/xfconf.py validate-modules:return-syntax-error
plugins/modules/yarn.py use-argspec-type-path