From be33b3364cff9cc3211d93f1575be13f36172c0e Mon Sep 17 00:00:00 2001 From: Steven Hardy Date: Tue, 4 Dec 2012 16:33:13 +0000 Subject: [PATCH] heat : Add HeatIdentifier from_url function Add method to create a HeatIdentifier from a URL containing an ARN ref bug 1087799 Signed-off-by: Steven Hardy Change-Id: Ie325de9105e85dbf9fd12be5e7b8c3d055e77795 --- heat/common/identifier.py | 29 ++++++++ heat/tests/test_identifier.py | 131 ++++++++++++++++++++++++++++++++++ 2 files changed, 160 insertions(+) diff --git a/heat/common/identifier.py b/heat/common/identifier.py index 9b9ae94f54..bd339eea97 100644 --- a/heat/common/identifier.py +++ b/heat/common/identifier.py @@ -14,6 +14,7 @@ import re import urllib +import urlparse import collections @@ -62,6 +63,28 @@ class HeatIdentifier(collections.Mapping): urllib.unquote(path.group(2)), urllib.unquote(path.group(3))) + @classmethod + def from_arn_url(cls, url): + ''' + Return a new HeatIdentifier generated by parsing the supplied URL + The URL is expected to contain a valid arn as part of the path + ''' + # Sanity check the URL + urlp = urlparse.urlparse(url) + if (urlp.scheme not in ('http', 'https') or + not urlp.netloc or not urlp.path): + raise ValueError('"%s" is not a valid URL' % url) + + # Remove any query-string and extract the ARN + arn_url_prefix = '/arn%3Aopenstack%3Aheat%3A%3A' + match = re.search(arn_url_prefix, urlp.path, re.IGNORECASE) + if match is None: + raise ValueError('"%s" is not a valid ARN URL' % url) + # the +1 is to skip the leading / + url_arn = urlp.path[match.start() + 1:] + arn = urllib.unquote(url_arn) + return cls.from_arn(arn) + def arn(self): ''' Return an ARN of the form: @@ -70,6 +93,12 @@ class HeatIdentifier(collections.Mapping): return 'arn:openstack:heat::%s:%s' % (urllib.quote(self.tenant, ''), self._tenant_path()) + def arn_url_path(self): + ''' + Return an ARN quoted correctly for use in a URL + ''' + return '/' + urllib.quote(self.arn(), '') + def url_path(self): ''' Return a URL-encoded path segment of a URL in the form: diff --git a/heat/tests/test_identifier.py b/heat/tests/test_identifier.py index af4d8f719d..3a951c6aac 100644 --- a/heat/tests/test_identifier.py +++ b/heat/tests/test_identifier.py @@ -25,6 +25,7 @@ from heat.common import identifier @attr(tag=['unit', 'identifier']) @attr(speed='fast') class IdentifierTest(unittest.TestCase): + url_prefix = 'http://1.2.3.4/foo/' def test_attrs(self): hi = identifier.HeatIdentifier('t', 's', 'i', 'p') @@ -58,6 +59,11 @@ class IdentifierTest(unittest.TestCase): hi = identifier.HeatIdentifier('t', 's', 'i', 'p') self.assertEqual(hi.arn(), 'arn:openstack:heat::t:stacks/s/i/p') + def test_arn_url(self): + hi = identifier.HeatIdentifier('t', 's', 'i', 'p') + self.assertEqual(hi.arn_url_path(), + '/arn%3Aopenstack%3Aheat%3A%3At%3Astacks%2Fs%2Fi%2Fp') + def test_arn_id_int(self): hi = identifier.HeatIdentifier('t', 's', 42, 'p') self.assertEqual(hi.arn(), 'arn:openstack:heat::t:stacks/s/42/p') @@ -70,6 +76,14 @@ class IdentifierTest(unittest.TestCase): self.assertEqual(hi.stack_id, 'i') self.assertEqual(hi.path, '/p') + def test_arn_url_parse(self): + url = self.url_prefix + 'arn%3Aopenstack%3Aheat%3A%3At%3Astacks/s/i/p' + hi = identifier.HeatIdentifier.from_arn_url(url) + self.assertEqual(hi.tenant, 't') + self.assertEqual(hi.stack_name, 's') + self.assertEqual(hi.stack_id, 'i') + self.assertEqual(hi.path, '/p') + def test_arn_parse_path_default(self): arn = 'arn:openstack:heat::t:stacks/s/i' hi = identifier.HeatIdentifier.from_arn(arn) @@ -78,35 +92,112 @@ class IdentifierTest(unittest.TestCase): self.assertEqual(hi.stack_id, 'i') self.assertEqual(hi.path, '') + def test_arn_url_parse_default(self): + url = self.url_prefix + 'arn%3Aopenstack%3Aheat%3A%3At%3Astacks/s/i' + hi = identifier.HeatIdentifier.from_arn_url(url) + self.assertEqual(hi.tenant, 't') + self.assertEqual(hi.stack_name, 's') + self.assertEqual(hi.stack_id, 'i') + self.assertEqual(hi.path, '') + def test_arn_parse_upper(self): arn = 'ARN:openstack:heat::t:stacks/s/i/p' hi = identifier.HeatIdentifier.from_arn(arn) self.assertEqual(hi.stack_name, 's') + self.assertEqual(hi.stack_id, 'i') + self.assertEqual(hi.path, '/p') + + def test_arn_url_parse_upper(self): + url = self.url_prefix + 'ARN%3Aopenstack%3Aheat%3A%3At%3Astacks/s/i/p' + hi = identifier.HeatIdentifier.from_arn_url(url) + self.assertEqual(hi.tenant, 't') + self.assertEqual(hi.stack_name, 's') + self.assertEqual(hi.stack_id, 'i') + self.assertEqual(hi.path, '/p') + + def test_arn_url_parse_qs(self): + url = self.url_prefix +\ + 'arn%3Aopenstack%3Aheat%3A%3At%3Astacks/s/i/p?foo=bar' + hi = identifier.HeatIdentifier.from_arn_url(url) + self.assertEqual(hi.tenant, 't') + self.assertEqual(hi.stack_name, 's') + self.assertEqual(hi.stack_id, 'i') + self.assertEqual(hi.path, '/p') def test_arn_parse_arn_invalid(self): arn = 'urn:openstack:heat::t:stacks/s/i' self.assertRaises(ValueError, identifier.HeatIdentifier.from_arn, arn) + def test_arn_url_parse_arn_invalid(self): + url = self.url_prefix + 'urn:openstack:heat::t:stacks/s/i/p' + self.assertRaises(ValueError, + identifier.HeatIdentifier.from_arn_url, url) + def test_arn_parse_os_invalid(self): arn = 'arn:aws:heat::t:stacks/s/i' self.assertRaises(ValueError, identifier.HeatIdentifier.from_arn, arn) + def test_arn_url_parse_os_invalid(self): + url = self.url_prefix + 'arn:aws:heat::t:stacks/s/i/p' + self.assertRaises(ValueError, + identifier.HeatIdentifier.from_arn_url, url) + def test_arn_parse_heat_invalid(self): arn = 'arn:openstack:cool::t:stacks/s/i' self.assertRaises(ValueError, identifier.HeatIdentifier.from_arn, arn) + def test_arn_url_parse_heat_invalid(self): + url = self.url_prefix + 'arn:openstack:cool::t:stacks/s/i/p' + self.assertRaises(ValueError, + identifier.HeatIdentifier.from_arn_url, url) + def test_arn_parse_stacks_invalid(self): arn = 'arn:openstack:heat::t:sticks/s/i' self.assertRaises(ValueError, identifier.HeatIdentifier.from_arn, arn) + def test_arn_url_parse_stacks_invalid(self): + url = self.url_prefix + 'arn%3Aopenstack%3Aheat%3A%3At%3Asticks/s/i/p' + self.assertRaises(ValueError, + identifier.HeatIdentifier.from_arn_url, url) + def test_arn_parse_missing_field(self): arn = 'arn:openstack:heat::t:stacks/s' self.assertRaises(ValueError, identifier.HeatIdentifier.from_arn, arn) + def test_arn_url_parse_missing_field(self): + url = self.url_prefix + 'arn%3Aopenstack%3Aheat%3A%3At%3Asticks/s/' + self.assertRaises(ValueError, + identifier.HeatIdentifier.from_arn_url, url) + def test_arn_parse_empty_field(self): arn = 'arn:openstack:heat::t:stacks//i' self.assertRaises(ValueError, identifier.HeatIdentifier.from_arn, arn) + def test_arn_url_parse_empty_field(self): + url = self.url_prefix + 'arn%3Aopenstack%3Aheat%3A%3At%3Asticks//i' + self.assertRaises(ValueError, + identifier.HeatIdentifier.from_arn_url, url) + + def test_arn_url_parse_leading_char(self): + url = self.url_prefix + 'Aarn%3Aopenstack%3Aheat%3A%3At%3Asticks/s/i/p' + self.assertRaises(ValueError, + identifier.HeatIdentifier.from_arn_url, url) + + def test_arn_url_parse_leading_space(self): + url = self.url_prefix + ' arn%3Aopenstack%3Aheat%3A%3At%3Asticks/s/i/p' + self.assertRaises(ValueError, + identifier.HeatIdentifier.from_arn_url, url) + + def test_arn_url_parse_badurl_proto(self): + url = 'htt://1.2.3.4/foo/arn%3Aopenstack%3Aheat%3A%3At%3Asticks/s/i/p' + self.assertRaises(ValueError, + identifier.HeatIdentifier.from_arn_url, url) + + def test_arn_url_parse_badurl_host(self): + url = 'htt:///foo/arn%3Aopenstack%3Aheat%3A%3At%3Asticks/s/i/p' + self.assertRaises(ValueError, + identifier.HeatIdentifier.from_arn_url, url) + def test_arn_round_trip(self): hii = identifier.HeatIdentifier('t', 's', 'i', 'p') hio = identifier.HeatIdentifier.from_arn(hii.arn()) @@ -120,6 +211,12 @@ class IdentifierTest(unittest.TestCase): hi = identifier.HeatIdentifier.from_arn(arn) self.assertEqual(hi.arn(), arn) + def test_arn_url_parse_round_trip(self): + arn = '/arn%3Aopenstack%3Aheat%3A%3At%3Astacks%2Fs%2Fi%2Fp' + url = 'http://1.2.3.4/foo' + arn + hi = identifier.HeatIdentifier.from_arn_url(url) + self.assertEqual(hi.arn_url_path(), arn) + def test_dict_round_trip(self): hii = identifier.HeatIdentifier('t', 's', 'i', 'p') hio = identifier.HeatIdentifier(**dict(hii)) @@ -165,21 +262,45 @@ class IdentifierTest(unittest.TestCase): hi = identifier.HeatIdentifier.from_arn(arn) self.assertEqual(hi.tenant, ':/') + def test_url_tenant_decode(self): + enc_arn = 'arn%3Aopenstack%3Aheat%3A%3A%253A%252F%3Astacks%2Fs%2Fi' + url = self.url_prefix + enc_arn + hi = identifier.HeatIdentifier.from_arn_url(url) + self.assertEqual(hi.tenant, ':/') + def test_name_decode(self): arn = 'arn:openstack:heat::t:stacks/%3A%2F/i' hi = identifier.HeatIdentifier.from_arn(arn) self.assertEqual(hi.stack_name, ':/') + def test_url_name_decode(self): + enc_arn = 'arn%3Aopenstack%3Aheat%3A%3At%3Astacks%2F%253A%252F%2Fi' + url = self.url_prefix + enc_arn + hi = identifier.HeatIdentifier.from_arn_url(url) + self.assertEqual(hi.stack_name, ':/') + def test_id_decode(self): arn = 'arn:openstack:heat::t:stacks/s/%3A%2F' hi = identifier.HeatIdentifier.from_arn(arn) self.assertEqual(hi.stack_id, ':/') + def test_url_id_decode(self): + enc_arn = 'arn%3Aopenstack%3Aheat%3A%3At%3Astacks%2Fs%2F%253A%252F' + url = self.url_prefix + enc_arn + hi = identifier.HeatIdentifier.from_arn_url(url) + self.assertEqual(hi.stack_id, ':/') + def test_path_decode(self): arn = 'arn:openstack:heat::t:stacks/s/i/%3A%2F' hi = identifier.HeatIdentifier.from_arn(arn) self.assertEqual(hi.path, '/:/') + def test_url_path_decode(self): + enc_arn = 'arn%3Aopenstack%3Aheat%3A%3At%3Astacks%2Fs%2Fi%2F%253A%252F' + url = self.url_prefix + enc_arn + hi = identifier.HeatIdentifier.from_arn_url(url) + self.assertEqual(hi.path, '/:/') + def test_arn_escape_decode_round_trip(self): hii = identifier.HeatIdentifier(':/', ':/', ':/', ':/') hio = identifier.HeatIdentifier.from_arn(hii.arn()) @@ -193,6 +314,16 @@ class IdentifierTest(unittest.TestCase): hi = identifier.HeatIdentifier.from_arn(arn) self.assertEqual(hi.arn(), arn) + def test_arn_url_decode_escape_round_trip(self): + enc_arn = "".join(['arn%3Aopenstack%3Aheat%3A%3A%253A%252F%3A', + 'stacks%2F%253A%252F%2F%253A%252F%2F%253A']) + url = self.url_prefix + enc_arn + hi = identifier.HeatIdentifier.from_arn_url(url) + print "SHDEBUG hi.arn()=%s" % hi.arn() + hi2 = identifier.HeatIdentifier.from_arn_url(self.url_prefix + + hi.arn_url_path()) + self.assertEqual(hi, hi2) + def test_equal(self): hi1 = identifier.HeatIdentifier('t', 's', 'i', 'p') hi2 = identifier.HeatIdentifier('t', 's', 'i', 'p')