mirror of
https://github.com/ansible/awx.git
synced 2026-01-11 01:57:35 -03:30
Add the ability to programmatically generate named URLs on client-side.
This commit is contained in:
parent
098c27e5cd
commit
2ed3be0aab
@ -119,10 +119,20 @@ class URLModificationMiddleware(object):
|
||||
field_class=fields.DictField,
|
||||
read_only=True,
|
||||
label=_('Formats of all available named urls'),
|
||||
help_text=_('Read-only list of key-value pairs that shows the format of all available named'
|
||||
' URLs. Use this list as a guide when composing named URLs for resources'),
|
||||
category=_('System'),
|
||||
category_slug='system',
|
||||
help_text=_('Read-only list of key-value pairs that shows the standard format of all '
|
||||
'available named URLs.'),
|
||||
category=_('Named URL'),
|
||||
category_slug='named-url',
|
||||
)
|
||||
register(
|
||||
'NAMED_URL_GRAPH_NODES',
|
||||
field_class=fields.DictField,
|
||||
read_only=True,
|
||||
label=_('List of all named url graph nodes.'),
|
||||
help_text=_('Read-only list of key-value pairs that exposes named URL graph topology.'
|
||||
' Use this list to programmatically generate named URLs for resources'),
|
||||
category=_('Named URL'),
|
||||
category_slug='named-url',
|
||||
)
|
||||
|
||||
def _named_url_to_pk(self, node, named_url):
|
||||
|
||||
@ -50,11 +50,18 @@ class GraphNode(object):
|
||||
stack.pop()
|
||||
else:
|
||||
to_append = stack[-1].adj_list[stack[-1].counter][NEXT_NODE]
|
||||
current_fk_name = stack[-1].adj_list[stack[-1].counter][FK_NAME] + '.'
|
||||
current_fk_name = "%s." % (stack[-1].adj_list[stack[-1].counter][FK_NAME],)
|
||||
stack[-1].counter += 1
|
||||
stack.append(to_append)
|
||||
return NAMED_URL_RES_DILIMITER.join(named_url_components)
|
||||
|
||||
@property
|
||||
def named_url_repr(self):
|
||||
ret = {}
|
||||
ret['fields'] = self.fields
|
||||
ret['adj_list'] = [[x[FK_NAME], x[NEXT_NODE].model_url_name] for x in self.adj_list]
|
||||
return ret
|
||||
|
||||
def _encode_uri(self, text):
|
||||
'''
|
||||
Performance assured: http://stackoverflow.com/a/27086669
|
||||
@ -144,11 +151,13 @@ class GraphNode(object):
|
||||
def add_bindings(self):
|
||||
if self.model_url_name not in settings.NAMED_URL_FORMATS:
|
||||
settings.NAMED_URL_FORMATS[self.model_url_name] = self.named_url_format
|
||||
settings.NAMED_URL_GRAPH_NODES[self.model_url_name] = self.named_url_repr
|
||||
settings.NAMED_URL_MAPPINGS[self.model_url_name] = self.model
|
||||
|
||||
def remove_bindings(self):
|
||||
if self.model_url_name in settings.NAMED_URL_FORMATS:
|
||||
settings.NAMED_URL_FORMATS.pop(self.model_url_name)
|
||||
settings.NAMED_URL_GRAPH_NODES.pop(self.model_url_name)
|
||||
settings.NAMED_URL_MAPPINGS.pop(self.model_url_name)
|
||||
|
||||
|
||||
@ -270,6 +279,7 @@ def _generate_single_graph(configuration, dead_ends):
|
||||
|
||||
def generate_graph(models):
|
||||
settings.NAMED_URL_FORMATS = {}
|
||||
settings.NAMED_URL_GRAPH_NODES = {}
|
||||
settings.NAMED_URL_MAPPINGS = {}
|
||||
candidate_nodes = {}
|
||||
dead_ends = set()
|
||||
|
||||
@ -2,7 +2,7 @@ Starting from API V2, the named URL feature lets user access Tower resources via
|
||||
|
||||
## Usage
|
||||
|
||||
There is one named-URL-related Tower configuration setting available under `/api/v2/settings/system/`: `NAMED_URL_FORMATS`, which is a *read only* key-value pair list of all available named URL identifier formats. A typical `NAMED_URL_FORMATS` looks like this:
|
||||
There are two named-URL-related Tower configuration setting available under `/api/v2/settings/named-url/`: `NAMED_URL_FORMATS` and `NAMED_URL_GRAPH_NODES`. `NAMED_URL_FORMATS` is a *read only* key-value pair list of all available named URL identifier formats. A typical `NAMED_URL_FORMATS` looks like this:
|
||||
```
|
||||
"NAMED_URL_FORMATS": {
|
||||
"job_templates": "<name>",
|
||||
@ -35,6 +35,7 @@ An important aspect of generating unique identifier for named URL is dealing wit
|
||||
|
||||
Although `NAMED_URL_FORMATS` is immutable on user side, it will be automatically modified and expanded over time, reflecting underlying resource modification and expansion. Please consult `NAMED_URL_FORMATS` on the same Tower cluster where you want to use named url feature against.
|
||||
|
||||
`NAMED_URL_GRAPH_NODES` is another *read-only* list of key-value pairs that exposes the internal graph data structure Tower used to manage named URLs. This is not supposed to be human-readable but should be used for programmatically generating named URLs. An example script of generating named URL given the primary key of arbitrary resource objects that can have named URL, using info provided by `NAMED_URL_GRAPH_NODES`, can be found as `/tools/scripts/pk_to_named_url.py`.
|
||||
|
||||
## Identifier Format Protocol
|
||||
|
||||
@ -71,3 +72,4 @@ In general, acceptance should follow what's in 'Usage' section. The contents in
|
||||
* A user following the rules specified in `NAMED_URL_FORMATS` should be able to generate named URL exactly the same as the `named_url` field.
|
||||
* A user should be able to access specified resource objects via accurately generated named URL. This includes not only the object itself but also its related URLs, like if `/api/v2/res_name/obj_slug/` is valid, `/api/v2/res_name/obj_slug/related_res_name/` should also be valid.
|
||||
* A user should not be able to access specified resource objects if the given named URL is inaccurate. For example, reserved characters not correctly escaped, or components whose corresponding foreign key field pointing nowhere is not replaced by empty string.
|
||||
* A user should be able to dynamically generate named URLs by utilizing `NAMED_URL_GRAPH_NODES`.
|
||||
|
||||
151
tools/scripts/pk_to_named_url.py
Executable file
151
tools/scripts/pk_to_named_url.py
Executable file
@ -0,0 +1,151 @@
|
||||
#!/usr/bin/env python
|
||||
import argparse
|
||||
import six
|
||||
|
||||
import requests
|
||||
|
||||
|
||||
NAMED_URL_RES_DILIMITER = "--"
|
||||
NAMED_URL_RES_INNER_DILIMITER = "-"
|
||||
NAMED_URL_RES_DILIMITER_ENCODE = "%2D"
|
||||
URL_PATH_RESERVED_CHARSET = {}
|
||||
for c in ';/?:@=&[]':
|
||||
URL_PATH_RESERVED_CHARSET[c] = six.moves.urllib.parse.quote(c, safe='')
|
||||
|
||||
|
||||
def _get_named_url_graph(url, auth):
|
||||
"""Get the graph data structure Tower used to manage all named URLs.
|
||||
|
||||
Args:
|
||||
url: String representing the URL of tower configuration endpoint where
|
||||
to fetch graph information.
|
||||
auth: Tuple of username + password to authenticate connection to Tower.
|
||||
|
||||
Return:
|
||||
A dict of graph nodes that in ensembly represent the graph structure. Each
|
||||
node is represented as a dict of 'fields' and 'adj_list'.
|
||||
|
||||
Raises:
|
||||
N/A
|
||||
"""
|
||||
r = requests.get(url, auth=auth, verify=False)
|
||||
ret = r.json()['NAMED_URL_GRAPH_NODES']
|
||||
return ret
|
||||
|
||||
|
||||
def _encode_uri(text):
|
||||
"""Properly encode input text to make it satisfy named URL convention.
|
||||
|
||||
Args:
|
||||
text: the original string to be encoded.
|
||||
|
||||
Return:
|
||||
The encoded string
|
||||
|
||||
Raises:
|
||||
N/A
|
||||
"""
|
||||
for c in URL_PATH_RESERVED_CHARSET:
|
||||
if c in text:
|
||||
text = text.replace(c, URL_PATH_RESERVED_CHARSET[c])
|
||||
text = text.replace(NAMED_URL_RES_INNER_DILIMITER,
|
||||
'[%s]' % NAMED_URL_RES_INNER_DILIMITER)
|
||||
return text
|
||||
|
||||
|
||||
def _generate_identifier_component(response, fields):
|
||||
"""Generate an individual component of named URL identifier.
|
||||
|
||||
Args:
|
||||
response: JSON containing the details of a particular resource object.
|
||||
fields: name of resource object fields needed to generate a named URL
|
||||
identifier component.
|
||||
|
||||
Return:
|
||||
A string representing generated identifier component.
|
||||
|
||||
Raises:
|
||||
N/A
|
||||
"""
|
||||
ret = []
|
||||
for field_name in fields:
|
||||
ret.append(_encode_uri(response[field_name]))
|
||||
return NAMED_URL_RES_INNER_DILIMITER.join(ret)
|
||||
|
||||
|
||||
def _get_named_url_identifier(url, named_url_graph, resource, tower_host, auth, ret):
|
||||
"""DFS the named URL graph structure to generate identifier for a resource object.
|
||||
|
||||
Args:
|
||||
url: A string used to access a particular resource object to generate identifier
|
||||
component from.
|
||||
named_url_graph: The graph structure used to DFS against.
|
||||
resource: Key name of the current graph node.
|
||||
tower_host: String representing the host name of Tower backend.
|
||||
auth: Tuple of username + password to authenticate connection to Tower.
|
||||
ret: list of strings storing components that would later be joined into
|
||||
the final named URL identifier.
|
||||
|
||||
Return:
|
||||
None. Note the actual outcome is stored in argument ret due to the recursive
|
||||
nature of this function.
|
||||
|
||||
Raises:
|
||||
"""
|
||||
r = requests.get(url, auth=auth, verify=False).json()
|
||||
ret.append(_generate_identifier_component(r, named_url_graph[resource]['fields']))
|
||||
for next_ in named_url_graph[resource]['adj_list']:
|
||||
next_fk, next_res = tuple(next_)
|
||||
if next_fk in r['related']:
|
||||
_get_named_url_identifier(tower_host.strip('/') + r['related'][next_fk],
|
||||
named_url_graph, next_res, tower_host, auth, ret)
|
||||
else:
|
||||
ret.append('')
|
||||
|
||||
|
||||
def main(username=None, password=None, tower_host=None, resource=None, pk=None):
|
||||
"""Main function for generating and printing named URL of a resource object given its pk.
|
||||
|
||||
Args:
|
||||
username: String representing the username needed to authenticating Tower.
|
||||
password: String representing the password needed to authenticating Tower.
|
||||
tower_host: String representing the host name of Tower backend.
|
||||
resource: REST API name of a specific resource, e.g. name for resource inventory
|
||||
is 'inventories'.
|
||||
pk: Primary key of the resource object whose named URL will be derived.
|
||||
|
||||
Returns:
|
||||
None
|
||||
|
||||
Raises:
|
||||
N/A
|
||||
"""
|
||||
start_url = '%s/api/v2/%s/%s/' % (tower_host.strip('/'), resource.strip('/'), pk)
|
||||
conf_url = '%s/api/v2/settings/named-url/' % tower_host.strip('/')
|
||||
auth = (username, password)
|
||||
named_url_graph = _get_named_url_graph(conf_url, auth)
|
||||
named_url_identifier = []
|
||||
_get_named_url_identifier(start_url, named_url_graph, resource,
|
||||
tower_host, auth, named_url_identifier)
|
||||
print('%s/api/v2/%s/%s/' % (tower_host.strip('/'), resource.strip('/'),
|
||||
NAMED_URL_RES_DILIMITER.join(named_url_identifier)))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument('--username', type=str, required=True,
|
||||
help='Name of the Tower user for making requests',
|
||||
dest='username', metavar='STR')
|
||||
parser.add_argument('--password', type=str, required=True,
|
||||
help='Password of the Tower user for making requests',
|
||||
dest='password', metavar='STR')
|
||||
parser.add_argument('--tower-host', type=str, required=True,
|
||||
help='Tower host name, like "http://127.0.0.1"',
|
||||
dest='tower_host', metavar='STR')
|
||||
parser.add_argument('--resource', type=str, required=True,
|
||||
help='Name of the resource in REST endpoints',
|
||||
dest='resource', metavar='STR')
|
||||
parser.add_argument('--pk', type=int, required=True,
|
||||
help='Primary key of resource object whose named URL will be derived',
|
||||
dest='pk', metavar='INT')
|
||||
main(**vars(parser.parse_args()))
|
||||
Loading…
x
Reference in New Issue
Block a user