From 2566fdbee06cf5708cfcd988a65fb81cdf794dbf Mon Sep 17 00:00:00 2001
From: Daniel Watkins <oddbloke@ubuntu.com>
Date: Fri, 27 Mar 2020 22:47:01 -0400
Subject: [PATCH] net: introduce is_ip_address function (#288)

This will be required for the mirror URL sanitisation work,
---
 cloudinit/net/__init__.py        | 19 +++++++++++++++++-
 cloudinit/net/tests/test_init.py | 33 ++++++++++++++++++++++++++++----
 2 files changed, 47 insertions(+), 5 deletions(-)

--- a/cloudinit/net/__init__.py
+++ b/cloudinit/net/__init__.py
@@ -6,13 +6,14 @@
 # This file is part of cloud-init. See LICENSE file for license information.
 
 import errno
+import ipaddress
 import logging
 import os
 import re
 from functools import partial
 
-from cloudinit.net.network_state import mask_to_net_prefix
 from cloudinit import util
+from cloudinit.net.network_state import mask_to_net_prefix
 from cloudinit.url_helper import UrlError, readurl
 
 LOG = logging.getLogger(__name__)
@@ -916,6 +917,22 @@ def has_url_connectivity(url):
         return False
     return True
 
+
+def is_ip_address(s: str) -> bool:
+    """Returns a bool indicating if ``s`` is an IP address.
+
+    :param s:
+        The string to test.
+
+    :return:
+        A bool indicating if the string contains an IP address or not.
+    """
+    try:
+        ipaddress.ip_address(s)
+    except ValueError:
+        return False
+    return True
+
 
 class EphemeralIPv4Network(object):
     """Context manager which sets up temporary static network configuration.
--- a/cloudinit/net/tests/test_init.py
+++ b/cloudinit/net/tests/test_init.py
@@ -2,16 +2,19 @@
 
 import copy
 import errno
-import httpretty
+import ipaddress
 import os
-import requests
 import textwrap
 from unittest import mock
 
+import httpretty
+import pytest
+import requests
+
 import cloudinit.net as net
-from cloudinit.util import ensure_file, write_file, ProcessExecutionError
-from cloudinit.tests.helpers import CiTestCase, HttprettyTestCase
 from cloudinit import safeyaml as yaml
+from cloudinit.tests.helpers import CiTestCase, HttprettyTestCase
+from cloudinit.util import ProcessExecutionError, ensure_file, write_file
 
 
 class TestSysDevPath(CiTestCase):
@@ -1297,4 +1300,26 @@ class TestNetFailOver(CiTestCase):
         m_standby.return_value = False
         self.assertFalse(net.is_netfailover(devname, driver))
 
+
+class TestIsIpAddress:
+    """Tests for net.is_ip_address.
+
+    Instead of testing with values we rely on the ipaddress stdlib module to
+    handle all values correctly, so simply test that is_ip_address defers to
+    the ipaddress module correctly.
+    """
+
+    @pytest.mark.parametrize('ip_address_side_effect,expected_return', (
+        (ValueError, False),
+        (lambda _: ipaddress.IPv4Address('192.168.0.1'), True),
+        (lambda _: ipaddress.IPv6Address('2001:db8::'), True),
+    ))
+    def test_is_ip_address(self, ip_address_side_effect, expected_return):
+        with mock.patch('cloudinit.net.ipaddress.ip_address',
+                        side_effect=ip_address_side_effect) as m_ip_address:
+            ret = net.is_ip_address(mock.sentinel.ip_address_in)
+        assert expected_return == ret
+        expected_call = mock.call(mock.sentinel.ip_address_in)
+        assert [expected_call] == m_ip_address.call_args_list
+
 # vi: ts=4 expandtab
