diff --git a/changelogs/fragments/5829-fix-yarn-global.yml b/changelogs/fragments/5829-fix-yarn-global.yml new file mode 100644 index 0000000000..fade7d97f6 --- /dev/null +++ b/changelogs/fragments/5829-fix-yarn-global.yml @@ -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) diff --git a/plugins/modules/yarn.py b/plugins/modules/yarn.py index 2e96cb799d..df55925dba 100644 --- a/plugins/modules/yarn.py +++ b/plugins/modules/yarn.py @@ -163,8 +163,6 @@ from ansible.module_utils.basic import AnsibleModule 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'] @@ -188,10 +186,12 @@ class Yarn(object): 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') @@ -207,7 +207,7 @@ class Yarn(object): # 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) @@ -233,24 +233,21 @@ class Yarn(object): 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-' - installed.append(data['data']['type'][5:]) - else: - if data['type'] == 'tree': - dependencies = data['data']['trees'] + if data['type'] == 'tree': + dependencies = data['data']['trees'] - for dep in dependencies: - name, version = dep['name'].rsplit('@', 1) - installed.append(name) + for dep in dependencies: + name, version = dep['name'].rsplit('@', 1) + installed.append(name) if self.name not in installed: missing.append(self.name) @@ -276,9 +273,12 @@ class Yarn(object): 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 @@ -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, diff --git a/tests/integration/targets/yarn/tasks/run.yml b/tests/integration/targets/yarn/tasks/run.yml index 367d7dcf2c..6521d45fcf 100644 --- a/tests/integration/targets/yarn/tasks/run.yml +++ b/tests/integration/targets/yarn/tasks/run.yml @@ -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 @@ -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 diff --git a/tests/sanity/ignore-2.11.txt b/tests/sanity/ignore-2.11.txt index 508785f220..336a923589 100644 --- a/tests/sanity/ignore-2.11.txt +++ b/tests/sanity/ignore-2.11.txt @@ -24,6 +24,5 @@ plugins/modules/rax_files.py validate-modules:parameter-state-invalid-choice plugins/modules/rax.py use-argspec-type-path # module deprecated - removed in 9.0.0 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 diff --git a/tests/sanity/ignore-2.12.txt b/tests/sanity/ignore-2.12.txt index 77a0105529..2386cda087 100644 --- a/tests/sanity/ignore-2.12.txt +++ b/tests/sanity/ignore-2.12.txt @@ -19,4 +19,3 @@ plugins/modules/rax_files.py validate-modules:parameter-state-invalid-choice plugins/modules/rax.py use-argspec-type-path # module deprecated - removed in 9.0.0 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 diff --git a/tests/sanity/ignore-2.13.txt b/tests/sanity/ignore-2.13.txt index 77a0105529..2386cda087 100644 --- a/tests/sanity/ignore-2.13.txt +++ b/tests/sanity/ignore-2.13.txt @@ -19,4 +19,3 @@ plugins/modules/rax_files.py validate-modules:parameter-state-invalid-choice plugins/modules/rax.py use-argspec-type-path # module deprecated - removed in 9.0.0 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 diff --git a/tests/sanity/ignore-2.14.txt b/tests/sanity/ignore-2.14.txt index db0e449b79..ec887e18b8 100644 --- a/tests/sanity/ignore-2.14.txt +++ b/tests/sanity/ignore-2.14.txt @@ -21,4 +21,3 @@ plugins/modules/rax.py use-argspec-type-path 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 diff --git a/tests/sanity/ignore-2.15.txt b/tests/sanity/ignore-2.15.txt index db0e449b79..ec887e18b8 100644 --- a/tests/sanity/ignore-2.15.txt +++ b/tests/sanity/ignore-2.15.txt @@ -21,4 +21,3 @@ plugins/modules/rax.py use-argspec-type-path 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