Remove the natural key lookup dict and make each Page responsible instead

This commit is contained in:
Jeff Bradberry 2020-03-30 16:07:12 -04:00
parent 07ba521b8b
commit e92c8cfdcc
16 changed files with 61 additions and 51 deletions

View File

@ -35,50 +35,6 @@ EXPORTABLE_DEPENDENT_OBJECTS = [
]
NATURAL_KEYS = {
'user': ('username',),
'organization': ('name',),
'team': ('organization', 'name'),
'credential_type': ('name', 'kind'),
'credential': ('organization', 'name', 'credential_type'),
'notification_template': ('organization', 'name'),
'project': ('organization', 'name'),
'inventory': ('organization', 'name'),
'job_template': ('organization', 'name'),
'workflow_job_template': ('organization', 'name'),
# related resources
'role': ('name', ':content_object'),
'notification_template': ('organization', 'name'),
'label': ('organization', 'name'), # FIXME: label will need to be fully constructed from this
'workflow_job_template_node': ('workflow_job_template', 'identifier'),
'schedule': ('unified_job_template', 'name'),
}
def get_natural_key(pg):
natural_key = {'type': pg['type']}
lookup = NATURAL_KEYS.get(pg['type'], ())
for key in lookup or ():
if key.startswith(':'):
# treat it like a special-case related object
related_objs = [
related for name, related in pg.related.items()
if name not in ('users', 'teams')
]
if related_objs:
natural_key[key[1:]] = get_natural_key(related_objs[0].get())
elif key in pg.related:
natural_key[key] = get_natural_key(pg.related[key].get())
elif key in pg:
natural_key[key] = pg[key]
if not natural_key:
return None
return natural_key
def freeze(key):
if key is None:
return None
@ -113,10 +69,11 @@ class ApiV2(base.Base):
key: asset.json[key] for key in options
if key in asset.json and key not in asset.related and key != 'id'
}
fields['natural_key'] = get_natural_key(asset)
fields['natural_key'] = asset.get_natural_key()
fk_fields = {
key: get_natural_key(asset.related[key].get()) for key in options
# FIXME: use caching by url
key: asset.related[key].get().get_natural_key() for key in options
if key in asset.related
}
@ -124,7 +81,7 @@ class ApiV2(base.Base):
for key, related_endpoint in asset.related.items():
if key in asset.json or not related_endpoint:
continue
if key == 'object_roles':
if key == 'object_roles': # FIXME
continue
rel = related_endpoint._create()
@ -139,7 +96,7 @@ class ApiV2(base.Base):
if 'results' in data:
related_options = self._get_options(related_endpoint)
related[key] = [
get_natural_key(x) if by_natural_key else self._serialize_asset(x, related_options)
x.get_natural_key() if by_natural_key else self._serialize_asset(x, related_options)
for x in data.results
]
else:
@ -190,7 +147,7 @@ class ApiV2(base.Base):
yield page_resource[page_cls]
def _register_page(self, page):
natural_key = freeze(get_natural_key(page))
natural_key = freeze(page.get_natural_key())
# FIXME: we need to keep a reference for the case where we
# don't have a natural key, so we can delete
if natural_key is not None:

View File

@ -149,6 +149,8 @@ def get_payload_field_and_value_from_kwargs_or_config_cred(
class CredentialType(HasCreate, base.Base):
NATURAL_KEY = ('name', 'kind')
def silent_delete(self):
if not self.managed_by_tower:
return super(CredentialType, self).silent_delete()
@ -204,6 +206,7 @@ class Credential(HasCopy, HasCreate, base.Base):
dependencies = [CredentialType]
optional_dependencies = [Organization, User, Team]
NATURAL_KEY = ('organization', 'name', 'credential_type')
def payload(
self,

View File

@ -32,6 +32,7 @@ log = logging.getLogger(__name__)
class Inventory(HasCopy, HasCreate, HasInstanceGroups, HasVariables, base.Base):
dependencies = [Organization]
NATURAL_KEY = ('organization', 'name')
def print_ini(self):
"""Print an ini version of the inventory"""

View File

@ -24,6 +24,7 @@ class JobTemplate(
UnifiedJobTemplate):
optional_dependencies = [Inventory, Credential, Project]
NATURAL_KEY = ('organization', 'name')
def launch(self, payload={}):
"""Launch the job_template using related->launch endpoint."""

View File

@ -9,6 +9,7 @@ from . import page
class Label(HasCreate, base.Base):
dependencies = [Organization]
NATURAL_KEY = ('organization', 'name')
def silent_delete(self):
"""Label pages do not support DELETE requests. Here, we override the base page object

View File

@ -24,6 +24,7 @@ notification_types = (
class NotificationTemplate(HasCopy, HasCreate, base.Base):
dependencies = [Organization]
NATURAL_KEY = ('organization', 'name')
def test(self):
"""Create test notification"""

View File

@ -8,6 +8,8 @@ from . import page
class Organization(HasCreate, HasInstanceGroups, HasNotifications, base.Base):
NATURAL_KEY = ('name',)
def add_admin(self, user):
if isinstance(user, page.Page):
user = user.json

View File

@ -317,6 +317,24 @@ class Page(object):
page_cls = get_registered_page(endpoint)
return page_cls(self.connection, endpoint=endpoint).get(**kw)
def get_natural_key(self):
if not getattr(self, 'NATURAL_KEY', None):
raise exc.NoNaturalKey(
"Page does not have a natural key: {}".format(getattr(self, 'endpoint', repr(self.__class__)))
)
natural_key = {}
for key in self.NATURAL_KEY:
if key in self.related:
# FIXME: use caching by url
natural_key[key] = self.related[key].get().get_natural_key()
elif key in self:
natural_key[key] = self[key]
if not natural_key:
return None
natural_key['type'] = self['type']
return natural_key
_exception_map = {http.NO_CONTENT: exc.NoContent,
http.NOT_FOUND: exc.NotFound,
@ -376,6 +394,9 @@ class PageList(object):
def create(self, *a, **kw):
return self.__item_class__(self.connection).create(*a, **kw)
def get_natural_key(self):
raise exc.NoNaturalKey
class TentativePage(str):

View File

@ -14,6 +14,7 @@ class Project(HasCopy, HasCreate, HasNotifications, UnifiedJobTemplate):
optional_dependencies = [Credential, Organization]
optional_schedule_fields = tuple()
NATURAL_KEY = ('organization', 'name')
def payload(self, organization, scm_type='git', **kwargs):
payload = PseudoNamespace(

View File

@ -5,7 +5,19 @@ from . import page
class Role(base.Base):
pass
NATURAL_KEY = ('name',)
def get_natural_key(self):
natural_key = super(Role, self).get_natural_key()
related_objs = [
related for name, related in self.related.items()
if name not in ('users', 'teams')
]
if related_objs:
# FIXME: use caching by url
natural_key['content_object'] = related_objs[0].get().get_natural_key()
return natural_key
page.register_page(resources.role, Role)

View File

@ -8,7 +8,7 @@ from . import base
class Schedule(UnifiedJob):
pass
NATURAL_KEY = ('unified_job_template', 'name')
page.register_page([resources.schedule,

View File

@ -11,6 +11,7 @@ from . import page
class Team(HasCreate, base.Base):
dependencies = [Organization]
NATURAL_KEY = ('organization', 'name')
def add_user(self, user):
if isinstance(user, page.Page):

View File

@ -9,6 +9,8 @@ from . import page
class User(HasCreate, base.Base):
NATURAL_KEY = ('username',)
def payload(self, **kwargs):
payload = PseudoNamespace(
username=kwargs.get('username') or 'User-{}'.format(

View File

@ -10,6 +10,7 @@ from . import page
class WorkflowJobTemplateNode(HasCreate, base.Base):
dependencies = [WorkflowJobTemplate, UnifiedJobTemplate]
NATURAL_KEY = ('workflow_job_template', 'identifier')
def payload(self, workflow_job_template, unified_job_template, **kwargs):
if not unified_job_template:

View File

@ -13,6 +13,7 @@ from . import page
class WorkflowJobTemplate(HasCopy, HasCreate, HasNotifications, HasSurvey, UnifiedJobTemplate):
optional_dependencies = [Organization]
NATURAL_KEY = ('organization', 'name')
def launch(self, payload={}):
"""Launch using related->launch endpoint."""

View File

@ -101,3 +101,8 @@ class UnexpectedAWXState(Common):
class IsMigrating(Common):
pass
class NoNaturalKey(Common):
pass