Repository: ansible-collections/community.general

Issue:
1Password lookup uses invalid config
### Summary

The 1Password lookup plug-in looks for the config file in `~/.op/config` but it nowadays exists in `~/.config/op/config` (`op` 1.12.2 on macOS).

### Issue Type

Bug Report

### Component Name

community.general.onepassword

### Ansible Version

```console (paste below)
$ ansible --version
ansible [core 2.12.1]
  config file = /Users/user/.ansible.cfg
  configured module search path = ['/Users/user/.ansible/plugins/modules', '/usr/share/ansible/plugins/modules']
  ansible python module location = /usr/local/Cellar/ansible/5.1.0/libexec/lib/python3.10/site-packages/ansible
  ansible collection location = /Users/user/.ansible/collections:/usr/share/ansible/collections
  executable location = /usr/local/bin/ansible
  python version = 3.10.1 (main, Dec  6 2021, 22:25:40) [Clang 13.0.0 (clang-1300.0.29.3)]
  jinja version = 3.0.3
  libyaml = True

```


### Community.general Version

```console (paste below)
$ ansible-galaxy collection list community.general
# /usr/local/Cellar/ansible/5.1.0/libexec/lib/python3.10/site-packages/ansible_collections
Collection        Version
----------------- -------
community.general 4.2.0  
```


### Configuration

```console (paste below)
$ ansible-config dump --only-changed
<empty>
```


### OS / Environment

macOS 12.1

### Steps to Reproduce

<!--- Paste example playbooks or commands between quotes below -->
```yaml (paste below)
- name: Retrieve password for KITT when already signed in to 1Password
  ansible.builtin.debug:
    var: lookup('community.general.onepassword', 'KITT')
```


### Expected Results

Password is prompted when signed out, but still configured (as documented).

### Actual Results

```console (paste below)
TASK [Retrieve password for KITT when already signed in to 1password] ***********
fatal: [host]: FAILED! => {"msg": "Unable to perform initial sign in to 1Password. subdomain, username, secret_key, and master_password are required to perform initial sign in."}
```


### Code of Conduct

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


Discussion:
Files identified in the description:
* [`plugins/modules/identity/onepassword_info.py`](https://github.com/ansible-collections/community.general/blob/main/plugins/modules/identity/onepassword_info.py)

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](https://github.com/ansible/ansibullbot/blob/devel/ISSUE_HELP.md)
<!--- boilerplate: components_banner --->
cc @Rylon
[click here for bot help](https://github.com/ansible/ansibullbot/blob/devel/ISSUE_HELP.md)
<!--- boilerplate: notify --->
!component +plugins/lookup/onepassword
Files identified in the description:
* `plugins/lookup/onepassword`
* [`plugins/modules/identity/onepassword_info.py`](https://github.com/ansible-collections/community.general/blob/main/plugins/modules/identity/onepassword_info.py)

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](https://github.com/ansible/ansibullbot/blob/devel/ISSUE_HELP.md)
<!--- boilerplate: components_banner --->
cc @samdoran
[click here for bot help](https://github.com/ansible/ansibullbot/blob/devel/ISSUE_HELP.md)
<!--- boilerplate: notify --->
!component -plugins/modules/identity/onepassword_info
!component =plugins/lookup/onepassword.py
Files identified in the description:
* [`plugins/lookup/onepassword.py`](https://github.com/ansible-collections/community.general/blob/main/plugins/lookup/onepassword.py)

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](https://github.com/ansible/ansibullbot/blob/devel/ISSUE_HELP.md)
<!--- boilerplate: components_banner --->
cc @azenk @scottsb
[click here for bot help](https://github.com/ansible/ansibullbot/blob/devel/ISSUE_HELP.md)
<!--- boilerplate: notify --->
The plugin documentation states that it uses `~/.op/config`, so this is clearly not a bug, but a mismatched expectation. This is a valid feature request though.
I would still classify it as a bug, since the feature doesn’t work anymore (for anyone with a reasonably recent version of the 1Password CLI). The documentation is also buggy if it claims the config should exist in an invalid location.
Ah, I see what you mean. I guess the plugin will have to check both locations, potentially depending on the version of the `op` tool.
cc @jatorcasso You can take a stab at this.

Resolving patch:
diff --git a/plugins/lookup/onepassword.py b/plugins/lookup/onepassword.py
--- a/plugins/lookup/onepassword.py
+++ b/plugins/lookup/onepassword.py
@@ -45,8 +45,8 @@
         description: Vault containing the item to retrieve (case-insensitive). If absent will search all vaults.
     notes:
       - This lookup will use an existing 1Password session if one exists. If not, and you have already
-        performed an initial sign in (meaning C(~/.op/config exists)), then only the C(master_password) is required.
-        You may optionally specify C(subdomain) in this scenario, otherwise the last used subdomain will be used by C(op).
+        performed an initial sign in (meaning C(~/.op/config), C(~/.config/op/config) or C(~/.config/.op/config) exists), then only the
+        C(master_password) is required. You may optionally specify C(subdomain) in this scenario, otherwise the last used subdomain will be used by C(op).
       - This lookup can perform an initial login by providing C(subdomain), C(username), C(secret_key), and C(master_password).
       - Due to the B(very) sensitive nature of these credentials, it is B(highly) recommended that you only pass in the minimal credentials
         needed at any given time. Also, store these credentials in an Ansible Vault using a key that is equal to or greater in strength
@@ -105,12 +105,12 @@
 from ansible.errors import AnsibleLookupError
 from ansible.module_utils.common.text.converters import to_bytes, to_text
 
+from ansible_collections.community.general.plugins.module_utils.onepassword import OnePasswordConfig
 
-class OnePass(object):
 
+class OnePass(object):
     def __init__(self, path='op'):
         self.cli_path = path
-        self.config_file_path = os.path.expanduser('~/.op/config')
         self.logged_in = False
         self.token = None
         self.subdomain = None
@@ -119,9 +119,11 @@ def __init__(self, path='op'):
         self.secret_key = None
         self.master_password = None
 
+        self._config = OnePasswordConfig()
+
     def get_token(self):
         # If the config file exists, assume an initial signin has taken place and try basic sign in
-        if os.path.isfile(self.config_file_path):
+        if os.path.isfile(self._config.config_file_path):
 
             if not self.master_password:
                 raise AnsibleLookupError('Unable to sign in to 1Password. master_password is required.')
@@ -281,4 +283,5 @@ def run(self, terms, variables=None, **kwargs):
         values = []
         for term in terms:
             values.append(op.get_field(term, field, section, vault))
+
         return values
diff --git a/plugins/module_utils/onepassword.py b/plugins/module_utils/onepassword.py
new file mode 100644
--- /dev/null
+++ b/plugins/module_utils/onepassword.py
@@ -0,0 +1,29 @@
+# -*- coding: utf-8 -*-
+# Simplified BSD License (see licenses/simplified_bsd.txt or https://opensource.org/licenses/BSD-2-Clause)
+
+from __future__ import (absolute_import, division, print_function)
+__metaclass__ = type
+
+import os
+
+
+class OnePasswordConfig(object):
+    _config_file_paths = (
+        "~/.op/config",
+        "~/.config/op/config",
+        "~/.config/.op/config",
+    )
+
+    def __init__(self):
+        self._config_file_path = ""
+
+    @property
+    def config_file_path(self):
+        if self._config_file_path:
+            return self._config_file_path
+
+        for path in self._config_file_paths:
+            realpath = os.path.expanduser(path)
+            if os.path.exists(realpath):
+                self._config_file_path = realpath
+                return self._config_file_path
diff --git a/plugins/modules/identity/onepassword_info.py b/plugins/modules/identity/onepassword_info.py
--- a/plugins/modules/identity/onepassword_info.py
+++ b/plugins/modules/identity/onepassword_info.py
@@ -166,6 +166,8 @@
 from ansible.module_utils.common.text.converters import to_bytes, to_native
 from ansible.module_utils.basic import AnsibleModule
 
+from ansible_collections.community.general.plugins.module_utils.onepassword import OnePasswordConfig
+
 
 class AnsibleModuleError(Exception):
     def __init__(self, results):
@@ -179,7 +181,6 @@ class OnePasswordInfo(object):
 
     def __init__(self):
         self.cli_path = module.params.get('cli_path')
-        self.config_file_path = '~/.op/config'
         self.auto_login = module.params.get('auto_login')
         self.logged_in = False
         self.token = None
@@ -187,6 +188,8 @@ def __init__(self):
         terms = module.params.get('search_terms')
         self.terms = self.parse_search_terms(terms)
 
+        self._config = OnePasswordConfig()
+
     def _run(self, args, expected_rc=0, command_input=None, ignore_errors=False):
         if self.token:
             # Adds the session token to all commands if we're logged in.
@@ -299,7 +302,7 @@ def full_login(self):
 
     def get_token(self):
         # If the config file exists, assume an initial signin has taken place and try basic sign in
-        if os.path.isfile(self.config_file_path):
+        if os.path.isfile(self._config.config_file_path):
 
             if self.auto_login is not None:
 


Test patch: