Compare commits

..

70 Commits
5.0.0 ... 6.0.0

Author SHA1 Message Date
Shane McDonald
4f829ab93f Release 6.0.0 2019-07-01 12:47:37 -04:00
softwarefactory-project-zuul[bot]
5ce78b383d Merge pull request #4190 from ryanpetrello/ldap-dict-order
fix a bug that causes LDAP TLS connection flags to not be set properly

Reviewed-by: https://github.com/softwarefactory-project-zuul[bot]
2019-07-01 16:19:51 +00:00
softwarefactory-project-zuul[bot]
2404faa5d8 Merge pull request #4184 from rooftopcellist/delete_tarball
Delete collection tarball when no longer needed

Reviewed-by: https://github.com/softwarefactory-project-zuul[bot]
2019-07-01 15:44:49 +00:00
Christian Adams
e72b2fac6d Delete collection tarball when no longer needed
* Delete after shipping it
   * Delete when ship() fails
2019-07-01 11:11:44 -04:00
Ryan Petrello
11b36982cd fix a bug that causes LDAP TLS connection flags to not be set properly
co-authored-by: Jim Ladd <jladd@redhat.com>
2019-06-28 22:15:35 -04:00
softwarefactory-project-zuul[bot]
d438a93fd2 Merge pull request #4183 from ryanpetrello/logging-deadlock
don't ship external logs from the main thread of the dispatcher

Reviewed-by: https://github.com/softwarefactory-project-zuul[bot]
2019-06-28 16:24:27 +00:00
Ryan Petrello
dfa8d44eb8 don't ship external logs from the main thread of the dispatcher
this is a fairly esoteric change that attempts to work around a bug
we've discovered in cpython itself

context: https://github.com/ansible/awx/issues/4181
2019-06-27 16:24:36 -04:00
softwarefactory-project-zuul[bot]
4470e9ca26 Merge pull request #4178 from rooftopcellist/collection_datetime_str
Fix collection datetime for isolated instance info

Reviewed-by: Elijah DeLee <kdelee@redhat.com>
             https://github.com/kdelee
2019-06-27 20:03:46 +00:00
softwarefactory-project-zuul[bot]
cf0fe729f5 Merge pull request #4039 from mabashian/application-lookup-column
Fix user token application lookup column widths

Reviewed-by: Michael Abashian
             https://github.com/mabashian
2019-06-27 18:31:48 +00:00
mabashian
913e06b865 Fix user token application lookup column widths 2019-06-27 13:32:51 -04:00
Christian Adams
4d7c49372c Fix collection datetime for isolated instance info
* 'last_isolated_check' was a non JSON-serializable object and needed to be a str
2019-06-27 13:31:10 -04:00
softwarefactory-project-zuul[bot]
5c338e582a Merge pull request #4167 from ryanpetrello/csp
add a reasonable default Content Security Policy

Reviewed-by: https://github.com/softwarefactory-project-zuul[bot]
2019-06-26 20:21:20 +00:00
Ryan Petrello
eacf819caf add a reasonable default Content Security Policy
ideally we'd improve this over time to remove the `unsafe-inline` lines,
but we can't due that today because Angular1 makes use of a lot of
inline <script> and <style> tag generation

see: https://github.com/ansible/awx/issues/2056
2019-06-26 10:46:26 -04:00
softwarefactory-project-zuul[bot]
273415b9aa Merge pull request #4077 from j-shade/devel
fixed row item labels to view horizontally instead of vertically

Reviewed-by: https://github.com/softwarefactory-project-zuul[bot]
2019-06-26 12:50:16 +00:00
Jeremy Shade
e612a167e2 fixed row item labels to view horizontally instead of vertically 2019-06-26 08:22:20 -04:00
softwarefactory-project-zuul[bot]
0a7d6e603e Merge pull request #4165 from shanemcd/sane-working-dir
Use the source tree as the working directory for our dev env

Reviewed-by: https://github.com/softwarefactory-project-zuul[bot]
2019-06-26 00:58:37 +00:00
Shane McDonald
f05bed6366 Use the source tree as the working directory for our dev env 2019-06-25 18:28:01 -04:00
softwarefactory-project-zuul[bot]
cbe6c5bd3b Merge pull request #4164 from aubrel/devel
Change `docker_service` to `docker_compose`.

Reviewed-by: https://github.com/softwarefactory-project-zuul[bot]
2019-06-25 21:45:49 +00:00
aubrel
e9ac44f561 Change docker_service to docker_compose.
Signed-off-by: aubrel <red_clover@riseup.net>
2019-06-25 15:58:52 -04:00
softwarefactory-project-zuul[bot]
aab29bef5b Merge pull request #4141 from ryanpetrello/ovirt4-dep-bump
bump ovirtsdk version to 4.3.0

Reviewed-by: https://github.com/softwarefactory-project-zuul[bot]
2019-06-24 21:18:40 +00:00
softwarefactory-project-zuul[bot]
9f42d9426c Merge pull request #4137 from chrismeyersfsu/fix-smart_inv_race
wrap smart inv cache update w/ advisory lock

Reviewed-by: https://github.com/softwarefactory-project-zuul[bot]
2019-06-21 20:25:10 +00:00
softwarefactory-project-zuul[bot]
b369609f07 Merge pull request #4103 from AlexSCorey/79-NotifyOnJobStart
Adds notify on start toggle

Reviewed-by: https://github.com/softwarefactory-project-zuul[bot]
2019-06-21 16:51:52 +00:00
Ryan Petrello
01d31231c0 bump ovirtsdk version to 4.3.0
see: bfc6a2a8d6
2019-06-21 12:49:16 -04:00
softwarefactory-project-zuul[bot]
c46be3e718 Merge pull request #4083 from rmkraus/devel
Updated ovirt4 dynamic inventory script.

Reviewed-by: https://github.com/softwarefactory-project-zuul[bot]
2019-06-21 16:40:29 +00:00
softwarefactory-project-zuul[bot]
38aedcdd48 Merge pull request #4136 from Spredzy/revert_makefile
Revert "Makefile: align pip and setuptools bump"

Reviewed-by: https://github.com/softwarefactory-project-zuul[bot]
2019-06-21 13:42:26 +00:00
softwarefactory-project-zuul[bot]
59bec99f4c Merge pull request #4128 from tchellomello/k8s-annotations
Introduces the ability to pass annotations to the Kubernetes Ingress Controllers

Reviewed-by: https://github.com/softwarefactory-project-zuul[bot]
2019-06-21 13:14:47 +00:00
Alex Corey
b0249a9a8b Addressed PR Issues 2019-06-21 08:58:51 -04:00
chris meyers
acb6d9c4d1 wrap smart inv cache update w/ advisory lock
* Two job templates that use the same smart inventory running at the
same time can easily race to recompute the smart inventory <-> host
mapping. In this case, bulk_create() can throw an error when racing.
* The per-smart-inventory advisory lock ensures that the state of the
system is consistent & that bulk_create() runs in isolation.
2019-06-21 08:52:34 -04:00
Yanis Guenane
78912d20f7 Revert "Makefile: align pip and setuptools bump"
This reverts commit ec92abf014.
2019-06-21 11:52:22 +02:00
Marcelo Mello
52712a0d9a Introduces the ability to pass annotations to the Kubernetes Ingress Controllers 2019-06-20 16:40:08 -04:00
Alex Corey
a7c787af02 Adds notify on start toggle 2019-06-20 13:07:09 -04:00
softwarefactory-project-zuul[bot]
e269634afc Merge pull request #4112 from jbradberry/fix-3603
Use the `in` operator to test against the Organization membership subquery

Reviewed-by: https://github.com/softwarefactory-project-zuul[bot]
2019-06-20 12:56:08 +00:00
softwarefactory-project-zuul[bot]
4daf574899 Merge pull request #4114 from shanemcd/revert-pip-and-setuptools
Revert pip and setuptools

Reviewed-by: https://github.com/softwarefactory-project-zuul[bot]
2019-06-20 02:42:04 +00:00
softwarefactory-project-zuul[bot]
067ba7f8fe Merge pull request #4116 from tchellomello/ca_trust_dir_project_data_dir
Fixes ca_trust_dir and project_data_dir for Kubernetes

Reviewed-by: https://github.com/softwarefactory-project-zuul[bot]
2019-06-20 02:19:25 +00:00
Marcelo Mello
7d77727a60 project_data_dir is not required in the awx_task containers 2019-06-19 21:35:49 -04:00
Marcelo Mello
47560fdf7c Fixes ca_trust_dir and project_data_dir for Kubernetes 2019-06-19 21:21:35 -04:00
Shane McDonald
2882f4afb5 Revert "upgrade pip and setuptools"
This reverts commit 76ebcf914b.
2019-06-19 16:02:52 -04:00
Shane McDonald
aaceccc426 Revert "Fix offline builds"
This reverts commit fe850dff38.
2019-06-19 16:02:43 -04:00
softwarefactory-project-zuul[bot]
1f3242900a Merge pull request #4098 from beeankha/any_notification_migration
Update "Notify On Start" Migration File

Reviewed-by: Ryan Petrello
             https://github.com/ryanpetrello
2019-06-19 19:57:12 +00:00
softwarefactory-project-zuul[bot]
e6232957b4 Merge pull request #4079 from ryanpetrello/ldap-setting-flake
work around a unit test that's periodically flaky

Reviewed-by: https://github.com/softwarefactory-project-zuul[bot]
2019-06-19 18:55:24 +00:00
Jeff Bradberry
1a72ff4c47 Use the in operator to test against the Organization membership subquery
If more than one Organization were selected by this subquery, then
Postgres would complain with "more than one row returned by a subquery
used as an expression".  We needed to allow for that case.

Annoyingly SQLite3 doesn't seem to care, so writing a py.test test to
exercise this isn't feasible under our current development setup.
2019-06-19 14:49:02 -04:00
softwarefactory-project-zuul[bot]
c585c3d07d Merge pull request #4105 from rooftopcellist/fix_instance_counts
Fixes analytics & metrics instance specific job counts

Reviewed-by: https://github.com/softwarefactory-project-zuul[bot]
2019-06-19 16:49:09 +00:00
softwarefactory-project-zuul[bot]
1413c1be7b Merge pull request #4074 from ryanpetrello/whoopsie
fix a bug introduced in the inventory source detail API by v1 removal

Reviewed-by: https://github.com/softwarefactory-project-zuul[bot]
2019-06-19 15:52:47 +00:00
Christian Adams
a5c057cc18 Fixes analytics & metrics instance specific job counts 2019-06-19 11:32:05 -04:00
softwarefactory-project-zuul[bot]
9c06dc7106 Merge pull request #4106 from shanemcd/fix-offline-builds
Downgrade pip

Reviewed-by: https://github.com/softwarefactory-project-zuul[bot]
2019-06-18 23:17:48 +00:00
Shane McDonald
fe850dff38 Fix offline builds
pip 19 added support for something called `pyproject.toml`. Several packages have been setting the option `build-backend = "setuptools.build_meta”` (bcrypt, attrs, jaraco) which seems to be the root of the problem when building from source. Until the community sorts this out I’m inclined to avoid pip 19.
2019-06-18 18:48:54 -04:00
beeankha
40840e3789 Update migration file to indicate there is no reverse function in case of a rollback 2019-06-18 12:57:50 -04:00
softwarefactory-project-zuul[bot]
e3750f541e Merge pull request #4096 from mabashian/4091-workflow-form
Fix field enablement on workflow form

Reviewed-by: https://github.com/softwarefactory-project-zuul[bot]
2019-06-18 16:15:57 +00:00
softwarefactory-project-zuul[bot]
5d49fe2170 Merge pull request #4097 from shanemcd/five-dot-oh-dot-oh
Bump version to AWX 5.0.0

Reviewed-by: https://github.com/softwarefactory-project-zuul[bot]
2019-06-18 14:43:25 +00:00
softwarefactory-project-zuul[bot]
ca0e8102fd Merge pull request #3982 from beeankha/notify_on_start
Notification On Job Start

Reviewed-by: Bianca Henderson <beeankha@gmail.com>
             https://github.com/beeankha
2019-06-18 14:23:58 +00:00
mabashian
164d305b51 More canAddWorkflowJobTemplate cleanup 2019-06-18 09:16:19 -04:00
mabashian
4d33e484d0 Fix field enablement on workflow form 2019-06-18 09:10:49 -04:00
beeankha
69502bc133 Add functions in migration file for deleting and altering 'any' state notifications 2019-06-17 10:47:58 -04:00
beeankha
17c89ed412 Remove tests for 'any' notification state 2019-06-17 10:47:58 -04:00
beeankha
f5b6bd65cf More deletions of 'any' state 2019-06-17 10:47:58 -04:00
beeankha
c6f1806a23 Removing references to 'any' state notifications 2019-06-17 10:47:58 -04:00
beeankha
c65e6ba30b Update the logic for 'any' and 'started' notifications 2019-06-17 10:47:58 -04:00
beeankha
d511d63a5a Fixed typo 2019-06-17 10:47:58 -04:00
beeankha
30741e762a Add more notification tests 2019-06-17 10:47:58 -04:00
beeankha
7687eddf6d Add api test, edit AWX docs 2019-06-17 10:47:58 -04:00
beeankha
9cfed6f2a8 Add check for no-op case back, remove redundant on_commit code 2019-06-17 10:47:58 -04:00
beeankha
95896b1acd Edit wfj running notification trigger 2019-06-17 10:47:58 -04:00
beeankha
68fe23d8b7 Update Organization Notification Template subclass, move success/fail wfj notification trigger 2019-06-17 10:47:58 -04:00
beeankha
dd372548a9 Update swagger test 2019-06-17 10:47:57 -04:00
beeankha
8d6e1f0927 Trigger running notifications in WFJs and edit unit test 2019-06-17 10:47:57 -04:00
beeankha
98fa1fc813 Add migration file 2019-06-17 10:47:57 -04:00
beeankha
8ec97235e3 Add feature for notifications to trigger on job start 2019-06-17 10:47:57 -04:00
Ryan Kraus
468e79a754 Updated ovirt4 dynamic inventory script.
Pulling in the new ovirt4 dynamic inventory script that will be pulled in ansible/ansible#57824

Signed-off-by: Ryan Kraus <rmkraus@gmail.com>
2019-06-16 11:55:46 -04:00
Ryan Petrello
d8bd72054d work around a unit test that's periodically flaky 2019-06-14 10:05:41 -04:00
Ryan Petrello
ae9032ce03 fix a bug introduced in the inventory source detail API by v1 removal
see: https://github.com/ansible/awx/issues/4059
2019-06-13 17:20:21 -04:00
59 changed files with 550 additions and 251 deletions

View File

@@ -124,8 +124,8 @@ virtualenv_ansible:
if [ ! -d "$(VENV_BASE)/ansible" ]; then \
virtualenv -p python --system-site-packages $(VENV_BASE)/ansible && \
$(VENV_BASE)/ansible/bin/pip install $(PIP_OPTIONS) --ignore-installed six packaging appdirs && \
$(VENV_BASE)/ansible/bin/pip install $(PIP_OPTIONS) --ignore-installed setuptools==41.0.1 && \
$(VENV_BASE)/ansible/bin/pip install $(PIP_OPTIONS) --ignore-installed pip==19.1.1; \
$(VENV_BASE)/ansible/bin/pip install $(PIP_OPTIONS) --ignore-installed setuptools==36.0.1 && \
$(VENV_BASE)/ansible/bin/pip install $(PIP_OPTIONS) --ignore-installed pip==9.0.1; \
fi; \
fi

View File

@@ -1 +1 @@
5.0.0
6.0.0

View File

@@ -1246,7 +1246,7 @@ class OrganizationSerializer(BaseSerializer):
applications = self.reverse('api:organization_applications_list', kwargs={'pk': obj.pk}),
activity_stream = self.reverse('api:organization_activity_stream_list', kwargs={'pk': obj.pk}),
notification_templates = self.reverse('api:organization_notification_templates_list', kwargs={'pk': obj.pk}),
notification_templates_any = self.reverse('api:organization_notification_templates_any_list', kwargs={'pk': obj.pk}),
notification_templates_started = self.reverse('api:organization_notification_templates_started_list', kwargs={'pk': obj.pk}),
notification_templates_success = self.reverse('api:organization_notification_templates_success_list', kwargs={'pk': obj.pk}),
notification_templates_error = self.reverse('api:organization_notification_templates_error_list', kwargs={'pk': obj.pk}),
object_roles = self.reverse('api:organization_object_roles_list', kwargs={'pk': obj.pk}),
@@ -1352,7 +1352,7 @@ class ProjectSerializer(UnifiedJobTemplateSerializer, ProjectOptionsSerializer):
scm_inventory_sources = self.reverse('api:project_scm_inventory_sources', kwargs={'pk': obj.pk}),
schedules = self.reverse('api:project_schedules_list', kwargs={'pk': obj.pk}),
activity_stream = self.reverse('api:project_activity_stream_list', kwargs={'pk': obj.pk}),
notification_templates_any = self.reverse('api:project_notification_templates_any_list', kwargs={'pk': obj.pk}),
notification_templates_started = self.reverse('api:project_notification_templates_started_list', kwargs={'pk': obj.pk}),
notification_templates_success = self.reverse('api:project_notification_templates_success_list', kwargs={'pk': obj.pk}),
notification_templates_error = self.reverse('api:project_notification_templates_error_list', kwargs={'pk': obj.pk}),
access_list = self.reverse('api:project_access_list', kwargs={'pk': obj.pk}),
@@ -1943,6 +1943,25 @@ class InventorySourceOptionsSerializer(BaseSerializer):
return super(InventorySourceOptionsSerializer, self).validate(attrs)
# TODO: remove when old 'credential' fields are removed
def get_summary_fields(self, obj):
summary_fields = super(InventorySourceOptionsSerializer, self).get_summary_fields(obj)
all_creds = []
if 'credential' in summary_fields:
cred = obj.get_cloud_credential()
if cred:
summarized_cred = {
'id': cred.id, 'name': cred.name, 'description': cred.description,
'kind': cred.kind, 'cloud': True
}
summary_fields['credential'] = summarized_cred
all_creds.append(summarized_cred)
summary_fields['credential']['credential_type_id'] = cred.credential_type_id
else:
summary_fields.pop('credential')
summary_fields['credentials'] = all_creds
return summary_fields
class InventorySourceSerializer(UnifiedJobTemplateSerializer, InventorySourceOptionsSerializer):
@@ -1970,7 +1989,7 @@ class InventorySourceSerializer(UnifiedJobTemplateSerializer, InventorySourceOpt
activity_stream = self.reverse('api:inventory_source_activity_stream_list', kwargs={'pk': obj.pk}),
hosts = self.reverse('api:inventory_source_hosts_list', kwargs={'pk': obj.pk}),
groups = self.reverse('api:inventory_source_groups_list', kwargs={'pk': obj.pk}),
notification_templates_any = self.reverse('api:inventory_source_notification_templates_any_list', kwargs={'pk': obj.pk}),
notification_templates_started = self.reverse('api:inventory_source_notification_templates_started_list', kwargs={'pk': obj.pk}),
notification_templates_success = self.reverse('api:inventory_source_notification_templates_success_list', kwargs={'pk': obj.pk}),
notification_templates_error = self.reverse('api:inventory_source_notification_templates_error_list', kwargs={'pk': obj.pk}),
))
@@ -2792,7 +2811,7 @@ class JobTemplateSerializer(JobTemplateMixin, UnifiedJobTemplateSerializer, JobO
schedules = self.reverse('api:job_template_schedules_list', kwargs={'pk': obj.pk}),
activity_stream = self.reverse('api:job_template_activity_stream_list', kwargs={'pk': obj.pk}),
launch = self.reverse('api:job_template_launch', kwargs={'pk': obj.pk}),
notification_templates_any = self.reverse('api:job_template_notification_templates_any_list', kwargs={'pk': obj.pk}),
notification_templates_started = self.reverse('api:job_template_notification_templates_started_list', kwargs={'pk': obj.pk}),
notification_templates_success = self.reverse('api:job_template_notification_templates_success_list', kwargs={'pk': obj.pk}),
notification_templates_error = self.reverse('api:job_template_notification_templates_error_list', kwargs={'pk': obj.pk}),
access_list = self.reverse('api:job_template_access_list', kwargs={'pk': obj.pk}),
@@ -3204,7 +3223,7 @@ class SystemJobTemplateSerializer(UnifiedJobTemplateSerializer):
jobs = self.reverse('api:system_job_template_jobs_list', kwargs={'pk': obj.pk}),
schedules = self.reverse('api:system_job_template_schedules_list', kwargs={'pk': obj.pk}),
launch = self.reverse('api:system_job_template_launch', kwargs={'pk': obj.pk}),
notification_templates_any = self.reverse('api:system_job_template_notification_templates_any_list', kwargs={'pk': obj.pk}),
notification_templates_started = self.reverse('api:system_job_template_notification_templates_started_list', kwargs={'pk': obj.pk}),
notification_templates_success = self.reverse('api:system_job_template_notification_templates_success_list', kwargs={'pk': obj.pk}),
notification_templates_error = self.reverse('api:system_job_template_notification_templates_error_list', kwargs={'pk': obj.pk}),
@@ -3271,7 +3290,7 @@ class WorkflowJobTemplateSerializer(JobTemplateMixin, LabelsListMixin, UnifiedJo
workflow_nodes = self.reverse('api:workflow_job_template_workflow_nodes_list', kwargs={'pk': obj.pk}),
labels = self.reverse('api:workflow_job_template_label_list', kwargs={'pk': obj.pk}),
activity_stream = self.reverse('api:workflow_job_template_activity_stream_list', kwargs={'pk': obj.pk}),
notification_templates_any = self.reverse('api:workflow_job_template_notification_templates_any_list', kwargs={'pk': obj.pk}),
notification_templates_started = self.reverse('api:workflow_job_template_notification_templates_started_list', kwargs={'pk': obj.pk}),
notification_templates_success = self.reverse('api:workflow_job_template_notification_templates_success_list', kwargs={'pk': obj.pk}),
notification_templates_error = self.reverse('api:workflow_job_template_notification_templates_error_list', kwargs={'pk': obj.pk}),
access_list = self.reverse('api:workflow_job_template_access_list', kwargs={'pk': obj.pk}),

View File

@@ -13,8 +13,8 @@ from awx.api.views import (
InventorySourceCredentialsList,
InventorySourceGroupsList,
InventorySourceHostsList,
InventorySourceNotificationTemplatesAnyList,
InventorySourceNotificationTemplatesErrorList,
InventorySourceNotificationTemplatesStartedList,
InventorySourceNotificationTemplatesSuccessList,
)
@@ -29,8 +29,8 @@ urls = [
url(r'^(?P<pk>[0-9]+)/credentials/$', InventorySourceCredentialsList.as_view(), name='inventory_source_credentials_list'),
url(r'^(?P<pk>[0-9]+)/groups/$', InventorySourceGroupsList.as_view(), name='inventory_source_groups_list'),
url(r'^(?P<pk>[0-9]+)/hosts/$', InventorySourceHostsList.as_view(), name='inventory_source_hosts_list'),
url(r'^(?P<pk>[0-9]+)/notification_templates_any/$', InventorySourceNotificationTemplatesAnyList.as_view(),
name='inventory_source_notification_templates_any_list'),
url(r'^(?P<pk>[0-9]+)/notification_templates_started/$', InventorySourceNotificationTemplatesStartedList.as_view(),
name='inventory_source_notification_templates_started_list'),
url(r'^(?P<pk>[0-9]+)/notification_templates_error/$', InventorySourceNotificationTemplatesErrorList.as_view(),
name='inventory_source_notification_templates_error_list'),
url(r'^(?P<pk>[0-9]+)/notification_templates_success/$', InventorySourceNotificationTemplatesSuccessList.as_view(),

View File

@@ -13,8 +13,8 @@ from awx.api.views import (
JobTemplateSchedulesList,
JobTemplateSurveySpec,
JobTemplateActivityStreamList,
JobTemplateNotificationTemplatesAnyList,
JobTemplateNotificationTemplatesErrorList,
JobTemplateNotificationTemplatesStartedList,
JobTemplateNotificationTemplatesSuccessList,
JobTemplateInstanceGroupsList,
JobTemplateAccessList,
@@ -34,8 +34,8 @@ urls = [
url(r'^(?P<pk>[0-9]+)/schedules/$', JobTemplateSchedulesList.as_view(), name='job_template_schedules_list'),
url(r'^(?P<pk>[0-9]+)/survey_spec/$', JobTemplateSurveySpec.as_view(), name='job_template_survey_spec'),
url(r'^(?P<pk>[0-9]+)/activity_stream/$', JobTemplateActivityStreamList.as_view(), name='job_template_activity_stream_list'),
url(r'^(?P<pk>[0-9]+)/notification_templates_any/$', JobTemplateNotificationTemplatesAnyList.as_view(),
name='job_template_notification_templates_any_list'),
url(r'^(?P<pk>[0-9]+)/notification_templates_started/$', JobTemplateNotificationTemplatesStartedList.as_view(),
name='job_template_notification_templates_started_list'),
url(r'^(?P<pk>[0-9]+)/notification_templates_error/$', JobTemplateNotificationTemplatesErrorList.as_view(),
name='job_template_notification_templates_error_list'),
url(r'^(?P<pk>[0-9]+)/notification_templates_success/$', JobTemplateNotificationTemplatesSuccessList.as_view(),

View File

@@ -15,8 +15,8 @@ from awx.api.views import (
OrganizationCredentialList,
OrganizationActivityStreamList,
OrganizationNotificationTemplatesList,
OrganizationNotificationTemplatesAnyList,
OrganizationNotificationTemplatesErrorList,
OrganizationNotificationTemplatesStartedList,
OrganizationNotificationTemplatesSuccessList,
OrganizationInstanceGroupsList,
OrganizationObjectRolesList,
@@ -25,7 +25,7 @@ from awx.api.views import (
)
urls = [
urls = [
url(r'^$', OrganizationList.as_view(), name='organization_list'),
url(r'^(?P<pk>[0-9]+)/$', OrganizationDetail.as_view(), name='organization_detail'),
url(r'^(?P<pk>[0-9]+)/users/$', OrganizationUsersList.as_view(), name='organization_users_list'),
@@ -37,8 +37,8 @@ urls = [
url(r'^(?P<pk>[0-9]+)/credentials/$', OrganizationCredentialList.as_view(), name='organization_credential_list'),
url(r'^(?P<pk>[0-9]+)/activity_stream/$', OrganizationActivityStreamList.as_view(), name='organization_activity_stream_list'),
url(r'^(?P<pk>[0-9]+)/notification_templates/$', OrganizationNotificationTemplatesList.as_view(), name='organization_notification_templates_list'),
url(r'^(?P<pk>[0-9]+)/notification_templates_any/$', OrganizationNotificationTemplatesAnyList.as_view(),
name='organization_notification_templates_any_list'),
url(r'^(?P<pk>[0-9]+)/notification_templates_started/$', OrganizationNotificationTemplatesStartedList.as_view(),
name='organization_notification_templates_started_list'),
url(r'^(?P<pk>[0-9]+)/notification_templates_error/$', OrganizationNotificationTemplatesErrorList.as_view(),
name='organization_notification_templates_error_list'),
url(r'^(?P<pk>[0-9]+)/notification_templates_success/$', OrganizationNotificationTemplatesSuccessList.as_view(),

View File

@@ -14,8 +14,8 @@ from awx.api.views import (
ProjectUpdatesList,
ProjectActivityStreamList,
ProjectSchedulesList,
ProjectNotificationTemplatesAnyList,
ProjectNotificationTemplatesErrorList,
ProjectNotificationTemplatesStartedList,
ProjectNotificationTemplatesSuccessList,
ProjectObjectRolesList,
ProjectAccessList,
@@ -34,10 +34,11 @@ urls = [
url(r'^(?P<pk>[0-9]+)/project_updates/$', ProjectUpdatesList.as_view(), name='project_updates_list'),
url(r'^(?P<pk>[0-9]+)/activity_stream/$', ProjectActivityStreamList.as_view(), name='project_activity_stream_list'),
url(r'^(?P<pk>[0-9]+)/schedules/$', ProjectSchedulesList.as_view(), name='project_schedules_list'),
url(r'^(?P<pk>[0-9]+)/notification_templates_any/$', ProjectNotificationTemplatesAnyList.as_view(), name='project_notification_templates_any_list'),
url(r'^(?P<pk>[0-9]+)/notification_templates_error/$', ProjectNotificationTemplatesErrorList.as_view(), name='project_notification_templates_error_list'),
url(r'^(?P<pk>[0-9]+)/notification_templates_success/$', ProjectNotificationTemplatesSuccessList.as_view(),
name='project_notification_templates_success_list'),
url(r'^(?P<pk>[0-9]+)/notification_templates_started/$', ProjectNotificationTemplatesStartedList.as_view(),
name='project_notification_templates_started_list'),
url(r'^(?P<pk>[0-9]+)/object_roles/$', ProjectObjectRolesList.as_view(), name='project_object_roles_list'),
url(r'^(?P<pk>[0-9]+)/access_list/$', ProjectAccessList.as_view(), name='project_access_list'),
url(r'^(?P<pk>[0-9]+)/copy/$', ProjectCopy.as_view(), name='project_copy'),

View File

@@ -9,8 +9,8 @@ from awx.api.views import (
SystemJobTemplateLaunch,
SystemJobTemplateJobsList,
SystemJobTemplateSchedulesList,
SystemJobTemplateNotificationTemplatesAnyList,
SystemJobTemplateNotificationTemplatesErrorList,
SystemJobTemplateNotificationTemplatesStartedList,
SystemJobTemplateNotificationTemplatesSuccessList,
)
@@ -21,8 +21,8 @@ urls = [
url(r'^(?P<pk>[0-9]+)/launch/$', SystemJobTemplateLaunch.as_view(), name='system_job_template_launch'),
url(r'^(?P<pk>[0-9]+)/jobs/$', SystemJobTemplateJobsList.as_view(), name='system_job_template_jobs_list'),
url(r'^(?P<pk>[0-9]+)/schedules/$', SystemJobTemplateSchedulesList.as_view(), name='system_job_template_schedules_list'),
url(r'^(?P<pk>[0-9]+)/notification_templates_any/$', SystemJobTemplateNotificationTemplatesAnyList.as_view(),
name='system_job_template_notification_templates_any_list'),
url(r'^(?P<pk>[0-9]+)/notification_templates_started/$', SystemJobTemplateNotificationTemplatesStartedList.as_view(),
name='system_job_template_notification_templates_started_list'),
url(r'^(?P<pk>[0-9]+)/notification_templates_error/$', SystemJobTemplateNotificationTemplatesErrorList.as_view(),
name='system_job_template_notification_templates_error_list'),
url(r'^(?P<pk>[0-9]+)/notification_templates_success/$', SystemJobTemplateNotificationTemplatesSuccessList.as_view(),

View File

@@ -13,8 +13,8 @@ from awx.api.views import (
WorkflowJobTemplateSurveySpec,
WorkflowJobTemplateWorkflowNodesList,
WorkflowJobTemplateActivityStreamList,
WorkflowJobTemplateNotificationTemplatesAnyList,
WorkflowJobTemplateNotificationTemplatesErrorList,
WorkflowJobTemplateNotificationTemplatesStartedList,
WorkflowJobTemplateNotificationTemplatesSuccessList,
WorkflowJobTemplateAccessList,
WorkflowJobTemplateObjectRolesList,
@@ -32,8 +32,8 @@ urls = [
url(r'^(?P<pk>[0-9]+)/survey_spec/$', WorkflowJobTemplateSurveySpec.as_view(), name='workflow_job_template_survey_spec'),
url(r'^(?P<pk>[0-9]+)/workflow_nodes/$', WorkflowJobTemplateWorkflowNodesList.as_view(), name='workflow_job_template_workflow_nodes_list'),
url(r'^(?P<pk>[0-9]+)/activity_stream/$', WorkflowJobTemplateActivityStreamList.as_view(), name='workflow_job_template_activity_stream_list'),
url(r'^(?P<pk>[0-9]+)/notification_templates_any/$', WorkflowJobTemplateNotificationTemplatesAnyList.as_view(),
name='workflow_job_template_notification_templates_any_list'),
url(r'^(?P<pk>[0-9]+)/notification_templates_started/$', WorkflowJobTemplateNotificationTemplatesStartedList.as_view(),
name='workflow_job_template_notification_templates_started_list'),
url(r'^(?P<pk>[0-9]+)/notification_templates_error/$', WorkflowJobTemplateNotificationTemplatesErrorList.as_view(),
name='workflow_job_template_notification_templates_error_list'),
url(r'^(?P<pk>[0-9]+)/notification_templates_success/$', WorkflowJobTemplateNotificationTemplatesSuccessList.as_view(),

View File

@@ -116,6 +116,7 @@ from awx.api.views.organization import ( # noqa
OrganizationNotificationTemplatesList,
OrganizationNotificationTemplatesAnyList,
OrganizationNotificationTemplatesErrorList,
OrganizationNotificationTemplatesStartedList,
OrganizationNotificationTemplatesSuccessList,
OrganizationInstanceGroupsList,
OrganizationAccessList,
@@ -747,22 +748,20 @@ class ProjectNotificationTemplatesAnyList(SubListCreateAttachDetachAPIView):
model = models.NotificationTemplate
serializer_class = serializers.NotificationTemplateSerializer
parent_model = models.Project
relationship = 'notification_templates_any'
class ProjectNotificationTemplatesErrorList(SubListCreateAttachDetachAPIView):
class ProjectNotificationTemplatesStartedList(ProjectNotificationTemplatesAnyList):
relationship = 'notification_templates_started'
class ProjectNotificationTemplatesErrorList(ProjectNotificationTemplatesAnyList):
model = models.NotificationTemplate
serializer_class = serializers.NotificationTemplateSerializer
parent_model = models.Project
relationship = 'notification_templates_error'
class ProjectNotificationTemplatesSuccessList(SubListCreateAttachDetachAPIView):
class ProjectNotificationTemplatesSuccessList(ProjectNotificationTemplatesAnyList):
model = models.NotificationTemplate
serializer_class = serializers.NotificationTemplateSerializer
parent_model = models.Project
relationship = 'notification_templates_success'
@@ -2102,7 +2101,6 @@ class InventorySourceNotificationTemplatesAnyList(SubListCreateAttachDetachAPIVi
model = models.NotificationTemplate
serializer_class = serializers.NotificationTemplateSerializer
parent_model = models.InventorySource
relationship = 'notification_templates_any'
def post(self, request, *args, **kwargs):
parent = self.get_parent_object()
@@ -2113,6 +2111,11 @@ class InventorySourceNotificationTemplatesAnyList(SubListCreateAttachDetachAPIVi
return super(InventorySourceNotificationTemplatesAnyList, self).post(request, *args, **kwargs)
class InventorySourceNotificationTemplatesStartedList(InventorySourceNotificationTemplatesAnyList):
relationship = 'notification_templates_started'
class InventorySourceNotificationTemplatesErrorList(InventorySourceNotificationTemplatesAnyList):
relationship = 'notification_templates_error'
@@ -2626,22 +2629,20 @@ class JobTemplateNotificationTemplatesAnyList(SubListCreateAttachDetachAPIView):
model = models.NotificationTemplate
serializer_class = serializers.NotificationTemplateSerializer
parent_model = models.JobTemplate
relationship = 'notification_templates_any'
class JobTemplateNotificationTemplatesErrorList(SubListCreateAttachDetachAPIView):
class JobTemplateNotificationTemplatesStartedList(JobTemplateNotificationTemplatesAnyList):
relationship = 'notification_templates_started'
class JobTemplateNotificationTemplatesErrorList(JobTemplateNotificationTemplatesAnyList):
model = models.NotificationTemplate
serializer_class = serializers.NotificationTemplateSerializer
parent_model = models.JobTemplate
relationship = 'notification_templates_error'
class JobTemplateNotificationTemplatesSuccessList(SubListCreateAttachDetachAPIView):
class JobTemplateNotificationTemplatesSuccessList(JobTemplateNotificationTemplatesAnyList):
model = models.NotificationTemplate
serializer_class = serializers.NotificationTemplateSerializer
parent_model = models.JobTemplate
relationship = 'notification_templates_success'
@@ -3234,22 +3235,20 @@ class WorkflowJobTemplateNotificationTemplatesAnyList(SubListCreateAttachDetachA
model = models.NotificationTemplate
serializer_class = serializers.NotificationTemplateSerializer
parent_model = models.WorkflowJobTemplate
relationship = 'notification_templates_any'
class WorkflowJobTemplateNotificationTemplatesErrorList(SubListCreateAttachDetachAPIView):
class WorkflowJobTemplateNotificationTemplatesStartedList(WorkflowJobTemplateNotificationTemplatesAnyList):
relationship = 'notification_templates_started'
class WorkflowJobTemplateNotificationTemplatesErrorList(WorkflowJobTemplateNotificationTemplatesAnyList):
model = models.NotificationTemplate
serializer_class = serializers.NotificationTemplateSerializer
parent_model = models.WorkflowJobTemplate
relationship = 'notification_templates_error'
class WorkflowJobTemplateNotificationTemplatesSuccessList(SubListCreateAttachDetachAPIView):
class WorkflowJobTemplateNotificationTemplatesSuccessList(WorkflowJobTemplateNotificationTemplatesAnyList):
model = models.NotificationTemplate
serializer_class = serializers.NotificationTemplateSerializer
parent_model = models.WorkflowJobTemplate
relationship = 'notification_templates_success'
@@ -3411,22 +3410,20 @@ class SystemJobTemplateNotificationTemplatesAnyList(SubListCreateAttachDetachAPI
model = models.NotificationTemplate
serializer_class = serializers.NotificationTemplateSerializer
parent_model = models.SystemJobTemplate
relationship = 'notification_templates_any'
class SystemJobTemplateNotificationTemplatesErrorList(SubListCreateAttachDetachAPIView):
class SystemJobTemplateNotificationTemplatesStartedList(SystemJobTemplateNotificationTemplatesAnyList):
relationship = 'notification_templates_started'
class SystemJobTemplateNotificationTemplatesErrorList(SystemJobTemplateNotificationTemplatesAnyList):
model = models.NotificationTemplate
serializer_class = serializers.NotificationTemplateSerializer
parent_model = models.SystemJobTemplate
relationship = 'notification_templates_error'
class SystemJobTemplateNotificationTemplatesSuccessList(SubListCreateAttachDetachAPIView):
class SystemJobTemplateNotificationTemplatesSuccessList(SystemJobTemplateNotificationTemplatesAnyList):
model = models.NotificationTemplate
serializer_class = serializers.NotificationTemplateSerializer
parent_model = models.SystemJobTemplate
relationship = 'notification_templates_success'

View File

@@ -178,22 +178,20 @@ class OrganizationNotificationTemplatesAnyList(SubListCreateAttachDetachAPIView)
model = NotificationTemplate
serializer_class = NotificationTemplateSerializer
parent_model = Organization
relationship = 'notification_templates_any'
class OrganizationNotificationTemplatesErrorList(SubListCreateAttachDetachAPIView):
class OrganizationNotificationTemplatesStartedList(OrganizationNotificationTemplatesAnyList):
relationship = 'notification_templates_started'
class OrganizationNotificationTemplatesErrorList(OrganizationNotificationTemplatesAnyList):
model = NotificationTemplate
serializer_class = NotificationTemplateSerializer
parent_model = Organization
relationship = 'notification_templates_error'
class OrganizationNotificationTemplatesSuccessList(SubListCreateAttachDetachAPIView):
class OrganizationNotificationTemplatesSuccessList(OrganizationNotificationTemplatesAnyList):
model = NotificationTemplate
serializer_class = NotificationTemplateSerializer
parent_model = Organization
relationship = 'notification_templates_success'

View File

@@ -1265,7 +1265,7 @@ class TeamAccess(BaseAccess):
(self.user.admin_of_organizations.exists() or self.user.auditor_of_organizations.exists()):
return self.model.objects.all()
return self.model.objects.filter(
Q(organization=Organization.accessible_pk_qs(self.user, 'member_role')) |
Q(organization__in=Organization.accessible_pk_qs(self.user, 'member_role')) |
Q(pk__in=self.model.accessible_pk_qs(self.user, 'read_role'))
)

View File

@@ -153,10 +153,16 @@ def projects_by_scm_type(since):
return counts
def _get_isolated_datetime(last_check):
if last_check:
return last_check.isoformat()
return last_check
@register('instance_info')
def instance_info(since):
info = {}
instances = models.Instance.objects.values_list('hostname').annotate().values(
instances = models.Instance.objects.values_list('hostname').values(
'uuid', 'version', 'capacity', 'cpu', 'memory', 'managed_by_policy', 'hostname', 'last_isolated_check', 'enabled')
for instance in instances:
instance_info = {
@@ -166,7 +172,7 @@ def instance_info(since):
'cpu': instance['cpu'],
'memory': instance['memory'],
'managed_by_policy': instance['managed_by_policy'],
'last_isolated_check': instance['last_isolated_check'],
'last_isolated_check': _get_isolated_datetime(instance['last_isolated_check']),
'enabled': instance['enabled']
}
info[instance['uuid']] = instance_info
@@ -187,12 +193,12 @@ def job_counts(since):
def job_instance_counts(since):
counts = {}
job_types = models.UnifiedJob.objects.exclude(launch_type='sync').values_list(
'execution_node', 'launch_type').annotate(job_launch_type=Count('launch_type'))
'execution_node', 'launch_type').annotate(job_launch_type=Count('launch_type')).order_by()
for job in job_types:
counts.setdefault(job[0], {}).setdefault('launch_type', {})[job[1]] = job[2]
job_statuses = models.UnifiedJob.objects.exclude(launch_type='sync').values_list(
'execution_node', 'status').annotate(job_status=Count('status'))
'execution_node', 'status').annotate(job_status=Count('status')).order_by()
for job in job_statuses:
counts.setdefault(job[0], {}).setdefault('status', {})[job[1]] = job[2]
return counts

View File

@@ -119,24 +119,28 @@ def ship(path):
"""
Ship gathered metrics via the Insights agent
"""
agent = 'insights-client'
if shutil.which(agent) is None:
logger.error('could not find {} on PATH'.format(agent))
return
logger.debug('shipping analytics file: {}'.format(path))
try:
cmd = [
agent, '--payload', path, '--content-type', settings.INSIGHTS_AGENT_MIME
]
output = smart_str(subprocess.check_output(cmd, timeout=60 * 5))
logger.debug(output)
# reset the `last_run` when data is shipped
run_now = now()
state = TowerAnalyticsState.get_solo()
state.last_run = run_now
state.save()
agent = 'insights-client'
if shutil.which(agent) is None:
logger.error('could not find {} on PATH'.format(agent))
return
logger.debug('shipping analytics file: {}'.format(path))
try:
cmd = [
agent, '--payload', path, '--content-type', settings.INSIGHTS_AGENT_MIME
]
output = smart_str(subprocess.check_output(cmd, timeout=60 * 5))
logger.debug(output)
# reset the `last_run` when data is shipped
run_now = now()
state = TowerAnalyticsState.get_solo()
state.last_run = run_now
state.save()
except subprocess.CalledProcessError:
logger.exception('{} failure:'.format(cmd))
except subprocess.TimeoutExpired:
logger.exception('{} timeout:'.format(cmd))
except subprocess.CalledProcessError:
logger.exception('{} failure:'.format(cmd))
except subprocess.TimeoutExpired:
logger.exception('{} timeout:'.format(cmd))
finally:
# cleanup tar.gz
os.remove(path)

View File

@@ -10,6 +10,7 @@ from django.core.management.base import BaseCommand
from django.db import connection as django_connection, connections
from kombu import Exchange, Queue
from awx.main.utils.handlers import AWXProxyHandler
from awx.main.dispatch import get_local_queuename, reaper
from awx.main.dispatch.control import Control
from awx.main.dispatch.kombu import Connection
@@ -121,6 +122,12 @@ class Command(BaseCommand):
reaper.reap()
consumer = None
# don't ship external logs inside the dispatcher's parent process
# this exists to work around a race condition + deadlock bug on fork
# in cpython itself:
# https://bugs.python.org/issue37429
AWXProxyHandler.disable()
with Connection(settings.BROKER_URL) as conn:
try:
bcast = 'tower_broadcast_all'

View File

@@ -0,0 +1,51 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.20 on 2019-05-30 20:35
from __future__ import unicode_literals
from django.db import migrations, models
def forwards_split_unified_job_template_any(apps, schema_editor):
UnifiedJobTemplate = apps.get_model('main', 'unifiedjobtemplate')
for ujt in UnifiedJobTemplate.objects.all():
for ujt_notification in ujt.notification_templates_any.all():
ujt.notification_templates_success.add(ujt_notification)
ujt.notification_templates_error.add(ujt_notification)
def forwards_split_organization_any(apps, schema_editor):
Organization = apps.get_model('main', 'organization')
for org in Organization.objects.all():
for org_notification in org.notification_templates_any.all():
org.notification_templates_success.add(org_notification)
org.notification_templates_error.add(org_notification)
class Migration(migrations.Migration):
dependencies = [
('main', '0080_v360_replace_job_origin'),
]
operations = [
migrations.AddField(
model_name='organization',
name='notification_templates_started',
field=models.ManyToManyField(blank=True, related_name='organization_notification_templates_for_started', to='main.NotificationTemplate'),
),
migrations.AddField(
model_name='unifiedjobtemplate',
name='notification_templates_started',
field=models.ManyToManyField(blank=True, related_name='unifiedjobtemplate_notification_templates_for_started', to='main.NotificationTemplate'),
),
# Separate out "any" notifications into "success" and "error" before the "any" state gets deleted.
migrations.RunPython(forwards_split_unified_job_template_any, None),
migrations.RunPython(forwards_split_organization_any, None),
migrations.RemoveField(
model_name='organization',
name='notification_templates_any',
),
migrations.RemoveField(
model_name='unifiedjobtemplate',
name='notification_templates_any',
),
]

View File

@@ -163,18 +163,18 @@ class AdHocCommand(UnifiedJob, JobNotificationMixin):
all_orgs.add(h.inventory.organization)
active_templates = dict(error=set(),
success=set(),
any=set())
started=set())
base_notification_templates = NotificationTemplate.objects
for org in all_orgs:
for templ in base_notification_templates.filter(organization_notification_templates_for_errors=org):
active_templates['error'].add(templ)
for templ in base_notification_templates.filter(organization_notification_templates_for_success=org):
active_templates['success'].add(templ)
for templ in base_notification_templates.filter(organization_notification_templates_for_any=org):
active_templates['any'].add(templ)
for templ in base_notification_templates.filter(organization_notification_templates_for_started=org):
active_templates['started'].add(templ)
active_templates['error'] = list(active_templates['error'])
active_templates['any'] = list(active_templates['any'])
active_templates['success'] = list(active_templates['success'])
active_templates['started'] = list(active_templates['started'])
return active_templates
def get_passwords_needed_to_start(self):

View File

@@ -386,14 +386,13 @@ class NotificationFieldsModel(BaseModel):
related_name='%(class)s_notification_templates_for_success'
)
notification_templates_any = models.ManyToManyField(
notification_templates_started = models.ManyToManyField(
"NotificationTemplate",
blank=True,
related_name='%(class)s_notification_templates_for_any'
related_name='%(class)s_notification_templates_for_started'
)
def prevent_search(relation):
"""
Used to mark a model field or relation as "restricted from filtering"

View File

@@ -1619,20 +1619,20 @@ class InventorySource(UnifiedJobTemplate, InventorySourceOptions, CustomVirtualE
base_notification_templates = NotificationTemplate.objects
error_notification_templates = list(base_notification_templates
.filter(unifiedjobtemplate_notification_templates_for_errors__in=[self]))
started_notification_templates = list(base_notification_templates
.filter(unifiedjobtemplate_notification_templates_for_started__in=[self]))
success_notification_templates = list(base_notification_templates
.filter(unifiedjobtemplate_notification_templates_for_success__in=[self]))
any_notification_templates = list(base_notification_templates
.filter(unifiedjobtemplate_notification_templates_for_any__in=[self]))
if self.inventory.organization is not None:
error_notification_templates = set(error_notification_templates + list(base_notification_templates
.filter(organization_notification_templates_for_errors=self.inventory.organization)))
started_notification_templates = set(started_notification_templates + list(base_notification_templates
.filter(organization_notification_templates_for_started=self.inventory.organization)))
success_notification_templates = set(success_notification_templates + list(base_notification_templates
.filter(organization_notification_templates_for_success=self.inventory.organization)))
any_notification_templates = set(any_notification_templates + list(base_notification_templates
.filter(organization_notification_templates_for_any=self.inventory.organization)))
return dict(error=list(error_notification_templates),
success=list(success_notification_templates),
any=list(any_notification_templates))
started=list(started_notification_templates),
success=list(success_notification_templates))
def clean_source(self): # TODO: remove in 3.3
source = self.source

View File

@@ -435,19 +435,21 @@ class JobTemplate(UnifiedJobTemplate, JobOptions, SurveyJobTemplateMixin, Resour
base_notification_templates = NotificationTemplate.objects
error_notification_templates = list(base_notification_templates.filter(
unifiedjobtemplate_notification_templates_for_errors__in=[self, self.project]))
started_notification_templates = list(base_notification_templates.filter(
unifiedjobtemplate_notification_templates_for_started__in=[self, self.project]))
success_notification_templates = list(base_notification_templates.filter(
unifiedjobtemplate_notification_templates_for_success__in=[self, self.project]))
any_notification_templates = list(base_notification_templates.filter(
unifiedjobtemplate_notification_templates_for_any__in=[self, self.project]))
# Get Organization NotificationTemplates
if self.project is not None and self.project.organization is not None:
error_notification_templates = set(error_notification_templates + list(base_notification_templates.filter(
organization_notification_templates_for_errors=self.project.organization)))
started_notification_templates = set(started_notification_templates + list(base_notification_templates.filter(
organization_notification_templates_for_started=self.project.organization)))
success_notification_templates = set(success_notification_templates + list(base_notification_templates.filter(
organization_notification_templates_for_success=self.project.organization)))
any_notification_templates = set(any_notification_templates + list(base_notification_templates.filter(
organization_notification_templates_for_any=self.project.organization)))
return dict(error=list(error_notification_templates), success=list(success_notification_templates), any=list(any_notification_templates))
return dict(error=list(error_notification_templates),
started=list(started_notification_templates),
success=list(success_notification_templates))
'''
RelatedJobsMixin
@@ -1133,13 +1135,13 @@ class SystemJobTemplate(UnifiedJobTemplate, SystemJobOptions):
base_notification_templates = NotificationTemplate.objects.all()
error_notification_templates = list(base_notification_templates
.filter(unifiedjobtemplate_notification_templates_for_errors__in=[self]))
started_notification_templates = list(base_notification_templates
.filter(unifiedjobtemplate_notification_templates_for_started__in=[self]))
success_notification_templates = list(base_notification_templates
.filter(unifiedjobtemplate_notification_templates_for_success__in=[self]))
any_notification_templates = list(base_notification_templates
.filter(unifiedjobtemplate_notification_templates_for_any__in=[self]))
return dict(error=list(error_notification_templates),
success=list(success_notification_templates),
any=list(any_notification_templates))
started=list(started_notification_templates),
success=list(success_notification_templates))
def _accept_or_ignore_job_kwargs(self, _exclude_errors=None, **kwargs):
extra_data = kwargs.pop('extra_vars', {})

View File

@@ -7,6 +7,7 @@ import logging
from django.db import models
from django.conf import settings
from django.core.mail.message import EmailMessage
from django.db import connection
from django.utils.translation import ugettext_lazy as _
from django.utils.encoding import smart_str, force_text
@@ -129,7 +130,7 @@ class NotificationTemplate(CommonModelNameNotUnique):
if field not in notification_configuration:
if 'default' in params:
notification_configuration[field] = params['default']
backend_obj = self.notification_class(**notification_configuration)
backend_obj = self.notification_class(**notification_configuration)
notification_obj = EmailMessage(subject, backend_obj.format_body(body), sender, recipients)
with set_environ(**settings.AWX_TASK_ENV):
return backend_obj.send_messages([notification_obj])
@@ -221,10 +222,13 @@ class JobNotificationMixin(object):
def build_notification_failed_message(self):
return self._build_notification_message('failed')
def build_notification_running_message(self):
return self._build_notification_message('running')
def send_notification_templates(self, status_str):
from awx.main.tasks import send_notifications # avoid circular import
if status_str not in ['succeeded', 'failed']:
raise ValueError(_("status_str must be either succeeded or failed"))
if status_str not in ['succeeded', 'failed', 'running']:
raise ValueError(_("status_str must be either running, succeeded or failed"))
try:
notification_templates = self.get_notification_templates()
except Exception:
@@ -233,14 +237,19 @@ class JobNotificationMixin(object):
if notification_templates:
if status_str == 'succeeded':
notification_template_type = 'success'
elif status_str == 'running':
notification_template_type = 'started'
else:
notification_template_type = 'error'
all_notification_templates = set(notification_templates.get(notification_template_type, []) + notification_templates.get('any', []))
all_notification_templates = set(notification_templates.get(notification_template_type, []))
if len(all_notification_templates):
try:
(notification_subject, notification_body) = getattr(self, 'build_notification_%s_message' % status_str)()
except AttributeError:
raise NotImplementedError("build_notification_%s_message() does not exist" % status_str)
send_notifications.delay([n.generate_notification(notification_subject, notification_body).id
for n in all_notification_templates],
job_id=self.id)
def send_it():
send_notifications.delay([n.generate_notification(notification_subject, notification_body).id
for n in all_notification_templates],
job_id=self.id)
connection.on_commit(send_it)

View File

@@ -411,24 +411,24 @@ class Project(UnifiedJobTemplate, ProjectOptions, ResourceMixin, CustomVirtualEn
base_notification_templates = NotificationTemplate.objects
error_notification_templates = list(base_notification_templates
.filter(unifiedjobtemplate_notification_templates_for_errors=self))
started_notification_templates = list(base_notification_templates
.filter(unifiedjobtemplate_notification_templates_for_started=self))
success_notification_templates = list(base_notification_templates
.filter(unifiedjobtemplate_notification_templates_for_success=self))
any_notification_templates = list(base_notification_templates
.filter(unifiedjobtemplate_notification_templates_for_any=self))
# Get Organization NotificationTemplates
if self.organization is not None:
error_notification_templates = set(error_notification_templates +
list(base_notification_templates
.filter(organization_notification_templates_for_errors=self.organization)))
started_notification_templates = set(started_notification_templates +
list(base_notification_templates
.filter(organization_notification_templates_for_started=self.organization)))
success_notification_templates = set(success_notification_templates +
list(base_notification_templates
.filter(organization_notification_templates_for_success=self.organization)))
any_notification_templates = set(any_notification_templates +
list(base_notification_templates
.filter(organization_notification_templates_for_any=self.organization)))
return dict(error=list(error_notification_templates),
success=list(success_notification_templates),
any=list(any_notification_templates))
started=list(started_notification_templates),
success=list(success_notification_templates))
def get_absolute_url(self, request=None):
return reverse('api:project_detail', kwargs={'pk': self.pk}, request=request)
@@ -567,5 +567,3 @@ class ProjectUpdate(UnifiedJob, ProjectOptions, JobNotificationMixin, TaskManage
if not selected_groups:
return self.global_instance_groups
return selected_groups

View File

@@ -419,13 +419,13 @@ class WorkflowJobTemplate(UnifiedJobTemplate, WorkflowJobOptions, SurveyJobTempl
base_notification_templates = NotificationTemplate.objects.all()
error_notification_templates = list(base_notification_templates
.filter(unifiedjobtemplate_notification_templates_for_errors__in=[self]))
started_notification_templates = list(base_notification_templates
.filter(unifiedjobtemplate_notification_templates_for_started__in=[self]))
success_notification_templates = list(base_notification_templates
.filter(unifiedjobtemplate_notification_templates_for_success__in=[self]))
any_notification_templates = list(base_notification_templates
.filter(unifiedjobtemplate_notification_templates_for_any__in=[self]))
return dict(error=list(error_notification_templates),
success=list(success_notification_templates),
any=list(any_notification_templates))
started=list(started_notification_templates),
success=list(success_notification_templates))
def create_unified_job(self, **kwargs):
workflow_job = super(WorkflowJobTemplate, self).create_unified_job(**kwargs)

View File

@@ -193,6 +193,8 @@ class TaskManager():
status_changed = True
if status_changed:
workflow_job.websocket_emit_status(workflow_job.status)
# Operations whose queries rely on modifications made during the atomic scheduling session
workflow_job.send_notification_templates('succeeded' if workflow_job.status == 'successful' else 'failed')
if workflow_job.spawned_by_workflow:
schedule_task_manager()
return result
@@ -233,6 +235,7 @@ class TaskManager():
else:
if type(task) is WorkflowJob:
task.status = 'running'
task.send_notification_templates('running')
logger.debug('Transitioning %s to running status.', task.log_format)
schedule_task_manager()
elif not task.supports_isolation() and rampart_group.controller_id:
@@ -581,10 +584,5 @@ class TaskManager():
logger.debug("Not running scheduler, another task holds lock")
return
logger.debug("Starting Scheduler")
with task_manager_bulk_reschedule():
finished_wfjs = self._schedule()
# Operations whose queries rely on modifications made during the atomic scheduling session
for wfj in WorkflowJob.objects.filter(id__in=finished_wfjs):
wfj.send_notification_templates('succeeded' if wfj.status == 'successful' else 'failed')
self._schedule()

View File

@@ -602,25 +602,26 @@ def update_inventory_computed_fields(inventory_id, should_update_hosts=True):
def update_smart_memberships_for_inventory(smart_inventory):
current = set(SmartInventoryMembership.objects.filter(inventory=smart_inventory).values_list('host_id', flat=True))
new = set(smart_inventory.hosts.values_list('id', flat=True))
additions = new - current
removals = current - new
if additions or removals:
with transaction.atomic():
if removals:
SmartInventoryMembership.objects.filter(inventory=smart_inventory, host_id__in=removals).delete()
if additions:
add_for_inventory = [
SmartInventoryMembership(inventory_id=smart_inventory.id, host_id=host_id)
for host_id in additions
]
SmartInventoryMembership.objects.bulk_create(add_for_inventory)
logger.debug('Smart host membership cached for {}, {} additions, {} removals, {} total count.'.format(
smart_inventory.pk, len(additions), len(removals), len(new)
))
return True # changed
return False
with advisory_lock('update_smart_memberships_for_inventory-{}'.format(smart_inventory.id)):
current = set(SmartInventoryMembership.objects.filter(inventory=smart_inventory).values_list('host_id', flat=True))
new = set(smart_inventory.hosts.values_list('id', flat=True))
additions = new - current
removals = current - new
if additions or removals:
with transaction.atomic():
if removals:
SmartInventoryMembership.objects.filter(inventory=smart_inventory, host_id__in=removals).delete()
if additions:
add_for_inventory = [
SmartInventoryMembership(inventory_id=smart_inventory.id, host_id=host_id)
for host_id in additions
]
SmartInventoryMembership.objects.bulk_create(add_for_inventory)
logger.debug('Smart host membership cached for {}, {} additions, {} removals, {} total count.'.format(
smart_inventory.pk, len(additions), len(removals), len(new)
))
return True # changed
return False
@task()
@@ -1131,6 +1132,7 @@ class BaseTask(object):
try:
isolated = self.instance.is_isolated()
self.instance.send_notification_templates("running")
self.pre_run_hook(self.instance)
if self.instance.cancel_flag:
self.instance = self.update_model(self.instance.pk, status='canceled')

View File

@@ -91,7 +91,7 @@ class TestSwaggerGeneration():
# The number of API endpoints changes over time, but let's just check
# for a reasonable number here; if this test starts failing, raise/lower the bounds
paths = JSON['paths']
assert 250 < len(paths) < 300
assert 250 < len(paths) < 350
assert list(paths['/api/'].keys()) == ['get']
assert list(paths['/api/v2/'].keys()) == ['get']
assert list(sorted(

View File

@@ -42,10 +42,6 @@ def test_inventory_source_notification_on_cloud_only(get, post, inventory_source
not_is = inventory_source_factory("not_ec2")
url = reverse('api:inventory_source_notification_templates_any_list', kwargs={'pk': cloud_is.id})
response = post(url, dict(id=notification_template.id), u)
assert response.status_code == 204
url = reverse('api:inventory_source_notification_templates_success_list', kwargs={'pk': not_is.id})
response = post(url, dict(id=notification_template.id), u)
assert response.status_code == 400

View File

@@ -0,0 +1,129 @@
import pytest
from awx.api.versioning import reverse
@pytest.mark.django_db
def test_get_org_running_notification(get, admin, organization):
url = reverse('api:organization_notification_templates_started_list', kwargs={'pk': organization.pk})
response = get(url, admin)
assert response.status_code == 200
assert len(response.data['results']) == 0
@pytest.mark.django_db
def test_post_org_running_notification(get, post, admin, notification_template, organization):
url = reverse('api:organization_notification_templates_started_list', kwargs={'pk': organization.pk})
response = post(url,
dict(id=notification_template.id,
associate=True),
admin)
assert response.status_code == 204
response = get(url, admin)
assert response.status_code == 200
assert len(response.data['results']) == 1
@pytest.mark.django_db
def test_get_project_running_notification(get, admin, project):
url = reverse('api:project_notification_templates_started_list', kwargs={'pk': project.pk})
response = get(url, admin)
assert response.status_code == 200
assert len(response.data['results']) == 0
@pytest.mark.django_db
def test_post_project_running_notification(get, post, admin, notification_template, project):
url = reverse('api:project_notification_templates_started_list', kwargs={'pk': project.pk})
response = post(url,
dict(id=notification_template.id,
associate=True),
admin)
assert response.status_code == 204
response = get(url, admin)
assert response.status_code == 200
assert len(response.data['results']) == 1
@pytest.mark.django_db
def test_get_inv_src_running_notification(get, admin, inventory_source):
url = reverse('api:inventory_source_notification_templates_started_list', kwargs={'pk': inventory_source.pk})
response = get(url, admin)
assert response.status_code == 200
assert len(response.data['results']) == 0
@pytest.mark.django_db
def test_post_inv_src_running_notification(get, post, admin, notification_template, inventory_source):
url = reverse('api:inventory_source_notification_templates_started_list', kwargs={'pk': inventory_source.pk})
response = post(url,
dict(id=notification_template.id,
associate=True),
admin)
assert response.status_code == 204
response = get(url, admin)
assert response.status_code == 200
assert len(response.data['results']) == 1
@pytest.mark.django_db
def test_get_jt_running_notification(get, admin, job_template):
url = reverse('api:job_template_notification_templates_started_list', kwargs={'pk': job_template.pk})
response = get(url, admin)
assert response.status_code == 200
assert len(response.data['results']) == 0
@pytest.mark.django_db
def test_post_jt_running_notification(get, post, admin, notification_template, job_template):
url = reverse('api:job_template_notification_templates_started_list', kwargs={'pk': job_template.pk})
response = post(url,
dict(id=notification_template.id,
associate=True),
admin)
assert response.status_code == 204
response = get(url, admin)
assert response.status_code == 200
assert len(response.data['results']) == 1
@pytest.mark.django_db
def test_get_sys_jt_running_notification(get, admin, system_job_template):
url = reverse('api:system_job_template_notification_templates_started_list', kwargs={'pk': system_job_template.pk})
response = get(url, admin)
assert response.status_code == 200
assert len(response.data['results']) == 0
@pytest.mark.django_db
def test_post_sys_jt_running_notification(get, post, admin, notification_template, system_job_template):
url = reverse('api:system_job_template_notification_templates_started_list', kwargs={'pk': system_job_template.pk})
response = post(url,
dict(id=notification_template.id,
associate=True),
admin)
assert response.status_code == 204
response = get(url, admin)
assert response.status_code == 200
assert len(response.data['results']) == 1
@pytest.mark.django_db
def test_get_wfjt_running_notification(get, admin, workflow_job_template):
url = reverse('api:workflow_job_template_notification_templates_started_list', kwargs={'pk': workflow_job_template.pk})
response = get(url, admin)
assert response.status_code == 200
assert len(response.data['results']) == 0
@pytest.mark.django_db
def test_post_wfjt_running_notification(get, post, admin, notification_template, workflow_job_template):
url = reverse('api:workflow_job_template_notification_templates_started_list', kwargs={'pk': workflow_job_template.pk})
response = post(url,
dict(id=notification_template.id,
associate=True),
admin)
assert response.status_code == 204
response = get(url, admin)
assert response.status_code == 200
assert len(response.data['results']) == 1

View File

@@ -92,27 +92,7 @@ def test_inherited_notification_templates(get, post, user, organization, project
isrc.save()
jt = JobTemplate.objects.create(name='test', inventory=i, project=project, playbook='debug.yml')
jt.save()
url = reverse('api:organization_notification_templates_any_list', kwargs={'pk': organization.id})
response = post(url, dict(id=notification_templates[0]), u)
assert response.status_code == 204
url = reverse('api:project_notification_templates_any_list', kwargs={'pk': project.id})
response = post(url, dict(id=notification_templates[1]), u)
assert response.status_code == 204
url = reverse('api:job_template_notification_templates_any_list', kwargs={'pk': jt.id})
response = post(url, dict(id=notification_templates[2]), u)
assert response.status_code == 204
assert len(jt.notification_templates['any']) == 3
assert len(project.notification_templates['any']) == 2
assert len(isrc.notification_templates['any']) == 1
@pytest.mark.django_db
def test_notification_template_merging(get, post, user, organization, project, notification_template):
user('admin-poster', True)
organization.notification_templates_any.add(notification_template)
project.notification_templates_any.add(notification_template)
assert len(project.notification_templates['any']) == 1
@pytest.mark.django_db
def test_notification_template_simple_patch(patch, notification_template, admin):

View File

@@ -71,7 +71,7 @@ class TestInventorySourceSerializerGetRelated(object):
'activity_stream',
'notification_templates_error',
'notification_templates_success',
'notification_templates_any',
'notification_templates_started',
'inventory_updates',
'update',
'hosts',

View File

@@ -50,7 +50,7 @@ class TestJobTemplateSerializerGetRelated():
'schedules',
'activity_stream',
'launch',
'notification_templates_any',
'notification_templates_started',
'notification_templates_success',
'notification_templates_error',
'survey_spec',

View File

@@ -378,6 +378,7 @@ class TestGenericRun():
job.status = 'running'
job.cancel_flag = True
job.websocket_emit_status = mock.Mock()
job.send_notification_templates = mock.Mock()
task = tasks.RunJob()
task.update_model = mock.Mock(wraps=update_model_wrapper)
@@ -536,6 +537,7 @@ class TestAdhocRun(TestJobExecution):
def test_options_jinja_usage(self, adhoc_job, adhoc_update_model_wrapper):
adhoc_job.module_args = '{{ ansible_ssh_pass }}'
adhoc_job.websocket_emit_status = mock.Mock()
adhoc_job.send_notification_templates = mock.Mock()
task = tasks.RunAdHocCommand()
task.update_model = mock.Mock(wraps=adhoc_update_model_wrapper)

View File

@@ -6,6 +6,7 @@ import logging
import json
import requests
import time
import threading
import socket
import select
from urllib import parse as urlparse
@@ -286,6 +287,8 @@ class AWXProxyHandler(logging.Handler):
Parameters match same parameters in the actualized handler classes.
'''
thread_local = threading.local()
def __init__(self, **kwargs):
# TODO: process 'level' kwarg
super(AWXProxyHandler, self).__init__(**kwargs)
@@ -322,8 +325,9 @@ class AWXProxyHandler(logging.Handler):
return self._handler
def emit(self, record):
actual_handler = self.get_handler()
return actual_handler.emit(record)
if AWXProxyHandler.thread_local.enabled:
actual_handler = self.get_handler()
return actual_handler.emit(record)
def perform_test(self, custom_settings):
"""
@@ -353,6 +357,13 @@ class AWXProxyHandler(logging.Handler):
except RequestException as e:
raise LoggingConnectivityException(str(e))
@classmethod
def disable(cls):
cls.thread_local.enabled = False
AWXProxyHandler.thread_local.enabled = True
ColorHandler = logging.StreamHandler

View File

@@ -1,14 +1,20 @@
# Copyright (c) 2015 Ansible, Inc.
# All Rights Reserved.
import json
# Django
from django.http import HttpResponse
from django.shortcuts import render
from django.utils.html import format_html
from django.utils.translation import ugettext_lazy as _
from django.views.decorators.csrf import csrf_exempt
# Django REST Framework
from rest_framework import exceptions, permissions, views
import logging
def _force_raising_exception(view_obj, request, format=None):
raise view_obj.exception_class()
@@ -84,3 +90,10 @@ def handle_500(request):
'content': _('A server error has occurred.'),
}
return handle_error(request, 500, **kwargs)
@csrf_exempt
def handle_csp_violation(request):
logger = logging.getLogger('awx')
logger.error(json.loads(request.body))
return HttpResponse(content=None)

View File

@@ -65,15 +65,9 @@ import sys
from collections import defaultdict
try:
import ConfigParser as configparser
except ImportError:
import configparser
from ansible.module_utils.six.moves import configparser
try:
import json
except ImportError:
import simplejson as json
import json
try:
import ovirtsdk4 as sdk
@@ -127,7 +121,7 @@ def create_connection():
'ovirt_url': os.environ.get('OVIRT_URL'),
'ovirt_username': os.environ.get('OVIRT_USERNAME'),
'ovirt_password': os.environ.get('OVIRT_PASSWORD'),
'ovirt_ca_file': os.environ.get('OVIRT_CAFILE'),
'ovirt_ca_file': os.environ.get('OVIRT_CAFILE', ''),
}
)
if not config.has_section('ovirt'):
@@ -139,8 +133,8 @@ def create_connection():
url=config.get('ovirt', 'ovirt_url'),
username=config.get('ovirt', 'ovirt_username'),
password=config.get('ovirt', 'ovirt_password', raw=True),
ca_file=config.get('ovirt', 'ovirt_ca_file'),
insecure=config.get('ovirt', 'ovirt_ca_file') is None,
ca_file=config.get('ovirt', 'ovirt_ca_file') or None,
insecure=not config.get('ovirt', 'ovirt_ca_file'),
)
@@ -179,7 +173,7 @@ def get_dict_of_struct(connection, vm):
if vm.name in [vm.name for vm in connection.follow_link(group.vms)]
],
'statistics': dict(
(stat.name, stat.values[0].datum) for stat in stats
(stat.name, stat.values[0].datum) for stat in stats if stat.values
),
'devices': dict(
(device.name, [ip.address for ip in device.ips]) for device in devices if device.ips
@@ -258,5 +252,6 @@ def main():
)
)
if __name__ == '__main__':
main()

View File

@@ -2,6 +2,7 @@
# All Rights Reserved.
# Python
from collections import OrderedDict
import logging
import uuid
@@ -54,6 +55,20 @@ class LDAPSettings(BaseLDAPSettings):
options[ldap.OPT_NETWORK_TIMEOUT] = 30
self.CONNECTION_OPTIONS = options
# when specifying `.set_option()` calls for TLS in python-ldap, the
# *order* in which you invoke them *matters*, particularly in Python3,
# where dictionary insertion order is persisted
#
# specifically, it is *critical* that `ldap.OPT_X_TLS_NEWCTX` be set *last*
# this manual sorting puts `OPT_X_TLS_NEWCTX` *after* other TLS-related
# options
#
# see: https://github.com/python-ldap/python-ldap/issues/55
newctx_option = self.CONNECTION_OPTIONS.pop(ldap.OPT_X_TLS_NEWCTX, None)
self.CONNECTION_OPTIONS = OrderedDict(self.CONNECTION_OPTIONS)
if newctx_option:
self.CONNECTION_OPTIONS[ldap.OPT_X_TLS_NEWCTX] = newctx_option
class LDAPBackend(BaseLDAPBackend):
'''

View File

@@ -18,9 +18,7 @@ def test_ldap_default_network_timeout(mocker):
from_db = mocker.Mock(**{'order_by.return_value': []})
with mocker.patch('awx.conf.models.Setting.objects.filter', return_value=from_db):
settings = LDAPSettings()
assert settings.CONNECTION_OPTIONS == {
ldap.OPT_NETWORK_TIMEOUT: 30
}
assert settings.CONNECTION_OPTIONS[ldap.OPT_NETWORK_TIMEOUT] == 30
def test_ldap_filter_validator():

View File

@@ -41,14 +41,13 @@ export default {
name: {
key: true,
label: 'Name',
columnClass: 'col-lg-2 col-md-3 col-sm-4 col-xs-4',
columnClass: 'col-sm-6',
awToolTip: '{{application.description | sanitize}}',
dataPlacement: 'top'
},
organization: {
label: 'Organization',
columnClass: 'col-lg-2 col-md-3 col-sm-4 col-xs-4',
modalColumnClass: 'col-lg-2 col-md-3 col-sm-4 col-xs-4',
columnClass: 'col-sm-6',
key: false,
ngBind: 'application.summary_fields.organization.name',
sourceModel: 'organization',

View File

@@ -260,7 +260,7 @@
.at-RowItem--labels {
line-height: @at-line-height-list-row-item-labels;
display: inline-block;
display: inline-flex;
* {
font-size: 10px;

View File

@@ -35,6 +35,18 @@ export default ['i18n', 'templateUrl', function(i18n, templateUrl){
excludeModal: true,
columnClass: 'd-none d-sm-flex col-md-4 col-sm-3'
},
notification_templates_started: {
label: i18n._("Start"),
flag: 'notification_templates_started',
type: "toggle",
ngClick: "toggleNotification($event, notification.id, \"notification_templates_started\")",
ngDisabled: "!sufficientRoleForNotifToggle",
awToolTip: "{{ schedule.play_tip }}",
dataTipWatch: "schedule.play_tip",
dataPlacement: "right",
nosort: true,
columnClass: 'd-none d-md-flex justify-content-start col-md-1'
},
notification_templates_success: {
label: i18n._('Success'),
flag: 'notification_templates_success',
@@ -45,11 +57,11 @@ export default ['i18n', 'templateUrl', function(i18n, templateUrl){
dataTipWatch: "schedule.play_tip",
dataPlacement: "right",
nosort: true,
columnClass: 'd-none d-md-flex justify-content-start col-md-2'
columnClass: 'd-none d-md-flex justify-content-start col-md-1'
},
notification_templates_error: {
label: i18n._('Failure'),
columnClass: 'd-none d-md-flex justify-content-start col-md-2 NotifierList-lastColumn',
columnClass: 'd-none d-md-flex justify-content-start col-md-1 NotifierList-lastColumn',
flag: 'notification_templates_error',
type: "toggle",
ngClick: "toggleNotification($event, notification.id, \"notification_templates_error\")",

View File

@@ -49,7 +49,7 @@ export default ['Wait', 'GetBasePath', 'ProcessErrors', 'Rest', 'GetChoices',
scope.relatednotificationsRemove();
}
scope.relatednotificationsRemove = scope.$on('relatednotifications', function () {
var columns = ['/notification_templates_success/', '/notification_templates_error/'];
var columns = ['/notification_templates_started/', '/notification_templates_success/', '/notification_templates_error/'];
GetChoices({
scope: scope,
@@ -64,7 +64,10 @@ export default ['Wait', 'GetBasePath', 'ProcessErrors', 'Rest', 'GetChoices',
Rest.setUrl(notifier_url);
Rest.get()
.then(function(response) {
var type = (response.config.url.indexOf('success')>0) ? "notification_templates_success" : "notification_templates_error";
let checkForSuccessOrError = response.config.url.indexOf('success') > 0 ? "notification_templates_success" : "notification_templates_error";
let type = response.config.url.indexOf('started') > 0 ? "notification_templates_started" : checkForSuccessOrError;
if (response.data.results) {
_.forEach(response.data.results, function(result){
_.forEach(scope.notifications, function(notification){

View File

@@ -21,7 +21,7 @@ export default ['Wait', 'ProcessErrors', 'Rest',
notifier = params.notifier,
notifier_id = params.notifier.id,
callback = params.callback,
column = params.column, // notification_template_success/notification_template__error
column = params.column, // notification_template_success/notification_template__error/notification_template_started
url = params.url + "/" + column + '/';
if(!notifier[column]){

View File

@@ -42,14 +42,14 @@ export default ['NotificationsList', 'i18n', function(NotificationsList, i18n) {
label: i18n._('Name'),
type: 'text',
required: true,
ngDisabled: '!(workflow_job_template_obj.summary_fields.user_capabilities.edit || canAddWorkflowJobTemplate)',
ngDisabled: '!(workflow_job_template_obj.summary_fields.user_capabilities.edit || canAddOrEdit)',
column: 1
},
description: {
label: i18n._('Description'),
type: 'text',
column: 1,
ngDisabled: '!(workflow_job_template_obj.summary_fields.user_capabilities.edit || canAddWorkflowJobTemplate)'
ngDisabled: '!(workflow_job_template_obj.summary_fields.user_capabilities.edit || canAddOrEdit)'
},
organization: {
label: i18n._('Organization'),
@@ -65,8 +65,8 @@ export default ['NotificationsList', 'i18n', function(NotificationsList, i18n) {
reqExpression: '!current_user.is_superuser'
},
column: 1,
ngDisabled: '!(workflow_job_template_obj.summary_fields.user_capabilities.edit || canAddWorkflowJobTemplate) || !canEditOrg',
awLookupWhen: '(workflow_job_template_obj.summary_fields.user_capabilities.edit || canAddWorkflowJobTemplate) && canEditOrg'
ngDisabled: '!(workflow_job_template_obj.summary_fields.user_capabilities.edit || canAddOrEdit) || !canEditOrg',
awLookupWhen: '(workflow_job_template_obj.summary_fields.user_capabilities.edit || canAddOrEdit) && canEditOrg'
},
inventory: {
label: i18n._('Inventory'),
@@ -87,7 +87,7 @@ export default ['NotificationsList', 'i18n', function(NotificationsList, i18n) {
ngChange: 'workflow_job_template_form.inventory_name.$validate()',
text: i18n._('Prompt on launch')
},
ngDisabled: '!(workflow_job_template_obj.summary_fields.user_capabilities.edit || canAddWorkflowJobTemplate) || !canEditInventory',
ngDisabled: '!(workflow_job_template_obj.summary_fields.user_capabilities.edit || canAddOrEdit) || !canEditInventory',
},
labels: {
label: i18n._('Labels'),
@@ -102,7 +102,7 @@ export default ['NotificationsList', 'i18n', function(NotificationsList, i18n) {
ngShow: 'workflow_job_template_labels_isValid !== true',
text: i18n._('Max 512 characters per label.'),
},
ngDisabled: '!(workflow_job_template_obj.summary_fields.user_capabilities.edit || canAddWorkflowJobTemplate)'
ngDisabled: '!(workflow_job_template_obj.summary_fields.user_capabilities.edit || canAddOrEdit)'
},
variables: {
label: i18n._('Extra Variables'),
@@ -119,7 +119,7 @@ export default ['NotificationsList', 'i18n', function(NotificationsList, i18n) {
variable: 'ask_variables_on_launch',
text: i18n._('Prompt on launch')
},
ngDisabled: '!(workflow_job_template_obj.summary_fields.user_capabilities.edit || canAddWorkflowJobTemplate)' // TODO: get working
ngDisabled: '!(workflow_job_template_obj.summary_fields.user_capabilities.edit || canAddOrEdit)' // TODO: get working
},
checkbox_group: {
label: i18n._('Options'),
@@ -133,7 +133,7 @@ export default ['NotificationsList', 'i18n', function(NotificationsList, i18n) {
dataPlacement: 'right',
dataTitle: i18n._('Enable Concurrent Jobs'),
dataContainer: "body",
ngDisabled: '!(workflow_job_template_obj.summary_fields.user_capabilities.edit || canAddWorkflowJobTemplate)'
ngDisabled: '!(workflow_job_template_obj.summary_fields.user_capabilities.edit || canAddOrEdit)'
}]
}
},
@@ -142,22 +142,22 @@ export default ['NotificationsList', 'i18n', function(NotificationsList, i18n) {
launch: {
component: 'at-launch-template',
templateObj: 'workflow_job_template_obj',
ngShow: '(workflow_job_template_obj.summary_fields.user_capabilities.start || canAddWorkflowJobTemplate)',
ngShow: '(workflow_job_template_obj.summary_fields.user_capabilities.start || canAddOrEdit)',
ngDisabled: 'disableLaunch || workflow_job_template_form.$dirty',
showTextButton: 'true'
},
cancel: {
ngClick: 'formCancel()',
ngShow: '(workflow_job_template_obj.summary_fields.user_capabilities.edit || canAddWorkflowJobTemplate)'
ngShow: '(workflow_job_template_obj.summary_fields.user_capabilities.edit || canAddOrEdit)'
},
close: {
ngClick: 'formCancel()',
ngShow: '!(workflow_job_template_obj.summary_fields.user_capabilities.edit || canAddWorkflowJobTemplate)'
ngShow: '!(workflow_job_template_obj.summary_fields.user_capabilities.edit || canAddOrEdit)'
},
save: {
ngClick: 'formSave()', //$scope.function to call on click, optional
ngDisabled: "workflow_job_template_form.$invalid || can_edit!==true", //Disable when $pristine or $invalid, optional and when can_edit = false, for permission reasons
ngShow: '(workflow_job_template_obj.summary_fields.user_capabilities.edit || canAddWorkflowJobTemplate)'
ngShow: '(workflow_job_template_obj.summary_fields.user_capabilities.edit || canAddOrEdit)'
}
},
@@ -183,7 +183,7 @@ export default ['NotificationsList', 'i18n', function(NotificationsList, i18n) {
awToolTip: i18n._('Add a permission'),
actionClass: 'at-Button--add',
actionId: 'button-add',
ngShow: '(workflow_job_template_obj.summary_fields.user_capabilities.edit || canAddWorkflowJobTemplate)'
ngShow: '(workflow_job_template_obj.summary_fields.user_capabilities.edit || canAddOrEdit)'
}
},
@@ -226,7 +226,7 @@ export default ['NotificationsList', 'i18n', function(NotificationsList, i18n) {
relatedButtons: {
view_survey: {
ngClick: 'editSurvey()',
ngShow: '($state.is(\'templates.addWorkflowJobTemplate\') || $state.is(\'templates.editWorkflowJobTemplate\') || $state.is(\'templates.editWorkflowJobTemplate.workflowMaker\')) && survey_exists && !(workflow_job_template_obj.summary_fields.user_capabilities.edit || canAddWorkflowJobTemplate)',
ngShow: '($state.is(\'templates.addWorkflowJobTemplate\') || $state.is(\'templates.editWorkflowJobTemplate\') || $state.is(\'templates.editWorkflowJobTemplate.workflowMaker\')) && survey_exists && !(workflow_job_template_obj.summary_fields.user_capabilities.edit || canAddOrEdit)',
label: i18n._('View Survey'),
class: 'Form-primaryButton'
},

View File

@@ -20,7 +20,7 @@ export default [
const workflowTemplate = resolvedModels[1];
$scope.canAddOrEdit = workflowTemplate.options('actions.POST');
$scope.canAddOrEdit = workflowTemplate.options('actions.POST') ? true : false;
$scope.canEditOrg = true;
$scope.canEditInventory = true;

View File

@@ -137,6 +137,6 @@
</div>
<div class="WorkflowMaker-buttonHolder">
<button type="button" class="btn btn-sm WorkflowMaker-cancelButton" ng-click="closeDialog()"> {{:: strings.get('CLOSE') }}</button>
<button type="button" class="btn btn-sm WorkflowMaker-saveButton" ng-click="saveWorkflowMaker()" ng-show="workflowJobTemplateObj.summary_fields.user_capabilities.edit || canAddWorkflowJobTemplate" ng-disabled="formState.showNodeForm || formState.showLinkForm"> {{:: strings.get('SAVE') }}</button>
<button type="button" class="btn btn-sm WorkflowMaker-saveButton" ng-click="saveWorkflowMaker()" ng-show="workflowJobTemplateObj.summary_fields.user_capabilities.edit || canAddOrEdit" ng-disabled="formState.showNodeForm || formState.showLinkForm"> {{:: strings.get('SAVE') }}</button>
</div>
</div>

View File

@@ -367,7 +367,6 @@
<workflow-chart
graph-state="graphState"
workflow-zoomed="workflowZoomed(zoom)"
can-add-workflow-job-template="canAddWorkflowJobTemplate"
mode="details"
read-only="readOnly"
class="WorkflowMaker-chart">

View File

@@ -8,6 +8,7 @@ from awx.main.views import (
handle_403,
handle_404,
handle_500,
handle_csp_violation,
)
@@ -20,6 +21,7 @@ urlpatterns = [
url(r'^(?:api/)?403.html$', handle_403),
url(r'^(?:api/)?404.html$', handle_404),
url(r'^(?:api/)?500.html$', handle_500),
url(r'^csp-violation/', handle_csp_violation),
]
if settings.SETTINGS_MODULE == 'awx.settings.development':

View File

@@ -22,9 +22,9 @@ Notification templates assigned at certain levels will inherit notifications def
## Workflow
When a job succeeds or fails, the error or success handler will pull a list of relevant notifications using the procedure defined above. It will then create a Notification object for each one containing relevant details about the job and then **sends** it to the destination (email addresses, slack channel(s), SMS numbers, etc.). These Notification objects are available as related resources on job types (Jobs, Inventory Updates, Project Updates), and also at `/api/v2/notifications`. You may also see what notifications have been sent from a notifications by examining its related resources.
When a job starts, succeeds or fails, the running, success or error handler, respectively, will pull a list of relevant notifications using the procedure defined above. It then creates a Notification Object for each one containing relevant details about the job and then **sends** it to the destination (email addresses, Slack channel(s), SMS numbers, etc.). These Notification objects are available as related resources on job types (Jobs, Inventory Updates, Project Updates), and also at `/api/v2/notifications`. You may also see what notifications have been sent by examining its related resources.
Notifications can succeed or fail but that will not cause its associated job to succeed or fail. The status of the notification can be viewed at its detail endpoint: `/api/v2/notifications/<n>`
Notifications can succeed or fail but that will _not_ cause its associated job to succeed or fail. The status of the notification can be viewed at its detail endpoint: `/api/v2/notifications/<n>`
## Testing Notifications Before Using Them

View File

@@ -26,6 +26,12 @@ dockerhub_base=ansible
# pg_cpu_limit=1000
# pg_mem_limit=2
# Kubernetes Ingress Annotations
# You can use the variables below to pass annotations to Kubernetes Ingress
# The example below shows an annotation to be used with Traefik but other Ingress controllers are also supported.
#kubernetes_ingress_hostname=awx.example.org
#kubernetes_ingress_annotations={'kubernetes.io/ingress.class': 'traefik', 'traefik.ingress.kubernetes.io/redirect-entry-point': 'https'}
# Kubernetes and Openshift Install Resource Requests
# These are the request and limit values for a pod's container for task/web/rabbitmq/memcached/management.
# The total amount of requested resources for a pod is the sum of all
@@ -133,7 +139,6 @@ secret_key=awxsecret
# CA Trust directory. If you need to provide custom CA certificates, supplying
# this variable causes this directory on the host to be bind mounted over
# /etc/pki/ca-trust in the awx_task and awx_web containers.
# NOTE: only obeyed in local_docker install
#ca_trust_dir=/etc/pki/ca-trust/source/anchors
# Include /etc/nginx/awx_extra.conf

View File

@@ -61,6 +61,8 @@ http {
# HSTS (ngx_http_headers_module is required) (15768000 seconds = 6 months)
add_header Strict-Transport-Security max-age=15768000;
add_header Content-Security-Policy "default-src 'self'; style-src 'self' 'unsafe-inline'; script-src 'self' 'unsafe-inline' cdn.pendo.io; report-uri /csp-violation/";
add_header X-Content-Security-Policy "default-src 'self'; style-src 'self' 'unsafe-inline'; script-src 'self' 'unsafe-inline' cdn.pendo.io; report-uri /csp-violation/";
# Protect against click-jacking https://www.owasp.org/index.php/Testing_for_Clickjacking_(OTG-CLIENT-009)
add_header X-Frame-Options "DENY";

View File

@@ -141,6 +141,16 @@ spec:
ports:
- containerPort: 8052
volumeMounts:
{% if ca_trust_dir is defined %}
- name: {{ kubernetes_deployment_name }}-ca-trust-dir
mountPath: "/etc/pki/ca-trust/source/anchors/"
readOnly: true
{% endif %}
{% if project_data_dir is defined %}
- name: {{ kubernetes_deployment_name }}-project-data-dir
mountPath: "/var/lib/awx/projects"
readOnly: false
{% endif %}
- name: {{ kubernetes_deployment_name }}-application-config
mountPath: "/etc/tower/settings.py"
subPath: settings.py
@@ -176,6 +186,11 @@ spec:
- /usr/bin/launch_awx_task.sh
imagePullPolicy: Always
volumeMounts:
{% if ca_trust_dir is defined %}
- name: {{ kubernetes_deployment_name }}-ca-trust-dir
mountPath: "/etc/pki/ca-trust/source/anchors/"
readOnly: true
{% endif %}
- name: {{ kubernetes_deployment_name }}-application-config
mountPath: "/etc/tower/settings.py"
subPath: settings.py
@@ -274,6 +289,18 @@ spec:
cpu: "{{ memcached_cpu_limit }}m"
{% endif %}
volumes:
{% if ca_trust_dir is defined %}
- name: {{ kubernetes_deployment_name }}-ca-trust-dir
hostPath:
path: "{{ ca_trust_dir }}"
type: Directory
{% endif %}
{% if project_data_dir is defined %}
- name: {{ kubernetes_deployment_name }}-project-data-dir
hostPath:
path: "{{ project_data_dir }}"
type: Directory
{% endif %}
- name: {{ kubernetes_deployment_name }}-application-config
configMap:
name: {{ kubernetes_deployment_name }}-config
@@ -346,11 +373,28 @@ kind: Ingress
metadata:
name: {{ kubernetes_deployment_name }}-web-svc
namespace: {{ kubernetes_namespace }}
{% if kubernetes_ingress_annotations is defined %}
annotations:
{% for key, value in kubernetes_ingress_annotations.items() %}
{{ key }}: {{ value }}
{% endfor %}
spec:
rules:
- host: {{ kubernetes_ingress_hostname }}
http:
paths:
- path: /
backend:
serviceName: {{ kubernetes_deployment_name }}-web-svc
servicePort: 80
{% else %}
spec:
backend:
serviceName: {{ kubernetes_deployment_name }}-web-svc
servicePort: 80
{% endif %}
{% endif %}
{% if openshift_host is defined %}
---
apiVersion: v1

View File

@@ -23,7 +23,7 @@
register: awx_secret_key
- name: Start the containers
docker_service:
docker_compose:
project_src: "{{ docker_compose_dir }}"
restarted: "{{ awx_compose_config is changed or awx_secret_key is changed }}"
register: awx_compose_start

View File

@@ -1,8 +1,6 @@
The requirements.txt and requirements_ansible.txt files are generated from requirements.in and requirements_ansible.in, respectively, using `pip-tools` `pip-compile`. The following commands should do this if ran inside the tools_awx container.
Run these commands from the root of the awx repo. This will produce python 3 requirements files.
If you do not wish to upgrade dependencies generally (such as adding a specific dependency
needed for a feature) then do not use the `-U` flag.
```
python3 -m venv /buildit

View File

@@ -45,5 +45,5 @@ tacacs_plus==1.0
twilio==6.10.4
uWSGI==2.0.17
uwsgitop==0.10.0
pip==19.1.1
setuptools==41.0.1
pip==9.0.1
setuptools==36.0.1

View File

@@ -122,5 +122,5 @@ xmlsec==1.3.3 # via python3-saml
zope.interface==4.6.0 # via twisted
# The following packages are considered to be unsafe in a requirements file:
pip==19.1.1
setuptools==41.0.1
pip==9.0.1
setuptools==36.0.1

View File

@@ -47,14 +47,14 @@ ncclient==0.6.3
# netaddr filter
netaddr
# oVirt/RHV
ovirt-engine-sdk-python==4.2.4 # minimum set inside Ansible facts module requirements
ovirt-engine-sdk-python==4.3.0 # minimum set inside Ansible facts module requirements
pycurl==7.43.0.1 # higher versions will not install without SSL backend specified
# AWX usage
pexpect==4.6.0 # same as AWX requirement
psutil==5.4.3 # same as AWX requirement
ptyprocess==0.5.2 # via pexpect, but needs to be pinned. Read the blame.
pip==19.1.1
setuptools==41.0.1
setuptools==36.0.1
pip==9.0.1
# VMware
pyvmomi==6.5
# WinRM

View File

@@ -83,7 +83,7 @@ ntlm-auth==1.3.0 # via requests-credssp, requests-ntlm
oauthlib==3.0.1 # via requests-oauthlib
openstacksdk==0.23.0
os-service-types==1.6.0 # via keystoneauth1, openstacksdk
ovirt-engine-sdk-python==4.2.4
ovirt-engine-sdk-python==4.3.0
packaging==19.0
paramiko==2.4.2 # via azure-cli-core, ncclient
pbr==5.2.0 # via keystoneauth1, openstacksdk, os-service-types, stevedore
@@ -122,5 +122,5 @@ wheel==0.30.0 # via azure-cli-core
xmltodict==0.12.0 # via pywinrm
# The following packages are considered to be unsafe in a requirements file:
pip==19.1.1
setuptools==41.0.1
pip==9.0.1
setuptools==36.0.1

View File

@@ -29,6 +29,7 @@ services:
# - sync
# volumes_from:
# - sync
working_dir: "/awx_devel"
volumes:
- "../:/awx_devel"
privileged: true

View File

@@ -22,6 +22,8 @@ server {
# HSTS (ngx_http_headers_module is required) (15768000 seconds = 6 months)
add_header Strict-Transport-Security max-age=15768000;
add_header Content-Security-Policy "default-src 'self'; style-src 'self' 'unsafe-inline'; script-src 'self' 'unsafe-inline' cdn.pendo.io; report-uri /csp-violation/";
add_header X-Content-Security-Policy "default-src 'self'; style-src 'self' 'unsafe-inline'; script-src 'self' 'unsafe-inline' cdn.pendo.io; report-uri /csp-violation/";
location /static/ {
root /awx_devel;
@@ -82,6 +84,8 @@ server {
# HSTS (ngx_http_headers_module is required) (15768000 seconds = 6 months)
add_header Strict-Transport-Security max-age=15768000;
add_header Content-Security-Policy "default-src 'self'; style-src 'self' 'unsafe-inline'; script-src 'self' 'unsafe-inline' cdn.pendo.io; report-uri /csp-violation/";
add_header X-Content-Security-Policy "default-src 'self'; style-src 'self' 'unsafe-inline'; script-src 'self' 'unsafe-inline' cdn.pendo.io; report-uri /csp-violation/";
location /static/ {
root /awx_devel;