From ca3beb68de8fdf7e79e1c7c0c3d775180a6d0248 Mon Sep 17 00:00:00 2001 From: Dominik Kukacka Date: Tue, 9 May 2023 19:48:29 +0200 Subject: [PATCH] Add ipbase_info module (#6477) * Add ipbase_facts module * Fixes * Fix * check * Move to _info * Fixes * Add apikey * check * check * Fix * check * check * Add test * Go * Fix test * Fix * load vs loads * check * make pep happy * test * check * fix * check * fix * Some typo fixes * Fix YAML boolean. * Docs improvement. --------- Co-authored-by: Felix Fontein --- .github/BOTMETA.yml | 2 + plugins/modules/ipbase_info.py | 304 ++++++++++++++++++++++ tests/unit/plugins/modules/test_ipbase.py | 187 +++++++++++++ 3 files changed, 493 insertions(+) create mode 100644 plugins/modules/ipbase_info.py create mode 100644 tests/unit/plugins/modules/test_ipbase.py diff --git a/.github/BOTMETA.yml b/.github/BOTMETA.yml index 6f4abeaea4..66269e3b20 100644 --- a/.github/BOTMETA.yml +++ b/.github/BOTMETA.yml @@ -623,6 +623,8 @@ files: maintainers: bregman-arie $modules/ipa_: maintainers: $team_ipa + $modules/ipbase_info.py: + maintainers: dominikkukacka $modules/ipa_pwpolicy.py: maintainers: adralioh $modules/ipa_service.py: diff --git a/plugins/modules/ipbase_info.py b/plugins/modules/ipbase_info.py new file mode 100644 index 0000000000..4eeb70c957 --- /dev/null +++ b/plugins/modules/ipbase_info.py @@ -0,0 +1,304 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# +# Copyright (c) 2023, Dominik Kukacka +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: "ipbase_info" +version_added: "7.0.0" +short_description: "Retrieve IP geolocation and other facts of a host's IP address using the ipbase.com API" +description: + - "Retrieve IP geolocation and other facts of a host's IP address using the ipbase.com API" +author: "Dominik Kukacka (@dominikkukacka)" +extends_documentation_fragment: + - "community.general.attributes" + - "community.general.attributes.info_module" +options: + ip: + description: + - "The IP you want to get the info for. If not specified the API will detect the IP automatically." + required: false + type: str + apikey: + description: + - "The API key for the request if you need more requests." + required: false + type: str + hostname: + description: + - "If the hostname parameter is set to C(true), the API response will contain the hostname of the IP." + required: false + type: bool + default: false + language: + description: + - "An ISO Alpha 2 Language Code for localizing the IP data" + required: false + type: str + default: "en" +notes: + - "Check U(https://ipbase.com/) for more information." +''' + +EXAMPLES = ''' +- name: "Get IP geolocation information of the primary outgoing IP" + community.general.ipbase_info: + register: my_ip_info + +- name: "Get IP geolocation information of a specific IP" + community.general.ipbase_info: + ip: "8.8.8.8" + register: my_ip_info + + +- name: "Get IP geolocation information of a specific IP with all other possible parameters" + community.general.ipbase_info: + ip: "8.8.8.8" + apikey: "xxxxxxxxxxxxxxxxxxxxxx" + hostname: true + language: "de" + register: my_ip_info + +''' + +RETURN = ''' +data: + description: "JSON parsed response from ipbase.com. Please refer to U(https://ipbase.com/docs/info) for the detailled structure of the response." + returned: success + type: dict + sample: { + "ip": "1.1.1.1", + "hostname": "one.one.one.one", + "type": "v4", + "range_type": { + "type": "PUBLIC", + "description": "Public address" + }, + "connection": { + "asn": 13335, + "organization": "Cloudflare, Inc.", + "isp": "APNIC Research and Development", + "range": "1.1.1.1/32" + }, + "location": { + "geonames_id": 5332870, + "latitude": 34.053611755371094, + "longitude": -118.24549865722656, + "zip": "90012", + "continent": { + "code": "NA", + "name": "North America", + "name_translated": "North America" + }, + "country": { + "alpha2": "US", + "alpha3": "USA", + "calling_codes": [ + "+1" + ], + "currencies": [ + { + "symbol": "$", + "name": "US Dollar", + "symbol_native": "$", + "decimal_digits": 2, + "rounding": 0, + "code": "USD", + "name_plural": "US dollars" + } + ], + "emoji": "...", + "ioc": "USA", + "languages": [ + { + "name": "English", + "name_native": "English" + } + ], + "name": "United States", + "name_translated": "United States", + "timezones": [ + "America/New_York", + "America/Detroit", + "America/Kentucky/Louisville", + "America/Kentucky/Monticello", + "America/Indiana/Indianapolis", + "America/Indiana/Vincennes", + "America/Indiana/Winamac", + "America/Indiana/Marengo", + "America/Indiana/Petersburg", + "America/Indiana/Vevay", + "America/Chicago", + "America/Indiana/Tell_City", + "America/Indiana/Knox", + "America/Menominee", + "America/North_Dakota/Center", + "America/North_Dakota/New_Salem", + "America/North_Dakota/Beulah", + "America/Denver", + "America/Boise", + "America/Phoenix", + "America/Los_Angeles", + "America/Anchorage", + "America/Juneau", + "America/Sitka", + "America/Metlakatla", + "America/Yakutat", + "America/Nome", + "America/Adak", + "Pacific/Honolulu" + ], + "is_in_european_union": false, + "fips": "US", + "geonames_id": 6252001, + "hasc_id": "US", + "wikidata_id": "Q30" + }, + "city": { + "fips": "644000", + "alpha2": null, + "geonames_id": 5368753, + "hasc_id": null, + "wikidata_id": "Q65", + "name": "Los Angeles", + "name_translated": "Los Angeles" + }, + "region": { + "fips": "US06", + "alpha2": "US-CA", + "geonames_id": 5332921, + "hasc_id": "US.CA", + "wikidata_id": "Q99", + "name": "California", + "name_translated": "California" + } + }, + "tlds": [ + ".us" + ], + "timezone": { + "id": "America/Los_Angeles", + "current_time": "2023-05-04T04:30:28-07:00", + "code": "PDT", + "is_daylight_saving": true, + "gmt_offset": -25200 + }, + "security": { + "is_anonymous": false, + "is_datacenter": false, + "is_vpn": false, + "is_bot": false, + "is_abuser": true, + "is_known_attacker": true, + "is_proxy": false, + "is_spam": false, + "is_tor": false, + "is_icloud_relay": false, + "threat_score": 100 + }, + "domains": { + "count": 10943, + "domains": [ + "eliwise.academy", + "accountingprose.academy", + "pistola.academy", + "1and1-test-ntlds-fr.accountant", + "omnergy.africa" + ] + } + } +''' + +from ansible.module_utils.basic import AnsibleModule + +from ansible.module_utils.urls import fetch_url + +from ansible.module_utils.six.moves.urllib.parse import urlencode + + +USER_AGENT = 'ansible-community.general.ipbase_info/0.1.0' +BASE_URL = 'https://api.ipbase.com/v2/info' + + +class IpbaseInfo(object): + + def __init__(self, module): + self.module = module + + def _get_url_data(self, url): + response, info = fetch_url( + self.module, + url, + force=True, + timeout=10, + headers={ + 'Accept': 'application/json', + 'User-Agent': USER_AGENT, + }) + + if info['status'] != 200: + self.module.fail_json(msg='The API request to ipbase.com returned an error status code {0}'.format(info['status'])) + else: + try: + content = response.read() + result = self.module.from_json(content.decode('utf8')) + except ValueError: + self.module.fail_json( + msg='Failed to parse the ipbase.com response: ' + '{0} {1}'.format(url, content)) + else: + return result + + def info(self): + + ip = self.module.params['ip'] + apikey = self.module.params['apikey'] + hostname = self.module.params['hostname'] + language = self.module.params['language'] + + url = BASE_URL + + params = {} + if ip: + params['ip'] = ip + + if apikey: + params['apikey'] = apikey + + if hostname: + params['hostname'] = 1 + + if language: + params['language'] = language + + if params: + url += '?' + urlencode(params) + + return self._get_url_data(url) + + +def main(): + module_args = dict( + ip=dict(type='str', required=False, no_log=False), + apikey=dict(type='str', required=False, no_log=True), + hostname=dict(type='bool', required=False, no_log=False, default=False), + language=dict(type='str', required=False, no_log=False, default='en'), + ) + + module = AnsibleModule( + argument_spec=module_args, + supports_check_mode=True, + ) + + ipbase = IpbaseInfo(module) + module.exit_json(**ipbase.info()) + + +if __name__ == '__main__': + main() diff --git a/tests/unit/plugins/modules/test_ipbase.py b/tests/unit/plugins/modules/test_ipbase.py new file mode 100644 index 0000000000..8106889da1 --- /dev/null +++ b/tests/unit/plugins/modules/test_ipbase.py @@ -0,0 +1,187 @@ +# -*- coding: utf-8 -*- +# Copyright (c) Ansible project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +import json + +from ansible_collections.community.general.plugins.modules.ipbase_info import IpbaseInfo +from ansible_collections.community.general.tests.unit.compat import unittest +from ansible_collections.community.general.tests.unit.compat.mock import Mock + + +IPBASE_DATA = { + "response": b""" +{ + "data": { + "ip": "1.1.1.1", + "hostname": "one.one.one.one", + "type": "v4", + "range_type": { + "type": "PUBLIC", + "description": "Public address" + }, + "connection": { + "asn": 13335, + "organization": "Cloudflare, Inc.", + "isp": "APNIC Research and Development", + "range": "1.1.1.1/32" + }, + "location": { + "geonames_id": 5332870, + "latitude": 34.053611755371094, + "longitude": -118.24549865722656, + "zip": "90012", + "continent": { + "code": "NA", + "name": "North America", + "name_translated": "North America" + }, + "country": { + "alpha2": "US", + "alpha3": "USA", + "calling_codes": [ + "+1" + ], + "currencies": [ + { + "symbol": "$", + "name": "US Dollar", + "symbol_native": "$", + "decimal_digits": 2, + "rounding": 0, + "code": "USD", + "name_plural": "US dollars" + } + ], + "emoji": "...", + "ioc": "USA", + "languages": [ + { + "name": "English", + "name_native": "English" + } + ], + "name": "United States", + "name_translated": "United States", + "timezones": [ + "America/New_York", + "America/Detroit", + "America/Kentucky/Louisville", + "America/Kentucky/Monticello", + "America/Indiana/Indianapolis", + "America/Indiana/Vincennes", + "America/Indiana/Winamac", + "America/Indiana/Marengo", + "America/Indiana/Petersburg", + "America/Indiana/Vevay", + "America/Chicago", + "America/Indiana/Tell_City", + "America/Indiana/Knox", + "America/Menominee", + "America/North_Dakota/Center", + "America/North_Dakota/New_Salem", + "America/North_Dakota/Beulah", + "America/Denver", + "America/Boise", + "America/Phoenix", + "America/Los_Angeles", + "America/Anchorage", + "America/Juneau", + "America/Sitka", + "America/Metlakatla", + "America/Yakutat", + "America/Nome", + "America/Adak", + "Pacific/Honolulu" + ], + "is_in_european_union": false, + "fips": "US", + "geonames_id": 6252001, + "hasc_id": "US", + "wikidata_id": "Q30" + }, + "city": { + "fips": "644000", + "alpha2": null, + "geonames_id": 5368753, + "hasc_id": null, + "wikidata_id": "Q65", + "name": "Los Angeles", + "name_translated": "Los Angeles" + }, + "region": { + "fips": "US06", + "alpha2": "US-CA", + "geonames_id": 5332921, + "hasc_id": "US.CA", + "wikidata_id": "Q99", + "name": "California", + "name_translated": "California" + } + }, + "tlds": [ + ".us" + ], + "timezone": { + "id": "America/Los_Angeles", + "current_time": "2023-05-04T04:30:28-07:00", + "code": "PDT", + "is_daylight_saving": true, + "gmt_offset": -25200 + }, + "security": { + "is_anonymous": false, + "is_datacenter": false, + "is_vpn": false, + "is_bot": false, + "is_abuser": true, + "is_known_attacker": true, + "is_proxy": false, + "is_spam": false, + "is_tor": false, + "is_icloud_relay": false, + "threat_score": 100 + }, + "domains": { + "count": 10943, + "domains": [ + "eliwise.academy", + "accountingprose.academy", + "pistola.academy", + "1and1-test-ntlds-fr.accountant", + "omnergy.africa" + ] + } + } +} +""" +} + + +class TestIpbase(unittest.TestCase): + def test_info(self,): + "test the json data extraction" + + params = { + "ip": "1.1.1.1", + "apikey": "aaa", + "hostname": True, + "language": "de", + } + module = Mock() + module.params = params + + data = json.loads(IPBASE_DATA['response'].decode("utf-8")) + + IpbaseInfo._get_url_data = Mock() + IpbaseInfo._get_url_data.return_value = data + jenkins_plugin = IpbaseInfo(module) + + json_data = jenkins_plugin.info() + + self.maxDiff = None + self.assertDictEqual(json_data, data)