mirror of
https://github.com/ansible/awx.git
synced 2026-02-24 14:36:00 -03:30
move code linting to a stricter pep8-esque auto-formatting tool, black
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
from awxkit.api import pages, client, resources # NOQA
|
||||
from awxkit.config import config # NOQA
|
||||
from awxkit import awx # NOQA
|
||||
from awxkit.ws import WSClient # NOQA
|
||||
from awxkit.api import pages, client, resources # NOQA
|
||||
from awxkit.config import config # NOQA
|
||||
from awxkit import awx # NOQA
|
||||
from awxkit.ws import WSClient # NOQA
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
from .pages import * # NOQA
|
||||
from .client import * # NOQA
|
||||
from .pages import * # NOQA
|
||||
from .client import * # NOQA
|
||||
|
||||
@@ -49,8 +49,7 @@ class Connection(object):
|
||||
_next = kwargs.get('next')
|
||||
if _next:
|
||||
headers = self.session.headers.copy()
|
||||
self.post('/api/login/', headers=headers,
|
||||
data=dict(username=username, password=password, next=_next))
|
||||
self.post('/api/login/', headers=headers, data=dict(username=username, password=password, next=_next))
|
||||
self.session_id = self.session.cookies.get('sessionid')
|
||||
self.uses_session_cookie = True
|
||||
else:
|
||||
@@ -79,8 +78,7 @@ class Connection(object):
|
||||
use_endpoint = use_endpoint[1:]
|
||||
url = '/'.join([self.server, use_endpoint])
|
||||
|
||||
kwargs = dict(verify=self.verify, params=query_parameters, json=json, data=data,
|
||||
hooks=dict(response=log_elapsed))
|
||||
kwargs = dict(verify=self.verify, params=query_parameters, json=json, data=data, hooks=dict(response=log_elapsed))
|
||||
|
||||
if headers is not None:
|
||||
kwargs['headers'] = headers
|
||||
|
||||
@@ -3,7 +3,6 @@ from awxkit.utils import random_title
|
||||
|
||||
|
||||
class HasCopy(object):
|
||||
|
||||
def can_copy(self):
|
||||
return self.get_related('copy').can_copy
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@ def dependency_graph(page, *provided_dependencies):
|
||||
return graph
|
||||
|
||||
|
||||
def optional_dependency_graph(page, *provided_dependencies):
|
||||
def optional_dependency_graph(page, *provided_dependencies):
|
||||
"""Creates a dependency graph for a page including all dependencies and optional_dependencies
|
||||
Any optional provided_dependencies will be included as if they were dependencies,
|
||||
without affecting the value of each keyed page.
|
||||
@@ -104,8 +104,7 @@ def all_instantiated_dependencies(*potential_parents):
|
||||
"""
|
||||
scope_provided_dependencies = []
|
||||
|
||||
instantiated = set([x for x in potential_parents
|
||||
if not isinstance(x, type) and not isinstance(x, tuple)])
|
||||
instantiated = set([x for x in potential_parents if not isinstance(x, type) and not isinstance(x, tuple)])
|
||||
|
||||
for potential_parent in [x for x in instantiated if hasattr(x, '_dependency_store')]:
|
||||
for dependency in potential_parent._dependency_store.values():
|
||||
@@ -178,7 +177,6 @@ class DSAdapter(object):
|
||||
# Hijack json.dumps and simplejson.dumps (used by requests)
|
||||
# to allow HasCreate.create_payload() serialization without impacting payload.ds access
|
||||
def filter_ds_from_payload(dumps):
|
||||
|
||||
def _filter_ds_from_payload(obj, *a, **kw):
|
||||
if hasattr(obj, 'get') and isinstance(obj.get('ds'), DSAdapter):
|
||||
filtered = obj.copy()
|
||||
@@ -191,10 +189,12 @@ def filter_ds_from_payload(dumps):
|
||||
|
||||
|
||||
import json # noqa
|
||||
|
||||
json.dumps = filter_ds_from_payload(json.dumps)
|
||||
|
||||
try:
|
||||
import simplejson # noqa
|
||||
|
||||
simplejson.dumps = filter_ds_from_payload(simplejson.dumps)
|
||||
except ImportError:
|
||||
pass
|
||||
@@ -299,8 +299,7 @@ class HasCreate(object):
|
||||
# remove falsy values
|
||||
provided_and_desired_dependencies = [x for x in provided_and_desired_dependencies if x]
|
||||
# (HasCreate(), True) tells HasCreate._update_dependencies to link
|
||||
provided_dependencies = [(x, True) for x in provided_and_desired_dependencies
|
||||
if not isinstance(x, type) and not isinstance(x, tuple)]
|
||||
provided_dependencies = [(x, True) for x in provided_and_desired_dependencies if not isinstance(x, type) and not isinstance(x, tuple)]
|
||||
|
||||
# Since dependencies are often declared at runtime, we need to use some introspection
|
||||
# to determine previously created ones for proper dependency store linking.
|
||||
@@ -374,12 +373,7 @@ class HasCreate(object):
|
||||
to_teardown = all_instantiated_dependencies(self)
|
||||
to_teardown_types = set(map(get_class_if_instance, to_teardown))
|
||||
order = [
|
||||
set(
|
||||
[
|
||||
potential for potential in (
|
||||
get_class_if_instance(x) for x in group) if potential in to_teardown_types
|
||||
]
|
||||
)
|
||||
set([potential for potential in (get_class_if_instance(x) for x in group) if potential in to_teardown_types])
|
||||
for group in page_creation_order(self, *to_teardown)
|
||||
]
|
||||
order.reverse()
|
||||
|
||||
@@ -3,7 +3,6 @@ import awxkit.exceptions as exc
|
||||
|
||||
|
||||
class HasInstanceGroups(object):
|
||||
|
||||
def add_instance_group(self, instance_group):
|
||||
with suppress(exc.NoContent):
|
||||
self.related['instance_groups'].post(dict(id=instance_group.id))
|
||||
|
||||
@@ -2,29 +2,25 @@ from awxkit.utils import suppress
|
||||
import awxkit.exceptions as exc
|
||||
|
||||
|
||||
notification_endpoints = ("notification_templates", "notification_templates_started", "notification_templates_error",
|
||||
"notification_templates_success")
|
||||
notification_endpoints = ("notification_templates", "notification_templates_started", "notification_templates_error", "notification_templates_success")
|
||||
wfjt_notification_endpoints = notification_endpoints + ('notification_templates_approvals',)
|
||||
|
||||
|
||||
class HasNotifications(object):
|
||||
|
||||
def add_notification_template(self, notification_template, endpoint="notification_templates_success"):
|
||||
from awxkit.api.pages.workflow_job_templates import WorkflowJobTemplate
|
||||
supported_endpoints = wfjt_notification_endpoints if isinstance(self, WorkflowJobTemplate) \
|
||||
else notification_endpoints
|
||||
|
||||
supported_endpoints = wfjt_notification_endpoints if isinstance(self, WorkflowJobTemplate) else notification_endpoints
|
||||
if endpoint not in supported_endpoints:
|
||||
raise ValueError('Unsupported notification endpoint "{0}". Please use one of {1}.'
|
||||
.format(endpoint, notification_endpoints))
|
||||
raise ValueError('Unsupported notification endpoint "{0}". Please use one of {1}.'.format(endpoint, notification_endpoints))
|
||||
with suppress(exc.NoContent):
|
||||
self.related[endpoint].post(dict(id=notification_template.id))
|
||||
|
||||
def remove_notification_template(self, notification_template, endpoint="notification_templates_success"):
|
||||
from awxkit.api.pages.workflow_job_templates import WorkflowJobTemplate
|
||||
supported_endpoints = wfjt_notification_endpoints if isinstance(self, WorkflowJobTemplate) \
|
||||
else notification_endpoints
|
||||
|
||||
supported_endpoints = wfjt_notification_endpoints if isinstance(self, WorkflowJobTemplate) else notification_endpoints
|
||||
if endpoint not in supported_endpoints:
|
||||
raise ValueError('Unsupported notification endpoint "{0}". Please use one of {1}.'
|
||||
.format(endpoint, notification_endpoints))
|
||||
raise ValueError('Unsupported notification endpoint "{0}". Please use one of {1}.'.format(endpoint, notification_endpoints))
|
||||
with suppress(exc.NoContent):
|
||||
self.related[endpoint].post(dict(id=notification_template.id, disassociate=notification_template.id))
|
||||
|
||||
@@ -40,8 +40,7 @@ class HasStatus(object):
|
||||
if not getattr(self, 'event_processing_finished', True):
|
||||
elapsed = datetime.utcnow() - start_time
|
||||
time_left = timeout - elapsed.total_seconds()
|
||||
poll_until(lambda: getattr(self.get(), 'event_processing_finished', True),
|
||||
interval=interval, timeout=time_left, **kwargs)
|
||||
poll_until(lambda: getattr(self.get(), 'event_processing_finished', True), interval=interval, timeout=time_left, **kwargs)
|
||||
return self
|
||||
|
||||
def wait_until_started(self, interval=1, timeout=60):
|
||||
@@ -65,9 +64,7 @@ class HasStatus(object):
|
||||
msg = ''
|
||||
else:
|
||||
msg += '\n'
|
||||
msg += '{0}-{1} has status of {2}, which is not in {3}.'.format(
|
||||
self.type.title(), self.id, self.status, status_list
|
||||
)
|
||||
msg += '{0}-{1} has status of {2}, which is not in {3}.'.format(self.type.title(), self.id, self.status, status_list)
|
||||
if getattr(self, 'job_explanation', ''):
|
||||
msg += '\njob_explanation: {}'.format(bytes_to_str(self.job_explanation))
|
||||
if getattr(self, 'result_traceback', ''):
|
||||
@@ -79,10 +76,8 @@ class HasStatus(object):
|
||||
try:
|
||||
data = json.loads(self.job_explanation.replace('Previous Task Failed: ', ''))
|
||||
dep_output = self.connection.get(
|
||||
'{0}/api/v2/{1}s/{2}/stdout/'.format(
|
||||
self.endpoint.split('/api')[0], data['job_type'], data['job_id']
|
||||
),
|
||||
query_parameters=dict(format='txt_download')
|
||||
'{0}/api/v2/{1}s/{2}/stdout/'.format(self.endpoint.split('/api')[0], data['job_type'], data['job_id']),
|
||||
query_parameters=dict(format='txt_download'),
|
||||
).content
|
||||
msg += '\nDependency output:\n{}'.format(bytes_to_str(dep_output))
|
||||
except Exception as e:
|
||||
|
||||
@@ -3,13 +3,11 @@ from awxkit.utils import random_title
|
||||
|
||||
class HasSurvey(object):
|
||||
def add_survey(self, spec=None, name=None, description=None, required=False, enabled=True):
|
||||
payload = dict(name=name or 'Survey - {}'.format(random_title()),
|
||||
description=description or random_title(10),
|
||||
spec=spec or [dict(required=required,
|
||||
question_name="What's the password?",
|
||||
variable="secret",
|
||||
type="password",
|
||||
default="foo")])
|
||||
payload = dict(
|
||||
name=name or 'Survey - {}'.format(random_title()),
|
||||
description=description or random_title(10),
|
||||
spec=spec or [dict(required=required, question_name="What's the password?", variable="secret", type="password", default="foo")],
|
||||
)
|
||||
if enabled != self.survey_enabled:
|
||||
self.patch(survey_enabled=enabled)
|
||||
return self.related.survey_spec.post(payload).get()
|
||||
|
||||
@@ -4,7 +4,6 @@ from awxkit.utils import PseudoNamespace
|
||||
|
||||
|
||||
class HasVariables(object):
|
||||
|
||||
@property
|
||||
def variables(self):
|
||||
return PseudoNamespace(yaml.safe_load(self.json.variables))
|
||||
|
||||
@@ -33,7 +33,7 @@ from .workflow_job_templates import * # NOQA
|
||||
from .workflow_job_template_nodes import * # NOQA
|
||||
from .workflow_jobs import * # NOQA
|
||||
from .workflow_job_nodes import * # NOQA
|
||||
from .workflow_approvals import * # NOQA
|
||||
from .workflow_approvals import * # NOQA
|
||||
from .settings import * # NOQA
|
||||
from .instances import * # NOQA
|
||||
from .instance_groups import * # NOQA
|
||||
|
||||
@@ -8,11 +8,16 @@ class AccessList(page.PageList, users.User):
|
||||
pass
|
||||
|
||||
|
||||
page.register_page([resources.organization_access_list,
|
||||
resources.user_access_list,
|
||||
resources.inventory_access_list,
|
||||
resources.group_access_list,
|
||||
resources.credential_access_list,
|
||||
resources.project_access_list,
|
||||
resources.job_template_access_list,
|
||||
resources.team_access_list], AccessList)
|
||||
page.register_page(
|
||||
[
|
||||
resources.organization_access_list,
|
||||
resources.user_access_list,
|
||||
resources.inventory_access_list,
|
||||
resources.group_access_list,
|
||||
resources.credential_access_list,
|
||||
resources.project_access_list,
|
||||
resources.job_template_access_list,
|
||||
resources.team_access_list,
|
||||
],
|
||||
AccessList,
|
||||
)
|
||||
|
||||
@@ -16,5 +16,4 @@ class ActivityStreams(page.PageList, ActivityStream):
|
||||
pass
|
||||
|
||||
|
||||
page.register_page([resources.activity_stream,
|
||||
resources.object_activity_stream], ActivityStreams)
|
||||
page.register_page([resources.activity_stream, resources.object_activity_stream], ActivityStreams)
|
||||
|
||||
@@ -24,31 +24,40 @@ class AdHocCommand(HasCreate, UnifiedJob):
|
||||
return self.walk(result.url)
|
||||
|
||||
def payload(self, inventory, credential, module_name='ping', **kwargs):
|
||||
payload = PseudoNamespace(inventory=inventory.id,
|
||||
credential=credential.id,
|
||||
module_name=module_name)
|
||||
payload = PseudoNamespace(inventory=inventory.id, credential=credential.id, module_name=module_name)
|
||||
|
||||
optional_fields = ('diff_mode', 'extra_vars', 'module_args', 'job_type', 'limit', 'forks',
|
||||
'verbosity')
|
||||
optional_fields = ('diff_mode', 'extra_vars', 'module_args', 'job_type', 'limit', 'forks', 'verbosity')
|
||||
return update_payload(payload, optional_fields, kwargs)
|
||||
|
||||
def create_payload(self, module_name='ping', module_args=np, job_type=np, limit=np, verbosity=np,
|
||||
inventory=Inventory, credential=Credential, **kwargs):
|
||||
def create_payload(self, module_name='ping', module_args=np, job_type=np, limit=np, verbosity=np, inventory=Inventory, credential=Credential, **kwargs):
|
||||
|
||||
self.create_and_update_dependencies(inventory, credential)
|
||||
|
||||
payload = self.payload(module_name=module_name, module_args=module_args, job_type=job_type, limit=limit,
|
||||
verbosity=verbosity, inventory=self.ds.inventory, credential=self.ds.credential,
|
||||
**kwargs)
|
||||
payload = self.payload(
|
||||
module_name=module_name,
|
||||
module_args=module_args,
|
||||
job_type=job_type,
|
||||
limit=limit,
|
||||
verbosity=verbosity,
|
||||
inventory=self.ds.inventory,
|
||||
credential=self.ds.credential,
|
||||
**kwargs
|
||||
)
|
||||
payload.ds = DSAdapter(self.__class__.__name__, self._dependency_store)
|
||||
return payload
|
||||
|
||||
def create(self, module_name='ping', module_args=np, job_type=np, limit=np, verbosity=np,
|
||||
inventory=Inventory, credential=Credential, **kwargs):
|
||||
def create(self, module_name='ping', module_args=np, job_type=np, limit=np, verbosity=np, inventory=Inventory, credential=Credential, **kwargs):
|
||||
|
||||
payload = self.create_payload(module_name=module_name, module_args=module_args,
|
||||
job_type=job_type, limit=limit, verbosity=verbosity,
|
||||
inventory=inventory, credential=credential, **kwargs)
|
||||
payload = self.create_payload(
|
||||
module_name=module_name,
|
||||
module_args=module_args,
|
||||
job_type=job_type,
|
||||
limit=limit,
|
||||
verbosity=verbosity,
|
||||
inventory=inventory,
|
||||
credential=credential,
|
||||
**kwargs
|
||||
)
|
||||
return self.update_identity(AdHocCommands(self.connection).post(payload))
|
||||
|
||||
|
||||
@@ -60,7 +69,7 @@ class AdHocCommands(page.PageList, AdHocCommand):
|
||||
pass
|
||||
|
||||
|
||||
page.register_page([resources.ad_hoc_commands,
|
||||
resources.inventory_related_ad_hoc_commands,
|
||||
resources.group_related_ad_hoc_commands,
|
||||
resources.host_related_ad_hoc_commands], AdHocCommands)
|
||||
page.register_page(
|
||||
[resources.ad_hoc_commands, resources.inventory_related_ad_hoc_commands, resources.group_related_ad_hoc_commands, resources.host_related_ad_hoc_commands],
|
||||
AdHocCommands,
|
||||
)
|
||||
|
||||
@@ -90,18 +90,14 @@ class ApiV2(base.Base):
|
||||
return None
|
||||
|
||||
# Note: doing _page[key] automatically parses json blob strings, which can be a problem.
|
||||
fields = {
|
||||
key: _page.json[key] for key in post_fields
|
||||
if key in _page.json and key not in _page.related and key != 'id'
|
||||
}
|
||||
fields = {key: _page.json[key] for key in post_fields if key in _page.json and key not in _page.related and key != 'id'}
|
||||
|
||||
for key in post_fields:
|
||||
if key in _page.related:
|
||||
related = _page.related[key]
|
||||
else:
|
||||
if post_fields[key]['type'] == 'id' and _page.json.get(key) is not None:
|
||||
log.warning("Related link %r missing from %s, attempting to reconstruct endpoint.",
|
||||
key, _page.endpoint)
|
||||
log.warning("Related link %r missing from %s, attempting to reconstruct endpoint.", key, _page.endpoint)
|
||||
resource = getattr(self, key, None)
|
||||
if resource is None:
|
||||
log.error("Unable to infer endpoint for %r on %s.", key, _page.endpoint)
|
||||
@@ -119,8 +115,7 @@ class ApiV2(base.Base):
|
||||
continue
|
||||
rel_natural_key = rel_endpoint.get_natural_key(self._cache)
|
||||
if rel_natural_key is None:
|
||||
log.error("Unable to construct a natural key for foreign key %r of object %s.",
|
||||
key, _page.endpoint)
|
||||
log.error("Unable to construct a natural key for foreign key %r of object %s.", key, _page.endpoint)
|
||||
return None # This foreign key has unresolvable dependencies
|
||||
fields[key] = rel_natural_key
|
||||
|
||||
@@ -154,10 +149,7 @@ class ApiV2(base.Base):
|
||||
continue
|
||||
|
||||
if 'results' in rel_page:
|
||||
results = (
|
||||
x.get_natural_key(self._cache) if by_natural_key else self._export(x, rel_post_fields)
|
||||
for x in rel_page.results
|
||||
)
|
||||
results = (x.get_natural_key(self._cache) if by_natural_key else self._export(x, rel_post_fields) for x in rel_page.results)
|
||||
related[key] = [x for x in results if x is not None]
|
||||
else:
|
||||
related[key] = rel_page.json
|
||||
@@ -190,8 +182,7 @@ class ApiV2(base.Base):
|
||||
if isinstance(value, int) or value.isdecimal():
|
||||
return endpoint.get(id=int(value))
|
||||
options = self._cache.get_options(endpoint)
|
||||
identifier = next(field for field in options['search_fields']
|
||||
if field in ('name', 'username', 'hostname'))
|
||||
identifier = next(field for field in options['search_fields'] if field in ('name', 'username', 'hostname'))
|
||||
return endpoint.get(**{identifier: value})
|
||||
|
||||
def export_assets(self, **kwargs):
|
||||
@@ -214,8 +205,7 @@ class ApiV2(base.Base):
|
||||
# Import methods
|
||||
|
||||
def _dependent_resources(self, data):
|
||||
page_resource = {getattr(self, resource)._create().__item_class__: resource
|
||||
for resource in self.json}
|
||||
page_resource = {getattr(self, resource)._create().__item_class__: resource for resource in self.json}
|
||||
data_pages = [getattr(self, resource)._create().__item_class__ for resource in EXPORTABLE_RESOURCES]
|
||||
|
||||
for page_cls in itertools.chain(*has_create.page_creation_order(*data_pages)):
|
||||
|
||||
@@ -12,10 +12,12 @@ class OAuth2Application(HasCreate, base.Base):
|
||||
dependencies = [Organization]
|
||||
|
||||
def payload(self, **kwargs):
|
||||
payload = PseudoNamespace(name=kwargs.get('name') or 'OAuth2Application - {}'.format(random_title()),
|
||||
description=kwargs.get('description') or random_title(10),
|
||||
client_type=kwargs.get('client_type', 'public'),
|
||||
authorization_grant_type=kwargs.get('authorization_grant_type', 'password'))
|
||||
payload = PseudoNamespace(
|
||||
name=kwargs.get('name') or 'OAuth2Application - {}'.format(random_title()),
|
||||
description=kwargs.get('description') or random_title(10),
|
||||
client_type=kwargs.get('client_type', 'public'),
|
||||
authorization_grant_type=kwargs.get('authorization_grant_type', 'password'),
|
||||
)
|
||||
if kwargs.get('organization'):
|
||||
payload.organization = kwargs['organization'].id
|
||||
|
||||
@@ -35,8 +37,7 @@ class OAuth2Application(HasCreate, base.Base):
|
||||
return self.update_identity(OAuth2Applications(self.connection).post(payload))
|
||||
|
||||
|
||||
page.register_page((resources.application,
|
||||
(resources.applications, 'post')), OAuth2Application)
|
||||
page.register_page((resources.application, (resources.applications, 'post')), OAuth2Application)
|
||||
|
||||
|
||||
class OAuth2Applications(page.PageList, OAuth2Application):
|
||||
@@ -51,8 +52,7 @@ class OAuth2AccessToken(HasCreate, base.Base):
|
||||
optional_dependencies = [OAuth2Application]
|
||||
|
||||
def payload(self, **kwargs):
|
||||
payload = PseudoNamespace(description=kwargs.get('description') or random_title(10),
|
||||
scope=kwargs.get('scope', 'write'))
|
||||
payload = PseudoNamespace(description=kwargs.get('description') or random_title(10), scope=kwargs.get('scope', 'write'))
|
||||
|
||||
if kwargs.get('oauth_2_application'):
|
||||
payload.application = kwargs['oauth_2_application'].id
|
||||
@@ -73,8 +73,7 @@ class OAuth2AccessToken(HasCreate, base.Base):
|
||||
return self.update_identity(OAuth2AccessTokens(self.connection).post(payload))
|
||||
|
||||
|
||||
page.register_page((resources.token,
|
||||
(resources.tokens, 'post')), OAuth2AccessToken)
|
||||
page.register_page((resources.token, (resources.tokens, 'post')), OAuth2AccessToken)
|
||||
|
||||
|
||||
class OAuth2AccessTokens(page.PageList, OAuth2AccessToken):
|
||||
|
||||
@@ -3,11 +3,7 @@ import logging
|
||||
|
||||
from requests.auth import HTTPBasicAuth
|
||||
|
||||
from awxkit.api.pages import (
|
||||
Page,
|
||||
get_registered_page,
|
||||
exception_from_status_code
|
||||
)
|
||||
from awxkit.api.pages import Page, get_registered_page, exception_from_status_code
|
||||
from awxkit.config import config
|
||||
from awxkit.api.resources import resources
|
||||
import awxkit.exceptions as exc
|
||||
@@ -17,7 +13,6 @@ log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class Base(Page):
|
||||
|
||||
def silent_delete(self):
|
||||
"""Delete the object. If it's already deleted, ignore the error"""
|
||||
try:
|
||||
@@ -129,14 +124,14 @@ class Base(Page):
|
||||
@property
|
||||
def object_roles(self):
|
||||
from awxkit.api.pages import Roles, Role
|
||||
|
||||
url = self.get().json.related.object_roles
|
||||
for obj_role in Roles(self.connection, endpoint=url).get().json.results:
|
||||
yield Role(self.connection, endpoint=obj_role.url).get()
|
||||
|
||||
def get_authtoken(self, username='', password=''):
|
||||
default_cred = config.credentials.default
|
||||
payload = dict(username=username or default_cred.username,
|
||||
password=password or default_cred.password)
|
||||
payload = dict(username=username or default_cred.username, password=password or default_cred.password)
|
||||
auth_url = resources.authtoken
|
||||
return get_registered_page(auth_url)(self.connection, endpoint=auth_url).post(payload).token
|
||||
|
||||
@@ -146,9 +141,7 @@ class Base(Page):
|
||||
|
||||
load_default_authtoken = load_authtoken
|
||||
|
||||
def get_oauth2_token(self, username='', password='', client_id=None,
|
||||
description='AWX CLI',
|
||||
client_secret=None, scope='write'):
|
||||
def get_oauth2_token(self, username='', password='', client_id=None, description='AWX CLI', client_secret=None, scope='write'):
|
||||
default_cred = config.credentials.default
|
||||
username = username or default_cred.username
|
||||
password = password or default_cred.password
|
||||
@@ -157,38 +150,21 @@ class Base(Page):
|
||||
HTTPBasicAuth(client_id, client_secret)(req)
|
||||
req.headers['Content-Type'] = 'application/x-www-form-urlencoded'
|
||||
resp = self.connection.post(
|
||||
'/api/o/token/',
|
||||
data={
|
||||
"grant_type": "password",
|
||||
"username": username,
|
||||
"password": password,
|
||||
"scope": scope
|
||||
},
|
||||
headers=req.headers
|
||||
'/api/o/token/', data={"grant_type": "password", "username": username, "password": password, "scope": scope}, headers=req.headers
|
||||
)
|
||||
elif client_id:
|
||||
req.headers['Content-Type'] = 'application/x-www-form-urlencoded'
|
||||
resp = self.connection.post(
|
||||
'/api/o/token/',
|
||||
data={
|
||||
"grant_type": "password",
|
||||
"username": username,
|
||||
"password": password,
|
||||
"client_id": client_id,
|
||||
"scope": scope
|
||||
},
|
||||
headers=req.headers
|
||||
data={"grant_type": "password", "username": username, "password": password, "client_id": client_id, "scope": scope},
|
||||
headers=req.headers,
|
||||
)
|
||||
else:
|
||||
HTTPBasicAuth(username, password)(req)
|
||||
resp = self.connection.post(
|
||||
'/api/v2/users/{}/personal_tokens/'.format(username),
|
||||
json={
|
||||
"description": description,
|
||||
"application": None,
|
||||
"scope": scope
|
||||
},
|
||||
headers=req.headers
|
||||
json={"description": description, "application": None, "scope": scope},
|
||||
headers=req.headers,
|
||||
)
|
||||
if resp.ok:
|
||||
result = resp.json()
|
||||
@@ -201,9 +177,9 @@ class Base(Page):
|
||||
|
||||
def load_session(self, username='', password=''):
|
||||
default_cred = config.credentials.default
|
||||
self.connection.login(username=username or default_cred.username,
|
||||
password=password or default_cred.password,
|
||||
**self.connection.get_session_requirements())
|
||||
self.connection.login(
|
||||
username=username or default_cred.username, password=password or default_cred.password, **self.connection.get_session_requirements()
|
||||
)
|
||||
return self
|
||||
|
||||
def cleanup(self):
|
||||
|
||||
@@ -4,22 +4,17 @@ from . import page
|
||||
|
||||
|
||||
class Config(base.Base):
|
||||
|
||||
@property
|
||||
def is_aws_license(self):
|
||||
return self.license_info.get('is_aws', False) or \
|
||||
'ami-id' in self.license_info or \
|
||||
'instance-id' in self.license_info
|
||||
return self.license_info.get('is_aws', False) or 'ami-id' in self.license_info or 'instance-id' in self.license_info
|
||||
|
||||
@property
|
||||
def is_valid_license(self):
|
||||
return self.license_info.get('valid_key', False) and \
|
||||
'instance_count' in self.license_info
|
||||
return self.license_info.get('valid_key', False) and 'instance_count' in self.license_info
|
||||
|
||||
@property
|
||||
def is_trial_license(self):
|
||||
return self.is_valid_license and \
|
||||
self.license_info.get('trial', False)
|
||||
return self.is_valid_license and self.license_info.get('trial', False)
|
||||
|
||||
@property
|
||||
def is_awx_license(self):
|
||||
@@ -27,8 +22,7 @@ class Config(base.Base):
|
||||
|
||||
@property
|
||||
def is_enterprise_license(self):
|
||||
return self.is_valid_license and \
|
||||
self.license_info.get('license_type', None) == 'enterprise'
|
||||
return self.is_valid_license and self.license_info.get('license_type', None) == 'enterprise'
|
||||
|
||||
@property
|
||||
def features(self):
|
||||
@@ -37,7 +31,6 @@ class Config(base.Base):
|
||||
|
||||
|
||||
class ConfigAttach(page.Page):
|
||||
|
||||
def attach(self, **kwargs):
|
||||
return self.post(json=kwargs).json
|
||||
|
||||
|
||||
@@ -16,5 +16,4 @@ class CredentialInputSources(page.PageList, CredentialInputSource):
|
||||
pass
|
||||
|
||||
|
||||
page.register_page([resources.credential_input_sources,
|
||||
resources.related_input_sources], CredentialInputSources)
|
||||
page.register_page([resources.credential_input_sources, resources.related_input_sources], CredentialInputSources)
|
||||
|
||||
@@ -44,7 +44,8 @@ credential_input_fields = (
|
||||
'tenant',
|
||||
'username',
|
||||
'vault_password',
|
||||
'vault_id')
|
||||
'vault_id',
|
||||
)
|
||||
|
||||
|
||||
def generate_private_key():
|
||||
@@ -52,15 +53,9 @@ def generate_private_key():
|
||||
from cryptography.hazmat.primitives import serialization
|
||||
from cryptography.hazmat.primitives.asymmetric import rsa
|
||||
|
||||
key = rsa.generate_private_key(
|
||||
public_exponent=65537,
|
||||
key_size=4096,
|
||||
backend=default_backend()
|
||||
)
|
||||
key = rsa.generate_private_key(public_exponent=65537, key_size=4096, backend=default_backend())
|
||||
return key.private_bytes(
|
||||
encoding=serialization.Encoding.PEM,
|
||||
format=serialization.PrivateFormat.TraditionalOpenSSL,
|
||||
encryption_algorithm=serialization.NoEncryption()
|
||||
encoding=serialization.Encoding.PEM, format=serialization.PrivateFormat.TraditionalOpenSSL, encryption_algorithm=serialization.NoEncryption()
|
||||
).decode('utf-8')
|
||||
|
||||
|
||||
@@ -98,11 +93,10 @@ credential_type_name_to_config_kind_map = {
|
||||
'source control': 'scm',
|
||||
'machine': 'ssh',
|
||||
'vault': 'vault',
|
||||
'vmware vcenter': 'vmware'}
|
||||
'vmware vcenter': 'vmware',
|
||||
}
|
||||
|
||||
config_kind_to_credential_type_name_map = {
|
||||
kind: name
|
||||
for name, kind in credential_type_name_to_config_kind_map.items()}
|
||||
config_kind_to_credential_type_name_map = {kind: name for name, kind in credential_type_name_to_config_kind_map.items()}
|
||||
|
||||
|
||||
def kind_and_config_cred_from_credential_type(credential_type):
|
||||
@@ -115,8 +109,7 @@ def kind_and_config_cred_from_credential_type(credential_type):
|
||||
config_cred = config.credentials.network
|
||||
kind = 'net'
|
||||
elif credential_type.kind == 'cloud':
|
||||
kind = credential_type_name_to_config_kind_map[credential_type.name.lower(
|
||||
)]
|
||||
kind = credential_type_name_to_config_kind_map[credential_type.name.lower()]
|
||||
config_kind = kind if kind != 'azure_rm' else 'azure'
|
||||
config_cred = config.credentials.cloud[config_kind]
|
||||
else:
|
||||
@@ -127,11 +120,8 @@ def kind_and_config_cred_from_credential_type(credential_type):
|
||||
return kind, PseudoNamespace()
|
||||
|
||||
|
||||
def get_payload_field_and_value_from_kwargs_or_config_cred(
|
||||
field, kind, kwargs, config_cred):
|
||||
if field in (
|
||||
'project_id',
|
||||
'project_name'): # Needed to prevent Project kwarg collision
|
||||
def get_payload_field_and_value_from_kwargs_or_config_cred(field, kind, kwargs, config_cred):
|
||||
if field in ('project_id', 'project_name'): # Needed to prevent Project kwarg collision
|
||||
config_field = 'project'
|
||||
elif field == 'subscription' and 'azure' in kind:
|
||||
config_field = 'subscription_id'
|
||||
@@ -159,10 +149,8 @@ class CredentialType(HasCreate, base.Base):
|
||||
|
||||
def payload(self, kind='cloud', **kwargs):
|
||||
payload = PseudoNamespace(
|
||||
name=kwargs.get('name') or 'CredentialType - {}'.format(
|
||||
random_title()),
|
||||
description=kwargs.get('description') or random_title(10),
|
||||
kind=kind)
|
||||
name=kwargs.get('name') or 'CredentialType - {}'.format(random_title()), description=kwargs.get('description') or random_title(10), kind=kind
|
||||
)
|
||||
fields = ('inputs', 'injectors')
|
||||
update_payload(payload, fields, kwargs)
|
||||
return payload
|
||||
@@ -174,17 +162,13 @@ class CredentialType(HasCreate, base.Base):
|
||||
|
||||
def create(self, kind='cloud', **kwargs):
|
||||
payload = self.create_payload(kind=kind, **kwargs)
|
||||
return self.update_identity(
|
||||
CredentialTypes(
|
||||
self.connection).post(payload))
|
||||
return self.update_identity(CredentialTypes(self.connection).post(payload))
|
||||
|
||||
def test(self, data):
|
||||
"""Test the credential type endpoint."""
|
||||
response = self.connection.post(urljoin(str(self.url), 'test/'), data)
|
||||
exception = exception_from_status_code(response.status_code)
|
||||
exc_str = "%s (%s) received" % (
|
||||
http.responses[response.status_code], response.status_code
|
||||
)
|
||||
exc_str = "%s (%s) received" % (http.responses[response.status_code], response.status_code)
|
||||
if exception:
|
||||
raise exception(exc_str, response.json())
|
||||
elif response.status_code == http.FORBIDDEN:
|
||||
@@ -192,8 +176,7 @@ class CredentialType(HasCreate, base.Base):
|
||||
return response
|
||||
|
||||
|
||||
page.register_page([resources.credential_type,
|
||||
(resources.credential_types, 'post')], CredentialType)
|
||||
page.register_page([resources.credential_type, (resources.credential_types, 'post')], CredentialType)
|
||||
|
||||
|
||||
class CredentialTypes(page.PageList, CredentialType):
|
||||
@@ -210,27 +193,19 @@ class Credential(HasCopy, HasCreate, base.Base):
|
||||
optional_dependencies = [Organization, User, Team]
|
||||
NATURAL_KEY = ('organization', 'name', 'credential_type')
|
||||
|
||||
def payload(
|
||||
self,
|
||||
credential_type,
|
||||
user=None,
|
||||
team=None,
|
||||
organization=None,
|
||||
inputs=None,
|
||||
**kwargs):
|
||||
def payload(self, credential_type, user=None, team=None, organization=None, inputs=None, **kwargs):
|
||||
if not any((user, team, organization)):
|
||||
raise TypeError(
|
||||
'{0.__class__.__name__} requires user, team, and/or organization instances.'.format(self))
|
||||
raise TypeError('{0.__class__.__name__} requires user, team, and/or organization instances.'.format(self))
|
||||
|
||||
if inputs is None:
|
||||
inputs = {}
|
||||
|
||||
payload = PseudoNamespace(
|
||||
name=kwargs.get('name') or 'Credential - {}'.format(
|
||||
random_title()),
|
||||
name=kwargs.get('name') or 'Credential - {}'.format(random_title()),
|
||||
description=kwargs.get('description') or random_title(10),
|
||||
credential_type=credential_type.id,
|
||||
inputs=inputs)
|
||||
inputs=inputs,
|
||||
)
|
||||
if user:
|
||||
payload.user = user.id
|
||||
if team:
|
||||
@@ -238,38 +213,26 @@ class Credential(HasCopy, HasCreate, base.Base):
|
||||
if organization:
|
||||
payload.organization = organization.id
|
||||
|
||||
kind, config_cred = kind_and_config_cred_from_credential_type(
|
||||
credential_type)
|
||||
kind, config_cred = kind_and_config_cred_from_credential_type(credential_type)
|
||||
|
||||
for field in credential_input_fields:
|
||||
field, value = get_payload_field_and_value_from_kwargs_or_config_cred(
|
||||
field, kind, inputs or kwargs, config_cred)
|
||||
field, value = get_payload_field_and_value_from_kwargs_or_config_cred(field, kind, inputs or kwargs, config_cred)
|
||||
if value != not_provided:
|
||||
payload.inputs[field] = value
|
||||
|
||||
if kind == 'net':
|
||||
payload.inputs.authorize = inputs.get(
|
||||
'authorize', bool(inputs.get('authorize_password')))
|
||||
payload.inputs.authorize = inputs.get('authorize', bool(inputs.get('authorize_password')))
|
||||
|
||||
if kind in ('ssh', 'net') and 'ssh_key_data' not in payload.inputs:
|
||||
payload.inputs.ssh_key_data = inputs.get(
|
||||
'ssh_key_data', generate_private_key())
|
||||
payload.inputs.ssh_key_data = inputs.get('ssh_key_data', generate_private_key())
|
||||
|
||||
return payload
|
||||
|
||||
def create_payload(
|
||||
self,
|
||||
credential_type=CredentialType,
|
||||
user=None,
|
||||
team=None,
|
||||
organization=Organization,
|
||||
inputs=None,
|
||||
**kwargs):
|
||||
def create_payload(self, credential_type=CredentialType, user=None, team=None, organization=Organization, inputs=None, **kwargs):
|
||||
if isinstance(credential_type, int):
|
||||
# if an int was passed, it is assumed to be the pk id of a
|
||||
# credential type
|
||||
credential_type = CredentialTypes(
|
||||
self.connection).get(id=credential_type).results.pop()
|
||||
credential_type = CredentialTypes(self.connection).get(id=credential_type).results.pop()
|
||||
|
||||
if credential_type == CredentialType:
|
||||
kind = kwargs.pop('kind', 'ssh')
|
||||
@@ -282,57 +245,29 @@ class Credential(HasCopy, HasCreate, base.Base):
|
||||
inputs = config.credentials.cloud['openstack']
|
||||
else:
|
||||
credential_type_name = config_kind_to_credential_type_name_map[kind]
|
||||
credential_type = CredentialTypes(
|
||||
self.connection).get(
|
||||
managed_by_tower=True,
|
||||
name__icontains=credential_type_name).results.pop()
|
||||
credential_type = CredentialTypes(self.connection).get(managed_by_tower=True, name__icontains=credential_type_name).results.pop()
|
||||
|
||||
credential_type, organization, user, team = filter_by_class(
|
||||
(credential_type, CredentialType), (organization, Organization), (user, User), (team, Team))
|
||||
credential_type, organization, user, team = filter_by_class((credential_type, CredentialType), (organization, Organization), (user, User), (team, Team))
|
||||
if not any((user, team, organization)):
|
||||
organization = Organization
|
||||
self.create_and_update_dependencies(
|
||||
credential_type, organization, user, team)
|
||||
self.create_and_update_dependencies(credential_type, organization, user, team)
|
||||
user = self.ds.user if user else None
|
||||
team = self.ds.team if team else None
|
||||
organization = self.ds.organization if organization else None
|
||||
|
||||
payload = self.payload(
|
||||
self.ds.credential_type,
|
||||
user=user,
|
||||
team=team,
|
||||
organization=organization,
|
||||
inputs=inputs,
|
||||
**kwargs)
|
||||
payload = self.payload(self.ds.credential_type, user=user, team=team, organization=organization, inputs=inputs, **kwargs)
|
||||
payload.ds = DSAdapter(self.__class__.__name__, self._dependency_store)
|
||||
return payload
|
||||
|
||||
def create(
|
||||
self,
|
||||
credential_type=CredentialType,
|
||||
user=None,
|
||||
team=None,
|
||||
organization=None,
|
||||
inputs=None,
|
||||
**kwargs):
|
||||
payload = self.create_payload(
|
||||
credential_type=credential_type,
|
||||
user=user,
|
||||
team=team,
|
||||
organization=organization,
|
||||
inputs=inputs,
|
||||
**kwargs)
|
||||
return self.update_identity(
|
||||
Credentials(
|
||||
self.connection)).post(payload)
|
||||
def create(self, credential_type=CredentialType, user=None, team=None, organization=None, inputs=None, **kwargs):
|
||||
payload = self.create_payload(credential_type=credential_type, user=user, team=team, organization=organization, inputs=inputs, **kwargs)
|
||||
return self.update_identity(Credentials(self.connection)).post(payload)
|
||||
|
||||
def test(self, data):
|
||||
"""Test the credential endpoint."""
|
||||
response = self.connection.post(urljoin(str(self.url), 'test/'), data)
|
||||
exception = exception_from_status_code(response.status_code)
|
||||
exc_str = "%s (%s) received" % (
|
||||
http.responses[response.status_code], response.status_code
|
||||
)
|
||||
exc_str = "%s (%s) received" % (http.responses[response.status_code], response.status_code)
|
||||
if exception:
|
||||
raise exception(exc_str, response.json())
|
||||
elif response.status_code == http.FORBIDDEN:
|
||||
@@ -343,11 +278,7 @@ class Credential(HasCopy, HasCreate, base.Base):
|
||||
def expected_passwords_needed_to_start(self):
|
||||
"""Return a list of expected passwords needed to start a job using this credential."""
|
||||
passwords = []
|
||||
for field in (
|
||||
'password',
|
||||
'become_password',
|
||||
'ssh_key_unlock',
|
||||
'vault_password'):
|
||||
for field in ('password', 'become_password', 'ssh_key_unlock', 'vault_password'):
|
||||
if getattr(self.inputs, field, None) == 'ASK':
|
||||
if field == 'password':
|
||||
passwords.append('ssh_password')
|
||||
@@ -356,9 +287,7 @@ class Credential(HasCopy, HasCreate, base.Base):
|
||||
return passwords
|
||||
|
||||
|
||||
page.register_page([resources.credential,
|
||||
(resources.credentials, 'post'),
|
||||
(resources.credential_copy, 'post')], Credential)
|
||||
page.register_page([resources.credential, (resources.credentials, 'post'), (resources.credential_copy, 'post')], Credential)
|
||||
|
||||
|
||||
class Credentials(page.PageList, Credential):
|
||||
@@ -366,9 +295,7 @@ class Credentials(page.PageList, Credential):
|
||||
pass
|
||||
|
||||
|
||||
page.register_page([resources.credentials,
|
||||
resources.related_credentials],
|
||||
Credentials)
|
||||
page.register_page([resources.credentials, resources.related_credentials], Credentials)
|
||||
|
||||
|
||||
class CredentialCopy(base.Base):
|
||||
|
||||
@@ -46,14 +46,13 @@ class ExecutionEnvironment(HasCreate, HasCopy, base.Base):
|
||||
return payload
|
||||
|
||||
|
||||
page.register_page([resources.execution_environment,
|
||||
(resources.execution_environments, 'post'),
|
||||
(resources.organization_execution_environments, 'post')], ExecutionEnvironment)
|
||||
page.register_page(
|
||||
[resources.execution_environment, (resources.execution_environments, 'post'), (resources.organization_execution_environments, 'post')], ExecutionEnvironment
|
||||
)
|
||||
|
||||
|
||||
class ExecutionEnvironments(page.PageList, ExecutionEnvironment):
|
||||
pass
|
||||
|
||||
|
||||
page.register_page([resources.execution_environments,
|
||||
resources.organization_execution_environments], ExecutionEnvironments)
|
||||
page.register_page([resources.execution_environments, resources.organization_execution_environments], ExecutionEnvironments)
|
||||
|
||||
@@ -7,7 +7,6 @@ from . import page
|
||||
|
||||
|
||||
class InstanceGroup(HasCreate, base.Base):
|
||||
|
||||
def add_instance(self, instance):
|
||||
with suppress(exc.NoContent):
|
||||
self.related.instances.post(dict(id=instance.id))
|
||||
@@ -17,8 +16,7 @@ class InstanceGroup(HasCreate, base.Base):
|
||||
self.related.instances.post(dict(id=instance.id, disassociate=True))
|
||||
|
||||
def payload(self, **kwargs):
|
||||
payload = PseudoNamespace(name=kwargs.get('name') or
|
||||
'Instance Group - {}'.format(random_title()))
|
||||
payload = PseudoNamespace(name=kwargs.get('name') or 'Instance Group - {}'.format(random_title()))
|
||||
fields = ('policy_instance_percentage', 'policy_instance_minimum', 'policy_instance_list', 'is_container_group')
|
||||
update_payload(payload, fields, kwargs)
|
||||
|
||||
@@ -35,8 +33,7 @@ class InstanceGroup(HasCreate, base.Base):
|
||||
return self.update_identity(InstanceGroups(self.connection).post(payload))
|
||||
|
||||
|
||||
page.register_page([resources.instance_group,
|
||||
(resources.instance_groups, 'post')], InstanceGroup)
|
||||
page.register_page([resources.instance_group, (resources.instance_groups, 'post')], InstanceGroup)
|
||||
|
||||
|
||||
class InstanceGroups(page.PageList, InstanceGroup):
|
||||
@@ -44,5 +41,4 @@ class InstanceGroups(page.PageList, InstanceGroup):
|
||||
pass
|
||||
|
||||
|
||||
page.register_page([resources.instance_groups,
|
||||
resources.related_instance_groups], InstanceGroups)
|
||||
page.register_page([resources.instance_groups, resources.related_instance_groups], InstanceGroups)
|
||||
|
||||
@@ -16,5 +16,4 @@ class Instances(page.PageList, Instance):
|
||||
pass
|
||||
|
||||
|
||||
page.register_page([resources.instances,
|
||||
resources.related_instances], Instances)
|
||||
page.register_page([resources.instances, resources.related_instances], Instances)
|
||||
|
||||
@@ -2,23 +2,8 @@ import logging
|
||||
import json
|
||||
import re
|
||||
|
||||
from awxkit.api.pages import (
|
||||
Credential,
|
||||
Organization,
|
||||
Project,
|
||||
UnifiedJob,
|
||||
UnifiedJobTemplate
|
||||
)
|
||||
from awxkit.utils import (
|
||||
filter_by_class,
|
||||
random_title,
|
||||
update_payload,
|
||||
suppress,
|
||||
not_provided,
|
||||
PseudoNamespace,
|
||||
poll_until,
|
||||
random_utf8
|
||||
)
|
||||
from awxkit.api.pages import Credential, Organization, Project, UnifiedJob, UnifiedJobTemplate
|
||||
from awxkit.utils import filter_by_class, random_title, update_payload, suppress, not_provided, PseudoNamespace, poll_until, random_utf8
|
||||
from awxkit.api.mixins import DSAdapter, HasCreate, HasInstanceGroups, HasNotifications, HasVariables, HasCopy
|
||||
from awxkit.api.resources import resources
|
||||
import awxkit.exceptions as exc
|
||||
@@ -68,56 +53,31 @@ class Inventory(HasCopy, HasCreate, HasInstanceGroups, HasVariables, base.Base):
|
||||
|
||||
def payload(self, organization, **kwargs):
|
||||
payload = PseudoNamespace(
|
||||
name=kwargs.get('name') or 'Inventory - {}'.format(
|
||||
random_title()),
|
||||
name=kwargs.get('name') or 'Inventory - {}'.format(random_title()),
|
||||
description=kwargs.get('description') or random_title(10),
|
||||
organization=organization.id)
|
||||
organization=organization.id,
|
||||
)
|
||||
|
||||
optional_fields = (
|
||||
'host_filter',
|
||||
'insights_credential',
|
||||
'kind',
|
||||
'variables')
|
||||
optional_fields = ('host_filter', 'insights_credential', 'kind', 'variables')
|
||||
|
||||
update_payload(payload, optional_fields, kwargs)
|
||||
|
||||
if 'variables' in payload and isinstance(payload.variables, dict):
|
||||
payload.variables = json.dumps(payload.variables)
|
||||
if 'insights_credential' in payload and isinstance(
|
||||
payload.insights_credential, Credential):
|
||||
if 'insights_credential' in payload and isinstance(payload.insights_credential, Credential):
|
||||
payload.insights_credential = payload.insights_credential.id
|
||||
|
||||
return payload
|
||||
|
||||
def create_payload(
|
||||
self,
|
||||
name='',
|
||||
description='',
|
||||
organization=Organization,
|
||||
**kwargs):
|
||||
def create_payload(self, name='', description='', organization=Organization, **kwargs):
|
||||
self.create_and_update_dependencies(organization)
|
||||
payload = self.payload(
|
||||
name=name,
|
||||
description=description,
|
||||
organization=self.ds.organization,
|
||||
**kwargs)
|
||||
payload = self.payload(name=name, description=description, organization=self.ds.organization, **kwargs)
|
||||
payload.ds = DSAdapter(self.__class__.__name__, self._dependency_store)
|
||||
return payload
|
||||
|
||||
def create(
|
||||
self,
|
||||
name='',
|
||||
description='',
|
||||
organization=Organization,
|
||||
**kwargs):
|
||||
payload = self.create_payload(
|
||||
name=name,
|
||||
description=description,
|
||||
organization=organization,
|
||||
**kwargs)
|
||||
return self.update_identity(
|
||||
Inventories(
|
||||
self.connection).post(payload))
|
||||
def create(self, name='', description='', organization=Organization, **kwargs):
|
||||
payload = self.create_payload(name=name, description=description, organization=organization, **kwargs)
|
||||
return self.update_identity(Inventories(self.connection).post(payload))
|
||||
|
||||
def add_host(self, host=None):
|
||||
if host is None:
|
||||
@@ -135,17 +95,16 @@ class Inventory(HasCopy, HasCreate, HasInstanceGroups, HasVariables, base.Base):
|
||||
self.get()
|
||||
except exc.NotFound:
|
||||
return True
|
||||
|
||||
poll_until(_wait, interval=1, timeout=60)
|
||||
|
||||
def update_inventory_sources(self, wait=False):
|
||||
response = self.related.update_inventory_sources.post()
|
||||
source_ids = [entry['inventory_source']
|
||||
for entry in response if entry['status'] == 'started']
|
||||
source_ids = [entry['inventory_source'] for entry in response if entry['status'] == 'started']
|
||||
|
||||
inv_updates = []
|
||||
for source_id in source_ids:
|
||||
inv_source = self.related.inventory_sources.get(
|
||||
id=source_id).results.pop()
|
||||
inv_source = self.related.inventory_sources.get(id=source_id).results.pop()
|
||||
inv_updates.append(inv_source.related.current_job.get())
|
||||
|
||||
if wait:
|
||||
@@ -154,9 +113,7 @@ class Inventory(HasCopy, HasCreate, HasInstanceGroups, HasVariables, base.Base):
|
||||
return inv_updates
|
||||
|
||||
|
||||
page.register_page([resources.inventory,
|
||||
(resources.inventories, 'post'),
|
||||
(resources.inventory_copy, 'post')], Inventory)
|
||||
page.register_page([resources.inventory, (resources.inventories, 'post'), (resources.inventory_copy, 'post')], Inventory)
|
||||
|
||||
|
||||
class Inventories(page.PageList, Inventory):
|
||||
@@ -164,8 +121,7 @@ class Inventories(page.PageList, Inventory):
|
||||
pass
|
||||
|
||||
|
||||
page.register_page([resources.inventories,
|
||||
resources.related_inventories], Inventories)
|
||||
page.register_page([resources.inventories, resources.related_inventories], Inventories)
|
||||
|
||||
|
||||
class InventoryScript(HasCopy, HasCreate, base.Base):
|
||||
@@ -174,77 +130,48 @@ class InventoryScript(HasCopy, HasCreate, base.Base):
|
||||
|
||||
def payload(self, organization, **kwargs):
|
||||
payload = PseudoNamespace(
|
||||
name=kwargs.get('name') or 'Inventory Script - {}'.format(
|
||||
random_title()),
|
||||
name=kwargs.get('name') or 'Inventory Script - {}'.format(random_title()),
|
||||
description=kwargs.get('description') or random_title(10),
|
||||
organization=organization.id,
|
||||
script=kwargs.get('script') or self._generate_script())
|
||||
script=kwargs.get('script') or self._generate_script(),
|
||||
)
|
||||
return payload
|
||||
|
||||
def create_payload(
|
||||
self,
|
||||
name='',
|
||||
description='',
|
||||
organization=Organization,
|
||||
script='',
|
||||
**kwargs):
|
||||
def create_payload(self, name='', description='', organization=Organization, script='', **kwargs):
|
||||
self.create_and_update_dependencies(organization)
|
||||
payload = self.payload(
|
||||
name=name,
|
||||
description=description,
|
||||
organization=self.ds.organization,
|
||||
script=script,
|
||||
**kwargs)
|
||||
payload = self.payload(name=name, description=description, organization=self.ds.organization, script=script, **kwargs)
|
||||
payload.ds = DSAdapter(self.__class__.__name__, self._dependency_store)
|
||||
return payload
|
||||
|
||||
def create(
|
||||
self,
|
||||
name='',
|
||||
description='',
|
||||
organization=Organization,
|
||||
script='',
|
||||
**kwargs):
|
||||
payload = self.create_payload(
|
||||
name=name,
|
||||
description=description,
|
||||
organization=organization,
|
||||
script=script,
|
||||
**kwargs)
|
||||
return self.update_identity(
|
||||
InventoryScripts(
|
||||
self.connection).post(payload))
|
||||
def create(self, name='', description='', organization=Organization, script='', **kwargs):
|
||||
payload = self.create_payload(name=name, description=description, organization=organization, script=script, **kwargs)
|
||||
return self.update_identity(InventoryScripts(self.connection).post(payload))
|
||||
|
||||
def _generate_script(self):
|
||||
script = '\n'.join([
|
||||
'#!/usr/bin/env python',
|
||||
'# -*- coding: utf-8 -*-',
|
||||
'import json',
|
||||
'inventory = dict()',
|
||||
'inventory["{0}"] = dict()',
|
||||
'inventory["{0}"]["hosts"] = list()',
|
||||
'inventory["{0}"]["hosts"].append("{1}")',
|
||||
'inventory["{0}"]["hosts"].append("{2}")',
|
||||
'inventory["{0}"]["hosts"].append("{3}")',
|
||||
'inventory["{0}"]["hosts"].append("{4}")',
|
||||
'inventory["{0}"]["hosts"].append("{5}")',
|
||||
'inventory["{0}"]["vars"] = dict(ansible_host="127.0.0.1", ansible_connection="local")',
|
||||
'print(json.dumps(inventory))'
|
||||
])
|
||||
script = '\n'.join(
|
||||
[
|
||||
'#!/usr/bin/env python',
|
||||
'# -*- coding: utf-8 -*-',
|
||||
'import json',
|
||||
'inventory = dict()',
|
||||
'inventory["{0}"] = dict()',
|
||||
'inventory["{0}"]["hosts"] = list()',
|
||||
'inventory["{0}"]["hosts"].append("{1}")',
|
||||
'inventory["{0}"]["hosts"].append("{2}")',
|
||||
'inventory["{0}"]["hosts"].append("{3}")',
|
||||
'inventory["{0}"]["hosts"].append("{4}")',
|
||||
'inventory["{0}"]["hosts"].append("{5}")',
|
||||
'inventory["{0}"]["vars"] = dict(ansible_host="127.0.0.1", ansible_connection="local")',
|
||||
'print(json.dumps(inventory))',
|
||||
]
|
||||
)
|
||||
group_name = re.sub(r"[\']", "", "group_{}".format(random_title(non_ascii=False)))
|
||||
host_names = [
|
||||
re.sub(
|
||||
r"[\':]",
|
||||
"",
|
||||
"host_{}".format(
|
||||
random_utf8())) for _ in range(5)]
|
||||
host_names = [re.sub(r"[\':]", "", "host_{}".format(random_utf8())) for _ in range(5)]
|
||||
|
||||
return script.format(group_name, *host_names)
|
||||
|
||||
|
||||
page.register_page([resources.inventory_script,
|
||||
(resources.inventory_scripts, 'post'),
|
||||
(resources.inventory_script_copy, 'post')], InventoryScript)
|
||||
page.register_page([resources.inventory_script, (resources.inventory_scripts, 'post'), (resources.inventory_script_copy, 'post')], InventoryScript)
|
||||
|
||||
|
||||
class InventoryScripts(page.PageList, InventoryScript):
|
||||
@@ -272,11 +199,10 @@ class Group(HasCreate, HasVariables, base.Base):
|
||||
|
||||
def payload(self, inventory, credential=None, **kwargs):
|
||||
payload = PseudoNamespace(
|
||||
name=kwargs.get('name') or 'Group{}'.format(
|
||||
random_title(
|
||||
non_ascii=False)),
|
||||
name=kwargs.get('name') or 'Group{}'.format(random_title(non_ascii=False)),
|
||||
description=kwargs.get('description') or random_title(10),
|
||||
inventory=inventory.id)
|
||||
inventory=inventory.id,
|
||||
)
|
||||
|
||||
if credential:
|
||||
payload.credential = credential.id
|
||||
@@ -288,38 +214,19 @@ class Group(HasCreate, HasVariables, base.Base):
|
||||
|
||||
return payload
|
||||
|
||||
def create_payload(
|
||||
self,
|
||||
name='',
|
||||
description='',
|
||||
inventory=Inventory,
|
||||
credential=None,
|
||||
source_script=None,
|
||||
**kwargs):
|
||||
credential, source_script = filter_by_class(
|
||||
(credential, Credential), (source_script, InventoryScript))
|
||||
self.create_and_update_dependencies(
|
||||
inventory, credential, source_script)
|
||||
def create_payload(self, name='', description='', inventory=Inventory, credential=None, source_script=None, **kwargs):
|
||||
credential, source_script = filter_by_class((credential, Credential), (source_script, InventoryScript))
|
||||
self.create_and_update_dependencies(inventory, credential, source_script)
|
||||
credential = self.ds.credential if credential else None
|
||||
payload = self.payload(
|
||||
inventory=self.ds.inventory,
|
||||
credential=credential,
|
||||
name=name,
|
||||
description=description,
|
||||
**kwargs)
|
||||
payload = self.payload(inventory=self.ds.inventory, credential=credential, name=name, description=description, **kwargs)
|
||||
payload.ds = DSAdapter(self.__class__.__name__, self._dependency_store)
|
||||
return payload
|
||||
|
||||
def create(self, name='', description='', inventory=Inventory, **kwargs):
|
||||
payload = self.create_payload(
|
||||
name=name,
|
||||
description=description,
|
||||
inventory=inventory,
|
||||
**kwargs)
|
||||
payload = self.create_payload(name=name, description=description, inventory=inventory, **kwargs)
|
||||
|
||||
parent = kwargs.get('parent', None) # parent must be a Group instance
|
||||
resource = parent.related.children if parent else Groups(
|
||||
self.connection)
|
||||
resource = parent.related.children if parent else Groups(self.connection)
|
||||
return self.update_identity(resource.post(payload))
|
||||
|
||||
def add_host(self, host=None):
|
||||
@@ -348,8 +255,7 @@ class Group(HasCreate, HasVariables, base.Base):
|
||||
self.related.children.post(dict(id=group.id, disassociate=True))
|
||||
|
||||
|
||||
page.register_page([resources.group,
|
||||
(resources.groups, 'post')], Group)
|
||||
page.register_page([resources.group, (resources.groups, 'post')], Group)
|
||||
|
||||
|
||||
class Groups(page.PageList, Group):
|
||||
@@ -357,12 +263,17 @@ class Groups(page.PageList, Group):
|
||||
pass
|
||||
|
||||
|
||||
page.register_page([resources.groups,
|
||||
resources.host_groups,
|
||||
resources.inventory_related_groups,
|
||||
resources.inventory_related_root_groups,
|
||||
resources.group_children,
|
||||
resources.group_potential_children], Groups)
|
||||
page.register_page(
|
||||
[
|
||||
resources.groups,
|
||||
resources.host_groups,
|
||||
resources.inventory_related_groups,
|
||||
resources.inventory_related_root_groups,
|
||||
resources.group_children,
|
||||
resources.group_potential_children,
|
||||
],
|
||||
Groups,
|
||||
)
|
||||
|
||||
|
||||
class Host(HasCreate, HasVariables, base.Base):
|
||||
@@ -372,11 +283,10 @@ class Host(HasCreate, HasVariables, base.Base):
|
||||
|
||||
def payload(self, inventory, **kwargs):
|
||||
payload = PseudoNamespace(
|
||||
name=kwargs.get('name') or 'Host{}'.format(
|
||||
random_title(
|
||||
non_ascii=False)),
|
||||
name=kwargs.get('name') or 'Host{}'.format(random_title(non_ascii=False)),
|
||||
description=kwargs.get('description') or random_title(10),
|
||||
inventory=inventory.id)
|
||||
inventory=inventory.id,
|
||||
)
|
||||
|
||||
optional_fields = ('enabled', 'instance_id')
|
||||
|
||||
@@ -385,9 +295,7 @@ class Host(HasCreate, HasVariables, base.Base):
|
||||
variables = kwargs.get('variables', not_provided)
|
||||
|
||||
if variables is None:
|
||||
variables = dict(
|
||||
ansible_host='127.0.0.1',
|
||||
ansible_connection='local')
|
||||
variables = dict(ansible_host='127.0.0.1', ansible_connection='local')
|
||||
|
||||
if variables != not_provided:
|
||||
if isinstance(variables, dict):
|
||||
@@ -396,42 +304,18 @@ class Host(HasCreate, HasVariables, base.Base):
|
||||
|
||||
return payload
|
||||
|
||||
def create_payload(
|
||||
self,
|
||||
name='',
|
||||
description='',
|
||||
variables=None,
|
||||
inventory=Inventory,
|
||||
**kwargs):
|
||||
self.create_and_update_dependencies(
|
||||
*filter_by_class((inventory, Inventory)))
|
||||
payload = self.payload(
|
||||
inventory=self.ds.inventory,
|
||||
name=name,
|
||||
description=description,
|
||||
variables=variables,
|
||||
**kwargs)
|
||||
def create_payload(self, name='', description='', variables=None, inventory=Inventory, **kwargs):
|
||||
self.create_and_update_dependencies(*filter_by_class((inventory, Inventory)))
|
||||
payload = self.payload(inventory=self.ds.inventory, name=name, description=description, variables=variables, **kwargs)
|
||||
payload.ds = DSAdapter(self.__class__.__name__, self._dependency_store)
|
||||
return payload
|
||||
|
||||
def create(
|
||||
self,
|
||||
name='',
|
||||
description='',
|
||||
variables=None,
|
||||
inventory=Inventory,
|
||||
**kwargs):
|
||||
payload = self.create_payload(
|
||||
name=name,
|
||||
description=description,
|
||||
variables=variables,
|
||||
inventory=inventory,
|
||||
**kwargs)
|
||||
def create(self, name='', description='', variables=None, inventory=Inventory, **kwargs):
|
||||
payload = self.create_payload(name=name, description=description, variables=variables, inventory=inventory, **kwargs)
|
||||
return self.update_identity(Hosts(self.connection).post(payload))
|
||||
|
||||
|
||||
page.register_page([resources.host,
|
||||
(resources.hosts, 'post')], Host)
|
||||
page.register_page([resources.host, (resources.hosts, 'post')], Host)
|
||||
|
||||
|
||||
class Hosts(page.PageList, Host):
|
||||
@@ -439,10 +323,7 @@ class Hosts(page.PageList, Host):
|
||||
pass
|
||||
|
||||
|
||||
page.register_page([resources.hosts,
|
||||
resources.group_related_hosts,
|
||||
resources.inventory_related_hosts,
|
||||
resources.inventory_sources_related_hosts], Hosts)
|
||||
page.register_page([resources.hosts, resources.group_related_hosts, resources.inventory_related_hosts, resources.inventory_sources_related_hosts], Hosts)
|
||||
|
||||
|
||||
class FactVersion(base.Base):
|
||||
@@ -454,7 +335,6 @@ page.register_page(resources.host_related_fact_version, FactVersion)
|
||||
|
||||
|
||||
class FactVersions(page.PageList, FactVersion):
|
||||
|
||||
@property
|
||||
def count(self):
|
||||
return len(self.results)
|
||||
@@ -478,20 +358,13 @@ class InventorySource(HasCreate, HasNotifications, UnifiedJobTemplate):
|
||||
optional_dependencies = [Credential, InventoryScript, Project]
|
||||
NATURAL_KEY = ('organization', 'name', 'inventory')
|
||||
|
||||
def payload(
|
||||
self,
|
||||
inventory,
|
||||
source='custom',
|
||||
credential=None,
|
||||
source_script=None,
|
||||
project=None,
|
||||
**kwargs):
|
||||
def payload(self, inventory, source='custom', credential=None, source_script=None, project=None, **kwargs):
|
||||
payload = PseudoNamespace(
|
||||
name=kwargs.get('name') or 'InventorySource - {}'.format(
|
||||
random_title()),
|
||||
name=kwargs.get('name') or 'InventorySource - {}'.format(random_title()),
|
||||
description=kwargs.get('description') or random_title(10),
|
||||
inventory=inventory.id,
|
||||
source=source)
|
||||
source=source,
|
||||
)
|
||||
|
||||
if credential:
|
||||
payload.credential = credential.id
|
||||
@@ -509,22 +382,16 @@ class InventorySource(HasCreate, HasNotifications, UnifiedJobTemplate):
|
||||
'update_cache_timeout',
|
||||
'update_on_launch',
|
||||
'update_on_project_update',
|
||||
'verbosity')
|
||||
'verbosity',
|
||||
)
|
||||
|
||||
update_payload(payload, optional_fields, kwargs)
|
||||
|
||||
return payload
|
||||
|
||||
def create_payload(
|
||||
self,
|
||||
name='',
|
||||
description='',
|
||||
source='custom',
|
||||
inventory=Inventory,
|
||||
credential=None,
|
||||
source_script=InventoryScript,
|
||||
project=None,
|
||||
**kwargs):
|
||||
self, name='', description='', source='custom', inventory=Inventory, credential=None, source_script=InventoryScript, project=None, **kwargs
|
||||
):
|
||||
if source != 'custom' and source_script == InventoryScript:
|
||||
source_script = None
|
||||
if source == 'scm':
|
||||
@@ -532,12 +399,10 @@ class InventorySource(HasCreate, HasNotifications, UnifiedJobTemplate):
|
||||
if project is None:
|
||||
project = Project
|
||||
|
||||
inventory, credential, source_script, project = filter_by_class((inventory, Inventory),
|
||||
(credential, Credential),
|
||||
(source_script, InventoryScript),
|
||||
(project, Project))
|
||||
self.create_and_update_dependencies(
|
||||
inventory, credential, source_script, project)
|
||||
inventory, credential, source_script, project = filter_by_class(
|
||||
(inventory, Inventory), (credential, Credential), (source_script, InventoryScript), (project, Project)
|
||||
)
|
||||
self.create_and_update_dependencies(inventory, credential, source_script, project)
|
||||
|
||||
if credential:
|
||||
credential = self.ds.credential
|
||||
@@ -554,20 +419,12 @@ class InventorySource(HasCreate, HasNotifications, UnifiedJobTemplate):
|
||||
project=project,
|
||||
name=name,
|
||||
description=description,
|
||||
**kwargs)
|
||||
**kwargs
|
||||
)
|
||||
payload.ds = DSAdapter(self.__class__.__name__, self._dependency_store)
|
||||
return payload
|
||||
|
||||
def create(
|
||||
self,
|
||||
name='',
|
||||
description='',
|
||||
source='custom',
|
||||
inventory=Inventory,
|
||||
credential=None,
|
||||
source_script=InventoryScript,
|
||||
project=None,
|
||||
**kwargs):
|
||||
def create(self, name='', description='', source='custom', inventory=Inventory, credential=None, source_script=InventoryScript, project=None, **kwargs):
|
||||
payload = self.create_payload(
|
||||
name=name,
|
||||
description=description,
|
||||
@@ -576,10 +433,9 @@ class InventorySource(HasCreate, HasNotifications, UnifiedJobTemplate):
|
||||
credential=credential,
|
||||
source_script=source_script,
|
||||
project=project,
|
||||
**kwargs)
|
||||
return self.update_identity(
|
||||
InventorySources(
|
||||
self.connection).post(payload))
|
||||
**kwargs
|
||||
)
|
||||
return self.update_identity(InventorySources(self.connection).post(payload))
|
||||
|
||||
def update(self):
|
||||
"""Update the inventory_source using related->update endpoint"""
|
||||
@@ -587,45 +443,37 @@ class InventorySource(HasCreate, HasNotifications, UnifiedJobTemplate):
|
||||
update_pg = self.get_related('update')
|
||||
|
||||
# assert can_update == True
|
||||
assert update_pg.can_update, \
|
||||
"The specified inventory_source (id:%s) is not able to update (can_update:%s)" % \
|
||||
(self.id, update_pg.can_update)
|
||||
assert update_pg.can_update, "The specified inventory_source (id:%s) is not able to update (can_update:%s)" % (self.id, update_pg.can_update)
|
||||
|
||||
# start the inventory_update
|
||||
result = update_pg.post()
|
||||
|
||||
# assert JSON response
|
||||
assert 'inventory_update' in result.json, \
|
||||
"Unexpected JSON response when starting an inventory_update.\n%s" % \
|
||||
json.dumps(result.json, indent=2)
|
||||
assert 'inventory_update' in result.json, "Unexpected JSON response when starting an inventory_update.\n%s" % json.dumps(result.json, indent=2)
|
||||
|
||||
# locate and return the inventory_update
|
||||
jobs_pg = self.related.inventory_updates.get(
|
||||
id=result.json['inventory_update'])
|
||||
assert jobs_pg.count == 1, \
|
||||
"An inventory_update started (id:%s) but job not found in response at %s/inventory_updates/" % \
|
||||
(result.json['inventory_update'], self.url)
|
||||
jobs_pg = self.related.inventory_updates.get(id=result.json['inventory_update'])
|
||||
assert jobs_pg.count == 1, "An inventory_update started (id:%s) but job not found in response at %s/inventory_updates/" % (
|
||||
result.json['inventory_update'],
|
||||
self.url,
|
||||
)
|
||||
return jobs_pg.results[0]
|
||||
|
||||
@property
|
||||
def is_successful(self):
|
||||
"""An inventory_source is considered successful when source != "" and super().is_successful ."""
|
||||
return self.source != "" and super(
|
||||
InventorySource, self).is_successful
|
||||
return self.source != "" and super(InventorySource, self).is_successful
|
||||
|
||||
def add_credential(self, credential):
|
||||
with suppress(exc.NoContent):
|
||||
self.related.credentials.post(
|
||||
dict(id=credential.id, associate=True))
|
||||
self.related.credentials.post(dict(id=credential.id, associate=True))
|
||||
|
||||
def remove_credential(self, credential):
|
||||
with suppress(exc.NoContent):
|
||||
self.related.credentials.post(
|
||||
dict(id=credential.id, disassociate=True))
|
||||
self.related.credentials.post(dict(id=credential.id, disassociate=True))
|
||||
|
||||
|
||||
page.register_page([resources.inventory_source,
|
||||
(resources.inventory_sources, 'post')], InventorySource)
|
||||
page.register_page([resources.inventory_source, (resources.inventory_sources, 'post')], InventorySource)
|
||||
|
||||
|
||||
class InventorySources(page.PageList, InventorySource):
|
||||
@@ -633,9 +481,7 @@ class InventorySources(page.PageList, InventorySource):
|
||||
pass
|
||||
|
||||
|
||||
page.register_page([resources.inventory_sources,
|
||||
resources.related_inventory_sources],
|
||||
InventorySources)
|
||||
page.register_page([resources.inventory_sources, resources.related_inventory_sources], InventorySources)
|
||||
|
||||
|
||||
class InventorySourceGroups(page.PageList, Group):
|
||||
@@ -643,9 +489,7 @@ class InventorySourceGroups(page.PageList, Group):
|
||||
pass
|
||||
|
||||
|
||||
page.register_page(
|
||||
resources.inventory_sources_related_groups,
|
||||
InventorySourceGroups)
|
||||
page.register_page(resources.inventory_sources_related_groups, InventorySourceGroups)
|
||||
|
||||
|
||||
class InventorySourceUpdate(base.Base):
|
||||
@@ -653,9 +497,7 @@ class InventorySourceUpdate(base.Base):
|
||||
pass
|
||||
|
||||
|
||||
page.register_page([resources.inventory_sources_related_update,
|
||||
resources.inventory_related_update_inventory_sources],
|
||||
InventorySourceUpdate)
|
||||
page.register_page([resources.inventory_sources_related_update, resources.inventory_related_update_inventory_sources], InventorySourceUpdate)
|
||||
|
||||
|
||||
class InventoryUpdate(UnifiedJob):
|
||||
@@ -671,10 +513,7 @@ class InventoryUpdates(page.PageList, InventoryUpdate):
|
||||
pass
|
||||
|
||||
|
||||
page.register_page([resources.inventory_updates,
|
||||
resources.inventory_source_updates,
|
||||
resources.project_update_scm_inventory_updates],
|
||||
InventoryUpdates)
|
||||
page.register_page([resources.inventory_updates, resources.inventory_source_updates, resources.project_update_scm_inventory_updates], InventoryUpdates)
|
||||
|
||||
|
||||
class InventoryUpdateCancel(base.Base):
|
||||
|
||||
@@ -1,13 +1,6 @@
|
||||
import json
|
||||
|
||||
from awxkit.utils import (
|
||||
filter_by_class,
|
||||
not_provided,
|
||||
random_title,
|
||||
suppress,
|
||||
update_payload,
|
||||
set_payload_foreign_key_args,
|
||||
PseudoNamespace)
|
||||
from awxkit.utils import filter_by_class, not_provided, random_title, suppress, update_payload, set_payload_foreign_key_args, PseudoNamespace
|
||||
from awxkit.api.pages import Credential, Inventory, Project, UnifiedJobTemplate
|
||||
from awxkit.api.mixins import HasCreate, HasInstanceGroups, HasNotifications, HasSurvey, HasCopy, DSAdapter
|
||||
from awxkit.api.resources import resources
|
||||
@@ -16,13 +9,7 @@ from . import base
|
||||
from . import page
|
||||
|
||||
|
||||
class JobTemplate(
|
||||
HasCopy,
|
||||
HasCreate,
|
||||
HasInstanceGroups,
|
||||
HasNotifications,
|
||||
HasSurvey,
|
||||
UnifiedJobTemplate):
|
||||
class JobTemplate(HasCopy, HasCreate, HasInstanceGroups, HasNotifications, HasSurvey, UnifiedJobTemplate):
|
||||
|
||||
optional_dependencies = [Inventory, Credential, Project]
|
||||
NATURAL_KEY = ('organization', 'name')
|
||||
@@ -38,16 +25,13 @@ class JobTemplate(
|
||||
# return job
|
||||
if result.json['type'] == 'job':
|
||||
jobs_pg = self.get_related('jobs', id=result.json['job'])
|
||||
assert jobs_pg.count == 1, \
|
||||
"job_template launched (id:%s) but job not found in response at %s/jobs/" % \
|
||||
(result.json['job'], self.url)
|
||||
assert jobs_pg.count == 1, "job_template launched (id:%s) but job not found in response at %s/jobs/" % (result.json['job'], self.url)
|
||||
return jobs_pg.results[0]
|
||||
elif result.json['type'] == 'workflow_job':
|
||||
slice_workflow_jobs = self.get_related(
|
||||
'slice_workflow_jobs', id=result.json['id'])
|
||||
assert slice_workflow_jobs.count == 1, (
|
||||
"job_template launched sliced job (id:%s) but not found in related %s/slice_workflow_jobs/" %
|
||||
(result.json['id'], self.url)
|
||||
slice_workflow_jobs = self.get_related('slice_workflow_jobs', id=result.json['id'])
|
||||
assert slice_workflow_jobs.count == 1, "job_template launched sliced job (id:%s) but not found in related %s/slice_workflow_jobs/" % (
|
||||
result.json['id'],
|
||||
self.url,
|
||||
)
|
||||
return slice_workflow_jobs.results[0]
|
||||
else:
|
||||
@@ -56,10 +40,7 @@ class JobTemplate(
|
||||
def payload(self, job_type='run', playbook='ping.yml', **kwargs):
|
||||
name = kwargs.get('name') or 'JobTemplate - {}'.format(random_title())
|
||||
description = kwargs.get('description') or random_title(10)
|
||||
payload = PseudoNamespace(
|
||||
name=name,
|
||||
description=description,
|
||||
job_type=job_type)
|
||||
payload = PseudoNamespace(name=name, description=description, job_type=job_type)
|
||||
|
||||
optional_fields = (
|
||||
'ask_scm_branch_on_launch',
|
||||
@@ -90,7 +71,8 @@ class JobTemplate(
|
||||
'job_slice_count',
|
||||
'webhook_service',
|
||||
'webhook_credential',
|
||||
'scm_branch')
|
||||
'scm_branch',
|
||||
)
|
||||
|
||||
update_payload(payload, optional_fields, kwargs)
|
||||
|
||||
@@ -113,94 +95,53 @@ class JobTemplate(
|
||||
with suppress(exc.NoContent):
|
||||
self.related.labels.post(label)
|
||||
|
||||
def create_payload(
|
||||
self,
|
||||
name='',
|
||||
description='',
|
||||
job_type='run',
|
||||
playbook='ping.yml',
|
||||
credential=Credential,
|
||||
inventory=Inventory,
|
||||
project=None,
|
||||
**kwargs):
|
||||
def create_payload(self, name='', description='', job_type='run', playbook='ping.yml', credential=Credential, inventory=Inventory, project=None, **kwargs):
|
||||
if not project:
|
||||
project = Project
|
||||
if not inventory and not kwargs.get('ask_inventory_on_launch', False):
|
||||
inventory = Inventory
|
||||
|
||||
self.create_and_update_dependencies(
|
||||
*
|
||||
filter_by_class(
|
||||
(credential,
|
||||
Credential),
|
||||
(inventory,
|
||||
Inventory),
|
||||
(project,
|
||||
Project)))
|
||||
self.create_and_update_dependencies(*filter_by_class((credential, Credential), (inventory, Inventory), (project, Project)))
|
||||
project = self.ds.project if project else None
|
||||
inventory = self.ds.inventory if inventory else None
|
||||
credential = self.ds.credential if credential else None
|
||||
|
||||
payload = self.payload(
|
||||
name=name,
|
||||
description=description,
|
||||
job_type=job_type,
|
||||
playbook=playbook,
|
||||
credential=credential,
|
||||
inventory=inventory,
|
||||
project=project,
|
||||
**kwargs)
|
||||
name=name, description=description, job_type=job_type, playbook=playbook, credential=credential, inventory=inventory, project=project, **kwargs
|
||||
)
|
||||
payload.ds = DSAdapter(self.__class__.__name__, self._dependency_store)
|
||||
return payload, credential
|
||||
|
||||
def create(
|
||||
self,
|
||||
name='',
|
||||
description='',
|
||||
job_type='run',
|
||||
playbook='ping.yml',
|
||||
credential=Credential,
|
||||
inventory=Inventory,
|
||||
project=None,
|
||||
**kwargs):
|
||||
payload, credential = self.create_payload(name=name, description=description, job_type=job_type,
|
||||
playbook=playbook, credential=credential, inventory=inventory,
|
||||
project=project, **kwargs)
|
||||
ret = self.update_identity(
|
||||
JobTemplates(
|
||||
self.connection).post(payload))
|
||||
def create(self, name='', description='', job_type='run', playbook='ping.yml', credential=Credential, inventory=Inventory, project=None, **kwargs):
|
||||
payload, credential = self.create_payload(
|
||||
name=name, description=description, job_type=job_type, playbook=playbook, credential=credential, inventory=inventory, project=project, **kwargs
|
||||
)
|
||||
ret = self.update_identity(JobTemplates(self.connection).post(payload))
|
||||
if credential:
|
||||
with suppress(exc.NoContent):
|
||||
self.related.credentials.post(dict(id=credential.id))
|
||||
if 'vault_credential' in kwargs:
|
||||
with suppress(exc.NoContent):
|
||||
if not isinstance(kwargs['vault_credential'], int):
|
||||
raise ValueError(
|
||||
"Expected 'vault_credential' value to be an integer, the id of the desired vault credential")
|
||||
self.related.credentials.post(
|
||||
dict(id=kwargs['vault_credential']))
|
||||
raise ValueError("Expected 'vault_credential' value to be an integer, the id of the desired vault credential")
|
||||
self.related.credentials.post(dict(id=kwargs['vault_credential']))
|
||||
return ret
|
||||
|
||||
def add_credential(self, credential):
|
||||
with suppress(exc.NoContent):
|
||||
self.related.credentials.post(
|
||||
dict(id=credential.id, associate=True))
|
||||
self.related.credentials.post(dict(id=credential.id, associate=True))
|
||||
|
||||
def remove_credential(self, credential):
|
||||
with suppress(exc.NoContent):
|
||||
self.related.credentials.post(
|
||||
dict(id=credential.id, disassociate=True))
|
||||
self.related.credentials.post(dict(id=credential.id, disassociate=True))
|
||||
|
||||
def remove_all_credentials(self):
|
||||
for cred in self.related.credentials.get().results:
|
||||
with suppress(exc.NoContent):
|
||||
self.related.credentials.post(
|
||||
dict(id=cred.id, disassociate=True))
|
||||
self.related.credentials.post(dict(id=cred.id, disassociate=True))
|
||||
|
||||
|
||||
page.register_page([resources.job_template,
|
||||
(resources.job_templates, 'post'),
|
||||
(resources.job_template_copy, 'post')], JobTemplate)
|
||||
page.register_page([resources.job_template, (resources.job_templates, 'post'), (resources.job_template_copy, 'post')], JobTemplate)
|
||||
|
||||
|
||||
class JobTemplates(page.PageList, JobTemplate):
|
||||
@@ -208,8 +149,7 @@ class JobTemplates(page.PageList, JobTemplate):
|
||||
pass
|
||||
|
||||
|
||||
page.register_page([resources.job_templates,
|
||||
resources.related_job_templates], JobTemplates)
|
||||
page.register_page([resources.job_templates, resources.related_job_templates], JobTemplates)
|
||||
|
||||
|
||||
class JobTemplateCallback(base.Base):
|
||||
|
||||
@@ -5,7 +5,6 @@ from . import page
|
||||
|
||||
|
||||
class Job(UnifiedJob):
|
||||
|
||||
def relaunch(self, payload={}):
|
||||
result = self.related.relaunch.post(payload)
|
||||
return self.walk(result.endpoint)
|
||||
@@ -19,9 +18,7 @@ class Jobs(page.PageList, Job):
|
||||
pass
|
||||
|
||||
|
||||
page.register_page([resources.jobs,
|
||||
resources.job_template_jobs,
|
||||
resources.system_job_template_jobs], Jobs)
|
||||
page.register_page([resources.jobs, resources.job_template_jobs, resources.system_job_template_jobs], Jobs)
|
||||
|
||||
|
||||
class JobCancel(UnifiedJob):
|
||||
@@ -37,8 +34,7 @@ class JobEvent(base.Base):
|
||||
pass
|
||||
|
||||
|
||||
page.register_page([resources.job_event,
|
||||
resources.job_job_event], JobEvent)
|
||||
page.register_page([resources.job_event, resources.job_job_event], JobEvent)
|
||||
|
||||
|
||||
class JobEvents(page.PageList, JobEvent):
|
||||
@@ -46,10 +42,7 @@ class JobEvents(page.PageList, JobEvent):
|
||||
pass
|
||||
|
||||
|
||||
page.register_page([resources.job_events,
|
||||
resources.job_job_events,
|
||||
resources.job_event_children,
|
||||
resources.group_related_job_events], JobEvents)
|
||||
page.register_page([resources.job_events, resources.job_job_events, resources.job_event_children, resources.group_related_job_events], JobEvents)
|
||||
|
||||
|
||||
class JobPlay(base.Base):
|
||||
@@ -97,8 +90,7 @@ class JobHostSummaries(page.PageList, JobHostSummary):
|
||||
pass
|
||||
|
||||
|
||||
page.register_page([resources.job_host_summaries,
|
||||
resources.group_related_job_host_summaries], JobHostSummaries)
|
||||
page.register_page([resources.job_host_summaries, resources.group_related_job_host_summaries], JobHostSummaries)
|
||||
|
||||
|
||||
class JobRelaunch(base.Base):
|
||||
|
||||
@@ -19,43 +19,24 @@ class Label(HasCreate, base.Base):
|
||||
|
||||
def payload(self, organization, **kwargs):
|
||||
payload = PseudoNamespace(
|
||||
name=kwargs.get('name') or 'Label - {}'.format(
|
||||
random_title()),
|
||||
name=kwargs.get('name') or 'Label - {}'.format(random_title()),
|
||||
description=kwargs.get('description') or random_title(10),
|
||||
organization=organization.id)
|
||||
organization=organization.id,
|
||||
)
|
||||
return payload
|
||||
|
||||
def create_payload(
|
||||
self,
|
||||
name='',
|
||||
description='',
|
||||
organization=Organization,
|
||||
**kwargs):
|
||||
def create_payload(self, name='', description='', organization=Organization, **kwargs):
|
||||
self.create_and_update_dependencies(organization)
|
||||
payload = self.payload(
|
||||
organization=self.ds.organization,
|
||||
name=name,
|
||||
description=description,
|
||||
**kwargs)
|
||||
payload = self.payload(organization=self.ds.organization, name=name, description=description, **kwargs)
|
||||
payload.ds = DSAdapter(self.__class__.__name__, self._dependency_store)
|
||||
return payload
|
||||
|
||||
def create(
|
||||
self,
|
||||
name='',
|
||||
description='',
|
||||
organization=Organization,
|
||||
**kwargs):
|
||||
payload = self.create_payload(
|
||||
name=name,
|
||||
description=description,
|
||||
organization=organization,
|
||||
**kwargs)
|
||||
def create(self, name='', description='', organization=Organization, **kwargs):
|
||||
payload = self.create_payload(name=name, description=description, organization=organization, **kwargs)
|
||||
return self.update_identity(Labels(self.connection).post(payload))
|
||||
|
||||
|
||||
page.register_page([resources.label,
|
||||
(resources.labels, 'post')], Label)
|
||||
page.register_page([resources.label, (resources.labels, 'post')], Label)
|
||||
|
||||
|
||||
class Labels(page.PageList, Label):
|
||||
@@ -63,7 +44,4 @@ class Labels(page.PageList, Label):
|
||||
pass
|
||||
|
||||
|
||||
page.register_page([resources.labels,
|
||||
resources.job_labels,
|
||||
resources.job_template_labels,
|
||||
resources.workflow_job_template_labels], Labels)
|
||||
page.register_page([resources.labels, resources.job_labels, resources.job_template_labels, resources.workflow_job_template_labels], Labels)
|
||||
|
||||
@@ -4,12 +4,9 @@ from . import page
|
||||
|
||||
|
||||
class Metrics(base.Base):
|
||||
|
||||
def get(self, **query_parameters):
|
||||
request = self.connection.get(self.endpoint, query_parameters,
|
||||
headers={'Accept': 'application/json'})
|
||||
request = self.connection.get(self.endpoint, query_parameters, headers={'Accept': 'application/json'})
|
||||
return self.page_identity(request)
|
||||
|
||||
|
||||
page.register_page([resources.metrics,
|
||||
(resources.metrics, 'get')], Metrics)
|
||||
page.register_page([resources.metrics, (resources.metrics, 'get')], Metrics)
|
||||
|
||||
@@ -9,16 +9,7 @@ from . import page
|
||||
|
||||
|
||||
job_results = ('any', 'error', 'success')
|
||||
notification_types = (
|
||||
'email',
|
||||
'irc',
|
||||
'pagerduty',
|
||||
'slack',
|
||||
'twilio',
|
||||
'webhook',
|
||||
'mattermost',
|
||||
'grafana',
|
||||
'rocketchat')
|
||||
notification_types = ('email', 'irc', 'pagerduty', 'slack', 'twilio', 'webhook', 'mattermost', 'grafana', 'rocketchat')
|
||||
|
||||
|
||||
class NotificationTemplate(HasCopy, HasCreate, base.Base):
|
||||
@@ -28,18 +19,17 @@ class NotificationTemplate(HasCopy, HasCreate, base.Base):
|
||||
|
||||
def test(self):
|
||||
"""Create test notification"""
|
||||
assert 'test' in self.related, \
|
||||
"No such related attribute 'test'"
|
||||
assert 'test' in self.related, "No such related attribute 'test'"
|
||||
|
||||
# trigger test notification
|
||||
notification_id = self.related.test.post().notification
|
||||
|
||||
# return notification page
|
||||
notifications_pg = self.get_related(
|
||||
'notifications', id=notification_id).wait_until_count(1)
|
||||
assert notifications_pg.count == 1, \
|
||||
"test notification triggered (id:%s) but notification not found in response at %s/notifications/" % \
|
||||
(notification_id, self.url)
|
||||
notifications_pg = self.get_related('notifications', id=notification_id).wait_until_count(1)
|
||||
assert notifications_pg.count == 1, "test notification triggered (id:%s) but notification not found in response at %s/notifications/" % (
|
||||
notification_id,
|
||||
self.url,
|
||||
)
|
||||
return notifications_pg.results[0]
|
||||
|
||||
def silent_delete(self):
|
||||
@@ -53,41 +43,25 @@ class NotificationTemplate(HasCopy, HasCreate, base.Base):
|
||||
|
||||
def payload(self, organization, notification_type='slack', messages=not_provided, **kwargs):
|
||||
payload = PseudoNamespace(
|
||||
name=kwargs.get('name') or 'NotificationTemplate ({0}) - {1}' .format(
|
||||
notification_type,
|
||||
random_title()),
|
||||
name=kwargs.get('name') or 'NotificationTemplate ({0}) - {1}'.format(notification_type, random_title()),
|
||||
description=kwargs.get('description') or random_title(10),
|
||||
organization=organization.id,
|
||||
notification_type=notification_type)
|
||||
notification_type=notification_type,
|
||||
)
|
||||
if messages != not_provided:
|
||||
payload['messages'] = messages
|
||||
|
||||
notification_configuration = kwargs.get(
|
||||
'notification_configuration', {})
|
||||
notification_configuration = kwargs.get('notification_configuration', {})
|
||||
payload.notification_configuration = notification_configuration
|
||||
|
||||
if payload.notification_configuration == {}:
|
||||
services = config.credentials.notification_services
|
||||
|
||||
if notification_type == 'email':
|
||||
fields = (
|
||||
'host',
|
||||
'username',
|
||||
'password',
|
||||
'port',
|
||||
'use_ssl',
|
||||
'use_tls',
|
||||
'sender',
|
||||
'recipients')
|
||||
fields = ('host', 'username', 'password', 'port', 'use_ssl', 'use_tls', 'sender', 'recipients')
|
||||
cred = services.email
|
||||
elif notification_type == 'irc':
|
||||
fields = (
|
||||
'server',
|
||||
'port',
|
||||
'use_ssl',
|
||||
'password',
|
||||
'nickname',
|
||||
'targets')
|
||||
fields = ('server', 'port', 'use_ssl', 'password', 'nickname', 'targets')
|
||||
cred = services.irc
|
||||
elif notification_type == 'pagerduty':
|
||||
fields = ('client_name', 'service_key', 'subdomain', 'token')
|
||||
@@ -96,34 +70,22 @@ class NotificationTemplate(HasCopy, HasCreate, base.Base):
|
||||
fields = ('channels', 'token')
|
||||
cred = services.slack
|
||||
elif notification_type == 'twilio':
|
||||
fields = (
|
||||
'account_sid',
|
||||
'account_token',
|
||||
'from_number',
|
||||
'to_numbers')
|
||||
fields = ('account_sid', 'account_token', 'from_number', 'to_numbers')
|
||||
cred = services.twilio
|
||||
elif notification_type == 'webhook':
|
||||
fields = ('url', 'headers')
|
||||
cred = services.webhook
|
||||
elif notification_type == 'mattermost':
|
||||
fields = (
|
||||
'mattermost_url',
|
||||
'mattermost_username',
|
||||
'mattermost_channel',
|
||||
'mattermost_icon_url',
|
||||
'mattermost_no_verify_ssl')
|
||||
fields = ('mattermost_url', 'mattermost_username', 'mattermost_channel', 'mattermost_icon_url', 'mattermost_no_verify_ssl')
|
||||
cred = services.mattermost
|
||||
elif notification_type == 'grafana':
|
||||
fields = ('grafana_url',
|
||||
'grafana_key')
|
||||
fields = ('grafana_url', 'grafana_key')
|
||||
cred = services.grafana
|
||||
elif notification_type == 'rocketchat':
|
||||
fields = ('rocketchat_url',
|
||||
'rocketchat_no_verify_ssl')
|
||||
fields = ('rocketchat_url', 'rocketchat_no_verify_ssl')
|
||||
cred = services.rocketchat
|
||||
else:
|
||||
raise ValueError(
|
||||
'Unknown notification_type {0}'.format(notification_type))
|
||||
raise ValueError('Unknown notification_type {0}'.format(notification_type))
|
||||
|
||||
for field in fields:
|
||||
if field == 'bot_token':
|
||||
@@ -136,47 +98,21 @@ class NotificationTemplate(HasCopy, HasCreate, base.Base):
|
||||
|
||||
return payload
|
||||
|
||||
def create_payload(
|
||||
self,
|
||||
name='',
|
||||
description='',
|
||||
notification_type='slack',
|
||||
organization=Organization,
|
||||
messages=not_provided,
|
||||
**kwargs):
|
||||
def create_payload(self, name='', description='', notification_type='slack', organization=Organization, messages=not_provided, **kwargs):
|
||||
if notification_type not in notification_types:
|
||||
raise ValueError(
|
||||
'Unsupported notification type "{0}". Please use one of {1}.' .format(
|
||||
notification_type, notification_types))
|
||||
raise ValueError('Unsupported notification type "{0}". Please use one of {1}.'.format(notification_type, notification_types))
|
||||
self.create_and_update_dependencies(organization)
|
||||
payload = self.payload(
|
||||
organization=self.ds.organization,
|
||||
notification_type=notification_type,
|
||||
name=name,
|
||||
description=description,
|
||||
messages=messages,
|
||||
**kwargs)
|
||||
organization=self.ds.organization, notification_type=notification_type, name=name, description=description, messages=messages, **kwargs
|
||||
)
|
||||
payload.ds = DSAdapter(self.__class__.__name__, self._dependency_store)
|
||||
return payload
|
||||
|
||||
def create(
|
||||
self,
|
||||
name='',
|
||||
description='',
|
||||
notification_type='slack',
|
||||
organization=Organization,
|
||||
messages=not_provided,
|
||||
**kwargs):
|
||||
def create(self, name='', description='', notification_type='slack', organization=Organization, messages=not_provided, **kwargs):
|
||||
payload = self.create_payload(
|
||||
name=name,
|
||||
description=description,
|
||||
notification_type=notification_type,
|
||||
organization=organization,
|
||||
messages=messages,
|
||||
**kwargs)
|
||||
return self.update_identity(
|
||||
NotificationTemplates(
|
||||
self.connection).post(payload))
|
||||
name=name, description=description, notification_type=notification_type, organization=organization, messages=messages, **kwargs
|
||||
)
|
||||
return self.update_identity(NotificationTemplates(self.connection).post(payload))
|
||||
|
||||
def associate(self, resource, job_result='any'):
|
||||
"""Associates a NotificationTemplate with the provided resource"""
|
||||
@@ -188,15 +124,11 @@ class NotificationTemplate(HasCopy, HasCreate, base.Base):
|
||||
|
||||
def _associate(self, resource, job_result='any', disassociate=False):
|
||||
if job_result not in job_results:
|
||||
raise ValueError(
|
||||
'Unsupported job_result type "{0}". Please use one of {1}.' .format(
|
||||
job_result, job_results))
|
||||
raise ValueError('Unsupported job_result type "{0}". Please use one of {1}.'.format(job_result, job_results))
|
||||
|
||||
result_attr = 'notification_templates_{0}'.format(job_result)
|
||||
if result_attr not in resource.related:
|
||||
raise ValueError(
|
||||
'Unsupported resource "{0}". Does not have a related {1} field.' .format(
|
||||
resource, result_attr))
|
||||
raise ValueError('Unsupported resource "{0}". Does not have a related {1} field.'.format(resource, result_attr))
|
||||
|
||||
payload = dict(id=self.id)
|
||||
if disassociate:
|
||||
@@ -206,14 +138,19 @@ class NotificationTemplate(HasCopy, HasCreate, base.Base):
|
||||
getattr(resource.related, result_attr).post(payload)
|
||||
|
||||
|
||||
page.register_page([resources.notification_template,
|
||||
(resources.notification_templates, 'post'),
|
||||
(resources.notification_template_copy, 'post'),
|
||||
resources.notification_template_any,
|
||||
resources.notification_template_started,
|
||||
resources.notification_template_error,
|
||||
resources.notification_template_success,
|
||||
resources.notification_template_approval], NotificationTemplate)
|
||||
page.register_page(
|
||||
[
|
||||
resources.notification_template,
|
||||
(resources.notification_templates, 'post'),
|
||||
(resources.notification_template_copy, 'post'),
|
||||
resources.notification_template_any,
|
||||
resources.notification_template_started,
|
||||
resources.notification_template_error,
|
||||
resources.notification_template_success,
|
||||
resources.notification_template_approval,
|
||||
],
|
||||
NotificationTemplate,
|
||||
)
|
||||
|
||||
|
||||
class NotificationTemplates(page.PageList, NotificationTemplate):
|
||||
@@ -221,14 +158,18 @@ class NotificationTemplates(page.PageList, NotificationTemplate):
|
||||
pass
|
||||
|
||||
|
||||
page.register_page([resources.notification_templates,
|
||||
resources.related_notification_templates,
|
||||
resources.notification_templates_any,
|
||||
resources.notification_templates_started,
|
||||
resources.notification_templates_error,
|
||||
resources.notification_templates_success,
|
||||
resources.notification_templates_approvals],
|
||||
NotificationTemplates)
|
||||
page.register_page(
|
||||
[
|
||||
resources.notification_templates,
|
||||
resources.related_notification_templates,
|
||||
resources.notification_templates_any,
|
||||
resources.notification_templates_started,
|
||||
resources.notification_templates_error,
|
||||
resources.notification_templates_success,
|
||||
resources.notification_templates_approvals,
|
||||
],
|
||||
NotificationTemplates,
|
||||
)
|
||||
|
||||
|
||||
class NotificationTemplateCopy(base.Base):
|
||||
@@ -244,6 +185,4 @@ class NotificationTemplateTest(base.Base):
|
||||
pass
|
||||
|
||||
|
||||
page.register_page(
|
||||
resources.notification_template_test,
|
||||
NotificationTemplateTest)
|
||||
page.register_page(resources.notification_template_test, NotificationTemplateTest)
|
||||
|
||||
@@ -6,10 +6,8 @@ from . import page
|
||||
|
||||
|
||||
class Notification(HasStatus, base.Base):
|
||||
|
||||
def __str__(self):
|
||||
items = ['id', 'notification_type', 'status', 'error', 'notifications_sent',
|
||||
'subject', 'recipients']
|
||||
items = ['id', 'notification_type', 'status', 'error', 'notifications_sent', 'subject', 'recipients']
|
||||
info = []
|
||||
for item in [x for x in items if hasattr(self, x)]:
|
||||
info.append('{0}:{1}'.format(item, getattr(self, item)))
|
||||
@@ -40,13 +38,10 @@ page.register_page(resources.notification, Notification)
|
||||
|
||||
|
||||
class Notifications(page.PageList, Notification):
|
||||
|
||||
def wait_until_count(self, count, interval=10, timeout=60, **kw):
|
||||
"""Poll notifications page until it is populated with `count` number of notifications."""
|
||||
poll_until(lambda: getattr(self.get(), 'count') == count,
|
||||
interval=interval, timeout=timeout, **kw)
|
||||
poll_until(lambda: getattr(self.get(), 'count') == count, interval=interval, timeout=timeout, **kw)
|
||||
return self
|
||||
|
||||
|
||||
page.register_page([resources.notifications,
|
||||
resources.related_notifications], Notifications)
|
||||
page.register_page([resources.notifications, resources.related_notifications], Notifications)
|
||||
|
||||
@@ -26,22 +26,27 @@ class Organization(HasCreate, HasInstanceGroups, HasNotifications, base.Base):
|
||||
if isinstance(credential, page.Page):
|
||||
credential = credential.json
|
||||
with suppress(exc.NoContent):
|
||||
self.related.galaxy_credentials.post({
|
||||
"id": credential.id,
|
||||
})
|
||||
self.related.galaxy_credentials.post(
|
||||
{
|
||||
"id": credential.id,
|
||||
}
|
||||
)
|
||||
|
||||
def remove_galaxy_credential(self, credential):
|
||||
if isinstance(credential, page.Page):
|
||||
credential = credential.json
|
||||
with suppress(exc.NoContent):
|
||||
self.related.galaxy_credentials.post({
|
||||
"id": credential.id,
|
||||
"disassociate": True,
|
||||
})
|
||||
self.related.galaxy_credentials.post(
|
||||
{
|
||||
"id": credential.id,
|
||||
"disassociate": True,
|
||||
}
|
||||
)
|
||||
|
||||
def payload(self, **kwargs):
|
||||
payload = PseudoNamespace(name=kwargs.get('name') or 'Organization - {}'.format(random_title()),
|
||||
description=kwargs.get('description') or random_title(10))
|
||||
payload = PseudoNamespace(
|
||||
name=kwargs.get('name') or 'Organization - {}'.format(random_title()), description=kwargs.get('description') or random_title(10)
|
||||
)
|
||||
|
||||
payload = set_payload_foreign_key_args(payload, ('default_environment',), kwargs)
|
||||
|
||||
@@ -57,8 +62,7 @@ class Organization(HasCreate, HasInstanceGroups, HasNotifications, base.Base):
|
||||
return self.update_identity(Organizations(self.connection).post(payload))
|
||||
|
||||
|
||||
page.register_page([resources.organization,
|
||||
(resources.organizations, 'post')], Organization)
|
||||
page.register_page([resources.organization, (resources.organizations, 'post')], Organization)
|
||||
|
||||
|
||||
class Organizations(page.PageList, Organization):
|
||||
@@ -66,6 +70,4 @@ class Organizations(page.PageList, Organization):
|
||||
pass
|
||||
|
||||
|
||||
page.register_page([resources.organizations,
|
||||
resources.user_organizations,
|
||||
resources.project_organizations], Organizations)
|
||||
page.register_page([resources.organizations, resources.user_organizations, resources.project_organizations], Organizations)
|
||||
|
||||
@@ -6,15 +6,7 @@ import re
|
||||
from requests import Response
|
||||
import http.client as http
|
||||
|
||||
from awxkit.utils import (
|
||||
PseudoNamespace,
|
||||
is_relative_endpoint,
|
||||
are_same_endpoint,
|
||||
super_dir_set,
|
||||
suppress,
|
||||
is_list_or_tuple,
|
||||
to_str
|
||||
)
|
||||
from awxkit.utils import PseudoNamespace, is_relative_endpoint, are_same_endpoint, super_dir_set, suppress, is_list_or_tuple, to_str
|
||||
from awxkit.api import utils
|
||||
from awxkit.api.client import Connection
|
||||
from awxkit.api.registry import URLRegistry
|
||||
@@ -41,17 +33,11 @@ def is_license_invalid(response):
|
||||
|
||||
|
||||
def is_license_exceeded(response):
|
||||
if re.match(
|
||||
r".*license range of.*instances has been exceeded.*",
|
||||
response.text):
|
||||
if re.match(r".*license range of.*instances has been exceeded.*", response.text):
|
||||
return True
|
||||
if re.match(
|
||||
r".*License count of.*instances has been reached.*",
|
||||
response.text):
|
||||
if re.match(r".*License count of.*instances has been reached.*", response.text):
|
||||
return True
|
||||
if re.match(
|
||||
r".*License count of.*instances has been exceeded.*",
|
||||
response.text):
|
||||
if re.match(r".*License count of.*instances has been exceeded.*", response.text):
|
||||
return True
|
||||
if re.match(r".*License has expired.*", response.text):
|
||||
return True
|
||||
@@ -67,6 +53,7 @@ def is_duplicate_error(response):
|
||||
def register_page(urls, page_cls):
|
||||
if not _page_registry.default:
|
||||
from awxkit.api.pages import Base
|
||||
|
||||
_page_registry.setdefault(Base)
|
||||
|
||||
if not is_list_or_tuple(urls):
|
||||
@@ -108,32 +95,23 @@ class Page(object):
|
||||
if 'endpoint' in kw:
|
||||
self.endpoint = kw['endpoint']
|
||||
|
||||
self.connection = connection or Connection(
|
||||
config.base_url, kw.get(
|
||||
'verify', not config.assume_untrusted))
|
||||
self.connection = connection or Connection(config.base_url, kw.get('verify', not config.assume_untrusted))
|
||||
|
||||
self.r = kw.get('r', None)
|
||||
self.json = kw.get(
|
||||
'json', objectify_response_json(
|
||||
self.r) if self.r else {})
|
||||
self.json = kw.get('json', objectify_response_json(self.r) if self.r else {})
|
||||
self.last_elapsed = kw.get('last_elapsed', None)
|
||||
|
||||
def __getattr__(self, name):
|
||||
if 'json' in self.__dict__ and name in self.json:
|
||||
value = self.json[name]
|
||||
if not isinstance(
|
||||
value,
|
||||
TentativePage) and is_relative_endpoint(value):
|
||||
if not isinstance(value, TentativePage) and is_relative_endpoint(value):
|
||||
value = TentativePage(value, self.connection)
|
||||
elif isinstance(value, dict):
|
||||
for key, item in value.items():
|
||||
if not isinstance(
|
||||
item, TentativePage) and is_relative_endpoint(item):
|
||||
if not isinstance(item, TentativePage) and is_relative_endpoint(item):
|
||||
value[key] = TentativePage(item, self.connection)
|
||||
return value
|
||||
raise AttributeError(
|
||||
"{!r} object has no attribute {!r}".format(
|
||||
self.__class__.__name__, name))
|
||||
raise AttributeError("{!r} object has no attribute {!r}".format(self.__class__.__name__, name))
|
||||
|
||||
def __setattr__(self, name, value):
|
||||
if 'json' in self.__dict__ and name in self.json:
|
||||
@@ -200,20 +178,15 @@ class Page(object):
|
||||
text = response.text
|
||||
if len(text) > 1024:
|
||||
text = text[:1024] + '... <<< Truncated >>> ...'
|
||||
log.debug(
|
||||
"Unable to parse JSON response ({0.status_code}): {1} - '{2}'".format(response, e, text))
|
||||
log.debug("Unable to parse JSON response ({0.status_code}): {1} - '{2}'".format(response, e, text))
|
||||
|
||||
exc_str = "%s (%s) received" % (
|
||||
http.responses[response.status_code], response.status_code)
|
||||
exc_str = "%s (%s) received" % (http.responses[response.status_code], response.status_code)
|
||||
|
||||
exception = exception_from_status_code(response.status_code)
|
||||
if exception:
|
||||
raise exception(exc_str, data)
|
||||
|
||||
if response.status_code in (
|
||||
http.OK,
|
||||
http.CREATED,
|
||||
http.ACCEPTED):
|
||||
if response.status_code in (http.OK, http.CREATED, http.ACCEPTED):
|
||||
|
||||
# Not all JSON responses include a URL. Grab it from the request
|
||||
# object, if needed.
|
||||
@@ -232,13 +205,7 @@ class Page(object):
|
||||
return self
|
||||
|
||||
registered_type = get_registered_page(request_path, request_method)
|
||||
return registered_type(
|
||||
self.connection,
|
||||
endpoint=endpoint,
|
||||
json=data,
|
||||
last_elapsed=response.elapsed,
|
||||
r=response,
|
||||
ds=ds)
|
||||
return registered_type(self.connection, endpoint=endpoint, json=data, last_elapsed=response.elapsed, r=response, ds=ds)
|
||||
|
||||
elif response.status_code == http.FORBIDDEN:
|
||||
if is_license_invalid(response):
|
||||
@@ -341,14 +308,16 @@ class Page(object):
|
||||
return natural_key
|
||||
|
||||
|
||||
_exception_map = {http.NO_CONTENT: exc.NoContent,
|
||||
http.NOT_FOUND: exc.NotFound,
|
||||
http.INTERNAL_SERVER_ERROR: exc.InternalServerError,
|
||||
http.BAD_GATEWAY: exc.BadGateway,
|
||||
http.METHOD_NOT_ALLOWED: exc.MethodNotAllowed,
|
||||
http.UNAUTHORIZED: exc.Unauthorized,
|
||||
http.PAYMENT_REQUIRED: exc.PaymentRequired,
|
||||
http.CONFLICT: exc.Conflict}
|
||||
_exception_map = {
|
||||
http.NO_CONTENT: exc.NoContent,
|
||||
http.NOT_FOUND: exc.NotFound,
|
||||
http.INTERNAL_SERVER_ERROR: exc.InternalServerError,
|
||||
http.BAD_GATEWAY: exc.BadGateway,
|
||||
http.METHOD_NOT_ALLOWED: exc.MethodNotAllowed,
|
||||
http.UNAUTHORIZED: exc.Unauthorized,
|
||||
http.PAYMENT_REQUIRED: exc.PaymentRequired,
|
||||
http.CONFLICT: exc.Conflict,
|
||||
}
|
||||
|
||||
|
||||
def exception_from_status_code(status_code):
|
||||
@@ -380,12 +349,7 @@ class PageList(object):
|
||||
registered_type = self.__item_class__
|
||||
else:
|
||||
registered_type = get_registered_page(endpoint)
|
||||
items.append(
|
||||
registered_type(
|
||||
self.connection,
|
||||
endpoint=endpoint,
|
||||
json=item,
|
||||
r=self.r))
|
||||
items.append(registered_type(self.connection, endpoint=endpoint, json=item, r=self.r))
|
||||
return items
|
||||
|
||||
def go_to_next(self):
|
||||
@@ -407,7 +371,6 @@ class PageList(object):
|
||||
|
||||
|
||||
class TentativePage(str):
|
||||
|
||||
def __new__(cls, endpoint, connection):
|
||||
return super(TentativePage, cls).__new__(cls, to_str(endpoint))
|
||||
|
||||
@@ -416,10 +379,7 @@ class TentativePage(str):
|
||||
self.connection = connection
|
||||
|
||||
def _create(self):
|
||||
return get_registered_page(
|
||||
self.endpoint)(
|
||||
self.connection,
|
||||
endpoint=self.endpoint)
|
||||
return get_registered_page(self.endpoint)(self.connection, endpoint=self.endpoint)
|
||||
|
||||
def get(self, **params):
|
||||
return self._create().get(**params)
|
||||
@@ -436,21 +396,15 @@ class TentativePage(str):
|
||||
page = None
|
||||
# look up users by username not name
|
||||
if 'users' in self:
|
||||
assert query_parameters.get(
|
||||
'username'), 'For this resource, you must call this method with a "username" to look up the object by'
|
||||
assert query_parameters.get('username'), 'For this resource, you must call this method with a "username" to look up the object by'
|
||||
page = self.get(username=query_parameters['username'])
|
||||
else:
|
||||
assert query_parameters.get(
|
||||
'name'), 'For this resource, you must call this method with a "name" to look up the object by'
|
||||
assert query_parameters.get('name'), 'For this resource, you must call this method with a "name" to look up the object by'
|
||||
if query_parameters.get('organization'):
|
||||
if isinstance(query_parameters.get('organization'), int):
|
||||
page = self.get(
|
||||
name=query_parameters['name'],
|
||||
organization=query_parameters.get('organization'))
|
||||
page = self.get(name=query_parameters['name'], organization=query_parameters.get('organization'))
|
||||
else:
|
||||
page = self.get(
|
||||
name=query_parameters['name'],
|
||||
organization=query_parameters.get('organization').id)
|
||||
page = self.get(name=query_parameters['name'], organization=query_parameters.get('organization').id)
|
||||
else:
|
||||
page = self.get(name=query_parameters['name'])
|
||||
if page and page.results:
|
||||
@@ -476,13 +430,9 @@ class TentativePage(str):
|
||||
if query_parameters.get('name'):
|
||||
if query_parameters.get('organization'):
|
||||
if isinstance(query_parameters.get('organization'), int):
|
||||
page = self.get(
|
||||
name=query_parameters['name'],
|
||||
organization=query_parameters.get('organization'))
|
||||
page = self.get(name=query_parameters['name'], organization=query_parameters.get('organization'))
|
||||
else:
|
||||
page = self.get(
|
||||
name=query_parameters['name'],
|
||||
organization=query_parameters.get('organization').id)
|
||||
page = self.get(name=query_parameters['name'], organization=query_parameters.get('organization').id)
|
||||
else:
|
||||
page = self.get(name=query_parameters['name'])
|
||||
|
||||
|
||||
@@ -18,13 +18,11 @@ class Project(HasCopy, HasCreate, HasNotifications, UnifiedJobTemplate):
|
||||
|
||||
def payload(self, organization, scm_type='git', **kwargs):
|
||||
payload = PseudoNamespace(
|
||||
name=kwargs.get('name') or 'Project - {}'.format(
|
||||
random_title()),
|
||||
name=kwargs.get('name') or 'Project - {}'.format(random_title()),
|
||||
description=kwargs.get('description') or random_title(10),
|
||||
scm_type=scm_type,
|
||||
scm_url=kwargs.get('scm_url') or config.project_urls.get(
|
||||
scm_type,
|
||||
''))
|
||||
scm_url=kwargs.get('scm_url') or config.project_urls.get(scm_type, ''),
|
||||
)
|
||||
|
||||
if organization is not None:
|
||||
payload.organization = organization.id
|
||||
@@ -40,43 +38,25 @@ class Project(HasCopy, HasCreate, HasNotifications, UnifiedJobTemplate):
|
||||
'scm_update_cache_timeout',
|
||||
'scm_update_on_launch',
|
||||
'scm_refspec',
|
||||
'allow_override')
|
||||
'allow_override',
|
||||
)
|
||||
update_payload(payload, fields, kwargs)
|
||||
|
||||
payload = set_payload_foreign_key_args(payload, ('execution_environment', 'default_environment'), kwargs)
|
||||
|
||||
return payload
|
||||
|
||||
def create_payload(
|
||||
self,
|
||||
name='',
|
||||
description='',
|
||||
scm_type='git',
|
||||
scm_url='',
|
||||
scm_branch='',
|
||||
organization=Organization,
|
||||
credential=None,
|
||||
**kwargs):
|
||||
def create_payload(self, name='', description='', scm_type='git', scm_url='', scm_branch='', organization=Organization, credential=None, **kwargs):
|
||||
if credential:
|
||||
if isinstance(credential, Credential):
|
||||
if credential.ds.credential_type.namespace not in (
|
||||
'scm', 'insights'):
|
||||
if credential.ds.credential_type.namespace not in ('scm', 'insights'):
|
||||
credential = None # ignore incompatible credential from HasCreate dependency injection
|
||||
elif credential in (Credential,):
|
||||
credential = (
|
||||
Credential, dict(
|
||||
credential_type=(
|
||||
True, dict(
|
||||
kind='scm'))))
|
||||
credential = (Credential, dict(credential_type=(True, dict(kind='scm'))))
|
||||
elif credential is True:
|
||||
credential = (
|
||||
Credential, dict(
|
||||
credential_type=(
|
||||
True, dict(
|
||||
kind='scm'))))
|
||||
credential = (Credential, dict(credential_type=(True, dict(kind='scm'))))
|
||||
|
||||
self.create_and_update_dependencies(
|
||||
*filter_by_class((credential, Credential), (organization, Organization)))
|
||||
self.create_and_update_dependencies(*filter_by_class((credential, Credential), (organization, Organization)))
|
||||
|
||||
credential = self.ds.credential if credential else None
|
||||
organization = self.ds.organization if organization else None
|
||||
@@ -89,20 +69,12 @@ class Project(HasCopy, HasCreate, HasNotifications, UnifiedJobTemplate):
|
||||
scm_url=scm_url,
|
||||
scm_branch=scm_branch,
|
||||
credential=credential,
|
||||
**kwargs)
|
||||
**kwargs
|
||||
)
|
||||
payload.ds = DSAdapter(self.__class__.__name__, self._dependency_store)
|
||||
return payload
|
||||
|
||||
def create(
|
||||
self,
|
||||
name='',
|
||||
description='',
|
||||
scm_type='git',
|
||||
scm_url='',
|
||||
scm_branch='',
|
||||
organization=Organization,
|
||||
credential=None,
|
||||
**kwargs):
|
||||
def create(self, name='', description='', scm_type='git', scm_url='', scm_branch='', organization=Organization, credential=None, **kwargs):
|
||||
payload = self.create_payload(
|
||||
name=name,
|
||||
description=description,
|
||||
@@ -111,7 +83,8 @@ class Project(HasCopy, HasCreate, HasNotifications, UnifiedJobTemplate):
|
||||
scm_branch=scm_branch,
|
||||
organization=organization,
|
||||
credential=credential,
|
||||
**kwargs)
|
||||
**kwargs
|
||||
)
|
||||
self.update_identity(Projects(self.connection).post(payload))
|
||||
|
||||
if kwargs.get('wait', True):
|
||||
@@ -127,25 +100,20 @@ class Project(HasCopy, HasCreate, HasNotifications, UnifiedJobTemplate):
|
||||
update_pg = self.get_related('update')
|
||||
|
||||
# assert can_update == True
|
||||
assert update_pg.can_update, \
|
||||
"The specified project (id:%s) is not able to update (can_update:%s)" % \
|
||||
(self.id, update_pg.can_update)
|
||||
assert update_pg.can_update, "The specified project (id:%s) is not able to update (can_update:%s)" % (self.id, update_pg.can_update)
|
||||
|
||||
# start the update
|
||||
result = update_pg.post()
|
||||
|
||||
# assert JSON response
|
||||
assert 'project_update' in result.json, \
|
||||
"Unexpected JSON response when starting an project_update.\n%s" % \
|
||||
json.dumps(result.json, indent=2)
|
||||
assert 'project_update' in result.json, "Unexpected JSON response when starting an project_update.\n%s" % json.dumps(result.json, indent=2)
|
||||
|
||||
# locate and return the specific update
|
||||
jobs_pg = self.get_related(
|
||||
'project_updates',
|
||||
id=result.json['project_update'])
|
||||
assert jobs_pg.count == 1, \
|
||||
"An project_update started (id:%s) but job not found in response at %s/inventory_updates/" % \
|
||||
(result.json['project_update'], self.url)
|
||||
jobs_pg = self.get_related('project_updates', id=result.json['project_update'])
|
||||
assert jobs_pg.count == 1, "An project_update started (id:%s) but job not found in response at %s/inventory_updates/" % (
|
||||
result.json['project_update'],
|
||||
self.url,
|
||||
)
|
||||
return jobs_pg.results[0]
|
||||
|
||||
@property
|
||||
@@ -154,13 +122,10 @@ class Project(HasCopy, HasCreate, HasNotifications, UnifiedJobTemplate):
|
||||
0) scm_type != ""
|
||||
1) unified_job_template.is_successful
|
||||
"""
|
||||
return self.scm_type != "" and \
|
||||
super(Project, self).is_successful
|
||||
return self.scm_type != "" and super(Project, self).is_successful
|
||||
|
||||
|
||||
page.register_page([resources.project,
|
||||
(resources.projects, 'post'),
|
||||
(resources.project_copy, 'post')], Project)
|
||||
page.register_page([resources.project, (resources.projects, 'post'), (resources.project_copy, 'post')], Project)
|
||||
|
||||
|
||||
class Projects(page.PageList, Project):
|
||||
@@ -168,8 +133,7 @@ class Projects(page.PageList, Project):
|
||||
pass
|
||||
|
||||
|
||||
page.register_page([resources.projects,
|
||||
resources.related_projects], Projects)
|
||||
page.register_page([resources.projects, resources.related_projects], Projects)
|
||||
|
||||
|
||||
class ProjectUpdate(UnifiedJob):
|
||||
@@ -185,8 +149,7 @@ class ProjectUpdates(page.PageList, ProjectUpdate):
|
||||
pass
|
||||
|
||||
|
||||
page.register_page([resources.project_updates,
|
||||
resources.project_project_updates], ProjectUpdates)
|
||||
page.register_page([resources.project_updates, resources.project_project_updates], ProjectUpdates)
|
||||
|
||||
|
||||
class ProjectUpdateLaunch(base.Base):
|
||||
|
||||
@@ -18,15 +18,11 @@ class Role(base.Base):
|
||||
cache = page.PageCache()
|
||||
|
||||
natural_key = super(Role, self).get_natural_key(cache=cache)
|
||||
related_objs = [
|
||||
related for name, related in self.related.items()
|
||||
if name not in ('users', 'teams')
|
||||
]
|
||||
related_objs = [related for name, related in self.related.items() if name not in ('users', 'teams')]
|
||||
if related_objs:
|
||||
related_endpoint = cache.get_page(related_objs[0])
|
||||
if related_endpoint is None:
|
||||
log.error("Unable to obtain content_object %s for role %s",
|
||||
related_objs[0], self.endpoint)
|
||||
log.error("Unable to obtain content_object %s for role %s", related_objs[0], self.endpoint)
|
||||
return None
|
||||
natural_key['content_object'] = related_endpoint.get_natural_key(cache=cache)
|
||||
|
||||
@@ -41,6 +37,4 @@ class Roles(page.PageList, Role):
|
||||
pass
|
||||
|
||||
|
||||
page.register_page([resources.roles,
|
||||
resources.related_roles,
|
||||
resources.related_object_roles], Roles)
|
||||
page.register_page([resources.roles, resources.related_roles, resources.related_object_roles], Roles)
|
||||
|
||||
@@ -11,12 +11,10 @@ class Schedule(UnifiedJob):
|
||||
NATURAL_KEY = ('unified_job_template', 'name')
|
||||
|
||||
|
||||
page.register_page([resources.schedule,
|
||||
resources.related_schedule], Schedule)
|
||||
page.register_page([resources.schedule, resources.related_schedule], Schedule)
|
||||
|
||||
|
||||
class Schedules(page.PageList, Schedule):
|
||||
|
||||
def get_zoneinfo(self):
|
||||
return SchedulesZoneInfo(self.connection).get()
|
||||
|
||||
@@ -33,8 +31,7 @@ class Schedules(page.PageList, Schedule):
|
||||
self.related.credentials.post(dict(id=cred.id, disassociate=True))
|
||||
|
||||
|
||||
page.register_page([resources.schedules,
|
||||
resources.related_schedules], Schedules)
|
||||
page.register_page([resources.schedules, resources.related_schedules], Schedules)
|
||||
|
||||
|
||||
class SchedulesPreview(base.Base):
|
||||
@@ -46,7 +43,6 @@ page.register_page(((resources.schedules_preview, 'post'),), SchedulesPreview)
|
||||
|
||||
|
||||
class SchedulesZoneInfo(base.Base):
|
||||
|
||||
def __getitem__(self, idx):
|
||||
return self.json[idx]
|
||||
|
||||
|
||||
@@ -8,27 +8,31 @@ class Setting(base.Base):
|
||||
pass
|
||||
|
||||
|
||||
page.register_page([resources.setting,
|
||||
resources.settings_all,
|
||||
resources.settings_authentication,
|
||||
resources.settings_changed,
|
||||
resources.settings_github,
|
||||
resources.settings_github_org,
|
||||
resources.settings_github_team,
|
||||
resources.settings_google_oauth2,
|
||||
resources.settings_jobs,
|
||||
resources.settings_ldap,
|
||||
resources.settings_radius,
|
||||
resources.settings_saml,
|
||||
resources.settings_system,
|
||||
resources.settings_tacacsplus,
|
||||
resources.settings_ui,
|
||||
resources.settings_user,
|
||||
resources.settings_user_defaults], Setting)
|
||||
page.register_page(
|
||||
[
|
||||
resources.setting,
|
||||
resources.settings_all,
|
||||
resources.settings_authentication,
|
||||
resources.settings_changed,
|
||||
resources.settings_github,
|
||||
resources.settings_github_org,
|
||||
resources.settings_github_team,
|
||||
resources.settings_google_oauth2,
|
||||
resources.settings_jobs,
|
||||
resources.settings_ldap,
|
||||
resources.settings_radius,
|
||||
resources.settings_saml,
|
||||
resources.settings_system,
|
||||
resources.settings_tacacsplus,
|
||||
resources.settings_ui,
|
||||
resources.settings_user,
|
||||
resources.settings_user_defaults,
|
||||
],
|
||||
Setting,
|
||||
)
|
||||
|
||||
|
||||
class Settings(page.PageList, Setting):
|
||||
|
||||
def get_endpoint(self, endpoint):
|
||||
"""Helper method used to navigate to a specific settings endpoint.
|
||||
(Pdb) settings_pg.get_endpoint('all')
|
||||
|
||||
@@ -3,7 +3,6 @@ from . import page
|
||||
|
||||
|
||||
class Subscriptions(page.Page):
|
||||
|
||||
def get_possible_licenses(self, **kwargs):
|
||||
return self.post(json=kwargs).json
|
||||
|
||||
|
||||
@@ -5,7 +5,6 @@ from awxkit.api.resources import resources
|
||||
|
||||
|
||||
class SurveySpec(base.Base):
|
||||
|
||||
def get_variable_default(self, var):
|
||||
for item in self.spec:
|
||||
if item.get('variable') == var:
|
||||
@@ -26,5 +25,4 @@ class SurveySpec(base.Base):
|
||||
return required_vars
|
||||
|
||||
|
||||
page.register_page([resources.job_template_survey_spec,
|
||||
resources.workflow_job_template_survey_spec], SurveySpec)
|
||||
page.register_page([resources.job_template_survey_spec, resources.workflow_job_template_survey_spec], SurveySpec)
|
||||
|
||||
@@ -5,16 +5,13 @@ from . import page
|
||||
|
||||
|
||||
class SystemJobTemplate(UnifiedJobTemplate, HasNotifications):
|
||||
|
||||
def launch(self, payload={}):
|
||||
"""Launch the system_job_template using related->launch endpoint."""
|
||||
result = self.related.launch.post(payload)
|
||||
|
||||
# return job
|
||||
jobs_pg = self.get_related('jobs', id=result.json['system_job'])
|
||||
assert jobs_pg.count == 1, \
|
||||
"system_job_template launched (id:%s) but unable to find matching " \
|
||||
"job at %s/jobs/" % (result.json['job'], self.url)
|
||||
assert jobs_pg.count == 1, "system_job_template launched (id:%s) but unable to find matching " "job at %s/jobs/" % (result.json['job'], self.url)
|
||||
return jobs_pg.results[0]
|
||||
|
||||
|
||||
|
||||
@@ -20,9 +20,11 @@ class Team(HasCreate, base.Base):
|
||||
self.related.users.post(user)
|
||||
|
||||
def payload(self, organization, **kwargs):
|
||||
payload = PseudoNamespace(name=kwargs.get('name') or 'Team - {}'.format(random_title()),
|
||||
description=kwargs.get('description') or random_title(10),
|
||||
organization=organization.id)
|
||||
payload = PseudoNamespace(
|
||||
name=kwargs.get('name') or 'Team - {}'.format(random_title()),
|
||||
description=kwargs.get('description') or random_title(10),
|
||||
organization=organization.id,
|
||||
)
|
||||
return payload
|
||||
|
||||
def create_payload(self, name='', description='', organization=Organization, **kwargs):
|
||||
@@ -36,8 +38,7 @@ class Team(HasCreate, base.Base):
|
||||
return self.update_identity(Teams(self.connection).post(payload))
|
||||
|
||||
|
||||
page.register_page([resources.team,
|
||||
(resources.teams, 'post')], Team)
|
||||
page.register_page([resources.team, (resources.teams, 'post')], Team)
|
||||
|
||||
|
||||
class Teams(page.PageList, Team):
|
||||
@@ -45,6 +46,4 @@ class Teams(page.PageList, Team):
|
||||
pass
|
||||
|
||||
|
||||
page.register_page([resources.teams,
|
||||
resources.credential_owner_teams,
|
||||
resources.related_teams], Teams)
|
||||
page.register_page([resources.teams, resources.credential_owner_teams, resources.related_teams], Teams)
|
||||
|
||||
@@ -26,38 +26,19 @@ class UnifiedJobTemplate(HasStatus, base.Base):
|
||||
# formatting issue where result_stdout contained '%s'. This later caused
|
||||
# a python traceback when attempting to display output from this
|
||||
# method.
|
||||
items = [
|
||||
'id',
|
||||
'name',
|
||||
'status',
|
||||
'source',
|
||||
'last_update_failed',
|
||||
'last_updated',
|
||||
'result_traceback',
|
||||
'job_explanation',
|
||||
'job_args']
|
||||
items = ['id', 'name', 'status', 'source', 'last_update_failed', 'last_updated', 'result_traceback', 'job_explanation', 'job_args']
|
||||
info = []
|
||||
for item in [x for x in items if hasattr(self, x)]:
|
||||
info.append('{0}:{1}'.format(item, getattr(self, item)))
|
||||
output = '<{0.__class__.__name__} {1}>'.format(self, ', '.join(info))
|
||||
return output.replace('%', '%%')
|
||||
|
||||
def add_schedule(
|
||||
self,
|
||||
name='',
|
||||
description='',
|
||||
enabled=True,
|
||||
rrule=None,
|
||||
**kwargs):
|
||||
def add_schedule(self, name='', description='', enabled=True, rrule=None, **kwargs):
|
||||
if rrule is None:
|
||||
rrule = "DTSTART:30180101T000000Z RRULE:FREQ=YEARLY;INTERVAL=1"
|
||||
payload = dict(
|
||||
name=name or "{0} Schedule {1}".format(
|
||||
self.name,
|
||||
random_title()),
|
||||
description=description or random_title(10),
|
||||
enabled=enabled,
|
||||
rrule=str(rrule))
|
||||
name=name or "{0} Schedule {1}".format(self.name, random_title()), description=description or random_title(10), enabled=enabled, rrule=str(rrule)
|
||||
)
|
||||
|
||||
update_payload(payload, self.optional_schedule_fields, kwargs)
|
||||
|
||||
@@ -70,9 +51,7 @@ class UnifiedJobTemplate(HasStatus, base.Base):
|
||||
2) not last_update_failed
|
||||
3) last_updated
|
||||
"""
|
||||
return super(
|
||||
UnifiedJobTemplate,
|
||||
self).is_successful and not self.last_update_failed and self.last_updated is not None
|
||||
return super(UnifiedJobTemplate, self).is_successful and not self.last_update_failed and self.last_updated is not None
|
||||
|
||||
|
||||
page.register_page(resources.unified_job_template, UnifiedJobTemplate)
|
||||
|
||||
@@ -21,8 +21,7 @@ class UnifiedJob(HasStatus, base.Base):
|
||||
# NOTE: I use .replace('%', '%%') to workaround an odd string
|
||||
# formatting issue where result_stdout contained '%s'. This later caused
|
||||
# a python traceback when attempting to display output from this method.
|
||||
items = ['id', 'name', 'status', 'failed', 'result_stdout', 'result_traceback',
|
||||
'job_explanation', 'job_args']
|
||||
items = ['id', 'name', 'status', 'failed', 'result_stdout', 'result_traceback', 'job_explanation', 'job_args']
|
||||
info = []
|
||||
for item in [x for x in items if hasattr(self, x)]:
|
||||
info.append('{0}:{1}'.format(item, getattr(self, item)))
|
||||
@@ -32,9 +31,7 @@ class UnifiedJob(HasStatus, base.Base):
|
||||
@property
|
||||
def result_stdout(self):
|
||||
if 'result_stdout' not in self.json and 'stdout' in self.related:
|
||||
return self.connection.get(
|
||||
self.related.stdout, query_parameters=dict(format='txt_download')
|
||||
).content.decode()
|
||||
return self.connection.get(self.related.stdout, query_parameters=dict(format='txt_download')).content.decode()
|
||||
return self.json.result_stdout.decode()
|
||||
|
||||
def assert_text_in_stdout(self, expected_text, replace_spaces=None, replace_newlines=' '):
|
||||
@@ -55,9 +52,7 @@ class UnifiedJob(HasStatus, base.Base):
|
||||
stdout = stdout.replace(' ', replace_spaces)
|
||||
if expected_text not in stdout:
|
||||
pretty_stdout = pformat(stdout)
|
||||
raise AssertionError(
|
||||
'Expected "{}", but it was not found in stdout. Full stdout:\n {}'.format(expected_text, pretty_stdout)
|
||||
)
|
||||
raise AssertionError('Expected "{}", but it was not found in stdout. Full stdout:\n {}'.format(expected_text, pretty_stdout))
|
||||
|
||||
@property
|
||||
def is_successful(self):
|
||||
@@ -103,7 +98,7 @@ class UnifiedJob(HasStatus, base.Base):
|
||||
# Race condition where job finishes between can_cancel
|
||||
# check and post.
|
||||
if not any("not allowed" in field for field in e.msg.values()):
|
||||
raise(e)
|
||||
raise (e)
|
||||
return self.get()
|
||||
|
||||
@property
|
||||
@@ -114,6 +109,7 @@ class UnifiedJob(HasStatus, base.Base):
|
||||
```assert dict(extra_var=extra_var_val) in unified_job.job_args```
|
||||
If you need to ensure the job_args are of awx-provided format use raw unified_job.json.job_args.
|
||||
"""
|
||||
|
||||
def attempt_yaml_load(arg):
|
||||
try:
|
||||
return yaml.safe_load(arg)
|
||||
@@ -151,10 +147,7 @@ class UnifiedJob(HasStatus, base.Base):
|
||||
if host_loc.startswith(expected_prefix):
|
||||
return host_loc
|
||||
raise RuntimeError(
|
||||
'Could not find a controller private_data_dir for this job. '
|
||||
'Searched for volume mount to {} inside of args {}'.format(
|
||||
expected_prefix, job_args
|
||||
)
|
||||
'Could not find a controller private_data_dir for this job. ' 'Searched for volume mount to {} inside of args {}'.format(expected_prefix, job_args)
|
||||
)
|
||||
|
||||
|
||||
@@ -163,7 +156,4 @@ class UnifiedJobs(page.PageList, UnifiedJob):
|
||||
pass
|
||||
|
||||
|
||||
page.register_page([resources.unified_jobs,
|
||||
resources.instance_related_jobs,
|
||||
resources.instance_group_related_jobs,
|
||||
resources.schedules_jobs], UnifiedJobs)
|
||||
page.register_page([resources.unified_jobs, resources.instance_related_jobs, resources.instance_group_related_jobs, resources.schedules_jobs], UnifiedJobs)
|
||||
|
||||
@@ -13,26 +13,13 @@ class User(HasCreate, base.Base):
|
||||
|
||||
def payload(self, **kwargs):
|
||||
payload = PseudoNamespace(
|
||||
username=kwargs.get('username') or 'User-{}'.format(
|
||||
random_title(
|
||||
non_ascii=False)),
|
||||
username=kwargs.get('username') or 'User-{}'.format(random_title(non_ascii=False)),
|
||||
password=kwargs.get('password') or config.credentials.default.password,
|
||||
is_superuser=kwargs.get(
|
||||
'is_superuser',
|
||||
False),
|
||||
is_system_auditor=kwargs.get(
|
||||
'is_system_auditor',
|
||||
False),
|
||||
first_name=kwargs.get(
|
||||
'first_name',
|
||||
random_title()),
|
||||
last_name=kwargs.get(
|
||||
'last_name',
|
||||
random_title()),
|
||||
email=kwargs.get(
|
||||
'email',
|
||||
'{}@example.com'.format(random_title(5, non_ascii=False))
|
||||
)
|
||||
is_superuser=kwargs.get('is_superuser', False),
|
||||
is_system_auditor=kwargs.get('is_system_auditor', False),
|
||||
first_name=kwargs.get('first_name', random_title()),
|
||||
last_name=kwargs.get('last_name', random_title()),
|
||||
email=kwargs.get('email', '{}@example.com'.format(random_title(5, non_ascii=False))),
|
||||
)
|
||||
return payload
|
||||
|
||||
@@ -42,8 +29,7 @@ class User(HasCreate, base.Base):
|
||||
return payload
|
||||
|
||||
def create(self, username='', password='', organization=None, **kwargs):
|
||||
payload = self.create_payload(
|
||||
username=username, password=password, **kwargs)
|
||||
payload = self.create_payload(username=username, password=password, **kwargs)
|
||||
self.password = payload.password
|
||||
|
||||
self.update_identity(Users(self.connection).post(payload))
|
||||
@@ -54,8 +40,7 @@ class User(HasCreate, base.Base):
|
||||
return self
|
||||
|
||||
|
||||
page.register_page([resources.user,
|
||||
(resources.users, 'post')], User)
|
||||
page.register_page([resources.user, (resources.users, 'post')], User)
|
||||
|
||||
|
||||
class Users(page.PageList, User):
|
||||
@@ -63,11 +48,9 @@ class Users(page.PageList, User):
|
||||
pass
|
||||
|
||||
|
||||
page.register_page([resources.users,
|
||||
resources.organization_admins,
|
||||
resources.related_users,
|
||||
resources.credential_owner_users,
|
||||
resources.user_admin_organizations], Users)
|
||||
page.register_page(
|
||||
[resources.users, resources.organization_admins, resources.related_users, resources.credential_owner_users, resources.user_admin_organizations], Users
|
||||
)
|
||||
|
||||
|
||||
class Me(Users):
|
||||
|
||||
@@ -5,7 +5,6 @@ from awxkit import exceptions
|
||||
|
||||
|
||||
class WorkflowApproval(UnifiedJob):
|
||||
|
||||
def approve(self):
|
||||
try:
|
||||
self.related.approve.post()
|
||||
|
||||
@@ -5,7 +5,6 @@ from . import page
|
||||
|
||||
|
||||
class WorkflowJobNode(base.Base):
|
||||
|
||||
def wait_for_job(self, interval=5, timeout=60, **kw):
|
||||
"""Waits until node's job exists"""
|
||||
adjusted_timeout = timeout - seconds_since_date_string(self.created)
|
||||
@@ -30,8 +29,13 @@ class WorkflowJobNodes(page.PageList, WorkflowJobNode):
|
||||
pass
|
||||
|
||||
|
||||
page.register_page([resources.workflow_job_nodes,
|
||||
resources.workflow_job_workflow_nodes,
|
||||
resources.workflow_job_node_always_nodes,
|
||||
resources.workflow_job_node_failure_nodes,
|
||||
resources.workflow_job_node_success_nodes], WorkflowJobNodes)
|
||||
page.register_page(
|
||||
[
|
||||
resources.workflow_job_nodes,
|
||||
resources.workflow_job_workflow_nodes,
|
||||
resources.workflow_job_node_always_nodes,
|
||||
resources.workflow_job_node_failure_nodes,
|
||||
resources.workflow_job_node_success_nodes,
|
||||
],
|
||||
WorkflowJobNodes,
|
||||
)
|
||||
|
||||
@@ -15,12 +15,9 @@ class WorkflowJobTemplateNode(HasCreate, base.Base):
|
||||
def payload(self, workflow_job_template, unified_job_template, **kwargs):
|
||||
if not unified_job_template:
|
||||
# May pass "None" to explicitly create an approval node
|
||||
payload = PseudoNamespace(
|
||||
workflow_job_template=workflow_job_template.id)
|
||||
payload = PseudoNamespace(workflow_job_template=workflow_job_template.id)
|
||||
else:
|
||||
payload = PseudoNamespace(
|
||||
workflow_job_template=workflow_job_template.id,
|
||||
unified_job_template=unified_job_template.id)
|
||||
payload = PseudoNamespace(workflow_job_template=workflow_job_template.id, unified_job_template=unified_job_template.id)
|
||||
|
||||
optional_fields = (
|
||||
'diff_mode',
|
||||
@@ -33,7 +30,8 @@ class WorkflowJobTemplateNode(HasCreate, base.Base):
|
||||
'verbosity',
|
||||
'extra_data',
|
||||
'identifier',
|
||||
'all_parents_must_converge')
|
||||
'all_parents_must_converge',
|
||||
)
|
||||
|
||||
update_payload(payload, optional_fields, kwargs)
|
||||
|
||||
@@ -42,45 +40,23 @@ class WorkflowJobTemplateNode(HasCreate, base.Base):
|
||||
|
||||
return payload
|
||||
|
||||
def create_payload(
|
||||
self,
|
||||
workflow_job_template=WorkflowJobTemplate,
|
||||
unified_job_template=JobTemplate,
|
||||
**kwargs):
|
||||
def create_payload(self, workflow_job_template=WorkflowJobTemplate, unified_job_template=JobTemplate, **kwargs):
|
||||
if not unified_job_template:
|
||||
self.create_and_update_dependencies(workflow_job_template)
|
||||
payload = self.payload(
|
||||
workflow_job_template=self.ds.workflow_job_template,
|
||||
unified_job_template=None,
|
||||
**kwargs)
|
||||
payload = self.payload(workflow_job_template=self.ds.workflow_job_template, unified_job_template=None, **kwargs)
|
||||
else:
|
||||
self.create_and_update_dependencies(
|
||||
workflow_job_template, unified_job_template)
|
||||
payload = self.payload(
|
||||
workflow_job_template=self.ds.workflow_job_template,
|
||||
unified_job_template=self.ds.unified_job_template,
|
||||
**kwargs)
|
||||
self.create_and_update_dependencies(workflow_job_template, unified_job_template)
|
||||
payload = self.payload(workflow_job_template=self.ds.workflow_job_template, unified_job_template=self.ds.unified_job_template, **kwargs)
|
||||
payload.ds = DSAdapter(self.__class__.__name__, self._dependency_store)
|
||||
return payload
|
||||
|
||||
def create(
|
||||
self,
|
||||
workflow_job_template=WorkflowJobTemplate,
|
||||
unified_job_template=JobTemplate,
|
||||
**kwargs):
|
||||
payload = self.create_payload(
|
||||
workflow_job_template=workflow_job_template,
|
||||
unified_job_template=unified_job_template,
|
||||
**kwargs)
|
||||
return self.update_identity(
|
||||
WorkflowJobTemplateNodes(
|
||||
self.connection).post(payload))
|
||||
def create(self, workflow_job_template=WorkflowJobTemplate, unified_job_template=JobTemplate, **kwargs):
|
||||
payload = self.create_payload(workflow_job_template=workflow_job_template, unified_job_template=unified_job_template, **kwargs)
|
||||
return self.update_identity(WorkflowJobTemplateNodes(self.connection).post(payload))
|
||||
|
||||
def _add_node(self, endpoint, unified_job_template, **kwargs):
|
||||
node = endpoint.post(
|
||||
dict(unified_job_template=unified_job_template.id, **kwargs))
|
||||
node.create_and_update_dependencies(
|
||||
self.ds.workflow_job_template, unified_job_template)
|
||||
node = endpoint.post(dict(unified_job_template=unified_job_template.id, **kwargs))
|
||||
node.create_and_update_dependencies(self.ds.workflow_job_template, unified_job_template)
|
||||
return node
|
||||
|
||||
def add_always_node(self, unified_job_template, **kwargs):
|
||||
@@ -94,24 +70,18 @@ class WorkflowJobTemplateNode(HasCreate, base.Base):
|
||||
|
||||
def add_credential(self, credential):
|
||||
with suppress(exc.NoContent):
|
||||
self.related.credentials.post(
|
||||
dict(id=credential.id, associate=True))
|
||||
self.related.credentials.post(dict(id=credential.id, associate=True))
|
||||
|
||||
def remove_credential(self, credential):
|
||||
with suppress(exc.NoContent):
|
||||
self.related.credentials.post(
|
||||
dict(id=credential.id, disassociate=True))
|
||||
self.related.credentials.post(dict(id=credential.id, disassociate=True))
|
||||
|
||||
def remove_all_credentials(self):
|
||||
for cred in self.related.credentials.get().results:
|
||||
with suppress(exc.NoContent):
|
||||
self.related.credentials.post(
|
||||
dict(id=cred.id, disassociate=True))
|
||||
self.related.credentials.post(dict(id=cred.id, disassociate=True))
|
||||
|
||||
def make_approval_node(
|
||||
self,
|
||||
**kwargs
|
||||
):
|
||||
def make_approval_node(self, **kwargs):
|
||||
if 'name' not in kwargs:
|
||||
kwargs['name'] = 'approval node {}'.format(random_title())
|
||||
self.related.create_approval_template.post(kwargs)
|
||||
@@ -122,10 +92,10 @@ class WorkflowJobTemplateNode(HasCreate, base.Base):
|
||||
return candidates.results.pop()
|
||||
|
||||
|
||||
page.register_page([resources.workflow_job_template_node,
|
||||
(resources.workflow_job_template_nodes, 'post'),
|
||||
(resources.workflow_job_template_workflow_nodes, 'post')],
|
||||
WorkflowJobTemplateNode)
|
||||
page.register_page(
|
||||
[resources.workflow_job_template_node, (resources.workflow_job_template_nodes, 'post'), (resources.workflow_job_template_workflow_nodes, 'post')],
|
||||
WorkflowJobTemplateNode,
|
||||
)
|
||||
|
||||
|
||||
class WorkflowJobTemplateNodes(page.PageList, WorkflowJobTemplateNode):
|
||||
@@ -133,9 +103,13 @@ class WorkflowJobTemplateNodes(page.PageList, WorkflowJobTemplateNode):
|
||||
pass
|
||||
|
||||
|
||||
page.register_page([resources.workflow_job_template_nodes,
|
||||
resources.workflow_job_template_workflow_nodes,
|
||||
resources.workflow_job_template_node_always_nodes,
|
||||
resources.workflow_job_template_node_failure_nodes,
|
||||
resources.workflow_job_template_node_success_nodes],
|
||||
WorkflowJobTemplateNodes)
|
||||
page.register_page(
|
||||
[
|
||||
resources.workflow_job_template_nodes,
|
||||
resources.workflow_job_template_workflow_nodes,
|
||||
resources.workflow_job_template_node_always_nodes,
|
||||
resources.workflow_job_template_node_failure_nodes,
|
||||
resources.workflow_job_template_node_success_nodes,
|
||||
],
|
||||
WorkflowJobTemplateNodes,
|
||||
)
|
||||
|
||||
@@ -26,15 +26,14 @@ class WorkflowJobTemplate(HasCopy, HasCreate, HasNotifications, HasSurvey, Unifi
|
||||
# return job
|
||||
jobs_pg = self.related.workflow_jobs.get(id=result.workflow_job)
|
||||
if jobs_pg.count != 1:
|
||||
msg = "workflow_job_template launched (id:{}) but job not found in response at {}/workflow_jobs/".format(
|
||||
result.json['workflow_job'], self.url
|
||||
)
|
||||
msg = "workflow_job_template launched (id:{}) but job not found in response at {}/workflow_jobs/".format(result.json['workflow_job'], self.url)
|
||||
raise exc.UnexpectedAWXState(msg)
|
||||
return jobs_pg.results[0]
|
||||
|
||||
def payload(self, **kwargs):
|
||||
payload = PseudoNamespace(name=kwargs.get('name') or 'WorkflowJobTemplate - {}'.format(random_title()),
|
||||
description=kwargs.get('description') or random_title(10))
|
||||
payload = PseudoNamespace(
|
||||
name=kwargs.get('name') or 'WorkflowJobTemplate - {}'.format(random_title()), description=kwargs.get('description') or random_title(10)
|
||||
)
|
||||
|
||||
optional_fields = (
|
||||
"allow_simultaneous",
|
||||
@@ -91,9 +90,9 @@ class WorkflowJobTemplate(HasCopy, HasCreate, HasNotifications, HasSurvey, Unifi
|
||||
self.related.labels.post(label)
|
||||
|
||||
|
||||
page.register_page([resources.workflow_job_template,
|
||||
(resources.workflow_job_templates, 'post'),
|
||||
(resources.workflow_job_template_copy, 'post')], WorkflowJobTemplate)
|
||||
page.register_page(
|
||||
[resources.workflow_job_template, (resources.workflow_job_templates, 'post'), (resources.workflow_job_template_copy, 'post')], WorkflowJobTemplate
|
||||
)
|
||||
|
||||
|
||||
class WorkflowJobTemplates(page.PageList, WorkflowJobTemplate):
|
||||
@@ -101,8 +100,7 @@ class WorkflowJobTemplates(page.PageList, WorkflowJobTemplate):
|
||||
pass
|
||||
|
||||
|
||||
page.register_page([resources.workflow_job_templates,
|
||||
resources.related_workflow_job_templates], WorkflowJobTemplates)
|
||||
page.register_page([resources.workflow_job_templates, resources.related_workflow_job_templates], WorkflowJobTemplates)
|
||||
|
||||
|
||||
class WorkflowJobTemplateLaunch(base.Base):
|
||||
|
||||
@@ -4,7 +4,6 @@ from . import page
|
||||
|
||||
|
||||
class WorkflowJob(UnifiedJob):
|
||||
|
||||
def __str__(self):
|
||||
# TODO: Update after endpoint's fields are finished filling out
|
||||
return super(UnifiedJob, self).__str__()
|
||||
@@ -56,7 +55,4 @@ class WorkflowJobs(page.PageList, WorkflowJob):
|
||||
pass
|
||||
|
||||
|
||||
page.register_page([resources.workflow_jobs,
|
||||
resources.workflow_job_template_jobs,
|
||||
resources.job_template_slice_workflow_jobs],
|
||||
WorkflowJobs)
|
||||
page.register_page([resources.workflow_jobs, resources.workflow_job_template_jobs, resources.job_template_slice_workflow_jobs], WorkflowJobs)
|
||||
|
||||
@@ -8,7 +8,6 @@ log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class URLRegistry(object):
|
||||
|
||||
def __init__(self):
|
||||
self.store = defaultdict(dict)
|
||||
self.default = {}
|
||||
@@ -81,8 +80,7 @@ class URLRegistry(object):
|
||||
if method_pattern.pattern == not_provided:
|
||||
exc_msg = '"{0.pattern}" already has methodless registration.'.format(url_pattern)
|
||||
else:
|
||||
exc_msg = ('"{0.pattern}" already has registered method "{1.pattern}"'
|
||||
.format(url_pattern, method_pattern))
|
||||
exc_msg = '"{0.pattern}" already has registered method "{1.pattern}"'.format(url_pattern, method_pattern)
|
||||
raise TypeError(exc_msg)
|
||||
self.store[url_pattern][method_pattern] = resource
|
||||
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
|
||||
class Resources(object):
|
||||
|
||||
_activity = r'activity_stream/\d+/'
|
||||
|
||||
@@ -15,12 +15,11 @@ def freeze(key):
|
||||
|
||||
def parse_description(desc):
|
||||
options = {}
|
||||
for line in desc[desc.index('POST'):].splitlines():
|
||||
for line in desc[desc.index('POST') :].splitlines():
|
||||
match = descRE.match(line)
|
||||
if not match:
|
||||
continue
|
||||
options[match.group(1)] = {'type': match.group(2),
|
||||
'required': match.group(3) == 'required'}
|
||||
options[match.group(1)] = {'type': match.group(2), 'required': match.group(3) == 'required'}
|
||||
return options
|
||||
|
||||
|
||||
@@ -45,6 +44,5 @@ def get_post_fields(page, cache):
|
||||
if 'POST' in options_page.json['actions']:
|
||||
return options_page.json['actions']['POST']
|
||||
else:
|
||||
log.warning(
|
||||
"Insufficient privileges on %s, inferring POST fields from description.", options_page.endpoint)
|
||||
log.warning("Insufficient privileges on %s, inferring POST fields from description.", options_page.endpoint)
|
||||
return parse_description(options_page.json['description'])
|
||||
|
||||
@@ -17,13 +17,14 @@ def upload_inventory(ansible_runner, nhosts=10, ini=False):
|
||||
copy_content = '''#!/bin/bash
|
||||
cat <<EOF
|
||||
%s
|
||||
EOF''' % json_inventory(nhosts)
|
||||
EOF''' % json_inventory(
|
||||
nhosts
|
||||
)
|
||||
|
||||
# Copy script to test system
|
||||
contacted = ansible_runner.copy(dest=copy_dest, force=True, mode=copy_mode, content=copy_content)
|
||||
for result in contacted.values():
|
||||
assert not result.get('failed', False), \
|
||||
"Failed to create inventory file: %s" % result
|
||||
assert not result.get('failed', False), "Failed to create inventory file: %s" % result
|
||||
return copy_dest
|
||||
|
||||
|
||||
@@ -49,8 +50,7 @@ def generate_inventory(nhosts=100):
|
||||
group_by_10s = 'group-%07dX.example.com' % (n / 10)
|
||||
group_by_100s = 'group-%06dXX.example.com' % (n / 100)
|
||||
group_by_1000s = 'group-%05dXXX.example.com' % (n / 1000)
|
||||
for group in [group_evens_odds, group_threes, group_fours, group_fives, group_sixes, group_sevens,
|
||||
group_eights, group_nines, group_tens, group_by_10s]:
|
||||
for group in [group_evens_odds, group_threes, group_fours, group_fives, group_sixes, group_sevens, group_eights, group_nines, group_tens, group_by_10s]:
|
||||
if not group:
|
||||
continue
|
||||
if group in inv_list:
|
||||
@@ -58,11 +58,9 @@ def generate_inventory(nhosts=100):
|
||||
else:
|
||||
inv_list[group] = {'hosts': [hostname], 'children': [], 'vars': {'group_prefix': group.split('.')[0]}}
|
||||
if group_by_1000s not in inv_list:
|
||||
inv_list[group_by_1000s] = {'hosts': [], 'children': [],
|
||||
'vars': {'group_prefix': group_by_1000s.split('.')[0]}}
|
||||
inv_list[group_by_1000s] = {'hosts': [], 'children': [], 'vars': {'group_prefix': group_by_1000s.split('.')[0]}}
|
||||
if group_by_100s not in inv_list:
|
||||
inv_list[group_by_100s] = {'hosts': [], 'children': [],
|
||||
'vars': {'group_prefix': group_by_100s.split('.')[0]}}
|
||||
inv_list[group_by_100s] = {'hosts': [], 'children': [], 'vars': {'group_prefix': group_by_100s.split('.')[0]}}
|
||||
if group_by_100s not in inv_list[group_by_1000s]['children']:
|
||||
inv_list[group_by_1000s]['children'].append(group_by_100s)
|
||||
if group_by_10s not in inv_list[group_by_100s]['children']:
|
||||
|
||||
@@ -31,9 +31,22 @@ def _delete_all(endpoint):
|
||||
|
||||
|
||||
def delete_all(v):
|
||||
for endpoint in (v.unified_jobs, v.job_templates, v.workflow_job_templates, v.notification_templates,
|
||||
v.projects, v.inventory, v.hosts, v.inventory_scripts, v.labels, v.credentials,
|
||||
v.teams, v.users, v.organizations, v.schedules):
|
||||
for endpoint in (
|
||||
v.unified_jobs,
|
||||
v.job_templates,
|
||||
v.workflow_job_templates,
|
||||
v.notification_templates,
|
||||
v.projects,
|
||||
v.inventory,
|
||||
v.hosts,
|
||||
v.inventory_scripts,
|
||||
v.labels,
|
||||
v.credentials,
|
||||
v.teams,
|
||||
v.users,
|
||||
v.organizations,
|
||||
v.schedules,
|
||||
):
|
||||
_delete_all(endpoint)
|
||||
|
||||
|
||||
|
||||
@@ -56,14 +56,7 @@ def run(stdout=sys.stdout, stderr=sys.stderr, argv=[]):
|
||||
json.dump(e.msg, sys.stdout)
|
||||
print('')
|
||||
elif cli.get_config('format') == 'yaml':
|
||||
sys.stdout.write(to_str(
|
||||
yaml.safe_dump(
|
||||
e.msg,
|
||||
default_flow_style=False,
|
||||
encoding='utf-8',
|
||||
allow_unicode=True
|
||||
)
|
||||
))
|
||||
sys.stdout.write(to_str(yaml.safe_dump(e.msg, default_flow_style=False, encoding='utf-8', allow_unicode=True)))
|
||||
elif cli.get_config('format') == 'human':
|
||||
sys.stdout.write(e.__class__.__name__)
|
||||
print('')
|
||||
|
||||
@@ -8,9 +8,7 @@ import sys
|
||||
from requests.exceptions import RequestException
|
||||
|
||||
from .custom import handle_custom_actions
|
||||
from .format import (add_authentication_arguments,
|
||||
add_output_formatting_arguments,
|
||||
FORMATTERS, format_response)
|
||||
from .format import add_authentication_arguments, add_output_formatting_arguments, FORMATTERS, format_response
|
||||
from .options import ResourceOptionsParser, UNIQUENESS_RULES
|
||||
from .resource import parse_resource, is_control_resource
|
||||
from awxkit import api, config, utils, exceptions, WSClient # noqa
|
||||
@@ -88,7 +86,9 @@ class CLI(object):
|
||||
token = self.get_config('token')
|
||||
if token:
|
||||
self.root.connection.login(
|
||||
None, None, token=token,
|
||||
None,
|
||||
None,
|
||||
token=token,
|
||||
)
|
||||
else:
|
||||
config.use_sessions = True
|
||||
@@ -102,12 +102,14 @@ class CLI(object):
|
||||
if self.get_config('insecure'):
|
||||
config.assume_untrusted = True
|
||||
|
||||
config.credentials = utils.PseudoNamespace({
|
||||
'default': {
|
||||
'username': self.get_config('username'),
|
||||
'password': self.get_config('password'),
|
||||
config.credentials = utils.PseudoNamespace(
|
||||
{
|
||||
'default': {
|
||||
'username': self.get_config('username'),
|
||||
'password': self.get_config('password'),
|
||||
}
|
||||
}
|
||||
})
|
||||
)
|
||||
|
||||
_, remainder = self.parser.parse_known_args()
|
||||
if remainder and remainder[0] == 'config':
|
||||
@@ -133,11 +135,7 @@ class CLI(object):
|
||||
try:
|
||||
self.v2 = self.root.get().available_versions.v2.get()
|
||||
except AttributeError:
|
||||
raise RuntimeError(
|
||||
'An error occurred while fetching {}/api/'.format(
|
||||
self.get_config('host')
|
||||
)
|
||||
)
|
||||
raise RuntimeError('An error occurred while fetching {}/api/'.format(self.get_config('host')))
|
||||
|
||||
def parse_resource(self, skip_deprecated=False):
|
||||
"""Attempt to parse the <resource> (e.g., jobs) specified on the CLI
|
||||
@@ -170,33 +168,15 @@ class CLI(object):
|
||||
_filter = self.get_config('filter')
|
||||
|
||||
# human format for metrics, settings is special
|
||||
if (
|
||||
self.resource in ('metrics', 'settings') and
|
||||
self.get_config('format') == 'human'
|
||||
):
|
||||
response.json = {
|
||||
'count': len(response.json),
|
||||
'results': [
|
||||
{'key': k, 'value': v}
|
||||
for k, v in response.json.items()
|
||||
]
|
||||
}
|
||||
if self.resource in ('metrics', 'settings') and self.get_config('format') == 'human':
|
||||
response.json = {'count': len(response.json), 'results': [{'key': k, 'value': v} for k, v in response.json.items()]}
|
||||
_filter = 'key, value'
|
||||
|
||||
if (
|
||||
self.get_config('format') == 'human' and
|
||||
_filter == '.' and
|
||||
self.resource in UNIQUENESS_RULES
|
||||
):
|
||||
if self.get_config('format') == 'human' and _filter == '.' and self.resource in UNIQUENESS_RULES:
|
||||
_filter = ', '.join(UNIQUENESS_RULES[self.resource])
|
||||
|
||||
formatted = format_response(
|
||||
response,
|
||||
fmt=self.get_config('format'),
|
||||
filter=_filter,
|
||||
changed=self.original_action in (
|
||||
'modify', 'create', 'associate', 'disassociate'
|
||||
)
|
||||
response, fmt=self.get_config('format'), filter=_filter, changed=self.original_action in ('modify', 'create', 'associate', 'disassociate')
|
||||
)
|
||||
if formatted:
|
||||
print(utils.to_str(formatted), file=self.stdout)
|
||||
@@ -219,10 +199,7 @@ class CLI(object):
|
||||
_without_ triggering a SystemExit (argparse's
|
||||
behavior if required arguments are missing)
|
||||
"""
|
||||
subparsers = self.subparsers[self.resource].add_subparsers(
|
||||
dest='action',
|
||||
metavar='action'
|
||||
)
|
||||
subparsers = self.subparsers[self.resource].add_subparsers(dest='action', metavar='action')
|
||||
subparsers.required = True
|
||||
|
||||
# parse the action from OPTIONS
|
||||
@@ -252,10 +229,7 @@ class CLI(object):
|
||||
if self.resource != 'settings':
|
||||
for method in ('list', 'modify', 'create'):
|
||||
if method in parser.parser.choices:
|
||||
parser.build_query_arguments(
|
||||
method,
|
||||
'GET' if method == 'list' else 'POST'
|
||||
)
|
||||
parser.build_query_arguments(method, 'GET' if method == 'list' else 'POST')
|
||||
if from_sphinx:
|
||||
parsed, extra = self.parser.parse_known_args(self.argv)
|
||||
else:
|
||||
@@ -263,10 +237,7 @@ class CLI(object):
|
||||
|
||||
if extra and self.verbose:
|
||||
# If extraneous arguments were provided, warn the user
|
||||
cprint('{}: unrecognized arguments: {}'.format(
|
||||
self.parser.prog,
|
||||
' '.join(extra)
|
||||
), 'yellow', file=self.stdout)
|
||||
cprint('{}: unrecognized arguments: {}'.format(self.parser.prog, ' '.join(extra)), 'yellow', file=self.stdout)
|
||||
|
||||
# build a dictionary of all of the _valid_ flags specified on the
|
||||
# command line so we can pass them on to the underlying awxkit call
|
||||
@@ -275,14 +246,7 @@ class CLI(object):
|
||||
# everything else is a flag used as a query argument for the HTTP
|
||||
# request we'll make (e.g., --username="Joe", --verbosity=3)
|
||||
parsed = parsed.__dict__
|
||||
parsed = dict(
|
||||
(k, v) for k, v in parsed.items()
|
||||
if (
|
||||
v is not None and
|
||||
k not in ('help', 'resource') and
|
||||
not k.startswith('conf.')
|
||||
)
|
||||
)
|
||||
parsed = dict((k, v) for k, v in parsed.items() if (v is not None and k not in ('help', 'resource') and not k.startswith('conf.')))
|
||||
|
||||
# if `id` is one of the arguments, it's a detail view
|
||||
if 'id' in parsed:
|
||||
@@ -290,9 +254,7 @@ class CLI(object):
|
||||
|
||||
# determine the awxkit method to call
|
||||
action = self.original_action = parsed.pop('action')
|
||||
page, action = handle_custom_actions(
|
||||
self.resource, action, page
|
||||
)
|
||||
page, action = handle_custom_actions(self.resource, action, page)
|
||||
self.method = {
|
||||
'list': 'get',
|
||||
'modify': 'patch',
|
||||
@@ -327,13 +289,7 @@ class CLI(object):
|
||||
action='store_true',
|
||||
help='prints usage information for the awx tool',
|
||||
)
|
||||
self.parser.add_argument(
|
||||
'--version',
|
||||
dest='conf.version',
|
||||
action='version',
|
||||
help='display awx CLI version',
|
||||
version=__version__
|
||||
)
|
||||
self.parser.add_argument('--version', dest='conf.version', action='version', help='display awx CLI version', version=__version__)
|
||||
add_authentication_arguments(self.parser, env)
|
||||
add_output_formatting_arguments(self.parser, env)
|
||||
|
||||
|
||||
@@ -16,7 +16,6 @@ def handle_custom_actions(resource, action, page):
|
||||
|
||||
|
||||
class CustomActionRegistryMeta(CustomRegistryMeta):
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
return ' '.join([self.resource, self.action])
|
||||
@@ -45,33 +44,16 @@ class CustomAction(metaclass=CustomActionRegistryMeta):
|
||||
|
||||
|
||||
class Launchable(object):
|
||||
|
||||
def add_arguments(self, parser, resource_options_parser, with_pk=True):
|
||||
from .options import pk_or_name
|
||||
if with_pk:
|
||||
parser.choices[self.action].add_argument(
|
||||
'id',
|
||||
type=functools.partial(
|
||||
pk_or_name, None, self.resource, page=self.page
|
||||
),
|
||||
help=''
|
||||
)
|
||||
parser.choices[self.action].add_argument(
|
||||
'--monitor', action='store_true',
|
||||
help='If set, prints stdout of the launched job until it finishes.'
|
||||
)
|
||||
parser.choices[self.action].add_argument(
|
||||
'--timeout', type=int,
|
||||
help='If set with --monitor or --wait, time out waiting on job completion.' # noqa
|
||||
)
|
||||
parser.choices[self.action].add_argument(
|
||||
'--wait', action='store_true',
|
||||
help='If set, waits until the launched job finishes.'
|
||||
)
|
||||
|
||||
launch_time_options = self.page.connection.options(
|
||||
self.page.endpoint + '1/{}/'.format(self.action)
|
||||
)
|
||||
if with_pk:
|
||||
parser.choices[self.action].add_argument('id', type=functools.partial(pk_or_name, None, self.resource, page=self.page), help='')
|
||||
parser.choices[self.action].add_argument('--monitor', action='store_true', help='If set, prints stdout of the launched job until it finishes.')
|
||||
parser.choices[self.action].add_argument('--timeout', type=int, help='If set with --monitor or --wait, time out waiting on job completion.') # noqa
|
||||
parser.choices[self.action].add_argument('--wait', action='store_true', help='If set, waits until the launched job finishes.')
|
||||
|
||||
launch_time_options = self.page.connection.options(self.page.endpoint + '1/{}/'.format(self.action))
|
||||
if launch_time_options.ok:
|
||||
launch_time_options = launch_time_options.json()['actions']['POST']
|
||||
resource_options_parser.options['LAUNCH'] = launch_time_options
|
||||
@@ -118,24 +100,15 @@ class ProjectCreate(CustomAction):
|
||||
resource = 'projects'
|
||||
|
||||
def add_arguments(self, parser, resource_options_parser):
|
||||
parser.choices[self.action].add_argument(
|
||||
'--monitor', action='store_true',
|
||||
help=('If set, prints stdout of the project update until '
|
||||
'it finishes.')
|
||||
)
|
||||
parser.choices[self.action].add_argument(
|
||||
'--wait', action='store_true',
|
||||
help='If set, waits until the new project has updated.'
|
||||
)
|
||||
parser.choices[self.action].add_argument('--monitor', action='store_true', help=('If set, prints stdout of the project update until ' 'it finishes.'))
|
||||
parser.choices[self.action].add_argument('--wait', action='store_true', help='If set, waits until the new project has updated.')
|
||||
|
||||
def post(self, kwargs):
|
||||
should_monitor = kwargs.pop('monitor', False)
|
||||
wait = kwargs.pop('wait', False)
|
||||
response = self.page.post(kwargs)
|
||||
if should_monitor or wait:
|
||||
update = response.related.project_updates.get(
|
||||
order_by='-created'
|
||||
).results[0]
|
||||
update = response.related.project_updates.get(order_by='-created').results[0]
|
||||
monitor(
|
||||
update,
|
||||
self.page.connection.session,
|
||||
@@ -154,9 +127,7 @@ class AdhocCommandLaunch(Launchable, CustomAction):
|
||||
resource = 'ad_hoc_commands'
|
||||
|
||||
def add_arguments(self, parser, resource_options_parser):
|
||||
Launchable.add_arguments(
|
||||
self, parser, resource_options_parser, with_pk=False
|
||||
)
|
||||
Launchable.add_arguments(self, parser, resource_options_parser, with_pk=False)
|
||||
|
||||
def perform(self, **kwargs):
|
||||
monitor_kwargs = {
|
||||
@@ -182,22 +153,14 @@ class HasStdout(object):
|
||||
|
||||
def add_arguments(self, parser, resource_options_parser):
|
||||
from .options import pk_or_name
|
||||
parser.choices['stdout'].add_argument(
|
||||
'id',
|
||||
type=functools.partial(
|
||||
pk_or_name, None, self.resource, page=self.page
|
||||
),
|
||||
help=''
|
||||
)
|
||||
|
||||
parser.choices['stdout'].add_argument('id', type=functools.partial(pk_or_name, None, self.resource, page=self.page), help='')
|
||||
|
||||
def perform(self):
|
||||
fmt = 'txt_download'
|
||||
if color_enabled():
|
||||
fmt = 'ansi_download'
|
||||
return self.page.connection.get(
|
||||
self.page.get().related.stdout,
|
||||
query_parameters=dict(format=fmt)
|
||||
).content.decode('utf-8')
|
||||
return self.page.connection.get(self.page.get().related.stdout, query_parameters=dict(format=fmt)).content.decode('utf-8')
|
||||
|
||||
|
||||
class JobStdout(HasStdout, CustomAction):
|
||||
@@ -222,13 +185,8 @@ class AssociationMixin(object):
|
||||
|
||||
def add_arguments(self, parser, resource_options_parser):
|
||||
from .options import pk_or_name
|
||||
parser.choices[self.action].add_argument(
|
||||
'id',
|
||||
type=functools.partial(
|
||||
pk_or_name, None, self.resource, page=self.page
|
||||
),
|
||||
help=''
|
||||
)
|
||||
|
||||
parser.choices[self.action].add_argument('id', type=functools.partial(pk_or_name, None, self.resource, page=self.page), help='')
|
||||
group = parser.choices[self.action].add_mutually_exclusive_group(required=True)
|
||||
for param, endpoint in self.targets.items():
|
||||
field, model_name = endpoint
|
||||
@@ -237,7 +195,6 @@ class AssociationMixin(object):
|
||||
help_text = 'The ID (or name) of the {} to {}'.format(model_name, self.action)
|
||||
|
||||
class related_page(object):
|
||||
|
||||
def __init__(self, connection, resource):
|
||||
self.conn = connection
|
||||
self.resource = {
|
||||
@@ -256,20 +213,15 @@ class AssociationMixin(object):
|
||||
group.add_argument(
|
||||
'--{}'.format(param),
|
||||
metavar='',
|
||||
type=functools.partial(
|
||||
pk_or_name, None, param,
|
||||
page=related_page(self.page.connection, param)
|
||||
),
|
||||
help=help_text
|
||||
type=functools.partial(pk_or_name, None, param, page=related_page(self.page.connection, param)),
|
||||
help=help_text,
|
||||
)
|
||||
|
||||
def perform(self, **kwargs):
|
||||
for k, v in kwargs.items():
|
||||
endpoint, _ = self.targets[k]
|
||||
try:
|
||||
self.page.get().related[endpoint].post(
|
||||
{'id': v, self.action: True}
|
||||
)
|
||||
self.page.get().related[endpoint].post({'id': v, self.action: True})
|
||||
except NoContent:
|
||||
# we expect to enter this block because these endpoints return
|
||||
# HTTP 204 on success
|
||||
@@ -279,18 +231,9 @@ class AssociationMixin(object):
|
||||
|
||||
class NotificationAssociateMixin(AssociationMixin):
|
||||
targets = {
|
||||
'start_notification': [
|
||||
'notification_templates_started',
|
||||
'notification_template'
|
||||
],
|
||||
'success_notification': [
|
||||
'notification_templates_success',
|
||||
'notification_template'
|
||||
],
|
||||
'failure_notification': [
|
||||
'notification_templates_error',
|
||||
'notification_template'
|
||||
],
|
||||
'start_notification': ['notification_templates_started', 'notification_template'],
|
||||
'success_notification': ['notification_templates_success', 'notification_template'],
|
||||
'failure_notification': ['notification_templates_error', 'notification_template'],
|
||||
}
|
||||
|
||||
|
||||
@@ -306,12 +249,16 @@ class JobTemplateNotificationDisAssociation(NotificationAssociateMixin, CustomAc
|
||||
targets = NotificationAssociateMixin.targets.copy()
|
||||
|
||||
|
||||
JobTemplateNotificationAssociation.targets.update({
|
||||
'credential': ['credentials', None],
|
||||
})
|
||||
JobTemplateNotificationDisAssociation.targets.update({
|
||||
'credential': ['credentials', None],
|
||||
})
|
||||
JobTemplateNotificationAssociation.targets.update(
|
||||
{
|
||||
'credential': ['credentials', None],
|
||||
}
|
||||
)
|
||||
JobTemplateNotificationDisAssociation.targets.update(
|
||||
{
|
||||
'credential': ['credentials', None],
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
class WorkflowJobTemplateNotificationAssociation(NotificationAssociateMixin, CustomAction):
|
||||
@@ -326,12 +273,16 @@ class WorkflowJobTemplateNotificationDisAssociation(NotificationAssociateMixin,
|
||||
targets = NotificationAssociateMixin.targets.copy()
|
||||
|
||||
|
||||
WorkflowJobTemplateNotificationAssociation.targets.update({
|
||||
'approval_notification': ['notification_templates_approvals', 'notification_template'],
|
||||
})
|
||||
WorkflowJobTemplateNotificationDisAssociation.targets.update({
|
||||
'approval_notification': ['notification_templates_approvals', 'notification_template'],
|
||||
})
|
||||
WorkflowJobTemplateNotificationAssociation.targets.update(
|
||||
{
|
||||
'approval_notification': ['notification_templates_approvals', 'notification_template'],
|
||||
}
|
||||
)
|
||||
WorkflowJobTemplateNotificationDisAssociation.targets.update(
|
||||
{
|
||||
'approval_notification': ['notification_templates_approvals', 'notification_template'],
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
class ProjectNotificationAssociation(NotificationAssociateMixin, CustomAction):
|
||||
@@ -366,14 +317,18 @@ class OrganizationNotificationDisAssociation(NotificationAssociateMixin, CustomA
|
||||
targets = NotificationAssociateMixin.targets.copy()
|
||||
|
||||
|
||||
OrganizationNotificationAssociation.targets.update({
|
||||
'approval_notification': ['notification_templates_approvals', 'notification_template'],
|
||||
'galaxy_credential': ['galaxy_credentials', 'credential'],
|
||||
})
|
||||
OrganizationNotificationDisAssociation.targets.update({
|
||||
'approval_notification': ['notification_templates_approvals', 'notification_template'],
|
||||
'galaxy_credential': ['galaxy_credentials', 'credential'],
|
||||
})
|
||||
OrganizationNotificationAssociation.targets.update(
|
||||
{
|
||||
'approval_notification': ['notification_templates_approvals', 'notification_template'],
|
||||
'galaxy_credential': ['galaxy_credentials', 'credential'],
|
||||
}
|
||||
)
|
||||
OrganizationNotificationDisAssociation.targets.update(
|
||||
{
|
||||
'approval_notification': ['notification_templates_approvals', 'notification_template'],
|
||||
'galaxy_credential': ['galaxy_credentials', 'credential'],
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
class SettingsList(CustomAction):
|
||||
@@ -381,9 +336,7 @@ class SettingsList(CustomAction):
|
||||
resource = 'settings'
|
||||
|
||||
def add_arguments(self, parser, resource_options_parser):
|
||||
parser.choices['list'].add_argument(
|
||||
'--slug', help='optional setting category/slug', default='all'
|
||||
)
|
||||
parser.choices['list'].add_argument('--slug', help='optional setting category/slug', default='all')
|
||||
|
||||
def perform(self, slug):
|
||||
self.page.endpoint = self.page.endpoint + '{}/'.format(slug)
|
||||
@@ -409,30 +362,18 @@ class RoleMixin(object):
|
||||
|
||||
if not RoleMixin.roles:
|
||||
for resource, flag in self.has_roles:
|
||||
options = self.page.__class__(
|
||||
self.page.endpoint.replace(self.resource, resource),
|
||||
self.page.connection
|
||||
).options()
|
||||
RoleMixin.roles[flag] = [
|
||||
role.replace('_role', '')
|
||||
for role in options.json.get('object_roles', [])
|
||||
]
|
||||
options = self.page.__class__(self.page.endpoint.replace(self.resource, resource), self.page.connection).options()
|
||||
RoleMixin.roles[flag] = [role.replace('_role', '') for role in options.json.get('object_roles', [])]
|
||||
|
||||
possible_roles = set()
|
||||
for v in RoleMixin.roles.values():
|
||||
possible_roles.update(v)
|
||||
|
||||
resource_group = parser.choices[self.action].add_mutually_exclusive_group(
|
||||
required=True
|
||||
)
|
||||
resource_group = parser.choices[self.action].add_mutually_exclusive_group(required=True)
|
||||
parser.choices[self.action].add_argument(
|
||||
'id',
|
||||
type=functools.partial(
|
||||
pk_or_name, None, self.resource, page=self.page
|
||||
),
|
||||
help='The ID (or name) of the {} to {} access to/from'.format(
|
||||
self.resource, self.action
|
||||
)
|
||||
type=functools.partial(pk_or_name, None, self.resource, page=self.page),
|
||||
help='The ID (or name) of the {} to {} access to/from'.format(self.resource, self.action),
|
||||
)
|
||||
for _type in RoleMixin.roles.keys():
|
||||
if _type == 'team' and self.resource == 'team':
|
||||
@@ -440,7 +381,6 @@ class RoleMixin(object):
|
||||
continue
|
||||
|
||||
class related_page(object):
|
||||
|
||||
def __init__(self, connection, resource):
|
||||
self.conn = connection
|
||||
if resource == 'inventories':
|
||||
@@ -453,19 +393,12 @@ class RoleMixin(object):
|
||||
|
||||
resource_group.add_argument(
|
||||
'--{}'.format(_type),
|
||||
type=functools.partial(
|
||||
pk_or_name, None, _type,
|
||||
page=related_page(
|
||||
self.page.connection,
|
||||
dict((v, k) for k, v in self.has_roles)[_type]
|
||||
)
|
||||
),
|
||||
type=functools.partial(pk_or_name, None, _type, page=related_page(self.page.connection, dict((v, k) for k, v in self.has_roles)[_type])),
|
||||
metavar='ID',
|
||||
help='The ID (or name) of the target {}'.format(_type),
|
||||
)
|
||||
parser.choices[self.action].add_argument(
|
||||
'--role', type=str, choices=possible_roles, required=True,
|
||||
help='The name of the role to {}'.format(self.action)
|
||||
'--role', type=str, choices=possible_roles, required=True, help='The name of the role to {}'.format(self.action)
|
||||
)
|
||||
|
||||
def perform(self, **kwargs):
|
||||
@@ -474,17 +407,10 @@ class RoleMixin(object):
|
||||
role = kwargs['role']
|
||||
if role not in RoleMixin.roles[flag]:
|
||||
options = ', '.join(RoleMixin.roles[flag])
|
||||
raise ValueError(
|
||||
"invalid choice: '{}' must be one of {}".format(
|
||||
role, options
|
||||
)
|
||||
)
|
||||
raise ValueError("invalid choice: '{}' must be one of {}".format(role, options))
|
||||
value = kwargs[flag]
|
||||
target = '/api/v2/{}/{}'.format(resource, value)
|
||||
detail = self.page.__class__(
|
||||
target,
|
||||
self.page.connection
|
||||
).get()
|
||||
detail = self.page.__class__(target, self.page.connection).get()
|
||||
object_roles = detail['summary_fields']['object_roles']
|
||||
actual_role = object_roles[role + '_role']
|
||||
params = {'id': actual_role['id']}
|
||||
@@ -530,15 +456,8 @@ class SettingsModify(CustomAction):
|
||||
resource = 'settings'
|
||||
|
||||
def add_arguments(self, parser, resource_options_parser):
|
||||
options = self.page.__class__(
|
||||
self.page.endpoint + 'all/', self.page.connection
|
||||
).options()
|
||||
parser.choices['modify'].add_argument(
|
||||
'key',
|
||||
choices=sorted(options['actions']['PUT'].keys()),
|
||||
metavar='key',
|
||||
help=''
|
||||
)
|
||||
options = self.page.__class__(self.page.endpoint + 'all/', self.page.connection).options()
|
||||
parser.choices['modify'].add_argument('key', choices=sorted(options['actions']['PUT'].keys()), metavar='key', help='')
|
||||
parser.choices['modify'].add_argument('value', help='')
|
||||
|
||||
def perform(self, key, value):
|
||||
@@ -563,13 +482,8 @@ class HasMonitor(object):
|
||||
|
||||
def add_arguments(self, parser, resource_options_parser):
|
||||
from .options import pk_or_name
|
||||
parser.choices[self.action].add_argument(
|
||||
'id',
|
||||
type=functools.partial(
|
||||
pk_or_name, None, self.resource, page=self.page
|
||||
),
|
||||
help=''
|
||||
)
|
||||
|
||||
parser.choices[self.action].add_argument('id', type=functools.partial(pk_or_name, None, self.resource, page=self.page), help='')
|
||||
|
||||
def perform(self, **kwargs):
|
||||
response = self.page.get()
|
||||
|
||||
@@ -27,9 +27,7 @@ author = 'Ansible by Red Hat'
|
||||
# Add any Sphinx extension module names here, as strings. They can be
|
||||
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
|
||||
# ones.
|
||||
extensions = [
|
||||
'awxkit.cli.sphinx'
|
||||
]
|
||||
extensions = ['awxkit.cli.sphinx']
|
||||
|
||||
# Add any paths that contain templates here, relative to this directory.
|
||||
templates_path = ['_templates']
|
||||
|
||||
@@ -48,24 +48,21 @@ def add_output_formatting_arguments(parser, env):
|
||||
dest='conf.format',
|
||||
choices=FORMATTERS.keys(),
|
||||
default=env.get('TOWER_FORMAT', 'json'),
|
||||
help=(
|
||||
'specify a format for the input and output'
|
||||
),
|
||||
help=('specify a format for the input and output'),
|
||||
)
|
||||
formatting.add_argument(
|
||||
'--filter',
|
||||
dest='conf.filter',
|
||||
default='.',
|
||||
metavar='TEXT',
|
||||
help=(
|
||||
'specify an output filter (only valid with jq or human format)'
|
||||
),
|
||||
help=('specify an output filter (only valid with jq or human format)'),
|
||||
)
|
||||
formatting.add_argument(
|
||||
'--conf.color',
|
||||
metavar='BOOLEAN',
|
||||
help='Display colorized output. Defaults to True',
|
||||
default=env.get('TOWER_COLOR', 't'), type=strtobool,
|
||||
default=env.get('TOWER_COLOR', 't'),
|
||||
type=strtobool,
|
||||
)
|
||||
formatting.add_argument(
|
||||
'-v',
|
||||
@@ -73,7 +70,7 @@ def add_output_formatting_arguments(parser, env):
|
||||
dest='conf.verbose',
|
||||
help='print debug-level logs, including requests made',
|
||||
default=strtobool(env.get('TOWER_VERBOSE', 'f')),
|
||||
action="store_true"
|
||||
action="store_true",
|
||||
)
|
||||
|
||||
|
||||
@@ -105,11 +102,10 @@ def format_jq(output, fmt):
|
||||
if fmt == '.':
|
||||
return output
|
||||
raise ImportError(
|
||||
'To use `-f jq`, you must install the optional jq dependency.\n'
|
||||
'`pip install jq`\n',
|
||||
'To use `-f jq`, you must install the optional jq dependency.\n' '`pip install jq`\n',
|
||||
'Note that some platforms may require additional programs to '
|
||||
'build jq from source (like `libtool`).\n'
|
||||
'See https://pypi.org/project/jq/ for instructions.'
|
||||
'See https://pypi.org/project/jq/ for instructions.',
|
||||
)
|
||||
results = []
|
||||
for x in jq.jq(fmt).transform(output, multiple_output=True):
|
||||
@@ -127,11 +123,7 @@ def format_json(output, fmt):
|
||||
|
||||
def format_yaml(output, fmt):
|
||||
output = json.loads(json.dumps(output))
|
||||
return yaml.safe_dump(
|
||||
output,
|
||||
default_flow_style=False,
|
||||
allow_unicode=True
|
||||
)
|
||||
return yaml.safe_dump(output, default_flow_style=False, allow_unicode=True)
|
||||
|
||||
|
||||
def format_human(output, fmt):
|
||||
@@ -151,10 +143,7 @@ def format_human(output, fmt):
|
||||
column_names.remove(k)
|
||||
|
||||
table = [column_names]
|
||||
table.extend([
|
||||
[record.get(col, '') for col in column_names]
|
||||
for record in output
|
||||
])
|
||||
table.extend([[record.get(col, '') for col in column_names] for record in output])
|
||||
col_paddings = []
|
||||
|
||||
def format_num(v):
|
||||
@@ -184,9 +173,4 @@ def format_human(output, fmt):
|
||||
return '\n'.join(lines)
|
||||
|
||||
|
||||
FORMATTERS = {
|
||||
'json': format_json,
|
||||
'yaml': format_yaml,
|
||||
'jq': format_jq,
|
||||
'human': format_human
|
||||
}
|
||||
FORMATTERS = {'json': format_json, 'yaml': format_yaml, 'jq': format_jq, 'human': format_human}
|
||||
|
||||
@@ -21,10 +21,7 @@ UNIQUENESS_RULES = {
|
||||
|
||||
|
||||
def pk_or_name_list(v2, model_name, value, page=None):
|
||||
return [
|
||||
pk_or_name(v2, model_name, v.strip(), page=page)
|
||||
for v in value.split(',')
|
||||
]
|
||||
return [pk_or_name(v2, model_name, v.strip(), page=page) for v in value.split(',')]
|
||||
|
||||
|
||||
def pk_or_name(v2, model_name, value, page=None):
|
||||
@@ -58,17 +55,9 @@ def pk_or_name(v2, model_name, value, page=None):
|
||||
return int(results.results[0].id)
|
||||
if results.count > 1:
|
||||
raise argparse.ArgumentTypeError(
|
||||
'Multiple {0} exist with that {1}. '
|
||||
'To look up an ID, run:\n'
|
||||
'awx {0} list --{1} "{2}" -f human'.format(
|
||||
model_name, identity, value
|
||||
)
|
||||
'Multiple {0} exist with that {1}. ' 'To look up an ID, run:\n' 'awx {0} list --{1} "{2}" -f human'.format(model_name, identity, value)
|
||||
)
|
||||
raise argparse.ArgumentTypeError(
|
||||
'Could not find any {0} with that {1}.'.format(
|
||||
model_name, identity
|
||||
)
|
||||
)
|
||||
raise argparse.ArgumentTypeError('Could not find any {0} with that {1}.'.format(model_name, identity))
|
||||
|
||||
return value
|
||||
|
||||
@@ -90,9 +79,7 @@ class ResourceOptionsParser(object):
|
||||
self.page = page
|
||||
self.resource = resource
|
||||
self.parser = parser
|
||||
self.options = getattr(
|
||||
self.page.options().json, 'actions', {'GET': {}}
|
||||
)
|
||||
self.options = getattr(self.page.options().json, 'actions', {'GET': {}})
|
||||
self.get_allowed_options()
|
||||
if self.resource != 'settings':
|
||||
# /api/v2/settings is a special resource that doesn't have
|
||||
@@ -103,9 +90,7 @@ class ResourceOptionsParser(object):
|
||||
self.handle_custom_actions()
|
||||
|
||||
def get_allowed_options(self):
|
||||
options = self.page.connection.options(
|
||||
self.page.endpoint + '1/'
|
||||
)
|
||||
options = self.page.connection.options(self.page.endpoint + '1/')
|
||||
warning = options.headers.get('Warning', '')
|
||||
if '299' in warning and 'deprecated' in warning:
|
||||
self.deprecated = True
|
||||
@@ -121,11 +106,10 @@ class ResourceOptionsParser(object):
|
||||
parser = self.parser.add_parser(method, help='')
|
||||
if method == 'list':
|
||||
parser.add_argument(
|
||||
'--all', dest='all_pages', action='store_true',
|
||||
help=(
|
||||
'fetch all pages of content from the API when '
|
||||
'returning results (instead of just the first page)'
|
||||
)
|
||||
'--all',
|
||||
dest='all_pages',
|
||||
action='store_true',
|
||||
help=('fetch all pages of content from the API when ' 'returning results (instead of just the first page)'),
|
||||
)
|
||||
add_output_formatting_arguments(parser, {})
|
||||
|
||||
@@ -138,9 +122,7 @@ class ResourceOptionsParser(object):
|
||||
for method in allowed:
|
||||
parser = self.parser.add_parser(method, help='')
|
||||
self.parser.choices[method].add_argument(
|
||||
'id',
|
||||
type=functools.partial(pk_or_name, self.v2, self.resource),
|
||||
help='the ID (or unique name) of the resource'
|
||||
'id', type=functools.partial(pk_or_name, self.v2, self.resource), help='the ID (or unique name) of the resource'
|
||||
)
|
||||
if method == 'get':
|
||||
add_output_formatting_arguments(parser, {})
|
||||
@@ -148,10 +130,7 @@ class ResourceOptionsParser(object):
|
||||
def build_query_arguments(self, method, http_method):
|
||||
required_group = None
|
||||
for k, param in self.options.get(http_method, {}).items():
|
||||
required = (
|
||||
method == 'create' and
|
||||
param.get('required', False) is True
|
||||
)
|
||||
required = method == 'create' and param.get('required', False) is True
|
||||
help_text = param.get('help_text', '')
|
||||
|
||||
if method == 'list':
|
||||
@@ -159,10 +138,7 @@ class ResourceOptionsParser(object):
|
||||
# don't allow `awx <resource> list` to filter on `--id`
|
||||
# it's weird, and that's what awx <resource> get is for
|
||||
continue
|
||||
help_text = 'only list {} with the specified {}'.format(
|
||||
self.resource,
|
||||
k
|
||||
)
|
||||
help_text = 'only list {} with the specified {}'.format(self.resource, k)
|
||||
|
||||
if method == 'list' and param.get('filterable') is False:
|
||||
continue
|
||||
@@ -256,9 +232,8 @@ class ResourceOptionsParser(object):
|
||||
# unlike *other* actual JSON fields in the API, inventory and JT
|
||||
# variables *actually* want json.dumps() strings (ugh)
|
||||
# see: https://github.com/ansible/awx/issues/2371
|
||||
if (
|
||||
(self.resource in ('job_templates', 'workflow_job_templates') and k == 'extra_vars') or
|
||||
(self.resource in ('inventory', 'groups', 'hosts') and k == 'variables')
|
||||
if (self.resource in ('job_templates', 'workflow_job_templates') and k == 'extra_vars') or (
|
||||
self.resource in ('inventory', 'groups', 'hosts') and k == 'variables'
|
||||
):
|
||||
kwargs['type'] = jsonstr
|
||||
|
||||
@@ -267,15 +242,9 @@ class ResourceOptionsParser(object):
|
||||
required_group = self.parser.choices[method].add_argument_group('required arguments')
|
||||
# put the required group first (before the optional args group)
|
||||
self.parser.choices[method]._action_groups.reverse()
|
||||
required_group.add_argument(
|
||||
'--{}'.format(k),
|
||||
**kwargs
|
||||
)
|
||||
required_group.add_argument('--{}'.format(k), **kwargs)
|
||||
else:
|
||||
self.parser.choices[method].add_argument(
|
||||
'--{}'.format(k),
|
||||
**kwargs
|
||||
)
|
||||
self.parser.choices[method].add_argument('--{}'.format(k), **kwargs)
|
||||
|
||||
def handle_custom_actions(self):
|
||||
for _, action in CustomAction.registry.items():
|
||||
|
||||
@@ -40,11 +40,9 @@ DEPRECATED_RESOURCES = {
|
||||
'teams': 'team',
|
||||
'workflow_job_templates': 'workflow',
|
||||
'workflow_jobs': 'workflow_job',
|
||||
'users': 'user'
|
||||
'users': 'user',
|
||||
}
|
||||
DEPRECATED_RESOURCES_REVERSE = dict(
|
||||
(v, k) for k, v in DEPRECATED_RESOURCES.items()
|
||||
)
|
||||
DEPRECATED_RESOURCES_REVERSE = dict((v, k) for k, v in DEPRECATED_RESOURCES.items())
|
||||
|
||||
|
||||
class CustomCommand(metaclass=CustomRegistryMeta):
|
||||
@@ -81,9 +79,7 @@ class Login(CustomCommand):
|
||||
auth.add_argument('--description', help='description of the generated OAuth2.0 token', metavar='TEXT')
|
||||
auth.add_argument('--conf.client_id', metavar='TEXT')
|
||||
auth.add_argument('--conf.client_secret', metavar='TEXT')
|
||||
auth.add_argument(
|
||||
'--conf.scope', choices=['read', 'write'], default='write'
|
||||
)
|
||||
auth.add_argument('--conf.scope', choices=['read', 'write'], default='write')
|
||||
if client.help:
|
||||
self.print_help(parser)
|
||||
raise SystemExit()
|
||||
@@ -99,10 +95,7 @@ class Login(CustomCommand):
|
||||
token = api.Api().get_oauth2_token(**kwargs)
|
||||
except Exception as e:
|
||||
self.print_help(parser)
|
||||
cprint(
|
||||
'Error retrieving an OAuth2.0 token ({}).'.format(e.__class__),
|
||||
'red'
|
||||
)
|
||||
cprint('Error retrieving an OAuth2.0 token ({}).'.format(e.__class__), 'red')
|
||||
else:
|
||||
fmt = client.get_config('format')
|
||||
if fmt == 'human':
|
||||
@@ -186,9 +179,7 @@ def parse_resource(client, skip_deprecated=False):
|
||||
|
||||
# check if the user is running a custom command
|
||||
for command in CustomCommand.__subclasses__():
|
||||
client.subparsers[command.name] = subparsers.add_parser(
|
||||
command.name, help=command.help_text
|
||||
)
|
||||
client.subparsers[command.name] = subparsers.add_parser(command.name, help=command.help_text)
|
||||
|
||||
if hasattr(client, 'v2'):
|
||||
for k in client.v2.json.keys():
|
||||
@@ -202,15 +193,11 @@ def parse_resource(client, skip_deprecated=False):
|
||||
if k in DEPRECATED_RESOURCES:
|
||||
kwargs['aliases'] = [DEPRECATED_RESOURCES[k]]
|
||||
|
||||
client.subparsers[k] = subparsers.add_parser(
|
||||
k, help='', **kwargs
|
||||
)
|
||||
client.subparsers[k] = subparsers.add_parser(k, help='', **kwargs)
|
||||
|
||||
resource = client.parser.parse_known_args()[0].resource
|
||||
if resource in DEPRECATED_RESOURCES.values():
|
||||
client.argv[
|
||||
client.argv.index(resource)
|
||||
] = DEPRECATED_RESOURCES_REVERSE[resource]
|
||||
client.argv[client.argv.index(resource)] = DEPRECATED_RESOURCES_REVERSE[resource]
|
||||
resource = DEPRECATED_RESOURCES_REVERSE[resource]
|
||||
|
||||
if resource in CustomCommand.registry:
|
||||
@@ -219,27 +206,14 @@ def parse_resource(client, skip_deprecated=False):
|
||||
response = command.handle(client, parser)
|
||||
if response:
|
||||
_filter = client.get_config('filter')
|
||||
if (
|
||||
resource == 'config' and
|
||||
client.get_config('format') == 'human'
|
||||
):
|
||||
response = {
|
||||
'count': len(response),
|
||||
'results': [
|
||||
{'key': k, 'value': v}
|
||||
for k, v in response.items()
|
||||
]
|
||||
}
|
||||
if resource == 'config' and client.get_config('format') == 'human':
|
||||
response = {'count': len(response), 'results': [{'key': k, 'value': v} for k, v in response.items()]}
|
||||
_filter = 'key, value'
|
||||
try:
|
||||
connection = client.root.connection
|
||||
except AttributeError:
|
||||
connection = None
|
||||
formatted = format_response(
|
||||
Page.from_json(response, connection=connection),
|
||||
fmt=client.get_config('format'),
|
||||
filter=_filter
|
||||
)
|
||||
formatted = format_response(Page.from_json(response, connection=connection), fmt=client.get_config('format'), filter=_filter)
|
||||
print(formatted)
|
||||
raise SystemExit()
|
||||
else:
|
||||
|
||||
@@ -8,7 +8,6 @@ from .resource import is_control_resource, CustomCommand
|
||||
|
||||
|
||||
class CustomAutoprogramDirective(AutoprogramDirective):
|
||||
|
||||
def run(self):
|
||||
nodes = super(CustomAutoprogramDirective, self).run()
|
||||
|
||||
@@ -23,12 +22,7 @@ class CustomAutoprogramDirective(AutoprogramDirective):
|
||||
nodes[0][0].children = [heading]
|
||||
|
||||
# add a descriptive top synopsis of the reference guide
|
||||
nodes[0].children.insert(1, paragraph(
|
||||
text=(
|
||||
'This is an exhaustive guide of every available command in '
|
||||
'the awx CLI tool.'
|
||||
)
|
||||
))
|
||||
nodes[0].children.insert(1, paragraph(text=('This is an exhaustive guide of every available command in ' 'the awx CLI tool.')))
|
||||
disclaimer = (
|
||||
'The commands and parameters documented here can (and will) '
|
||||
'vary based on a variety of factors, such as the AWX API '
|
||||
@@ -51,9 +45,7 @@ def render():
|
||||
# Sphinx document from.
|
||||
for e in ('TOWER_HOST', 'TOWER_USERNAME', 'TOWER_PASSWORD'):
|
||||
if not os.environ.get(e):
|
||||
raise SystemExit(
|
||||
'Please specify a valid {} for a real (running) Tower install.'.format(e) # noqa
|
||||
)
|
||||
raise SystemExit('Please specify a valid {} for a real (running) Tower install.'.format(e)) # noqa
|
||||
cli = CLI()
|
||||
cli.parse_args(['awx', '--help'])
|
||||
cli.connect()
|
||||
|
||||
@@ -9,8 +9,7 @@ from .utils import cprint, color_enabled, STATUS_COLORS
|
||||
from awxkit.utils import to_str
|
||||
|
||||
|
||||
def monitor_workflow(response, session, print_stdout=True, timeout=None,
|
||||
interval=.25):
|
||||
def monitor_workflow(response, session, print_stdout=True, timeout=None, interval=0.25):
|
||||
get = response.url.get
|
||||
payload = {
|
||||
'order_by': 'finished',
|
||||
@@ -18,9 +17,7 @@ def monitor_workflow(response, session, print_stdout=True, timeout=None,
|
||||
}
|
||||
|
||||
def fetch(seen):
|
||||
results = response.connection.get(
|
||||
'/api/v2/unified_jobs', payload
|
||||
).json()['results']
|
||||
results = response.connection.get('/api/v2/unified_jobs', payload).json()['results']
|
||||
|
||||
# erase lines we've previously printed
|
||||
if print_stdout and sys.stdout.isatty():
|
||||
@@ -61,7 +58,7 @@ def monitor_workflow(response, session, print_stdout=True, timeout=None,
|
||||
# all at the end
|
||||
fetch(seen)
|
||||
|
||||
time.sleep(.25)
|
||||
time.sleep(0.25)
|
||||
json = get().json
|
||||
if json.finished:
|
||||
fetch(seen)
|
||||
@@ -71,7 +68,7 @@ def monitor_workflow(response, session, print_stdout=True, timeout=None,
|
||||
return get().json.status
|
||||
|
||||
|
||||
def monitor(response, session, print_stdout=True, timeout=None, interval=.25):
|
||||
def monitor(response, session, print_stdout=True, timeout=None, interval=0.25):
|
||||
get = response.url.get
|
||||
payload = {'order_by': 'start_line', 'no_truncate': True}
|
||||
if response.type == 'job':
|
||||
@@ -108,12 +105,9 @@ def monitor(response, session, print_stdout=True, timeout=None, interval=.25):
|
||||
if next_line:
|
||||
payload['start_line__gte'] = next_line
|
||||
|
||||
time.sleep(.25)
|
||||
time.sleep(0.25)
|
||||
json = get().json
|
||||
if (
|
||||
json.event_processing_finished is True or
|
||||
json.status in ('error', 'canceled')
|
||||
):
|
||||
if json.event_processing_finished is True or json.status in ('error', 'canceled'):
|
||||
fetch(next_line)
|
||||
break
|
||||
if print_stdout:
|
||||
|
||||
@@ -9,8 +9,7 @@ _color = threading.local()
|
||||
_color.enabled = True
|
||||
|
||||
|
||||
__all__ = ['CustomRegistryMeta', 'HelpfulArgumentParser', 'disable_color',
|
||||
'color_enabled', 'colored', 'cprint', 'STATUS_COLORS']
|
||||
__all__ = ['CustomRegistryMeta', 'HelpfulArgumentParser', 'disable_color', 'color_enabled', 'colored', 'cprint', 'STATUS_COLORS']
|
||||
|
||||
|
||||
STATUS_COLORS = {
|
||||
@@ -25,17 +24,12 @@ STATUS_COLORS = {
|
||||
|
||||
|
||||
class CustomRegistryMeta(type):
|
||||
|
||||
@property
|
||||
def registry(cls):
|
||||
return dict(
|
||||
(command.name, command)
|
||||
for command in cls.__subclasses__()
|
||||
)
|
||||
return dict((command.name, command) for command in cls.__subclasses__())
|
||||
|
||||
|
||||
class HelpfulArgumentParser(ArgumentParser):
|
||||
|
||||
def error(self, message): # pragma: nocover
|
||||
"""Prints a usage message incorporating the message to stderr and
|
||||
exits.
|
||||
@@ -67,10 +61,16 @@ COLORS = dict(
|
||||
list(
|
||||
zip(
|
||||
[
|
||||
'grey', 'red', 'green', 'yellow', 'blue', 'magenta', 'cyan',
|
||||
'grey',
|
||||
'red',
|
||||
'green',
|
||||
'yellow',
|
||||
'blue',
|
||||
'magenta',
|
||||
'cyan',
|
||||
'white',
|
||||
],
|
||||
list(range(30, 38))
|
||||
list(range(30, 38)),
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
|
||||
class Common(Exception):
|
||||
|
||||
def __init__(self, status_string='', message=''):
|
||||
if isinstance(status_string, Exception):
|
||||
self.status_string = ''
|
||||
|
||||
@@ -17,35 +17,26 @@ def parse_args():
|
||||
parser.add_argument(
|
||||
'--base-url',
|
||||
dest='base_url',
|
||||
default=os.getenv(
|
||||
'AWXKIT_BASE_URL',
|
||||
'http://127.0.0.1:8013'),
|
||||
help='URL for AWX. Defaults to env var AWXKIT_BASE_URL or http://127.0.0.1:8013')
|
||||
default=os.getenv('AWXKIT_BASE_URL', 'http://127.0.0.1:8013'),
|
||||
help='URL for AWX. Defaults to env var AWXKIT_BASE_URL or http://127.0.0.1:8013',
|
||||
)
|
||||
parser.add_argument(
|
||||
'-c',
|
||||
'--credential-file',
|
||||
dest='credential_file',
|
||||
default=os.getenv(
|
||||
'AWXKIT_CREDENTIAL_FILE',
|
||||
utils.not_provided),
|
||||
default=os.getenv('AWXKIT_CREDENTIAL_FILE', utils.not_provided),
|
||||
help='Path for yml credential file. If not provided or set by AWXKIT_CREDENTIAL_FILE, set '
|
||||
'AWXKIT_USER and AWXKIT_USER_PASSWORD env vars for awx user credentials.')
|
||||
'AWXKIT_USER and AWXKIT_USER_PASSWORD env vars for awx user credentials.',
|
||||
)
|
||||
parser.add_argument(
|
||||
'-p',
|
||||
'--project-file',
|
||||
dest='project_file',
|
||||
default=os.getenv(
|
||||
'AWXKIT_PROJECT_FILE'),
|
||||
help='Path for yml project config file.'
|
||||
'If not provided or set by AWXKIT_PROJECT_FILE, projects will not have default SCM_URL')
|
||||
parser.add_argument('-f', '--file', dest='akit_script', default=False,
|
||||
help='akit script file to run in interactive session.')
|
||||
parser.add_argument(
|
||||
'-x',
|
||||
'--non-interactive',
|
||||
action='store_true',
|
||||
dest='non_interactive',
|
||||
help='Do not run in interactive mode.')
|
||||
default=os.getenv('AWXKIT_PROJECT_FILE'),
|
||||
help='Path for yml project config file.' 'If not provided or set by AWXKIT_PROJECT_FILE, projects will not have default SCM_URL',
|
||||
)
|
||||
parser.add_argument('-f', '--file', dest='akit_script', default=False, help='akit script file to run in interactive session.')
|
||||
parser.add_argument('-x', '--non-interactive', action='store_true', dest='non_interactive', help='Do not run in interactive mode.')
|
||||
return parser.parse_known_args()[0]
|
||||
|
||||
|
||||
@@ -57,19 +48,14 @@ def main():
|
||||
config.base_url = akit_args.base_url
|
||||
|
||||
if akit_args.credential_file != utils.not_provided:
|
||||
config.credentials = utils.load_credentials(
|
||||
akit_args.credential_file)
|
||||
config.credentials = utils.load_credentials(akit_args.credential_file)
|
||||
else:
|
||||
config.credentials = utils.PseudoNamespace({
|
||||
'default': {
|
||||
'username': os.getenv('AWXKIT_USER', 'admin'),
|
||||
'password': os.getenv('AWXKIT_USER_PASSWORD', 'password')
|
||||
}
|
||||
})
|
||||
config.credentials = utils.PseudoNamespace(
|
||||
{'default': {'username': os.getenv('AWXKIT_USER', 'admin'), 'password': os.getenv('AWXKIT_USER_PASSWORD', 'password')}}
|
||||
)
|
||||
|
||||
if akit_args.project_file != utils.not_provided:
|
||||
config.project_urls = utils.load_projects(
|
||||
akit_args.project_file)
|
||||
config.project_urls = utils.load_projects(akit_args.project_file)
|
||||
|
||||
global root
|
||||
root = api.Api()
|
||||
@@ -106,6 +92,7 @@ def load_interactive():
|
||||
|
||||
try:
|
||||
from IPython import start_ipython
|
||||
|
||||
basic_session_path = os.path.abspath(__file__)
|
||||
if basic_session_path[-1] == 'c': # start_ipython doesn't work w/ .pyc
|
||||
basic_session_path = basic_session_path[:-1]
|
||||
@@ -115,6 +102,7 @@ def load_interactive():
|
||||
return start_ipython(argv=sargs)
|
||||
except ImportError:
|
||||
from code import interact
|
||||
|
||||
main()
|
||||
interact('', local=dict(globals(), **locals()))
|
||||
|
||||
|
||||
@@ -34,7 +34,8 @@ cloud_types = (
|
||||
'rhv',
|
||||
'satellite6',
|
||||
'tower',
|
||||
'vmware')
|
||||
'vmware',
|
||||
)
|
||||
credential_type_kinds = ('cloud', 'net')
|
||||
|
||||
not_provided = 'xx__NOT_PROVIDED__xx'
|
||||
@@ -52,7 +53,6 @@ class NoReloadError(Exception):
|
||||
|
||||
|
||||
class PseudoNamespace(dict):
|
||||
|
||||
def __init__(self, _d=None, **loaded):
|
||||
if not isinstance(_d, dict):
|
||||
_d = {}
|
||||
@@ -79,9 +79,7 @@ class PseudoNamespace(dict):
|
||||
try:
|
||||
return self.__getitem__(attr)
|
||||
except KeyError:
|
||||
raise AttributeError(
|
||||
"{!r} has no attribute {!r}".format(
|
||||
self.__class__.__name__, attr))
|
||||
raise AttributeError("{!r} has no attribute {!r}".format(self.__class__.__name__, attr))
|
||||
|
||||
def __setattr__(self, attr, value):
|
||||
self.__setitem__(attr, value)
|
||||
@@ -116,11 +114,7 @@ class PseudoNamespace(dict):
|
||||
# PseudoNamespaces if applicable
|
||||
def update(self, iterable=None, **kw):
|
||||
if iterable:
|
||||
if (hasattr(iterable,
|
||||
'keys') and isinstance(iterable.keys,
|
||||
(types.FunctionType,
|
||||
types.BuiltinFunctionType,
|
||||
types.MethodType))):
|
||||
if hasattr(iterable, 'keys') and isinstance(iterable.keys, (types.FunctionType, types.BuiltinFunctionType, types.MethodType)):
|
||||
for key in iterable:
|
||||
self[key] = iterable[key]
|
||||
else:
|
||||
@@ -161,11 +155,7 @@ def filter_by_class(*item_class_tuples):
|
||||
examined_item = item[0]
|
||||
else:
|
||||
examined_item = item
|
||||
if is_class_or_instance(
|
||||
examined_item,
|
||||
cls) or is_proper_subclass(
|
||||
examined_item,
|
||||
cls):
|
||||
if is_class_or_instance(examined_item, cls) or is_proper_subclass(examined_item, cls):
|
||||
results.append(item)
|
||||
else:
|
||||
updated = (cls, item[1]) if was_tuple else cls
|
||||
@@ -249,7 +239,7 @@ def gen_utf_char():
|
||||
is_char = False
|
||||
b = 'b'
|
||||
while not is_char:
|
||||
b = random.randint(32, 0x10ffff)
|
||||
b = random.randint(32, 0x10FFFF)
|
||||
is_char = chr(b).isprintable()
|
||||
return chr(b)
|
||||
|
||||
@@ -266,20 +256,12 @@ def random_ipv4():
|
||||
|
||||
def random_ipv6():
|
||||
"""Generates a random ipv6 address;; useful for testing."""
|
||||
return ':'.join(
|
||||
'{0:x}'.format(
|
||||
random.randint(
|
||||
0,
|
||||
2 ** 16 -
|
||||
1)) for i in range(8))
|
||||
return ':'.join('{0:x}'.format(random.randint(0, 2 ** 16 - 1)) for i in range(8))
|
||||
|
||||
|
||||
def random_loopback_ip():
|
||||
"""Generates a random loopback ipv4 address;; useful for testing."""
|
||||
return "127.{}.{}.{}".format(
|
||||
random_int(255),
|
||||
random_int(255),
|
||||
random_int(255))
|
||||
return "127.{}.{}.{}".format(random_int(255), random_int(255), random_int(255))
|
||||
|
||||
|
||||
def random_utf8(*args, **kwargs):
|
||||
@@ -289,8 +271,7 @@ def random_utf8(*args, **kwargs):
|
||||
"""
|
||||
pattern = re.compile('[^\u0000-\uD7FF\uE000-\uFFFF]', re.UNICODE)
|
||||
length = args[0] if len(args) else kwargs.get('length', 10)
|
||||
scrubbed = pattern.sub('\uFFFD', ''.join(
|
||||
[gen_utf_char() for _ in range(length)]))
|
||||
scrubbed = pattern.sub('\uFFFD', ''.join([gen_utf_char() for _ in range(length)]))
|
||||
|
||||
return scrubbed
|
||||
|
||||
@@ -374,8 +355,10 @@ def is_proper_subclass(obj, cls):
|
||||
|
||||
def are_same_endpoint(first, second):
|
||||
"""Equivalence check of two urls, stripped of query parameters"""
|
||||
|
||||
def strip(url):
|
||||
return url.replace('www.', '').split('?')[0]
|
||||
|
||||
return strip(first) == strip(second)
|
||||
|
||||
|
||||
@@ -421,10 +404,7 @@ class UTC(tzinfo):
|
||||
return timedelta(0)
|
||||
|
||||
|
||||
def seconds_since_date_string(
|
||||
date_str,
|
||||
fmt='%Y-%m-%dT%H:%M:%S.%fZ',
|
||||
default_tz=UTC()):
|
||||
def seconds_since_date_string(date_str, fmt='%Y-%m-%dT%H:%M:%S.%fZ', default_tz=UTC()):
|
||||
"""Return the number of seconds since the date and time indicated by a date
|
||||
string and its corresponding format string.
|
||||
|
||||
|
||||
@@ -42,18 +42,19 @@ class CircularDependencyError(ValueError):
|
||||
def __init__(self, data):
|
||||
# Sort the data just to make the output consistent, for use in
|
||||
# error messages. That's convenient for doctests.
|
||||
s = 'Circular dependencies exist among these items: {{{}}}'.format(', '.join('{!r}:{!r}'.format(key, value) for key, value in sorted(data.items()))) # noqa
|
||||
s = 'Circular dependencies exist among these items: {{{}}}'.format(
|
||||
', '.join('{!r}:{!r}'.format(key, value) for key, value in sorted(data.items()))
|
||||
) # noqa
|
||||
super(CircularDependencyError, self).__init__(s)
|
||||
self.data = data
|
||||
|
||||
|
||||
def toposort(data):
|
||||
"""Dependencies are expressed as a dictionary whose keys are items
|
||||
and whose values are a set of dependent items. Output is a list of
|
||||
sets in topological order. The first set consists of items with no
|
||||
dependences, each subsequent set consists of items that depend upon
|
||||
items in the preceeding sets.
|
||||
"""
|
||||
and whose values are a set of dependent items. Output is a list of
|
||||
sets in topological order. The first set consists of items with no
|
||||
dependences, each subsequent set consists of items that depend upon
|
||||
items in the preceeding sets."""
|
||||
|
||||
# Special case empty input.
|
||||
if len(data) == 0:
|
||||
@@ -74,9 +75,6 @@ items in the preceeding sets.
|
||||
if not ordered:
|
||||
break
|
||||
yield ordered
|
||||
data = {
|
||||
item: (dep - ordered)
|
||||
for item, dep in data.items() if item not in ordered
|
||||
}
|
||||
data = {item: (dep - ordered) for item, dep in data.items() if item not in ordered}
|
||||
if len(data) != 0:
|
||||
raise CircularDependencyError(data)
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -84,12 +84,9 @@ class WSClient(object):
|
||||
auth_cookie = ''
|
||||
pref = 'wss://' if self._use_ssl else 'ws://'
|
||||
url = '{0}{1.hostname}:{1.port}/websocket/'.format(pref, self)
|
||||
self.ws = websocket.WebSocketApp(url,
|
||||
on_open=self._on_open,
|
||||
on_message=self._on_message,
|
||||
on_error=self._on_error,
|
||||
on_close=self._on_close,
|
||||
cookie=auth_cookie)
|
||||
self.ws = websocket.WebSocketApp(
|
||||
url, on_open=self._on_open, on_message=self._on_message, on_error=self._on_error, on_close=self._on_close, cookie=auth_cookie
|
||||
)
|
||||
self._message_cache = []
|
||||
self._should_subscribe_to_pending_job = False
|
||||
self._pending_unsubscribe = threading.Event()
|
||||
@@ -199,12 +196,8 @@ class WSClient(object):
|
||||
message = json.loads(message)
|
||||
log.debug('received message: {}'.format(message))
|
||||
|
||||
if all([message.get('group_name') == 'jobs',
|
||||
message.get('status') == 'pending',
|
||||
message.get('unified_job_id'),
|
||||
self._should_subscribe_to_pending_job]):
|
||||
if bool(message.get('project_id')) == (
|
||||
self._should_subscribe_to_pending_job['events'] == 'project_update_events'):
|
||||
if all([message.get('group_name') == 'jobs', message.get('status') == 'pending', message.get('unified_job_id'), self._should_subscribe_to_pending_job]):
|
||||
if bool(message.get('project_id')) == (self._should_subscribe_to_pending_job['events'] == 'project_update_events'):
|
||||
self._update_subscription(message['unified_job_id'])
|
||||
|
||||
ret = self._recv_queue.put(message)
|
||||
|
||||
@@ -12,7 +12,6 @@ file_path_cache = {}
|
||||
|
||||
|
||||
class Loader(yaml.SafeLoader):
|
||||
|
||||
def __init__(self, stream):
|
||||
self._root = os.path.split(stream.name)[0]
|
||||
super(Loader, self).__init__(stream)
|
||||
@@ -82,6 +81,7 @@ def load_file(filename):
|
||||
random_thing: "{random_string:24}"
|
||||
"""
|
||||
from py.path import local
|
||||
|
||||
if filename is None:
|
||||
this_file = os.path.abspath(__file__)
|
||||
path = local(this_file).new(basename='../data.yaml')
|
||||
|
||||
@@ -68,11 +68,7 @@ setup(
|
||||
'requests',
|
||||
],
|
||||
python_requires=">=3.6",
|
||||
extras_require={
|
||||
'formatting': ['jq'],
|
||||
'websockets': ['websocket-client==0.57.0'],
|
||||
'crypto': ['cryptography']
|
||||
},
|
||||
extras_require={'formatting': ['jq'], 'websockets': ['websocket-client==0.57.0'], 'crypto': ['cryptography']},
|
||||
license='Apache 2.0',
|
||||
classifiers=[
|
||||
'Development Status :: 5 - Production/Stable',
|
||||
@@ -87,10 +83,5 @@ setup(
|
||||
'Topic :: System :: Software Distribution',
|
||||
'Topic :: System :: Systems Administration',
|
||||
],
|
||||
entry_points={
|
||||
'console_scripts': [
|
||||
'akit=awxkit.scripts.basic_session:load_interactive',
|
||||
'awx=awxkit.cli:run'
|
||||
]
|
||||
}
|
||||
entry_points={'console_scripts': ['akit=awxkit.scripts.basic_session:load_interactive', 'awx=awxkit.cli:run']},
|
||||
)
|
||||
|
||||
@@ -7,7 +7,6 @@ from awxkit.cli import run, CLI
|
||||
|
||||
|
||||
class MockedCLI(CLI):
|
||||
|
||||
def fetch_version_root(self):
|
||||
pass
|
||||
|
||||
@@ -17,9 +16,7 @@ class MockedCLI(CLI):
|
||||
|
||||
@property
|
||||
def json(self):
|
||||
return {
|
||||
'users': None
|
||||
}
|
||||
return {'users': None}
|
||||
|
||||
|
||||
@pytest.mark.parametrize('help_param', ['-h', '--help'])
|
||||
@@ -29,10 +26,7 @@ def test_help(capfd, help_param):
|
||||
out, err = capfd.readouterr()
|
||||
|
||||
assert "usage:" in out
|
||||
for snippet in (
|
||||
'--conf.host https://example.awx.org]',
|
||||
'-v, --verbose'
|
||||
):
|
||||
for snippet in ('--conf.host https://example.awx.org]', '-v, --verbose'):
|
||||
assert snippet in out
|
||||
|
||||
|
||||
@@ -59,8 +53,5 @@ def test_list_resources(capfd, resource):
|
||||
_, out = capfd.readouterr()
|
||||
|
||||
assert "usage:" in out
|
||||
for snippet in (
|
||||
'--conf.host https://example.awx.org]',
|
||||
'-v, --verbose'
|
||||
):
|
||||
for snippet in ('--conf.host https://example.awx.org]', '-v, --verbose'):
|
||||
assert snippet in out
|
||||
|
||||
@@ -4,16 +4,15 @@ from requests.exceptions import ConnectionError
|
||||
from awxkit.cli import CLI
|
||||
from awxkit import config
|
||||
|
||||
|
||||
def test_host_from_environment():
|
||||
cli = CLI()
|
||||
cli.parse_args(
|
||||
['awx'],
|
||||
env={'TOWER_HOST': 'https://xyz.local'}
|
||||
)
|
||||
cli.parse_args(['awx'], env={'TOWER_HOST': 'https://xyz.local'})
|
||||
with pytest.raises(ConnectionError):
|
||||
cli.connect()
|
||||
assert config.base_url == 'https://xyz.local'
|
||||
|
||||
|
||||
def test_host_from_argv():
|
||||
cli = CLI()
|
||||
cli.parse_args(['awx', '--conf.host', 'https://xyz.local'])
|
||||
@@ -21,43 +20,30 @@ def test_host_from_argv():
|
||||
cli.connect()
|
||||
assert config.base_url == 'https://xyz.local'
|
||||
|
||||
|
||||
def test_username_and_password_from_environment():
|
||||
cli = CLI()
|
||||
cli.parse_args(
|
||||
['awx'],
|
||||
env={
|
||||
'TOWER_USERNAME': 'mary',
|
||||
'TOWER_PASSWORD': 'secret'
|
||||
}
|
||||
)
|
||||
cli.parse_args(['awx'], env={'TOWER_USERNAME': 'mary', 'TOWER_PASSWORD': 'secret'})
|
||||
with pytest.raises(ConnectionError):
|
||||
cli.connect()
|
||||
|
||||
assert config.credentials.default.username == 'mary'
|
||||
assert config.credentials.default.password == 'secret'
|
||||
|
||||
|
||||
def test_username_and_password_argv():
|
||||
cli = CLI()
|
||||
cli.parse_args([
|
||||
'awx', '--conf.username', 'mary', '--conf.password', 'secret'
|
||||
])
|
||||
cli.parse_args(['awx', '--conf.username', 'mary', '--conf.password', 'secret'])
|
||||
with pytest.raises(ConnectionError):
|
||||
cli.connect()
|
||||
|
||||
assert config.credentials.default.username == 'mary'
|
||||
assert config.credentials.default.password == 'secret'
|
||||
|
||||
|
||||
def test_config_precedence():
|
||||
cli = CLI()
|
||||
cli.parse_args(
|
||||
[
|
||||
'awx', '--conf.username', 'mary', '--conf.password', 'secret'
|
||||
],
|
||||
env={
|
||||
'TOWER_USERNAME': 'IGNORE',
|
||||
'TOWER_PASSWORD': 'IGNORE'
|
||||
}
|
||||
)
|
||||
cli.parse_args(['awx', '--conf.username', 'mary', '--conf.password', 'secret'], env={'TOWER_USERNAME': 'IGNORE', 'TOWER_PASSWORD': 'IGNORE'})
|
||||
with pytest.raises(ConnectionError):
|
||||
cli.connect()
|
||||
|
||||
|
||||
@@ -11,19 +11,17 @@ from awxkit.cli.resource import Import
|
||||
|
||||
|
||||
def test_json_empty_list():
|
||||
page = Page.from_json({
|
||||
'results': []
|
||||
})
|
||||
page = Page.from_json({'results': []})
|
||||
formatted = format_response(page)
|
||||
assert json.loads(formatted) == {'results': []}
|
||||
|
||||
|
||||
def test_yaml_empty_list():
|
||||
page = Page.from_json({
|
||||
'results': []
|
||||
})
|
||||
page = Page.from_json({'results': []})
|
||||
formatted = format_response(page, fmt='yaml')
|
||||
assert yaml.safe_load(formatted) == {'results': []}
|
||||
|
||||
|
||||
def test_json_list():
|
||||
users = {
|
||||
'results': [
|
||||
@@ -36,6 +34,7 @@ def test_json_list():
|
||||
formatted = format_response(page)
|
||||
assert json.loads(formatted) == users
|
||||
|
||||
|
||||
def test_yaml_list():
|
||||
users = {
|
||||
'results': [
|
||||
|
||||
@@ -11,13 +11,11 @@ from awxkit.cli.options import ResourceOptionsParser
|
||||
|
||||
|
||||
class ResourceOptionsParser(ResourceOptionsParser):
|
||||
|
||||
def get_allowed_options(self):
|
||||
self.allowed_options = ['GET', 'POST', 'PUT', 'PATCH', 'DELETE']
|
||||
|
||||
|
||||
class OptionsPage(Page):
|
||||
|
||||
def options(self):
|
||||
return self
|
||||
|
||||
@@ -33,30 +31,31 @@ class OptionsPage(Page):
|
||||
|
||||
|
||||
class TestOptions(unittest.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
_parser = argparse.ArgumentParser()
|
||||
self.parser = _parser.add_subparsers(help='action')
|
||||
|
||||
def test_list(self):
|
||||
page = OptionsPage.from_json({
|
||||
'actions': {
|
||||
'GET': {},
|
||||
'POST': {},
|
||||
page = OptionsPage.from_json(
|
||||
{
|
||||
'actions': {
|
||||
'GET': {},
|
||||
'POST': {},
|
||||
}
|
||||
}
|
||||
})
|
||||
)
|
||||
ResourceOptionsParser(None, page, 'users', self.parser)
|
||||
assert 'list' in self.parser.choices
|
||||
|
||||
def test_list_filtering(self):
|
||||
page = OptionsPage.from_json({
|
||||
'actions': {
|
||||
'GET': {},
|
||||
'POST': {
|
||||
'first_name': {'type': 'string'}
|
||||
},
|
||||
page = OptionsPage.from_json(
|
||||
{
|
||||
'actions': {
|
||||
'GET': {},
|
||||
'POST': {'first_name': {'type': 'string'}},
|
||||
}
|
||||
}
|
||||
})
|
||||
)
|
||||
options = ResourceOptionsParser(None, page, 'users', self.parser)
|
||||
options.build_query_arguments('list', 'POST')
|
||||
assert 'list' in self.parser.choices
|
||||
@@ -66,14 +65,14 @@ class TestOptions(unittest.TestCase):
|
||||
assert '--first_name TEXT' in out.getvalue()
|
||||
|
||||
def test_list_not_filterable(self):
|
||||
page = OptionsPage.from_json({
|
||||
'actions': {
|
||||
'GET': {},
|
||||
'POST': {
|
||||
'middle_name': {'type': 'string', 'filterable': False}
|
||||
},
|
||||
page = OptionsPage.from_json(
|
||||
{
|
||||
'actions': {
|
||||
'GET': {},
|
||||
'POST': {'middle_name': {'type': 'string', 'filterable': False}},
|
||||
}
|
||||
}
|
||||
})
|
||||
)
|
||||
options = ResourceOptionsParser(None, page, 'users', self.parser)
|
||||
options.build_query_arguments('list', 'POST')
|
||||
assert 'list' in self.parser.choices
|
||||
@@ -83,16 +82,18 @@ class TestOptions(unittest.TestCase):
|
||||
assert '--middle_name' not in out.getvalue()
|
||||
|
||||
def test_creation_optional_argument(self):
|
||||
page = OptionsPage.from_json({
|
||||
'actions': {
|
||||
'POST': {
|
||||
'first_name': {
|
||||
'type': 'string',
|
||||
'help_text': 'Please specify your first name',
|
||||
}
|
||||
},
|
||||
page = OptionsPage.from_json(
|
||||
{
|
||||
'actions': {
|
||||
'POST': {
|
||||
'first_name': {
|
||||
'type': 'string',
|
||||
'help_text': 'Please specify your first name',
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
})
|
||||
)
|
||||
options = ResourceOptionsParser(None, page, 'users', self.parser)
|
||||
options.build_query_arguments('create', 'POST')
|
||||
assert 'create' in self.parser.choices
|
||||
@@ -102,17 +103,13 @@ class TestOptions(unittest.TestCase):
|
||||
assert '--first_name TEXT Please specify your first name' in out.getvalue()
|
||||
|
||||
def test_creation_required_argument(self):
|
||||
page = OptionsPage.from_json({
|
||||
'actions': {
|
||||
'POST': {
|
||||
'username': {
|
||||
'type': 'string',
|
||||
'help_text': 'Please specify a username',
|
||||
'required': True
|
||||
}
|
||||
},
|
||||
page = OptionsPage.from_json(
|
||||
{
|
||||
'actions': {
|
||||
'POST': {'username': {'type': 'string', 'help_text': 'Please specify a username', 'required': True}},
|
||||
}
|
||||
}
|
||||
})
|
||||
)
|
||||
options = ResourceOptionsParser(None, page, 'users', self.parser)
|
||||
options.build_query_arguments('create', 'POST')
|
||||
assert 'create' in self.parser.choices
|
||||
@@ -122,13 +119,13 @@ class TestOptions(unittest.TestCase):
|
||||
assert '--username TEXT Please specify a username'
|
||||
|
||||
def test_integer_argument(self):
|
||||
page = OptionsPage.from_json({
|
||||
'actions': {
|
||||
'POST': {
|
||||
'max_hosts': {'type': 'integer'}
|
||||
},
|
||||
page = OptionsPage.from_json(
|
||||
{
|
||||
'actions': {
|
||||
'POST': {'max_hosts': {'type': 'integer'}},
|
||||
}
|
||||
}
|
||||
})
|
||||
)
|
||||
options = ResourceOptionsParser(None, page, 'organizations', self.parser)
|
||||
options.build_query_arguments('create', 'POST')
|
||||
assert 'create' in self.parser.choices
|
||||
@@ -138,13 +135,13 @@ class TestOptions(unittest.TestCase):
|
||||
assert '--max_hosts INTEGER' in out.getvalue()
|
||||
|
||||
def test_boolean_argument(self):
|
||||
page = OptionsPage.from_json({
|
||||
'actions': {
|
||||
'POST': {
|
||||
'diff_mode': {'type': 'boolean'}
|
||||
},
|
||||
page = OptionsPage.from_json(
|
||||
{
|
||||
'actions': {
|
||||
'POST': {'diff_mode': {'type': 'boolean'}},
|
||||
}
|
||||
}
|
||||
})
|
||||
)
|
||||
options = ResourceOptionsParser(None, page, 'users', self.parser)
|
||||
options.build_query_arguments('create', 'POST')
|
||||
assert 'create' in self.parser.choices
|
||||
@@ -154,23 +151,25 @@ class TestOptions(unittest.TestCase):
|
||||
assert '--diff_mode BOOLEAN' in out.getvalue()
|
||||
|
||||
def test_choices(self):
|
||||
page = OptionsPage.from_json({
|
||||
'actions': {
|
||||
'POST': {
|
||||
'verbosity': {
|
||||
'type': 'integer',
|
||||
'choices': [
|
||||
(0, '0 (Normal)'),
|
||||
(1, '1 (Verbose)'),
|
||||
(2, '2 (More Verbose)'),
|
||||
(3, '3 (Debug)'),
|
||||
(4, '4 (Connection Debug)'),
|
||||
(5, '5 (WinRM Debug)'),
|
||||
]
|
||||
}
|
||||
},
|
||||
page = OptionsPage.from_json(
|
||||
{
|
||||
'actions': {
|
||||
'POST': {
|
||||
'verbosity': {
|
||||
'type': 'integer',
|
||||
'choices': [
|
||||
(0, '0 (Normal)'),
|
||||
(1, '1 (Verbose)'),
|
||||
(2, '2 (More Verbose)'),
|
||||
(3, '3 (Debug)'),
|
||||
(4, '4 (Connection Debug)'),
|
||||
(5, '5 (WinRM Debug)'),
|
||||
],
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
})
|
||||
)
|
||||
options = ResourceOptionsParser(None, page, 'users', self.parser)
|
||||
options.build_query_arguments('create', 'POST')
|
||||
assert 'create' in self.parser.choices
|
||||
@@ -181,9 +180,7 @@ class TestOptions(unittest.TestCase):
|
||||
|
||||
def test_actions_with_primary_key(self):
|
||||
for method in ('get', 'modify', 'delete'):
|
||||
page = OptionsPage.from_json({
|
||||
'actions': {'GET': {}, 'POST': {}}
|
||||
})
|
||||
page = OptionsPage.from_json({'actions': {'GET': {}, 'POST': {}}})
|
||||
ResourceOptionsParser(None, page, 'jobs', self.parser)
|
||||
assert method in self.parser.choices
|
||||
|
||||
@@ -193,19 +190,20 @@ class TestOptions(unittest.TestCase):
|
||||
|
||||
|
||||
class TestSettingsOptions(unittest.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
_parser = argparse.ArgumentParser()
|
||||
self.parser = _parser.add_subparsers(help='action')
|
||||
|
||||
def test_list(self):
|
||||
page = OptionsPage.from_json({
|
||||
'actions': {
|
||||
'GET': {},
|
||||
'POST': {},
|
||||
'PUT': {},
|
||||
page = OptionsPage.from_json(
|
||||
{
|
||||
'actions': {
|
||||
'GET': {},
|
||||
'POST': {},
|
||||
'PUT': {},
|
||||
}
|
||||
}
|
||||
})
|
||||
)
|
||||
page.endpoint = '/settings/all/'
|
||||
ResourceOptionsParser(None, page, 'settings', self.parser)
|
||||
assert 'list' in self.parser.choices
|
||||
|
||||
@@ -14,32 +14,39 @@ def set_config_cred_to_desired(config, location):
|
||||
config_ref = config_ref[_location]
|
||||
setattr(config_ref, split[-1], 'desired')
|
||||
|
||||
class MockCredentialType(object):
|
||||
|
||||
class MockCredentialType(object):
|
||||
def __init__(self, name, kind, managed_by_tower=True):
|
||||
self.name = name
|
||||
self.kind = kind
|
||||
self.managed_by_tower = managed_by_tower
|
||||
|
||||
@pytest.mark.parametrize('field, kind, config_cred, desired_field, desired_value',
|
||||
[('field', 'ssh', PseudoNamespace(field=123), 'field', 123),
|
||||
('subscription', 'azure', PseudoNamespace(subscription_id=123), 'subscription', 123),
|
||||
('project_id', 'gce', PseudoNamespace(project=123), 'project', 123),
|
||||
('authorize_password', 'net', PseudoNamespace(authorize=123), 'authorize_password', 123)])
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
'field, kind, config_cred, desired_field, desired_value',
|
||||
[
|
||||
('field', 'ssh', PseudoNamespace(field=123), 'field', 123),
|
||||
('subscription', 'azure', PseudoNamespace(subscription_id=123), 'subscription', 123),
|
||||
('project_id', 'gce', PseudoNamespace(project=123), 'project', 123),
|
||||
('authorize_password', 'net', PseudoNamespace(authorize=123), 'authorize_password', 123),
|
||||
],
|
||||
)
|
||||
def test_get_payload_field_and_value_from_config_cred(field, kind, config_cred, desired_field, desired_value):
|
||||
ret_field, ret_val = credentials.get_payload_field_and_value_from_kwargs_or_config_cred(field, kind, {},
|
||||
config_cred)
|
||||
ret_field, ret_val = credentials.get_payload_field_and_value_from_kwargs_or_config_cred(field, kind, {}, config_cred)
|
||||
assert ret_field == desired_field
|
||||
assert ret_val == desired_value
|
||||
|
||||
|
||||
@pytest.mark.parametrize('field, kind, kwargs, desired_field, desired_value',
|
||||
[('field', 'ssh', dict(field=123), 'field', 123),
|
||||
('subscription', 'azure', dict(subscription=123), 'subscription', 123),
|
||||
('project_id', 'gce', dict(project_id=123), 'project', 123),
|
||||
('authorize_password', 'net', dict(authorize_password=123), 'authorize_password', 123)])
|
||||
@pytest.mark.parametrize(
|
||||
'field, kind, kwargs, desired_field, desired_value',
|
||||
[
|
||||
('field', 'ssh', dict(field=123), 'field', 123),
|
||||
('subscription', 'azure', dict(subscription=123), 'subscription', 123),
|
||||
('project_id', 'gce', dict(project_id=123), 'project', 123),
|
||||
('authorize_password', 'net', dict(authorize_password=123), 'authorize_password', 123),
|
||||
],
|
||||
)
|
||||
def test_get_payload_field_and_value_from_kwarg(field, kind, kwargs, desired_field, desired_value):
|
||||
ret_field, ret_val = credentials.get_payload_field_and_value_from_kwargs_or_config_cred(field, kind, kwargs,
|
||||
PseudoNamespace())
|
||||
ret_field, ret_val = credentials.get_payload_field_and_value_from_kwargs_or_config_cred(field, kind, kwargs, PseudoNamespace())
|
||||
assert ret_field == desired_field
|
||||
assert ret_val == desired_value
|
||||
|
||||
@@ -21,7 +21,6 @@ class MockHasCreate(has_create.HasCreate):
|
||||
|
||||
|
||||
class A(MockHasCreate):
|
||||
|
||||
def create(self, **kw):
|
||||
return self
|
||||
|
||||
@@ -87,13 +86,12 @@ class H(MockHasCreate):
|
||||
|
||||
optional_dependencies = [E, A]
|
||||
|
||||
def create(self, a=None, e=None, **kw):
|
||||
def create(self, a=None, e=None, **kw):
|
||||
self.create_and_update_dependencies(*filter_by_class((a, A), (e, E)))
|
||||
return self
|
||||
|
||||
|
||||
class MultipleWordClassName(MockHasCreate):
|
||||
|
||||
def create(self, **kw):
|
||||
return self
|
||||
|
||||
@@ -102,7 +100,7 @@ class AnotherMultipleWordClassName(MockHasCreate):
|
||||
|
||||
optional_dependencies = [MultipleWordClassName]
|
||||
|
||||
def create(self, multiple_word_class_name=None, **kw):
|
||||
def create(self, multiple_word_class_name=None, **kw):
|
||||
self.create_and_update_dependencies(*filter_by_class((multiple_word_class_name, MultipleWordClassName)))
|
||||
return self
|
||||
|
||||
@@ -183,19 +181,17 @@ def test_optional_dependency_graph_with_additional():
|
||||
|
||||
def test_creation_order():
|
||||
"""confirms that `has_create.creation_order()` returns a valid creation order in the desired list of sets format"""
|
||||
dependency_graph = dict(eight=set(['seven', 'six']),
|
||||
seven=set(['five']),
|
||||
six=set(),
|
||||
five=set(['two', 'one']),
|
||||
four=set(['one']),
|
||||
three=set(['two']),
|
||||
two=set(['one']),
|
||||
one=set())
|
||||
desired = [set(['one', 'six']),
|
||||
set(['two', 'four']),
|
||||
set(['three', 'five']),
|
||||
set(['seven']),
|
||||
set(['eight'])]
|
||||
dependency_graph = dict(
|
||||
eight=set(['seven', 'six']),
|
||||
seven=set(['five']),
|
||||
six=set(),
|
||||
five=set(['two', 'one']),
|
||||
four=set(['one']),
|
||||
three=set(['two']),
|
||||
two=set(['one']),
|
||||
one=set(),
|
||||
)
|
||||
desired = [set(['one', 'six']), set(['two', 'four']), set(['three', 'five']), set(['seven']), set(['eight'])]
|
||||
assert has_create.creation_order(dependency_graph) == desired
|
||||
|
||||
|
||||
@@ -203,14 +199,16 @@ def test_creation_order_with_loop():
|
||||
"""confirms that `has_create.creation_order()` raises toposort.CircularDependencyError when evaluating
|
||||
a cyclic dependency graph
|
||||
"""
|
||||
dependency_graph = dict(eight=set(['seven', 'six']),
|
||||
seven=set(['five']),
|
||||
six=set(),
|
||||
five=set(['two', 'one']),
|
||||
four=set(['one']),
|
||||
three=set(['two']),
|
||||
two=set(['one']),
|
||||
one=set(['eight']))
|
||||
dependency_graph = dict(
|
||||
eight=set(['seven', 'six']),
|
||||
seven=set(['five']),
|
||||
six=set(),
|
||||
five=set(['two', 'one']),
|
||||
four=set(['one']),
|
||||
three=set(['two']),
|
||||
two=set(['one']),
|
||||
one=set(['eight']),
|
||||
)
|
||||
with pytest.raises(CircularDependencyError):
|
||||
assert has_create.creation_order(dependency_graph)
|
||||
|
||||
@@ -239,9 +237,11 @@ class Five(MockHasCreate):
|
||||
class IsntAHasCreate(object):
|
||||
pass
|
||||
|
||||
|
||||
class Six(MockHasCreate, IsntAHasCreate):
|
||||
dependencies = [Two]
|
||||
|
||||
|
||||
class Seven(MockHasCreate):
|
||||
dependencies = [IsntAHasCreate]
|
||||
|
||||
@@ -265,8 +265,7 @@ def test_separate_async_optionals_three_exist():
|
||||
the class that has shared item as a dependency occurs first in a separate creation group
|
||||
"""
|
||||
order = has_create.creation_order(has_create.optional_dependency_graph(Five, Four, Three))
|
||||
assert has_create.separate_async_optionals(order) == [set([One]), set([Two]), set([Three]),
|
||||
set([Five]), set([Four])]
|
||||
assert has_create.separate_async_optionals(order) == [set([One]), set([Two]), set([Three]), set([Five]), set([Four])]
|
||||
|
||||
|
||||
def test_separate_async_optionals_not_has_create():
|
||||
@@ -345,8 +344,7 @@ def test_dependency_resolution_complete():
|
||||
|
||||
for item in (h, a, e, d, c, b):
|
||||
if item._dependency_store:
|
||||
assert all(item._dependency_store.values()
|
||||
), "{0} missing dependency: {0._dependency_store}".format(item)
|
||||
assert all(item._dependency_store.values()), "{0} missing dependency: {0._dependency_store}".format(item)
|
||||
|
||||
assert a == b._dependency_store[A], "Duplicate dependency detected"
|
||||
assert a == c._dependency_store[A], "Duplicate dependency detected"
|
||||
@@ -468,7 +466,6 @@ def test_teardown_ds_cleared():
|
||||
|
||||
|
||||
class OneWithArgs(MockHasCreate):
|
||||
|
||||
def create(self, **kw):
|
||||
self.kw = kw
|
||||
return self
|
||||
@@ -492,18 +489,17 @@ class ThreeWithArgs(MockHasCreate):
|
||||
optional_dependencies = [TwoWithArgs]
|
||||
|
||||
def create(self, one_with_args=OneWithArgs, two_with_args=None, **kw):
|
||||
self.create_and_update_dependencies(*filter_by_class((one_with_args, OneWithArgs),
|
||||
(two_with_args, TwoWithArgs)))
|
||||
self.create_and_update_dependencies(*filter_by_class((one_with_args, OneWithArgs), (two_with_args, TwoWithArgs)))
|
||||
self.kw = kw
|
||||
return self
|
||||
|
||||
|
||||
class FourWithArgs(MockHasCreate):
|
||||
|
||||
dependencies = [TwoWithArgs, ThreeWithArgs]
|
||||
|
||||
def create(self, two_with_args=TwoWithArgs, three_with_args=ThreeWithArgs, **kw):
|
||||
self.create_and_update_dependencies(*filter_by_class((two_with_args, TwoWithArgs),
|
||||
(three_with_args, ThreeWithArgs)))
|
||||
self.create_and_update_dependencies(*filter_by_class((two_with_args, TwoWithArgs), (three_with_args, ThreeWithArgs)))
|
||||
self.kw = kw
|
||||
return self
|
||||
|
||||
@@ -536,10 +532,9 @@ def test_no_tuple_for_class_arg_causes_shared_dependencies_nested_staggering():
|
||||
|
||||
def test_tuple_for_class_arg_causes_unshared_dependencies_when_downstream():
|
||||
"""Confirms that provided arg-tuple for dependency type is applied instead of chained dependency"""
|
||||
three_wa = ThreeWithArgs().create(two_with_args=(TwoWithArgs, dict(one_with_args=False,
|
||||
make_one_with_args=True,
|
||||
two_with_args_kw_arg=234)),
|
||||
three_with_args_kw_arg=345)
|
||||
three_wa = ThreeWithArgs().create(
|
||||
two_with_args=(TwoWithArgs, dict(one_with_args=False, make_one_with_args=True, two_with_args_kw_arg=234)), three_with_args_kw_arg=345
|
||||
)
|
||||
assert isinstance(three_wa.ds.one_with_args, OneWithArgs)
|
||||
assert isinstance(three_wa.ds.two_with_args, TwoWithArgs)
|
||||
assert isinstance(three_wa.ds.two_with_args.ds.one_with_args, OneWithArgs)
|
||||
@@ -552,13 +547,12 @@ def test_tuple_for_class_arg_causes_unshared_dependencies_when_downstream():
|
||||
|
||||
def test_tuples_for_class_arg_cause_unshared_dependencies_when_downstream():
|
||||
"""Confirms that provided arg-tuple for dependency type is applied instead of chained dependency"""
|
||||
four_wa = FourWithArgs().create(two_with_args=(TwoWithArgs, dict(one_with_args=False,
|
||||
make_one_with_args=True,
|
||||
two_with_args_kw_arg=456)),
|
||||
# No shared dependencies with four_wa.ds.two_with_args
|
||||
three_with_args=(ThreeWithArgs, dict(one_with_args=(OneWithArgs, {}),
|
||||
two_with_args=False)),
|
||||
four_with_args_kw=567)
|
||||
four_wa = FourWithArgs().create(
|
||||
two_with_args=(TwoWithArgs, dict(one_with_args=False, make_one_with_args=True, two_with_args_kw_arg=456)),
|
||||
# No shared dependencies with four_wa.ds.two_with_args
|
||||
three_with_args=(ThreeWithArgs, dict(one_with_args=(OneWithArgs, {}), two_with_args=False)),
|
||||
four_with_args_kw=567,
|
||||
)
|
||||
assert isinstance(four_wa.ds.two_with_args, TwoWithArgs)
|
||||
assert isinstance(four_wa.ds.three_with_args, ThreeWithArgs)
|
||||
assert isinstance(four_wa.ds.two_with_args.ds.one_with_args, OneWithArgs)
|
||||
@@ -575,25 +569,21 @@ class NotHasCreate(object):
|
||||
|
||||
|
||||
class MixinUserA(MockHasCreate, NotHasCreate):
|
||||
|
||||
def create(self, **kw):
|
||||
return self
|
||||
|
||||
|
||||
class MixinUserB(MockHasCreate, NotHasCreate):
|
||||
|
||||
def create(self, **kw):
|
||||
return self
|
||||
|
||||
|
||||
class MixinUserC(MixinUserB):
|
||||
|
||||
def create(self, **kw):
|
||||
return self
|
||||
|
||||
|
||||
class MixinUserD(MixinUserC):
|
||||
|
||||
def create(self, **kw):
|
||||
return self
|
||||
|
||||
@@ -646,17 +636,12 @@ class DynamicallyDeclaresNotHasCreateDependency(MockHasCreate):
|
||||
dependencies = [NotHasCreate]
|
||||
|
||||
def create(self, not_has_create=MixinUserA):
|
||||
dynamic_dependency = dict(mixinusera=MixinUserA,
|
||||
mixinuserb=MixinUserB,
|
||||
mixinuserc=MixinUserC)
|
||||
dynamic_dependency = dict(mixinusera=MixinUserA, mixinuserb=MixinUserB, mixinuserc=MixinUserC)
|
||||
self.create_and_update_dependencies(dynamic_dependency[not_has_create])
|
||||
return self
|
||||
|
||||
|
||||
@pytest.mark.parametrize('dependency,dependency_class',
|
||||
[('mixinusera', MixinUserA),
|
||||
('mixinuserb', MixinUserB),
|
||||
('mixinuserc', MixinUserC)])
|
||||
@pytest.mark.parametrize('dependency,dependency_class', [('mixinusera', MixinUserA), ('mixinuserb', MixinUserB), ('mixinuserc', MixinUserC)])
|
||||
def test_subclass_or_parent_dynamic_not_has_create_dependency_declaration(dependency, dependency_class):
|
||||
"""Confirms that dependencies that dynamically declare dependencies subclassed from not HasCreate
|
||||
are properly linked
|
||||
@@ -670,17 +655,12 @@ class DynamicallyDeclaresHasCreateDependency(MockHasCreate):
|
||||
dependencies = [MixinUserB]
|
||||
|
||||
def create(self, mixin_user_b=MixinUserB):
|
||||
dynamic_dependency = dict(mixinuserb=MixinUserB,
|
||||
mixinuserc=MixinUserC,
|
||||
mixinuserd=MixinUserD)
|
||||
dynamic_dependency = dict(mixinuserb=MixinUserB, mixinuserc=MixinUserC, mixinuserd=MixinUserD)
|
||||
self.create_and_update_dependencies(dynamic_dependency[mixin_user_b])
|
||||
return self
|
||||
|
||||
|
||||
@pytest.mark.parametrize('dependency,dependency_class',
|
||||
[('mixinuserb', MixinUserB),
|
||||
('mixinuserc', MixinUserC),
|
||||
('mixinuserd', MixinUserD)])
|
||||
@pytest.mark.parametrize('dependency,dependency_class', [('mixinuserb', MixinUserB), ('mixinuserc', MixinUserC), ('mixinuserd', MixinUserD)])
|
||||
def test_subclass_or_parent_dynamic_has_create_dependency_declaration(dependency, dependency_class):
|
||||
"""Confirms that dependencies that dynamically declare dependencies subclassed from not HasCreate
|
||||
are properly linked
|
||||
|
||||
@@ -169,8 +169,7 @@ def test_wildcard_and_specific_method_registration_acts_as_default(reg):
|
||||
def test_multiple_method_registrations_disallowed_for_single_path_single_registration(reg, method):
|
||||
with pytest.raises(TypeError) as e:
|
||||
reg.register((('some_path', method), ('some_path', method)), One)
|
||||
assert str(e.value) == ('"{0.pattern}" already has registered method "{1}"'
|
||||
.format(reg.url_pattern('some_path'), method))
|
||||
assert str(e.value) == ('"{0.pattern}" already has registered method "{1}"'.format(reg.url_pattern('some_path'), method))
|
||||
|
||||
|
||||
@pytest.mark.parametrize('method', ('method', '.*'))
|
||||
@@ -178,8 +177,7 @@ def test_multiple_method_registrations_disallowed_for_single_path_multiple_regis
|
||||
reg.register('some_path', method, One)
|
||||
with pytest.raises(TypeError) as e:
|
||||
reg.register('some_path', method, One)
|
||||
assert str(e.value) == ('"{0.pattern}" already has registered method "{1}"'
|
||||
.format(reg.url_pattern('some_path'), method))
|
||||
assert str(e.value) == ('"{0.pattern}" already has registered method "{1}"'.format(reg.url_pattern('some_path'), method))
|
||||
|
||||
|
||||
def test_paths_can_be_patterns(reg):
|
||||
@@ -188,10 +186,9 @@ def test_paths_can_be_patterns(reg):
|
||||
|
||||
|
||||
def test_mixed_form_single_registration(reg):
|
||||
reg.register([('some_path_one', 'method_one'),
|
||||
'some_path_two',
|
||||
('some_path_three', ('method_two', 'method_three')),
|
||||
'some_path_four', 'some_path_five'], One)
|
||||
reg.register(
|
||||
[('some_path_one', 'method_one'), 'some_path_two', ('some_path_three', ('method_two', 'method_three')), 'some_path_four', 'some_path_five'], One
|
||||
)
|
||||
assert reg.get('some_path_one', 'method_one') is One
|
||||
assert reg.get('some_path_one') is None
|
||||
assert reg.get('some_path_one', 'nonexistent') is None
|
||||
@@ -209,10 +206,9 @@ def test_mixed_form_single_registration(reg):
|
||||
|
||||
def test_mixed_form_single_registration_with_methodless_default(reg):
|
||||
reg.setdefault(One)
|
||||
reg.register([('some_path_one', 'method_one'),
|
||||
'some_path_two',
|
||||
('some_path_three', ('method_two', 'method_three')),
|
||||
'some_path_four', 'some_path_five'], Two)
|
||||
reg.register(
|
||||
[('some_path_one', 'method_one'), 'some_path_two', ('some_path_three', ('method_two', 'method_three')), 'some_path_four', 'some_path_five'], Two
|
||||
)
|
||||
assert reg.get('some_path_one', 'method_one') is Two
|
||||
assert reg.get('some_path_one') is One
|
||||
assert reg.get('some_path_one', 'nonexistent') is One
|
||||
@@ -230,10 +226,9 @@ def test_mixed_form_single_registration_with_methodless_default(reg):
|
||||
|
||||
def test_mixed_form_single_registration_with_method_default(reg):
|
||||
reg.setdefault('existent', One)
|
||||
reg.register([('some_path_one', 'method_one'),
|
||||
'some_path_two',
|
||||
('some_path_three', ('method_two', 'method_three')),
|
||||
'some_path_four', 'some_path_five'], Two)
|
||||
reg.register(
|
||||
[('some_path_one', 'method_one'), 'some_path_two', ('some_path_three', ('method_two', 'method_three')), 'some_path_four', 'some_path_five'], Two
|
||||
)
|
||||
assert reg.get('some_path_one', 'method_one') is Two
|
||||
assert reg.get('some_path_one') is None
|
||||
assert reg.get('some_path_one', 'existent') is One
|
||||
|
||||
@@ -9,60 +9,68 @@ from awxkit import utils
|
||||
from awxkit import exceptions as exc
|
||||
|
||||
|
||||
@pytest.mark.parametrize('inp, out',
|
||||
[[True, True],
|
||||
[False, False],
|
||||
[1, True],
|
||||
[0, False],
|
||||
[1.0, True],
|
||||
[0.0, False],
|
||||
['TrUe', True],
|
||||
['FalSe', False],
|
||||
['yEs', True],
|
||||
['No', False],
|
||||
['oN', True],
|
||||
['oFf', False],
|
||||
['asdf', True],
|
||||
['0', False],
|
||||
['', False],
|
||||
[{1: 1}, True],
|
||||
[{}, False],
|
||||
[(0,), True],
|
||||
[(), False],
|
||||
[[1], True],
|
||||
[[], False]])
|
||||
@pytest.mark.parametrize(
|
||||
'inp, out',
|
||||
[
|
||||
[True, True],
|
||||
[False, False],
|
||||
[1, True],
|
||||
[0, False],
|
||||
[1.0, True],
|
||||
[0.0, False],
|
||||
['TrUe', True],
|
||||
['FalSe', False],
|
||||
['yEs', True],
|
||||
['No', False],
|
||||
['oN', True],
|
||||
['oFf', False],
|
||||
['asdf', True],
|
||||
['0', False],
|
||||
['', False],
|
||||
[{1: 1}, True],
|
||||
[{}, False],
|
||||
[(0,), True],
|
||||
[(), False],
|
||||
[[1], True],
|
||||
[[], False],
|
||||
],
|
||||
)
|
||||
def test_to_bool(inp, out):
|
||||
assert utils.to_bool(inp) == out
|
||||
|
||||
|
||||
@pytest.mark.parametrize('inp, out',
|
||||
[["{}", {}],
|
||||
["{'null': null}", {"null": None}],
|
||||
["{'bool': true}", {"bool": True}],
|
||||
["{'bool': false}", {"bool": False}],
|
||||
["{'int': 0}", {"int": 0}],
|
||||
["{'float': 1.0}", {"float": 1.0}],
|
||||
["{'str': 'abc'}", {"str": "abc"}],
|
||||
["{'obj': {}}", {"obj": {}}],
|
||||
["{'list': []}", {"list": []}],
|
||||
["---", None],
|
||||
["---\n'null': null", {'null': None}],
|
||||
["---\n'bool': true", {'bool': True}],
|
||||
["---\n'bool': false", {'bool': False}],
|
||||
["---\n'int': 0", {'int': 0}],
|
||||
["---\n'float': 1.0", {'float': 1.0}],
|
||||
["---\n'string': 'abc'", {'string': 'abc'}],
|
||||
["---\n'obj': {}", {'obj': {}}],
|
||||
["---\n'list': []", {'list': []}],
|
||||
["", None],
|
||||
["'null': null", {'null': None}],
|
||||
["'bool': true", {'bool': True}],
|
||||
["'bool': false", {'bool': False}],
|
||||
["'int': 0", {'int': 0}],
|
||||
["'float': 1.0", {'float': 1.0}],
|
||||
["'string': 'abc'", {'string': 'abc'}],
|
||||
["'obj': {}", {'obj': {}}],
|
||||
["'list': []", {'list': []}]])
|
||||
@pytest.mark.parametrize(
|
||||
'inp, out',
|
||||
[
|
||||
["{}", {}],
|
||||
["{'null': null}", {"null": None}],
|
||||
["{'bool': true}", {"bool": True}],
|
||||
["{'bool': false}", {"bool": False}],
|
||||
["{'int': 0}", {"int": 0}],
|
||||
["{'float': 1.0}", {"float": 1.0}],
|
||||
["{'str': 'abc'}", {"str": "abc"}],
|
||||
["{'obj': {}}", {"obj": {}}],
|
||||
["{'list': []}", {"list": []}],
|
||||
["---", None],
|
||||
["---\n'null': null", {'null': None}],
|
||||
["---\n'bool': true", {'bool': True}],
|
||||
["---\n'bool': false", {'bool': False}],
|
||||
["---\n'int': 0", {'int': 0}],
|
||||
["---\n'float': 1.0", {'float': 1.0}],
|
||||
["---\n'string': 'abc'", {'string': 'abc'}],
|
||||
["---\n'obj': {}", {'obj': {}}],
|
||||
["---\n'list': []", {'list': []}],
|
||||
["", None],
|
||||
["'null': null", {'null': None}],
|
||||
["'bool': true", {'bool': True}],
|
||||
["'bool': false", {'bool': False}],
|
||||
["'int': 0", {'int': 0}],
|
||||
["'float': 1.0", {'float': 1.0}],
|
||||
["'string': 'abc'", {'string': 'abc'}],
|
||||
["'obj': {}", {'obj': {}}],
|
||||
["'list': []", {'list': []}],
|
||||
],
|
||||
)
|
||||
def test_load_valid_json_or_yaml(inp, out):
|
||||
assert utils.load_json_or_yaml(inp) == out
|
||||
|
||||
@@ -74,19 +82,13 @@ def test_load_invalid_json_or_yaml(inp):
|
||||
|
||||
|
||||
@pytest.mark.parametrize('non_ascii', [True, False])
|
||||
@pytest.mark.skipif(
|
||||
sys.version_info < (3, 6),
|
||||
reason='this is only intended to be used in py3, not the CLI'
|
||||
)
|
||||
@pytest.mark.skipif(sys.version_info < (3, 6), reason='this is only intended to be used in py3, not the CLI')
|
||||
def test_random_titles_are_unicode(non_ascii):
|
||||
assert isinstance(utils.random_title(non_ascii=non_ascii), str)
|
||||
|
||||
|
||||
@pytest.mark.parametrize('non_ascii', [True, False])
|
||||
@pytest.mark.skipif(
|
||||
sys.version_info < (3, 6),
|
||||
reason='this is only intended to be used in py3, not the CLI'
|
||||
)
|
||||
@pytest.mark.skipif(sys.version_info < (3, 6), reason='this is only intended to be used in py3, not the CLI')
|
||||
def test_random_titles_generates_correct_characters(non_ascii):
|
||||
title = utils.random_title(non_ascii=non_ascii)
|
||||
if non_ascii:
|
||||
@@ -98,34 +100,39 @@ def test_random_titles_generates_correct_characters(non_ascii):
|
||||
title.encode('utf-8')
|
||||
|
||||
|
||||
@pytest.mark.parametrize('inp, out',
|
||||
[['ClassNameShouldChange', 'class_name_should_change'],
|
||||
['classnameshouldntchange', 'classnameshouldntchange'],
|
||||
['Classspacingshouldntchange', 'classspacingshouldntchange'],
|
||||
['Class1Name2Should3Change', 'class_1_name_2_should_3_change'],
|
||||
['Class123name234should345change456', 'class_123_name_234_should_345_change_456']])
|
||||
@pytest.mark.parametrize(
|
||||
'inp, out',
|
||||
[
|
||||
['ClassNameShouldChange', 'class_name_should_change'],
|
||||
['classnameshouldntchange', 'classnameshouldntchange'],
|
||||
['Classspacingshouldntchange', 'classspacingshouldntchange'],
|
||||
['Class1Name2Should3Change', 'class_1_name_2_should_3_change'],
|
||||
['Class123name234should345change456', 'class_123_name_234_should_345_change_456'],
|
||||
],
|
||||
)
|
||||
def test_class_name_to_kw_arg(inp, out):
|
||||
assert utils.class_name_to_kw_arg(inp) == out
|
||||
|
||||
|
||||
@pytest.mark.parametrize('first, second, expected',
|
||||
[['/api/v2/resources/', '/api/v2/resources/', True],
|
||||
['/api/v2/resources/', '/api/v2/resources/?test=ignored', True],
|
||||
['/api/v2/resources/?one=ignored', '/api/v2/resources/?two=ignored', True],
|
||||
['http://one.com', 'http://one.com', True],
|
||||
['http://one.com', 'http://www.one.com', True],
|
||||
['http://one.com', 'http://one.com?test=ignored', True],
|
||||
['http://one.com', 'http://www.one.com?test=ignored', True],
|
||||
['http://one.com', 'https://one.com', False],
|
||||
['http://one.com', 'https://one.com?test=ignored', False]])
|
||||
@pytest.mark.parametrize(
|
||||
'first, second, expected',
|
||||
[
|
||||
['/api/v2/resources/', '/api/v2/resources/', True],
|
||||
['/api/v2/resources/', '/api/v2/resources/?test=ignored', True],
|
||||
['/api/v2/resources/?one=ignored', '/api/v2/resources/?two=ignored', True],
|
||||
['http://one.com', 'http://one.com', True],
|
||||
['http://one.com', 'http://www.one.com', True],
|
||||
['http://one.com', 'http://one.com?test=ignored', True],
|
||||
['http://one.com', 'http://www.one.com?test=ignored', True],
|
||||
['http://one.com', 'https://one.com', False],
|
||||
['http://one.com', 'https://one.com?test=ignored', False],
|
||||
],
|
||||
)
|
||||
def test_are_same_endpoint(first, second, expected):
|
||||
assert utils.are_same_endpoint(first, second) == expected
|
||||
|
||||
|
||||
@pytest.mark.parametrize('endpoint, expected',
|
||||
[['/api/v2/resources/', 'v2'],
|
||||
['/api/v2000/resources/', 'v2000'],
|
||||
['/api/', 'common']])
|
||||
@pytest.mark.parametrize('endpoint, expected', [['/api/v2/resources/', 'v2'], ['/api/v2000/resources/', 'v2000'], ['/api/', 'common']])
|
||||
def test_version_from_endpoint(endpoint, expected):
|
||||
assert utils.version_from_endpoint(endpoint) == expected
|
||||
|
||||
@@ -133,42 +140,51 @@ def test_version_from_endpoint(endpoint, expected):
|
||||
class OneClass:
|
||||
pass
|
||||
|
||||
|
||||
class TwoClass:
|
||||
pass
|
||||
|
||||
|
||||
class ThreeClass:
|
||||
pass
|
||||
|
||||
|
||||
class FourClass(ThreeClass):
|
||||
pass
|
||||
|
||||
|
||||
def test_filter_by_class_with_subclass_class():
|
||||
filtered = utils.filter_by_class((OneClass, OneClass), (FourClass, ThreeClass))
|
||||
assert filtered == [OneClass, FourClass]
|
||||
|
||||
|
||||
def test_filter_by_class_with_subclass_instance():
|
||||
one = OneClass()
|
||||
four = FourClass()
|
||||
filtered = utils.filter_by_class((one, OneClass), (four, ThreeClass))
|
||||
assert filtered == [one, four]
|
||||
|
||||
|
||||
def test_filter_by_class_no_arg_tuples():
|
||||
three = ThreeClass()
|
||||
filtered = utils.filter_by_class((True, OneClass), (False, TwoClass), (three, ThreeClass))
|
||||
assert filtered == [OneClass, None, three]
|
||||
|
||||
|
||||
def test_filter_by_class_with_arg_tuples_containing_class():
|
||||
one = OneClass()
|
||||
three = (ThreeClass, dict(one=1, two=2))
|
||||
filtered = utils.filter_by_class((one, OneClass), (False, TwoClass), (three, ThreeClass))
|
||||
assert filtered == [one, None, three]
|
||||
|
||||
|
||||
def test_filter_by_class_with_arg_tuples_containing_subclass():
|
||||
one = OneClass()
|
||||
three = (FourClass, dict(one=1, two=2))
|
||||
filtered = utils.filter_by_class((one, OneClass), (False, TwoClass), (three, ThreeClass))
|
||||
assert filtered == [one, None, three]
|
||||
|
||||
|
||||
@pytest.mark.parametrize('truthy', (True, 123, 'yes'))
|
||||
def test_filter_by_class_with_arg_tuples_containing_truthy(truthy):
|
||||
one = OneClass()
|
||||
@@ -177,18 +193,20 @@ def test_filter_by_class_with_arg_tuples_containing_truthy(truthy):
|
||||
assert filtered == [one, None, (ThreeClass, dict(one=1, two=2))]
|
||||
|
||||
|
||||
@pytest.mark.parametrize('date_string,now,expected', [
|
||||
('2017-12-20T00:00:01.5Z', datetime(2017, 12, 20, 0, 0, 2, 750000), 1.25),
|
||||
('2017-12-20T00:00:01.5Z', datetime(2017, 12, 20, 0, 0, 1, 500000), 0.00),
|
||||
('2017-12-20T00:00:01.5Z', datetime(2017, 12, 20, 0, 0, 0, 500000), -1.00),
|
||||
])
|
||||
@pytest.mark.parametrize(
|
||||
'date_string,now,expected',
|
||||
[
|
||||
('2017-12-20T00:00:01.5Z', datetime(2017, 12, 20, 0, 0, 2, 750000), 1.25),
|
||||
('2017-12-20T00:00:01.5Z', datetime(2017, 12, 20, 0, 0, 1, 500000), 0.00),
|
||||
('2017-12-20T00:00:01.5Z', datetime(2017, 12, 20, 0, 0, 0, 500000), -1.00),
|
||||
],
|
||||
)
|
||||
def test_seconds_since_date_string(date_string, now, expected):
|
||||
with mock.patch('awxkit.utils.utcnow', return_value=now):
|
||||
assert utils.seconds_since_date_string(date_string) == expected
|
||||
|
||||
|
||||
class RecordingCallback(object):
|
||||
|
||||
def __init__(self, value=True):
|
||||
self.call_count = 0
|
||||
self.value = value
|
||||
@@ -225,7 +243,6 @@ def test_suppress():
|
||||
|
||||
|
||||
class TestPollUntil(object):
|
||||
|
||||
@pytest.mark.parametrize('timeout', [0, 0.0, -0.5, -1, -9999999])
|
||||
def test_callback_called_once_for_non_positive_timeout(self, timeout):
|
||||
with mock.patch('awxkit.utils.logged_sleep') as sleep:
|
||||
@@ -246,7 +263,6 @@ class TestPollUntil(object):
|
||||
|
||||
|
||||
class TestPseudoNamespace(object):
|
||||
|
||||
def test_set_item_check_item(self):
|
||||
pn = utils.PseudoNamespace()
|
||||
pn['key'] = 'value'
|
||||
@@ -319,10 +335,7 @@ class TestPseudoNamespace(object):
|
||||
assert pn == dict(one=[dict(two=2), dict(three=3)])
|
||||
|
||||
def test_instantiation_via_nested_dict_with_lists(self):
|
||||
pn = utils.PseudoNamespace(dict(one=[dict(two=2),
|
||||
dict(three=dict(four=4,
|
||||
five=[dict(six=6),
|
||||
dict(seven=7)]))]))
|
||||
pn = utils.PseudoNamespace(dict(one=[dict(two=2), dict(three=dict(four=4, five=[dict(six=6), dict(seven=7)]))]))
|
||||
assert pn.one[1].three.five[1].seven == 7
|
||||
|
||||
def test_instantiation_via_nested_dict_with_tuple(self):
|
||||
@@ -332,10 +345,7 @@ class TestPseudoNamespace(object):
|
||||
assert pn == dict(one=(dict(two=2), dict(three=3)))
|
||||
|
||||
def test_instantiation_via_nested_dict_with_tuples(self):
|
||||
pn = utils.PseudoNamespace(dict(one=(dict(two=2),
|
||||
dict(three=dict(four=4,
|
||||
five=(dict(six=6),
|
||||
dict(seven=7)))))))
|
||||
pn = utils.PseudoNamespace(dict(one=(dict(two=2), dict(three=dict(four=4, five=(dict(six=6), dict(seven=7)))))))
|
||||
assert pn.one[1].three.five[1].seven == 7
|
||||
|
||||
def test_update_with_nested_dict(self):
|
||||
@@ -348,23 +358,16 @@ class TestPseudoNamespace(object):
|
||||
|
||||
def test_update_with_nested_dict_with_lists(self):
|
||||
pn = utils.PseudoNamespace()
|
||||
pn.update(dict(one=[dict(two=2),
|
||||
dict(three=dict(four=4,
|
||||
five=[dict(six=6),
|
||||
dict(seven=7)]))]))
|
||||
pn.update(dict(one=[dict(two=2), dict(three=dict(four=4, five=[dict(six=6), dict(seven=7)]))]))
|
||||
assert pn.one[1].three.five[1].seven == 7
|
||||
|
||||
def test_update_with_nested_dict_with_tuples(self):
|
||||
pn = utils.PseudoNamespace()
|
||||
pn.update(dict(one=(dict(two=2),
|
||||
dict(three=dict(four=4,
|
||||
five=(dict(six=6),
|
||||
dict(seven=7)))))))
|
||||
pn.update(dict(one=(dict(two=2), dict(three=dict(four=4, five=(dict(six=6), dict(seven=7)))))))
|
||||
assert pn.one[1].three.five[1].seven == 7
|
||||
|
||||
|
||||
class TestUpdatePayload(object):
|
||||
|
||||
def test_empty_payload(self):
|
||||
fields = ('one', 'two', 'three', 'four')
|
||||
kwargs = dict(two=2, four=4)
|
||||
|
||||
@@ -8,6 +8,7 @@ from awxkit.ws import WSClient
|
||||
|
||||
ParseResult = namedtuple("ParseResult", ["port", "hostname", "secure"])
|
||||
|
||||
|
||||
def test_explicit_hostname():
|
||||
client = WSClient("token", "some-hostname", 556, False)
|
||||
assert client.port == 556
|
||||
@@ -16,12 +17,15 @@ def test_explicit_hostname():
|
||||
assert client.token == "token"
|
||||
|
||||
|
||||
@pytest.mark.parametrize('url, result',
|
||||
[['https://somename:123', ParseResult(123, "somename", True)],
|
||||
['http://othername:456', ParseResult(456, "othername", False)],
|
||||
['http://othername', ParseResult(80, "othername", False)],
|
||||
['https://othername', ParseResult(443, "othername", True)],
|
||||
])
|
||||
@pytest.mark.parametrize(
|
||||
'url, result',
|
||||
[
|
||||
['https://somename:123', ParseResult(123, "somename", True)],
|
||||
['http://othername:456', ParseResult(456, "othername", False)],
|
||||
['http://othername', ParseResult(80, "othername", False)],
|
||||
['https://othername', ParseResult(443, "othername", True)],
|
||||
],
|
||||
)
|
||||
def test_urlparsing(url, result):
|
||||
with patch("awxkit.ws.config") as mock_config:
|
||||
mock_config.base_url = url
|
||||
|
||||
Reference in New Issue
Block a user