Support Zaqar signed queue URLs
This adds new resource to generate a signed URL for a Zaqar queue. Change-Id: Ib42f11bf13d1e836563023a9fa587b93c875ec61 Closes-Bug: #1628691
This commit is contained in:
parent
d30965a015
commit
571740f07d
|
@ -11,8 +11,11 @@
|
|||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from oslo_serialization import jsonutils
|
||||
|
||||
from heat.common.i18n import _
|
||||
from heat.engine import attributes
|
||||
from heat.engine import constraints
|
||||
from heat.engine import properties
|
||||
from heat.engine import resource
|
||||
from heat.engine import support
|
||||
|
@ -129,7 +132,108 @@ class ZaqarQueue(resource.Resource):
|
|||
}
|
||||
|
||||
|
||||
class ZaqarSignedQueueURL(resource.Resource):
|
||||
"""A resource for managing signed URLs of Zaqar queues.
|
||||
|
||||
Signed URLs allow to give specific access to queues, for example to be used
|
||||
as alarm notifications.
|
||||
"""
|
||||
|
||||
default_client_name = "zaqar"
|
||||
|
||||
support_status = support.SupportStatus(version='8.0.0')
|
||||
|
||||
PROPERTIES = (
|
||||
QUEUE, PATHS, TTL, METHODS,
|
||||
) = (
|
||||
'queue', 'paths', 'ttl', 'methods',
|
||||
)
|
||||
|
||||
ATTRIBUTES = (
|
||||
SIGNATURE, EXPIRES, PATHS_ATTR, METHODS_ATTR,
|
||||
) = (
|
||||
'signature', 'expires', 'paths', 'methods',
|
||||
)
|
||||
|
||||
properties_schema = {
|
||||
QUEUE: properties.Schema(
|
||||
properties.Schema.STRING,
|
||||
_("Name of the queue instance to create a URL for."),
|
||||
required=True),
|
||||
PATHS: properties.Schema(
|
||||
properties.Schema.LIST,
|
||||
description=_("List of allowed paths to be accessed. "
|
||||
"Default to allow queue messages URL.")),
|
||||
TTL: properties.Schema(
|
||||
properties.Schema.INTEGER,
|
||||
description=_("Time validity of the URL, in seconds. "
|
||||
"Default to one day.")),
|
||||
METHODS: properties.Schema(
|
||||
properties.Schema.LIST,
|
||||
description=_("List of allowed HTTP methods to be used. "
|
||||
"Default to allow GET."),
|
||||
schema=properties.Schema(
|
||||
properties.Schema.STRING,
|
||||
constraints=[
|
||||
constraints.AllowedValues(['GET', 'DELETE', 'PATCH',
|
||||
'POST', 'PUT']),
|
||||
],
|
||||
))
|
||||
}
|
||||
|
||||
attributes_schema = {
|
||||
SIGNATURE: attributes.Schema(
|
||||
_("Signature of the URL built by Zaqar.")
|
||||
),
|
||||
EXPIRES: attributes.Schema(
|
||||
_("Expiration date of the URL.")
|
||||
),
|
||||
PATHS_ATTR: attributes.Schema(
|
||||
_("Comma-delimited list of paths for convenience.")
|
||||
),
|
||||
METHODS_ATTR: attributes.Schema(
|
||||
_("Comma-delimited list of methods for convenience.")
|
||||
),
|
||||
}
|
||||
|
||||
def handle_create(self):
|
||||
queue = self.client().queue(self.properties[self.QUEUE])
|
||||
signed_url = queue.signed_url(paths=self.properties[self.PATHS],
|
||||
methods=self.properties[self.METHODS],
|
||||
ttl_seconds=self.properties[self.TTL])
|
||||
self.data_set(self.SIGNATURE, signed_url['signature'])
|
||||
self.data_set(self.EXPIRES, signed_url['expires'])
|
||||
self.data_set(self.PATHS, jsonutils.dumps(signed_url['paths']))
|
||||
self.data_set(self.METHODS, jsonutils.dumps(signed_url['methods']))
|
||||
self.resource_id_set(self._url(signed_url))
|
||||
|
||||
def _url(self, data):
|
||||
"""Return a URL that can be used for example for alarming."""
|
||||
return ('zaqar://?signature={0}&expires={1}&paths={2}'
|
||||
'&methods={3}&project_id={4}&queue_name={5}'.format(
|
||||
data['signature'], data['expires'],
|
||||
','.join(data['paths']), ','.join(data['methods']),
|
||||
data['project'], self.properties[self.QUEUE]))
|
||||
|
||||
def handle_delete(self):
|
||||
# We can't delete a signed URL
|
||||
return True
|
||||
|
||||
def _resolve_attribute(self, name):
|
||||
if not self.resource_id:
|
||||
return
|
||||
if name == self.SIGNATURE:
|
||||
return self.data()[self.SIGNATURE]
|
||||
elif name == self.EXPIRES:
|
||||
return self.data()[self.EXPIRES]
|
||||
elif name == self.PATHS:
|
||||
return jsonutils.loads(self.data()[self.PATHS])
|
||||
elif name == self.METHODS:
|
||||
return jsonutils.loads(self.data()[self.METHODS])
|
||||
|
||||
|
||||
def resource_mapping():
|
||||
return {
|
||||
'OS::Zaqar::Queue': ZaqarQueue,
|
||||
'OS::Zaqar::SignedQueueURL': ZaqarSignedQueueURL,
|
||||
}
|
||||
|
|
|
@ -270,3 +270,56 @@ class ZaqarMessageQueueTest(common.HeatTestCase):
|
|||
queue._show_resource()))
|
||||
|
||||
self.m.VerifyAll()
|
||||
|
||||
|
||||
class ZaqarSignedQueueURLTest(common.HeatTestCase):
|
||||
tmpl = '''
|
||||
heat_template_version: 2015-10-15
|
||||
resources:
|
||||
signed_url:
|
||||
type: OS::Zaqar::SignedQueueURL
|
||||
properties:
|
||||
queue: foo
|
||||
ttl: 60
|
||||
paths:
|
||||
- messages
|
||||
- subscription
|
||||
methods:
|
||||
- POST
|
||||
- DELETE
|
||||
'''
|
||||
|
||||
@mock.patch('zaqarclient.queues.v2.queues.Queue.signed_url')
|
||||
def test_create(self, mock_signed_url):
|
||||
mock_signed_url.return_value = {
|
||||
'expires': '2020-01-01',
|
||||
'signature': 'secret',
|
||||
'project': 'project_id',
|
||||
'paths': ['/v2/foo/messages', '/v2/foo/sub'],
|
||||
'methods': ['DELETE', 'POST']}
|
||||
|
||||
self.t = template_format.parse(self.tmpl)
|
||||
self.stack = utils.parse_stack(self.t)
|
||||
self.rsrc = self.stack['signed_url']
|
||||
self.assertIsNone(self.rsrc.validate())
|
||||
self.stack.create()
|
||||
self.assertEqual(self.rsrc.CREATE, self.rsrc.action)
|
||||
self.assertEqual(self.rsrc.COMPLETE, self.rsrc.status)
|
||||
self.assertEqual(self.stack.CREATE, self.stack.action)
|
||||
self.assertEqual(self.stack.COMPLETE, self.stack.status)
|
||||
|
||||
mock_signed_url.assert_called_once_with(
|
||||
paths=['messages', 'subscription'],
|
||||
methods=['POST', 'DELETE'],
|
||||
ttl_seconds=60)
|
||||
|
||||
self.assertEqual('secret', self.rsrc.FnGetAtt('signature'))
|
||||
self.assertEqual('2020-01-01', self.rsrc.FnGetAtt('expires'))
|
||||
self.assertEqual(['/v2/foo/messages', '/v2/foo/sub'],
|
||||
self.rsrc.FnGetAtt('paths'))
|
||||
self.assertEqual(['DELETE', 'POST'], self.rsrc.FnGetAtt('methods'))
|
||||
self.assertEqual(
|
||||
'zaqar://?signature=secret&expires=2020-01-01'
|
||||
'&paths=/v2/foo/messages,/v2/foo/sub&methods=DELETE,POST'
|
||||
'&project_id=project_id&queue_name=foo',
|
||||
self.rsrc.resource_id)
|
||||
|
|
Loading…
Reference in New Issue