mirror of
https://github.com/ansible-collections/community.general.git
synced 2025-07-28 07:31:23 -07:00
Initial commit
This commit is contained in:
commit
aebc1b03fd
4861 changed files with 812621 additions and 0 deletions
0
tests/unit/plugins/lookup/__init__.py
Normal file
0
tests/unit/plugins/lookup/__init__.py
Normal file
104
tests/unit/plugins/lookup/fixtures/avi.json
Normal file
104
tests/unit/plugins/lookup/fixtures/avi.json
Normal file
|
@ -0,0 +1,104 @@
|
|||
{
|
||||
"mock_single_obj": {
|
||||
"_last_modified": "",
|
||||
"cloud_ref": "https://192.0.2.132/api/cloud/cloud-4d063be1-99c2-44cf-8b28-977bd970524c",
|
||||
"dhcp_enabled": true,
|
||||
"exclude_discovered_subnets": false,
|
||||
"name": "PG-123",
|
||||
"synced_from_se": true,
|
||||
"tenant_ref": "https://192.0.2.132/api/tenant/admin",
|
||||
"url": "https://192.0.2.132/api/network/dvportgroup-2084-cloud-4d063be1-99c2-44cf-8b28-977bd970524c",
|
||||
"uuid": "dvportgroup-2084-cloud-4d063be1-99c2-44cf-8b28-977bd970524c",
|
||||
"vcenter_dvs": true,
|
||||
"vimgrnw_ref": "https://192.0.2.132/api/vimgrnwruntime/dvportgroup-2084-cloud-4d063be1-99c2-44cf-8b28-977bd970524c",
|
||||
"vrf_context_ref": "https://192.0.2.132/api/vrfcontext/vrfcontext-31f1b55f-319c-44eb-862f-69d79ffdf295"
|
||||
},
|
||||
"mock_multiple_obj": {
|
||||
"results": [
|
||||
{
|
||||
"_last_modified": "",
|
||||
"cloud_ref": "https://192.0.2.132/api/cloud/cloud-4d063be1-99c2-44cf-8b28-977bd970524c",
|
||||
"dhcp_enabled": true,
|
||||
"exclude_discovered_subnets": false,
|
||||
"name": "J-PG-0682",
|
||||
"synced_from_se": true,
|
||||
"tenant_ref": "https://192.0.2.132/api/tenant/admin",
|
||||
"url": "https://192.0.2.132/api/network/dvportgroup-2084-cloud-4d063be1-99c2-44cf-8b28-977bd970524c",
|
||||
"uuid": "dvportgroup-2084-cloud-4d063be1-99c2-44cf-8b28-977bd970524c",
|
||||
"vcenter_dvs": true,
|
||||
"vimgrnw_ref": "https://192.0.2.132/api/vimgrnwruntime/dvportgroup-2084-cloud-4d063be1-99c2-44cf-8b28-977bd970524c",
|
||||
"vrf_context_ref": "https://192.0.2.132/api/vrfcontext/vrfcontext-31f1b55f-319c-44eb-862f-69d79ffdf295"
|
||||
},
|
||||
{
|
||||
"_last_modified": "",
|
||||
"cloud_ref": "https://192.0.2.132/api/cloud/cloud-4d063be1-99c2-44cf-8b28-977bd970524c",
|
||||
"dhcp_enabled": true,
|
||||
"exclude_discovered_subnets": false,
|
||||
"name": "J-PG-0231",
|
||||
"synced_from_se": true,
|
||||
"tenant_ref": "https://192.0.2.132/api/tenant/admin",
|
||||
"url": "https://192.0.2.132/api/network/dvportgroup-1627-cloud-4d063be1-99c2-44cf-8b28-977bd970524c",
|
||||
"uuid": "dvportgroup-1627-cloud-4d063be1-99c2-44cf-8b28-977bd970524c",
|
||||
"vcenter_dvs": true,
|
||||
"vimgrnw_ref": "https://192.0.2.132/api/vimgrnwruntime/dvportgroup-1627-cloud-4d063be1-99c2-44cf-8b28-977bd970524c",
|
||||
"vrf_context_ref": "https://192.0.2.132/api/vrfcontext/vrfcontext-31f1b55f-319c-44eb-862f-69d79ffdf295"
|
||||
},
|
||||
{
|
||||
"_last_modified": "",
|
||||
"cloud_ref": "https://192.0.2.132/api/cloud/cloud-4d063be1-99c2-44cf-8b28-977bd970524c",
|
||||
"dhcp_enabled": true,
|
||||
"exclude_discovered_subnets": false,
|
||||
"name": "J-PG-0535",
|
||||
"synced_from_se": true,
|
||||
"tenant_ref": "https://192.0.2.132/api/tenant/admin",
|
||||
"url": "https://192.0.2.132/api/network/dvportgroup-1934-cloud-4d063be1-99c2-44cf-8b28-977bd970524c",
|
||||
"uuid": "dvportgroup-1934-cloud-4d063be1-99c2-44cf-8b28-977bd970524c",
|
||||
"vcenter_dvs": true,
|
||||
"vimgrnw_ref": "https://192.0.2.132/api/vimgrnwruntime/dvportgroup-1934-cloud-4d063be1-99c2-44cf-8b28-977bd970524c",
|
||||
"vrf_context_ref": "https://192.0.2.132/api/vrfcontext/vrfcontext-31f1b55f-319c-44eb-862f-69d79ffdf295"
|
||||
},
|
||||
{
|
||||
"_last_modified": "",
|
||||
"cloud_ref": "https://192.0.2.132/api/cloud/cloud-4d063be1-99c2-44cf-8b28-977bd970524c",
|
||||
"dhcp_enabled": true,
|
||||
"exclude_discovered_subnets": false,
|
||||
"name": "J-PG-0094",
|
||||
"synced_from_se": true,
|
||||
"tenant_ref": "https://192.0.2.132/api/tenant/admin",
|
||||
"url": "https://192.0.2.132/api/network/dvportgroup-1458-cloud-4d063be1-99c2-44cf-8b28-977bd970524c",
|
||||
"uuid": "dvportgroup-1458-cloud-4d063be1-99c2-44cf-8b28-977bd970524c",
|
||||
"vcenter_dvs": true,
|
||||
"vimgrnw_ref": "https://192.0.2.132/api/vimgrnwruntime/dvportgroup-1458-cloud-4d063be1-99c2-44cf-8b28-977bd970524c",
|
||||
"vrf_context_ref": "https://192.0.2.132/api/vrfcontext/vrfcontext-31f1b55f-319c-44eb-862f-69d79ffdf295"
|
||||
},
|
||||
{
|
||||
"_last_modified": "",
|
||||
"cloud_ref": "https://192.0.2.132/api/cloud/cloud-4d063be1-99c2-44cf-8b28-977bd970524c",
|
||||
"dhcp_enabled": true,
|
||||
"exclude_discovered_subnets": false,
|
||||
"name": "J-PG-0437",
|
||||
"synced_from_se": true,
|
||||
"tenant_ref": "https://192.0.2.132/api/tenant/admin",
|
||||
"url": "https://192.0.2.132/api/network/dvportgroup-1836-cloud-4d063be1-99c2-44cf-8b28-977bd970524c",
|
||||
"uuid": "dvportgroup-1836-cloud-4d063be1-99c2-44cf-8b28-977bd970524c",
|
||||
"vcenter_dvs": true,
|
||||
"vimgrnw_ref": "https://192.0.2.132/api/vimgrnwruntime/dvportgroup-1836-cloud-4d063be1-99c2-44cf-8b28-977bd970524c",
|
||||
"vrf_context_ref": "https://192.0.2.132/api/vrfcontext/vrfcontext-31f1b55f-319c-44eb-862f-69d79ffdf295"
|
||||
},
|
||||
{
|
||||
"_last_modified": "",
|
||||
"cloud_ref": "https://192.0.2.132/api/cloud/cloud-4d063be1-99c2-44cf-8b28-977bd970524c",
|
||||
"dhcp_enabled": true,
|
||||
"exclude_discovered_subnets": false,
|
||||
"name": "J-PG-0673",
|
||||
"synced_from_se": true,
|
||||
"tenant_ref": "https://192.0.2.132/api/tenant/admin",
|
||||
"url": "https://192.0.2.132/api/network/dvportgroup-2075-cloud-4d063be1-99c2-44cf-8b28-977bd970524c",
|
||||
"uuid": "dvportgroup-2075-cloud-4d063be1-99c2-44cf-8b28-977bd970524c",
|
||||
"vcenter_dvs": true,
|
||||
"vimgrnw_ref": "https://192.0.2.132/api/vimgrnwruntime/dvportgroup-2075-cloud-4d063be1-99c2-44cf-8b28-977bd970524c",
|
||||
"vrf_context_ref": "https://192.0.2.132/api/vrfcontext/vrfcontext-31f1b55f-319c-44eb-862f-69d79ffdf295"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
92
tests/unit/plugins/lookup/test_avi.py
Normal file
92
tests/unit/plugins/lookup/test_avi.py
Normal file
|
@ -0,0 +1,92 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# (c) 2019, Sandeep Bandi <sandeepb@avinetworks.com>
|
||||
#
|
||||
# This file is part of Ansible
|
||||
#
|
||||
# Ansible is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Ansible is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
# Make coding more python3-ish
|
||||
from __future__ import (absolute_import, division, print_function)
|
||||
__metaclass__ = type
|
||||
|
||||
import os
|
||||
import pytest
|
||||
import json
|
||||
|
||||
from ansible_collections.community.general.tests.unit.compat.mock import patch, MagicMock
|
||||
|
||||
from ansible.errors import AnsibleError
|
||||
from ansible.plugins.loader import lookup_loader
|
||||
from ansible_collections.community.general.plugins.lookup import avi
|
||||
|
||||
|
||||
try:
|
||||
import builtins as __builtin__
|
||||
except ImportError:
|
||||
import __builtin__
|
||||
|
||||
|
||||
fixture_path = os.path.join(os.path.dirname(__file__), 'fixtures')
|
||||
|
||||
with open(fixture_path + '/avi.json') as json_file:
|
||||
data = json.load(json_file)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def dummy_credentials():
|
||||
dummy_credentials = {}
|
||||
dummy_credentials['controller'] = "192.0.2.13"
|
||||
dummy_credentials['username'] = "admin"
|
||||
dummy_credentials['password'] = "password"
|
||||
dummy_credentials['api_version'] = "17.2.14"
|
||||
dummy_credentials['tenant'] = 'admin'
|
||||
return dummy_credentials
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def super_switcher(scope="function", autouse=True):
|
||||
# Mocking the inbuilt super as it is used in ApiSession initialization
|
||||
original_super = __builtin__.super
|
||||
__builtin__.super = MagicMock()
|
||||
yield
|
||||
# Revert the super to default state
|
||||
__builtin__.super = original_super
|
||||
|
||||
|
||||
def test_lookup_multiple_obj(dummy_credentials):
|
||||
avi_lookup = lookup_loader.get('avi')
|
||||
avi_mock = MagicMock()
|
||||
avi_mock.return_value.get.return_value.json.return_value = data["mock_multiple_obj"]
|
||||
with patch.object(avi, 'ApiSession', avi_mock):
|
||||
retval = avi_lookup.run([], {}, avi_credentials=dummy_credentials,
|
||||
obj_type="network")
|
||||
assert retval == data["mock_multiple_obj"]["results"]
|
||||
|
||||
|
||||
def test_lookup_single_obj(dummy_credentials):
|
||||
avi_lookup = lookup_loader.get('avi')
|
||||
avi_mock = MagicMock()
|
||||
avi_mock.return_value.get_object_by_name.return_value = data["mock_single_obj"]
|
||||
with patch.object(avi, 'ApiSession', avi_mock):
|
||||
retval = avi_lookup.run([], {}, avi_credentials=dummy_credentials,
|
||||
obj_type="network", obj_name='PG-123')
|
||||
assert retval[0] == data["mock_single_obj"]
|
||||
|
||||
|
||||
def test_invalid_lookup(dummy_credentials):
|
||||
avi_lookup = lookup_loader.get('avi')
|
||||
avi_mock = MagicMock()
|
||||
with pytest.raises(AnsibleError):
|
||||
with patch.object(avi, 'ApiSession', avi_mock):
|
||||
avi_lookup.run([], {}, avi_credentials=dummy_credentials)
|
110
tests/unit/plugins/lookup/test_conjur_variable.py
Normal file
110
tests/unit/plugins/lookup/test_conjur_variable.py
Normal file
|
@ -0,0 +1,110 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# (c) 2018, Jason Vanderhoof <jason.vanderhoof@cyberark.com>
|
||||
#
|
||||
# This file is part of Ansible
|
||||
#
|
||||
# Ansible is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Ansible is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
# Make coding more python3-ish
|
||||
from __future__ import (absolute_import, division, print_function)
|
||||
__metaclass__ = type
|
||||
|
||||
|
||||
import pytest
|
||||
from ansible_collections.community.general.tests.unit.compat.mock import MagicMock
|
||||
from ansible.errors import AnsibleError
|
||||
from ansible.module_utils.six.moves import http_client
|
||||
from ansible_collections.community.general.plugins.lookup import conjur_variable
|
||||
import tempfile
|
||||
|
||||
|
||||
class TestLookupModule:
|
||||
def test_valid_netrc_file(self):
|
||||
with tempfile.NamedTemporaryFile() as temp_netrc:
|
||||
temp_netrc.write(b"machine http://localhost/authn\n")
|
||||
temp_netrc.write(b" login admin\n")
|
||||
temp_netrc.write(b" password my-pass\n")
|
||||
temp_netrc.seek(0)
|
||||
|
||||
results = conjur_variable._load_identity_from_file(temp_netrc.name, 'http://localhost')
|
||||
|
||||
assert results['id'] == 'admin'
|
||||
assert results['api_key'] == 'my-pass'
|
||||
|
||||
def test_netrc_without_host_file(self):
|
||||
with tempfile.NamedTemporaryFile() as temp_netrc:
|
||||
temp_netrc.write(b"machine http://localhost/authn\n")
|
||||
temp_netrc.write(b" login admin\n")
|
||||
temp_netrc.write(b" password my-pass\n")
|
||||
temp_netrc.seek(0)
|
||||
|
||||
with pytest.raises(AnsibleError):
|
||||
conjur_variable._load_identity_from_file(temp_netrc.name, 'http://foo')
|
||||
|
||||
def test_valid_configuration(self):
|
||||
with tempfile.NamedTemporaryFile() as configuration_file:
|
||||
configuration_file.write(b"---\n")
|
||||
configuration_file.write(b"account: demo-policy\n")
|
||||
configuration_file.write(b"plugins: []\n")
|
||||
configuration_file.write(b"appliance_url: http://localhost:8080\n")
|
||||
configuration_file.seek(0)
|
||||
|
||||
results = conjur_variable._load_conf_from_file(configuration_file.name)
|
||||
assert results['account'] == 'demo-policy'
|
||||
assert results['appliance_url'] == 'http://localhost:8080'
|
||||
|
||||
def test_valid_token_retrieval(self, mocker):
|
||||
mock_response = MagicMock(spec_set=http_client.HTTPResponse)
|
||||
try:
|
||||
mock_response.getcode.return_value = 200
|
||||
except Exception:
|
||||
# HTTPResponse is a Python 3 only feature. This uses a generic mock for python 2.6
|
||||
mock_response = MagicMock()
|
||||
mock_response.getcode.return_value = 200
|
||||
|
||||
mock_response.read.return_value = 'foo-bar-token'
|
||||
mocker.patch.object(conjur_variable, 'open_url', return_value=mock_response)
|
||||
|
||||
response = conjur_variable._fetch_conjur_token('http://conjur', 'account', 'username', 'api_key')
|
||||
assert response == 'foo-bar-token'
|
||||
|
||||
def test_valid_fetch_conjur_variable(self, mocker):
|
||||
mock_response = MagicMock(spec_set=http_client.HTTPResponse)
|
||||
try:
|
||||
mock_response.getcode.return_value = 200
|
||||
except Exception:
|
||||
# HTTPResponse is a Python 3 only feature. This uses a generic mock for python 2.6
|
||||
mock_response = MagicMock()
|
||||
mock_response.getcode.return_value = 200
|
||||
|
||||
mock_response.read.return_value = 'foo-bar'
|
||||
mocker.patch.object(conjur_variable, 'open_url', return_value=mock_response)
|
||||
|
||||
response = conjur_variable._fetch_conjur_token('super-secret', 'token', 'http://conjur', 'account')
|
||||
assert response == 'foo-bar'
|
||||
|
||||
def test_invalid_fetch_conjur_variable(self, mocker):
|
||||
for code in [401, 403, 404]:
|
||||
mock_response = MagicMock(spec_set=http_client.HTTPResponse)
|
||||
try:
|
||||
mock_response.getcode.return_value = code
|
||||
except Exception:
|
||||
# HTTPResponse is a Python 3 only feature. This uses a generic mock for python 2.6
|
||||
mock_response = MagicMock()
|
||||
mock_response.getcode.return_value = code
|
||||
|
||||
mocker.patch.object(conjur_variable, 'open_url', return_value=mock_response)
|
||||
|
||||
with pytest.raises(AnsibleError):
|
||||
response = conjur_variable._fetch_conjur_token('super-secret', 'token', 'http://conjur', 'account')
|
187
tests/unit/plugins/lookup/test_lastpass.py
Normal file
187
tests/unit/plugins/lookup/test_lastpass.py
Normal file
|
@ -0,0 +1,187 @@
|
|||
# (c)2016 Andrew Zenk <azenk@umn.edu>
|
||||
#
|
||||
# This file is part of Ansible
|
||||
#
|
||||
# Ansible is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Ansible is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
# Make coding more python3-ish
|
||||
from __future__ import (absolute_import, division, print_function)
|
||||
__metaclass__ = type
|
||||
|
||||
from argparse import ArgumentParser
|
||||
|
||||
from ansible_collections.community.general.tests.unit.compat import unittest
|
||||
from ansible_collections.community.general.tests.unit.compat.mock import patch
|
||||
|
||||
from ansible.errors import AnsibleError
|
||||
from ansible.module_utils import six
|
||||
from ansible_collections.community.general.plugins.lookup.lastpass import LookupModule, LPass, LPassException
|
||||
|
||||
|
||||
MOCK_ENTRIES = [{'username': 'user',
|
||||
'name': 'Mock Entry',
|
||||
'password': 't0pS3cret passphrase entry!',
|
||||
'url': 'https://localhost/login',
|
||||
'notes': 'Test\nnote with multiple lines.\n',
|
||||
'id': '0123456789'}]
|
||||
|
||||
|
||||
class MockLPass(LPass):
|
||||
|
||||
_mock_logged_out = False
|
||||
_mock_disconnected = False
|
||||
|
||||
def _lookup_mock_entry(self, key):
|
||||
for entry in MOCK_ENTRIES:
|
||||
if key == entry['id'] or key == entry['name']:
|
||||
return entry
|
||||
|
||||
def _run(self, args, stdin=None, expected_rc=0):
|
||||
# Mock behavior of lpass executable
|
||||
base_options = ArgumentParser(add_help=False)
|
||||
base_options.add_argument('--color', default="auto", choices=['auto', 'always', 'never'])
|
||||
|
||||
p = ArgumentParser()
|
||||
sp = p.add_subparsers(help='command', dest='subparser_name')
|
||||
|
||||
logout_p = sp.add_parser('logout', parents=[base_options], help='logout')
|
||||
show_p = sp.add_parser('show', parents=[base_options], help='show entry details')
|
||||
|
||||
field_group = show_p.add_mutually_exclusive_group(required=True)
|
||||
for field in MOCK_ENTRIES[0].keys():
|
||||
field_group.add_argument("--{0}".format(field), default=False, action='store_true')
|
||||
field_group.add_argument('--field', default=None)
|
||||
show_p.add_argument('selector', help='Unique Name or ID')
|
||||
|
||||
args = p.parse_args(args)
|
||||
|
||||
def mock_exit(output='', error='', rc=0):
|
||||
if rc != expected_rc:
|
||||
raise LPassException(error)
|
||||
return output, error
|
||||
|
||||
if args.color != 'never':
|
||||
return mock_exit(error='Error: Mock only supports --color=never', rc=1)
|
||||
|
||||
if args.subparser_name == 'logout':
|
||||
if self._mock_logged_out:
|
||||
return mock_exit(error='Error: Not currently logged in', rc=1)
|
||||
|
||||
logged_in_error = 'Are you sure you would like to log out? [Y/n]'
|
||||
if stdin and stdin.lower() == 'n\n':
|
||||
return mock_exit(output='Log out: aborted.', error=logged_in_error, rc=1)
|
||||
elif stdin and stdin.lower() == 'y\n':
|
||||
return mock_exit(output='Log out: complete.', error=logged_in_error, rc=0)
|
||||
else:
|
||||
return mock_exit(error='Error: aborted response', rc=1)
|
||||
|
||||
if args.subparser_name == 'show':
|
||||
if self._mock_logged_out:
|
||||
return mock_exit(error='Error: Could not find decryption key.' +
|
||||
' Perhaps you need to login with `lpass login`.', rc=1)
|
||||
|
||||
if self._mock_disconnected:
|
||||
return mock_exit(error='Error: Couldn\'t resolve host name.', rc=1)
|
||||
|
||||
mock_entry = self._lookup_mock_entry(args.selector)
|
||||
|
||||
if args.field:
|
||||
return mock_exit(output=mock_entry.get(args.field, ''))
|
||||
elif args.password:
|
||||
return mock_exit(output=mock_entry.get('password', ''))
|
||||
elif args.username:
|
||||
return mock_exit(output=mock_entry.get('username', ''))
|
||||
elif args.url:
|
||||
return mock_exit(output=mock_entry.get('url', ''))
|
||||
elif args.name:
|
||||
return mock_exit(output=mock_entry.get('name', ''))
|
||||
elif args.id:
|
||||
return mock_exit(output=mock_entry.get('id', ''))
|
||||
elif args.notes:
|
||||
return mock_exit(output=mock_entry.get('notes', ''))
|
||||
|
||||
raise LPassException('We should never get here')
|
||||
|
||||
|
||||
class DisconnectedMockLPass(MockLPass):
|
||||
|
||||
_mock_disconnected = True
|
||||
|
||||
|
||||
class LoggedOutMockLPass(MockLPass):
|
||||
|
||||
_mock_logged_out = True
|
||||
|
||||
|
||||
class TestLPass(unittest.TestCase):
|
||||
|
||||
def test_lastpass_cli_path(self):
|
||||
lp = MockLPass(path='/dev/null')
|
||||
self.assertEqual('/dev/null', lp.cli_path)
|
||||
|
||||
def test_lastpass_build_args_logout(self):
|
||||
lp = MockLPass()
|
||||
self.assertEqual(['logout', '--color=never'], lp._build_args("logout"))
|
||||
|
||||
def test_lastpass_logged_in_true(self):
|
||||
lp = MockLPass()
|
||||
self.assertTrue(lp.logged_in)
|
||||
|
||||
def test_lastpass_logged_in_false(self):
|
||||
lp = LoggedOutMockLPass()
|
||||
self.assertFalse(lp.logged_in)
|
||||
|
||||
def test_lastpass_show_disconnected(self):
|
||||
lp = DisconnectedMockLPass()
|
||||
|
||||
with self.assertRaises(LPassException):
|
||||
lp.get_field('0123456789', 'username')
|
||||
|
||||
def test_lastpass_show(self):
|
||||
lp = MockLPass()
|
||||
for entry in MOCK_ENTRIES:
|
||||
entry_id = entry.get('id')
|
||||
for k, v in six.iteritems(entry):
|
||||
self.assertEqual(v.strip(), lp.get_field(entry_id, k))
|
||||
|
||||
|
||||
class TestLastpassPlugin(unittest.TestCase):
|
||||
|
||||
@patch('ansible_collections.community.general.plugins.lookup.lastpass.LPass', new=MockLPass)
|
||||
def test_lastpass_plugin_normal(self):
|
||||
lookup_plugin = LookupModule()
|
||||
|
||||
for entry in MOCK_ENTRIES:
|
||||
entry_id = entry.get('id')
|
||||
for k, v in six.iteritems(entry):
|
||||
self.assertEqual(v.strip(),
|
||||
lookup_plugin.run([entry_id], field=k)[0])
|
||||
|
||||
@patch('ansible_collections.community.general.plugins.lookup.lastpass.LPass', LoggedOutMockLPass)
|
||||
def test_lastpass_plugin_logged_out(self):
|
||||
lookup_plugin = LookupModule()
|
||||
|
||||
entry = MOCK_ENTRIES[0]
|
||||
entry_id = entry.get('id')
|
||||
with self.assertRaises(AnsibleError):
|
||||
lookup_plugin.run([entry_id], field='password')
|
||||
|
||||
@patch('ansible_collections.community.general.plugins.lookup.lastpass.LPass', DisconnectedMockLPass)
|
||||
def test_lastpass_plugin_disconnected(self):
|
||||
lookup_plugin = LookupModule()
|
||||
|
||||
entry = MOCK_ENTRIES[0]
|
||||
entry_id = entry.get('id')
|
||||
with self.assertRaises(AnsibleError):
|
||||
lookup_plugin.run([entry_id], field='password')
|
536
tests/unit/plugins/lookup/test_manifold.py
Normal file
536
tests/unit/plugins/lookup/test_manifold.py
Normal file
|
@ -0,0 +1,536 @@
|
|||
# (c) 2018, Arigato Machine Inc.
|
||||
# (c) 2018, Ansible Project
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
from __future__ import (absolute_import, division, print_function)
|
||||
__metaclass__ = type
|
||||
|
||||
from ansible_collections.community.general.tests.unit.compat import unittest
|
||||
from ansible_collections.community.general.tests.unit.compat.mock import patch, call
|
||||
from ansible.errors import AnsibleError
|
||||
from ansible.module_utils.urls import ConnectionError, SSLValidationError
|
||||
from ansible.module_utils.six.moves.urllib.error import HTTPError, URLError
|
||||
from ansible.module_utils import six
|
||||
from ansible_collections.community.general.plugins.lookup.manifold import ManifoldApiClient, LookupModule, ApiError
|
||||
import json
|
||||
|
||||
|
||||
API_FIXTURES = {
|
||||
'https://api.marketplace.manifold.co/v1/resources':
|
||||
[
|
||||
{
|
||||
"body": {
|
||||
"label": "resource-1",
|
||||
"name": "Resource 1"
|
||||
},
|
||||
"id": "rid-1"
|
||||
},
|
||||
{
|
||||
"body": {
|
||||
"label": "resource-2",
|
||||
"name": "Resource 2"
|
||||
},
|
||||
"id": "rid-2"
|
||||
}
|
||||
],
|
||||
'https://api.marketplace.manifold.co/v1/resources?label=resource-1':
|
||||
[
|
||||
{
|
||||
"body": {
|
||||
"label": "resource-1",
|
||||
"name": "Resource 1"
|
||||
},
|
||||
"id": "rid-1"
|
||||
}
|
||||
],
|
||||
'https://api.marketplace.manifold.co/v1/resources?label=resource-2':
|
||||
[
|
||||
{
|
||||
"body": {
|
||||
"label": "resource-2",
|
||||
"name": "Resource 2"
|
||||
},
|
||||
"id": "rid-2"
|
||||
}
|
||||
],
|
||||
'https://api.marketplace.manifold.co/v1/resources?team_id=tid-1':
|
||||
[
|
||||
{
|
||||
"body": {
|
||||
"label": "resource-1",
|
||||
"name": "Resource 1"
|
||||
},
|
||||
"id": "rid-1"
|
||||
}
|
||||
],
|
||||
'https://api.marketplace.manifold.co/v1/resources?project_id=pid-1':
|
||||
[
|
||||
{
|
||||
"body": {
|
||||
"label": "resource-2",
|
||||
"name": "Resource 2"
|
||||
},
|
||||
"id": "rid-2"
|
||||
}
|
||||
],
|
||||
'https://api.marketplace.manifold.co/v1/resources?project_id=pid-2':
|
||||
[
|
||||
{
|
||||
"body": {
|
||||
"label": "resource-1",
|
||||
"name": "Resource 1"
|
||||
},
|
||||
"id": "rid-1"
|
||||
},
|
||||
{
|
||||
"body": {
|
||||
"label": "resource-3",
|
||||
"name": "Resource 3"
|
||||
},
|
||||
"id": "rid-3"
|
||||
}
|
||||
],
|
||||
'https://api.marketplace.manifold.co/v1/resources?team_id=tid-1&project_id=pid-1':
|
||||
[
|
||||
{
|
||||
"body": {
|
||||
"label": "resource-1",
|
||||
"name": "Resource 1"
|
||||
},
|
||||
"id": "rid-1"
|
||||
}
|
||||
],
|
||||
'https://api.marketplace.manifold.co/v1/projects':
|
||||
[
|
||||
{
|
||||
"body": {
|
||||
"label": "project-1",
|
||||
"name": "Project 1",
|
||||
},
|
||||
"id": "pid-1",
|
||||
},
|
||||
{
|
||||
"body": {
|
||||
"label": "project-2",
|
||||
"name": "Project 2",
|
||||
},
|
||||
"id": "pid-2",
|
||||
}
|
||||
],
|
||||
'https://api.marketplace.manifold.co/v1/projects?label=project-2':
|
||||
[
|
||||
{
|
||||
"body": {
|
||||
"label": "project-2",
|
||||
"name": "Project 2",
|
||||
},
|
||||
"id": "pid-2",
|
||||
}
|
||||
],
|
||||
'https://api.marketplace.manifold.co/v1/credentials?resource_id=rid-1':
|
||||
[
|
||||
{
|
||||
"body": {
|
||||
"resource_id": "rid-1",
|
||||
"values": {
|
||||
"RESOURCE_TOKEN_1": "token-1",
|
||||
"RESOURCE_TOKEN_2": "token-2"
|
||||
}
|
||||
},
|
||||
"id": "cid-1",
|
||||
}
|
||||
],
|
||||
'https://api.marketplace.manifold.co/v1/credentials?resource_id=rid-2':
|
||||
[
|
||||
{
|
||||
"body": {
|
||||
"resource_id": "rid-2",
|
||||
"values": {
|
||||
"RESOURCE_TOKEN_3": "token-3",
|
||||
"RESOURCE_TOKEN_4": "token-4"
|
||||
}
|
||||
},
|
||||
"id": "cid-2",
|
||||
}
|
||||
],
|
||||
'https://api.marketplace.manifold.co/v1/credentials?resource_id=rid-3':
|
||||
[
|
||||
{
|
||||
"body": {
|
||||
"resource_id": "rid-3",
|
||||
"values": {
|
||||
"RESOURCE_TOKEN_1": "token-5",
|
||||
"RESOURCE_TOKEN_2": "token-6"
|
||||
}
|
||||
},
|
||||
"id": "cid-3",
|
||||
}
|
||||
],
|
||||
'https://api.identity.manifold.co/v1/teams':
|
||||
[
|
||||
{
|
||||
"id": "tid-1",
|
||||
"body": {
|
||||
"name": "Team 1",
|
||||
"label": "team-1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "tid-2",
|
||||
"body": {
|
||||
"name": "Team 2",
|
||||
"label": "team-2"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
def mock_fixture(open_url_mock, fixture=None, data=None, headers=None):
|
||||
if not headers:
|
||||
headers = {}
|
||||
if fixture:
|
||||
data = json.dumps(API_FIXTURES[fixture])
|
||||
if 'content-type' not in headers:
|
||||
headers['content-type'] = 'application/json'
|
||||
|
||||
open_url_mock.return_value.read.return_value = data
|
||||
open_url_mock.return_value.headers = headers
|
||||
|
||||
|
||||
class TestManifoldApiClient(unittest.TestCase):
|
||||
@patch('ansible_collections.community.general.plugins.lookup.manifold.open_url')
|
||||
def test_request_sends_default_headers(self, open_url_mock):
|
||||
mock_fixture(open_url_mock, data='hello')
|
||||
client = ManifoldApiClient('token-123')
|
||||
client.request('test', 'endpoint')
|
||||
open_url_mock.assert_called_with('https://api.test.manifold.co/v1/endpoint',
|
||||
headers={'Accept': '*/*', 'Authorization': 'Bearer token-123'},
|
||||
http_agent='python-manifold-ansible-1.0.0')
|
||||
|
||||
@patch('ansible_collections.community.general.plugins.lookup.manifold.open_url')
|
||||
def test_request_decodes_json(self, open_url_mock):
|
||||
mock_fixture(open_url_mock, fixture='https://api.marketplace.manifold.co/v1/resources')
|
||||
client = ManifoldApiClient('token-123')
|
||||
self.assertIsInstance(client.request('marketplace', 'resources'), list)
|
||||
|
||||
@patch('ansible_collections.community.general.plugins.lookup.manifold.open_url')
|
||||
def test_request_streams_text(self, open_url_mock):
|
||||
mock_fixture(open_url_mock, data='hello', headers={'content-type': "text/plain"})
|
||||
client = ManifoldApiClient('token-123')
|
||||
self.assertEqual('hello', client.request('test', 'endpoint'))
|
||||
|
||||
@patch('ansible_collections.community.general.plugins.lookup.manifold.open_url')
|
||||
def test_request_processes_parameterized_headers(self, open_url_mock):
|
||||
mock_fixture(open_url_mock, data='hello')
|
||||
client = ManifoldApiClient('token-123')
|
||||
client.request('test', 'endpoint', headers={'X-HEADER': 'MANIFOLD'})
|
||||
open_url_mock.assert_called_with('https://api.test.manifold.co/v1/endpoint',
|
||||
headers={'Accept': '*/*', 'Authorization': 'Bearer token-123',
|
||||
'X-HEADER': 'MANIFOLD'},
|
||||
http_agent='python-manifold-ansible-1.0.0')
|
||||
|
||||
@patch('ansible_collections.community.general.plugins.lookup.manifold.open_url')
|
||||
def test_request_passes_arbitrary_parameters(self, open_url_mock):
|
||||
mock_fixture(open_url_mock, data='hello')
|
||||
client = ManifoldApiClient('token-123')
|
||||
client.request('test', 'endpoint', use_proxy=False, timeout=5)
|
||||
open_url_mock.assert_called_with('https://api.test.manifold.co/v1/endpoint',
|
||||
headers={'Accept': '*/*', 'Authorization': 'Bearer token-123'},
|
||||
http_agent='python-manifold-ansible-1.0.0',
|
||||
use_proxy=False, timeout=5)
|
||||
|
||||
@patch('ansible_collections.community.general.plugins.lookup.manifold.open_url')
|
||||
def test_request_raises_on_incorrect_json(self, open_url_mock):
|
||||
mock_fixture(open_url_mock, data='noJson', headers={'content-type': "application/json"})
|
||||
client = ManifoldApiClient('token-123')
|
||||
with self.assertRaises(ApiError) as context:
|
||||
client.request('test', 'endpoint')
|
||||
self.assertEqual('JSON response can\'t be parsed while requesting https://api.test.manifold.co/v1/endpoint:\n'
|
||||
'noJson',
|
||||
str(context.exception))
|
||||
|
||||
@patch('ansible_collections.community.general.plugins.lookup.manifold.open_url')
|
||||
def test_request_raises_on_status_500(self, open_url_mock):
|
||||
open_url_mock.side_effect = HTTPError('https://api.test.manifold.co/v1/endpoint',
|
||||
500, 'Server error', {}, six.StringIO('ERROR'))
|
||||
client = ManifoldApiClient('token-123')
|
||||
with self.assertRaises(ApiError) as context:
|
||||
client.request('test', 'endpoint')
|
||||
self.assertEqual('Server returned: HTTP Error 500: Server error while requesting '
|
||||
'https://api.test.manifold.co/v1/endpoint:\nERROR',
|
||||
str(context.exception))
|
||||
|
||||
@patch('ansible_collections.community.general.plugins.lookup.manifold.open_url')
|
||||
def test_request_raises_on_bad_url(self, open_url_mock):
|
||||
open_url_mock.side_effect = URLError('URL is invalid')
|
||||
client = ManifoldApiClient('token-123')
|
||||
with self.assertRaises(ApiError) as context:
|
||||
client.request('test', 'endpoint')
|
||||
self.assertEqual('Failed lookup url for https://api.test.manifold.co/v1/endpoint : <url'
|
||||
'open error URL is invalid>',
|
||||
str(context.exception))
|
||||
|
||||
@patch('ansible_collections.community.general.plugins.lookup.manifold.open_url')
|
||||
def test_request_raises_on_ssl_error(self, open_url_mock):
|
||||
open_url_mock.side_effect = SSLValidationError('SSL Error')
|
||||
client = ManifoldApiClient('token-123')
|
||||
with self.assertRaises(ApiError) as context:
|
||||
client.request('test', 'endpoint')
|
||||
self.assertEqual('Error validating the server\'s certificate for https://api.test.manifold.co/v1/endpoint: '
|
||||
'SSL Error',
|
||||
str(context.exception))
|
||||
|
||||
@patch('ansible_collections.community.general.plugins.lookup.manifold.open_url')
|
||||
def test_request_raises_on_connection_error(self, open_url_mock):
|
||||
open_url_mock.side_effect = ConnectionError('Unknown connection error')
|
||||
client = ManifoldApiClient('token-123')
|
||||
with self.assertRaises(ApiError) as context:
|
||||
client.request('test', 'endpoint')
|
||||
self.assertEqual('Error connecting to https://api.test.manifold.co/v1/endpoint: Unknown connection error',
|
||||
str(context.exception))
|
||||
|
||||
@patch('ansible_collections.community.general.plugins.lookup.manifold.open_url')
|
||||
def test_get_resources_get_all(self, open_url_mock):
|
||||
url = 'https://api.marketplace.manifold.co/v1/resources'
|
||||
mock_fixture(open_url_mock, fixture=url)
|
||||
client = ManifoldApiClient('token-123')
|
||||
self.assertListEqual(API_FIXTURES[url], client.get_resources())
|
||||
open_url_mock.assert_called_with(url,
|
||||
headers={'Accept': '*/*', 'Authorization': 'Bearer token-123'},
|
||||
http_agent='python-manifold-ansible-1.0.0')
|
||||
|
||||
@patch('ansible_collections.community.general.plugins.lookup.manifold.open_url')
|
||||
def test_get_resources_filter_label(self, open_url_mock):
|
||||
url = 'https://api.marketplace.manifold.co/v1/resources?label=resource-1'
|
||||
mock_fixture(open_url_mock, fixture=url)
|
||||
client = ManifoldApiClient('token-123')
|
||||
self.assertListEqual(API_FIXTURES[url], client.get_resources(label='resource-1'))
|
||||
open_url_mock.assert_called_with(url,
|
||||
headers={'Accept': '*/*', 'Authorization': 'Bearer token-123'},
|
||||
http_agent='python-manifold-ansible-1.0.0')
|
||||
|
||||
@patch('ansible_collections.community.general.plugins.lookup.manifold.open_url')
|
||||
def test_get_resources_filter_team_and_project(self, open_url_mock):
|
||||
url = 'https://api.marketplace.manifold.co/v1/resources?team_id=tid-1&project_id=pid-1'
|
||||
mock_fixture(open_url_mock, fixture=url)
|
||||
client = ManifoldApiClient('token-123')
|
||||
self.assertListEqual(API_FIXTURES[url], client.get_resources(team_id='tid-1', project_id='pid-1'))
|
||||
args, kwargs = open_url_mock.call_args
|
||||
url_called = args[0]
|
||||
# Dict order is not guaranteed, so an url may have querystring parameters order randomized
|
||||
self.assertIn('team_id=tid-1', url_called)
|
||||
self.assertIn('project_id=pid-1', url_called)
|
||||
|
||||
@patch('ansible_collections.community.general.plugins.lookup.manifold.open_url')
|
||||
def test_get_teams_get_all(self, open_url_mock):
|
||||
url = 'https://api.identity.manifold.co/v1/teams'
|
||||
mock_fixture(open_url_mock, fixture=url)
|
||||
client = ManifoldApiClient('token-123')
|
||||
self.assertListEqual(API_FIXTURES[url], client.get_teams())
|
||||
open_url_mock.assert_called_with(url,
|
||||
headers={'Accept': '*/*', 'Authorization': 'Bearer token-123'},
|
||||
http_agent='python-manifold-ansible-1.0.0')
|
||||
|
||||
@patch('ansible_collections.community.general.plugins.lookup.manifold.open_url')
|
||||
def test_get_teams_filter_label(self, open_url_mock):
|
||||
url = 'https://api.identity.manifold.co/v1/teams'
|
||||
mock_fixture(open_url_mock, fixture=url)
|
||||
client = ManifoldApiClient('token-123')
|
||||
self.assertListEqual(API_FIXTURES[url][1:2], client.get_teams(label='team-2'))
|
||||
open_url_mock.assert_called_with(url,
|
||||
headers={'Accept': '*/*', 'Authorization': 'Bearer token-123'},
|
||||
http_agent='python-manifold-ansible-1.0.0')
|
||||
|
||||
@patch('ansible_collections.community.general.plugins.lookup.manifold.open_url')
|
||||
def test_get_projects_get_all(self, open_url_mock):
|
||||
url = 'https://api.marketplace.manifold.co/v1/projects'
|
||||
mock_fixture(open_url_mock, fixture=url)
|
||||
client = ManifoldApiClient('token-123')
|
||||
self.assertListEqual(API_FIXTURES[url], client.get_projects())
|
||||
open_url_mock.assert_called_with(url,
|
||||
headers={'Accept': '*/*', 'Authorization': 'Bearer token-123'},
|
||||
http_agent='python-manifold-ansible-1.0.0')
|
||||
|
||||
@patch('ansible_collections.community.general.plugins.lookup.manifold.open_url')
|
||||
def test_get_projects_filter_label(self, open_url_mock):
|
||||
url = 'https://api.marketplace.manifold.co/v1/projects?label=project-2'
|
||||
mock_fixture(open_url_mock, fixture=url)
|
||||
client = ManifoldApiClient('token-123')
|
||||
self.assertListEqual(API_FIXTURES[url], client.get_projects(label='project-2'))
|
||||
open_url_mock.assert_called_with(url,
|
||||
headers={'Accept': '*/*', 'Authorization': 'Bearer token-123'},
|
||||
http_agent='python-manifold-ansible-1.0.0')
|
||||
|
||||
@patch('ansible_collections.community.general.plugins.lookup.manifold.open_url')
|
||||
def test_get_credentials(self, open_url_mock):
|
||||
url = 'https://api.marketplace.manifold.co/v1/credentials?resource_id=rid-1'
|
||||
mock_fixture(open_url_mock, fixture=url)
|
||||
client = ManifoldApiClient('token-123')
|
||||
self.assertListEqual(API_FIXTURES[url], client.get_credentials(resource_id='rid-1'))
|
||||
open_url_mock.assert_called_with(url,
|
||||
headers={'Accept': '*/*', 'Authorization': 'Bearer token-123'},
|
||||
http_agent='python-manifold-ansible-1.0.0')
|
||||
|
||||
|
||||
class TestLookupModule(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.lookup = LookupModule()
|
||||
self.lookup._load_name = "manifold"
|
||||
|
||||
@patch('ansible_collections.community.general.plugins.lookup.manifold.ManifoldApiClient')
|
||||
def test_get_all(self, client_mock):
|
||||
expected_result = [{'RESOURCE_TOKEN_1': 'token-1',
|
||||
'RESOURCE_TOKEN_2': 'token-2',
|
||||
'RESOURCE_TOKEN_3': 'token-3',
|
||||
'RESOURCE_TOKEN_4': 'token-4'
|
||||
}]
|
||||
client_mock.return_value.get_resources.return_value = API_FIXTURES['https://api.marketplace.manifold.co/v1/resources']
|
||||
client_mock.return_value.get_credentials.side_effect = lambda x: API_FIXTURES['https://api.marketplace.manifold.co/v1/'
|
||||
'credentials?resource_id={0}'.format(x)]
|
||||
self.assertListEqual(expected_result, self.lookup.run([], api_token='token-123'))
|
||||
client_mock.assert_called_with('token-123')
|
||||
client_mock.return_value.get_resources.assert_called_with(team_id=None, project_id=None)
|
||||
|
||||
@patch('ansible_collections.community.general.plugins.lookup.manifold.ManifoldApiClient')
|
||||
def test_get_one_resource(self, client_mock):
|
||||
expected_result = [{'RESOURCE_TOKEN_3': 'token-3',
|
||||
'RESOURCE_TOKEN_4': 'token-4'
|
||||
}]
|
||||
client_mock.return_value.get_resources.return_value = API_FIXTURES['https://api.marketplace.manifold.co/v1/resources?label=resource-2']
|
||||
client_mock.return_value.get_credentials.side_effect = lambda x: API_FIXTURES['https://api.marketplace.manifold.co/v1/'
|
||||
'credentials?resource_id={0}'.format(x)]
|
||||
self.assertListEqual(expected_result, self.lookup.run(['resource-2'], api_token='token-123'))
|
||||
client_mock.return_value.get_resources.assert_called_with(team_id=None, project_id=None, label='resource-2')
|
||||
|
||||
@patch('ansible_collections.community.general.plugins.lookup.manifold.ManifoldApiClient')
|
||||
def test_get_two_resources(self, client_mock):
|
||||
expected_result = [{'RESOURCE_TOKEN_1': 'token-1',
|
||||
'RESOURCE_TOKEN_2': 'token-2',
|
||||
'RESOURCE_TOKEN_3': 'token-3',
|
||||
'RESOURCE_TOKEN_4': 'token-4'
|
||||
}]
|
||||
client_mock.return_value.get_resources.return_value = API_FIXTURES['https://api.marketplace.manifold.co/v1/resources']
|
||||
client_mock.return_value.get_credentials.side_effect = lambda x: API_FIXTURES['https://api.marketplace.manifold.co/v1/'
|
||||
'credentials?resource_id={0}'.format(x)]
|
||||
self.assertListEqual(expected_result, self.lookup.run(['resource-1', 'resource-2'], api_token='token-123'))
|
||||
client_mock.assert_called_with('token-123')
|
||||
client_mock.return_value.get_resources.assert_called_with(team_id=None, project_id=None)
|
||||
|
||||
@patch('ansible_collections.community.general.plugins.lookup.manifold.display')
|
||||
@patch('ansible_collections.community.general.plugins.lookup.manifold.ManifoldApiClient')
|
||||
def test_get_resources_with_same_credential_names(self, client_mock, display_mock):
|
||||
expected_result = [{'RESOURCE_TOKEN_1': 'token-5',
|
||||
'RESOURCE_TOKEN_2': 'token-6'
|
||||
}]
|
||||
client_mock.return_value.get_resources.return_value = API_FIXTURES['https://api.marketplace.manifold.co/v1/resources?project_id=pid-2']
|
||||
client_mock.return_value.get_projects.return_value = API_FIXTURES['https://api.marketplace.manifold.co/v1/projects?label=project-2']
|
||||
client_mock.return_value.get_credentials.side_effect = lambda x: API_FIXTURES['https://api.marketplace.manifold.co/v1/'
|
||||
'credentials?resource_id={0}'.format(x)]
|
||||
self.assertListEqual(expected_result, self.lookup.run([], api_token='token-123', project='project-2'))
|
||||
client_mock.assert_called_with('token-123')
|
||||
display_mock.warning.assert_has_calls([
|
||||
call("'RESOURCE_TOKEN_1' with label 'resource-1' was replaced by resource data with label 'resource-3'"),
|
||||
call("'RESOURCE_TOKEN_2' with label 'resource-1' was replaced by resource data with label 'resource-3'")],
|
||||
any_order=True
|
||||
)
|
||||
client_mock.return_value.get_resources.assert_called_with(team_id=None, project_id='pid-2')
|
||||
|
||||
@patch('ansible_collections.community.general.plugins.lookup.manifold.ManifoldApiClient')
|
||||
def test_filter_by_team(self, client_mock):
|
||||
expected_result = [{'RESOURCE_TOKEN_1': 'token-1',
|
||||
'RESOURCE_TOKEN_2': 'token-2'
|
||||
}]
|
||||
client_mock.return_value.get_resources.return_value = API_FIXTURES['https://api.marketplace.manifold.co/v1/resources?team_id=tid-1']
|
||||
client_mock.return_value.get_teams.return_value = API_FIXTURES['https://api.identity.manifold.co/v1/teams'][0:1]
|
||||
client_mock.return_value.get_credentials.side_effect = lambda x: API_FIXTURES['https://api.marketplace.manifold.co/v1/'
|
||||
'credentials?resource_id={0}'.format(x)]
|
||||
self.assertListEqual(expected_result, self.lookup.run([], api_token='token-123', team='team-1'))
|
||||
client_mock.assert_called_with('token-123')
|
||||
client_mock.return_value.get_resources.assert_called_with(team_id='tid-1', project_id=None)
|
||||
|
||||
@patch('ansible_collections.community.general.plugins.lookup.manifold.ManifoldApiClient')
|
||||
def test_filter_by_project(self, client_mock):
|
||||
expected_result = [{'RESOURCE_TOKEN_3': 'token-3',
|
||||
'RESOURCE_TOKEN_4': 'token-4'
|
||||
}]
|
||||
client_mock.return_value.get_resources.return_value = API_FIXTURES['https://api.marketplace.manifold.co/v1/resources?project_id=pid-1']
|
||||
client_mock.return_value.get_projects.return_value = API_FIXTURES['https://api.marketplace.manifold.co/v1/projects'][0:1]
|
||||
client_mock.return_value.get_credentials.side_effect = lambda x: API_FIXTURES['https://api.marketplace.manifold.co/v1/'
|
||||
'credentials?resource_id={0}'.format(x)]
|
||||
self.assertListEqual(expected_result, self.lookup.run([], api_token='token-123', project='project-1'))
|
||||
client_mock.assert_called_with('token-123')
|
||||
client_mock.return_value.get_resources.assert_called_with(team_id=None, project_id='pid-1')
|
||||
|
||||
@patch('ansible_collections.community.general.plugins.lookup.manifold.ManifoldApiClient')
|
||||
def test_filter_by_team_and_project(self, client_mock):
|
||||
expected_result = [{'RESOURCE_TOKEN_1': 'token-1',
|
||||
'RESOURCE_TOKEN_2': 'token-2'
|
||||
}]
|
||||
client_mock.return_value.get_resources.return_value = API_FIXTURES['https://api.marketplace.manifold.co/v1/resources?team_id=tid-1&project_id=pid-1']
|
||||
client_mock.return_value.get_teams.return_value = API_FIXTURES['https://api.identity.manifold.co/v1/teams'][0:1]
|
||||
client_mock.return_value.get_projects.return_value = API_FIXTURES['https://api.marketplace.manifold.co/v1/projects'][0:1]
|
||||
client_mock.return_value.get_credentials.side_effect = lambda x: API_FIXTURES['https://api.marketplace.manifold.co/v1/'
|
||||
'credentials?resource_id={0}'.format(x)]
|
||||
self.assertListEqual(expected_result, self.lookup.run([], api_token='token-123', project='project-1'))
|
||||
client_mock.assert_called_with('token-123')
|
||||
client_mock.return_value.get_resources.assert_called_with(team_id=None, project_id='pid-1')
|
||||
|
||||
@patch('ansible_collections.community.general.plugins.lookup.manifold.ManifoldApiClient')
|
||||
def test_raise_team_doesnt_exist(self, client_mock):
|
||||
client_mock.return_value.get_teams.return_value = []
|
||||
with self.assertRaises(AnsibleError) as context:
|
||||
self.lookup.run([], api_token='token-123', team='no-team')
|
||||
self.assertEqual("Team 'no-team' does not exist",
|
||||
str(context.exception))
|
||||
|
||||
@patch('ansible_collections.community.general.plugins.lookup.manifold.ManifoldApiClient')
|
||||
def test_raise_project_doesnt_exist(self, client_mock):
|
||||
client_mock.return_value.get_projects.return_value = []
|
||||
with self.assertRaises(AnsibleError) as context:
|
||||
self.lookup.run([], api_token='token-123', project='no-project')
|
||||
self.assertEqual("Project 'no-project' does not exist",
|
||||
str(context.exception))
|
||||
|
||||
@patch('ansible_collections.community.general.plugins.lookup.manifold.ManifoldApiClient')
|
||||
def test_raise_resource_doesnt_exist(self, client_mock):
|
||||
client_mock.return_value.get_resources.return_value = API_FIXTURES['https://api.marketplace.manifold.co/v1/resources']
|
||||
with self.assertRaises(AnsibleError) as context:
|
||||
self.lookup.run(['resource-1', 'no-resource-1', 'no-resource-2'], api_token='token-123')
|
||||
self.assertEqual("Resource(s) no-resource-1, no-resource-2 do not exist",
|
||||
str(context.exception))
|
||||
|
||||
@patch('ansible_collections.community.general.plugins.lookup.manifold.ManifoldApiClient')
|
||||
def test_catch_api_error(self, client_mock):
|
||||
client_mock.side_effect = ApiError('Generic error')
|
||||
with self.assertRaises(AnsibleError) as context:
|
||||
self.lookup.run([], api_token='token-123')
|
||||
self.assertEqual("API Error: Generic error",
|
||||
str(context.exception))
|
||||
|
||||
@patch('ansible_collections.community.general.plugins.lookup.manifold.ManifoldApiClient')
|
||||
def test_catch_unhandled_exception(self, client_mock):
|
||||
client_mock.side_effect = Exception('Unknown error')
|
||||
with self.assertRaises(AnsibleError) as context:
|
||||
self.lookup.run([], api_token='token-123')
|
||||
self.assertTrue('Exception: Unknown error' in str(context.exception))
|
||||
|
||||
@patch('ansible_collections.community.general.plugins.lookup.manifold.os.getenv')
|
||||
@patch('ansible_collections.community.general.plugins.lookup.manifold.ManifoldApiClient')
|
||||
def test_falls_back_to_env_var(self, client_mock, getenv_mock):
|
||||
getenv_mock.return_value = 'token-321'
|
||||
client_mock.return_value.get_resources.return_value = []
|
||||
client_mock.return_value.get_credentials.return_value = []
|
||||
self.lookup.run([])
|
||||
getenv_mock.assert_called_with('MANIFOLD_API_TOKEN')
|
||||
client_mock.assert_called_with('token-321')
|
||||
|
||||
@patch('ansible_collections.community.general.plugins.lookup.manifold.os.getenv')
|
||||
@patch('ansible_collections.community.general.plugins.lookup.manifold.ManifoldApiClient')
|
||||
def test_falls_raises_on_no_token(self, client_mock, getenv_mock):
|
||||
getenv_mock.return_value = None
|
||||
client_mock.return_value.get_resources.return_value = []
|
||||
client_mock.return_value.get_credentials.return_value = []
|
||||
with self.assertRaises(AnsibleError) as context:
|
||||
self.lookup.run([])
|
||||
self.assertEqual('API token is required. Please set api_token parameter or MANIFOLD_API_TOKEN env var',
|
||||
str(context.exception))
|
321
tests/unit/plugins/lookup/test_onepassword.py
Normal file
321
tests/unit/plugins/lookup/test_onepassword.py
Normal file
|
@ -0,0 +1,321 @@
|
|||
# (c) 2018, Scott Buchanan <sbuchanan@ri.pn>
|
||||
# (c) 2016, Andrew Zenk <azenk@umn.edu> (test_lastpass.py used as starting point)
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
from __future__ import (absolute_import, division, print_function)
|
||||
__metaclass__ = type
|
||||
|
||||
import json
|
||||
import datetime
|
||||
|
||||
try:
|
||||
from urllib.parse import urlparse
|
||||
except ImportError:
|
||||
from urlparse import urlparse
|
||||
|
||||
from argparse import ArgumentParser
|
||||
|
||||
|
||||
from ansible_collections.community.general.tests.unit.compat import unittest
|
||||
from ansible_collections.community.general.tests.unit.compat.mock import patch
|
||||
from ansible.errors import AnsibleError
|
||||
from ansible_collections.community.general.plugins.lookup.onepassword import OnePass, LookupModule
|
||||
from ansible_collections.community.general.plugins.lookup.onepassword_raw import LookupModule as OnePasswordRawLookup
|
||||
|
||||
|
||||
# Intentionally excludes metadata leaf nodes that would exist in real output if not relevant.
|
||||
MOCK_ENTRIES = [
|
||||
{
|
||||
'vault_name': 'Acme "Quot\'d" Servers',
|
||||
'queries': [
|
||||
'0123456789',
|
||||
'Mock "Quot\'d" Server'
|
||||
],
|
||||
'output': {
|
||||
'uuid': '0123456789',
|
||||
'vaultUuid': '2468',
|
||||
'overview': {
|
||||
'title': 'Mock "Quot\'d" Server'
|
||||
},
|
||||
'details': {
|
||||
'sections': [{
|
||||
'title': '',
|
||||
'fields': [
|
||||
{'t': 'username', 'v': 'jamesbond'},
|
||||
{'t': 'password', 'v': 't0pS3cret'},
|
||||
{'t': 'notes', 'v': 'Test note with\nmultiple lines and trailing space.\n\n'},
|
||||
{'t': 'tricksy "quot\'d" field\\', 'v': '"quot\'d" value'}
|
||||
]
|
||||
}]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
'vault_name': 'Acme Logins',
|
||||
'queries': [
|
||||
'9876543210',
|
||||
'Mock Website',
|
||||
'acme.com'
|
||||
],
|
||||
'output': {
|
||||
'uuid': '9876543210',
|
||||
'vaultUuid': '1357',
|
||||
'overview': {
|
||||
'title': 'Mock Website',
|
||||
'URLs': [
|
||||
{'l': 'website', 'u': 'https://acme.com/login'}
|
||||
]
|
||||
},
|
||||
'details': {
|
||||
'sections': [{
|
||||
'title': '',
|
||||
'fields': [
|
||||
{'t': 'password', 'v': 't0pS3cret'}
|
||||
]
|
||||
}]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
'vault_name': 'Acme Logins',
|
||||
'queries': [
|
||||
'864201357'
|
||||
],
|
||||
'output': {
|
||||
'uuid': '864201357',
|
||||
'vaultUuid': '1357',
|
||||
'overview': {
|
||||
'title': 'Mock Something'
|
||||
},
|
||||
'details': {
|
||||
'fields': [
|
||||
{
|
||||
'value': 'jbond@mi6.gov.uk',
|
||||
'name': 'emailAddress'
|
||||
},
|
||||
{
|
||||
'name': 'password',
|
||||
'value': 'vauxhall'
|
||||
},
|
||||
{},
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
def get_mock_query_generator(require_field=None):
|
||||
def _process_field(field, section_title=None):
|
||||
field_name = field.get('name', field.get('t', ''))
|
||||
field_value = field.get('value', field.get('v', ''))
|
||||
|
||||
if require_field is None or field_name == require_field:
|
||||
return entry, query, section_title, field_name, field_value
|
||||
|
||||
for entry in MOCK_ENTRIES:
|
||||
for query in entry['queries']:
|
||||
for field in entry['output']['details'].get('fields', []):
|
||||
fixture = _process_field(field)
|
||||
if fixture:
|
||||
yield fixture
|
||||
for section in entry['output']['details'].get('sections', []):
|
||||
for field in section['fields']:
|
||||
fixture = _process_field(field, section['title'])
|
||||
if fixture:
|
||||
yield fixture
|
||||
|
||||
|
||||
def get_one_mock_query(require_field=None):
|
||||
generator = get_mock_query_generator(require_field)
|
||||
return next(generator)
|
||||
|
||||
|
||||
class MockOnePass(OnePass):
|
||||
|
||||
_mock_logged_out = False
|
||||
_mock_timed_out = False
|
||||
|
||||
def _lookup_mock_entry(self, key, vault=None):
|
||||
for entry in MOCK_ENTRIES:
|
||||
if vault is not None and vault.lower() != entry['vault_name'].lower() and vault.lower() != entry['output']['vaultUuid'].lower():
|
||||
continue
|
||||
|
||||
match_fields = [
|
||||
entry['output']['uuid'],
|
||||
entry['output']['overview']['title']
|
||||
]
|
||||
|
||||
# Note that exactly how 1Password matches on domains in non-trivial cases is neither documented
|
||||
# nor obvious, so this may not precisely match the real behavior.
|
||||
urls = entry['output']['overview'].get('URLs')
|
||||
if urls is not None:
|
||||
match_fields += [urlparse(url['u']).netloc for url in urls]
|
||||
|
||||
if key in match_fields:
|
||||
return entry['output']
|
||||
|
||||
def _run(self, args, expected_rc=0, command_input=None, ignore_errors=False):
|
||||
parser = ArgumentParser()
|
||||
|
||||
command_parser = parser.add_subparsers(dest='command')
|
||||
|
||||
get_parser = command_parser.add_parser('get')
|
||||
get_options = ArgumentParser(add_help=False)
|
||||
get_options.add_argument('--vault')
|
||||
get_type_parser = get_parser.add_subparsers(dest='object_type')
|
||||
get_type_parser.add_parser('account', parents=[get_options])
|
||||
get_item_parser = get_type_parser.add_parser('item', parents=[get_options])
|
||||
get_item_parser.add_argument('item_id')
|
||||
|
||||
args = parser.parse_args(args)
|
||||
|
||||
def mock_exit(output='', error='', rc=0):
|
||||
if rc != expected_rc:
|
||||
raise AnsibleError(error)
|
||||
if error != '':
|
||||
now = datetime.date.today()
|
||||
error = '[LOG] {0} (ERROR) {1}'.format(now.strftime('%Y/%m/%d %H:$M:$S'), error)
|
||||
return rc, output, error
|
||||
|
||||
if args.command == 'get':
|
||||
if self._mock_logged_out:
|
||||
return mock_exit(error='You are not currently signed in. Please run `op signin --help` for instructions', rc=1)
|
||||
|
||||
if self._mock_timed_out:
|
||||
return mock_exit(error='401: Authentication required.', rc=1)
|
||||
|
||||
if args.object_type == 'item':
|
||||
mock_entry = self._lookup_mock_entry(args.item_id, args.vault)
|
||||
|
||||
if mock_entry is None:
|
||||
return mock_exit(error='Item {0} not found'.format(args.item_id))
|
||||
|
||||
return mock_exit(output=json.dumps(mock_entry))
|
||||
|
||||
if args.object_type == 'account':
|
||||
# Since we don't actually ever use this output, don't bother mocking output.
|
||||
return mock_exit()
|
||||
|
||||
raise AnsibleError('Unsupported command string passed to OnePass mock: {0}'.format(args))
|
||||
|
||||
|
||||
class LoggedOutMockOnePass(MockOnePass):
|
||||
|
||||
_mock_logged_out = True
|
||||
|
||||
|
||||
class TimedOutMockOnePass(MockOnePass):
|
||||
|
||||
_mock_timed_out = True
|
||||
|
||||
|
||||
class TestOnePass(unittest.TestCase):
|
||||
|
||||
def test_onepassword_cli_path(self):
|
||||
op = MockOnePass(path='/dev/null')
|
||||
self.assertEqual('/dev/null', op.cli_path)
|
||||
|
||||
def test_onepassword_logged_in(self):
|
||||
op = MockOnePass()
|
||||
try:
|
||||
op.assert_logged_in()
|
||||
except Exception:
|
||||
self.fail()
|
||||
|
||||
def test_onepassword_logged_out(self):
|
||||
op = LoggedOutMockOnePass()
|
||||
with self.assertRaises(AnsibleError):
|
||||
op.assert_logged_in()
|
||||
|
||||
def test_onepassword_timed_out(self):
|
||||
op = TimedOutMockOnePass()
|
||||
with self.assertRaises(AnsibleError):
|
||||
op.assert_logged_in()
|
||||
|
||||
def test_onepassword_get(self):
|
||||
op = MockOnePass()
|
||||
op.logged_in = True
|
||||
query_generator = get_mock_query_generator()
|
||||
for dummy, query, dummy, field_name, field_value in query_generator:
|
||||
self.assertEqual(field_value, op.get_field(query, field_name))
|
||||
|
||||
def test_onepassword_get_raw(self):
|
||||
op = MockOnePass()
|
||||
op.logged_in = True
|
||||
for entry in MOCK_ENTRIES:
|
||||
for query in entry['queries']:
|
||||
self.assertEqual(json.dumps(entry['output']), op.get_raw(query))
|
||||
|
||||
def test_onepassword_get_not_found(self):
|
||||
op = MockOnePass()
|
||||
op.logged_in = True
|
||||
self.assertEqual('', op.get_field('a fake query', 'a fake field'))
|
||||
|
||||
def test_onepassword_get_with_section(self):
|
||||
op = MockOnePass()
|
||||
op.logged_in = True
|
||||
dummy, query, section_title, field_name, field_value = get_one_mock_query()
|
||||
self.assertEqual(field_value, op.get_field(query, field_name, section=section_title))
|
||||
|
||||
def test_onepassword_get_with_vault(self):
|
||||
op = MockOnePass()
|
||||
op.logged_in = True
|
||||
entry, query, dummy, field_name, field_value = get_one_mock_query()
|
||||
for vault_query in [entry['vault_name'], entry['output']['vaultUuid']]:
|
||||
self.assertEqual(field_value, op.get_field(query, field_name, vault=vault_query))
|
||||
|
||||
def test_onepassword_get_with_wrong_vault(self):
|
||||
op = MockOnePass()
|
||||
op.logged_in = True
|
||||
dummy, query, dummy, field_name, dummy = get_one_mock_query()
|
||||
self.assertEqual('', op.get_field(query, field_name, vault='a fake vault'))
|
||||
|
||||
def test_onepassword_get_diff_case(self):
|
||||
op = MockOnePass()
|
||||
op.logged_in = True
|
||||
entry, query, section_title, field_name, field_value = get_one_mock_query()
|
||||
self.assertEqual(
|
||||
field_value,
|
||||
op.get_field(
|
||||
query,
|
||||
field_name.upper(),
|
||||
vault=entry['vault_name'].upper(),
|
||||
section=section_title.upper()
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@patch('ansible_collections.community.general.plugins.lookup.onepassword.OnePass', MockOnePass)
|
||||
class TestLookupModule(unittest.TestCase):
|
||||
|
||||
def test_onepassword_plugin_multiple(self):
|
||||
lookup_plugin = LookupModule()
|
||||
|
||||
entry = MOCK_ENTRIES[0]
|
||||
field = entry['output']['details']['sections'][0]['fields'][0]
|
||||
|
||||
self.assertEqual(
|
||||
[field['v']] * len(entry['queries']),
|
||||
lookup_plugin.run(entry['queries'], field=field['t'])
|
||||
)
|
||||
|
||||
def test_onepassword_plugin_default_field(self):
|
||||
lookup_plugin = LookupModule()
|
||||
|
||||
dummy, query, dummy, dummy, field_value = get_one_mock_query('password')
|
||||
self.assertEqual([field_value], lookup_plugin.run([query]))
|
||||
|
||||
|
||||
@patch('ansible_collections.community.general.plugins.lookup.onepassword_raw.OnePass', MockOnePass)
|
||||
class TestOnePasswordRawLookup(unittest.TestCase):
|
||||
|
||||
def test_onepassword_raw_plugin_multiple(self):
|
||||
raw_lookup_plugin = OnePasswordRawLookup()
|
||||
|
||||
entry = MOCK_ENTRIES[0]
|
||||
raw_value = entry['output']
|
||||
|
||||
self.assertEqual(
|
||||
[raw_value] * len(entry['queries']),
|
||||
raw_lookup_plugin.run(entry['queries'])
|
||||
)
|
Loading…
Add table
Add a link
Reference in a new issue