Compare commits

..

1098 Commits
1.0.4 ... 1.0.6

Author SHA1 Message Date
Ryan Petrello
327d3c3f5b Merge pull request #1810 from ryanpetrello/devel
restore the celery hostname
2018-04-27 12:20:30 -04:00
Ryan Petrello
8bc8bb1bd6 restore the celery hostname 2018-04-27 12:16:16 -04:00
Ryan Petrello
732698440f Merge pull request #1809 from shanemcd/devel
Merge branch 'release_3.3.0' into awx/devel
2018-04-27 11:33:52 -04:00
Shane McDonald
4fdf462b98 Merge branch 'release_3.3.0' into awx/devel
# Conflicts:
#	awx/ui/client/src/standard-out/standard-out-factories/main.js
#	awx/ui/package.json
2018-04-27 10:17:59 -04:00
Jared Tabor
1b013eb2e3 Merge pull request #1577 from jaredevantabor/network-dep-cleanup
Removes dupclicate Angular import
2018-04-26 14:36:42 -07:00
Jake McDermott
af44a12e7b Merge pull request #1600 from wenottingham/and-the-years-start-coming-and-they-dont-stop-coming
Fix another stray 2017 in the UI.
2018-04-26 14:27:30 -04:00
Bill Nottingham
23a009f8bb Fix another stray 2017 in the UI. 2018-04-26 14:03:57 -04:00
Alan Rominger
22e763a44c Merge pull request #1595 from AlanCoding/revert_team
Revert allowing org members to see teams
2018-04-26 14:02:27 -04:00
AlanCoding
c88303ca67 revert change to allow org members to see teams 2018-04-26 13:37:08 -04:00
Jared Tabor
41f0b2b703 Merge pull request #1801 from jaredevantabor/update-installmd
Updates README.md to include Node and NPM version req's
2018-04-26 10:34:49 -07:00
Jared Tabor
da79f1450c Merge pull request #1576 from jaredevantabor/1021-system-job-cleanup
Cleanup the Cleanup
2018-04-26 10:18:34 -07:00
Jared Tabor
5f38b4fde4 removes code/references related to cleanup_facts mgmt jobs, and their schedules 2018-04-26 09:35:34 -07:00
Jared Tabor
b154949639 Removes dupclicate Angular import, and removes tower.app.js module from network UI 2018-04-26 09:22:25 -07:00
AlanCoding
dff0f2f9ed Revert "update tests for org members seeing teams"
This reverts commit fe04f69e89.
2018-04-26 12:21:50 -04:00
Ryan Petrello
a009d21edc Merge pull request #1592 from ryanpetrello/release_3.3.0
3.2.4 -> 3.3.0
2018-04-26 11:34:16 -04:00
Ryan Petrello
1eb5e98743 Merge branch 'release_3.2.4' into release_3.3.0 2018-04-26 11:10:28 -04:00
Alan Rominger
074302b573 Merge pull request #1588 from AlanCoding/notification_update_fields
Avoid unrelated errors saving notification
2018-04-26 10:19:40 -04:00
Alan Rominger
144e89f0a9 Merge pull request #1587 from AlanCoding/workflow_job_log
Add corresponding log for WorkflowJob submission
2018-04-26 09:50:05 -04:00
AlanCoding
52d8d851fe avoid unrelated errors saving notification 2018-04-26 09:36:43 -04:00
AlanCoding
84d9273012 add corresponding log for WorkflowJob submission 2018-04-26 09:30:19 -04:00
Matthew Jones
ccada295ee Merge pull request #1586 from ansible/fix_missing_instance_group
Fix an issue where missing instance group would cause an error
2018-04-26 09:21:37 -04:00
Matthew Jones
2f3d7b17f6 Fix an issue where missing instance group would cause an error
We'll now default to queue submission to the basic management queue
2018-04-26 09:11:08 -04:00
Jake McDermott
0046b25fa0 Merge pull request #1578 from jakemcdermott/fix-jobz-expanded-host-event
ensure host modal can be visible when details panel is collapsed
2018-04-26 00:31:43 -04:00
Jake McDermott
8fa224f846 ensure host modal can be visible when details panel is collapsed 2018-04-26 00:03:53 -04:00
Jared Tabor
b05becf302 Updates README.md to include Node and NPM version req's 2018-04-25 15:06:24 -07:00
Jake McDermott
9809ddb84c Merge pull request #1575 from mabashian/1522-expand-results
Implemented expand/collapse job results
2018-04-25 17:56:53 -04:00
Michael Abashian
8f6bb15acf Merge pull request #1564 from mabashian/1444-prompt-socket-v2
Hold off on refreshing lists when launch prompt modal is open
2018-04-25 17:00:02 -04:00
mabashian
fe7a0a63b9 Implemented expand/collapse job results 2018-04-25 16:56:11 -04:00
Ryan Petrello
6e3c35dba8 Merge pull request #1574 from ryanpetrello/select-poll
use select.poll() instead of select.select() for file descriptors
2018-04-25 16:49:41 -04:00
Ryan Petrello
f0d3713e99 use select.poll() instead of select.select() for file descriptors
see: https://github.com/ansible/tower/issues/861
see: https://github.com/pexpect/pexpect/pull/474
2018-04-25 16:29:55 -04:00
Christian Adams
5d82ee5a27 Merge pull request #1572 from rooftopcellist/radius_auth_fix
Radius auth fix
2018-04-25 16:17:31 -04:00
Ryan Petrello
69b10feecd Merge pull request #1571 from ryanpetrello/remove-old-fact-cleanup
remove the legacy fact cleanup system template
2018-04-25 16:14:38 -04:00
Christian Adams
2476dc1a61 Merge pull request #1800 from rooftopcellist/auth-backends-fix
fixes sso auth backends
2018-04-25 15:20:25 -04:00
Bill Nottingham
523613c64c Merge pull request #1554 from wenottingham/(don't)-use-the-force-rey
Only force role updates when we actually check out a new version.
2018-04-25 15:11:26 -04:00
adamscmRH
90308066af update radius backend for dr1.3.3 2018-04-25 15:04:46 -04:00
Ryan Petrello
0bc5665e92 remove the legacy fact cleanup system template
see: https://github.com/ansible/tower/issues/1021
2018-04-25 14:20:19 -04:00
Wayne Witzel III
9319bbce8d Merge pull request #1565 from wwitzel3/fix-703
Allow real null to be searched in host_filter
2018-04-25 13:47:29 -04:00
Bill Nottingham
b3ca7acb41 Only pass --force if we have an scm_result (i.e., we ran an actual checkout/revision change.) 2018-04-25 13:46:46 -04:00
Marliana Lara
7f87cd9c47 Merge pull request #1570 from marshmalien/fix/1550-ig-readonly-org-admins
[UI] Org Admin permissions for Instance Groups
2018-04-25 13:41:40 -04:00
adamscmRH
db1072563f fixes sso auth backends 2018-04-25 13:15:01 -04:00
Michael Abashian
cb1f25393a Merge pull request #1543 from mabashian/1447-prompt-strings
Mark prompt strings for translation
2018-04-25 13:02:01 -04:00
Marliana Lara
9192829de2 [UI] Org Admin permissions for Instance Groups 2018-04-25 12:54:08 -04:00
Wayne Witzel III
14b0298217 Merge pull request #1566 from wwitzel3/make-awx-link
Add awx-link make target
2018-04-25 12:39:25 -04:00
mabashian
ac4697e93b Fixed bug displaying default cred with password when creating a schedule 2018-04-25 12:01:10 -04:00
Wayne Witzel III
7151071779 Add awx-link make target 2018-04-25 11:55:57 -04:00
mabashian
cf38faa899 Use string interpolation rather than passing action text in via a var 2018-04-25 11:51:27 -04:00
mabashian
82f2783c97 Mark prompt strings for translation 2018-04-25 11:51:27 -04:00
Wayne Witzel III
404b476576 Allow real null to be searched in host_filter 2018-04-25 11:46:21 -04:00
Jared Tabor
e5a1049edf Merge pull request #1402 from jaredevantabor/vars-component
Adds atCodeMirror directive
2018-04-25 08:43:12 -07:00
Michael Abashian
33a080d9dc Merge pull request #1525 from mabashian/schedule-extra-vars-fixes
Populate JT schedule add with default extra vars when promptable.
2018-04-25 11:40:24 -04:00
Michael Abashian
91ab129d9c Merge pull request #1395 from mabashian/1134-schedule-cred-password
Prevent credentials with passwords in schedule/workflow node prompts
2018-04-25 11:40:00 -04:00
Shane McDonald
5313e069ca Merge pull request #1799 from shanemcd/devel
Fix Helm PostgreSQL deployment name
2018-04-25 11:22:39 -04:00
Michael Abashian
b14a7eda35 Merge pull request #1499 from mabashian/1458-vault-pass-prompt
Fixed bug where vault password prompt was not being hidden after removing default cred
2018-04-25 11:20:49 -04:00
mabashian
d44d28beba Populate JT schedule add with default extra vars when promptable. Hide schedule vars for everything except JT's with promptable extra vars 2018-04-25 11:19:43 -04:00
Shane McDonald
e5faf0798f Always pull memcached image 2018-04-25 11:17:37 -04:00
Shane McDonald
e623c3d7cd Don’t hardcode Helm PostgreSQL deployment name 2018-04-25 11:17:26 -04:00
mabashian
eaa2227890 Replace styles with classes and move string to strings file 2018-04-25 11:08:08 -04:00
mabashian
895ad70a12 Prevent users from attempting to create a template schedule or workflow node with credentials that require passwords 2018-04-25 11:08:08 -04:00
Michael Abashian
d5c2af0492 Merge pull request #1528 from mabashian/1489-wfjt-schedule-prompt
Adds prompting to workflow schedules when a survey is present on the wfjt
2018-04-25 11:00:34 -04:00
Michael Abashian
e62a8797ae Merge branch 'release_3.3.0' into 1458-vault-pass-prompt 2018-04-25 10:59:57 -04:00
Alan Rominger
4919198c1d Merge pull request #1561 from AlanCoding/event_missing_stuff
Add handling for missing related items of events
2018-04-25 10:51:31 -04:00
mabashian
ea80fb8497 Hold off on refreshing lists when ws-jobs events come in and the launch modal is open to prevent the modal from getting wiped 2018-04-25 10:51:13 -04:00
Alan Rominger
c966492222 Merge pull request #1340 from AlanCoding/more_ig_validation
More Instance Group validation
2018-04-25 10:40:52 -04:00
Chris Meyers
1bf3624db7 Merge pull request #1548 from chrismeyersfsu/fix-very_round_up_instance_policy_percentage
ensure instance policy percentages round up
2018-04-25 10:37:10 -04:00
AlanCoding
b7f280588c add handling for missing related items of events 2018-04-25 10:33:43 -04:00
chris meyers
14c6265b27 ensure instance policy percentages round up 2018-04-25 10:11:40 -04:00
AlanCoding
c691d16b11 validate instance group host list 2018-04-25 09:45:30 -04:00
Matthew Jones
c6e76ec6c7 Merge pull request #1557 from ansible/instance_group_rebuild_on_save
Update group cluster policies on save, not just created
2018-04-25 08:57:47 -04:00
Matthew Jones
4442e7de25 Merge pull request #1555 from ansible/no_instancegroup_for_workflowjob
Remove Instance Group concept/usage from WorkflowJobs
2018-04-25 08:30:40 -04:00
Matthew Jones
4af8a53232 Remove Instance Group concept/usage from WorkflowJobs
This also relaxes some of the task manager rules on Instance Groups
down the full stack such that workflow jobs tend to shortcut the
processing or omit it altogether.

This lets the workflow job spawning logic exist outside of the
instance group queues, which it doesn't need to participate in in the
first place.
2018-04-25 08:29:49 -04:00
Wayne Witzel III
ce65914143 Merge pull request #1556 from ryanpetrello/fix-1490
fix a bug in custom virtualenv when Project.organization is None
2018-04-25 07:01:47 -04:00
Matthew Jones
05419d010b Update group cluster policies on save, not just created
Currently updating policy settings doesn't trigger a re-evaluation of
instance group policies, this makes sure we re-evaluate in the event
that anything changes.
2018-04-24 21:40:11 -04:00
Ryan Petrello
c98ede2f27 fix a bug in custom virtualenv when Project.organization is None
see: https://github.com/ansible/tower/issues/1490
2018-04-24 16:53:10 -04:00
Marliana Lara
2d9c4cff32 Merge pull request #1509 from marshmalien/fix/1380-org-admin-ig-access
Show instance groups tab if user is an Org Admin
2018-04-24 14:30:37 -04:00
Chris Meyers
f8b547e528 Merge pull request #1552 from chrismeyersfsu/fix-policy_not_required_field
policy fields not required
2018-04-24 14:23:35 -04:00
Jake McDermott
42e5f6ad33 Merge pull request #1538 from jakemcdermott/fix-smoke-nav
don't anticipate ui-disablement on navbar click to current page
2018-04-24 14:08:37 -04:00
chris meyers
619ec905b2 policy fields not required
* They were previously not required until a min/max was enforced. This
caused the fields to, unintentionally, be required.
* This fix makes the policy fields not required and provides sane defaults.
2018-04-24 14:05:38 -04:00
John Mitchell
c8442d705b Merge pull request #1530 from jlmitch5/newListXSSSanitize
specifically bind html only the row items that need it for new lists …
2018-04-24 13:58:46 -04:00
Alan Rominger
8765de732e Merge pull request #1546 from AlanCoding/named_words
use named formatting in error messages
2018-04-24 13:43:48 -04:00
Alan Rominger
7c0fdd5f05 Merge pull request #1540 from AlanCoding/modified_by_alice
Do not update modified_by for system fields
2018-04-24 12:25:03 -04:00
AlanCoding
13550acb91 fix cross-talk between JT-proj due to arg mutability 2018-04-24 11:29:33 -04:00
AlanCoding
7c621a91ee use named formatting in error messages 2018-04-24 11:26:36 -04:00
Alan Rominger
3b129d784a Merge pull request #1526 from AlanCoding/network_logs
Add more logs for network canvas activity
2018-04-24 11:14:22 -04:00
Michael Abashian
311e0343df Merge pull request #1520 from mabashian/1446-verbosity-prompt-preview
Show verbosity on prompt preview tab
2018-04-24 11:04:26 -04:00
AlanCoding
68975572f3 do not update modified_by for system fields 2018-04-24 10:48:23 -04:00
Marliana Lara
fd52fe9b96 Merge pull request #1527 from marshmalien/fix/1415-Tower-group-name-readonly
Make tower instance group name input field read-only
2018-04-24 07:40:24 -04:00
Jake McDermott
155daf47ea don't anticipate spinny on navbar click to current page 2018-04-24 01:19:53 -04:00
Jake McDermott
43aab10d18 Merge pull request #1532 from jaredevantabor/adhoc-host-events
Adhoc host events
2018-04-24 00:31:27 -04:00
Jake McDermott
d0c8c7d5ce Merge pull request #1533 from jaredevantabor/1436-angular-in-stdout
Adds NgNonBindable directive to stdout lines
2018-04-24 00:25:13 -04:00
Matthew Jones
047e2a92c5 Merge pull request #1506 from ansible/update_vmware_inventory
Updating vmware_inventory from upstream
2018-04-24 00:01:09 -04:00
Jared Tabor
6f1774f1b1 Adds NgNonBindable directive to stdout lines
in order to prevent stdout code from running angular snippets
2018-04-23 19:51:14 -07:00
Jared Tabor
99fb8e6d83 Updates URL for host events for adhoc commands 2018-04-23 19:15:12 -07:00
Jared Tabor
298af25bab Points the host event modal's Standard Out tab at event_data.res.stdout
instead of `event_data.res.results.stdout`. Same for stderr. This was
some stale copy pasta from the host event modal rework
2018-04-23 18:13:26 -07:00
Jared Tabor
929129e8ad Makes CodeMirror Modal resizable 2018-04-23 18:05:47 -07:00
Jared Tabor
0b4d88a57d Adds ability for parent controller to call atCodeMirror's init() function.
This enables the parent controller to re-instantiate the CodeMirror instance
on the fly, when necessary. This was necessary on the NetworkUI to update the
CodeMirror instance on the Host Detail panel.
2018-04-23 17:35:17 -07:00
Jared Tabor
19ebaa6916 Adds atCdeMirror directive
This commit adds a new component to be used for showing CodeMirror
instances, along with an expandable capability to view more variables.
It also removes the previous directive for the Network UI that used
to include this functionality.
2018-04-23 17:35:16 -07:00
Marliana Lara
bcee83e87c Fix broken unit tests 2018-04-23 19:35:59 -04:00
John Mitchell
9cfd264103 specifically bind html only the row items that need it for new lists in the ui 2018-04-23 16:57:43 -04:00
mabashian
6241290733 Adds prompting to workflow schedules when a survey is present on the wfjt 2018-04-23 15:53:16 -04:00
Marliana Lara
b9427ecf6d Show instance groups tab if user is an Org Admin 2018-04-23 15:50:48 -04:00
Marliana Lara
29b9c44a1e Show tower group name field as read-only 2018-04-23 15:29:53 -04:00
AlanCoding
405a936544 add more logs for network canvas activity 2018-04-23 15:29:13 -04:00
Ryan Petrello
e4ffdeb0b5 Merge pull request #1518 from ryanpetrello/botocore-dep-hell
unpin botocore now that an upstream dependency issue is resolved
2018-04-23 15:15:59 -04:00
Ryan Petrello
19d5d71937 unpin botocore now that an upstream dependency issue is resolved
related: 4f585dd09e
related: https://github.com/boto/botocore/pull/1433
2018-04-23 14:43:09 -04:00
John Mitchell
0de88d6566 Merge pull request #1440 from jlmitch5/fixNewTab
fix new tab in router
2018-04-23 13:52:27 -04:00
mabashian
5848a61238 Show verbosity on prompt preview tab 2018-04-23 13:52:11 -04:00
Michael Abashian
7a281cefed Merge pull request #1516 from mabashian/fix-prompt-creds
Fixed merge conflict error that prevents launching JTs with cred prompting
2018-04-23 13:46:50 -04:00
mabashian
e38d779d04 Fixed botched merge conflict bug which prevents launching jobs that need credential selection 2018-04-23 13:42:37 -04:00
Matthew Jones
18b41791ab Updating vmware_inventory from upstream 2018-04-23 12:59:41 -04:00
John Mitchell
fd598f5adc Merge pull request #1439 from jlmitch5/fixInstanceGroupsRunningJobs
update instance groups to only be running jobs
2018-04-23 12:43:23 -04:00
John Mitchell
a1541d679c remove obsolete .go unit test 2018-04-23 12:41:07 -04:00
mabashian
19ef91f2aa Fixed bug where vault password prompt was not being hidden after removing default credential 2018-04-23 12:26:07 -04:00
Ryan Petrello
488e0cc4c6 Merge pull request #1459 from wwitzel3/become-enable
Add new credential become methods, inject instead of set in database
2018-04-23 11:50:31 -04:00
Michael Abashian
4b4bbcebae Merge pull request #1399 from mabashian/1137-cancel-prompt
Fixed bug where user could still save schedule/node after halfway filling out prompt step(s)
2018-04-23 11:30:00 -04:00
Wayne Witzel III
765ad07d9e Fix migration name/ordering 2018-04-23 11:00:21 -04:00
Michael Abashian
d36ec19e24 Merge branch 'release_3.3.0' into 1137-cancel-prompt 2018-04-23 10:57:01 -04:00
Michael Abashian
8c10a064f2 Merge pull request #1438 from mabashian/1437-host-filter-tags
Fixed bug where host filter search tags were not present
2018-04-23 10:55:42 -04:00
Wayne Witzel III
f1b37ff53a Fix order become methods render and migration query 2018-04-23 10:33:59 -04:00
Wayne Witzel III
c1a8d8670f Pop the type to be consistent 2018-04-23 10:33:59 -04:00
Wayne Witzel III
07474d5b21 Extend become_method to model field validation as well 2018-04-23 10:33:59 -04:00
Wayne Witzel III
d6ac9b6e3d Restore previous choices, clean up some minor things 2018-04-23 10:33:59 -04:00
Wayne Witzel III
8fa98d0d54 Add new credential become methods, inject instead of set in database 2018-04-23 10:33:59 -04:00
Wayne Witzel III
df4f6ecfd6 Merge pull request #1480 from wwitzel3/release_3.3.0
Fixup migrations.
2018-04-23 06:01:06 -07:00
Wayne Witzel III
44470200a2 Fix migration numbering 2018-04-23 07:51:36 -04:00
Wayne Witzel III
c37ed32c55 Merge pull request #1204 from AlanCoding/org_members_team
allow org members to see teams in org
2018-04-23 04:15:38 -07:00
Matthew Jones
ec8a8119b9 Merge pull request #1468 from ansible/protect_iso_groups
Protect isolated and control groups from api deletion
2018-04-20 17:36:30 -04:00
Alan Rominger
cdb7e16e6d Merge pull request #1463 from AlanCoding/update_supervisor
Update the dev supervisor file to match recent change
2018-04-20 16:51:08 -04:00
Christian Adams
136a58ee85 Merge pull request #1465 from rooftopcellist/update_oauth2_docs
Update oauth2 docs
2018-04-20 16:50:12 -04:00
John Mitchell
ee137b8b4d fix open in new tab for credentials and inventory scripts 2018-04-20 16:00:57 -04:00
Matthew Jones
062329f56e Protect isolated and control groups from api deletion
Isolated and Control groups are managed strictly from the standalone
setup playbook installer and should not be directly managable from the
api. Especially true since you can't assign or create isolated groups
from within the API itself.

In the future this may change but allowing this in the API could leave
the system in a bad state.
2018-04-20 15:30:13 -04:00
adamscmRH
350ebab161 fix curl example 2018-04-20 15:22:28 -04:00
AlanCoding
4036f64cc4 update the dev supervisor file to match recent change 2018-04-20 14:53:21 -04:00
Chris Meyers
a2901a47ee Merge pull request #1410 from chrismeyersfsu/fix-revert_tower_special_group
send all tower work to a user-hidden queue
2018-04-20 14:21:50 -04:00
Christian Adams
b9d5f96f00 Merge pull request #1460 from rooftopcellist/saml_auth_issue
fix multiple auth backends
2018-04-20 14:04:36 -04:00
Matthew Jones
d04bbde3c2 Merge pull request #1786 from shanemcd/devel
Fix image build role when not deploying to localhost
2018-04-20 13:46:50 -04:00
adamscmRH
3627c713fa fix multiple auth backends 2018-04-20 13:34:51 -04:00
Alan Rominger
996cf550f2 Merge pull request #1443 from AlanCoding/start_anyway
Minor tweak to WFJT can_start launch field
2018-04-20 13:04:42 -04:00
chris meyers
a56771c8f0 send all tower work to a user-hidden queue
* Before, we had a special group, tower, that ran any async work that
tower needed done. This allowed users fine grain control over which
nodes did background work. However, this granularity was too complicated
for users. So now, all tower system work goes to a special non-user
exposed celery queue. Tower remains the fallback instance group to
execute jobs on. The tower group will be created upon install and
protected from deletion.
2018-04-20 13:04:36 -04:00
Alan Rominger
a916bf07bb Merge pull request #1238 from AlanCoding/licenses
Licenses
2018-04-20 13:03:35 -04:00
Alan Rominger
2c917f876f Merge pull request #1455 from AlanCoding/event_of_the_century
Fix event querysets for non superusers
2018-04-20 13:02:54 -04:00
AlanCoding
129701d825 minor tweak to WFJT can_start launch field 2018-04-20 12:49:38 -04:00
AlanCoding
6cb237d5d9 fix event querysets for non superusers 2018-04-20 11:44:52 -04:00
Shane McDonald
e2deab485e Merge pull request #1776 from enginvardar/devel
Install unzip for awx_task docker image to enable usage of unarchive …
2018-04-20 09:11:23 -04:00
Marliana Lara
8d6bd2f3a9 Merge pull request #1396 from marshmalien/fix/1342-css-gridify-lists
Fix alignment of list action icons and invalid item bar
2018-04-20 09:03:49 -04:00
Engin Vardar
6b06d1896e Install unzip to enable usage of unarchive module
Signed-off-by: Engin Vardar <enginvardar@gmail.com>
2018-04-20 09:17:27 +02:00
Shane McDonald
98a9e82d2d Fix image build role when not deploying to localhost 2018-04-20 00:07:25 -04:00
Shane McDonald
590d5ccad1 Merge pull request #1718 from soumikgh/devel
Bind mount to custom certs to `source/anchors` subfolder
2018-04-19 22:33:36 -04:00
John Mitchell
3f509d5ae8 fix new tab in router 2018-04-19 18:27:15 -04:00
John Mitchell
b042beeef7 update instance groups to only be running jobs 2018-04-19 18:15:35 -04:00
mabashian
d4f46fa0e4 Fixed bug where host filter search tags were not present 2018-04-19 17:40:26 -04:00
Marliana Lara
763afc7245 Fix row label bugs and responsive actions button alignment 2018-04-19 16:08:26 -04:00
Alan Rominger
81234986b4 Merge pull request #1435 from AlanCoding/dragon_riddle
Add relative location to CT schema errors
2018-04-19 16:01:18 -04:00
Marliana Lara
92a742af98 Fix alignment of action icons and invalid bar with css grid 2018-04-19 15:18:32 -04:00
AlanCoding
fb43538333 add relative location to CT schema errors 2018-04-19 14:58:40 -04:00
Alan Rominger
60e3dfe22c Merge pull request #1432 from AlanCoding/dragon_born_again
Fix server error with unicode in template
2018-04-19 14:49:11 -04:00
AlanCoding
9f6a4e135f fix server error with unicode in template 2018-04-19 14:25:59 -04:00
Alan Rominger
3798decafc Merge pull request #1429 from AlanCoding/invalid_dragon
prevent server error with CT unicode keys
2018-04-19 13:51:58 -04:00
AlanCoding
f78037c0f3 prevent server error with CT unicode keys 2018-04-19 13:27:25 -04:00
Alan Rominger
a7ecc306e1 Merge pull request #1328 from AlanCoding/job_del_protect
make deletion protection mixin work with inventories
2018-04-19 13:18:31 -04:00
Alan Rominger
c0fd56c654 Merge pull request #1381 from AlanCoding/test_docker
Run ansible tests separately
2018-04-19 13:04:14 -04:00
Christian Adams
d08790a5b4 Merge pull request #1420 from rooftopcellist/act_stream_access
Act stream access
2018-04-19 11:51:33 -04:00
Wayne Witzel III
ab311d5c2f Merge pull request #1422 from wwitzel3/release_3.3.0
Remove unused code, OrderedDictLoader
2018-04-19 08:33:16 -07:00
adamscmRH
b6fcfd43b1 Fix app activity stream permissions 2018-04-19 11:19:19 -04:00
Michael Abashian
ae69abf73c Merge pull request #1413 from mabashian/694-wfjt-missing-jts
Show warning on wfjt form when the workflow contains deleted job templates
2018-04-19 11:17:54 -04:00
Bill Nottingham
dd6527288f Merge pull request #1425 from wenottingham/out-with-the-stdout
Drop /var/lib/awx/job_status listing from sosreport.
2018-04-19 11:10:21 -04:00
mabashian
783cca316e Fixed broken wfjt save test 2018-04-19 11:05:03 -04:00
mabashian
15b31c7abe Removed trailing whitespace 2018-04-19 10:54:35 -04:00
mabashian
b37ee2f611 Show warning on wfjt form when the workflow contains deleted job templates 2018-04-19 10:54:04 -04:00
Bill Nottingham
ea9278a3ea Drop /var/lib/awx/job_status listing from sosreport.
This directory is not persistently populated any more.
2018-04-19 10:47:45 -04:00
Michael Abashian
1fc6a34e62 Merge pull request #1362 from mabashian/933-form-checkboxes-too-wide
Make checkbox labels the width of their contents not their parent
2018-04-19 10:34:04 -04:00
Marliana Lara
bcc5508efd Merge pull request #1401 from marshmalien/fix/ff/1361-template-add-button-styles
Remove +Add button bevel border in Firefox
2018-04-19 10:22:51 -04:00
Marliana Lara
5f7466364b Merge pull request #1407 from marshmalien/fix/1392-relaunch-on-failed
Show relaunch dropdown for only jobs with the status of "failed"
2018-04-19 10:19:06 -04:00
mabashian
4794fc510d Use flex instead of float on form checkboxes 2018-04-19 10:08:34 -04:00
Marliana Lara
4daf1d815b Merge pull request #1398 from marshmalien/fix/1362-instance-modal-scrollable
Add scroll to instance modal
2018-04-19 10:05:07 -04:00
Michael Abashian
6595515987 Merge pull request #1421 from mabashian/692-broken-jt-workflow-form
Prevent user from selecting an invalid JT when adding/editing a wfjt node
2018-04-19 10:02:04 -04:00
Michael Abashian
d2f4ed46ea Merge pull request #1376 from mabashian/1022-groups-all-groups
Show all inventory groups instead of just root groups on the inventor…
2018-04-19 10:01:26 -04:00
Michael Abashian
d3a3694a49 Merge pull request #1356 from mabashian/947-allow-simultaneous
Add checkbox for allow_simultaneous on the workflow job template forms
2018-04-19 09:58:39 -04:00
Michael Abashian
38d83081bb Merge pull request #1335 from mabashian/1260-org-credentials
Check for org credentials and present the count to the user before org deletion
2018-04-19 09:58:00 -04:00
Michael Abashian
4158d64410 Merge pull request #1332 from mabashian/1318-workflow-template-survey
Fixed bug launching workflow with survey
2018-04-19 09:57:25 -04:00
Ryan Petrello
3c7a0b5505 Merge pull request #1424 from ryanpetrello/more-yaml-unsafe
add more edge case handling for yaml unsafe marking
2018-04-19 09:32:25 -04:00
Ryan Petrello
f8211b0588 add more edge case handling for yaml unsafe marking 2018-04-19 09:16:22 -04:00
Chris Meyers
df298cec36 Merge pull request #1374 from chrismeyersfsu/fix-proj_update_redact
redact project update urls when downloading stdout
2018-04-19 09:09:24 -04:00
Wayne Witzel III
a23aadf346 Remove unused code, OrderedDictLoader 2018-04-19 08:07:09 -04:00
Ryan Petrello
31ea55acb9 Merge pull request #1397 from ryanpetrello/unsafe-extra-vars-324
mark launch-time extra vars as !unsafe and sanitize Jinja from certain CLI flags
2018-04-19 07:55:53 -04:00
Wayne Witzel III
3c5928fe5b Merge pull request #1405 from wenottingham/sending-out-an-sos-to-the-world
Update sosreport plugin to pull in more information
2018-04-19 04:10:54 -07:00
Wayne Witzel III
250e036d0b Merge pull request #1412 from AlanCoding/yaml_json_cats_dogs
Add protection for job-compatible vars
2018-04-19 04:04:42 -07:00
mabashian
a918539e23 Prevent user from selecting an invalid JT when adding/editing a wfjt node 2018-04-18 18:13:51 -04:00
chris meyers
09d5645b90 redact project update urls when downloading stdout
* For ProjectUpdate jobs. Redact potentially sensitive urls from the
output.
2018-04-18 16:45:55 -04:00
Alan Rominger
cbe3bc3f2a Merge pull request #1337 from AlanCoding/not_negative_no
never show negative remaining capacity values
2018-04-18 16:33:09 -04:00
Alan Rominger
5cd53142a6 Merge pull request #1341 from AlanCoding/AND_help
Clarify help text on AND searches
2018-04-18 16:19:18 -04:00
Alan Rominger
63b3cc84c4 Merge pull request #1414 from AlanCoding/job_add
avoid server error adding job
2018-04-18 12:04:07 -04:00
Shane McDonald
f4f22dc585 Merge pull request #1777 from shanemcd/devel
Use Deployment on Kubernetes
2018-04-18 10:32:40 -04:00
Shane McDonald
cf5149ecf4 Use Deployment on Kubernetes
I messed this up when rebasing.
2018-04-18 10:28:50 -04:00
Christian Adams
87ba56ba67 Merge pull request #1409 from rooftopcellist/validate_scopes
adds help-text for scopes
2018-04-18 10:09:59 -04:00
Alan Rominger
18a7e86e0c Merge pull request #1175 from AlanCoding/protect_hosts
Add deletion protection to hosts
2018-04-18 10:06:05 -04:00
AlanCoding
13c483e463 avoid server error adding job 2018-04-18 08:27:08 -04:00
AlanCoding
c397cacea5 add protection for job-compatible vars 2018-04-18 07:14:02 -04:00
Ryan Petrello
835f2eebc3 make extra var YAML serialization more robust to non-dict extra vars 2018-04-17 15:39:37 -04:00
Alan Rominger
283132cd08 Merge pull request #1393 from AlanCoding/no_cred_pass
Fix API browser server error on relaunch
2018-04-17 15:28:35 -04:00
adamscmRH
50a5a1a9f8 adds help-text for scopes 2018-04-17 14:38:51 -04:00
Christian Adams
2db44cbf17 Merge pull request #1404 from rooftopcellist/application_id_act_stream
Fix app json error
2018-04-17 13:49:57 -04:00
Marliana Lara
328d9dbe01 Show relaunch dropdown if job has status of "failed" 2018-04-17 13:35:01 -04:00
Bill Nottingham
40cf80db1d Add rabbitmq status commands as well. 2018-04-17 12:25:26 -04:00
Bill Nottingham
ade51c93d8 Add some more information to sosreports.
- tower cluster configuration
- list of all venvs (including custom ones)
2018-04-17 12:25:26 -04:00
Ryan Petrello
fe47b75aad use a three-prong setting for Jinja extra vars policy 2018-04-17 12:08:07 -04:00
adamscmRH
0d2f92c364 Fix app json error 2018-04-17 12:02:35 -04:00
Shane McDonald
ffc347bfd0 Merge pull request #1771 from shanemcd/kubernetes-role-cleanup
Kubernetes role cleanup
2018-04-17 11:16:46 -04:00
Ryan Petrello
7304301948 don't bother building a safe extra vars namespace; it's a file path now 2018-04-17 10:24:14 -04:00
Ryan Petrello
7074dcd677 don't allow usage of jinja templates in certain ansible CLI flags
see: https://github.com/ansible/tower/issues/1338
2018-04-17 09:20:05 -04:00
Alan Rominger
4e4dabb7f1 Merge pull request #1391 from AlanCoding/crusty_setting
Remove setting corresponding to removed tests
2018-04-16 20:36:37 -04:00
Shane McDonald
40d7751fbd Remove image push logic from installer roles
I’m going to be reusing this code on the Tower side, and I’m trying to refactor some of the AWX specific bits out. There will probably be more to come, but this is a good start.
2018-04-16 19:01:43 -04:00
Shane McDonald
2b6fe7969f Move rabbitmq and memcached images into variables 2018-04-16 17:43:15 -04:00
Shane McDonald
0786b41ac6 Allow for customizing kubernetes deployment name 2018-04-16 17:43:15 -04:00
Shane McDonald
479a56c6d3 Generalize variable names in installer
secret_key
2018-04-16 17:43:15 -04:00
Marliana Lara
c4f04f544f Fix add button styles in Firefox 2018-04-16 17:40:04 -04:00
mabashian
fcf9e45d0a Only update the promptData object when the user successfully completes the prompt process 2018-04-16 16:59:23 -04:00
Marliana Lara
acda67f0fe Make instance modal scrollable 2018-04-16 16:52:17 -04:00
Ryan Petrello
88c243c92a mark all unsafe launch-time extra vars as !unsafe
see: https://github.com/ansible/tower/issues/1338
see: https://bugzilla.redhat.com/show_bug.cgi?id=1565865
2018-04-16 16:47:44 -04:00
Bill Nottingham
bba7f45972 Pass extra vars via file rather than via commandline, including custom creds.
The extra vars file created lives in the playbook private runtime
directory, and will be reaped along with the rest of the directory.

Adjust assorted unit tests as necessary.
2018-04-16 16:31:50 -04:00
Shane McDonald
e4a6fc55df Remove unused variable from inventory 2018-04-16 15:51:36 -04:00
Shane McDonald
bebc37b3eb Set kubernetes_namespace when deploying on OpenShift
kubernetes_namespace is referenced later it the role but may not be set if deploying on openshift
2018-04-16 15:51:36 -04:00
Shane McDonald
534b2f1601 Fix openshift_pg_emptydir logic 2018-04-16 15:51:36 -04:00
Shane McDonald
db02bd7531 Remove explicit nodePort declarations for RabbitMQ service
This lets Kubernetes handle the port mapping, which resolves a port collision issue when running multiple deployments of AWX in a single cluster.
2018-04-16 15:51:36 -04:00
Shane McDonald
e9ddf7b9db Use a DeploymentConfig in OpenShift 2018-04-16 15:51:36 -04:00
Shane McDonald
0055c2ffff Merge pull request #1760 from AlanCoding/fix_tests_25
Fix tests fallout from 2.5 upgrade
2018-04-16 15:51:11 -04:00
AlanCoding
d234b71fd8 fix API browser server error on relaunch 2018-04-16 15:03:44 -04:00
AlanCoding
4ff2f3e061 remove setting corresponding to removed tests 2018-04-16 14:49:54 -04:00
Alan Rominger
a03316cdb9 Merge pull request #1388 from AlanCoding/dep_warn
Fix deprecation warning for project update
2018-04-16 14:26:56 -04:00
AlanCoding
affaf23a6b fix deprecation warning for project update 2018-04-16 13:22:58 -04:00
Marliana Lara
cf5ac47a7b Merge pull request #1349 from marshmalien/fix/1290-readonly-tower-group
UI - Display tower instance group as read-only
2018-04-16 13:05:35 -04:00
Michael Abashian
7cd26451b5 Merge pull request #1365 from mabashian/1346-empty-codemirror
Fixed empty codemirror bug
2018-04-16 12:06:05 -04:00
AlanCoding
5eaffb3520 run ansible tests separately 2018-04-16 10:32:51 -04:00
Alan Rominger
88e2741836 Merge pull request #1370 from AlanCoding/backport_with_items
[3.2.4] with_items no_log fix
2018-04-16 09:57:25 -04:00
Alan Rominger
8a55b7f296 Merge pull request #1378 from AlanCoding/fix_unit_test
Fix conditional LDAP test fail
2018-04-16 09:13:59 -04:00
AlanCoding
9bd64fd490 never show negative remaining capacity values 2018-04-16 08:05:16 -04:00
AlanCoding
52416188e2 clarify help text on AND searches 2018-04-16 08:04:07 -04:00
AlanCoding
8ea323895a fix conditional LDAP test fail
Previously, if the main unit tests, test_common.py was
run before running this test, it would fail.
By clearing the cache at the start of the test, we
make its behavior consistent and predictable no
matter what other tests are also being ran,
and the assertion is adjusted to match.
2018-04-16 07:53:44 -04:00
Alan Rominger
900ea14883 Merge pull request #1286 from AlanCoding/remove_user_roles
Remove the "user admin role" entirely
2018-04-16 07:33:55 -04:00
mabashian
7239b28dd8 Show all inventory groups instead of just root groups on the inventory groups tab 2018-04-13 16:21:24 -04:00
AlanCoding
de07ef0680 fix tests fallout from 2.5 upgrade 2018-04-13 16:01:11 -04:00
chris meyers
04693ecb0f remove infinite loop regex
* Fancy url finding regex can result in infinite loop for malformed ipv6
urls so replace it with a more nieve regex that can overmatch.
* regex's that find malformed ipv6 urls will be passed to urlparse. This
can result in a parsing/ValueError. For these cases we redact the entire
found URI.
2018-04-13 15:48:06 -04:00
AlanCoding
77aab65f57 fix no_log leaking with_items values 2018-04-13 15:41:57 -04:00
Jake McDermott
b1f4fb3a98 Merge pull request #1368 from jakemcdermott/fix-1334
add expandable explanation and traceback details
2018-04-13 15:15:34 -04:00
Christian Adams
a1dbd4dd57 Merge pull request #1371 from rooftopcellist/fix_app_change_id
Fix id in activity_stream
2018-04-13 15:11:48 -04:00
adamscmRH
dcb6ca33a5 fix id for app in act_stream 2018-04-13 14:37:19 -04:00
Jake McDermott
9f7dd2af09 add expandable explanation and traceback details 2018-04-13 13:50:52 -04:00
Alan Rominger
684091f4f3 Merge pull request #1367 from AlanCoding/another_lib_fail
Fix test so that playbook runs to completion
2018-04-13 13:39:07 -04:00
AlanCoding
ddc8871e12 fix test so that playbook runs to completion 2018-04-13 13:21:38 -04:00
mabashian
ec1897ac3e Check to make sure extraVarsClone exists before attempting to cull passwords 2018-04-13 13:05:28 -04:00
Alan Rominger
e5165f152a Merge pull request #1351 from AlanCoding/loop_log_fix
[fixes test fail] Fix no_log leaking with_items values
2018-04-13 13:02:28 -04:00
mabashian
cb01dea55f Make checkbox labels the width of their contents no their parent 2018-04-13 10:34:39 -04:00
Michael Abashian
2dad8cc980 Merge pull request #1347 from mabashian/1188-launch-double-submit
Prevent double click on launch which spawns multiple jobs
2018-04-13 10:00:29 -04:00
Michael Abashian
9512bbe1df Merge pull request #1344 from mabashian/845-jobs-list-cancel
Added cancel job logic to the jobs list
2018-04-13 09:58:43 -04:00
Alan Rominger
fb53bc95db Merge pull request #1358 from AlanCoding/fix_lib
fix PATH hack used in test
2018-04-13 09:21:18 -04:00
AlanCoding
6f9ff54430 fix PATH hack used in test 2018-04-13 08:44:39 -04:00
AlanCoding
0c2a621a4e fix no_log leaking with_items values 2018-04-13 07:16:44 -04:00
Alan Rominger
8f6688a84b Merge pull request #1355 from AlanCoding/rm_test
[fixes test fail] Remove test for behavior going away in Ansible 2.5
2018-04-12 17:40:14 -04:00
AlanCoding
e3984cb89b remove test for behavior going away in Ansible 2.5 2018-04-12 15:34:58 -04:00
mabashian
93dc27f0e7 Add checkbox for allow_simultaneous on the workflow job template forms 2018-04-12 15:34:36 -04:00
Michael Abashian
35a75196a9 Merge pull request #1336 from mabashian/1301-activity-stream-setting
Added setting name and setting category to activity stream detail modal
2018-04-12 14:23:10 -04:00
AlanCoding
4995ee7a60 remove admin_role for users 2018-04-12 13:18:49 -04:00
Christian Adams
087ac17c90 Merge pull request #1308 from rooftopcellist/fix_censor
Fix censor
2018-04-12 11:56:44 -04:00
Marliana Lara
05415da3f9 Display Tower instance group as read-only 2018-04-12 10:39:36 -04:00
mabashian
52e226780a Disable prompt final action button after first click to prevent double clicking and launching multiple jobs 2018-04-12 09:54:56 -04:00
mabashian
f35bc4b40a Cancelable job status' should be pending, waiting or running 2018-04-12 09:30:04 -04:00
mabashian
fab3f3d592 Added cancel job logic to the jobs list 2018-04-11 16:53:03 -04:00
Christian Adams
a2cc357f21 Merge pull request #1333 from rooftopcellist/app_help_text
add oauth2 help text
2018-04-11 16:12:45 -04:00
adamscmRH
d1b8142b94 add oauth2 help text 2018-04-11 15:10:39 -04:00
Alan Rominger
4cc84d020d Merge pull request #1169 from AlanCoding/filterability
Add tip in OPTIONS for fields that cannot be filtered
2018-04-11 14:35:37 -04:00
Alan Rominger
7ad42161fc Merge pull request #1331 from AlanCoding/show_me_your_exceptions
Always log uncaught task exceptions
2018-04-11 14:26:09 -04:00
Michael Abashian
5959c01611 Merge pull request #1315 from mabashian/1004-smart-inv-hosts
Force on/off toggle to be disabled on smart inventory host list
2018-04-11 13:23:35 -04:00
mabashian
6757f5bbe5 Added setting name and setting category to activity stream detail modal 2018-04-11 13:21:06 -04:00
mabashian
94ecfbee6a Check for org credentials and present the count to the user before org deletion 2018-04-11 12:53:49 -04:00
Shane McDonald
b0425d524c Merge pull request #1754 from ansible/fix-docker-compose-template
Fix whitespace issues with docker-compose jinja template
2018-04-11 12:38:09 -04:00
mabashian
3dd9ca3fb6 Check to make sure vm.promptData.launchConf.passwords_needed_to_start exists before looping 2018-04-11 11:09:01 -04:00
Shane McDonald
3720c57c63 Fix whitespace issues with docker-compose jinja template
See https://github.com/ansible/awx/issues/1710
2018-04-11 11:06:34 -04:00
AlanCoding
0a23195a7b always log uncaught task exceptions 2018-04-11 11:03:06 -04:00
Alan Rominger
c936fd7035 Merge pull request #1329 from AlanCoding/help_text_words
expand on fact cache timeout help text
2018-04-11 10:32:24 -04:00
Marliana Lara
cee94e7b97 Merge pull request #1327 from marshmalien/fix/976-jt-admin-error-prompt
Fix erroneous job template admin error message
2018-04-11 10:19:24 -04:00
Michael Abashian
0459043b8e Merge pull request #1307 from mabashian/957-nested-group-name
Fixed nested group name link
2018-04-11 10:10:59 -04:00
Michael Abashian
9b7f9c4276 Merge pull request #1304 from mabashian/1136-credential-password-prompt
1136 credential password prompt
2018-04-11 10:10:36 -04:00
Michael Abashian
e77369d113 Merge pull request #1299 from mabashian/1005-adhoc-cred-machine
Limit adhoc credential lookup to only machine credentials
2018-04-11 10:10:13 -04:00
mabashian
9e31c25025 Pass ngDisabled a simple boolean instead of truthy expression 2018-04-11 10:07:49 -04:00
AlanCoding
24369572dc expand on fact cache timeout help text 2018-04-11 07:58:37 -04:00
AlanCoding
0c224df9ad make deletion protection mixin work with inventories 2018-04-11 07:54:09 -04:00
Jared Tabor
03f4010edc Merge pull request #1275 from jaredevantabor/webpack-dev
Changes https flag to false for UI dev env watcher
2018-04-10 14:01:03 -07:00
Chris Meyers
85b026dec7 Merge pull request #1326 from chrismeyersfsu/fix-ldap_defaults
set better defaults for ldap
2018-04-10 15:54:56 -04:00
chris meyers
00cd6cb384 set better defaults for ldap
* LDAP params is a new field. It contains the kwargs that will be passed
to the python class specified by group type. The default for group type
is MemberDNGroupType. The required params are now those in the defaults.
2018-04-10 15:44:08 -04:00
Marliana Lara
07a01e741b Fix erroneous job template admin error message 2018-04-10 15:41:02 -04:00
Marliana Lara
0f79c940a0 Merge pull request #1322 from marshmalien/fix/1293-delete-ig-infinite-spinner
Fix infinite Wait spinner when deleting instance group
2018-04-10 15:36:30 -04:00
Marliana Lara
46f6c67566 Add updates to instance groups strings file 2018-04-10 15:25:24 -04:00
Jake McDermott
55ae23a2f4 Merge pull request #1320 from jakemcdermott/use-copy-capability-field
use copy capability field for copy action
2018-04-10 14:34:12 -04:00
Alan Rominger
ffa61250bb Merge pull request #1312 from AlanCoding/fix_silent_fail
fix silent traceback tests were causing
2018-04-10 14:02:36 -04:00
adamscmRH
8529f2b5eb makes censor characters consistent 2018-04-10 14:02:33 -04:00
Marliana Lara
6beaa4b166 Fix infinite Wait spinner when deleting instance group 2018-04-10 13:46:55 -04:00
Jake McDermott
02d7ce97c6 use copy capability field for copy action 2018-04-10 13:38:07 -04:00
Alan Rominger
e78b5dab73 Merge pull request #1310 from AlanCoding/null_encrypted
do not server error if this value is None
2018-04-10 12:52:56 -04:00
Jake McDermott
53944a2cf3 Merge pull request #1314 from jakemcdermott/fix-delete-cancel-urls
use newly ordained list of statuses for showing cancel/delete
2018-04-10 12:49:30 -04:00
Jake McDermott
f0e0a8a338 use newly ordained list of statuses for showing cancel/delete 2018-04-10 12:36:21 -04:00
mabashian
2f96169f07 Force on/off toggle to be disabled on smart inventory host list 2018-04-10 12:27:09 -04:00
Jake McDermott
1961f99106 Merge pull request #1313 from jakemcdermott/fix-delete-cancel-urls
use ordained list of statuses for showing cancel/delete
2018-04-10 12:07:26 -04:00
Jake McDermott
6768b10638 use ordained list of statuses for showing cancel/delete 2018-04-10 12:05:10 -04:00
John Mitchell
ec586de687 Merge pull request #1281 from jlmitch5/componentizeListInvalidity
move invalid list row properties to row component
2018-04-10 11:58:35 -04:00
Marliana Lara
888a1cbea8 Merge pull request #1236 from marshmalien/fix/926-prompt-for-days-form
Fix Cleanup Job Days cannot be changed from 30
2018-04-10 11:54:46 -04:00
AlanCoding
69822391b3 fix silent traceback tests were causing 2018-04-10 11:51:05 -04:00
Marliana Lara
704a2a73c7 Fix error where controller was accessing wrong prompt_for_days_form scope. 2018-04-10 11:32:35 -04:00
Jake McDermott
ffa1f37742 Merge pull request #1309 from jakemcdermott/fix-delete-cancel-urls
fix cancel-delete urls
2018-04-10 11:01:58 -04:00
AlanCoding
4f65b283df do not server error if this value is None 2018-04-10 11:00:37 -04:00
Jake McDermott
02f9fd0ca0 fix cancel-delete urls 2018-04-10 10:58:33 -04:00
mabashian
50a1a5707e Fixed nested group name link 2018-04-10 10:51:56 -04:00
mabashian
c8b12ed23e Fixed bug where typing in a password would update all password inputs 2018-04-10 10:26:56 -04:00
mabashian
a6dd7dbf07 Fixed credential prompting bug where only one password field is ever shown even if multiple passwords are required 2018-04-10 10:08:49 -04:00
Ryan Petrello
ea900b6f95 Merge pull request #1298 from ryanpetrello/fix-1266
fix a bug preventing custom credential templates from including unicode
2018-04-10 08:43:13 -04:00
Alan Rominger
486e5a2ef2 Merge pull request #1297 from AlanCoding/millennials
Prevent OverflowError in SESSION_COOKIE_AGE
2018-04-10 07:19:25 -04:00
mabashian
6e2133aa52 Limit adhoc credential lookup to only machine credentials 2018-04-09 17:10:15 -04:00
Ryan Petrello
b1028a2e0a fix a bug preventing custom credential templates from including unicode
see: https://github.com/ansible/tower/issues/1266
2018-04-09 17:08:10 -04:00
Jake McDermott
dd5a34ce3b pin container images 2018-04-09 16:30:47 -04:00
Michael Abashian
150467a1cb Merge pull request #1295 from mabashian/1033-inv-source-error-handling
Fixed inventory source form error message
2018-04-09 15:51:27 -04:00
Michael Abashian
335e8be709 Merge pull request #1287 from mabashian/1077-host-pagination
Fixed host/groups pagination issue
2018-04-09 15:49:52 -04:00
AlanCoding
13759fd8ce prevent OverflowError in SESSION_COOKIE_AGE 2018-04-09 15:48:26 -04:00
John Mitchell
e6d4778049 Merge pull request #1277 from jlmitch5/navUpdates33
ux updates to navigation
2018-04-09 15:34:56 -04:00
Jake McDermott
e343a8d983 Merge pull request #1296 from jakemcdermott/bug-1294
update badge count on search
2018-04-09 15:25:18 -04:00
mabashian
453a8507b0 Function/code cleanup 2018-04-09 15:07:27 -04:00
Jake McDermott
04d8642daf update badge count on search 2018-04-09 15:04:52 -04:00
mabashian
23d1454646 Fixed inventory source form error message. Leveraged inv src model for adding/editing existing object 2018-04-09 15:01:16 -04:00
Ryan Petrello
1c24ab913e Merge pull request #1282 from ryanpetrello/fix-1268
add exception handling to deprecated v1 credential support
2018-04-09 14:46:48 -04:00
Alan Rominger
51d43e59e4 Merge pull request #1272 from AlanCoding/user_create_err
handle 400 error creating sys auditor
2018-04-09 14:37:25 -04:00
Ryan Petrello
18c95bf706 add exception handling to deprecated v1 credential support
see: https://github.com/ansible/tower/issues/1268
2018-04-09 14:23:43 -04:00
Michael Abashian
5b619ff0c1 Merge pull request #1274 from mabashian/1026-relaunch-adhoc-error-handle
Added generic error handling to promises in relaunch button component
2018-04-09 13:45:49 -04:00
Jake McDermott
8b7884a965 Merge pull request #1280 from jakemcdermott/bug-1271
ensure correct auth and system configuration forms are loaded
2018-04-09 13:34:25 -04:00
Jake McDermott
67ba534097 ensure correct system and auth forms are loaded 2018-04-09 13:09:49 -04:00
John Mitchell
8371e73bd0 update collapse nav spacer padding amount 2018-04-09 13:04:57 -04:00
Jake McDermott
831fb13347 Merge pull request #1285 from jakemcdermott/output-details-command-args
show module arg details for command jobs
2018-04-09 12:29:24 -04:00
Jake McDermott
d2dfca23f6 show module arg details for command jobs 2018-04-09 12:10:43 -04:00
John Mitchell
3ab255bda8 remove todo messages 2018-04-09 11:57:32 -04:00
mabashian
93e6d06bca Fixed host/groups pagination issue 2018-04-09 11:53:59 -04:00
Jake McDermott
27e8e55d15 add e2e test for auth form rendering after tab switch 2018-04-09 11:30:52 -04:00
John Mitchell
37546d6495 move invalid list row properties to row component 2018-04-09 11:20:26 -04:00
Alan Rominger
5594bae766 Merge pull request #1247 from AlanCoding/more_v1_yay
More accurate handling of serializer cred versioning
2018-04-09 10:39:54 -04:00
AlanCoding
c1f1921995 add test for JT credential summary_fields 2018-04-09 09:56:43 -04:00
Jake McDermott
4a9bf0e46d Merge pull request #1278 from jakemcdermott/bug-1276
fix stats bar issue for adhoc jobs + account for 'check' type project update jobs
2018-04-09 09:56:17 -04:00
Ryan Petrello
c420146c56 Merge pull request #1279 from ryanpetrello/release_3.3.0
missing import for celery failure handler
2018-04-09 09:23:35 -04:00
Ryan Petrello
ba66996add missing import for celery failure handler 2018-04-09 09:10:50 -04:00
AlanCoding
c88621f5fb more accurate handling of serializer cred versioning 2018-04-09 08:39:27 -04:00
Jake McDermott
78e0c02a08 no stats event expected for inventory updates 2018-04-09 08:38:20 -04:00
AlanCoding
f369e3ba0f handle 400 error creating sys auditor 2018-04-09 08:37:54 -04:00
Jake McDermott
56935fef94 account for existence of 'check' project update jobs 2018-04-09 08:35:14 -04:00
Jake McDermott
60d311c1a9 don't try to use stats events for adhoc commands 2018-04-09 08:34:43 -04:00
John Mitchell
177b771826 fix less syntax arrow 2018-04-06 18:04:10 -04:00
John Mitchell
ea16bef39b ux updates to navigation 2018-04-06 17:50:49 -04:00
Jake McDermott
8134110e6f Merge pull request #1263 from mabashian/1067-workflow-running-status
Handle workflow job status race condition
2018-04-06 17:32:57 -04:00
Jared Tabor
639da5de59 Changes https flag to false for UI dev env 2018-04-06 13:28:24 -07:00
Jake McDermott
ac29e5d299 Merge pull request #1264 from mabashian/1059-job-results-resource-tooltips
Updated job results related resources tooltips
2018-04-06 16:07:37 -04:00
mabashian
0f07f4f956 Added generic error handling to promises in relaunch button component 2018-04-06 15:52:29 -04:00
Wayne Witzel III
a088621425 Merge pull request #1250 from wwitzel3/fix-1228
Update role hierarchy when a JobTemplate moves orgs.
2018-04-06 15:31:23 -04:00
Wayne Witzel III
99fb0fa4cd Extract update_role_parentage_for_instance 2018-04-06 15:19:41 -04:00
Alan Rominger
89f770c9ca Merge pull request #1270 from AlanCoding/user_editability
User editing permission changes (3.2.4 backport)
2018-04-06 14:19:50 -04:00
Alan Rominger
aa464fdcc8 Merge pull request #1267 from AlanCoding/ig_min_max
Add min/max to IG fields
2018-04-06 14:08:28 -04:00
Alan Rominger
5df957e223 Merge pull request #1269 from AlanCoding/no_more_jobs
Deprecate jobs creation via sublist
2018-04-06 14:08:10 -04:00
AlanCoding
1195385492 User editing permission changes
Only allow administrative action for a user
who is a system admin or auditor if the
the requesting-user is a system admin.

Previously a user could be edited if the
requesting-user was an admin of ANY of the
orgs the user was member of.
This is changed to require admin permission
to ALL orgs the user is member of.

As a special-case, allow org admins to add
a user as a member to their organization if
the following conditions are met:
- the user is not member of any other orgs
- the org admin has permissions to all of
  the roles the user has
2018-04-06 14:05:29 -04:00
Ryan Petrello
5560dc1da9 Merge pull request #1252 from ryanpetrello/celery-failure-handler
implement celery failure logging using CELERY_ANNOTATIONS
2018-04-06 13:40:34 -04:00
Wayne Witzel III
81fe778676 Collect roles and update parentage instead of saving JT 2018-04-06 13:35:24 -04:00
Alan Rominger
a38e6fc882 Merge pull request #1261 from AlanCoding/user_work_items
More User admin-ing: (2) superuser escalation fix (3) ANY->ALL (4) orphan adoption
2018-04-06 13:14:40 -04:00
AlanCoding
5a380b4437 deprecate jobs creation via sublist 2018-04-06 12:59:53 -04:00
AlanCoding
7dd4dd00b3 add min/max to IG fields 2018-04-06 12:37:11 -04:00
AlanCoding
12979260bb include new org roles in permissions fix 2018-04-06 12:03:43 -04:00
Chris Meyers
675920efb6 Merge pull request #1254 from chrismeyersfsu/fix-polymorphic_delete
update polymorphic delete workaround django 1.11
2018-04-06 11:28:16 -04:00
Ryan Petrello
4c0096a524 implement celery failure logging using CELERY_ANNOTATIONS
see: https://github.com/ansible/awx/issues/1720
see: https://github.com/ansible/tower/issues/1190
2018-04-06 11:23:23 -04:00
chris meyers
bd7d9db1ce correctly cascade set null
* It's problematic to delete an instance that is referenced by a foreign
key; where the referening model is one that has a Polymorphic parent.
* Specifically, when Django goes to nullify the relationship it relies
on the related instances[0] class type to issue a query to decide what
to nullify. So if the foreignkey references multiple different types
(i.e. ProjectUpdate, Job) then only 1 of those class types will get
nullified. The end result is an IntegrityError when delete() is called.
* This changeset ensures that the parent Polymorphic class is queried so
that all the foreignkey entries are nullified
* Also remove old Django "hack" that doesn't work with Django 1.11
2018-04-06 11:10:16 -04:00
mabashian
5fb532c87b Updated job results related resources tooltips 2018-04-06 11:05:54 -04:00
Ryan Petrello
ac2ece5313 Merge pull request #1217 from rooftopcellist/json_parser
Json parser
2018-04-06 10:23:09 -04:00
mabashian
cb92f1794a Handle race condition where workflow job status might appear stuck in waiting if we miss the socket event indicating that it got moved to running 2018-04-06 10:15:24 -04:00
AlanCoding
a344ceda0e User editing permission changes
Only allow administrative action for a user
who is a system admin or auditor if the
the requesting-user is a system admin.

Previously a user could be edited if the
requesting-user was an admin of ANY of the
orgs the user was member of.
This is changed to require admin permission
to ALL orgs the user is member of.

As a special-case, allow org admins to add
a user as a member to their organization if
the following conditions are met:
- the user is not member of any other orgs
- the org admin has permissions to all of
  the roles the user has
2018-04-06 09:51:08 -04:00
adamscmRH
0f046338ac check ParseError fix 2018-04-06 08:47:54 -04:00
Jake McDermott
21b58e689a Merge pull request #1256 from wenottingham/typo-fix
Fix trivial UI typo
2018-04-05 20:15:11 -04:00
Paul Neumann
c407cb78b5 Fix trivial UI typo 2018-04-05 14:15:55 -04:00
Michael Abashian
d1b504e34d Merge pull request #1255 from mabashian/templates-forms-disabled
Fixed bug where job/workflow templates add forms were disabled
2018-04-05 14:13:30 -04:00
Bill Nottingham
538bf40f96 Merge pull request #1735 from paneu/devel
Fix trivial UI typo
2018-04-05 14:00:58 -04:00
mabashian
d9c9df73d2 Fixed unit test failure 2018-04-05 14:00:13 -04:00
mabashian
78893590d1 Fixed bug where job/workflow templates add forms were fully disabled for users with add access 2018-04-05 13:34:16 -04:00
Paul Neumann
c11c9abcaa Fix trivial UI typo 2018-04-05 19:32:31 +02:00
adamscmRH
881688dd77 fix authentication order 2018-04-05 12:04:07 -04:00
mabashian
2f746c9fd9 Complete removal of InitialPlaybookRun 2018-04-05 12:00:41 -04:00
Matthew Jones
62e2be9c4b Merge pull request #1722 from avantassel/devel
Added nginx_status to nginx.conf
2018-04-05 11:26:53 -04:00
Wayne Witzel III
0bd9919108 Make use of callback explicitly for Project and Inventory 2018-04-05 11:05:48 -04:00
Wayne Witzel III
3411389d00 Added JobTemplate ownership change test 2018-04-05 11:00:13 -04:00
Bill Nottingham
442c209899 Merge pull request #1251 from wenottingham/release_3.3.0
Set `raw=True` when reading passwords from ConfigParser files.
2018-04-05 10:46:12 -04:00
Bill Nottingham
17f8ec64ce Set raw=True when reading passwords from ConfigParser files.
Cherry-pick of https://github.com/ansible/ansible/pull/35582
2018-04-05 10:32:50 -04:00
Michael Abashian
e47570e323 Merge pull request #1243 from mabashian/1011-codemirror-syntax
Fixed codemirror syntax highlighting
2018-04-05 09:59:10 -04:00
Wayne Witzel III
524343870b Added Project & Inventory signals for JobTemplate RBAC 2018-04-05 09:46:03 -04:00
Ben Thomasson
bb596e8ce8 Merge pull request #1203 from benthomasson/release_3.3.0
Removes --fake-initial from awx-manage migrate.
2018-04-05 09:08:16 -04:00
Ben Thomasson
dfe35bd405 Merge pull request #1187 from benthomasson/networking-acceptance-ben
Adds acceptance doc for networking UI
2018-04-05 09:07:57 -04:00
Alan Rominger
ab277e816a Merge pull request #1242 from AlanCoding/copy_scripts
Remove shortcut for custom scripts copy
2018-04-05 08:45:15 -04:00
Alan Rominger
596523b2fa Merge pull request #1246 from AlanCoding/cred_total_int
Fix bug from new credential property
2018-04-05 08:23:03 -04:00
Alan Rominger
e5f93bdf95 Merge pull request #1245 from AlanCoding/orphaned_workflows
Fix WFJT user_capabilities special-case
2018-04-05 08:22:42 -04:00
Alan Rominger
1d26c2feb0 Merge pull request #1241 from AlanCoding/actually_validate_spec
check for existence and type of all spec items
2018-04-05 08:06:19 -04:00
AlanCoding
133cca1446 fix WFJT user_capabilities special-case 2018-04-05 08:04:18 -04:00
AlanCoding
50794452c8 fix bug from new credential property 2018-04-05 08:01:26 -04:00
Alan Rominger
16732e52f2 Merge pull request #1244 from AlanCoding/cancel_weirdness
avoid v1 cruft fields in JobCancelSerializer
2018-04-05 07:23:52 -04:00
Jake McDermott
dd0e7e2751 Merge pull request #1163 from ansible/job-results
job results / job event output
2018-04-05 03:12:29 -04:00
Jake McDermott
1362b444f2 move search tag test 2018-04-05 02:50:09 -04:00
Jake McDermott
cf68df41d5 remove unused code 2018-04-05 02:49:56 -04:00
Jake McDermott
01d9c8546e fix team, credential, and workflow copy regressions 2018-04-05 02:49:45 -04:00
Jake McDermott
939666f172 add polyfills for phantomjs 2018-04-05 02:49:42 -04:00
Jake McDermott
b44c7127f7 reactivate linter for dev server 2018-04-05 02:49:39 -04:00
Jared Tabor
fe58b74d1e Adds the host event modal to the standard out feature
Removes old host modal code
2018-04-05 02:49:38 -04:00
Jake McDermott
18dc0e9066 switch ordering of output panel actions 2018-04-05 02:49:35 -04:00
Jake McDermott
a7bcb491d7 disable search while searching 2018-04-05 02:49:32 -04:00
Jake McDermott
356defff09 remove unused lib code 2018-04-05 02:49:28 -04:00
Jake McDermott
379e2226fa rename search tag test 2018-04-05 02:49:24 -04:00
Jake McDermott
e4ad34fa14 update smoke test for new job results view 2018-04-05 02:49:20 -04:00
Jake McDermott
cf4b29c6d5 remove has-ansi dependency 2018-04-05 02:49:12 -04:00
Jake McDermott
95a37fab05 fix lint errors 2018-04-05 02:48:47 -04:00
Jake McDermott
a5e20117e3 move search into feature-level component 2018-04-05 02:48:23 -04:00
Jake McDermott
1f9b325f38 use status service in details and stats components 2018-04-05 02:47:54 -04:00
Jake McDermott
bdd36341ae add status service 2018-04-05 02:47:51 -04:00
Jake McDermott
6c8923d653 tighten up status transitions for relaunch 2018-04-05 02:47:47 -04:00
Jake McDermott
66dcf01088 add raw txt download 2018-04-05 02:47:44 -04:00
Jake McDermott
181d7e0e01 add delete and cancel 2018-04-05 02:47:41 -04:00
Jake McDermott
fc01af2298 hide dev utility 2018-04-05 02:47:37 -04:00
Jake McDermott
07186e1606 disable search when running 2018-04-05 02:47:34 -04:00
Jake McDermott
6b302ef167 job results link-in 2018-04-05 02:47:31 -04:00
Jake McDermott
a53f70f0af add inventory updates 2018-04-05 02:47:27 -04:00
Jake McDermott
8da2c3cad2 fix regression with opening credential edit/add 2018-04-05 02:47:24 -04:00
Jake McDermott
b10dc6d4ff wip - integrate adhoc and workflow 2018-04-05 02:47:20 -04:00
Jake McDermott
91e5659042 rename to engine service
The rename reflects the fact that this service is now driven by an external caller
after being initialized with hooks.
2018-04-05 02:47:15 -04:00
Jake McDermott
450eaeca96 add event processing for stats and host status components 2018-04-05 02:47:11 -04:00
Jake McDermott
faa33e0bec navigate to new job results view on relaunch 2018-04-05 02:47:08 -04:00
Jake McDermott
b577f50930 event processing for details panel and initial stats bar integration 2018-04-05 02:47:05 -04:00
Jake McDermott
f65d170cab initial details panel integration 2018-04-05 02:47:02 -04:00
gconsidine
a23e5e920f Add support for in progress jobs and omit expand
* Any event received by the stream service will start rendering
(instead of JOB_START events only)
* Expand/collapse only shown for static results
2018-04-05 02:46:59 -04:00
gconsidine
033314e4f6 Add fixes to results
- Handle out of order events by batching lines until all lines
  are present
  - In static mode, fetch pages of results until container is full
  and scroll bar appears (for scroll events related to pagination)
2018-04-05 02:46:59 -04:00
gconsidine
e3d42d8e1b Fix webpack-dev-server proxy to accommodate auth changes 2018-04-05 02:46:59 -04:00
gconsidine
81c85913ac Add encoding of html entities in stdout from the API 2018-04-05 02:46:58 -04:00
gconsidine
c9612b8c75 Fix (most) lint errors 2018-04-05 02:46:58 -04:00
gconsidine
57ea582898 Fix stream pause/resume transitions 2018-04-05 02:46:58 -04:00
gconsidine
189963ae83 Add independent stream service 2018-04-05 02:46:58 -04:00
Jake McDermott
7acc99cf15 initial search integration 2018-04-05 02:46:56 -04:00
Jake McDermott
13162ca33a move data transformation logic into a service so it can be reused 2018-04-05 02:46:53 -04:00
Jake McDermott
0adf671de4 refactor, lint, separate data transformation logic from display logic 2018-04-05 02:46:48 -04:00
Jake McDermott
c12173233b add initial test and sanity check for search tags 2018-04-05 02:46:43 -04:00
Jake McDermott
1cc7d5535e hacking together some basic bootstrapping for job results view - not yet an actual test 2018-04-05 02:46:32 -04:00
John Mitchell
a1b7d86981 remove the rest of templates list code and update organization tempaltes list to using new code 2018-04-04 17:01:05 -04:00
John Mitchell
e492043819 utilize new list on projects template list 2018-04-04 17:00:25 -04:00
John Mitchell
3f91cd72c3 Merge pull request #1231 from jlmitch5/licenseOnSettingsPage
add license route to settings page
2018-04-04 16:49:57 -04:00
John Mitchell
a0948b410e embed license panel within settings pane 2018-04-04 16:43:32 -04:00
AlanCoding
02b7424b29 check for existence and type of all spec items 2018-04-04 16:39:36 -04:00
AlanCoding
f0862cd413 avoid v1 cruft fields in JobCancelSerializer 2018-04-04 16:30:44 -04:00
Michael Abashian
0d9759102e Merge pull request #1232 from mabashian/889-firefox-event
Fixed event error in firefox when logging in
2018-04-04 16:13:53 -04:00
gconsidine
60a19246ae Add promise-based event processesing for consistency 2018-04-04 16:03:42 -04:00
gconsidine
3705169de0 Remove stream service from job index includes 2018-04-04 16:03:42 -04:00
gconsidine
0c09447f2d Refactor scroll handling into independent service 2018-04-04 16:03:42 -04:00
gconsidine
b16d9a89e3 Refactor page handling 2018-04-04 16:03:42 -04:00
gconsidine
df84f822f6 [WIP] Move page-related functionality into separate service 2018-04-04 16:03:42 -04:00
gconsidine
a5bd905f18 [WIP] Add event buffering on scroll/resume 2018-04-04 16:03:42 -04:00
gconsidine
60a43015e2 Update when scroll,stream flags are flipped 2018-04-04 16:03:42 -04:00
gconsidine
5c3cf83d08 Implement memory max (NodeList ejection) in real-time mode 2018-04-04 16:03:42 -04:00
gconsidine
d48f69317f Add scroll lock for real-time display 2018-04-04 16:03:42 -04:00
gconsidine
83897d43a7 Add websocket connection info for remaining job types 2018-04-04 16:03:42 -04:00
gconsidine
e143698484 Fix resource references in models 2018-04-04 16:03:42 -04:00
gconsidine
d6e7058947 Remove extraneous model import 2018-04-04 16:03:42 -04:00
gconsidine
3d02ef8209 Add basic (no optimization) real-time implementation 2018-04-04 16:03:42 -04:00
gconsidine
ad1764c7f2 Add ws subscription to job results 2018-04-04 16:03:42 -04:00
gconsidine
2eef166325 Remove git merge conflict artifacts 2018-04-04 16:03:42 -04:00
gconsidine
a6ee7b6aac Remove unused functionality from controller 2018-04-04 16:03:42 -04:00
gconsidine
2e07fee39f Add more robust stdout navigation 2018-04-04 16:03:42 -04:00
gconsidine
07ff25a241 Add generalized resource to job results view 2018-04-04 16:03:42 -04:00
gconsidine
41d3d29ae8 Fix project update model 2018-04-04 16:03:42 -04:00
gconsidine
52a6cca206 Add models for remaining event types 2018-04-04 16:03:42 -04:00
gconsidine
e5187e4ac8 Adjust pagination/scrolling 2018-04-04 16:03:42 -04:00
gconsidine
cc36ee6bed Add WIP prepend/previous on scroll 2018-04-04 16:03:42 -04:00
gconsidine
745e547e34 Add nested page cache 2018-04-04 16:03:42 -04:00
gconsidine
4b81d8d494 Add WIP implementation of pagination with cache 2018-04-04 16:03:42 -04:00
gconsidine
ab8651eab6 Add functions to calc number of rows in view 2018-04-04 16:03:42 -04:00
gconsidine
fa59f46f2b Update less variable names 2018-04-04 16:03:42 -04:00
gconsidine
c08538b8f0 Fix model pagination behavior, limit, and cache 2018-04-04 16:03:42 -04:00
gconsidine
5a75059c86 Add load on scroll and max results to base model 2018-04-04 16:03:42 -04:00
gconsidine
b88ad50a75 Update style of stdout container 2018-04-04 16:03:42 -04:00
gconsidine
3006caffe1 Update style to be inline with mockups 2018-04-04 16:03:42 -04:00
gconsidine
e26c977b36 Add partial implementation of model.next 2018-04-04 16:03:42 -04:00
gconsidine
81dac1d1b8 Update code/output components 2018-04-04 16:03:42 -04:00
gconsidine
a7f29aac3a Add component-based stdout for host modal 2018-04-04 16:03:42 -04:00
gconsidine
21e74fc5eb Add click to launch host event detail modal 2018-04-04 16:03:42 -04:00
gconsidine
56b6d7e85d Add scrollTo for top and bottom, add better expand/collapse 2018-04-04 16:03:42 -04:00
gconsidine
d914b70bb6 Add expand/collapse to parent events 2018-04-04 16:03:42 -04:00
gconsidine
dbf1fd2d4f Use event record with output template 2018-04-04 16:03:42 -04:00
gconsidine
6f7841a920 Add record object to maintain event meta info 2018-04-04 16:03:42 -04:00
gconsidine
0a66d1c3fc Add dynamic angular interaction after HTML insertion 2018-04-04 16:03:42 -04:00
gconsidine
aaec3474b0 Add support for params to BaseModel.extend 2018-04-04 16:03:42 -04:00
gconsidine
3096a58272 Update style to match latest mockup 2018-04-04 16:03:42 -04:00
gconsidine
5c10ce3082 Update job output styling 2018-04-04 16:03:42 -04:00
gconsidine
30c472c499 Add basic event output in sandbox 2018-04-04 16:03:42 -04:00
gconsidine
946f3b5c92 Add ansi parsing libs 2018-04-04 16:03:42 -04:00
gconsidine
0de5301c23 Fix eslint errors/warnings emitted to browser 2018-04-04 16:03:42 -04:00
gconsidine
5b8d2e7659 Add model and resolve block to sandbox view 2018-04-04 16:03:42 -04:00
gconsidine
212ab96a31 Add structure for sandboxed job results 2018-04-04 16:03:42 -04:00
Ryan Petrello
0554e62f70 Merge pull request #1227 from ryanpetrello/encrypt-oauth2-client-secret
automatically encrypt/decrypt main_oauth2application.client_secret
2018-04-04 16:00:30 -04:00
mabashian
7084fe9a6d Fixed codemirror syntax highlighting 2018-04-04 15:36:37 -04:00
Ryan Petrello
5f01d26224 automatically encrypt/decrypt main_oauth2application.client_secret
see: https://github.com/ansible/awx/issues/1416
2018-04-04 15:35:24 -04:00
Jared Tabor
0f5f2804a7 Reverts DeleteJob factory, fixes null provider to stdout pages
and removing unnecessary console.logs in app.js
2018-04-04 11:38:27 -07:00
AlanCoding
6e1e7d8426 remove shortcut for custom scripts copy 2018-04-04 14:35:28 -04:00
AlanCoding
e48fa5c7bf manual license updating 2018-04-04 13:09:42 -04:00
AlanCoding
a6e9ed97d2 add the licenses obtained algorithmically 2018-04-04 12:02:37 -04:00
AlanCoding
17c1ac4468 remove licenses for packes removed in 3.3 2018-04-04 10:59:54 -04:00
Alan Rominger
c2446beb6e Merge pull request #1235 from AlanCoding/rm_TOWER_HOST
remove TOWER_HOST from job env vars
2018-04-04 10:42:16 -04:00
Christian Adams
2b7ad578d5 Merge pull request #1121 from rooftopcellist/organization_based_permission
Organization based permission
2018-04-04 10:39:40 -04:00
AlanCoding
c064195025 remove TOWER_HOST from job env vars 2018-04-04 10:32:41 -04:00
Alan Rominger
716a2a6b0f Merge pull request #1213 from AlanCoding/alan_does_jsonschema
custom message for JSONschema type error
2018-04-04 10:27:23 -04:00
adamscmRH
53139b109e clean up application logic 2018-04-04 10:22:49 -04:00
Ryan Petrello
808267d3fe Merge pull request #1234 from ryanpetrello/hey-hey-hey-goodbye
remove old crusty test fixtures
2018-04-04 09:48:09 -04:00
AlanCoding
996a5b20b0 unit tests of cred field types 2018-04-04 09:38:50 -04:00
Ryan Petrello
4b518298a6 remove old crusty test fixtures 2018-04-04 09:32:17 -04:00
Ryan Petrello
0359c4ed98 Merge pull request #1233 from ryanpetrello/fix-1215
more gracefully account for undefined stdout
2018-04-04 08:45:22 -04:00
Ryan Petrello
0db24a5c97 more gracefully account for undefined stdout
see: https://github.com/ansible/tower/issues/1215
related: https://github.com/ansible/tower/pull/1192#issuecomment-377982131
2018-04-04 08:29:57 -04:00
Wayne Witzel III
6c7a7dbbc0 Merge pull request #1151 from wwitzel3/release_3.3.0
Fix issue with adding new RBAC fields
2018-04-04 08:03:52 -04:00
Ryan Petrello
856312e7ea Merge pull request #1226 from ryanpetrello/fix-1224
add more custom credential type env blacklist items
2018-04-04 08:03:02 -04:00
Wayne Witzel III
bab2745392 Refresh the old instance so the returned obj is up-to-date 2018-04-04 07:49:29 -04:00
Wayne Witzel III
5f888d8400 Fix issue with adding new RBAC fields 2018-04-04 07:48:21 -04:00
Alan Rominger
bf4251794a Merge pull request #1223 from AlanCoding/delete_user_role
Delete user role on deletion of a user
2018-04-04 07:04:04 -04:00
Jared Tabor
77d3d5f5cb Merge pull request #1725 from jaredevantabor/fix-1648
Increases the angular-tz-extension tag in package.json
2018-04-03 23:00:42 +00:00
Michael Abashian
bec66e05ff Merge pull request #1197 from mabashian/941-activity-stream-inv
Fixed regular inventory activity stream link
2018-04-03 18:40:05 -04:00
Jared Tabor
f70917df64 Increases the angular-tz-extension tag in package.json
The angular-tz-extensions was changed to accomodate for an issue with
UTC dates not getting parsed correctly.
2018-04-03 15:38:45 -07:00
mabashian
e8d80b5502 Fixed event error in firefox when logging in. Removed transition console logs. 2018-04-03 18:36:34 -04:00
Jared Tabor
96e5ed57e1 Merge pull request #1229 from jaredevantabor/scheduler-deps
Increases the angular-tz-extension tag in package.json
2018-04-03 21:52:31 +00:00
John Mitchell
4e6ce9af18 add license route to settings page 2018-04-03 17:36:46 -04:00
Jared Tabor
9f07beed59 Increases the angular-tz-extension tag in package.json
The angular-tz-extensions was changed to accomodate for an issue with
UTC dates not getting parsed correctly.
2018-04-03 21:27:38 +00:00
Ryan Petrello
95b5bac6e7 Merge pull request #1208 from ryanpetrello/fix-playbook-on-notify
properly support `v2_playbook_on_notify` in ansible 2.5+
2018-04-03 16:19:10 -04:00
Ryan Petrello
31a0eab880 add more custom credential type env blacklist items
see: https://github.com/ansible/tower/issues/1224
2018-04-03 15:44:44 -04:00
Alan Rominger
ac1bc08480 Merge pull request #1220 from AlanCoding/fix_check_related
fix bug where role name was given incorrectly
2018-04-03 15:12:23 -04:00
Chris Meyers
21e8661fae Merge pull request #1222 from chrismeyersfsu/fix-isolated_query
use non-deprecated way of setting many2many
2018-04-03 14:40:26 -04:00
AlanCoding
ee8416140a custom message for JSONschema type error 2018-04-03 14:38:30 -04:00
AlanCoding
a52b22ffdf delete user role on deletion of a user 2018-04-03 14:31:56 -04:00
chris meyers
88fbb6706f use non-deprecated way of setting many2many 2018-04-03 14:31:37 -04:00
Chris Meyers
9677a9841c Merge pull request #1221 from chrismeyersfsu/fix-isolated_query
fixed isolated instance query
2018-04-03 14:04:01 -04:00
Alan Rominger
59c30af19f Merge pull request #1219 from AlanCoding/dragon_vault_id
Prevent unicode errors in cred unique_hash
2018-04-03 14:01:51 -04:00
chris meyers
c3100afd0e fixed isolated instance query
* Was considering an isolated instance: any instance that has at least 1
group with no controller. This is technically correct since an iso node
can not be a part of a non-iso group.
* The query is now more robust and considers a node an iso node if ALL
groups that a node belong to ALL have a controller.
* Also added better debugging for the special tower instance group
* Added a check for the existance of the special tower group so that
logs are less "messy" during the install process.
2018-04-03 13:50:57 -04:00
AlanCoding
8a7f00bdf7 fix bug where role name was given incorrectly 2018-04-03 13:39:16 -04:00
AlanCoding
9695031b27 prevent unicode errors in cred unique_hash 2018-04-03 13:34:21 -04:00
Andrew Van Tassel
84329fc735 Update nginx.conf (#1)
* Update nginx.conf

Added nginx_status, Sysdig is relentless...
2018-04-03 11:24:30 -06:00
Ryan Petrello
8e51f61afa Merge pull request #1195 from ryanpetrello/dont_log_task_args
only record task.args in the callback plugin if `DISPLAY_ARGS_TO_STDOUT`
2018-04-03 12:44:53 -04:00
Alan Rominger
275cc061f0 Merge pull request #1216 from AlanCoding/no_inv_no_callback
prohibit config callback with no inventory
2018-04-03 12:35:31 -04:00
adamscmRH
9ef1fce5e1 add tests & correct auditor permissions 2018-04-03 11:03:50 -04:00
AlanCoding
482c159ac6 prohibit config callback with no inventory 2018-04-03 10:56:35 -04:00
Ryan Petrello
5fd5c95a1d only record task.args in the callback plugin if DISPLAY_ARGS_TO_STDOUT
see: https://github.com/ansible/awx/issues/1633
2018-04-03 10:52:55 -04:00
Wayne Witzel III
f64587cd1c Merge pull request #1207 from wwitzel3/fix-32-1189
Back port of 3.3.0 fix
2018-04-03 09:29:38 -04:00
adamscmRH
e9a128138a add org-app endpoint & permissions 2018-04-03 08:58:53 -04:00
adamscmRH
a7625b8747 add organization to app model 2018-04-03 08:58:53 -04:00
Ryan Petrello
f8c40cc26f properly support v2_playbook_on_notify in ansible 2.5+
see: https://github.com/ansible/awx/issues/1705
see: https://github.com/ansible/tower/issues/1205
related: https://github.com/ansible/ansible/pull/32633
2018-04-03 08:53:13 -04:00
AlanCoding
fe04f69e89 update tests for org members seeing teams 2018-04-03 07:50:49 -04:00
AlanCoding
d2ec880cad allow org members to see teams in org 2018-04-03 07:41:06 -04:00
Jake McDermott
652bdf7875 Merge pull request #1210 from mabashian/1029-passwords-preview
Scrub passwords from extra vars preview on launch
2018-04-03 01:36:17 -04:00
mabashian
278a2091c2 Scrub passwords from extra vars preview on launch 2018-04-02 16:47:49 -04:00
Chris Meyers
654a1b3e09 Merge pull request #1201 from chrismeyersfsu/fix-provision_instance_list
append registered hostname to policy list
2018-04-02 16:39:20 -04:00
Chris Meyers
d2c9bd2f3b Merge pull request #1194 from chrismeyersfsu/fix-more_better_unicode_handling
better unicode handling
2018-04-02 16:31:21 -04:00
chris meyers
e5dcfda1fe append registered hostname to policy list
* The Instance Group list of instances was getting over-written with
every call to the register_instance management command.
* This changeset appends --hostnames to the Instance Group policy list.
2018-04-02 16:26:59 -04:00
Wayne Witzel III
c4635fa683 Merge pull request #1199 from wwitzel3/fix-1189
Fixes RBAC issue, ensures can admin of sub_obj when needed
2018-04-02 16:06:11 -04:00
Wayne Witzel III
b905aec1d4 Merge pull request #1199 from wwitzel3/fix-1189
Fixes RBAC issue, ensures can admin of sub_obj when needed
2018-04-02 16:03:00 -04:00
Wayne Witzel III
067ead35ac Extend test and fix to include the admin_role 2018-04-02 15:39:01 -04:00
Ben Thomasson
43601be8a7 Removes --fake-initial from awx-manage migrate.
The --fake-initial option is no longer needed and can cause
application with an initial migration to fail as was seen
in the network_ui application.
2018-04-02 15:34:39 -04:00
John Mitchell
3f66379f9d Merge pull request #1122 from jlmitch5/addWorkflowToJobsList
add workflow job row item to jobs list
2018-04-02 14:36:16 -04:00
Michael Abashian
7526ac05bc Merge pull request #1196 from mabashian/1055-pending-running-icon
Added running indicator to smart status and sockets to template list
2018-04-02 14:25:09 -04:00
Soumik Ghosh
41c3e69450 Bind mount to custom certs to 2018-04-02 14:23:23 -04:00
John Mitchell
139cfbfc55 add workflow job row item to jobs list 2018-04-02 14:17:50 -04:00
Michael Abashian
d9cd205929 Merge pull request #1182 from mabashian/1082-relaunch-playbook-run
Fixed bug relaunching job on successful/failed hosts
2018-04-02 14:13:22 -04:00
Wayne Witzel III
ea7a0b2f58 Fixes RBAC issue, ensures can admin of sub_obj when needed 2018-04-02 14:10:14 -04:00
mabashian
d68679c559 Fixed regular inventory activity stream link 2018-04-02 13:57:44 -04:00
mabashian
1cf8e3cc20 Fixed linting errors 2018-04-02 13:48:07 -04:00
Michael Abashian
8ad9d07896 Merge pull request #1178 from mabashian/1161-templates-list-pagination
Fixed templates list pagination
2018-04-02 13:44:33 -04:00
Michael Abashian
5a881d4eb0 Merge pull request #1156 from mabashian/990-host-filter-remove-term
Fixed host filter tag removal
2018-04-02 13:41:18 -04:00
Michael Abashian
0d281dbe21 Merge pull request #1145 from mabashian/prompt-cleanup-v2
Complete removal of InitiatePlaybookRun
2018-04-02 13:40:51 -04:00
mabashian
6bd6cc7fbc Added running indicator to smart status. Added sockets back to templates list 2018-04-02 13:30:17 -04:00
AlanCoding
dd8acb70be add deletion protection to hosts 2018-04-02 13:21:22 -04:00
Alan Rominger
6a835b8a6b Merge pull request #1193 from AlanCoding/broken_no_launch
hide launch button for invalid JTs (using user_capabilities)
2018-04-02 13:08:53 -04:00
chris meyers
557637afcb better unicode handling 2018-04-02 12:05:16 -04:00
Chris Meyers
07aa99f949 Merge pull request #1192 from chrismeyersfsu/fix-stdout_handler_undefined
more gracefully account for undefined stdout
2018-04-02 11:58:07 -04:00
AlanCoding
d8f37e799b hide launch button for invalid JTs 2018-04-02 11:58:02 -04:00
chris meyers
7e7ff8137d more gracefully account for undefined stdout
* It's possible to have an exception raised in BaseTask.run() before the
stdout handler gets defined. This is problematic when the exception
handler tries to access that undefined var .. causing another exception.
Note that the second exception is caught also but it's not desirable to
lose the first exception.
* This fix checks to see if the stdout handler var is defined before
calling it's methods. Thus, we retain the original error message.
2018-04-02 11:41:30 -04:00
Chris Meyers
47fa99d3ad Merge pull request #1154 from chrismeyersfsu/enhancement-tower_in_all_groups
add all instances to special tower instance group
2018-04-02 09:39:04 -04:00
Alan Rominger
2e1a2f0a95 Merge pull request #1184 from AlanCoding/validate_scope
Validate token scope
2018-04-02 09:36:12 -04:00
Alan Rominger
afd54a9ff9 Merge pull request #1174 from AlanCoding/cred_type_deets
Include credential type details (and more!) in activity stream
2018-04-02 08:03:33 -04:00
Jared Tabor
92dc450940 Adds acceptance doc for networking UI
* Adds gerkin feature file for networking visualization
* Adds implementation details to networking.md
2018-03-29 16:53:57 -04:00
chris meyers
838b723c73 add all instances to special tower instance group
* All instances except isolated instances
* Also, prevent any tower attributes from being modified via the API
2018-03-29 16:47:52 -04:00
AlanCoding
a4721dc9e7 validate token scope 2018-03-29 15:43:25 -04:00
mabashian
2ccbf5a817 Fixed bug relaunching job on successful/failed hosts 2018-03-29 15:09:31 -04:00
Alan Rominger
09801d6dab Merge pull request #1150 from AlanCoding/types_docs
Make example credential type work
2018-03-29 15:03:03 -04:00
AlanCoding
9244989a14 test for all models in ActivityStreamSerializer 2018-03-29 14:57:26 -04:00
Alan Rominger
b919c36994 Merge pull request #1166 from AlanCoding/role_mods
Exclude created/modified from RoleSerializer
2018-03-29 14:05:41 -04:00
mabashian
3f15966d9d Fixed templates list pagination 2018-03-29 13:54:22 -04:00
Marliana Lara
1f0889431c Merge pull request #1138 from marshmalien/fix/auth_ldap_group_type_params_field
Add ldap_group_type_params codemirror field to LDAP forms
2018-03-29 13:47:25 -04:00
Marliana Lara
372b2925d2 Merge pull request #1123 from marshmalien/fix/manage_org_auth
Warning banner when Org Admin doesn't have authorization
2018-03-29 13:38:37 -04:00
Marliana Lara
471248e66c Fix LDAP_GROUP_TYPE_PARAMS revert value 2018-03-29 13:33:17 -04:00
AlanCoding
0295351bf1 include credential type details in activity stream 2018-03-29 12:07:05 -04:00
Alan Rominger
f16ad97081 Merge pull request #1165 from AlanCoding/you_know_who_you_are
Prevent modified from showing up in activity stream
2018-03-29 12:00:36 -04:00
Alan Rominger
05a56b9b22 Merge pull request #1164 from AlanCoding/path_words
Add verbosity to local_path error
2018-03-29 11:58:39 -04:00
AlanCoding
86579775b2 field OPTIONS tip for filterability 2018-03-29 10:55:43 -04:00
Marliana Lara
5ffe0f40d2 Only show banner if user is an org admin without edit capabilities 2018-03-29 10:53:09 -04:00
AlanCoding
19c3e26cf2 exclude created/modified from RoleSerializer 2018-03-29 08:59:03 -04:00
Ryan Petrello
61d58b83a4 Merge pull request #1162 from ryanpetrello/release_3.2.4
properly filter disabled hosts on smart inventory composition
2018-03-29 08:51:20 -04:00
AlanCoding
48112f2c56 prevent modified from showing up in activity stream 2018-03-29 08:37:20 -04:00
AlanCoding
27e4630426 add verbosity to local_path error 2018-03-29 07:53:10 -04:00
Ryan Petrello
c1cc92afa0 properly filter disabled hosts on smart inventory composition
see: #1053
related: https://github.com/ansible/tower/pull/1155
2018-03-28 17:02:32 -04:00
Shane McDonald
3cb37b131b Merge pull request #1702 from shanemcd/postgresql-persistent-template
Customizable template for OpenShift PostgreSQL deployment
2018-03-28 16:37:02 -04:00
Chris Meyers
8c3b1c06d9 Merge pull request #1160 from chrismeyersfsu/fix-instance_running_jobs
fix api browser endpoint title
2018-03-28 16:31:00 -04:00
Shane McDonald
1ef7d73bc9 Customizable template for OpenShift PostgreSQL deployment
`oc new-app --template=postgresql-persistent` has been kind of a pain. It would attempt to create a Persistent Volume, but does not allow you to specify the storageClass.

This code assumes that a Persistent Volume is already available and will fail with a helpful error message if it is not.

Signed-off-by: Shane McDonald <me@shanemcd.com>
2018-03-28 16:25:43 -04:00
chris meyers
bf6412ea06 fix api browser endpoint name
* Endpoint exposes all jobs associated with an Instance. This is what we
want. Align the endpoint description with this behavior by removing the
word running.
2018-03-28 16:24:19 -04:00
chris meyers
8438331563 make jobs_running more rich in OPTIONS
* Expose jobs_running as an IntegerField
2018-03-28 16:01:24 -04:00
Alan Rominger
c7ecbb7d2a Merge pull request #1157 from AlanCoding/no_access_token
Remove access_token from Activity Stream serializer
2018-03-28 15:56:12 -04:00
AlanCoding
33e2457721 remove access_token from ActStr serializer 2018-03-28 15:38:58 -04:00
Ryan Petrello
6e246c1782 Merge pull request #1155 from ryanpetrello/fix-1053
properly filter disabled hosts on smart inventory composition
2018-03-28 15:34:16 -04:00
mabashian
4e0b890a03 Fixed host filter tag removal 2018-03-28 15:27:12 -04:00
Ryan Petrello
23267bce38 properly filter disabled hosts on smart inventory composition
see: https://github.com/ansible/tower/issues/1053
2018-03-28 15:16:26 -04:00
AlanCoding
2a45f352bb make example cred type work 2018-03-28 14:40:38 -04:00
Alan Rominger
ae5d17d151 Merge pull request #1147 from AlanCoding/exclude_last_used
Exclude last_used from activity stream
2018-03-28 14:32:55 -04:00
Michael Abashian
9b3bb2a9b3 Merge branch 'release_3.3.0' into prompt-cleanup-v2 2018-03-28 13:07:10 -04:00
AlanCoding
98dc59765e exclude last_used from activity stream 2018-03-28 12:53:11 -04:00
Shane McDonald
84e3bcc031 Merge pull request #1701 from shanemcd/devel
Move rabbitmq_version out of inventory file
2018-03-28 12:47:43 -04:00
Shane McDonald
c8ea03e67b Move rabbitmq_version out of inventory file
Signed-off-by: Shane McDonald <me@shanemcd.com>
2018-03-28 12:45:05 -04:00
Shane McDonald
c1e1bf32d0 Merge pull request #1699 from shanemcd/rabbitmq-3.7
Upgrade to RabbitMQ 3.7, remove need for etcd2
2018-03-28 12:14:54 -04:00
Jared Tabor
ed86828a6f Merge pull request #1691 from jaredevantabor/fix-1687
Reverts DeleteJob factory, fixes null provider to stdout pages
2018-03-28 08:54:31 -07:00
Shane McDonald
935dc8bca7 Upgrade to RabbitMQ 3.7, remove need for etcd2
Signed-off-by: Shane McDonald <me@shanemcd.com>
2018-03-28 11:45:33 -04:00
Alan Rominger
bed9b06426 Merge pull request #1124 from AlanCoding/hot_potato
Stream standard out in non-event models
2018-03-28 11:38:49 -04:00
Shane McDonald
3f685c42fe Merge pull request #1693 from shanemcd/consolidate-roles
Consolidate OpenShift and Kubernetes roles
2018-03-28 11:37:12 -04:00
Shane McDonald
98f5dc3fcc Consolidate OpenShift and Kubernetes roles
Signed-off-by: Shane McDonald <me@shanemcd.com>
2018-03-28 11:36:08 -04:00
Marliana Lara
d5d80860e9 Merge pull request #1131 from marshmalien/fix/small_ui_improvements
UI Fix inventory list action buttons, upgrade screen, custom venv fields
2018-03-28 11:26:21 -04:00
Michael Abashian
81e85408eb Merge pull request #1120 from mabashian/1066-workflow-node-credentials
Fixed workflow node credential bug
2018-03-28 11:22:51 -04:00
mabashian
767991fc2b Complete removal of InitialPlaybookRun 2018-03-28 11:18:09 -04:00
AlanCoding
8c167e50c9 Continuously stream data from verbose jobs
In verbose unified job models (inventory updates, system jobs,
etc.), do not delay dispatch just because the encoded
event data is not part of the data written to the buffer.

This allows output from these commands to be submitted
to the callback queue as they are produced, instead
of waiting until the buffer is closed.
2018-03-28 11:05:49 -04:00
Chris Meyers
fe0e873108 Merge pull request #1144 from chrismeyersfsu/fix-auto_reprovision
delay looking up settings SYSTEM_UUID
2018-03-28 10:32:24 -04:00
chris meyers
eef6f7ecb0 delay looking up settings SYSTEM_UUID 2018-03-28 09:54:51 -04:00
Chris Meyers
bc34a74b7e Merge pull request #1142 from chrismeyersfsu/fix-ldap_options_label
fix ldap group type params label
2018-03-28 09:15:39 -04:00
chris meyers
dc46a732bc fix ldap group type params label
* copy pasted ldap group type label and did no previously update. This
updates.
2018-03-28 09:12:06 -04:00
Wayne Witzel III
3936b5e7a3 Merge pull request #1690 from ryanpetrello/coc
add a link to the community code of conduct
2018-03-28 08:55:57 -04:00
Jared Tabor
f10281e9ef Reverts DeleteJob factory, fixes null provider to stdout pages
and removing unnecessary console.logs in app.js
2018-03-27 14:21:39 -07:00
Ryan Petrello
d9a0029ef7 add a link to the community code of conduct 2018-03-27 16:51:30 -04:00
Marliana Lara
492e74a345 Remove pending_deletion button bug from inventory list 2018-03-27 16:43:50 -04:00
Marliana Lara
9b30b02acb Hide Ansible Environment form fields when there are no custom venvs 2018-03-27 16:43:39 -04:00
Marliana Lara
25c8bf93ec Add AUTH_LDAP_GROUP_TYPE_PARAMS field to LDAP form 2018-03-27 16:06:26 -04:00
Chris Meyers
4740f90dc7 Merge pull request #1129 from chrismeyersfsu/enhancement-node_reregister
reregister node when they come back online
2018-03-27 15:24:58 -04:00
chris meyers
7ce8907b7b reregister node when they come back online
* Nodes are marked offline, then deleted; given enough time. Nodes can
come back for various reasions (i.e. netsplit). When they come back,
have them recreate the node Instance if AWX_AUTO_DEPROVISION_INSTANCES
is True. Otherwise, do nothing. The do nothing case will show up in the
logs as celery job tracebacks as they fail to be self aware.
2018-03-27 14:30:47 -04:00
Ryan Petrello
96f85c4dd5 Merge pull request #1689 from ryanpetrello/security-issue-template
more issue template updates
2018-03-27 11:47:12 -04:00
Ryan Petrello
14886af969 more issue template updates 2018-03-27 11:44:25 -04:00
Ryan Petrello
4ea97c0f86 Merge pull request #1688 from ryanpetrello/security-issue-template
add instructions for responsible disclosure of security issues
2018-03-27 11:42:13 -04:00
Marliana Lara
4fab94d2ce Add warning banner when Org Admin doesn't have manage auth 2018-03-27 11:41:16 -04:00
Ryan Petrello
e6d4aead65 add instructions for responsible disclosure of security issues 2018-03-27 11:38:22 -04:00
mabashian
d744679d22 Fixed bug where the machine credential was being stripped from a workflow node if the edge type was changed. 2018-03-27 11:21:24 -04:00
Shane McDonald
7002c6f1b1 Delete unused namespace file
Signed-off-by: Shane McDonald <me@shanemcd.com>
2018-03-27 10:57:22 -04:00
Shane McDonald
3072c3bd8d Whitespace fix
Signed-off-by: Shane McDonald <me@shanemcd.com>
2018-03-27 10:57:22 -04:00
Matthew Jones
3a3c883504 Merge pull request #1115 from ryanpetrello/newer-dateutil
remove an RRULE parsing bug fix that landed upstream in python-dateutil
2018-03-26 20:36:10 -07:00
Ryan Petrello
21d629531f remove an RRULE parsing bug fix that landed upstream in python-dateutil
related: a2a246a834
2018-03-26 18:02:52 -04:00
Ryan Petrello
f3bf9bc34f Merge pull request #1114 from ryanpetrello/newer-dateutil
update to a newer python-dateutil with more bug fixes we were vendoring
2018-03-26 17:21:45 -04:00
Ryan Petrello
a2a246a834 update to a newer python-dateutil with more bug fixes we were vendoring
related: dateutil/dateutil#649
2018-03-26 17:12:01 -04:00
Matthew Jones
9637058406 Merge pull request #1512 from chrismeyersfsu/feature-new_ldap_group_type
add ldap group type like posixGroupType
2018-03-26 12:29:01 -07:00
Matthew Jones
d6203b521f Merge pull request #1679 from mabashian/workflow-node-start-bug
Fixed js error when adding first workflow node
2018-03-26 12:20:38 -07:00
Matthew Jones
d685815478 Merge pull request #1607 from AlanCoding/tower_verify_field
Sync tower verify_ssl parameter with tower-cli
2018-03-26 12:12:45 -07:00
Matthew Jones
71d2a4b4cf Merge pull request #1610 from AlanCoding/group_related
Include related Jobs in group deletion protection
2018-03-26 12:09:45 -07:00
mabashian
6c321f810a Tweaked isRoot logic to follow the same pattern as the rest of the function 2018-03-26 15:00:33 -04:00
mabashian
2c1fe14206 Fixed js error when adding first workflow node 2018-03-26 14:55:01 -04:00
Alan Rominger
3411721a2c Merge pull request #1676 from AlanCoding/unused_settings
Removed unused settings (replaced by source_vars)
2018-03-26 14:16:42 -04:00
AlanCoding
9db0fdfc0b sync tower verify_ssl parameter with tower-cli
Add new input for the tower type credential
elsewhere, tests are being added for verify_ssl in modules
tower-cli also updating to use the original tower.py var
2018-03-26 13:42:52 -04:00
Chris Meyers
09babbe862 Merge pull request #1677 from chrismeyersfsu/fix-traceback_msg
call celery method with celery context
2018-03-26 13:03:32 -04:00
John Mitchell
b1cd7dbd2f Merge pull request #1678 from jlmitch5/removeTowerLicenseDir
remove tower-license dir
2018-03-26 12:17:58 -04:00
John Mitchell
02a97a2ec2 update .gitignore to include root tower-license dir 2018-03-26 12:13:00 -04:00
Alan Rominger
e38955e1fa Merge pull request #1670 from AlanCoding/no_credential
Inventory source can_change - remove credential check
2018-03-26 12:06:57 -04:00
John Mitchell
012e644b9f remove tower-license dir 2018-03-26 12:03:40 -04:00
chris meyers
1503e0505e call celery method with celery context
* Tracebacks are more informative this way.
2018-03-26 12:02:42 -04:00
Ryan Petrello
7842b67bea Merge pull request #1104 from ryanpetrello/fix-1101
properly sanitize module arguments with no_log (like uri:password)
2018-03-26 11:57:28 -04:00
chris meyers
b9b8502738 introspect ldap group types for param validation
* Instead of keeping a hard-coded mapping of valid args for each ldap
group type; introspect the subclass to determine valid/invalid fields
2018-03-26 11:40:49 -04:00
Alan Rominger
4c2cff7a63 Merge pull request #1666 from AlanCoding/fix_workflow_vars_again
Ignore workflow survey passwords when not applicable
2018-03-26 11:17:32 -04:00
Alan Rominger
fe6755eca8 Merge pull request #1659 from AlanCoding/relaunch_creds
New credential passwords system on relaunch
2018-03-26 11:16:03 -04:00
Matthew Jones
bddb288ac1 Merge pull request #1352 from ansible/network_ui_3_3
Graphical UI for Network Inventory
2018-03-26 08:11:21 -07:00
John Mitchell
ac70945071 Merge pull request #1657 from jlmitch5/jobsNewListUi
implement new style jobs list in ui
2018-03-26 11:07:56 -04:00
Michael Abashian
e486b16706 Merge pull request #1662 from mabashian/1555-permissions-checkboxes
Fixed permissions multi-select deselect bug
2018-03-26 10:30:42 -04:00
Michael Abashian
b1e959bdaa Merge pull request #1671 from mabashian/t-1099-workflow-nodes
Fixed several workflow node bugs
2018-03-26 10:30:23 -04:00
AlanCoding
d91ce03652 removed unused settings (replaced by source_vars) 2018-03-26 10:19:17 -04:00
John Mitchell
01982e7eab utilize transation for instance groups jobs sub panels titles
and fix a few linting errors
2018-03-26 10:07:47 -04:00
AlanCoding
894eeee979 inventory source can_change rm credential check 2018-03-26 09:45:45 -04:00
Matthew Jones
f5252d9147 Merge pull request #1624 from theblazehen/devel
Add Rocket.Chat notification type
2018-03-26 06:41:48 -07:00
Jeandre Le Roux
c25d8a5d34 Fix rocket.chat notification test flake8
Signed-off-by: Jeandre Le Roux <theblazehen@theblazehen.com>
2018-03-26 15:13:33 +02:00
Christian Adams
8646aa8c34 Merge pull request #1673 from HNKNTA/devel
Fixed parentless function
2018-03-26 09:06:18 -04:00
HNKNTA
7ddbc49568 Fixed parentless function
Signed-off-by: HNKNTA <hnknta@gmail.com>
2018-03-25 18:33:46 +03:00
Jared Tabor
7979bc93fb Merge pull request #1105 from jaredevantabor/background-tabs
Fixes issue with sockets and XHR requests for backgrounded tabs
2018-03-23 16:18:25 -07:00
Jared Tabor
df60876bf3 Adds a debug function to turn on $log.debug 2018-03-23 16:17:24 -07:00
Ben Thomasson
cafc62bd20 Renames Persistence to NetworkingEvents and removes the persistance channel 2018-03-23 15:46:51 -07:00
John Mitchell
f3329c8cce fix instance groups sub jobs lists 2018-03-23 17:00:41 -04:00
Ben Thomasson
38eb2691a8 Updates models based on PR feedback from matburt et al.
* Moves topology_data to views
* Changes id to cid
* Changes pk to id
* Changes host_id and inventory_id to ForeignKeys
* Resets migrations for network_ui
* Cleans up old files
2018-03-23 17:00:29 -04:00
Jared Tabor
883545d4cb Fixes some bugs from PR feedback
* Fixes bug where new devices on the canvas weren't added to the search dropdown
* Fixes bug with closing the details panel
* Changes the fill color to white for remote-selected devices
* Fixes read-only mode by swapping out move controller for move read-only
* Updates range on the zoom widget
2018-03-23 17:00:28 -04:00
Ben Thomasson
8086906a43 Improves pagination unrolling based on jmcdermott's feedback
* Fixes pagination and fsm-diff
* Removes unused Array.extend
2018-03-23 17:00:28 -04:00
Jared Tabor
82ec0d4d4b Disables actions according to RBAC privilege
* Removes the toolbox if user doesn't have permission to edit
* Fixes the extra click that was identified with the context menu
* Adds new readonly version of the move FSM
* Adds an enhancement to debug directive to align the text better
* Disables the toolbox FSM if user doesn't have permission to edit
2018-03-23 17:00:27 -04:00
Ben Thomasson
96b3ebd31e Moves network_ui_test to an external repo 2018-03-23 17:00:27 -04:00
Jared Tabor
abb95fdad6 Cleans up the Network UI after PR feedback and UX feedback
-removes stale commented-out lines
-makes "unknown" type devices smaller on canvas
-moves "unknown" type device title underneath icon
-removes collapsed inventory toolbox
-changes "Delete" to "Remove"
-removes the "Close" button for "Cancel" on details panel
-changing Remove color to red
2018-03-23 17:00:27 -04:00
Ben Thomasson
0e32644a27 Check for missing environ in request 2018-03-23 17:00:27 -04:00
Ben Thomasson
174d0e610f Fixes double import of instanceGroups app 2018-03-23 17:00:26 -04:00
Ben Thomasson
297816b110 Adds CONTRIBUTING docs 2018-03-23 17:00:26 -04:00
Ben Thomasson
f8992e0edf Makes changes suggested by wwitzel3's review 2018-03-23 17:00:26 -04:00
Ben Thomasson
bcf8f0bd42 Resetting migrations for network_ui 2018-03-23 17:00:25 -04:00
Ben Thomasson
b9d4fc2bb9 Adds creating links from host vars from inventory
* Adds creating links from host vars
* Returns devices to the toolbox after they are removed from the canvas
2018-03-23 17:00:25 -04:00
Jared Tabor
66c351c60c Cleans up network UI code for 3.3
This removes features that were not selected for 3.3.

* Removes breadcrumb
* Removes "Jump To" panel and some of the hotkey panel items
* Removes Buttons in favor of Action Dropdown
* Removes chevrons
* Removes ActionIcon model
* Removes the Rename button on the context menu
* Makes details panel readonly
* Adds expand modal for extra vars
* Adds inventory copy function back to inventory list
* Sets cursor to visible
* Adds hide_menus
* Adds fix for mice that return large mousewheel deltas
2018-03-23 17:00:25 -04:00
Ben Thomasson
766bee3753 Refactors network_ui_test out of network_ui
* Separates test messages from application messages
* Removes test runner and groups, processes, and streams from network_ui
* Adds network_ui_test
* Fixes routing for network_ui_test
* Removes coverage_report tool from network_ui
* Fixes network_ui_test test workflow
* Sets width and height of the page during tests
2018-03-23 17:00:25 -04:00
Ben Thomasson
b29a605800 Cleans up feature set for 3.3
This removes the experimental features that were not selected
for 3.3 release.

* Removes dpath requirement
* Removes generated action_plugins
* Removes network UI v1 api
* Removes unused network management commands
* Removes network UI CLI client
* Removes templates
* Removes unused DataBinding models
* Removes obsolete test
* Removes unused admin and tests
* Removes experimental UndoPersistence, RedoPersistence, and auto-layout functions
* Removes API endpoints for network visualization
* Removes unused consumer routes
* Removes group, site, and rack features for 3.3
* Removes unused tables controller
* Removes undo/redo
* Removes group code and scale checks
2018-03-23 17:00:24 -04:00
Ben Thomasson
8d28748451 Updates UI CONTRIBUTING for 3.3
* Fixes links in CONTRIBUTING.md
* Moves CONTRIBUTING and UI design files to ui/client/src/network-ui
* Adds README.md for network_ui/designs
* Updates design
2018-03-23 17:00:24 -04:00
Ben Thomasson
f8d83638b0 Adds inventory tracking and templating to network UI groups and hosts.
* Adds group_id to Group table
* Adds inventory_group_id to Group table
* Adds creation of inventory hosts and groups from the network UI
* Changes network UI variables to be under awx key
* Fixes variables initial value
* Adds group membership association/disassociation
* Removes items from the inventory toolbar when loaded by a snaphot
* Adds nunjucks dependency to package.json
* Adds templating to hosts
* Adds templating for racks
* Adds site templating
* Adds group associations for sites
* Squashes migrations for network_ui
* Flake8 migrations
* Changes reserved field type to device_type, group_type, and process_type
* Allows blank values for all CharFields in network_ui models
* Changes reserved field type to device_type, group_type, and process_type
2018-03-23 17:00:23 -04:00
Ben Thomasson
b7848ab4f6 Adds breadcrumb to network UI
* Adds functionality for breadcrumb
* Changes the site icon to stay the same size
2018-03-23 17:00:23 -04:00
Ben Thomasson
a222fb5ebd Updates pipeline and FSM design and development tools
* Updates pipeline and FSM design for 3.4 features:
    group and read/write design features.
* Adds tool to copy layout from existing design
* Adds pipeline design
2018-03-23 17:00:23 -04:00
Jared Tabor
14ee6a8360 Adds editing to the host details form.
* Adds editing host vars, description, and name to the host details
form.
* Adds details panel FSM and updates to keybindings FSM
2018-03-23 17:00:22 -04:00
Ben Thomasson
6f3bf4fd1b Adds search field and jump-to a device UI.
Adds a search field in the network UI and a jump-to level menu. This
allows users to quickly find a device on the canvas or jump to a
certain mode/zoom-level.

Adds animation to smooth out the transition from the current viewport
to a viewport centered on the searched for device or zoom-level.

* Adds animation FSM and changes the 0 hot key to use it
* Adds jump to animation
* Adds search bar type ahead
* Adds jump animation to search and jump-to menus
* Adds keybinding FSM
* Updates the dropdown when devices are added/edit/removed
* Highlights the searched for host
2018-03-23 17:00:22 -04:00
Ben Thomasson
00a9283e32 Adds an API for network UI, action plugins, and API client
* Adds a simple DRF API for network-ui
* Moves network_ui api to v1_api
* Uses BaseSerializer for networking v1 api
* Adds v2 of the network API
* Uses standard AWX base classes for the network UI API
* Adds canvas prefix to network UI api URL names
* Adds ansible action plugins for automating network UI workflows
* Adds python client for the networking visualization API
2018-03-23 17:00:22 -04:00
Ben Thomasson
2736aecfb2 Adds context menus for group, racks, and sites
* Adds context menu for a rack, and adding more error handling for
    items that don't exist in Tower
* Adds context menu for sites
* Adds handler for showing details for links and interfaces
* Fixes the removed "watchCollection" in order to update details panel
* Removes the context menu when changing the scale of the canvas
* Adds delete context menu button, as well as refactoring the delete
    functionality to the network.ui.controller.js
* Updates delete functionality to delete nested groups/devices
    if the current_scale is set to site or rack icons
* Adds context menu to a group
* Hides rack/site title in top left of group, as well as centering
    labels on all icons
* Moves the context menu off screen when disabling it
* Adds unique name to hosts, routers, switches, and groups
* Makes the names of host/switch/router/group SVG elements so they update when
    the user updates the name of the SVG element
* Removing svg buttons and adding new html toolbar
* Adds panel for Jump To feature, along with basic functionality
* Adds Key dropdown for hotkeys and adding browser refresh hotkey
* Adds breadcrumb bar and making adjustments after feedback with UX
* Rearrages panels and adding some resize logic
* Fixes z-index of key-panel  and jump-to panel
* Adds white background to text underneath icons
* Makes all icons blue
* Changes sizes and colors of icons. Also made icon text background white
* Adjusts sizes of rack and site icons within group boundary
2018-03-23 17:00:21 -04:00
Ben Thomasson
7f0b23c357 Removes early experiments from network UI.
* Removing unused widgets app
* Removes dead code for rack from move
* Removes experimental touch support
* Removes unused Group corners function
* Removes experimental tables app
* Removes stencil and layers
* Removes status light and task status
* Removes configuration
* Removes unused PasteGroup message and handler
* Removes unused inventory clip path partial
* Removes old recordings
* Removes unused table messages
* Removes unused Task and Configuration models
2018-03-23 17:00:21 -04:00
Ben Thomasson
bf7f4ee1e1 Adds network UI test framework
This adds a test framework to drive UI tests from the client
instead of injecting events from the websocket.  Tests consist
of a pair of snapshots (before and after the test) and
a list of UI events to process.  Tests are run using a FSM
in the client that controls the resetting of state to the snapshot,
injecting the events into the UI, recording test coverage,
and reporting tests to the server.

* Adds design for event trace table
* Adds design for a coverage tracking table
* Adds models for EventTrace and Coverage
* Adds trace_id to recording messages
* Adds design for TopologySnapshot table
* Adds order to TopologySnapshot table
* Adds TopologySnapshot table
* Adds Snapshot message when recordings are started and stoppped
* Adds models for tracking test cases and test results
* Adds designs for a test runner FSM
* Updates test management commands with new schema
* Adds download recording button
* Adds models to track tests
* Adds ui test runner
* Adds id and client to TestResult design
* Adds id and client to TestResult
* Update message types
* Stores test results and code coverage from the test runner
* Adds tool to generate a test coverage report
* Adds APIs for tests and code coverage
* Adds per-test-case coverage reports
* Breaks out coverage for loading the modules from the tests
* Re-raises server-side errors
* Captures errors during tests
* Adds defaults for host name and host type
* Disables test FSM trace storage
* Adds support for sending server error message to the client
* Resets the UI flags, history, and toolbox contents between tests
* Adds istanbul instrumentation to network-ui
2018-03-23 17:00:21 -04:00
Ben Thomasson
eeaf7c257c Fixes #945 by removing the network_ui plugin 2018-03-23 17:00:20 -04:00
Jared Tabor
050f43e3bf Improves host details panel UI
* Hooks up the first two context menu buttons
* Makes the rename and details menu show up
    wherever the user's cursor's location
* Adds TopologyInventory and DeviceHost tables
* Adds design for host_id on the Device table
* Adds migrations for TopologyInventory
* Adds host_id to Device table
* Adds inventory_id and host_id tracking
* Auto-closes the right hand panel if focus is directed to the canvas.
* Retrieves the host details on inventory load.
* Adds back support for inventory and host_id tracking
* Adds host icon
* Changes rack icon to new icon
* Site icon replacement
* Fixes host icon "hitbox", and adding debug and construction
* Adds construction and debug lines for switch, router, rack, and site
* Adds some error handling for REST calls, as well as alert on
    host detail panel.
2018-03-23 17:00:20 -04:00
Ben Thomasson
1c1844d889 Reorganizes the network-ui code under awx/client/src/network-ui
* Moves network UI source to awx/client/src/network-ui
* Moves network ui partials to awx/ui/client/network-ui
* Renames widgets with suffix partial.svg
* Updates directives to use bundled partials
* Uses ~network-ui for loading UI component
2018-03-23 17:00:20 -04:00
Ben Thomasson
f6eecad25e Adds explicit channels between FSMs to add in tracing message flows.
* Adds channels between FSMs
* Adds FSMTrace model
* Adds FSMTrace storage and download

Channels between FSMs make the processing pipeline delegation explicit
and allow for better instrumentation to trace the state of the entire
pipeline including FSM state transitions and message flow through
the pipeline.  This feature is not turned on by default and is
only necessary for debugging or certain kinds of testing.
2018-03-23 17:00:19 -04:00
Jared Tabor
a1f639bc8f Adds host detail panel UI and improves toolbox UI
* Changes Layers' panel's default setting to not expanded
* Adds OffScreen2 state to handle the case where a toolbox is both offscreen and disabled
* Adds a collapsed view of the toolbox, as well as a model for ActionIcons
    which is a model whose purpose is to connect the button FSM with the
    chevron icons that are used on the toolbox.
* Adds action-icon directive
* Enables/disables the icons if they're not shown
* Fixes initial state of the toolboxes
* Creates context menu and context menu buttons in the network UI
* Adds extra vars to details panel on left hand side
2018-03-23 17:00:19 -04:00
Ben Thomasson
519983308a Adds content to CONTRIBUTING.md
* Adds SVG intro to CONTRIBUTING.md
* Add FSM intro
* Add rendered images of the FSM designs
* Adding example
* Adding links
* Adds details about the FSM design workflows
* Adds FSM state docs
* Adds event handler docs
* Adds details about FSMController
* Adds example of making an FSMController
* Adds details about messages, models, and message passing
* Adds models and messages to CONTRIBUTING.md
* Adds example to widget development
* Adds detail to the widget development example
* Add message type definitions
2018-03-23 17:00:19 -04:00
Ben Thomasson
809eafe9a9 Adds devserver support
* Adds support for webpack devserver
* Enable istanbul on network UI
* Enable capture and replay tests on the network ui
* Normalize mouse wheel events
* Fix missing trailing slash on hosts API
* Add Export YAML button
2018-03-23 17:00:18 -04:00
Ben Thomasson
2713ec2dd5 Adds Red Hat copyright notice 2018-03-23 17:00:18 -04:00
Jared Tabor
2a8ced5a5d Adds network UI shell wrapper
* Adds networking icons, state, and shell
* Adds network UI to the network UI shell.
* Removes jquery as a dependency of network-ui
* Fills the entire viewport with the network canvas and
    makes header panel and the right panel overlay on
    top of it
2018-03-23 17:00:18 -04:00
Ben Thomasson
09d461b1d0 Improves FSM design and adds tools to diff design and implementation.
* Resolves conflicts between designs and implementation
* Adding fsm_diff to network_ui/tools
* Add extract.js for FSM toolchain to network_ui
2018-03-23 17:00:17 -04:00
Ben Thomasson
56991552d2 Adds task status on the device
* Show task status on device for now

This shows the status of the last few tasks run on a device as a
green/red circle on the device icon.  This data live updates
from data emitted over the websocket.
2018-03-23 17:00:17 -04:00
Ben Thomasson
3f84ef69eb Adds facts processing for ansible_net_neighbors
* Adds logic for consuming ansible_net_neighbors facts

This consumes facts emitted from Ansible over a websocket to
Tower.  This allows consumers in network to process the facts and
emit messges to the network UI.  This requires a special callback
plugin to run in Tower to emit the messages into the websocket using
the python websocket-client library.
2018-03-23 17:00:17 -04:00
Ben Thomasson
6f1000cd94 Adds toolbox to network UI
* Calls API to get inventory
* Adds CopySite message
* Adds Toolbox and ToolboxItem model design
* Add Toolbox and ToolboxItem tables
* Sends toolbox items to client from server on connect
2018-03-23 17:00:17 -04:00
Ben Thomasson
c79ef60d8b Adds streams and processes for application design
Adds application level streams and process widgets to
model applications that run on networking devices or hosts.

* Changes Application to Process
* Adds StreamCreate and ProcessCreate messages
* Adds process id sequence to device
* Add serializers for streams and processes
2018-03-23 17:00:16 -04:00
Ben Thomasson
d153d5f907 Adds a type field to group to support racks and sites
* Add type support to Group
2018-03-23 17:00:16 -04:00
Ben Thomasson
9dc4e22fe6 Adds support for multiple view modes
Adds mulitple view modes based on zoom-level.  This allows for easy
drilling into a device for more detail or zooming-out for a overview.

* Adds support for multi-site and device modes
* Adds icons to remote device in device detail
* Adds site widget
* Adds link between sites
* Adds toolboxes for inventory, site, and applications
* Adds rack mode
* Adds UI for adding processes to devices
* Adds copy and paste support
* Adds streams
2018-03-23 17:00:16 -04:00
Ben Thomasson
8fb54efa8e Adds a tabular view of the topology data
The traditional network engineer workflow includes a diagram, a
spreadsheet, and the CLI.  This adds an experimental view of the
network topology data in a spreadsheet like table view.

* Adds angular-xeditable dependency for tables view.
* Add data binding models
* Add message transformations from table to topology formats
* Adding dependencies for tables view
2018-03-23 17:00:15 -04:00
Ben Thomasson
d0e402c39a Begins network-ui prototype integration into Tower UI.
* Moves network ui into a directive
* Adds awxNet prefix to network ui directives
* Adds a module to integrate the stand alone network UI with
    Tower UI.
* Adds reconnectingwebsocket to webpack bundle
* Adds configuration for webpack
* Moves ngTouch and hamsterjs to webpack vendor bundle
* Moves angular to network UI vendor bundle
* Adds ui-router dependency
* Changes CSS to BEM style
* Adds unique id sequences for devices and links on Topology and interfaces on Device
* Adds group widget with move, resize, delete, and edit label support
2018-03-23 17:00:15 -04:00
Ben Thomasson
640e687f3e Adds JSON and YAML export of the network topology.
Adds views that export the entire network topology as JSON and YAML.
2018-03-23 17:00:15 -04:00
Ben Thomasson
257cf6a7d7 Adds callback plugin for network_ui
The callback plugin for the network UI adds real-time
event streaming to the canvas from Ansible events.
2018-03-23 17:00:14 -04:00
Ben Thomasson
701150bd1a Adds configuration for the network-ui websocket
* Configures NGINX for the network-ui websocket.
* Configures supervisor.conf for network_ui websocket.
2018-03-23 17:00:14 -04:00
Ben Thomasson
48d801271c Imports prototype from ansible-network-ui
The ansible-network-ui prototype project builds a standalone Network UI
outside of Tower as its own Django application. The original prototype
code is located here:
https://github.com/benthomasson/ansible-network-ui.

The prototype provides a virtual canvas that supports placing
networking devices onto 2D plane and connecting those devices together
with connections called links.  The point where the link connects
to the network device is called an interface.  The devices, interfaces,
and links may all have their respective names.  This models physical
networking devices is a simple fashion.

The prototype implements a pannable and zoomable 2D canvas in using SVG
elements and AngularJS directives.   This is done by adding event
listeners for mouse and keyboard events to an SVG element that fills the
entire browser window.

Mouse and keyboard events are handled in a processing pipeline where
the processing units are implemented as finite state machines that
provide deterministic behavior to the UI.

The finite state machines are built in a visual way that makes
the states and transitions clearly evident.  The visual tool for
building FSM is located here:
https://github.com/benthomasson/fsm-designer-svg.   This tool
is a fork of this project where the canvas is the same.  The elements
on the page are FSM states and the directional connections are called
transitions.   The bootstrapping of the FSM designer tool and
network-ui happen in parallel.  It was useful to try experiemental
code in FSM designer and then import it into network-ui.

The FSM designer tool provides a YAML description of the design
which can be used to generate skeleton code and check the implementation
against the design for discrepancies.

Events supported:

* Mouse click
* Mouse scroll-wheel
* Keyboard events
* Touch events

Interactions supported:

* Pan canvas by clicking-and-dragging on the background
* Zooming canvas by scrolling mousewheel
* Adding devices and links by using hotkeys
* Selecting devices, interaces, and links by clicking on their icon
* Editing labels on devices, interfaces, and links by double-clicking on
  their icon
* Moving devices around the canvas by clicking-and-dragging on their
  icon

Device types supported:

* router
* switch
* host
* racks

The database schema for the prototype is also developed with a visual
tool that makes the relationships in the snowflake schema for the models
quickly evident.  This tool makes it very easy to build queries across
multiple tables using Django's query builder.

See: https://github.com/benthomasson/db-designer-svg

The client and the server communicate asynchronously over a websocket.
This allows the UI to be very responsive to user interaction since
the full request/response cycle is not needed for every user
interaction.

The server provides persistence of the UI state in the database
using event handlers for events generated in the UI.  The UI
processes mouse and keyboard events, updates the UI, and
generates new types of events that are then sent to the server
to be persisted in the database.

UI elements are tracked by unique ids generated on the client
when an element is first created.  This allows the elements to
be correctly tracked before they are stored in the database.

The history of the UI is stored in the TopologyHistory model
which is useful for tracking which client made which change
and is useful for implementing undo/redo.

Each message is given a unique id per client and has
a known message type.  Message types are pre-populated
in the MessageType model using a database migration.

A History message containing all the change messages for a topology is
sent when the websocket is connected.  This allows for undo/redo work
across sessions.

This prototype provides a server-side test runner for driving
tests in the user interface.  Events are emitted on the server
to drive the UI.  Test code coverage is measured using the
istanbul library which produces instrumented client code.
Code coverage for the server is is measured by the coverage library.

The test code coverage for the Python code is 100%.
2018-03-23 17:00:14 -04:00
mabashian
348de30a17 Fixed several workflow node bugs 2018-03-23 15:50:34 -04:00
chris meyers
cb7e17885f remove uneeded auth ldap settings
* I had thought that setting the settings was required. But carefully
selected defaults for the settings is the correct way to deal with
errors I was seeing early in developing this feature.
2018-03-23 15:41:08 -04:00
Jared Tabor
8643972064 Fixes issue with sockets and XHR requests for backgrounded tabs
adjusts toggling based on API setting and doesn't toggle for job stdout page
2018-03-23 12:40:34 -07:00
AlanCoding
f6e507ad12 add API setting for UI live updates
include context data

update help text
2018-03-23 12:40:31 -07:00
John Mitchell
babad0b868 move all jobs views to using new view 2018-03-23 14:53:20 -04:00
AlanCoding
7ea9575e78 ignore workflow survey passwords when not applicable 2018-03-23 13:01:45 -04:00
AlanCoding
5fe63894d5 new credential passwords system on relaunch 2018-03-23 12:44:52 -04:00
Shane McDonald
1595947ae2 Merge pull request #1663 from jakemcdermott/fix-docker-installer-paths
update reference to role file path to work with installer roles dir
2018-03-23 12:43:54 -04:00
Jake McDermott
4a8f24becc update reference to role file path to work with roles dir 2018-03-23 12:43:13 -04:00
Ryan Petrello
35e38760aa properly sanitize module arguments with no_log (like uri:password)
this will _not_ sanitize playbooks that have secrets hard-coded *in* the
playbook - for that, people will need to use Vault or a variable/lookup

see: https://github.com/ansible/tower/issues/1101
see: https://github.com/ansible/awx/issues/1633
2018-03-23 11:15:37 -04:00
mabashian
bf142fa434 Fixed permissions multi-select deselect bug 2018-03-23 10:51:44 -04:00
Ryan Petrello
07680dd7c0 Merge pull request #1652 from ryanpetrello/fix-500
send job notification templates _after_ all events have been processed
2018-03-23 10:51:06 -04:00
John Mitchell
95f80ce512 implement new style jobs list in ui 2018-03-23 09:35:41 -04:00
Michael Abashian
e7cfe1e0b6 Merge pull request #1640 from mabashian/1561-survey-multi-select
Fixed bug on non-required multiple choice survey questions
2018-03-23 09:31:56 -04:00
Michael Abashian
224d996b9c Merge pull request #1622 from mabashian/169-prompt-cleanup
Propagate new launch/relaunch logic across the app
2018-03-23 09:31:33 -04:00
Ryan Petrello
61aafe15d6 fix busted shippable builds 2018-03-22 16:56:04 -04:00
mabashian
7a4bc233f6 Pass job into the relaunch component rather than pull it from the parent. Added launch template component, use it on the templates lists. 2018-03-22 16:14:54 -04:00
Shane McDonald
caf576cac0 Merge pull request #1655 from shanemcd/devel
Move installer roles into roles directory
2018-03-22 14:39:03 -04:00
Shane McDonald
84cd933702 Move installer roles into roles directory
Signed-off-by: Shane McDonald <me@shanemcd.com>
2018-03-22 14:34:03 -04:00
mabashian
c3b32e2a73 Cleaned up awRequireMultiple and fixed broken survey question error messaging 2018-03-22 11:58:47 -04:00
Shane McDonald
0d86e646ec Merge pull request #1100 from rooftopcellist/fix_upgrade_python_saml24
fix_python_saml24_update
2018-03-22 11:48:24 -04:00
adamscmRH
ad37f71af4 fix_python_saml24_update 2018-03-22 11:26:17 -04:00
Jeandre Le Roux
0525df595e Add unit test for rocket.chat notifications
Signed-off-by: Jeandre Le Roux <theblazehen@theblazehen.com>
2018-03-22 16:11:03 +02:00
Ryan Petrello
f59f47435b send job notification templates _after_ all events have been processed
see: https://github.com/ansible/awx/issues/500
2018-03-22 09:30:41 -04:00
Chris Meyers
ddf000e8e7 Merge pull request #1643 from chrismeyersfsu/fix-tower_special_group
do not allow tower group delete or name change
2018-03-22 08:06:03 -04:00
chris meyers
305ef6fa7e do not allow tower group delete or name change
* DO allow policy changes and other attribute changes
2018-03-22 08:05:06 -04:00
Chris Meyers
3446134501 Merge pull request #1646 from chrismeyersfsu/fix-kombu_unicode
use non-unicode queue names
2018-03-21 21:59:05 -04:00
Bill Nottingham
eae85e803e Merge pull request #1644 from wenottingham/botbotbot
update team map
2018-03-21 19:35:58 -04:00
mabashian
8d04be0fc8 Fixed unit test failures 2018-03-21 19:22:08 -04:00
chris meyers
e0803b9f08 use non-unicode queue names
* Use unicode InstanceGroup and queue names up until the point we
actually create the queue
* kombu add_consumers returns a dict with a value that contians the
passed in queue name. Trouble is, the returned dict value is a string
and not a unicode string and this results in an error.
2018-03-21 16:50:07 -04:00
Chris Meyers
724812e87c Merge pull request #1637 from chrismeyersfsu/fix-instance_removed_from_group
handle instance group names unicode
2018-03-21 15:47:00 -04:00
Bill Nottingham
45240a6bf0 update team map 2018-03-21 15:46:50 -04:00
mabashian
0cadea1cb5 Fixed bug preventing the user from ignoring a non-required multi-select survey question on launch 2018-03-21 14:55:38 -04:00
Alan Rominger
b3e15f70cb Merge pull request #1612 from AlanCoding/token_no
Make user_capabilities False for read tokens
2018-03-21 14:45:19 -04:00
Ryan Petrello
a13ddff81a Merge pull request #1627 from aperigault/fix_deprecation
Replace deprecated -U option by --become-user
2018-03-21 14:32:13 -04:00
Bill Nottingham
88ef889cf1 Merge pull request #1634 from wenottingham/winrm-rf
Cherry-pick fix for WinRM listener to AzureRM inventory script.
2018-03-21 14:13:08 -04:00
chris meyers
91bfed3d50 handle instance group names unicode 2018-03-21 13:41:48 -04:00
AlanCoding
4f1f578fde make user_capabilities False for read tokens 2018-03-21 13:14:14 -04:00
Shane McDonald
f8b5318206 Merge pull request #1093 from rooftopcellist/upgrade_python_saml
upgrade python-saml for CVE fix
2018-03-21 12:27:35 -04:00
adamscmRH
c3842b6bf9 upgrade python-saml for CVE fix 2018-03-21 12:07:18 -04:00
Ryan Petrello
1a542c5e06 Merge pull request #1620 from ryanpetrello/dynamic-autoscale
dynamically set worker autoscale max_concurrency based on system memory
2018-03-21 11:52:16 -04:00
Marliana Lara
6e11b5b9c8 Merge pull request #1557 from marshmalien/feat/final_granular_permission_types
New RBAC roles at the Org level
2018-03-21 11:35:15 -04:00
Marliana Lara
4106e496df Merge pull request #1574 from marshmalien/fix/capacity_adjustment_value
Add capacity adjuster slider label
2018-03-21 11:35:03 -04:00
Ryan Petrello
6a96e6a268 dynamically set worker autoscale max_concurrency based on system memory 2018-03-21 11:10:48 -04:00
mabashian
bee7148c61 Addressed jshint errors 2018-03-21 10:59:13 -04:00
Marliana Lara
2ae02fda82 Update pr based on feedback 2018-03-21 10:55:00 -04:00
Marliana Lara
01d35ea9c0 Show organizations based on more granular RBAC roles 2018-03-21 10:54:59 -04:00
Marliana Lara
c156a0af99 Add capacity adjustment slider label 2018-03-21 10:53:16 -04:00
Bill Nottingham
531e5b5137 Cherry-pick fix for WinRM listenr to AzureRM inventory script.
(ref: https://github.com/ansible/ansible/pull/37499/)
2018-03-21 10:46:30 -04:00
mabashian
f0ff578923 Cleanup linting errors 2018-03-21 10:27:48 -04:00
chris meyers
1c578cdd74 validate group type params 2018-03-21 09:13:08 -04:00
chris meyers
17795f82e8 more parameters 2018-03-21 09:13:08 -04:00
chris meyers
e3c362956d add ldap group type like posixGroupType
* Adds pattern to easy add django-auth-ldap group types classes and to
pass parameters via AUTH_LDAP_GROUP_TYPE_PARAMS
* Adds new group type PosixUIDGroupType that accepts the attribute,
ldap_group_user_attr, on which to search for the user(s) in the group.
2018-03-21 09:13:08 -04:00
AlanCoding
001fa634aa include related Jobs in group del protection 2018-03-21 08:05:16 -04:00
Antony PERIGAULT
3adcdb43ad Replace deprecated -U option by --become-user 2018-03-21 12:28:27 +01:00
Jeandre Le Roux
fd12c44ada Add Rocket.Chat notification type
Summary: Add Rocket.Chat notification type
Issue type: Feature Pull Request
Component: Notifications

Signed-off-by: Jeandre Le Roux <theblazehen@theblazehen.com>
2018-03-21 10:02:50 +02:00
Bill Nottingham
e58038b056 Merge pull request #1623 from cvick/patch-1
Added a space before closing quote to fix spelling
2018-03-20 23:25:12 -04:00
Chris Vick
fca1e7028f Added a space before closing quote to fix spelling
Without the space before the lines closing quote, 'or' and 'other' get concatenated to 'orother' in the tooltip
2018-03-20 19:36:37 -07:00
Wayne Witzel III
f4e57e2906 Merge pull request #1608 from wwitzel3/devel
System Setting for Orgainization User/Team permissions.
2018-03-20 17:17:49 -04:00
mabashian
2e858790db Propagate launch/relaunch logic across the app. Removed some old launch related factories. 2018-03-20 15:53:21 -04:00
Alan Rominger
8056ac5393 Delay import of freeze to make tests run (#1617)
* Delay import of freeze to make tests run

* fix flake8 error
2018-03-20 11:58:11 -04:00
Matthew Jones
6419339094 Merge pull request #1570 from rooftopcellist/authorization_flow_docs
add authorization grant to docs
2018-03-20 07:26:10 -07:00
Matthew Jones
3ee8b3b514 Merge pull request #1613 from EagleIJoe/patch-1
Corrected alternate dns servers entries in docker-compose template
2018-03-20 07:24:31 -07:00
Wayne Witzel III
d7f26f417d Reword help text for manage org auth 2018-03-20 07:31:08 -04:00
Alan Rominger
30fb4076df Merge pull request #1569 from AlanCoding/relaunch_survey
Allow normal users to relaunch jobs with survey answers
2018-03-20 07:14:09 -04:00
Matthew Jones
c0661722b6 Merge pull request #1523 from paihu/slack-color-notification
support slack color notification #1490
2018-03-19 18:05:28 -07:00
Martin Adler
ca7b6ad648 Corrected alternate dns servers entries
As lstrip_blocks: True was added, this broke the formating when adding alternate DNS servers within the template. Removing the extra white space removals within the if and endif statements fixed the resulting yml formating.
2018-03-19 21:08:52 +01:00
Wayne Witzel III
d5564e8d81 Fix user capabilities when MANAGE_ORGANIZATION_AUTH is disabled 2018-03-19 15:16:54 -04:00
Wayne Witzel III
a9da494904 switch to single toggle and change name 2018-03-19 14:45:52 -04:00
John Mitchell
a9e13cc5f4 Merge pull request #1580 from jlmitch5/usersAppCrudUi
implement users tokens sub list
2018-03-19 13:23:29 -04:00
Wayne Witzel III
771108e298 Protect team assignment for the roles access point 2018-03-19 12:10:13 -04:00
Wayne Witzel III
eb3b518507 Add Organization User/Team toggle to UI 2018-03-19 11:25:14 -04:00
Wayne Witzel III
33ac8a9668 System wide toggle for org admin user/team abilities 2018-03-19 11:24:36 -04:00
Ryan Petrello
2b443b51eb Merge pull request #1606 from ryanpetrello/uwsgi-top
add uwsgitop as a dependency
2018-03-19 10:58:22 -04:00
Ryan Petrello
918f372c20 add uwsgitop as a dependency
see: https://github.com/ansible/ansible-tower/issues/7966
2018-03-19 08:53:30 -04:00
Matthew Jones
681918be9a Merge pull request #1598 from ryanpetrello/pin-boto-core
pin botocore to avoid dependency hell re: latest python-dateutil
2018-03-17 13:08:44 -07:00
Bill Nottingham
2780cd0d4c Merge pull request #1601 from wenottingham/following-a-new-path
Just set ANSIBLE_SSH_CONTROL_PATH_DIR, and don't worry about the socket file name.
2018-03-16 22:35:26 -04:00
John Mitchell
cbc20093d7 move users tokens to features folder 2018-03-16 17:15:28 -04:00
Bill Nottingham
6fc4274c68 Just set ANSIBLE_SSH_CONTROL_PATH_DIR, and don't worry about the socket file name.
Ansible itself (since 2.3) has code to have a shorter hashed control path socket name.
2018-03-16 16:56:45 -04:00
Ryan Petrello
4f585dd09e pin botocore to avoid dependency hell re: latest python-dateutil
boto decided to pin python-dateutil on a version _lower than_ what we
need for the TZID= bug fix:
90d7692702 (diff-b4ef698db8ca845e5845c4618278f29a)
2018-03-16 16:08:03 -04:00
John Mitchell
8babac49a6 update users token crud list to utilize string file 2018-03-16 15:56:30 -04:00
Matthew Jones
8aa7e4692d Merge pull request #1596 from ansible/jlmitch5-patch-3
update .gitignore to include tower license dir
2018-03-16 12:36:00 -07:00
John Mitchell
cf20943434 update .gitignore to include tower license dir 2018-03-16 15:33:18 -04:00
Bill Nottingham
d5d2858626 Merge pull request #1591 from wenottingham/bad-date
Bump copyright date.
2018-03-16 15:11:10 -04:00
Bill Nottingham
52599f16ad Bump copyright date.
We don't need to do this at the source code level, but we should do it for the app as a whole.
2018-03-16 14:57:08 -04:00
Ryan Petrello
a1f15362ab Merge pull request #1575 from aperigault/fix_nginx_upstreams
Fix nginx upstreams
2018-03-16 14:53:48 -04:00
Alan Rominger
1413659f5f Merge pull request #1593 from AlanCoding/fix_processed
Fix bug with non-event model
2018-03-16 14:53:05 -04:00
AlanCoding
bbbb7def0a fix bug with non-event model 2018-03-16 14:27:36 -04:00
Marliana Lara
85a95c8cb8 Merge pull request #1577 from marshmalien/fix/empty_ig_list_results_error
Fix error where list directive requires results attribute
2018-03-16 13:52:34 -04:00
adamscmRH
5f6a8ca2c0 add authorization grant to docs 2018-03-16 12:21:22 -04:00
Alan Rominger
75dd8d7d30 Merge pull request #1587 from AlanCoding/more_event_blocking
Block deletion of resources with unprocessed events
2018-03-16 11:26:33 -04:00
AlanCoding
66108164b9 remove unnecessary mock 2018-03-16 10:55:48 -04:00
AlanCoding
69eccd3130 move ACTIVE_STATES to constants 2018-03-16 10:31:41 -04:00
AlanCoding
7881c921ac block deletion of resources w unprocessed events 2018-03-16 10:14:28 -04:00
Wayne Witzel III
16aa3d724f Merge pull request #1586 from wwitzel3/devel
Moved RelatedJobMixin impl to Project instead of ProjectUpdate
2018-03-16 09:58:34 -04:00
Wayne Witzel III
6231742f71 Move RelatedJob mixin to Project 2018-03-16 09:42:32 -04:00
Wayne Witzel III
c628e9de0a Filter active jobs by WFT/JT 2018-03-16 09:32:42 -04:00
Wayne Witzel III
c54d9a9445 Fix query using self -> self.project and fix imports 2018-03-16 09:24:46 -04:00
Wayne Witzel III
f594f62dfc Project needs to expose all of its ProjectUpdate jobs in an active state 2018-03-16 09:12:20 -04:00
Chris Meyers
0689cea806 Merge pull request #1572 from chrismeyersfsu/fix-instance_removed_from_group
handle unicode things in task logger
2018-03-15 16:25:13 -04:00
Chris Meyers
0cf1b4d603 Merge pull request #1535 from chrismeyersfsu/fix-protect_tower_group
prevent tower group delete and update
2018-03-15 16:02:36 -04:00
chris meyers
1f7506e982 prevent tower group delete and update
* related to https://github.com/ansible/ansible-tower/issues/7931
* The Tower Instance group is special. It should always exist, so
prevent any delete to it.
* Only allow super users to associate/disassociate instances the 'tower'
instance group.
* Do not allow fields of tower instance group to be changed.
2018-03-15 15:23:06 -04:00
Chris Meyers
2640ef8b1c Merge pull request #1536 from chrismeyersfsu/fix-protect_instance_groups
prevent instance group delete if running jobs
2018-03-15 14:57:45 -04:00
John Mitchell
e7a0bbb5db implement users tokens sub list 2018-03-15 14:53:49 -04:00
chris meyers
5d5d8152c5 prevent instance group delete if running jobs
* related to https://github.com/ansible/ansible-tower/issues/7936
2018-03-15 14:25:49 -04:00
Matthew Jones
3928f536d8 Merge pull request #1571 from matburt/fixing_cluster_resources
Fixing some issues defining resource requests in openshift and k8s
2018-03-15 11:20:56 -07:00
Marliana Lara
84904420ad Pass results attr to list directive from instance groups list 2018-03-15 14:13:33 -04:00
chris meyers
2ea0b31e2b handle unicode things in task logger
Related to https://github.com/ansible/ansible-tower/issues/7957

* Problem presented itself as Instances falling out of Instance Groups.
This was due to the cluster membership policy decider erroring out on a
logger message with unicode.
* Fixed up potential other unicode logger unicode issues in tasks.py
2018-03-15 14:04:39 -04:00
Antony PERIGAULT
8cf1c1a180 Fix nginx configuration to avoid ipv6 resolutions errors 2018-03-15 17:54:51 +01:00
Matthew Jones
192dc82458 Update documentation for default pod resource requests
Including information on how to override the default resources
2018-03-15 12:01:02 -04:00
Matthew Jones
3ba7095ba4 Fixing some issues defining resource requests in openshift and k8s
* Allow overriding all container resource requests by setting defaults/
* Fix an issue where template vars were reversed in the deployment config
* Remove `limit` usage to allow for resource ballooning if it's available
* Fix type error when using templated values in the config map for resources
2018-03-15 12:00:53 -04:00
AlanCoding
43aef6c630 allow normal users to relaunch jobs w survey answers 2018-03-15 07:43:03 -04:00
Michael Abashian
597874b849 Merge pull request #1489 from mabashian/169-workflow-nodes
Implemented new workflow node prompting
2018-03-14 16:58:35 -04:00
mabashian
9873bab451 Removed unused/commented code 2018-03-14 16:26:02 -04:00
Matthew Jones
cec77964ac Merge pull request #1563 from matburt/container_cluster_capacity
Implement container-cluster aware capacity determination
2018-03-14 12:06:25 -07:00
Christian Adams
2abf4ccf3b Merge pull request #1562 from rooftopcellist/python_saml_upgrade
add xmlsec flag to docker installs
2018-03-14 14:53:26 -04:00
Matthew Jones
b0cf4de072 Implement container-cluster aware capacity determination
* Added two settings values for declaring absolute cpu and memory
  capacity that will be picked up by the capacity utility methods
* installer inventory variables for controlling the amount of cpu and
  memory container requests/limits for the awx task containers
* Added fixed values for cpu and memory container requests for other
  containers
* configmap uses the declared inventory variables to define the
  capacity inputs that will be used by AWX to correspond to the same
  inputs for requests/limits on the deployment.
2018-03-14 14:35:45 -04:00
Shane McDonald
2af085e1fe Merge pull request #1552 from jffz/devel
Add ca_trust_dir to local docker installations
2018-03-14 14:32:55 -04:00
adamscmRH
8d460490c1 add xmlsec flag to docker installs 2018-03-14 14:28:35 -04:00
John Mitchell
5eed816c4d Merge pull request #1558 from ansible/jlmitch5-patch-2
encode username and password when sending login POST from ui
2018-03-14 11:29:34 -04:00
John Mitchell
17cdbef376 encode username and password when sending login POST from ui
fixes #1553
2018-03-14 11:12:50 -04:00
Alexander Bauer
709cb0ae2b fixup! Add local_docker facility for bind-mounting ca-trust 2018-03-14 10:52:36 -04:00
Alexander Bauer
db8df5f724 Add local_docker facility for bind-mounting ca-trust
This implements one possible solution for #411, but does not solve it for
Kubernetes or Openshift installations.

# Conflicts:
#	installer/inventory
2018-03-14 10:52:36 -04:00
Alan Rominger
5c0a52df16 Merge pull request #1533 from AlanCoding/count_events
Track emitted events on model
2018-03-14 10:30:43 -04:00
John Mitchell
ea5ab2df7f Merge pull request #1453 from jlmitch5/licenseInSettingsUi
[Tower only] Make pendo license settings opt out whenever license is added
2018-03-14 10:26:08 -04:00
jeff
4fa0d2406a Remove unneeded jinja endif 2018-03-14 15:16:26 +01:00
Alan Rominger
92b8fc7e73 Merge pull request #1554 from AlanCoding/poly_who
fix bugs with UJT optimizations
2018-03-14 09:11:57 -04:00
Matthew Jones
63f0082e4d Merge pull request #1543 from matburt/k8s_helm_instructions
Adding information on Kubernetes RBAC considerations for Helm
2018-03-14 05:52:12 -07:00
AlanCoding
5170fb80dc fix bugs with UJT optimizations 2018-03-14 08:19:53 -04:00
AlanCoding
04a27d5b4d Namechange events_processed -> event_processing_finished
from PR review, also adding tests to assert that the
value is passed from the stdout_handle to the UnifiedJob
object on finalization of job run in tasks.py
2018-03-14 07:53:04 -04:00
AlanCoding
b803a6e557 Track emitted events on model 2018-03-14 07:53:02 -04:00
Alan Rominger
0db584e23e Merge pull request #1530 from AlanCoding/inv_env_vars
More restrictive inventory env vars management
2018-03-14 07:23:42 -04:00
jeff
f9f91ecf81 Add ca_trust_dir to task image 2018-03-14 11:41:10 +01:00
jeff
aca74d05ae Add 'ca_trust_dir' variable to allow Custom CA sharing between host and containers 2018-03-14 11:40:56 +01:00
Matthew Jones
b646e675d6 Merge pull request #1544 from matburt/sorting_region_choices
Sort cloud regions in a stable way
2018-03-13 18:02:31 -07:00
Matthew Jones
4a5f458a36 Merge pull request #1542 from matburt/adding_more_extra_vars
Adding more helpful job extra vars
2018-03-13 17:53:44 -07:00
John Mitchell
04bc044340 Merge pull request #1437 from jlmitch5/appCrudUi
implements application crud ui
2018-03-13 18:07:10 -04:00
John Mitchell
c65342acc9 make call to pendo setting and set check box based on that when license already exists 2018-03-13 15:41:45 -04:00
Matthew Jones
acde2520d0 Sort cloud regions in a stable way
* All comes first
* Then US regions
* Then all other regions alphabetically
2018-03-13 15:31:28 -04:00
Wayne Witzel III
4b27b05fd2 Merge pull request #1541 from wwitzel3/devel
Fix member_role parent to include credential_admin_role
2018-03-13 13:52:58 -04:00
Matthew Jones
dcf0b49840 Adding information on Kubernetes RBAC considerations for Helm 2018-03-13 13:48:10 -04:00
John Mitchell
f8c6187007 add back in comments describing license type payload 2018-03-13 13:34:03 -04:00
John Mitchell
d9f5eab404 add separator above checkbox 2018-03-13 13:34:03 -04:00
John Mitchell
8b10d64d73 update code formatting based on feedback 2018-03-13 13:34:02 -04:00
John Mitchell
2b4a53147e turn pendo tracking off in settings when checkbox is unchecked 2018-03-13 13:34:02 -04:00
John Mitchell
5f4f4a2fb9 make pendo license settings opt out whenever license is added 2018-03-13 13:34:00 -04:00
Matthew Jones
45ad94f057 Adding more helpful job extra vars
* Adds email, first name, last name as extra vars to job launches
* Remove old ad-hoc command extra vars population... use our
  base-class method instead
2018-03-13 13:33:54 -04:00
Wayne Witzel III
db38cf8f93 Fix member_role parent to include credential_admin_role 2018-03-13 12:20:40 -04:00
Alan Rominger
dcae4f65b5 Merge pull request #1330 from AlanCoding/capable_of_anything
New copy fields, clean up user_capabilities logic
2018-03-13 12:05:45 -04:00
paihu
dfea3a4b95 fix: broken backward compatibility
fix: param hex_color isn't optional

Signed-off-by: paihu <paihu_j@yahoo.co.jp>
2018-03-13 18:04:47 +09:00
John Mitchell
9d6fab9417 update edit controller to PUT app instead of POST
remove old applications tokens code
2018-03-12 17:40:08 -04:00
Christian Adams
f995b99af6 Merge pull request #1531 from rooftopcellist/application_description
add description to app serializer
2018-03-12 17:19:54 -04:00
Chris Meyers
724ca23685 Merge pull request #1534 from chrismeyersfsu/fix-4_job_limit
autoscale celery up to 50 workers
2018-03-12 15:45:01 -04:00
chris meyers
a4859a929c autoscale celery up to 50 workers 2018-03-12 15:36:15 -04:00
adamscmRH
91214aa899 add description to app serializer 2018-03-12 15:07:59 -04:00
John Mitchell
80db90b34c reduce delete prompting cruft for app ui 2018-03-12 14:35:03 -04:00
AlanCoding
3566140ecc more restrictive inventory env vars management 2018-03-12 13:35:22 -04:00
John Mitchell
3cf447c49b remove N_ dependency in favor strings files 2018-03-12 13:31:19 -04:00
John Mitchell
a22f1387d1 adjust user tokens list labeling 2018-03-12 13:31:19 -04:00
John Mitchell
8a28d7c950 remove permissions subview code from applications ui crud 2018-03-12 13:31:18 -04:00
John Mitchell
8031337114 add applications.edit.organization route 2018-03-12 13:31:18 -04:00
John Mitchell
8d2c0b58e1 remove unnecessary conditional 2018-03-12 13:31:18 -04:00
John Mitchell
f4ad9afc5e add app crud ui 2018-03-12 13:31:18 -04:00
Marliana Lara
c19bb79587 Merge pull request #1499 from marshmalien/style/display_invalid_items
Add border between invalid and active template flags
2018-03-12 12:37:42 -04:00
Ryan Petrello
6d9b386727 Merge pull request #1529 from ryanpetrello/new-dateutil
bump python-dateutil to latest
2018-03-12 12:34:02 -04:00
Ryan Petrello
44adab0e9e bump python-dateutil to latest
this change provides support for numerous bug fixes, along with
support for parsing TZINFO= from rrule strings

related: https://github.com/ansible/ansible-tower/issues/823
related: https://github.com/dateutil/dateutil/issues/614
2018-03-12 12:20:03 -04:00
mabashian
8aa9569074 In the JT form, moved options from its own line to in-line 2018-03-12 10:56:36 -04:00
mabashian
982b83c2d3 Fixed several workflow prompting and edge type bugs 2018-03-12 10:50:33 -04:00
Matthew Jones
346c9fcc8a Merge pull request #1514 from wenottingham/a-period-piece
Add some periods.
2018-03-12 07:40:16 -07:00
Matthew Jones
eaff7443d2 Merge pull request #1522 from therealmaxmouse/patch-1
Update INSTALL.md
2018-03-12 07:39:48 -07:00
Matthew Jones
8a9397a997 Merge pull request #1528 from jffz/devel
Fix project_data_dir templating for local_docker install
2018-03-12 07:37:11 -07:00
jeff
4972755ccb Fix project_data_dir templating for local_docker install 2018-03-12 14:50:44 +01:00
Ryan Petrello
6d43b8c4dd Merge pull request #1527 from ryanpetrello/oauth2-filter
restrict API filtering on oauth-related fields
2018-03-12 09:43:05 -04:00
Ryan Petrello
a61187e132 restrict API filtering on oauth-related fields
related: https://github.com/ansible/awx/issues/1354
2018-03-12 09:16:37 -04:00
paihu
9b5e088d70 support slack color notification #1490
Signed-off-by: paihu <paihu_j@yahoo.co.jp>
2018-03-12 14:14:44 +09:00
therealmaxmouse
54ae039b95 Update INSTALL.md
fixing typo
2018-03-11 11:45:35 -04:00
Bill Nottingham
7b2b71e3ef ... update string in tests as well. 2018-03-09 17:49:46 -05:00
Bill Nottingham
fb05eecee0 Add some periods. 2018-03-09 17:23:52 -05:00
Ryan Petrello
dcab97f94f Merge pull request #1504 from ryanpetrello/oauth2-swagger
properly categorize OAuth2 endpoints for swagger autogen
2018-03-09 15:27:02 -05:00
Ryan Petrello
397b9071a6 properly categorize OAuth2 endpoints for swagger autogen 2018-03-09 15:07:50 -05:00
Shane McDonald
7984bd2824 Merge pull request #1493 from jffz/devel
Fix for dns and dns_search templating
2018-03-09 12:52:10 -05:00
Marliana Lara
6f7cb0a16e Add border between invalid and active indicators 2018-03-09 12:21:46 -05:00
Marliana Lara
bfbbb95256 Merge pull request #1475 from marshmalien/feat/style_upgrade_page
Style migrations-pending page
2018-03-09 11:47:34 -05:00
Marliana Lara
882ed4d05a Merge pull request #1497 from marshmalien/feat/display_invalid_items_onPrompt
Denote invalid template when no inventory and no prompt-for-inventory
2018-03-09 10:38:20 -05:00
Christian Adams
cee12c4e6c Merge pull request #1378 from rooftopcellist/no_patch_app
disallow changing token-app
2018-03-09 10:33:24 -05:00
Marliana Lara
c2a3e82d29 Check Inventory ask_inventory_on_launch value when verifying template validity 2018-03-09 10:08:39 -05:00
Chris Meyers
181af03ab9 Merge pull request #1495 from chrismeyersfsu/fix-celery_rollback
more celery rollback
2018-03-09 09:31:31 -05:00
chris meyers
e2ed1542e6 more celery rollback
* Setting reload code calls a celery 4.x method signature. This changes
it back to a 3.x safe call.
2018-03-09 09:27:09 -05:00
jffz
ca27dee4fc Fix dns and dns_search templating
Fix templating for dns and dns_search entries for both `awx_web` and `awx_task` images.

Multiple entries were templated in a oneliner style while docker-compose wanted them in a list style.
2018-03-09 11:04:26 +01:00
mabashian
c98e7f6ecd Implemented workflow node prompting 2018-03-08 18:45:28 -05:00
Christian Adams
8a25342ce5 Merge pull request #1373 from rooftopcellist/oauth_doc_csrf
update docs
2018-03-08 18:15:04 -05:00
Alan Rominger
b41d9c4620 Merge pull request #1470 from AlanCoding/mo_exceptions
Include stack trace for delete_inventory logs
2018-03-08 17:18:40 -05:00
adamscmRH
91c0f2da6f simplifies detail serializer 2018-03-08 14:55:25 -05:00
Matthew Jones
b11b1acc68 Update middleware warning for latest minor version 2018-03-08 12:54:26 -05:00
adamscmRH
9b195bc80f fix oauth docs 2018-03-08 12:44:53 -05:00
adamscmRH
fd7c078a8b update docs 2018-03-08 12:10:29 -05:00
adamscmRH
06bacd7bdc add serializer for token detail 2018-03-08 12:03:50 -05:00
Marliana Lara
6f23147d98 Style migrations/pending page 2018-03-08 11:47:59 -05:00
Alan Rominger
3605dbfd73 Merge pull request #1472 from AlanCoding/more_deps
Add shade back into AWX requirements
2018-03-08 11:04:20 -05:00
Michael Abashian
599d84403b Merge pull request #1425 from mabashian/169-credentials
Added add/replace credential validation on jt launch and schedule
2018-03-08 10:57:27 -05:00
AlanCoding
4a01805a19 add shade back into AWX requirements
Last round of dependency updates showed that AWX
depended on packages which came implicitly from shade
decorator is added as an explicit dependency
and all of the rest of shade requirements are
added back in here.
2018-03-08 10:32:19 -05:00
Michael Abashian
c580146c77 Merge branch 'devel' into 169-credentials 2018-03-08 10:03:29 -05:00
mabashian
ce3dc40649 Edit schedule credential prompting code cleanup 2018-03-08 09:58:31 -05:00
Shane McDonald
5bf2e00d24 Merge pull request #1471 from shanemcd/devel
Fix container boots on AppArmor protected systems
2018-03-08 09:44:33 -05:00
Shane McDonald
02102f5ba8 Fix container boots on AppArmor protected systems
Link https://github.com/ansible/awx/issues/1297

Signed-off-by: Shane McDonald <me@shanemcd.com>
2018-03-08 09:41:04 -05:00
Shane McDonald
2861397433 Set imagePullPolicy to Always
Not sure why we werent doing this before.
2018-03-08 09:41:04 -05:00
AlanCoding
54a68da088 include stack trace for delete_inventory logs 2018-03-08 08:30:59 -05:00
Alan Rominger
044b85ce7a Merge pull request #1415 from AlanCoding/depgrades
Dependency Upgrades
2018-03-08 08:29:46 -05:00
Michael Abashian
b970452950 Merge pull request #1441 from marshmalien/feat/display_invalid_items
Denote invalid job templates and scheduled jobs
2018-03-07 15:26:13 -05:00
adamscmRH
f485a04dfc disallow changing token-app 2018-03-07 15:13:56 -05:00
mabashian
a5043029c1 Implemented the ability to specify credentials when creating a scheduled job run. Added validation for removing but not replacing default credentials. 2018-03-07 11:57:31 -05:00
Matthew Jones
61a48996ee Merge pull request #1459 from rooftopcellist/update_session_setting
add csrf & session settings
2018-03-07 08:41:17 -08:00
Jake McDermott
8f58d0b998 Merge pull request #1455 from ansible/jakemcdermott-patch-2
auto hide multi credential scrollbar
2018-03-07 11:33:29 -05:00
adamscmRH
0490bca268 add csrf & session settings 2018-03-07 09:32:24 -05:00
Christian Adams
095515bb56 Merge pull request #1458 from rooftopcellist/fix_expiration
Fix expiration
2018-03-07 08:13:25 -05:00
adamscmRH
efaa698939 fix token expiration time 2018-03-07 00:42:44 -05:00
AlanCoding
556e6c4a11 Dependency Updates
Upgrades of minor dependency upgrades
Inventory scripts were upgraded in separate commit

Major exclusions from this update
- celery was already downgraded for other reasons
- Django / DRF major update already done, minor bumps here
- asgi-amqp has fixes coming independently, not touched
- TACACS plus added features not needed

Removals of note
- remove shade from AWX requirements
- remove kombu from Ansible requirements

Other notes

Add note about pinning setuptools and pip,
done but not mentioned previously

Stop pinning gevent-websocket and twisted

upgrade Azure to Ansible core requirements

more detailed notes
https://gist.github.com/AlanCoding/9442a512ab6977940bc7b5b346d4f70b

upgrade version of Django for Exception
2018-03-06 16:04:01 -05:00
Matthew Jones
8421d2b0d2 Merge pull request #1457 from matburt/remove_old_migrations
Remove old south migrations from before a previous django upgrade
2018-03-06 12:59:32 -08:00
Jake McDermott
f8ca0a613f Update prompt.block.less 2018-03-06 14:56:38 -05:00
Christian Adams
db91e30464 Merge pull request #1449 from rooftopcellist/fix_exp_time
fix token expiration time
2018-03-06 14:55:38 -05:00
Matthew Jones
d19ef60d97 Remove old south migrations from before a previous django upgrade 2018-03-06 14:47:09 -05:00
Jake McDermott
5971d79a8f auto hide multi credential scrollbar 2018-03-06 14:33:28 -05:00
Wayne Witzel III
a3b2f29478 Merge pull request #1454 from wwitzel3/fix-role-summary
Fix role summary when role description is overloaded
2018-03-06 14:03:22 -05:00
adamscmRH
a80e3855cd fix token expiration time 2018-03-06 13:22:12 -05:00
Wayne Witzel III
8dce5c826c Fix role summary when role description is overloaded 2018-03-06 13:07:29 -05:00
Demin, Petr
f4a241aba2 Constrain requests 2018-03-06 12:47:34 -05:00
Jake McDermott
105b4982c4 Merge pull request #1451 from tburko/devel
Fix "System settings panel form is not rendering #1440"
2018-03-06 11:15:16 -05:00
Alan Rominger
cc33109412 Merge pull request #1445 from AlanCoding/platform
Add platform to ec2 group by options
2018-03-06 07:23:22 -05:00
Taras Burko
8a5cd3ec7d Fix "System settings panel form is not rendering #1440" 2018-03-06 14:03:36 +02:00
Ryan Petrello
d6af0bfd50 Merge pull request #1448 from ryanpetrello/fix-7923
normalize custom_virtualenv empty values to null
2018-03-05 17:25:51 -05:00
Ryan Petrello
8955e6bc1c normalize custom_virtualenv empty values to null
see: https://github.com/ansible/ansible-tower/issues/7923
2018-03-05 17:05:10 -05:00
Shane McDonald
61087940c5 Merge pull request #1446 from matburt/fix_kubernetes_configmap
Apply celery rollback changes to kubernetes configmap
2018-03-05 16:46:57 -05:00
Matthew Jones
e99184656e Apply rabbitmq and setting kubernetes changes post-celery rollback 2018-03-05 16:22:27 -05:00
AlanCoding
341e2c0fe2 add platform to ec2 group by options 2018-03-05 15:43:28 -05:00
Matthew Jones
105b82c436 Apply celery rollback changes to kubernetes configmap 2018-03-05 15:32:24 -05:00
Ryan Petrello
1596b2907b Merge pull request #1439 from ryanpetrello/fix-7923
add validation to InventorySource.inventory to avoid task manager death
2018-03-05 15:12:24 -05:00
Marliana Lara
18f3c79bc3 Denote invalid job templates and scheduled jobs by displaying a red invalid bar 2018-03-05 14:52:40 -05:00
Ryan Petrello
8887be5952 add validation to InventorySource.inventory to avoid task manager death
see: https://github.com/ansible/awx/issues/1438
2018-03-05 14:40:57 -05:00
Shane McDonald
44f6423af3 Merge pull request #1442 from ryanpetrello/devel
fix busted shippable builds
2018-03-05 14:38:35 -05:00
Chris Meyers
80a970288d Merge pull request #1443 from chrismeyersfsu/fix-named_urls
handle 404 returned by resolve()
2018-03-05 14:37:07 -05:00
chris meyers
ccfb6d64bf handle 404 returned by resolve()
* related to https://github.com/ansible/ansible-tower/issues/7926
* if 404 on url in migration loading middelware, do NOT short circuit
middleware. Simply call the normal middlware code path in this case.
2018-03-05 14:34:53 -05:00
Ryan Petrello
13672cc88c fix busted shippable builds 2018-03-05 14:16:42 -05:00
Shane McDonald
d5773c58d3 Merge pull request #1426 from chrismeyersfsu/fix-migration_in_progress
short-circuit middleware if migration loading url
2018-03-03 10:48:50 -05:00
Jake McDermott
5370c5e07d Merge pull request #1431 from wenottingham/check-check-check-it-out
Adjust some wording in the UI.
2018-03-02 21:07:18 -05:00
Bill Nottingham
1606380f61 Adjust some wording in the UI.
Attempt to make the 'scm update' vs 'scm checkout' more clear.
Remove 'future' from scheduling tooltips (superfluous).
2018-03-02 19:54:02 -05:00
Christian Adams
953850a0d7 Merge pull request #1427 from rooftopcellist/hide_client_secret
Hide client_secret from activity stream
2018-03-02 15:43:49 -05:00
adamscmRH
701a5c9a36 hides client_secret from act stream 2018-03-02 14:47:49 -05:00
chris meyers
36d59651af inherit rather than monkey patch
* Enable migration in progress page in ALL environments
2018-03-02 12:37:48 -05:00
Christian Adams
d1319b7394 Merge pull request #1414 from rooftopcellist/testing_oauth
fix token creation at `api/o/token`
2018-03-02 11:36:59 -05:00
chris meyers
746a2c1eea short-circuit middleware if migration loading url
* Had to monkey patch django middleware logic.
* Left checks to tell coders to use new middleware behavior in favor of
monkey patching.
2018-03-02 11:21:26 -05:00
Chris Meyers
9df76f963b Merge pull request #1412 from chrismeyersfsu/reap_new_nodes_too
reap all nodes that havn't checked in
2018-03-02 10:03:37 -05:00
chris meyers
17de084d04 perform the min needed DB ops to offline a node
* Don't do an extra save to the DB that could conflict with another
heartbeat when it isn't needed since we will be deleting the node
anyway.
2018-03-02 07:57:59 -05:00
Chris Meyers
f907995374 Merge pull request #1417 from chrismeyersfsu/fix-config_watcher
invoke main() in config watcher script
2018-03-01 17:11:58 -05:00
chris meyers
b69315f2eb fix up the config map watcher script
* invoke main() in config watcher script
* correctly call hash update by passing the filename
2018-03-01 17:06:07 -05:00
chris meyers
a3a618d733 call node init procedures as early as possible
* invoke the first heartbeat as early as possible. Results in a much
better user experience where when a user scales up an awx node, the node
appears with capacity earlier.
2018-03-01 17:05:58 -05:00
adamscmRH
fa7647f828 fix token creation 2018-03-01 16:19:58 -05:00
Chris Meyers
8c1ec37c80 Merge pull request #1411 from chrismeyersfsu/early_first_heartbeat
call node init procedures as early as possible
2018-03-01 13:01:37 -05:00
Jake McDermott
d7616accf5 Improve documentation for AWX E2E (#1381)
* Improve documentation for AWX E2E
2018-03-01 12:00:16 -05:00
chris meyers
5c647c2a0d call node init procedures as early as possible
* invoke the first heartbeat as early as possible. Results in a much
better user experience where when a user scales up an awx node, the node
appears with capacity earlier.
2018-03-01 11:24:45 -05:00
chris meyers
e94bd128b8 reap all nodes that havn't checked in
* Before this change we would exclude the reaping of new nodes. With
this change, new nodes will be considered for reaping just like old
nodes.
2018-03-01 11:21:54 -05:00
Alan Rominger
8d57b84251 Merge pull request #1353 from AlanCoding/dep_scripts
Update inventory scripts
2018-03-01 10:56:11 -05:00
Chris Meyers
f18d99d7a9 Merge pull request #1409 from chrismeyersfsu/openshift_runtime_rabbitmq_cookie
dynamically set rabbitmq cookie
2018-03-01 09:57:11 -05:00
chris meyers
9436e8ae25 dynamically set rabbitmq cookie 2018-03-01 09:23:45 -05:00
Wayne Witzel III
dba78e6bfb Merge pull request #1398 from wwitzel3/devel
Update to latest asgi-amqp
2018-02-28 15:20:55 -05:00
Wayne Witzel III
73f0a0d147 Update to latest asgi-amqp 2018-02-28 14:43:37 -05:00
Cédric Levasseur
a2d543eb3b Inserting a note about PostgreSQL minimal version 9.4 in installation doc (#1385)
* Minimal postgresql version

* moving the Postgresql minimal version note.

* moved to System requirements and 'minimal' replaced by 'minimum'.
2018-02-28 13:44:50 -05:00
Shane McDonald
7087341570 Merge pull request #1397 from shanemcd/devel
Fix celery 3 broker url reference in standalone docker install
2018-02-28 12:50:57 -05:00
Shane McDonald
0e9a8d5592 Fix celery 3 broker url reference 2018-02-28 12:47:05 -05:00
Alan Rominger
4fba2d61e6 Merge pull request #1394 from AlanCoding/text_type2
Prevent unicode bug in job_explanation
2018-02-28 12:42:11 -05:00
AlanCoding
54c0436959 prevent unicode bug in job_explanation 2018-02-28 11:01:20 -05:00
Alan Rominger
ee0e239a9e Merge pull request #1374 from AlanCoding/your_name
More consistent representations of model objects
2018-02-28 09:08:29 -05:00
Matthew Jones
dc4b9341da Merge pull request #1383 from jakemcdermott/401-on-invalid-login
issue a 401 on invalid login
2018-02-28 08:35:11 -05:00
Jake McDermott
75a27f2457 issue 401 on invalid login 2018-02-28 02:02:52 -05:00
Jake McDermott
ee20fc478b add test for invalid login 2018-02-28 02:02:39 -05:00
Jake McDermott
01ee2adf30 Merge pull request #1382 from jakemcdermott/cookie-settings
adding in default session cookie setting for docker stand alone
2018-02-27 20:42:46 -05:00
Jake McDermott
877cde9a7f add default cookie settings 2018-02-27 20:40:41 -05:00
Ryan Petrello
b5a46c346d Merge pull request #1379 from ryanpetrello/fix-1366
don't inject custom extra_vars for inventory updates
2018-02-27 17:00:22 -05:00
Christian Adams
6e39388090 Merge pull request #1380 from rooftopcellist/csrf_flag
adds csrf flag to support http
2018-02-27 16:36:23 -05:00
Christian Adams
47c4eb38df Merge pull request #1377 from rooftopcellist/remove_authtoken_model
Removes Auth token
2018-02-27 16:33:20 -05:00
adamscmRH
69f8304643 adds csrf flag to support http 2018-02-27 16:19:46 -05:00
adamscmRH
40d563626e removes authtoken 2018-02-27 16:12:13 -05:00
Ryan Petrello
b9ab06734d don't inject custom extra_vars for inventory updates
see: https://github.com/ansible/awx/issues/1366
2018-02-27 16:10:23 -05:00
Chris Meyers
d551566b4d Merge pull request #1372 from chrismeyersfsu/old-celery3
celery 4.x to 3.x roll back
2018-02-27 15:26:46 -05:00
chris meyers
6606a29f57 celery 4.x -> 3.x change route config name 2018-02-27 14:13:05 -05:00
Jake McDermott
f9129aefba Merge pull request #1361 from mabashian/1279-preview-credentials
Put credentials on their own line in the launch preview
2018-02-27 13:42:00 -05:00
AlanCoding
bacd895705 more consistent representations of model objects 2018-02-27 12:18:57 -05:00
chris meyers
148baf7674 add explicit awx_celery container version 2018-02-27 11:37:10 -05:00
chris meyers
5918fa5573 remove () from postgres port value
* awx task container uses postgres port to wait for postgres to become
available before the container init continues. The () are problematic
and are removed.
* () was originally added to fix an openshift issues. That error does
NOT occur with this fix.
2018-02-27 11:36:55 -05:00
chris meyers
e4470aa4cf remove uneeded celery configs
* Celery routes and queues are set and defined at runtime. Thus, a
static definition of routes and queues is not needed.
2018-02-27 11:36:55 -05:00
chris meyers
fe05b4c0d5 use celery 3.x BROKER_URL
* Celery 4.x specifies the broker via CELERY_BROKER_URL. Since we are
now on 3.x, use 3.x way of specifying the broker via BROKER_URL
2018-02-27 11:36:55 -05:00
Alan Rominger
6d7f60ea61 Merge pull request #1368 from AlanCoding/none_client
Fix server error with absent client_secret
2018-02-27 10:39:50 -05:00
Ryan Petrello
a4ab424134 Merge pull request #1362 from ryanpetrello/rdb-sdb
replace our rdb tooling w/ the sdb PyPI package
2018-02-27 10:06:21 -05:00
Ryan Petrello
3636a7c582 Merge pull request #1355 from ryanpetrello/devel
set $HOME via an API call so AWX_TASK_ENV isn't marked as readonly
2018-02-27 09:57:17 -05:00
AlanCoding
c900027f82 fix server error with absent client_secret 2018-02-27 09:23:36 -05:00
Ryan Petrello
d743b77353 replace our rdb tooling w/ the sdb PyPI package 2018-02-26 19:05:50 -05:00
Ryan Petrello
7741de5153 set $HOME via an API call so AWX_TASK_ENV isn't marked as readonly
see: https://github.com/ansible/awx/issues/1315
2018-02-26 16:35:36 -05:00
Michael Abashian
c3968ca2b6 Merge pull request #1357 from mabashian/1281-prompt-inv
Fixed bug preventing users from selecting non-default inventory on job launch
2018-02-26 16:18:42 -05:00
mabashian
c58ea0ea25 Put credentials on their own line in the launch preview and forced them to wrap 2018-02-26 16:06:52 -05:00
Bill Nottingham
4519013a13 Merge pull request #1356 from wenottingham/mongo-only-pawn
Remove some obsolete code.
2018-02-26 15:30:37 -05:00
Bill Nottingham
c1203942e0 Remove obsolete ansible_awx.egg-info. 2018-02-26 15:04:37 -05:00
Bill Nottingham
e7a8ecc05a Fix another instance. 2018-02-26 14:57:24 -05:00
Bill Nottingham
9c722cba22 Remove some obsolete code. 2018-02-26 14:55:13 -05:00
mabashian
9ad8bdf8de Fixed bug preventing users from selecting non-default inventory on job launch 2018-02-26 14:50:31 -05:00
AlanCoding
b878a844d0 Update inventory scripts
ec2
- added support for tags and instance attributes
- allow filtering RDS instances by tags
- add option to group by platform
- set missing defaults
- make cache unique to script ran
- bug fixes
- implement AND'd filters
azure_rm
- minor python 3 upgrades
cloudforms
- minor regex fix
foreman
- several new configurables
- changes to caching
gce
- python 3 upgrades
- added gce_subnetwork param
openstack
- added `--cloud` parameter
ovirt4
- obtain defaults from env vars
vmware_inventory
- changed imports
- allow for custom filters
- changed host_filters
- error handling
- python 3 upgrades
2018-02-26 13:46:21 -05:00
AlanCoding
7b78a2ebcc update tests for new call pattern for capabilities prefetch 2018-02-26 12:13:41 -05:00
AlanCoding
ce9234df0f Revamp user_capabilities with new copy fields
Add copy fields corresponding to new server-side copying

Refactor the way user_capabilities are delivered
 - move the prefetch definition from views to serializer
 - store temporary mapping in serializer context
 - use serializer backlinks to denote polymorphic prefetch model exclusions
2018-02-26 12:13:41 -05:00
Christian Adams
9493b72f29 Merge pull request #904 from ansible/oauth_n_session
Implement session-based  and OAuth 2 authentications
2018-02-26 12:12:38 -05:00
Jake McDermott
7430856ac9 Merge pull request #1344 from jakemcdermott/e2e-updates
e2e / nightwatch updates
2018-02-26 11:58:29 -05:00
adamscmRH
407bcd0cbd fix def application test 2018-02-26 11:35:09 -05:00
Jake McDermott
350f25c6e5 Merge pull request #1343 from jakemcdermott/oauth_n_session
ui tooling fixes / updates for oauth changes
2018-02-26 10:42:04 -05:00
Jake McDermott
c786736688 add setup step for org lookup check 2018-02-25 19:40:22 -05:00
Jake McDermott
01a8b2771a add worker file push command 2018-02-25 19:40:19 -05:00
Jake McDermott
a23e4732b6 bump nightwatch and chromedriver versions 2018-02-25 19:40:15 -05:00
Jake McDermott
24fd4a360e use updated project when checking copy 2018-02-25 19:40:11 -05:00
Jake McDermott
8bf31600b0 stabilize local test runs 2018-02-25 19:40:08 -05:00
Jake McDermott
0e7db2a816 do searchability check last
This fixes a small race condition that sometimes occurs when running
locally by ensuring that the delayed paged scrolling that happens
from using search doesn't put the password reset button out of view
when the test runner is trying to find and click it.
2018-02-25 19:40:02 -05:00
Jake McDermott
59e278a648 ensure correct url is built for inventory hosts page 2018-02-25 19:39:38 -05:00
Jake McDermott
44acecf61e use basic auth by default for data setup 2018-02-25 14:28:09 -05:00
adamscmRH
30b473b0df remove default app creation 2018-02-24 21:34:07 -05:00
Jake McDermott
6bdcba307c fix missing comma 2018-02-24 13:59:55 -05:00
Marliana Lara
434cd31df8 Merge pull request #1338 from marshmalien/feat/multiple_venvs
Implement UI selects for Playbook, Project, and Organization Virtualenvs
2018-02-23 15:48:41 -05:00
Jake McDermott
2b4e631838 Merge pull request #1339 from jakemcdermott/use-navbar-in-smoke-test
use navbar when accessing project and template views
2018-02-23 15:36:46 -05:00
Jake McDermott
b0e0b8f0e3 use navbar when accessing project and template views 2018-02-23 15:08:44 -05:00
Marliana Lara
8a163b5939 Add error handling 2018-02-23 14:49:00 -05:00
Marliana Lara
23300003ab Add dropdown inputs for Job Template, Project, and Organization virtual
envs
2018-02-23 14:49:00 -05:00
adamscmRH
87350e1014 prelim update to docs 2018-02-23 14:10:29 -05:00
adamscmRH
2911dec324 fixes app token endpoint 2018-02-23 11:06:53 -05:00
adamscmRH
99989892cd fixs naming 2018-02-23 09:25:23 -05:00
Alan Rominger
ad8822bcfc Merge pull request #1314 from AlanCoding/fix_rescheduling
Correct permission check for job rescheduling
2018-02-22 16:04:04 -05:00
Ryan Petrello
c35c01e7b1 Merge pull request #1328 from ryanpetrello/devel
Revert "changes to license compliance"
2018-02-22 15:28:54 -05:00
adamscmRH
ecc61b62ca reverts cookie change 2018-02-22 15:18:12 -05:00
John Mitchell
09efc03163 update incorrect logic for auth service rootscope/cookie logged in status vars 2018-02-22 15:18:12 -05:00
John Mitchell
db748775c8 make auth function convert values from cookies to boolean 2018-02-22 15:18:12 -05:00
adamscmRH
310f37dd37 clears authtoken & add PAT 2018-02-22 15:18:12 -05:00
John Mitchell
88bc4a0a9c ui auth works on 8013 now 2018-02-22 15:18:12 -05:00
John Mitchell
976766e4a3 excise token-based auth from ui 2018-02-22 15:18:12 -05:00
Aaron Tan
1c2621cd60 Implement session-based and OAuth 2 authentications
Relates #21. Please see acceptance docs for feature details.

Signed-off-by: Aaron Tan <jangsutsr@gmail.com>
2018-02-22 15:18:12 -05:00
Ryan Petrello
35f629d42c Revert "changes to license compliance"
This reverts commit 218dfb680e.
2018-02-22 15:02:33 -05:00
Alan Rominger
db39ab1b0c Merge pull request #1322 from AlanCoding/check_version
Loosen overwrite_vars constraint for new feature
2018-02-22 14:26:30 -05:00
Shane McDonald
c612ab1c89 Merge pull request #1327 from marshmalien/fix/bump-angular-scheduler-version
Bump angular-scheduler version to 0.3.2
2018-02-22 13:55:07 -05:00
Shane McDonald
c0fe6866c4 Merge pull request #1070 from vrutkovs/installer-ocp-token
Allow authenticating with Openshift via a token
2018-02-22 13:40:01 -05:00
Marliana Lara
746b99046f Bump angular-scheduler version to 0.3.2 2018-02-22 13:35:33 -05:00
Wayne Witzel III
91c6d406c5 Rollback celery 2018-02-22 09:37:14 -05:00
AlanCoding
4727cda336 Loosen overwrite_vars constraint for new feature 2018-02-22 07:47:28 -05:00
Alan Rominger
2ebee58727 Merge pull request #1321 from AlanCoding/magic_credential
Alias filters by credential to credentials
2018-02-21 15:51:42 -05:00
AlanCoding
91e59ebd29 alias filters by credential to credentials 2018-02-21 14:57:26 -05:00
AlanCoding
992d7831b1 add test for ScheduleAccess prompts 2018-02-21 14:11:55 -05:00
Marliana Lara
88b67c894c Merge pull request #1231 from marshmalien/fix/tzid-schedules
Add support for TZID in schedule rrules
2018-02-21 13:55:31 -05:00
Marliana Lara
d71ecf1eee Fix jshint confusing semantics error 2018-02-21 13:18:43 -05:00
Marliana Lara
b9a2f7a87e Add debounce function to preview list to reduce overhead 2018-02-21 13:18:40 -05:00
Marliana Lara
e0cfd18aac Set local timezone dropdown to rrule TZID value 2018-02-21 13:18:39 -05:00
Marliana Lara
73916ade45 Filter dates with moment.js instead of built-in angular date filter 2018-02-21 13:18:38 -05:00
Marliana Lara
1768001881 Add support for TZID in schedule rrules 2018-02-21 13:18:33 -05:00
Chris Church
795681a887 Merge pull request #1311 from cchurch/fix-dummy-data
Fix dummy data generator for WFJT node credentials.
2018-02-21 10:19:50 -05:00
AlanCoding
de4e95f396 correct permission check for job rescheduling 2018-02-21 09:25:43 -05:00
Chris Church
4ec683efcb Fix dummy data generator for WFJT node credentials. 2018-02-21 08:55:43 -05:00
Chris Church
727ded2d4d Merge pull request #1308 from cclauss/patch-1
Py3 Syntax Errors: 0700 -> 0x700 and 0600 -> 0x600
2018-02-21 08:29:49 -05:00
cclauss
8967afc645 octal, not hex 2018-02-21 14:13:47 +01:00
cclauss
d66cad3e0e Py3 Syntax Errors: 0700 -> 0x700 and 0600 -> 0x600
$ __python3 -c "0700"__
```
  File "<string>", line 1
    0700
       ^
SyntaxError: invalid token
```
2018-02-21 12:18:52 +01:00
Ryan Petrello
7db05855de Merge pull request #1306 from ryanpetrello/isolated-fact-cache
support fact caching for isolated hosts
2018-02-20 15:50:49 -05:00
Ryan Petrello
7d9e4d6e2f support fact caching for isolated hosts
see: https://github.com/ansible/awx/issues/198
2018-02-20 15:00:47 -05:00
Ryan Petrello
662f4ec346 Merge pull request #1304 from ryanpetrello/devel
remove dead code
2018-02-20 14:44:52 -05:00
Ryan Petrello
ac3ce82eb1 remove dead code
the code that persists `set_stat` data for workflows now lives elsewhere

related: d57470ce49
2018-02-20 14:14:23 -05:00
Alan Rominger
1582fcbb50 Merge pull request #1277 from AlanCoding/inv_multicred
Use the m2m field for inventory source credentials
2018-02-20 14:08:22 -05:00
AlanCoding
bb6032cff6 docs and review change for IS multivault
Mention inventory sources /credentials/ endpoint in docs
Also change means of identifying projects for the purose
of injecting custom credentials
2018-02-20 12:34:58 -05:00
AlanCoding
9c4d89f512 use the m2m field for inventory source creds 2018-02-20 12:34:56 -05:00
Vadim Rutkovsky
5e25859069 Allow authenticating with Openshift via a token 2018-02-18 16:24:16 +01:00
1020 changed files with 37383 additions and 75292 deletions

4
.github/BOTMETA.yml vendored
View File

@@ -12,5 +12,5 @@ files:
labels: component:installer
macros:
team_api: wwitzel3 matburt chrismeyersfsu cchurch AlanCoding ryanpetrello jangstur
team_ui: jlmitch5 jaredevantabor mabashian gconsidine marshmalien benthomasson
team_api: wwitzel3 matburt chrismeyersfsu cchurch AlanCoding ryanpetrello rooftopcellist
team_ui: jlmitch5 jaredevantabor mabashian marshmalien benthomasson jakemcdermott

3
.github/CODE_OF_CONDUCT.md vendored Normal file
View File

@@ -0,0 +1,3 @@
# Community Code of Conduct
Please see the official [Ansible Community Code of Conduct](https://docs.ansible.com/ansible/latest/community/code_of_conduct.html).

View File

@@ -1,3 +1,11 @@
<!---
The Ansible community is highly committed to the security of our open source
projects. Security concerns should be reported directly by email to
security@ansible.com. For more information on the Ansible community's
practices regarding responsible disclosure, see
https://www.ansible.com/security
-->
##### ISSUE TYPE
<!--- Pick one below and delete the rest: -->
- Bug Report

2
.gitignore vendored
View File

@@ -22,6 +22,8 @@ awx/ui/build_test
awx/ui/client/languages
awx/ui/templates/ui/index.html
awx/ui/templates/ui/installing.html
/tower-license
/tower-license/**
# Tower setup playbook testing
setup/test/roles/postgresql

View File

@@ -23,6 +23,7 @@ This document provides a guide for installing AWX.
- [Kubernetes](#kubernetes)
- [Prerequisites](#prerequisites-2)
- [Pre-build steps](#pre-build-steps-1)
- [Configuring Helm](#configuring-helm)
- [Start the build](#start-the-build-1)
- [Accessing AWX](#accessing-awx-1)
- [SSL Termination](#ssl-termination)
@@ -61,6 +62,8 @@ Before you can run a deployment, you'll need the following installed in your loc
- [docker-py](https://github.com/docker/docker-py) Python module
- [GNU Make](https://www.gnu.org/software/make/)
- [Git](https://git-scm.com/) Requires Version 1.8.4+
- [Node 6.x LTS version](https://nodejs.org/en/download/)
- [NPM 3.x LTS](https://docs.npmjs.com/)
### System Requirements
@@ -70,6 +73,7 @@ The system that runs the AWX service will need to satisfy the following requirem
- At least 2 cpu cores
- At least 20GB of space
- Running Docker, Openshift, or Kubernetes
- If you choose to use an external PostgreSQL database, please note that the minimum version is 9.4.
### AWX Tunables
@@ -115,6 +119,15 @@ To complete a deployment to OpenShift, you will obviously need access to an Open
You will also need to have the `oc` command in your PATH. The `install.yml` playbook will call out to `oc` when logging into, and creating objects on the cluster.
The default resource requests per-pod requires:
> Memory: 6GB
> CPU: 3 cores
This can be tuned by overriding the variables found in [/installer/openshift/defaults/main.yml](/installer/openshift/defaults/main.yml). Special care should be taken when doing this as undersized instances will experience crashes and resource exhaustion.
For more detail on how resource requests are formed see: [https://docs.openshift.com/container-platform/latest/dev_guide/compute_resources.html#dev-compute-resources](https://docs.openshift.com/container-platform/latest/dev_guide/compute_resources.html#dev-compute-resources)
#### Deploying to Minishift
Install Minishift by following the [installation guide](https://docs.openshift.org/latest/minishift/getting-started/installing.html).
@@ -287,6 +300,15 @@ A Kubernetes deployment will require you to have access to a Kubernetes cluster
The installation program will reference `kubectl` directly. `helm` is only necessary if you are letting the installer configure PostgreSQL for you.
The default resource requests per-pod requires:
> Memory: 6GB
> CPU: 3 cores
This can be tuned by overriding the variables found in [/installer/kubernetes/defaults/main.yml](/installer/kubernetes[/defaults/main.yml). Special care should be taken when doing this as undersized instances will experience crashes and resource exhaustion.
For more detail on how resource requests are formed see: [https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/](https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/)
### Pre-build steps
Before starting the build process, review the [inventory](./installer/inventory) file, and uncomment and provide values for the following variables found in the `[all:vars]` section uncommenting when necessary. Make sure the openshift and standalone docker sections are commented out:
@@ -303,6 +325,12 @@ Before starting the build process, review the [inventory](./installer/inventory)
> These settings should be used if building your own base images. You'll need access to an external registry and are responsible for making sure your kube cluster can talk to it and use it. If these are undefined and the dockerhub_ configuration settings are uncommented then the images will be pulled from dockerhub instead
### Configuring Helm
If you want the AWX installer to manage creating the database pod (rather than installing and configuring postgres on your own). Then you will need to have a working `helm` installation, you can find details here: [https://docs.helm.sh/using_helm/#quickstart-guide](https://docs.helm.sh/using_helm/#quickstart-guide).
Newer Kubernetes clusters with RBAC enabled will need to make sure a service account is created, make sure to follow the instructions here [https://docs.helm.sh/using_helm/#role-based-access-control](https://docs.helm.sh/using_helm/#role-based-access-control)
### Start the build
After making changes to the `inventory` file use `ansible-playbook` to begin the install

View File

@@ -72,7 +72,7 @@ UI_RELEASE_FLAG_FILE = awx/ui/.release_built
I18N_FLAG_FILE = .i18n_built
.PHONY: clean clean-tmp clean-venv requirements requirements_dev \
.PHONY: awx-link clean clean-tmp clean-venv requirements requirements_dev \
develop refresh adduser migrate dbchange dbshell runserver celeryd \
receiver test test_unit test_ansible test_coverage coverage_html \
dev_build release_build release_clean sdist \
@@ -184,7 +184,6 @@ requirements_awx: virtualenv_awx
requirements_awx_dev:
$(VENV_BASE)/awx/bin/pip install -r requirements/requirements_dev.txt
$(VENV_BASE)/awx/bin/pip uninstall --yes -r requirements/requirements_dev_uninstall.txt
requirements: requirements_ansible requirements_awx
@@ -235,7 +234,7 @@ migrate:
if [ "$(VENV_BASE)" ]; then \
. $(VENV_BASE)/awx/bin/activate; \
fi; \
$(MANAGEMENT_COMMAND) migrate --noinput --fake-initial
$(MANAGEMENT_COMMAND) migrate --noinput
# Run after making changes to the models to create a new migration.
dbchange:
@@ -324,7 +323,7 @@ celeryd:
@if [ "$(VENV_BASE)" ]; then \
. $(VENV_BASE)/awx/bin/activate; \
fi; \
celery worker -A awx -l DEBUG -B -Ofair --autoscale=100,4 --schedule=$(CELERY_SCHEDULE_FILE) -Q tower_broadcast_all -n celery@$(COMPOSE_HOST) --pidfile /tmp/celery_pid
celery worker -A awx -l DEBUG -B -Ofair --autoscale=100,4 --schedule=$(CELERY_SCHEDULE_FILE) -n celery@$(COMPOSE_HOST) --pidfile /tmp/celery_pid
# Run to start the zeromq callback receiver
receiver:
@@ -336,9 +335,6 @@ receiver:
nginx:
nginx -g "daemon off;"
rdb:
$(PYTHON) tools/rdb.py
jupyter:
@if [ "$(VENV_BASE)" ]; then \
. $(VENV_BASE)/awx/bin/activate; \
@@ -371,14 +367,21 @@ swagger: reports
check: flake8 pep8 # pyflakes pylint
awx-link:
cp -R /tmp/awx.egg-info /awx_devel/ || true
sed -i "s/placeholder/$(shell git describe --long | sed 's/\./\\./g')/" /awx_devel/awx.egg-info/PKG-INFO
cp /tmp/awx.egg-link /venv/awx/lib/python2.7/site-packages/awx.egg-link
TEST_DIRS ?= awx/main/tests/unit awx/main/tests/functional awx/conf/tests awx/sso/tests
# Run all API unit tests.
test: test_ansible
test:
@if [ "$(VENV_BASE)" ]; then \
. $(VENV_BASE)/awx/bin/activate; \
fi; \
py.test $(TEST_DIRS)
test_combined: test_ansible test
test_unit:
@if [ "$(VENV_BASE)" ]; then \
. $(VENV_BASE)/awx/bin/activate; \

View File

@@ -7,7 +7,7 @@ import sys
import warnings
from pkg_resources import get_distribution
from .celery import app as celery_app
from .celery import app as celery_app # noqa
__version__ = get_distribution('awx').version

View File

@@ -2,126 +2,21 @@
# All Rights Reserved.
# Python
import urllib
import logging
# Django
from django.conf import settings
from django.utils.timezone import now as tz_now
from django.utils.encoding import smart_text
from django.utils.translation import ugettext_lazy as _
# Django REST Framework
from rest_framework import authentication
from rest_framework import exceptions
from rest_framework import HTTP_HEADER_ENCODING
# AWX
from awx.main.models import AuthToken
# Django OAuth Toolkit
from oauth2_provider.contrib.rest_framework import OAuth2Authentication
logger = logging.getLogger('awx.api.authentication')
class TokenAuthentication(authentication.TokenAuthentication):
'''
Custom token authentication using tokens that expire and are associated
with parameters specific to the request.
'''
model = AuthToken
@staticmethod
def _get_x_auth_token_header(request):
auth = request.META.get('HTTP_X_AUTH_TOKEN', '')
if isinstance(auth, type('')):
# Work around django test client oddness
auth = auth.encode(HTTP_HEADER_ENCODING)
return auth
@staticmethod
def _get_auth_token_cookie(request):
token = request.COOKIES.get('token', '')
if token:
token = urllib.unquote(token).strip('"')
return 'token %s' % token
return ''
def authenticate(self, request):
self.request = request
# Prefer the custom X-Auth-Token header over the Authorization header,
# to handle cases where the browser submits saved Basic auth and
# overrides the UI's normal use of the Authorization header.
auth = TokenAuthentication._get_x_auth_token_header(request).split()
if not auth or auth[0].lower() != 'token':
auth = authentication.get_authorization_header(request).split()
# Prefer basic auth over cookie token
if auth and auth[0].lower() == 'basic':
return None
elif not auth or auth[0].lower() != 'token':
auth = TokenAuthentication._get_auth_token_cookie(request).split()
if not auth or auth[0].lower() != 'token':
return None
if len(auth) == 1:
msg = _('Invalid token header. No credentials provided.')
raise exceptions.AuthenticationFailed(msg)
elif len(auth) > 2:
msg = _('Invalid token header. Token string should not contain spaces.')
raise exceptions.AuthenticationFailed(msg)
return self.authenticate_credentials(auth[1])
def authenticate_credentials(self, key):
now = tz_now()
# Retrieve the request hash and token.
try:
request_hash = self.model.get_request_hash(self.request)
token = self.model.objects.select_related('user').get(
key=key,
request_hash=request_hash,
)
except self.model.DoesNotExist:
raise exceptions.AuthenticationFailed(AuthToken.reason_long('invalid_token'))
# Tell the user why their token was previously invalidated.
if token.invalidated:
raise exceptions.AuthenticationFailed(AuthToken.reason_long(token.reason))
# Explicitly handle expired tokens
if token.is_expired(now=now):
token.invalidate(reason='timeout_reached')
raise exceptions.AuthenticationFailed(AuthToken.reason_long('timeout_reached'))
# Token invalidated due to session limit config being reduced
# Session limit reached invalidation will also take place on authentication
if settings.AUTH_TOKEN_PER_USER != -1:
if not token.in_valid_tokens(now=now):
token.invalidate(reason='limit_reached')
raise exceptions.AuthenticationFailed(AuthToken.reason_long('limit_reached'))
# If the user is inactive, then return an error.
if not token.user.is_active:
raise exceptions.AuthenticationFailed(_('User inactive or deleted'))
# Refresh the token.
# The token is extended from "right now" + configurable setting amount.
token.refresh(now=now)
# Return the user object and the token.
return (token.user, token)
class TokenGetAuthentication(TokenAuthentication):
def authenticate(self, request):
if request.method.lower() == 'get':
token = request.GET.get('token', None)
if token:
request.META['HTTP_X_AUTH_TOKEN'] = 'Token %s' % token
return super(TokenGetAuthentication, self).authenticate(request)
class LoggedBasicAuthentication(authentication.BasicAuthentication):
def authenticate(self, request):
@@ -137,3 +32,28 @@ class LoggedBasicAuthentication(authentication.BasicAuthentication):
if not settings.AUTH_BASIC_ENABLED:
return
return super(LoggedBasicAuthentication, self).authenticate_header(request)
class SessionAuthentication(authentication.SessionAuthentication):
def authenticate_header(self, request):
return 'Session'
def enforce_csrf(self, request):
return None
class LoggedOAuth2Authentication(OAuth2Authentication):
def authenticate(self, request):
ret = super(LoggedOAuth2Authentication, self).authenticate(request)
if ret:
user, token = ret
username = user.username if user else '<none>'
logger.debug(smart_text(
u"User {} performed a {} to {} through the API using OAuth token {}.".format(
username, request.method, request.path, token.pk
)
))
setattr(user, 'oauth_scopes', [x for x in token.scope.split() if x])
return ret

View File

@@ -1,30 +1,30 @@
# Django
from django.utils.translation import ugettext_lazy as _
# Tower
# AWX
from awx.conf import fields, register
from awx.api.fields import OAuth2ProviderField
register(
'AUTH_TOKEN_EXPIRATION',
'SESSION_COOKIE_AGE',
field_class=fields.IntegerField,
min_value=60,
max_value=30000000000, # approx 1,000 years, higher values give OverflowError
label=_('Idle Time Force Log Out'),
help_text=_('Number of seconds that a user is inactive before they will need to login again.'),
category=_('Authentication'),
category_slug='authentication',
)
register(
'AUTH_TOKEN_PER_USER',
'SESSIONS_PER_USER',
field_class=fields.IntegerField,
min_value=-1,
label=_('Maximum number of simultaneous logins'),
help_text=_('Maximum number of simultaneous logins a user may have. To disable enter -1.'),
label=_('Maximum number of simultaneous logged in sessions'),
help_text=_('Maximum number of simultaneous logged in sessions a user may have. To disable enter -1.'),
category=_('Authentication'),
category_slug='authentication',
)
register(
'AUTH_BASIC_ENABLED',
field_class=fields.BooleanField,
@@ -33,3 +33,16 @@ register(
category=_('Authentication'),
category_slug='authentication',
)
register(
'OAUTH2_PROVIDER',
field_class=OAuth2ProviderField,
default={'ACCESS_TOKEN_EXPIRE_SECONDS': 315360000000,
'AUTHORIZATION_CODE_EXPIRE_SECONDS': 600},
label=_('OAuth 2 Timeout Settings'),
help_text=_('Dictionary for customizing OAuth 2 timeouts, available items are '
'`ACCESS_TOKEN_EXPIRE_SECONDS`, the duration of access tokens in the number '
'of seconds, and `AUTHORIZATION_CODE_EXPIRE_SECONDS`, the duration of '
'authorization grants in the number of seconds.'),
category=_('Authentication'),
category_slug='authentication',
)

18
awx/api/exceptions.py Normal file
View File

@@ -0,0 +1,18 @@
# Copyright (c) 2018 Ansible by Red Hat
# All Rights Reserved.
# Django
from django.utils.translation import ugettext_lazy as _
# Django REST Framework
from rest_framework.exceptions import ValidationError
class ActiveJobConflict(ValidationError):
status_code = 409
def __init__(self, active_jobs):
super(ActiveJobConflict, self).__init__({
"error": _("Resource is being used by running jobs."),
"active_jobs": active_jobs
})

View File

@@ -1,10 +1,17 @@
# Copyright (c) 2016 Ansible, Inc.
# All Rights Reserved.
# Django
from django.utils.translation import ugettext_lazy as _
from django.core.exceptions import ObjectDoesNotExist
# Django REST Framework
from rest_framework import serializers
# AWX
from awx.conf import fields
from awx.main.models import Credential
__all__ = ['BooleanNullField', 'CharNullField', 'ChoiceNullField', 'VerbatimField']
@@ -66,3 +73,36 @@ class VerbatimField(serializers.Field):
def to_representation(self, value):
return value
class OAuth2ProviderField(fields.DictField):
default_error_messages = {
'invalid_key_names': _('Invalid key names: {invalid_key_names}'),
}
valid_key_names = {'ACCESS_TOKEN_EXPIRE_SECONDS', 'AUTHORIZATION_CODE_EXPIRE_SECONDS'}
child = fields.IntegerField(min_value=1)
def to_internal_value(self, data):
data = super(OAuth2ProviderField, self).to_internal_value(data)
invalid_flags = (set(data.keys()) - self.valid_key_names)
if invalid_flags:
self.fail('invalid_key_names', invalid_key_names=', '.join(list(invalid_flags)))
return data
class DeprecatedCredentialField(serializers.IntegerField):
def __init__(self, **kwargs):
kwargs['allow_null'] = True
kwargs['default'] = None
kwargs['min_value'] = 1
kwargs['help_text'] = 'This resource has been deprecated and will be removed in a future release'
super(DeprecatedCredentialField, self).__init__(**kwargs)
def to_internal_value(self, pk):
try:
Credential.objects.get(pk=pk)
except ObjectDoesNotExist:
raise serializers.ValidationError(_('Credential {} does not exist').format(pk))
return pk

View File

@@ -27,13 +27,6 @@ from awx.main.models.credential import CredentialType
from awx.main.models.rbac import RoleAncestorEntry
class MongoFilterBackend(BaseFilterBackend):
# FIX: Note that MongoEngine can't use the filter backends from DRF
def filter_queryset(self, request, queryset, view):
return queryset
class V1CredentialFilterBackend(BaseFilterBackend):
'''
For /api/v1/ requests, filter out v2 (custom) credentials
@@ -138,6 +131,8 @@ class FieldLookupBackend(BaseFilterBackend):
new_parts.append(name_alt)
else:
field = model._meta.get_field(name)
if 'auth' in name or 'token' in name:
raise PermissionDenied(_('Filtering on %s is not allowed.' % name))
if isinstance(field, ForeignObjectRel) and getattr(field.field, '__prevent_search__', False):
raise PermissionDenied(_('Filtering on %s is not allowed.' % name))
elif getattr(field, '__prevent_search__', False):
@@ -276,7 +271,7 @@ class FieldLookupBackend(BaseFilterBackend):
# TODO: remove after API v1 deprecation period
if queryset.model._meta.object_name in ('JobTemplate', 'Job') and key in (
'credential', 'vault_credential', 'cloud_credential', 'network_credential'
):
) or queryset.model._meta.object_name in ('InventorySource', 'InventoryUpdate') and key == 'credential':
key = 'credentials'
# Make legacy v1 Credential fields work for backwards compatability

View File

@@ -19,10 +19,11 @@ from django.utils.encoding import smart_text
from django.utils.safestring import mark_safe
from django.contrib.contenttypes.models import ContentType
from django.utils.translation import ugettext_lazy as _
from django.contrib.auth import views as auth_views
# Django REST Framework
from rest_framework.authentication import get_authorization_header
from rest_framework.exceptions import PermissionDenied, AuthenticationFailed
from rest_framework.exceptions import PermissionDenied, AuthenticationFailed, ParseError
from rest_framework import generics
from rest_framework.response import Response
from rest_framework import status
@@ -59,6 +60,34 @@ logger = logging.getLogger('awx.api.generics')
analytics_logger = logging.getLogger('awx.analytics.performance')
class LoggedLoginView(auth_views.LoginView):
def post(self, request, *args, **kwargs):
original_user = getattr(request, 'user', None)
ret = super(LoggedLoginView, self).post(request, *args, **kwargs)
current_user = getattr(request, 'user', None)
if current_user and getattr(current_user, 'pk', None) and current_user != original_user:
logger.info("User {} logged in.".format(current_user.username))
if request.user.is_authenticated:
return ret
else:
ret.status_code = 401
return ret
class LoggedLogoutView(auth_views.LogoutView):
def dispatch(self, request, *args, **kwargs):
original_user = getattr(request, 'user', None)
ret = super(LoggedLogoutView, self).dispatch(request, *args, **kwargs)
current_user = getattr(request, 'user', None)
if (not current_user or not getattr(current_user, 'pk', True)) \
and current_user != original_user:
logger.info("User {} logged out.".format(original_user.username))
return ret
def get_view_name(cls, suffix=None):
'''
Wrapper around REST framework get_view_name() to support get_name() method
@@ -136,6 +165,9 @@ class APIView(views.APIView):
request.drf_request_user = getattr(drf_request, 'user', False)
except AuthenticationFailed:
request.drf_request_user = None
except ParseError as exc:
request.drf_request_user = None
self.__init_request_error__ = exc
return drf_request
def finalize_response(self, request, response, *args, **kwargs):
@@ -145,6 +177,8 @@ class APIView(views.APIView):
if response.status_code >= 400:
status_msg = "status %s received by user %s attempting to access %s from %s" % \
(response.status_code, request.user, request.path, request.META.get('REMOTE_ADDR', None))
if hasattr(self, '__init_request_error__'):
response = self.handle_exception(self.__init_request_error__)
if response.status_code == 401:
logger.info(status_msg)
else:
@@ -327,13 +361,6 @@ class ListAPIView(generics.ListAPIView, GenericAPIView):
def get_queryset(self):
return self.request.user.get_queryset(self.model)
def paginate_queryset(self, queryset):
page = super(ListAPIView, self).paginate_queryset(queryset)
# Queries RBAC info & stores into list objects
if hasattr(self, 'capabilities_prefetch') and page is not None:
cache_list_capabilities(page, self.capabilities_prefetch, self.model, self.request.user)
return page
def get_description_context(self):
if 'username' in get_all_field_names(self.model):
order_field = 'username'

View File

@@ -44,9 +44,9 @@ class Metadata(metadata.SimpleMetadata):
if placeholder is not serializers.empty:
field_info['placeholder'] = placeholder
# Update help text for common fields.
serializer = getattr(field, 'parent', None)
if serializer:
if serializer and hasattr(serializer, 'Meta') and hasattr(serializer.Meta, 'model'):
# Update help text for common fields.
field_help_text = {
'id': _('Database ID for this {}.'),
'name': _('Name of this {}.'),
@@ -59,10 +59,18 @@ class Metadata(metadata.SimpleMetadata):
'modified': _('Timestamp when this {} was last modified.'),
}
if field.field_name in field_help_text:
if hasattr(serializer, 'Meta') and hasattr(serializer.Meta, 'model'):
opts = serializer.Meta.model._meta.concrete_model._meta
verbose_name = smart_text(opts.verbose_name)
field_info['help_text'] = field_help_text[field.field_name].format(verbose_name)
opts = serializer.Meta.model._meta.concrete_model._meta
verbose_name = smart_text(opts.verbose_name)
field_info['help_text'] = field_help_text[field.field_name].format(verbose_name)
# If field is not part of the model, then show it as non-filterable
else:
is_model_field = False
for model_field in serializer.Meta.model._meta.fields:
if field.field_name == model_field.name:
is_model_field = True
break
if not is_model_field:
field_info['filterable'] = False
# Indicate if a field has a default value.
# FIXME: Still isn't showing all default values?

View File

@@ -1,7 +1,6 @@
# Python
from collections import OrderedDict
import json
import yaml
# Django
from django.conf import settings
@@ -13,36 +12,6 @@ from rest_framework import parsers
from rest_framework.exceptions import ParseError
class OrderedDictLoader(yaml.SafeLoader):
"""
This yaml loader is used to deal with current pyYAML (3.12) not supporting
custom object pairs hook. Remove it when new version adds that support.
"""
def construct_mapping(self, node, deep=False):
if isinstance(node, yaml.nodes.MappingNode):
self.flatten_mapping(node)
else:
raise yaml.constructor.ConstructorError(
None, None,
"expected a mapping node, but found %s" % node.id,
node.start_mark
)
mapping = OrderedDict()
for key_node, value_node in node.value:
key = self.construct_object(key_node, deep=deep)
try:
hash(key)
except TypeError as exc:
raise yaml.constructor.ConstructorError(
"while constructing a mapping", node.start_mark,
"found unacceptable key (%s)" % exc, key_node.start_mark
)
value = self.construct_object(value_node, deep=deep)
mapping[key] = value
return mapping
class JSONParser(parsers.JSONParser):
"""
Parses JSON-serialized data, preserving order of dictionary keys.

View File

@@ -17,7 +17,7 @@ logger = logging.getLogger('awx.api.permissions')
__all__ = ['ModelAccessPermission', 'JobTemplateCallbackPermission',
'TaskPermission', 'ProjectUpdatePermission', 'InventoryInventorySourcesUpdatePermission',
'UserPermission', 'IsSuperUser']
'UserPermission', 'IsSuperUser', 'InstanceGroupTowerPermission',]
class ModelAccessPermission(permissions.BasePermission):
@@ -103,7 +103,8 @@ class ModelAccessPermission(permissions.BasePermission):
return False
# Always allow superusers
if getattr(view, 'always_allow_superuser', True) and request.user.is_superuser:
if getattr(view, 'always_allow_superuser', True) and request.user.is_superuser \
and not hasattr(request.user, 'oauth_scopes'):
return True
# Check if view supports the request method before checking permission
@@ -226,3 +227,11 @@ class IsSuperUser(permissions.BasePermission):
def has_permission(self, request, view):
return request.user and request.user.is_superuser
class InstanceGroupTowerPermission(ModelAccessPermission):
def has_object_permission(self, request, view, obj):
if request.method == 'DELETE' and obj.name == "tower":
return False
return super(InstanceGroupTowerPermission, self).has_object_permission(request, view, obj)

File diff suppressed because it is too large Load Diff

View File

@@ -60,9 +60,10 @@ _Added in AWX 1.4_
?related__search=findme
Note: If you want to provide more than one search terms, please use multiple
Note: If you want to provide more than one search term, multiple
search fields with the same key, like `?related__search=foo&related__search=bar`,
All search terms with the same key will be ORed together.
will be ORed together. Terms separated by commas, like `?related__search=foo,bar`
will be ANDed together.
## Filtering

View File

@@ -0,0 +1,122 @@
# Token Handling using OAuth2
This page lists OAuth 2 utility endpoints used for authorization, token refresh and revoke.
Note endpoints other than `/api/o/authorize/` are not meant to be used in browsers and do not
support HTTP GET. The endpoints here strictly follow
[RFC specs for OAuth2](https://tools.ietf.org/html/rfc6749), so please use that for detailed
reference. Note AWX net location default to `http://localhost:8013` in examples:
## Create Token for an Application using Authorization code grant type
Given an application "AuthCodeApp" of grant type `authorization-code`,
from the client app, the user makes a GET to the Authorize endpoint with
* `response_type`
* `client_id`
* `redirect_uris`
* `scope`
AWX will respond with the authorization `code` and `state`
to the redirect_uri specified in the application. The client application will then make a POST to the
`api/o/token/` endpoint on AWX with
* `code`
* `client_id`
* `client_secret`
* `grant_type`
* `redirect_uri`
AWX will respond with the `access_token`, `token_type`, `refresh_token`, and `expires_in`. For more
information on testing this flow, refer to [django-oauth-toolkit](http://django-oauth-toolkit.readthedocs.io/en/latest/tutorial/tutorial_01.html#test-your-authorization-server).
## Create Token for an Application using Implicit grant type
Suppose we have an application "admin's app" of grant type `implicit`.
In API browser, first make sure the user is logged in via session auth, then visit authorization
endpoint with given parameters:
```text
http://localhost:8013/api/o/authorize/?response_type=token&client_id=L0uQQWW8pKX51hoqIRQGsuqmIdPi2AcXZ9EJRGmj&scope=read
```
Here the value of `client_id` should be the same as that of `client_id` field of underlying application.
On success, an authorization page should be displayed asking the logged in user to grant/deny the access token.
Once the user clicks on 'grant', the API browser will try POSTing to the same endpoint with the same parameters
in POST body, on success a 302 redirect will be returned.
## Create Token for an Application using Password grant type
Log in is not required for `password` grant type, so a simple `curl` can be used to acquire a personal access token
via `/api/o/token/` with
* `grant_type`: Required to be "password"
* `username`
* `password`
* `client_id`: Associated application must have grant_type "password"
* `client_secret`
For example:
```bash
curl -X POST \
-d "grant_type=password&username=<username>&password=<password>&scope=read" \
-u "gwSPoasWSdNkMDtBN3Hu2WYQpPWCO9SwUEsKK22l:fI6ZpfocHYBGfm1tP92r0yIgCyfRdDQt0Tos9L8a4fNsJjQQMwp9569e
IaUBsaVDgt2eiwOGe0bg5m5vCSstClZmtdy359RVx2rQK5YlIWyPlrolpt2LEpVeKXWaiybo" \
http://localhost:8013/api/o/token/ -i
```
In the above post request, parameters `username` and `password` are username and password of the related
AWX user of the underlying application, and the authentication information is of format
`<client_id>:<client_secret>`, where `client_id` and `client_secret` are the corresponding fields of
underlying application.
Upon success, access token, refresh token and other information are given in the response body in JSON
format:
```text
{
"access_token": "9epHOqHhnXUcgYK8QanOmUQPSgX92g",
"token_type": "Bearer",
"expires_in": 31536000000,
"refresh_token": "jMRX6QvzOTf046KHee3TU5mT3nyXsz",
"scope": "read"
}
```
## Refresh an existing access token
The `/api/o/token/` endpoint is used for refreshing access token:
```bash
curl -X POST \
-d "grant_type=refresh_token&refresh_token=AL0NK9TTpv0qp54dGbC4VUZtsZ9r8z" \
-u "gwSPoasWSdNkMDtBN3Hu2WYQpPWCO9SwUEsKK22l:fI6ZpfocHYBGfm1tP92r0yIgCyfRdDQt0Tos9L8a4fNsJjQQMwp9569eIaUBsaVDgt2eiwOGe0bg5m5vCSstClZmtdy359RVx2rQK5YlIWyPlrolpt2LEpVeKXWaiybo" \
http://localhost:8013/api/o/token/ -i
```
In the above post request, `refresh_token` is provided by `refresh_token` field of the access token
above. The authentication information is of format `<client_id>:<client_secret>`, where `client_id`
and `client_secret` are the corresponding fields of underlying related application of the access token.
Upon success, the new (refreshed) access token with the same scope information as the previous one is
given in the response body in JSON format:
```text
{
"access_token": "NDInWxGJI4iZgqpsreujjbvzCfJqgR",
"token_type": "Bearer",
"expires_in": 31536000000,
"refresh_token": "DqOrmz8bx3srlHkZNKmDpqA86bnQkT",
"scope": "read write"
}
```
Internally, the refresh operation deletes the existing token and a new token is created immediately
after, with information like scope and related application identical to the original one. We can
verify by checking the new token is present at the `api/v2/tokens` endpoint.
## Revoke an access token
Revoking an access token is the same as deleting the token resource object.
Revoking is done by POSTing to `/api/o/revoke_token/` with the token to revoke as parameter:
```bash
curl -X POST -d "token=rQONsve372fQwuc2pn76k3IHDCYpi7" \
-u "gwSPoasWSdNkMDtBN3Hu2WYQpPWCO9SwUEsKK22l:fI6ZpfocHYBGfm1tP92r0yIgCyfRdDQt0Tos9L8a4fNsJjQQMwp9569eIaUBsaVDgt2eiwOGe0bg5m5vCSstClZmtdy359RVx2rQK5YlIWyPlrolpt2LEpVeKXWaiybo" \
http://localhost:8013/api/o/revoke_token/ -i
```
`200 OK` means a successful delete.

View File

@@ -1,43 +0,0 @@
{% ifmeth POST %}
# Generate an Auth Token
Make a POST request to this resource with `username` and `password` fields to
obtain an authentication token to use for subsequent requests.
Example JSON to POST (content type is `application/json`):
{"username": "user", "password": "my pass"}
Example form data to post (content type is `application/x-www-form-urlencoded`):
username=user&password=my%20pass
If the username and password provided are valid, the response will contain a
`token` field with the authentication token to use and an `expires` field with
the timestamp when the token will expire:
{
"token": "8f17825cf08a7efea124f2638f3896f6637f8745",
"expires": "2013-09-05T21:46:35.729Z"
}
Otherwise, the response will indicate the error that occurred and return a 4xx
status code.
For subsequent requests, pass the token via the HTTP `Authorization` request
header:
Authorization: Token 8f17825cf08a7efea124f2638f3896f6637f8745
The auth token is only valid when used from the same remote address and user
agent that originally obtained it.
Each request that uses the token for authentication will refresh its expiration
timestamp and keep it from expiring. A token only expires when it is not used
for the configured timeout interval (default 1800 seconds).
{% endifmeth %}
{% ifmeth DELETE %}
# Delete an Auth Token
A DELETE request with the token header set will cause the token to be
invalidated and no further requests can be made with it.
{% endifmeth %}

View File

@@ -12,12 +12,6 @@ For example on `cleanup_jobs` and `cleanup_activitystream`:
Which will act on data older than 30 days.
For `cleanup_facts`:
`{"extra_vars": {"older_than": "4w", "granularity": "3d"}}`
Which will reduce the granularity of scan data to one scan per 3 days when the data is older than 4w.
For `cleanup_activitystream` and `cleanup_jobs` commands, providing
`"dry_run": true` inside of `extra_vars` will show items that will be
removed without deleting them.
@@ -27,7 +21,6 @@ applicable either when running it from the command line or launching its
system job template with empty `extra_vars`.
- Defaults for `cleanup_activitystream`: days=90
- Defaults for `cleanup_facts`: older_than="30d", granularity="1w"
- Defaults for `cleanup_jobs`: days=90
If successful, the response status code will be 202. If the job cannot be

View File

@@ -10,6 +10,7 @@ from awx.api.views import (
InventorySourceUpdatesList,
InventorySourceActivityStreamList,
InventorySourceSchedulesList,
InventorySourceCredentialsList,
InventorySourceGroupsList,
InventorySourceHostsList,
InventorySourceNotificationTemplatesAnyList,
@@ -25,6 +26,7 @@ urls = [
url(r'^(?P<pk>[0-9]+)/inventory_updates/$', InventorySourceUpdatesList.as_view(), name='inventory_source_updates_list'),
url(r'^(?P<pk>[0-9]+)/activity_stream/$', InventorySourceActivityStreamList.as_view(), name='inventory_source_activity_stream_list'),
url(r'^(?P<pk>[0-9]+)/schedules/$', InventorySourceSchedulesList.as_view(), name='inventory_source_schedules_list'),
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(),

View File

@@ -9,6 +9,7 @@ from awx.api.views import (
InventoryUpdateCancel,
InventoryUpdateStdout,
InventoryUpdateNotificationsList,
InventoryUpdateCredentialsList,
InventoryUpdateEventsList,
)
@@ -19,6 +20,7 @@ urls = [
url(r'^(?P<pk>[0-9]+)/cancel/$', InventoryUpdateCancel.as_view(), name='inventory_update_cancel'),
url(r'^(?P<pk>[0-9]+)/stdout/$', InventoryUpdateStdout.as_view(), name='inventory_update_stdout'),
url(r'^(?P<pk>[0-9]+)/notifications/$', InventoryUpdateNotificationsList.as_view(), name='inventory_update_notifications_list'),
url(r'^(?P<pk>[0-9]+)/credentials/$', InventoryUpdateCredentialsList.as_view(), name='inventory_update_credentials_list'),
url(r'^(?P<pk>[0-9]+)/events/$', InventoryUpdateEventsList.as_view(), name='inventory_update_events_list'),
]

18
awx/api/urls/oauth.py Normal file
View File

@@ -0,0 +1,18 @@
# Copyright (c) 2017 Ansible, Inc.
# All Rights Reserved.
from django.conf.urls import url
from oauth2_provider.urls import base_urlpatterns
from awx.api.views import (
ApiOAuthAuthorizationRootView,
)
urls = [
url(r'^$', ApiOAuthAuthorizationRootView.as_view(), name='oauth_authorization_root_view'),
] + base_urlpatterns
__all__ = ['urls']

View File

@@ -21,6 +21,7 @@ from awx.api.views import (
OrganizationInstanceGroupsList,
OrganizationObjectRolesList,
OrganizationAccessList,
OrganizationApplicationList,
)
@@ -45,6 +46,7 @@ urls = [
url(r'^(?P<pk>[0-9]+)/instance_groups/$', OrganizationInstanceGroupsList.as_view(), name='organization_instance_groups_list'),
url(r'^(?P<pk>[0-9]+)/object_roles/$', OrganizationObjectRolesList.as_view(), name='organization_object_roles_list'),
url(r'^(?P<pk>[0-9]+)/access_list/$', OrganizationAccessList.as_view(), name='organization_access_list'),
url(r'^(?P<pk>[0-9]+)/applications/$', OrganizationApplicationList.as_view(), name='organization_applications_list'),
]
__all__ = ['urls']

View File

@@ -5,6 +5,10 @@ from __future__ import absolute_import, unicode_literals
from django.conf import settings
from django.conf.urls import include, url
from awx.api.generics import (
LoggedLoginView,
LoggedLogoutView,
)
from awx.api.views import (
ApiRootView,
ApiV1RootView,
@@ -12,7 +16,6 @@ from awx.api.views import (
ApiV1PingView,
ApiV1ConfigView,
AuthView,
AuthTokenView,
UserMeList,
DashboardView,
DashboardJobsGraphView,
@@ -25,6 +28,10 @@ from awx.api.views import (
JobTemplateExtraCredentialsList,
SchedulePreview,
ScheduleZoneInfo,
OAuth2ApplicationList,
OAuth2TokenList,
ApplicationOAuth2TokenList,
OAuth2ApplicationDetail,
)
from .organization import urls as organization_urls
@@ -60,6 +67,8 @@ from .schedule import urls as schedule_urls
from .activity_stream import urls as activity_stream_urls
from .instance import urls as instance_urls
from .instance_group import urls as instance_group_urls
from .user_oauth import urls as user_oauth_urls
from .oauth import urls as oauth_urls
v1_urls = [
@@ -67,7 +76,6 @@ v1_urls = [
url(r'^ping/$', ApiV1PingView.as_view(), name='api_v1_ping_view'),
url(r'^config/$', ApiV1ConfigView.as_view(), name='api_v1_config_view'),
url(r'^auth/$', AuthView.as_view()),
url(r'^authtoken/$', AuthTokenView.as_view(), name='auth_token_view'),
url(r'^me/$', UserMeList.as_view(), name='user_me_list'),
url(r'^dashboard/$', DashboardView.as_view(), name='dashboard_view'),
url(r'^dashboard/graphs/jobs/$', DashboardJobsGraphView.as_view(), name='dashboard_jobs_graph_view'),
@@ -118,6 +126,11 @@ v2_urls = [
url(r'^job_templates/(?P<pk>[0-9]+)/credentials/$', JobTemplateCredentialsList.as_view(), name='job_template_credentials_list'),
url(r'^schedules/preview/$', SchedulePreview.as_view(), name='schedule_rrule'),
url(r'^schedules/zoneinfo/$', ScheduleZoneInfo.as_view(), name='schedule_zoneinfo'),
url(r'^applications/$', OAuth2ApplicationList.as_view(), name='o_auth2_application_list'),
url(r'^applications/(?P<pk>[0-9]+)/$', OAuth2ApplicationDetail.as_view(), name='o_auth2_application_detail'),
url(r'^applications/(?P<pk>[0-9]+)/tokens/$', ApplicationOAuth2TokenList.as_view(), name='application_o_auth2_token_list'),
url(r'^tokens/$', OAuth2TokenList.as_view(), name='o_auth2_token_list'),
url(r'^', include(user_oauth_urls)),
]
app_name = 'api'
@@ -125,6 +138,14 @@ urlpatterns = [
url(r'^$', ApiRootView.as_view(), name='api_root_view'),
url(r'^(?P<version>(v2))/', include(v2_urls)),
url(r'^(?P<version>(v1|v2))/', include(v1_urls)),
url(r'^login/$', LoggedLoginView.as_view(
template_name='rest_framework/login.html',
extra_context={'inside_login_context': True}
), name='login'),
url(r'^logout/$', LoggedLogoutView.as_view(
next_page='/api/', redirect_field_name='next'
), name='logout'),
url(r'^o/', include(oauth_urls)),
]
if settings.SETTINGS_MODULE == 'awx.settings.development':
from awx.api.swagger import SwaggerSchemaView

View File

@@ -14,9 +14,12 @@ from awx.api.views import (
UserRolesList,
UserActivityStreamList,
UserAccessList,
OAuth2ApplicationList,
OAuth2TokenList,
OAuth2PersonalTokenList,
UserAuthorizedTokenList,
)
urls = [
url(r'^$', UserList.as_view(), name='user_list'),
url(r'^(?P<pk>[0-9]+)/$', UserDetail.as_view(), name='user_detail'),
@@ -28,6 +31,11 @@ urls = [
url(r'^(?P<pk>[0-9]+)/roles/$', UserRolesList.as_view(), name='user_roles_list'),
url(r'^(?P<pk>[0-9]+)/activity_stream/$', UserActivityStreamList.as_view(), name='user_activity_stream_list'),
url(r'^(?P<pk>[0-9]+)/access_list/$', UserAccessList.as_view(), name='user_access_list'),
url(r'^(?P<pk>[0-9]+)/applications/$', OAuth2ApplicationList.as_view(), name='o_auth2_application_list'),
url(r'^(?P<pk>[0-9]+)/tokens/$', OAuth2TokenList.as_view(), name='o_auth2_token_list'),
url(r'^(?P<pk>[0-9]+)/authorized_tokens/$', UserAuthorizedTokenList.as_view(), name='user_authorized_token_list'),
url(r'^(?P<pk>[0-9]+)/personal_tokens/$', OAuth2PersonalTokenList.as_view(), name='o_auth2_personal_token_list'),
]
__all__ = ['urls']

View File

@@ -0,0 +1,49 @@
# Copyright (c) 2017 Ansible, Inc.
# All Rights Reserved.
from django.conf.urls import url
from awx.api.views import (
OAuth2ApplicationList,
OAuth2ApplicationDetail,
ApplicationOAuth2TokenList,
OAuth2ApplicationActivityStreamList,
OAuth2TokenList,
OAuth2TokenDetail,
OAuth2TokenActivityStreamList,
OAuth2PersonalTokenList
)
urls = [
url(r'^applications/$', OAuth2ApplicationList.as_view(), name='o_auth2_application_list'),
url(
r'^applications/(?P<pk>[0-9]+)/$',
OAuth2ApplicationDetail.as_view(),
name='o_auth2_application_detail'
),
url(
r'^applications/(?P<pk>[0-9]+)/tokens/$',
ApplicationOAuth2TokenList.as_view(),
name='o_auth2_application_token_list'
),
url(
r'^applications/(?P<pk>[0-9]+)/activity_stream/$',
OAuth2ApplicationActivityStreamList.as_view(),
name='o_auth2_application_activity_stream_list'
),
url(r'^tokens/$', OAuth2TokenList.as_view(), name='o_auth2_token_list'),
url(
r'^tokens/(?P<pk>[0-9]+)/$',
OAuth2TokenDetail.as_view(),
name='o_auth2_token_detail'
),
url(
r'^tokens/(?P<pk>[0-9]+)/activity_stream/$',
OAuth2TokenActivityStreamList.as_view(),
name='o_auth2_token_activity_stream_list'
),
url(r'^personal_tokens/$', OAuth2PersonalTokenList.as_view(), name='o_auth2_personal_token_list'),
]
__all__ = ['urls']

View File

@@ -14,17 +14,17 @@ from base64 import b64encode
from collections import OrderedDict, Iterable
import six
# Django
from django.conf import settings
from django.core.exceptions import FieldError, ObjectDoesNotExist
from django.db.models import Q, Count, F
from django.db import IntegrityError, transaction
from django.shortcuts import get_object_or_404
from django.utils.encoding import smart_text, force_text
from django.utils.encoding import smart_text
from django.utils.safestring import mark_safe
from django.utils.timezone import now
from django.views.decorators.csrf import csrf_exempt
from django.views.decorators.cache import never_cache
from django.template.loader import render_to_string
from django.http import HttpResponse
from django.contrib.contenttypes.models import ContentType
@@ -53,6 +53,9 @@ import ansiconv
# Python Social Auth
from social_core.backends.utils import load_backends
# Django OAuth Toolkit
from oauth2_provider.models import get_access_token_model
import pytz
from wsgiref.util import FileWrapper
@@ -60,11 +63,10 @@ from wsgiref.util import FileWrapper
from awx.main.tasks import send_notifications, handle_ha_toplogy_changes
from awx.main.access import get_user_queryset
from awx.main.ha import is_ha_environment
from awx.api.authentication import TokenGetAuthentication
from awx.api.filters import V1CredentialFilterBackend
from awx.api.generics import get_view_name
from awx.api.generics import * # noqa
from awx.api.versioning import reverse, get_request_version
from awx.api.versioning import reverse, get_request_version, drf_reverse
from awx.conf.license import get_license, feature_enabled, feature_exists, LicenseForbids
from awx.main.models import * # noqa
from awx.main.utils import * # noqa
@@ -75,14 +77,21 @@ from awx.main.utils import (
from awx.main.utils.encryption import encrypt_value
from awx.main.utils.filters import SmartFilter
from awx.main.utils.insights import filter_insights_api_response
from awx.api.permissions import * # noqa
from awx.main.redact import UriCleaner
from awx.api.permissions import (
JobTemplateCallbackPermission,
TaskPermission,
ProjectUpdatePermission,
InventoryInventorySourcesUpdatePermission,
UserPermission,
InstanceGroupTowerPermission,
)
from awx.api.renderers import * # noqa
from awx.api.serializers import * # noqa
from awx.api.metadata import RoleMetadata, JobTypeMetadata
from awx.main.consumers import emit_channel_notification
from awx.main.models.unified_jobs import ACTIVE_STATES
from awx.main.constants import ACTIVE_STATES
from awx.main.scheduler.tasks import run_job_complete
from awx.api.exceptions import ActiveJobConflict
logger = logging.getLogger('awx.api.views')
@@ -144,6 +153,16 @@ class UnifiedJobDeletionMixin(object):
# Still allow deletion of new status, because these can be manually created
if obj.status in ACTIVE_STATES and obj.status != 'new':
raise PermissionDenied(detail=_("Cannot delete running job resource."))
elif not obj.event_processing_finished:
# Prohibit deletion if job events are still coming in
if obj.finished and now() < obj.finished + dateutil.relativedelta.relativedelta(minutes=1):
# less than 1 minute has passed since job finished and events are not in
return Response({"error": _("Job has not finished processing events.")},
status=status.HTTP_400_BAD_REQUEST)
else:
# if it has been > 1 minute, events are probably lost
logger.warning('Allowing deletion of {} through the API without all events '
'processed.'.format(obj.log_format))
obj.delete()
return Response(status=status.HTTP_204_NO_CONTENT)
@@ -183,9 +202,26 @@ class InstanceGroupMembershipMixin(object):
return response
class RelatedJobsPreventDeleteMixin(object):
def perform_destroy(self, obj):
self.check_related_active_jobs(obj)
return super(RelatedJobsPreventDeleteMixin, self).perform_destroy(obj)
def check_related_active_jobs(self, obj):
active_jobs = obj.get_active_jobs()
if len(active_jobs) > 0:
raise ActiveJobConflict(active_jobs)
time_cutoff = now() - dateutil.relativedelta.relativedelta(minutes=1)
recent_jobs = obj._get_related_jobs().filter(finished__gte = time_cutoff)
for unified_job in recent_jobs.get_real_instances():
if not unified_job.event_processing_finished:
raise PermissionDenied(_(
'Related job {} is still processing events.'
).format(unified_job.log_format))
class ApiRootView(APIView):
authentication_classes = []
permission_classes = (AllowAny,)
view_name = _('REST API')
versioning_class = None
@@ -204,19 +240,33 @@ class ApiRootView(APIView):
if feature_enabled('rebranding'):
data['custom_logo'] = settings.CUSTOM_LOGO
data['custom_login_info'] = settings.CUSTOM_LOGIN_INFO
data['oauth2'] = drf_reverse('api:oauth_authorization_root_view')
return Response(data)
class ApiOAuthAuthorizationRootView(APIView):
permission_classes = (AllowAny,)
view_name = _("API OAuth 2 Authorization Root")
versioning_class = None
swagger_topic = 'Authentication'
def get(self, request, format=None):
data = OrderedDict()
data['authorize'] = drf_reverse('api:authorize')
data['token'] = drf_reverse('api:token')
data['revoke_token'] = drf_reverse('api:revoke-token')
return Response(data)
class ApiVersionRootView(APIView):
authentication_classes = []
permission_classes = (AllowAny,)
swagger_topic = 'Versioning'
def get(self, request, format=None):
''' List top level resources '''
data = OrderedDict()
data['authtoken'] = reverse('api:auth_token_view', request=request)
data['ping'] = reverse('api:api_v1_ping_view', request=request)
data['instances'] = reverse('api:instance_list', request=request)
data['instance_groups'] = reverse('api:instance_group_list', request=request)
@@ -232,6 +282,8 @@ class ApiVersionRootView(APIView):
data['credentials'] = reverse('api:credential_list', request=request)
if get_request_version(request) > 1:
data['credential_types'] = reverse('api:credential_type_list', request=request)
data['applications'] = reverse('api:o_auth2_application_list', request=request)
data['tokens'] = reverse('api:o_auth2_token_list', request=request)
data['inventory'] = reverse('api:inventory_list', request=request)
data['inventory_scripts'] = reverse('api:inventory_script_list', request=request)
data['inventory_sources'] = reverse('api:inventory_source_list', request=request)
@@ -583,7 +635,7 @@ class InstanceDetail(RetrieveUpdateAPIView):
class InstanceUnifiedJobsList(SubListAPIView):
view_name = _("Instance Running Jobs")
view_name = _("Instance Jobs")
model = UnifiedJob
serializer_class = UnifiedJobSerializer
parent_model = Instance
@@ -611,11 +663,21 @@ class InstanceGroupList(ListCreateAPIView):
serializer_class = InstanceGroupSerializer
class InstanceGroupDetail(RetrieveUpdateDestroyAPIView):
class InstanceGroupDetail(RelatedJobsPreventDeleteMixin, RetrieveUpdateDestroyAPIView):
always_allow_superuser = False
view_name = _("Instance Group Detail")
model = InstanceGroup
serializer_class = InstanceGroupSerializer
permission_classes = (InstanceGroupTowerPermission,)
def destroy(self, request, *args, **kwargs):
instance = self.get_object()
if instance.controller is not None:
raise PermissionDenied(detail=_("Isolated Groups can not be removed from the API"))
if instance.controlled_groups.count():
raise PermissionDenied(detail=_("Instance Groups acting as a controller for an Isolated Group can not be removed from the API"))
return super(InstanceGroupDetail, self).destroy(request, *args, **kwargs)
class InstanceGroupUnifiedJobsList(SubListAPIView):
@@ -780,78 +842,6 @@ class AuthView(APIView):
return Response(data)
class AuthTokenView(APIView):
authentication_classes = []
permission_classes = (AllowAny,)
serializer_class = AuthTokenSerializer
model = AuthToken
swagger_topic = 'Authentication'
def get_serializer(self, *args, **kwargs):
serializer = self.serializer_class(*args, **kwargs)
# Override when called from browsable API to generate raw data form;
# update serializer "validated" data to be displayed by the raw data
# form.
if hasattr(self, '_raw_data_form_marker'):
# Always remove read only fields from serializer.
for name, field in serializer.fields.items():
if getattr(field, 'read_only', None):
del serializer.fields[name]
serializer._data = self.update_raw_data(serializer.data)
return serializer
@never_cache
def post(self, request):
serializer = self.get_serializer(data=request.data)
if serializer.is_valid():
request_hash = AuthToken.get_request_hash(self.request)
try:
token = AuthToken.objects.filter(user=serializer.validated_data['user'],
request_hash=request_hash,
expires__gt=now(),
reason='')[0]
token.refresh()
if 'username' in request.data:
logger.info(smart_text(u"User {} logged in".format(request.data['username'])),
extra=dict(actor=request.data['username']))
except IndexError:
token = AuthToken.objects.create(user=serializer.validated_data['user'],
request_hash=request_hash)
if 'username' in request.data:
logger.info(smart_text(u"User {} logged in".format(request.data['username'])),
extra=dict(actor=request.data['username']))
# Get user un-expired tokens that are not invalidated that are
# over the configured limit.
# Mark them as invalid and inform the user
invalid_tokens = AuthToken.get_tokens_over_limit(serializer.validated_data['user'])
for t in invalid_tokens:
emit_channel_notification('control-limit_reached', dict(group_name='control',
reason=force_text(AuthToken.reason_long('limit_reached')),
token_key=t.key))
t.invalidate(reason='limit_reached')
# Note: This header is normally added in the middleware whenever an
# auth token is included in the request header.
headers = {
'Auth-Token-Timeout': int(settings.AUTH_TOKEN_EXPIRATION),
'Pragma': 'no-cache',
}
return Response({'token': token.key, 'expires': token.expires}, headers=headers)
if 'username' in request.data:
logger.warning(smart_text(u"Login failed for user {}".format(request.data['username'])),
extra=dict(actor=request.data['username']))
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
def delete(self, request):
if 'HTTP_AUTHORIZATION' in request.META:
token_match = re.match("Token\s(.+)", request.META['HTTP_AUTHORIZATION'])
if token_match:
filter_tokens = AuthToken.objects.filter(key=token_match.groups()[0])
if filter_tokens.exists():
filter_tokens[0].invalidate()
return Response(status=status.HTTP_204_NO_CONTENT)
class OrganizationCountsMixin(object):
@@ -968,7 +958,7 @@ class OrganizationList(OrganizationCountsMixin, ListCreateAPIView):
return super(OrganizationList, self).create(request, *args, **kwargs)
class OrganizationDetail(RetrieveUpdateDestroyAPIView):
class OrganizationDetail(RelatedJobsPreventDeleteMixin, RetrieveUpdateDestroyAPIView):
model = Organization
serializer_class = OrganizationSerializer
@@ -1017,6 +1007,8 @@ class OrganizationInventoriesList(SubListAPIView):
class BaseUsersList(SubListCreateAttachDetachAPIView):
def post(self, request, *args, **kwargs):
ret = super(BaseUsersList, self).post( request, *args, **kwargs)
if ret.status_code != 201:
return ret
try:
if ret.data is not None and request.data.get('is_system_auditor', False):
# This is a faux-field that just maps to checking the system
@@ -1262,7 +1254,6 @@ class ProjectList(ListCreateAPIView):
model = Project
serializer_class = ProjectSerializer
capabilities_prefetch = ['admin', 'update']
def get_queryset(self):
projects_qs = Project.accessible_objects(self.request.user, 'read_role')
@@ -1277,20 +1268,11 @@ class ProjectList(ListCreateAPIView):
return projects_qs
class ProjectDetail(RetrieveUpdateDestroyAPIView):
class ProjectDetail(RelatedJobsPreventDeleteMixin, RetrieveUpdateDestroyAPIView):
model = Project
serializer_class = ProjectSerializer
def destroy(self, request, *args, **kwargs):
obj = self.get_object()
can_delete = request.user.can_access(Project, 'delete', obj)
if not can_delete:
raise PermissionDenied(_("Cannot delete project."))
for pu in obj.project_updates.filter(status__in=['new', 'pending', 'waiting', 'running']):
pu.cancel()
return super(ProjectDetail, self).destroy(request, *args, **kwargs)
class ProjectPlaybooks(RetrieveAPIView):
@@ -1554,6 +1536,129 @@ class UserMeList(ListAPIView):
return self.model.objects.filter(pk=self.request.user.pk)
class OAuth2ApplicationList(ListCreateAPIView):
view_name = _("OAuth 2 Applications")
model = OAuth2Application
serializer_class = OAuth2ApplicationSerializer
swagger_topic = 'Authentication'
class OAuth2ApplicationDetail(RetrieveUpdateDestroyAPIView):
view_name = _("OAuth 2 Application Detail")
model = OAuth2Application
serializer_class = OAuth2ApplicationSerializer
swagger_topic = 'Authentication'
class ApplicationOAuth2TokenList(SubListCreateAPIView):
view_name = _("OAuth 2 Application Tokens")
model = OAuth2AccessToken
serializer_class = OAuth2TokenSerializer
parent_model = OAuth2Application
relationship = 'oauth2accesstoken_set'
parent_key = 'application'
swagger_topic = 'Authentication'
class OAuth2ApplicationActivityStreamList(ActivityStreamEnforcementMixin, SubListAPIView):
model = ActivityStream
serializer_class = ActivityStreamSerializer
parent_model = OAuth2Application
relationship = 'activitystream_set'
swagger_topic = 'Authentication'
class OAuth2TokenList(ListCreateAPIView):
view_name = _("OAuth2 Tokens")
model = OAuth2AccessToken
serializer_class = OAuth2TokenSerializer
swagger_topic = 'Authentication'
class OAuth2AuthorizedTokenList(SubListCreateAPIView):
view_name = _("OAuth2 Authorized Access Tokens")
model = OAuth2AccessToken
serializer_class = OAuth2AuthorizedTokenSerializer
parent_model = OAuth2Application
relationship = 'oauth2accesstoken_set'
parent_key = 'application'
swagger_topic = 'Authentication'
def get_queryset(self):
return get_access_token_model().objects.filter(application__isnull=False, user=self.request.user)
class UserAuthorizedTokenList(SubListCreateAPIView):
view_name = _("OAuth2 User Authorized Access Tokens")
model = OAuth2AccessToken
serializer_class = OAuth2AuthorizedTokenSerializer
parent_model = User
relationship = 'oauth2accesstoken_set'
parent_key = 'user'
swagger_topic = 'Authentication'
def get_queryset(self):
return get_access_token_model().objects.filter(application__isnull=False, user=self.request.user)
class OrganizationApplicationList(SubListCreateAPIView):
view_name = _("Organization OAuth2 Applications")
model = OAuth2Application
serializer_class = OAuth2ApplicationSerializer
parent_model = Organization
relationship = 'applications'
parent_key = 'organization'
swagger_topic = 'Authentication'
class OAuth2PersonalTokenList(SubListCreateAPIView):
view_name = _("OAuth2 Personal Access Tokens")
model = OAuth2AccessToken
serializer_class = OAuth2PersonalTokenSerializer
parent_model = User
relationship = 'main_oauth2accesstoken'
parent_key = 'user'
swagger_topic = 'Authentication'
def get_queryset(self):
return get_access_token_model().objects.filter(application__isnull=True, user=self.request.user)
class OAuth2TokenDetail(RetrieveUpdateDestroyAPIView):
view_name = _("OAuth Token Detail")
model = OAuth2AccessToken
serializer_class = OAuth2TokenDetailSerializer
swagger_topic = 'Authentication'
class OAuth2TokenActivityStreamList(ActivityStreamEnforcementMixin, SubListAPIView):
model = ActivityStream
serializer_class = ActivityStreamSerializer
parent_model = OAuth2AccessToken
relationship = 'activitystream_set'
swagger_topic = 'Authentication'
class UserTeamsList(ListAPIView):
model = User
@@ -1590,14 +1695,8 @@ class UserRolesList(SubListAttachDetachAPIView):
if not sub_id:
return super(UserRolesList, self).post(request)
if sub_id == self.request.user.admin_role.pk:
raise PermissionDenied(_('You may not perform any action with your own admin_role.'))
user = get_object_or_400(User, pk=self.kwargs['pk'])
role = get_object_or_400(Role, pk=sub_id)
user_content_type = ContentType.objects.get_for_model(User)
if role.content_type == user_content_type:
raise PermissionDenied(_('You may not change the membership of a users admin_role'))
credential_content_type = ContentType.objects.get_for_model(Credential)
if role.content_type == credential_content_type:
@@ -1770,7 +1869,6 @@ class CredentialList(CredentialViewMixin, ListCreateAPIView):
model = Credential
serializer_class = CredentialSerializerCreate
capabilities_prefetch = ['admin', 'use']
filter_backends = ListCreateAPIView.filter_backends + [V1CredentialFilterBackend]
@@ -1937,7 +2035,6 @@ class InventoryList(ListCreateAPIView):
model = Inventory
serializer_class = InventorySerializer
capabilities_prefetch = ['admin', 'adhoc']
def get_queryset(self):
qs = Inventory.accessible_objects(self.request.user, 'read_role')
@@ -1976,7 +2073,7 @@ class ControlledByScmMixin(object):
return obj
class InventoryDetail(ControlledByScmMixin, RetrieveUpdateDestroyAPIView):
class InventoryDetail(RelatedJobsPreventDeleteMixin, ControlledByScmMixin, RetrieveUpdateDestroyAPIView):
model = Inventory
serializer_class = InventoryDetailSerializer
@@ -1994,6 +2091,7 @@ class InventoryDetail(ControlledByScmMixin, RetrieveUpdateDestroyAPIView):
obj = self.get_object()
if not request.user.can_access(self.model, 'delete', obj):
raise PermissionDenied()
self.check_related_active_jobs(obj) # related jobs mixin
try:
obj.schedule_deletion(getattr(request.user, 'id', None))
return Response(status=status.HTTP_202_ACCEPTED)
@@ -2076,7 +2174,6 @@ class HostList(HostRelatedSearchMixin, ListCreateAPIView):
always_allow_superuser = False
model = Host
serializer_class = HostSerializer
capabilities_prefetch = ['inventory.admin']
def get_queryset(self):
qs = super(HostList, self).get_queryset()
@@ -2093,7 +2190,7 @@ class HostList(HostRelatedSearchMixin, ListCreateAPIView):
return Response(dict(error=_(six.text_type(e))), status=status.HTTP_400_BAD_REQUEST)
class HostDetail(ControlledByScmMixin, RetrieveUpdateDestroyAPIView):
class HostDetail(RelatedJobsPreventDeleteMixin, ControlledByScmMixin, RetrieveUpdateDestroyAPIView):
always_allow_superuser = False
model = Host
@@ -2113,7 +2210,6 @@ class InventoryHostsList(HostRelatedSearchMixin, SubListCreateAttachDetachAPIVie
parent_model = Inventory
relationship = 'hosts'
parent_key = 'inventory'
capabilities_prefetch = ['inventory.admin']
def get_queryset(self):
inventory = self.get_parent_object()
@@ -2290,7 +2386,6 @@ class GroupList(ListCreateAPIView):
model = Group
serializer_class = GroupSerializer
capabilities_prefetch = ['inventory.admin', 'inventory.adhoc']
class EnforceParentRelationshipMixin(object):
@@ -2377,7 +2472,6 @@ class GroupHostsList(HostRelatedSearchMixin,
serializer_class = HostSerializer
parent_model = Group
relationship = 'hosts'
capabilities_prefetch = ['inventory.admin']
def update_raw_data(self, data):
data.pop('inventory', None)
@@ -2404,7 +2498,6 @@ class GroupAllHostsList(HostRelatedSearchMixin, SubListAPIView):
serializer_class = HostSerializer
parent_model = Group
relationship = 'hosts'
capabilities_prefetch = ['inventory.admin']
def get_queryset(self):
parent = self.get_parent_object()
@@ -2436,7 +2529,7 @@ class GroupActivityStreamList(ActivityStreamEnforcementMixin, SubListAPIView):
return qs.filter(Q(group=parent) | Q(host__in=parent.hosts.all()))
class GroupDetail(ControlledByScmMixin, RetrieveUpdateDestroyAPIView):
class GroupDetail(RelatedJobsPreventDeleteMixin, ControlledByScmMixin, RetrieveUpdateDestroyAPIView):
model = Group
serializer_class = GroupSerializer
@@ -2636,20 +2729,11 @@ class InventorySourceList(ListCreateAPIView):
return methods
class InventorySourceDetail(RetrieveUpdateDestroyAPIView):
class InventorySourceDetail(RelatedJobsPreventDeleteMixin, RetrieveUpdateDestroyAPIView):
model = InventorySource
serializer_class = InventorySourceSerializer
def destroy(self, request, *args, **kwargs):
obj = self.get_object()
can_delete = request.user.can_access(InventorySource, 'delete', obj)
if not can_delete:
raise PermissionDenied(_("Cannot delete inventory source."))
for pu in obj.inventory_updates.filter(status__in=['new', 'pending', 'waiting', 'running']):
pu.cancel()
return super(InventorySourceDetail, self).destroy(request, *args, **kwargs)
class InventorySourceSchedulesList(SubListCreateAPIView):
@@ -2703,7 +2787,6 @@ class InventorySourceHostsList(HostRelatedSearchMixin, SubListDestroyAPIView):
parent_model = InventorySource
relationship = 'hosts'
check_sub_obj_permission = False
capabilities_prefetch = ['inventory.admin']
class InventorySourceGroupsList(SubListDestroyAPIView):
@@ -2723,6 +2806,28 @@ class InventorySourceUpdatesList(SubListAPIView):
relationship = 'inventory_updates'
class InventorySourceCredentialsList(SubListAttachDetachAPIView):
parent_model = InventorySource
model = Credential
serializer_class = CredentialSerializer
relationship = 'credentials'
def is_valid_relation(self, parent, sub, created=False):
error = InventorySource.cloud_credential_validation(parent.source, sub)
if error:
return {'msg': error}
if sub.credential_type == 'vault':
# TODO: support this
return {"msg": _("Vault credentials are not yet supported for inventory sources.")}
else:
# Cloud credentials are exclusive with all other cloud credentials
cloud_cred_qs = parent.credentials.exclude(credential_type__kind='vault')
if cloud_cred_qs.exists():
return {'msg': _("Source already has cloud credential assigned.")}
return None
class InventorySourceUpdateView(RetrieveAPIView):
model = InventorySource
@@ -2757,6 +2862,14 @@ class InventoryUpdateDetail(UnifiedJobDeletionMixin, RetrieveDestroyAPIView):
serializer_class = InventoryUpdateSerializer
class InventoryUpdateCredentialsList(SubListAPIView):
parent_model = InventoryUpdate
model = Credential
serializer_class = CredentialSerializer
relationship = 'credentials'
class InventoryUpdateCancel(RetrieveAPIView):
model = InventoryUpdate
@@ -2786,10 +2899,6 @@ class JobTemplateList(ListCreateAPIView):
metadata_class = JobTypeMetadata
serializer_class = JobTemplateSerializer
always_allow_superuser = False
capabilities_prefetch = [
'admin', 'execute',
{'copy': ['project.use', 'inventory.use']}
]
def post(self, request, *args, **kwargs):
ret = super(JobTemplateList, self).post(request, *args, **kwargs)
@@ -2799,7 +2908,7 @@ class JobTemplateList(ListCreateAPIView):
return ret
class JobTemplateDetail(RetrieveUpdateDestroyAPIView):
class JobTemplateDetail(RelatedJobsPreventDeleteMixin, RetrieveUpdateDestroyAPIView):
model = JobTemplate
metadata_class = JobTypeMetadata
@@ -3028,16 +3137,22 @@ class JobTemplateSurveySpec(GenericAPIView):
return Response()
def _validate_spec_data(self, new_spec, old_spec):
if "name" not in new_spec:
return Response(dict(error=_("'name' missing from survey spec.")), status=status.HTTP_400_BAD_REQUEST)
if "description" not in new_spec:
return Response(dict(error=_("'description' missing from survey spec.")), status=status.HTTP_400_BAD_REQUEST)
if "spec" not in new_spec:
return Response(dict(error=_("'spec' missing from survey spec.")), status=status.HTTP_400_BAD_REQUEST)
if not isinstance(new_spec["spec"], list):
return Response(dict(error=_("'spec' must be a list of items.")), status=status.HTTP_400_BAD_REQUEST)
if len(new_spec["spec"]) < 1:
return Response(dict(error=_("'spec' doesn't contain any items.")), status=status.HTTP_400_BAD_REQUEST)
schema_errors = {}
for field, expect_type, type_label in [
('name', six.string_types, 'string'),
('description', six.string_types, 'string'),
('spec', list, 'list of items')]:
if field not in new_spec:
schema_errors['error'] = _("Field '{}' is missing from survey spec.").format(field)
elif not isinstance(new_spec[field], expect_type):
schema_errors['error'] = _("Expected {} for field '{}', received {} type.").format(
type_label, field, type(new_spec[field]).__name__)
if isinstance(new_spec.get('spec', None), list) and len(new_spec["spec"]) < 1:
schema_errors['error'] = _("'spec' doesn't contain any items.")
if schema_errors:
return Response(schema_errors, status=status.HTTP_400_BAD_REQUEST)
variable_set = set()
old_spec_dict = JobTemplate.pivot_spec(old_spec)
@@ -3354,6 +3469,7 @@ class JobTemplateCallback(GenericAPIView):
result = job.signal_start(inventory_sources_already_updated=inventory_sources_already_updated)
if not result:
data = dict(msg=_('Error starting job!'))
job.delete()
return Response(data, status=status.HTTP_400_BAD_REQUEST)
# Return the location of the new job.
@@ -3369,6 +3485,13 @@ class JobTemplateJobsList(SubListCreateAPIView):
relationship = 'jobs'
parent_key = 'job_template'
@property
def allowed_methods(self):
methods = super(JobTemplateJobsList, self).allowed_methods
if get_request_version(getattr(self, 'request', None)) > 1:
methods.remove('POST')
return methods
class JobTemplateInstanceGroupsList(SubListAttachDetachAPIView):
@@ -3546,7 +3669,7 @@ class WorkflowJobTemplateList(WorkflowsEnforcementMixin, ListCreateAPIView):
always_allow_superuser = False
class WorkflowJobTemplateDetail(WorkflowsEnforcementMixin, RetrieveUpdateDestroyAPIView):
class WorkflowJobTemplateDetail(RelatedJobsPreventDeleteMixin, WorkflowsEnforcementMixin, RetrieveUpdateDestroyAPIView):
model = WorkflowJobTemplate
serializer_class = WorkflowJobTemplateSerializer
@@ -4020,6 +4143,22 @@ class JobRelaunch(RetrieveAPIView):
obj_permission_type = 'start'
serializer_class = JobRelaunchSerializer
def update_raw_data(self, data):
data = super(JobRelaunch, self).update_raw_data(data)
try:
obj = self.get_object()
except PermissionDenied:
return data
if obj:
needed_passwords = obj.passwords_needed_to_start
if needed_passwords:
data['credential_passwords'] = {}
for p in needed_passwords:
data['credential_passwords'][p] = u''
else:
data.pop('credential_passwords', None)
return data
@csrf_exempt
@transaction.non_atomic_requests
def dispatch(self, *args, **kwargs):
@@ -4034,15 +4173,22 @@ class JobRelaunch(RetrieveAPIView):
def post(self, request, *args, **kwargs):
obj = self.get_object()
context = self.get_serializer_context()
modified_data = request.data.copy()
modified_data.setdefault('credential_passwords', {})
for password in obj.passwords_needed_to_start:
if password in modified_data:
modified_data['credential_passwords'][password] = modified_data[password]
# Note: is_valid() may modify request.data
# It will remove any key/value pair who's key is not in the 'passwords_needed_to_start' list
serializer = self.serializer_class(data=request.data, context={'obj': obj, 'data': request.data})
serializer = self.serializer_class(data=modified_data, context=context, instance=obj)
if not serializer.is_valid():
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
copy_kwargs = {}
retry_hosts = request.data.get('hosts', None)
retry_hosts = serializer.validated_data.get('hosts', None)
if retry_hosts and retry_hosts != 'all':
if obj.status in ACTIVE_STATES:
return Response({'hosts': _(
@@ -4061,12 +4207,13 @@ class JobRelaunch(RetrieveAPIView):
copy_kwargs['limit'] = ','.join(retry_host_list)
new_job = obj.copy_unified_job(**copy_kwargs)
result = new_job.signal_start(**request.data)
result = new_job.signal_start(**serializer.validated_data['credential_passwords'])
if not result:
data = dict(passwords_needed_to_start=new_job.passwords_needed_to_start)
data = dict(msg=_('Error starting job!'))
new_job.delete()
return Response(data, status=status.HTTP_400_BAD_REQUEST)
else:
data = JobSerializer(new_job, context=self.get_serializer_context()).data
data = JobSerializer(new_job, context=context).data
# Add job key to match what old relaunch returned.
data['job'] = new_job.id
headers = {'Location': new_job.get_absolute_url(request=request)}
@@ -4095,8 +4242,6 @@ class JobCreateSchedule(RetrieveAPIView):
status=status.HTTP_400_BAD_REQUEST)
config = obj.launch_config
if not request.user.can_access(JobLaunchConfig, 'add', {'reference_obj': obj}):
raise PermissionDenied()
# Make up a name for the schedule, guarentee that it is unique
name = 'Auto-generated schedule from job {}'.format(obj.id)
@@ -4109,7 +4254,7 @@ class JobCreateSchedule(RetrieveAPIView):
alt_name = '{} - number {}'.format(name, idx)
name = alt_name
schedule = Schedule.objects.create(
schedule_data = dict(
name=name,
unified_job_template=obj.unified_job_template,
enabled=False,
@@ -4117,11 +4262,18 @@ class JobCreateSchedule(RetrieveAPIView):
extra_data=config.extra_data,
survey_passwords=config.survey_passwords,
inventory=config.inventory,
char_prompts=config.char_prompts
char_prompts=config.char_prompts,
credentials=set(config.credentials.all())
)
schedule.credentials.add(*config.credentials.all())
if not request.user.can_access(Schedule, 'add', schedule_data):
raise PermissionDenied()
creds_list = schedule_data.pop('credentials')
schedule = Schedule.objects.create(**schedule_data)
schedule.credentials.add(*creds_list)
data = ScheduleSerializer(schedule, context=self.get_serializer_context()).data
data.serializer.instance = None # hack to avoid permissions.py assuming this is Job model
headers = {'Location': schedule.get_absolute_url(request=request)}
return Response(data, status=status.HTTP_201_CREATED, headers=headers)
@@ -4197,7 +4349,6 @@ class JobEventHostsList(HostRelatedSearchMixin, SubListAPIView):
parent_model = JobEvent
relationship = 'hosts'
view_name = _('Job Event Hosts List')
capabilities_prefetch = ['inventory.admin']
class BaseJobEventsList(SubListAPIView):
@@ -4295,6 +4446,7 @@ class AdHocCommandList(ListCreateAPIView):
result = ad_hoc_command.signal_start(**request.data)
if not result:
data = dict(passwords_needed_to_start=ad_hoc_command.passwords_needed_to_start)
ad_hoc_command.delete()
return Response(data, status=status.HTTP_400_BAD_REQUEST)
return response
@@ -4387,6 +4539,7 @@ class AdHocCommandRelaunch(GenericAPIView):
result = new_ad_hoc_command.signal_start(**request.data)
if not result:
data = dict(passwords_needed_to_start=new_ad_hoc_command.passwords_needed_to_start)
new_ad_hoc_command.delete()
return Response(data, status=status.HTTP_400_BAD_REQUEST)
else:
data = AdHocCommandSerializer(new_ad_hoc_command, context=self.get_serializer_context()).data
@@ -4491,11 +4644,6 @@ class UnifiedJobTemplateList(ListAPIView):
model = UnifiedJobTemplate
serializer_class = UnifiedJobTemplateSerializer
capabilities_prefetch = [
'admin', 'execute',
{'copy': ['jobtemplate.project.use', 'jobtemplate.inventory.use',
'workflowjobtemplate.organization.workflow_admin']}
]
class UnifiedJobList(ListAPIView):
@@ -4504,9 +4652,17 @@ class UnifiedJobList(ListAPIView):
serializer_class = UnifiedJobListSerializer
class StdoutANSIFilter(object):
def redact_ansi(line):
# Remove ANSI escape sequences used to embed event data.
line = re.sub(r'\x1b\[K(?:[A-Za-z0-9+/=]+\x1b\[\d+D)+\x1b\[K', '', line)
# Remove ANSI color escape sequences.
return re.sub(r'\x1b[^m]*m', '', line)
class StdoutFilter(object):
def __init__(self, fileobj):
self._functions = []
self.fileobj = fileobj
self.extra_data = ''
if hasattr(fileobj, 'close'):
@@ -4518,10 +4674,7 @@ class StdoutANSIFilter(object):
line = self.fileobj.readline(size)
if not line:
break
# Remove ANSI escape sequences used to embed event data.
line = re.sub(r'\x1b\[K(?:[A-Za-z0-9+/=]+\x1b\[\d+D)+\x1b\[K', '', line)
# Remove ANSI color escape sequences.
line = re.sub(r'\x1b[^m]*m', '', line)
line = self.process_line(line)
data += line
if size > 0 and len(data) > size:
self.extra_data = data[size:]
@@ -4530,10 +4683,18 @@ class StdoutANSIFilter(object):
self.extra_data = ''
return data
def register(self, func):
self._functions.append(func)
def process_line(self, line):
for func in self._functions:
line = func(line)
return line
class UnifiedJobStdout(RetrieveAPIView):
authentication_classes = [TokenGetAuthentication] + api_settings.DEFAULT_AUTHENTICATION_CLASSES
authentication_classes = api_settings.DEFAULT_AUTHENTICATION_CLASSES
serializer_class = UnifiedJobStdoutSerializer
renderer_classes = [BrowsableAPIRenderer, renderers.StaticHTMLRenderer,
PlainTextRenderer, AnsiTextRenderer,
@@ -4587,9 +4748,12 @@ class UnifiedJobStdout(RetrieveAPIView):
suffix='.ansi' if target_format == 'ansi_download' else ''
)
content_fd = unified_job.result_stdout_raw_handle(enforce_max_bytes=False)
redactor = StdoutFilter(content_fd)
if target_format == 'txt_download':
content_fd = StdoutANSIFilter(content_fd)
response = HttpResponse(FileWrapper(content_fd), content_type='text/plain')
redactor.register(redact_ansi)
if type(unified_job) == ProjectUpdate:
redactor.register(UriCleaner.remove_sensitive)
response = HttpResponse(FileWrapper(redactor), content_type='text/plain')
response["Content-Disposition"] = 'attachment; filename="{}"'.format(filename)
return response
else:
@@ -4597,7 +4761,7 @@ class UnifiedJobStdout(RetrieveAPIView):
except StdoutMaxBytesExceeded as e:
response_message = _(
"Standard Output too large to display ({text_size} bytes), "
"only download supported for sizes over {supported_size} bytes").format(
"only download supported for sizes over {supported_size} bytes.").format(
text_size=e.total, supported_size=e.supported
)
if request.accepted_renderer.format == 'json':
@@ -4768,12 +4932,6 @@ class RoleUsersList(SubListAttachDetachAPIView):
user = get_object_or_400(User, pk=sub_id)
role = self.get_parent_object()
if role == self.request.user.admin_role:
raise PermissionDenied(_('You may not perform any action with your own admin_role.'))
user_content_type = ContentType.objects.get_for_model(User)
if role.content_type == user_content_type:
raise PermissionDenied(_('You may not change the membership of a users admin_role'))
credential_content_type = ContentType.objects.get_for_model(Credential)
if role.content_type == credential_content_type:

View File

@@ -1,3 +1,4 @@
# Copyright (c) 2017 Ansible, Inc.
# All Rights Reserved.
@@ -5,6 +6,7 @@ from __future__ import absolute_import, unicode_literals
import os
from celery import Celery
from django.conf import settings # noqa
try:
@@ -16,8 +18,8 @@ except ImportError: # pragma: no cover
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'awx.settings.%s' % MODE)
app = Celery('awx')
app.config_from_object('django.conf:settings', namespace='CELERY')
app.autodiscover_tasks()
app.config_from_object('django.conf:settings')
app.autodiscover_tasks(lambda: settings.INSTALLED_APPS)
if __name__ == '__main__':
app.start()

View File

@@ -11,8 +11,16 @@ class ConfConfig(AppConfig):
name = 'awx.conf'
verbose_name = _('Configuration')
def configure_oauth2_provider(self, settings):
from oauth2_provider import settings as o_settings
o_settings.oauth2_settings = o_settings.OAuth2ProviderSettings(
settings.OAUTH2_PROVIDER, o_settings.DEFAULTS,
o_settings.IMPORT_STRINGS, o_settings.MANDATORY
)
def ready(self):
self.module.autodiscover()
from .settings import SettingsWrapper
SettingsWrapper.initialize()
configure_external_logger(settings)
self.configure_oauth2_provider(settings)

View File

@@ -305,7 +305,7 @@ class SettingsWrapper(UserSettingsHolder):
settings_to_cache['_awx_conf_preload_expires'] = self._awx_conf_preload_expires
self.cache.set_many(settings_to_cache, timeout=SETTING_CACHE_TIMEOUT)
def _get_local(self, name):
def _get_local(self, name, validate=True):
self._preload_cache()
cache_key = Setting.get_cache_key(name)
try:
@@ -368,7 +368,10 @@ class SettingsWrapper(UserSettingsHolder):
field.run_validators(internal_value)
return internal_value
else:
return field.run_validation(value)
if validate:
return field.run_validation(value)
else:
return value
except Exception:
logger.warning(
'The current value "%r" for setting "%s" is invalid.',

View File

@@ -28,6 +28,7 @@ import uuid
from copy import copy
# Ansible
from ansible import constants as C
from ansible.plugins.callback import CallbackBase
from ansible.plugins.callback.default import CallbackModule as DefaultCallbackModule
@@ -126,16 +127,19 @@ class BaseCallbackModule(CallbackBase):
task=(task.name or task.action),
task_uuid=str(task._uuid),
task_action=task.action,
task_args='',
)
try:
task_ctx['task_path'] = task.get_path()
except AttributeError:
pass
if task.no_log:
task_ctx['task_args'] = "the output has been hidden due to the fact that 'no_log: true' was specified for this result"
else:
task_args = ', '.join(('%s=%s' % a for a in task.args.items()))
task_ctx['task_args'] = task_args
if C.DISPLAY_ARGS_TO_STDOUT:
if task.no_log:
task_ctx['task_args'] = "the output has been hidden due to the fact that 'no_log: true' was specified for this result"
else:
task_args = ', '.join(('%s=%s' % a for a in task.args.items()))
task_ctx['task_args'] = task_args
if getattr(task, '_role', None):
task_role = task._role._role_name
else:
@@ -274,15 +278,14 @@ class BaseCallbackModule(CallbackBase):
with self.capture_event_data('playbook_on_no_hosts_remaining'):
super(BaseCallbackModule, self).v2_playbook_on_no_hosts_remaining()
def v2_playbook_on_notify(self, result, handler):
# NOTE: Not used by Ansible 2.x.
def v2_playbook_on_notify(self, handler, host):
# NOTE: Not used by Ansible < 2.5.
event_data = dict(
host=result._host.get_name(),
task=result._task,
handler=handler,
host=host.get_name(),
handler=handler.get_name(),
)
with self.capture_event_data('playbook_on_notify', **event_data):
super(BaseCallbackModule, self).v2_playbook_on_notify(result, handler)
super(BaseCallbackModule, self).v2_playbook_on_notify(handler, host)
'''
ansible_stats is, retoractively, added in 2.2
@@ -315,6 +318,14 @@ class BaseCallbackModule(CallbackBase):
with self.capture_event_data('playbook_on_stats', **event_data):
super(BaseCallbackModule, self).v2_playbook_on_stats(stats)
@staticmethod
def _get_event_loop(task):
if hasattr(task, 'loop_with'): # Ansible >=2.5
return task.loop_with
elif hasattr(task, 'loop'): # Ansible <2.4
return task.loop
return None
def v2_runner_on_ok(self, result):
# FIXME: Display detailed results or not based on verbosity.
@@ -328,7 +339,7 @@ class BaseCallbackModule(CallbackBase):
remote_addr=result._host.address,
task=result._task,
res=result._result,
event_loop=result._task.loop if hasattr(result._task, 'loop') else None,
event_loop=self._get_event_loop(result._task),
)
with self.capture_event_data('runner_on_ok', **event_data):
super(BaseCallbackModule, self).v2_runner_on_ok(result)
@@ -341,7 +352,7 @@ class BaseCallbackModule(CallbackBase):
res=result._result,
task=result._task,
ignore_errors=ignore_errors,
event_loop=result._task.loop if hasattr(result._task, 'loop') else None,
event_loop=self._get_event_loop(result._task),
)
with self.capture_event_data('runner_on_failed', **event_data):
super(BaseCallbackModule, self).v2_runner_on_failed(result, ignore_errors)
@@ -351,7 +362,7 @@ class BaseCallbackModule(CallbackBase):
host=result._host.get_name(),
remote_addr=result._host.address,
task=result._task,
event_loop=result._task.loop if hasattr(result._task, 'loop') else None,
event_loop=self._get_event_loop(result._task),
)
with self.capture_event_data('runner_on_skipped', **event_data):
super(BaseCallbackModule, self).v2_runner_on_skipped(result)

View File

@@ -28,6 +28,7 @@ CALLBACK = os.path.splitext(os.path.basename(__file__))[0]
PLUGINS = os.path.dirname(__file__)
with mock.patch.dict(os.environ, {'ANSIBLE_STDOUT_CALLBACK': CALLBACK,
'ANSIBLE_CALLBACK_PLUGINS': PLUGINS}):
from ansible import __version__ as ANSIBLE_VERSION
from ansible.cli.playbook import PlaybookCLI
from ansible.executor.playbook_executor import PlaybookExecutor
from ansible.inventory.manager import InventoryManager
@@ -35,7 +36,7 @@ with mock.patch.dict(os.environ, {'ANSIBLE_STDOUT_CALLBACK': CALLBACK,
from ansible.vars.manager import VariableManager
# Add awx/lib to sys.path so we can use the plugin
path = os.path.abspath(os.path.join(PLUGINS, '..', '..'))
path = os.path.abspath(os.path.join(PLUGINS, '..', '..', 'lib'))
if path not in sys.path:
sys.path.insert(0, path)
@@ -176,6 +177,19 @@ def test_callback_plugin_receives_events(executor, cache, event, playbook):
when: item != "SENSITIVE-SKIPPED"
failed_when: item == "SENSITIVE-FAILED"
ignore_errors: yes
'''}, # noqa, NOTE: with_items will be deprecated in 2.9
{'loop.yml': '''
- name: loop tasks should be suppressed with no_log
connection: local
hosts: all
gather_facts: no
tasks:
- shell: echo {{ item }}
no_log: true
loop: [ "SENSITIVE", "SENSITIVE-SKIPPED", "SENSITIVE-FAILED" ]
when: item != "SENSITIVE-SKIPPED"
failed_when: item == "SENSITIVE-FAILED"
ignore_errors: yes
'''}, # noqa
])
def test_callback_plugin_no_log_filters(executor, cache, playbook):
@@ -186,14 +200,16 @@ def test_callback_plugin_no_log_filters(executor, cache, playbook):
@pytest.mark.parametrize('playbook', [
{'no_log_on_ok.yml': '''
- name: args should not be logged when task-level no_log is set
- name: args should not be logged when no_log is set at the task or module level
connection: local
hosts: all
gather_facts: no
tasks:
- shell: echo "SENSITIVE"
- shell: echo "PUBLIC"
- shell: echo "PRIVATE"
no_log: true
- uri: url=https://example.org username="PUBLIC" password="PRIVATE"
- copy: content="PRIVATE" dest="/tmp/tmp_no_log"
'''}, # noqa
])
def test_callback_plugin_task_args_leak(executor, cache, playbook):
@@ -204,15 +220,15 @@ def test_callback_plugin_task_args_leak(executor, cache, playbook):
# task 1
assert events[2]['event'] == 'playbook_on_task_start'
assert 'SENSITIVE' in events[2]['event_data']['task_args']
assert events[3]['event'] == 'runner_on_ok'
assert 'SENSITIVE' in events[3]['event_data']['task_args']
# task 2 no_log=True
assert events[4]['event'] == 'playbook_on_task_start'
assert events[4]['event_data']['task_args'] == "the output has been hidden due to the fact that 'no_log: true' was specified for this result" # noqa
assert events[5]['event'] == 'runner_on_ok'
assert events[5]['event_data']['task_args'] == "the output has been hidden due to the fact that 'no_log: true' was specified for this result" # noqa
assert 'PUBLIC' in json.dumps(cache.items())
assert 'PRIVATE' not in json.dumps(cache.items())
# make sure playbook was successful, so all tasks were hit
assert not events[-1]['event_data']['failures'], 'Unexpected playbook execution failure'
@pytest.mark.parametrize('playbook', [
@@ -284,3 +300,54 @@ def test_callback_plugin_saves_custom_stats(executor, cache, playbook):
assert json.load(f) == {'foo': 'bar'}
finally:
shutil.rmtree(os.path.join(private_data_dir))
@pytest.mark.parametrize('playbook', [
{'handle_playbook_on_notify.yml': '''
- name: handle playbook_on_notify events properly
connection: local
hosts: all
handlers:
- name: my_handler
debug: msg="My Handler"
tasks:
- debug: msg="My Task"
changed_when: true
notify:
- my_handler
'''}, # noqa
])
@pytest.mark.skipif(ANSIBLE_VERSION < '2.5', reason="v2_playbook_on_notify doesn't work before ansible 2.5")
def test_callback_plugin_records_notify_events(executor, cache, playbook):
executor.run()
assert len(cache)
notify_events = [x[1] for x in cache.items() if x[1]['event'] == 'playbook_on_notify']
assert len(notify_events) == 1
assert notify_events[0]['event_data']['handler'] == 'my_handler'
assert notify_events[0]['event_data']['host'] == 'localhost'
assert notify_events[0]['event_data']['task'] == 'debug'
@pytest.mark.parametrize('playbook', [
{'no_log_module_with_var.yml': '''
- name: ensure that module-level secrets are redacted
connection: local
hosts: all
vars:
- pw: SENSITIVE
tasks:
- uri:
url: https://example.org
user: john-jacob-jingleheimer-schmidt
password: "{{ pw }}"
'''}, # noqa
])
def test_module_level_no_log(executor, cache, playbook):
# https://github.com/ansible/tower/issues/1101
# It's possible for `no_log=True` to be defined at the _module_ level,
# e.g., for the URI module password parameter
# This test ensures that we properly redact those
executor.run()
assert len(cache)
assert 'john-jacob-jingleheimer-schmidt' in json.dumps(cache.items())
assert 'SENSITIVE' not in json.dumps(cache.items())

View File

@@ -15,7 +15,10 @@ from django.utils.translation import ugettext_lazy as _
from django.core.exceptions import ObjectDoesNotExist
# Django REST Framework
from rest_framework.exceptions import ParseError, PermissionDenied, ValidationError
from rest_framework.exceptions import ParseError, PermissionDenied
# Django OAuth Toolkit
from awx.main.models.oauth import OAuth2Application, OAuth2AccessToken
# AWX
from awx.main.utils import (
@@ -25,14 +28,12 @@ from awx.main.utils import (
get_licenser,
)
from awx.main.models import * # noqa
from awx.main.models.unified_jobs import ACTIVE_STATES
from awx.main.models.mixins import ResourceMixin
from awx.conf.license import LicenseForbids, feature_enabled
__all__ = ['get_user_queryset', 'check_user_access', 'check_user_access_with_errors',
'user_accessible_objects', 'consumer_access',
'user_admin_role', 'ActiveJobConflict',]
'user_accessible_objects', 'consumer_access',]
logger = logging.getLogger('awx.main.access')
@@ -72,32 +73,10 @@ def get_object_from_data(field, Model, data, obj=None):
raise ParseError(_("Bad data found in related field %s." % field))
class ActiveJobConflict(ValidationError):
status_code = 409
def __init__(self, active_jobs):
super(ActiveJobConflict, self).__init__({
"conflict": _("Resource is being used by running jobs."),
"active_jobs": active_jobs
})
def register_access(model_class, access_class):
access_registry[model_class] = access_class
@property
def user_admin_role(self):
role = Role.objects.get(
content_type=ContentType.objects.get_for_model(User),
object_id=self.id,
role_field='admin_role'
)
# Trick the user.admin_role so that the signal filtering for RBAC activity stream works as intended.
role.parents = [org.admin_role.pk for org in self.organizations]
return role
def user_accessible_objects(user, role_name):
return ResourceMixin._accessible_objects(User, user, role_name)
@@ -117,6 +96,8 @@ def check_user_access(user, model_class, action, *args, **kwargs):
Return True if user can perform action against model_class with the
provided parameters.
'''
if 'write' not in getattr(user, 'oauth_scopes', ['write']) and action != 'read':
return False
access_class = access_registry[model_class]
access_instance = access_class(user)
access_method = getattr(access_instance, 'can_%s' % action)
@@ -233,6 +214,9 @@ class BaseAccess(object):
def can_delete(self, obj):
return self.user.is_superuser
def can_copy(self, obj):
return self.can_add({'reference_obj': obj})
def can_attach(self, obj, sub_obj, relationship, data,
skip_sub_obj_read_check=False):
if skip_sub_obj_read_check:
@@ -308,7 +292,7 @@ class BaseAccess(object):
if check_expiration and validation_info.get('time_remaining', None) is None:
raise PermissionDenied(_("License is missing."))
if check_expiration and validation_info.get("grace_period_remaining") <= 0:
logger.error(_("License has expired."))
raise PermissionDenied(_("License has expired."))
free_instances = validation_info.get('free_instances', 0)
available_instances = validation_info.get('available_instances', 0)
@@ -316,11 +300,11 @@ class BaseAccess(object):
if add_host_name:
host_exists = Host.objects.filter(name=add_host_name).exists()
if not host_exists and free_instances == 0:
logger.error(_("License count of %s instances has been reached.") % available_instances)
raise PermissionDenied(_("License count of %s instances has been reached.") % available_instances)
elif not host_exists and free_instances < 0:
logger.error(_("License count of %s instances has been exceeded.") % available_instances)
raise PermissionDenied(_("License count of %s instances has been exceeded.") % available_instances)
elif not add_host_name and free_instances < 0:
raise logger.error(_("Host count exceeds available instances."))
raise PermissionDenied(_("Host count exceeds available instances."))
if feature is not None:
if "features" in validation_info and not validation_info["features"].get(feature, False):
@@ -328,7 +312,7 @@ class BaseAccess(object):
elif "features" not in validation_info:
raise LicenseForbids(_("Features not found in active license."))
def get_user_capabilities(self, obj, method_list=[], parent_obj=None):
def get_user_capabilities(self, obj, method_list=[], parent_obj=None, capabilities_cache={}):
if obj is None:
return {}
user_capabilities = {}
@@ -338,19 +322,29 @@ class BaseAccess(object):
if display_method not in method_list:
continue
if not settings.MANAGE_ORGANIZATION_AUTH and isinstance(obj, (Team, User)):
user_capabilities[display_method] = self.user.is_superuser
continue
# Actions not possible for reason unrelated to RBAC
# Cannot copy with validation errors, or update a manual group/project
if display_method == 'copy' and isinstance(obj, JobTemplate):
if 'write' not in getattr(self.user, 'oauth_scopes', ['write']):
user_capabilities[display_method] = False # Read tokens cannot take any actions
continue
elif display_method in ['copy', 'start', 'schedule'] and isinstance(obj, JobTemplate):
if obj.validation_errors:
user_capabilities[display_method] = False
continue
elif isinstance(obj, (WorkflowJobTemplate, WorkflowJob)):
if not feature_enabled('workflows'):
user_capabilities[display_method] = (display_method == 'delete')
continue
elif isinstance(obj, (WorkflowJobTemplate, WorkflowJob)) and (not feature_enabled('workflows')):
user_capabilities[display_method] = (display_method == 'delete')
continue
elif display_method == 'copy' and isinstance(obj, WorkflowJobTemplate) and obj.organization_id is None:
user_capabilities[display_method] = self.user.is_superuser
continue
elif display_method == 'copy' and isinstance(obj, Project) and obj.scm_type == '':
# Connot copy manual project without errors
user_capabilities[display_method] = False
continue
elif display_method in ['start', 'schedule'] and isinstance(obj, Group): # TODO: remove in 3.3
try:
if obj.deprecated_inventory_source and not obj.deprecated_inventory_source._can_update():
@@ -365,8 +359,8 @@ class BaseAccess(object):
continue
# Grab the answer from the cache, if available
if hasattr(obj, 'capabilities_cache') and display_method in obj.capabilities_cache:
user_capabilities[display_method] = obj.capabilities_cache[display_method]
if display_method in capabilities_cache:
user_capabilities[display_method] = capabilities_cache[display_method]
if self.user.is_superuser and not user_capabilities[display_method]:
# Cache override for models with bad orphaned state
user_capabilities[display_method] = True
@@ -384,7 +378,7 @@ class BaseAccess(object):
if display_method == 'schedule':
user_capabilities['schedule'] = user_capabilities['start']
continue
elif display_method == 'delete' and not isinstance(obj, (User, UnifiedJob)):
elif display_method == 'delete' and not isinstance(obj, (User, UnifiedJob, CustomInventoryScript)):
user_capabilities['delete'] = user_capabilities['edit']
continue
elif display_method == 'copy' and isinstance(obj, (Group, Host)):
@@ -461,14 +455,11 @@ class InstanceGroupAccess(BaseAccess):
def can_change(self, obj, data):
return self.user.is_superuser
def can_delete(self, obj):
return self.user.is_superuser
class UserAccess(BaseAccess):
'''
I can see user records when:
- I'm a useruser
- I'm a superuser
- I'm in a role with them (such as in an organization or team)
- They are in a role which includes a role of mine
- I am in a role that includes a role of theirs
@@ -507,6 +498,8 @@ class UserAccess(BaseAccess):
return False
if self.user.is_superuser:
return True
if not settings.MANAGE_ORGANIZATION_AUTH:
return False
return Organization.accessible_objects(self.user, 'admin_role').exists()
def can_change(self, obj, data):
@@ -519,12 +512,46 @@ class UserAccess(BaseAccess):
# A user can be changed if they are themselves, or by org admins or
# superusers. Change permission implies changing only certain fields
# that a user should be able to edit for themselves.
if not settings.MANAGE_ORGANIZATION_AUTH:
return False
return bool(self.user == obj or self.can_admin(obj, data))
def user_membership_roles(self, u):
return Role.objects.filter(
content_type=ContentType.objects.get_for_model(Organization),
role_field__in=[
'admin_role', 'member_role',
'execute_role', 'project_admin_role', 'inventory_admin_role',
'credential_admin_role', 'workflow_admin_role',
'notification_admin_role'
],
members=u
)
def is_all_org_admin(self, u):
return not self.user_membership_roles(u).exclude(
ancestors__in=self.user.roles.filter(role_field='admin_role')
).exists()
def user_is_orphaned(self, u):
return not self.user_membership_roles(u).exists()
@check_superuser
def can_admin(self, obj, data):
return Organization.objects.filter(Q(member_role__members=obj) | Q(admin_role__members=obj),
Q(admin_role__members=self.user)).exists()
def can_admin(self, obj, data, allow_orphans=False):
if not settings.MANAGE_ORGANIZATION_AUTH:
return False
if obj.is_superuser or obj.is_system_auditor:
# must be superuser to admin users with system roles
return False
if self.user_is_orphaned(obj):
if not allow_orphans:
# in these cases only superusers can modify orphan users
return False
return not obj.roles.all().exclude(
content_type=ContentType.objects.get_for_model(User)
).filter(ancestors__in=self.user.roles.all()).exists()
else:
return self.is_all_org_admin(obj)
def can_delete(self, obj):
if obj == self.user:
@@ -539,19 +566,100 @@ class UserAccess(BaseAccess):
return False
def can_attach(self, obj, sub_obj, relationship, *args, **kwargs):
"Reverse obj and sub_obj, defer to RoleAccess if this is a role assignment."
if not settings.MANAGE_ORGANIZATION_AUTH:
return False
# Reverse obj and sub_obj, defer to RoleAccess if this is a role assignment.
if relationship == 'roles':
role_access = RoleAccess(self.user)
return role_access.can_attach(sub_obj, obj, 'members', *args, **kwargs)
return super(UserAccess, self).can_attach(obj, sub_obj, relationship, *args, **kwargs)
def can_unattach(self, obj, sub_obj, relationship, *args, **kwargs):
if not settings.MANAGE_ORGANIZATION_AUTH:
return False
if relationship == 'roles':
role_access = RoleAccess(self.user)
return role_access.can_unattach(sub_obj, obj, 'members', *args, **kwargs)
return super(UserAccess, self).can_unattach(obj, sub_obj, relationship, *args, **kwargs)
class OAuth2ApplicationAccess(BaseAccess):
'''
I can read, change or delete OAuth 2 applications when:
- I am a superuser.
- I am the admin of the organization of the user of the application.
- I am a user in the organization of the application.
I can create OAuth 2 applications when:
- I am a superuser.
- I am the admin of the organization of the application.
'''
model = OAuth2Application
select_related = ('user',)
def filtered_queryset(self):
return self.model.objects.filter(organization__in=self.user.organizations)
def can_change(self, obj, data):
return self.user.is_superuser or self.check_related('organization', Organization, data, obj=obj,
role_field='admin_role', mandatory=True)
def can_delete(self, obj):
return self.user.is_superuser or obj.organization in self.user.admin_of_organizations
def can_add(self, data):
if self.user.is_superuser:
return True
if not data:
return Organization.accessible_objects(self.user, 'admin_role').exists()
return self.check_related('organization', Organization, data, role_field='admin_role', mandatory=True)
class OAuth2TokenAccess(BaseAccess):
'''
I can read, change or delete an app token when:
- I am a superuser.
- I am the admin of the organization of the application of the token.
- I am the user of the token.
I can create an OAuth2 app token when:
- I have the read permission of the related application.
I can read, change or delete a personal token when:
- I am the user of the token
- I am the superuser
I can create an OAuth2 Personal Access Token when:
- I am a user. But I can only create a PAT for myself.
'''
model = OAuth2AccessToken
select_related = ('user', 'application')
def filtered_queryset(self):
org_access_qs = Organization.objects.filter(
Q(admin_role__members=self.user) | Q(auditor_role__members=self.user))
return self.model.objects.filter(application__organization__in=org_access_qs) | self.model.objects.filter(user__id=self.user.pk)
def can_delete(self, obj):
if (self.user.is_superuser) | (obj.user == self.user):
return True
elif not obj.application:
return False
return self.user in obj.application.organization.admin_role
def can_change(self, obj, data):
return self.can_delete(obj)
def can_add(self, data):
if 'application' in data:
app = get_object_from_data('application', OAuth2Application, data)
if app is None:
return True
return OAuth2ApplicationAccess(self.user).can_read(app)
return True
class OrganizationAccess(BaseAccess):
'''
I can see organizations when:
@@ -579,15 +687,6 @@ class OrganizationAccess(BaseAccess):
is_change_possible = self.can_change(obj, None)
if not is_change_possible:
return False
active_jobs = []
active_jobs.extend([dict(type="job", id=o.id)
for o in Job.objects.filter(project__in=obj.projects.all(), status__in=ACTIVE_STATES)])
active_jobs.extend([dict(type="project_update", id=o.id)
for o in ProjectUpdate.objects.filter(project__in=obj.projects.all(), status__in=ACTIVE_STATES)])
active_jobs.extend([dict(type="inventory_update", id=o.id)
for o in InventoryUpdate.objects.filter(inventory_source__inventory__organization=obj, status__in=ACTIVE_STATES)])
if len(active_jobs) > 0:
raise ActiveJobConflict(active_jobs)
return True
def can_attach(self, obj, sub_obj, relationship, *args, **kwargs):
@@ -670,19 +769,7 @@ class InventoryAccess(BaseAccess):
return self.user in obj.update_role
def can_delete(self, obj):
is_can_admin = self.can_admin(obj, None)
if not is_can_admin:
return False
active_jobs = []
active_jobs.extend([dict(type="job", id=o.id)
for o in Job.objects.filter(inventory=obj, status__in=ACTIVE_STATES)])
active_jobs.extend([dict(type="inventory_update", id=o.id)
for o in InventoryUpdate.objects.filter(inventory_source__inventory=obj, status__in=ACTIVE_STATES)])
active_jobs.extend([dict(type="ad_hoc_command", id=o.id)
for o in AdHocCommand.objects.filter(inventory=obj, status__in=ACTIVE_STATES)])
if len(active_jobs) > 0:
raise ActiveJobConflict(active_jobs)
return True
return self.can_admin(obj, None)
def can_run_ad_hoc_commands(self, obj):
return self.user in obj.adhoc_role
@@ -799,15 +886,7 @@ class GroupAccess(BaseAccess):
return True
def can_delete(self, obj):
is_delete_allowed = bool(obj and self.user in obj.inventory.admin_role)
if not is_delete_allowed:
return False
active_jobs = []
active_jobs.extend([dict(type="inventory_update", id=o.id)
for o in InventoryUpdate.objects.filter(inventory_source__in=obj.inventory_sources.all(), status__in=ACTIVE_STATES)])
if len(active_jobs) > 0:
raise ActiveJobConflict(active_jobs)
return True
return bool(obj and self.user in obj.inventory.admin_role)
def can_start(self, obj, validate_license=True):
# TODO: Delete for 3.3, only used by v1 serializer
@@ -830,7 +909,9 @@ class InventorySourceAccess(BaseAccess):
'''
model = InventorySource
select_related = ('created_by', 'modified_by', 'inventory',)
select_related = ('created_by', 'modified_by', 'inventory')
prefetch_related = ('credentials__credential_type', 'last_job',
'source_script', 'source_project')
def filtered_queryset(self):
return self.model.objects.filter(inventory__in=Inventory.accessible_pk_qs(self.user, 'read_role'))
@@ -854,9 +935,6 @@ class InventorySourceAccess(BaseAccess):
if not self.user.is_superuser and \
not (obj and obj.inventory and self.user.can_access(Inventory, 'admin', obj.inventory, None)):
return False
active_jobs_qs = InventoryUpdate.objects.filter(inventory_source=obj, status__in=ACTIVE_STATES)
if active_jobs_qs.exists():
raise ActiveJobConflict([dict(type="inventory_update", id=o.id) for o in active_jobs_qs.all()])
return True
@check_superuser
@@ -865,7 +943,6 @@ class InventorySourceAccess(BaseAccess):
if obj and obj.inventory:
return (
self.user.can_access(Inventory, 'change', obj.inventory, None) and
self.check_related('credential', Credential, data, obj=obj, role_field='use_role') and
self.check_related('source_project', Project, data, obj=obj, role_field='use_role')
)
# Can't change inventory sources attached to only the inventory, since
@@ -878,6 +955,21 @@ class InventorySourceAccess(BaseAccess):
return self.user in obj.inventory.update_role
return False
@check_superuser
def can_attach(self, obj, sub_obj, relationship, data, skip_sub_obj_read_check=False):
if relationship == 'credentials' and isinstance(sub_obj, Credential):
return (
obj and obj.inventory and self.user in obj.inventory.admin_role and
self.user in sub_obj.use_role)
return super(InventorySourceAccess, self).can_attach(
obj, sub_obj, relationship, data, skip_sub_obj_read_check=skip_sub_obj_read_check)
@check_superuser
def can_unattach(self, obj, sub_obj, relationship, *args, **kwargs):
if relationship == 'credentials' and isinstance(sub_obj, Credential):
return obj and obj.inventory and self.user in obj.inventory.admin_role
return super(InventorySourceAccess, self).can_attach(obj, sub_obj, relationship, *args, **kwargs)
class InventoryUpdateAccess(BaseAccess):
'''
@@ -888,7 +980,7 @@ class InventoryUpdateAccess(BaseAccess):
model = InventoryUpdate
select_related = ('created_by', 'modified_by', 'inventory_source__inventory',)
prefetch_related = ('unified_job_template', 'instance_group',)
prefetch_related = ('unified_job_template', 'instance_group', 'credentials',)
def filtered_queryset(self):
return self.model.objects.filter(inventory_source__inventory__in=Inventory.accessible_pk_qs(self.user, 'read_role'))
@@ -1028,6 +1120,8 @@ class TeamAccess(BaseAccess):
def can_add(self, data):
if not data: # So the browseable API will work
return Organization.accessible_objects(self.user, 'admin_role').exists()
if not settings.MANAGE_ORGANIZATION_AUTH:
return False
return self.check_related('organization', Organization, data)
def can_change(self, obj, data):
@@ -1037,6 +1131,8 @@ class TeamAccess(BaseAccess):
raise PermissionDenied(_('Unable to change organization on a team.'))
if self.user.is_superuser:
return True
if not settings.MANAGE_ORGANIZATION_AUTH:
return False
return self.user in obj.admin_role
def can_delete(self, obj):
@@ -1045,6 +1141,8 @@ class TeamAccess(BaseAccess):
def can_attach(self, obj, sub_obj, relationship, *args, **kwargs):
"""Reverse obj and sub_obj, defer to RoleAccess if this is an assignment
of a resource role to the team."""
if not settings.MANAGE_ORGANIZATION_AUTH:
return False
if isinstance(sub_obj, Role):
if sub_obj.content_object is None:
raise PermissionDenied(_("The {} role cannot be assigned to a team").format(sub_obj.name))
@@ -1055,10 +1153,15 @@ class TeamAccess(BaseAccess):
role_access = RoleAccess(self.user)
return role_access.can_attach(sub_obj, obj, 'member_role.parents',
*args, **kwargs)
if self.user.is_superuser:
return True
return super(TeamAccess, self).can_attach(obj, sub_obj, relationship,
*args, **kwargs)
def can_unattach(self, obj, sub_obj, relationship, *args, **kwargs):
if not settings.MANAGE_ORGANIZATION_AUTH:
return False
if isinstance(sub_obj, Role):
if isinstance(sub_obj.content_object, ResourceMixin):
role_access = RoleAccess(self.user)
@@ -1103,23 +1206,13 @@ class ProjectAccess(BaseAccess):
return False
return self.user in obj.admin_role
def can_delete(self, obj):
is_change_allowed = self.can_change(obj, None)
if not is_change_allowed:
return False
active_jobs = []
active_jobs.extend([dict(type="job", id=o.id)
for o in Job.objects.filter(project=obj, status__in=ACTIVE_STATES)])
active_jobs.extend([dict(type="project_update", id=o.id)
for o in ProjectUpdate.objects.filter(project=obj, status__in=ACTIVE_STATES)])
if len(active_jobs) > 0:
raise ActiveJobConflict(active_jobs)
return True
@check_superuser
def can_start(self, obj, validate_license=True):
return obj and self.user in obj.update_role
def can_delete(self, obj):
return self.can_change(obj, None)
class ProjectUpdateAccess(BaseAccess):
'''
@@ -1228,9 +1321,6 @@ class JobTemplateAccess(BaseAccess):
else:
return False
def can_copy(self, obj):
return self.can_add({'reference_obj': obj})
def can_start(self, obj, validate_license=True):
# Check license.
if validate_license:
@@ -1289,14 +1379,7 @@ class JobTemplateAccess(BaseAccess):
return True
def can_delete(self, obj):
is_delete_allowed = self.user.is_superuser or self.user in obj.admin_role
if not is_delete_allowed:
return False
active_jobs = [dict(type="job", id=o.id)
for o in obj.jobs.filter(status__in=ACTIVE_STATES)]
if len(active_jobs) > 0:
raise ActiveJobConflict(active_jobs)
return True
return self.user.is_superuser or self.user in obj.admin_role
@check_superuser
def can_attach(self, obj, sub_obj, relationship, data, skip_sub_obj_read_check=False):
@@ -1382,24 +1465,7 @@ class JobAccess(BaseAccess):
if not data: # So the browseable API will work
return True
if not self.user.is_superuser:
return False
add_data = dict(data.items())
# If a job template is provided, the user should have read access to it.
if data and data.get('job_template', None):
job_template = get_object_from_data('job_template', JobTemplate, data)
add_data.setdefault('inventory', job_template.inventory.pk)
add_data.setdefault('project', job_template.project.pk)
add_data.setdefault('job_type', job_template.job_type)
if job_template.credential:
add_data.setdefault('credential', job_template.credential.pk)
else:
job_template = None
return True
return self.user.is_superuser
def can_change(self, obj, data):
return (obj.status == 'new' and
@@ -1793,18 +1859,11 @@ class WorkflowJobTemplateAccess(BaseAccess):
if self.user.is_superuser:
return True
return (self.check_related('organization', Organization, data, role_field='workflow_admin_field', obj=obj) and
return (self.check_related('organization', Organization, data, role_field='workflow_admin_role', obj=obj) and
self.user in obj.admin_role)
def can_delete(self, obj):
is_delete_allowed = self.user.is_superuser or self.user in obj.admin_role
if not is_delete_allowed:
return False
active_jobs = [dict(type="workflow_job", id=o.id)
for o in obj.workflow_jobs.filter(status__in=ACTIVE_STATES)]
if len(active_jobs) > 0:
raise ActiveJobConflict(active_jobs)
return True
return self.user.is_superuser or self.user in obj.admin_role
class WorkflowJobAccess(BaseAccess):
@@ -2019,7 +2078,7 @@ class ProjectUpdateEventAccess(BaseAccess):
def filtered_queryset(self):
return self.model.objects.filter(
Q(project_update__in=ProjectUpdate.accessible_pk_qs(self.user, 'read_role')))
Q(project_update__project__in=Project.accessible_pk_qs(self.user, 'read_role')))
def can_add(self, data):
return False
@@ -2040,7 +2099,7 @@ class InventoryUpdateEventAccess(BaseAccess):
def filtered_queryset(self):
return self.model.objects.filter(
Q(inventory_update__in=InventoryUpdate.accessible_pk_qs(self.user, 'read_role')))
Q(inventory_update__inventory_source__inventory__in=Inventory.accessible_pk_qs(self.user, 'read_role')))
def can_add(self, data):
return False
@@ -2314,7 +2373,7 @@ class ActivityStreamAccess(BaseAccess):
model = ActivityStream
prefetch_related = ('organization', 'user', 'inventory', 'host', 'group',
'inventory_update', 'credential', 'credential_type', 'team',
'ad_hoc_command',
'ad_hoc_command', 'o_auth2_application', 'o_auth2_access_token',
'notification_template', 'notification', 'label', 'role', 'actor',
'schedule', 'custom_inventory_script', 'unified_job_template',
'workflow_job_template_node',)
@@ -2357,9 +2416,13 @@ class ActivityStreamAccess(BaseAccess):
jt_set = JobTemplate.accessible_objects(self.user, 'read_role')
team_set = Team.accessible_objects(self.user, 'read_role')
wfjt_set = WorkflowJobTemplate.accessible_objects(self.user, 'read_role')
app_set = OAuth2ApplicationAccess(self.user).filtered_queryset()
token_set = OAuth2TokenAccess(self.user).filtered_queryset()
return qs.filter(
Q(ad_hoc_command__inventory__in=inventory_set) |
Q(o_auth2_application__in=app_set) |
Q(o_auth2_access_token__in=token_set) |
Q(user__in=auditing_orgs.values('member_role__members')) |
Q(user=self.user) |
Q(organization__in=auditing_orgs) |
@@ -2449,6 +2512,10 @@ class RoleAccess(BaseAccess):
@check_superuser
def can_unattach(self, obj, sub_obj, relationship, data=None, skip_sub_obj_read_check=False):
if isinstance(obj.content_object, Team):
if not settings.MANAGE_ORGANIZATION_AUTH:
return False
if not skip_sub_obj_read_check and relationship in ['members', 'member_role.parents', 'parents']:
# If we are unattaching a team Role, check the Team read access
if relationship == 'parents':
@@ -2458,6 +2525,14 @@ class RoleAccess(BaseAccess):
if not check_user_access(self.user, sub_obj_resource.__class__, 'read', sub_obj_resource):
return False
# Being a user in the member_role or admin_role of an organization grants
# administrators of that Organization the ability to edit that user. To prevent
# unwanted escalations lets ensure that the Organization administartor has the abilty
# to admin the user being added to the role.
if isinstance(obj.content_object, Organization) and obj.role_field in ['member_role', 'admin_role']:
if not UserAccess(self.user).can_admin(sub_obj, None, allow_orphans=True):
return False
if isinstance(obj.content_object, ResourceMixin) and \
self.user in obj.content_object.admin_role:
return True

View File

@@ -43,6 +43,16 @@ register(
category_slug='system',
)
register(
'MANAGE_ORGANIZATION_AUTH',
field_class=fields.BooleanField,
label=_('Organization Admins Can Manage Users and Teams'),
help_text=_('Controls whether any Organization Admin has the privileges to create and manage users and teams. '
'You may want to disable this ability if you are using an LDAP or SAML integration.'),
category=_('System'),
category_slug='system',
)
register(
'TOWER_ADMIN_ALERTS',
field_class=fields.BooleanField,
@@ -125,6 +135,27 @@ register(
required=False,
)
register(
'ALLOW_JINJA_IN_EXTRA_VARS',
field_class=fields.ChoiceField,
choices=[
('always', _('Always')),
('never', _('Never')),
('template', _('Only On Job Template Definitions')),
],
required=True,
label=_('When can extra variables contain Jinja templates?'),
help_text=_(
'Ansible allows variable substitution via the Jinja2 templating '
'language for --extra-vars. This poses a potential security '
'risk where Tower users with the ability to specify extra vars at job '
'launch time can use Jinja2 templates to run arbitrary Python. It is '
'recommended that this value be set to "template" or "never".'
),
category=_('Jobs'),
category_slug='jobs',
)
register(
'AWX_PROOT_ENABLED',
field_class=fields.BooleanField,
@@ -331,7 +362,8 @@ register(
label=_('Per-Host Ansible Fact Cache Timeout'),
help_text=_('Maximum time, in seconds, that stored Ansible facts are considered valid since '
'the last time they were modified. Only valid, non-stale, facts will be accessible by '
'a playbook. Note, this does not influence the deletion of ansible_facts from the database.'),
'a playbook. Note, this does not influence the deletion of ansible_facts from the database. '
'Use a value of 0 to indicate that no timeout should be imposed.'),
category=_('Jobs'),
category_slug='jobs',
)
@@ -411,7 +443,7 @@ register(
field_class=fields.BooleanField,
default=False,
label=_('Log System Tracking Facts Individually'),
help_text=_('If set, system tracking facts will be sent for each package, service, or'
help_text=_('If set, system tracking facts will be sent for each package, service, or '
'other item found in a scan, allowing for greater search query granularity. '
'If unset, facts will be sent as a single dictionary, allowing for greater '
'efficiency in fact processing.'),

View File

@@ -5,9 +5,21 @@ import re
from django.utils.translation import ugettext_lazy as _
__all__ = [
'CLOUD_PROVIDERS', 'SCHEDULEABLE_PROVIDERS', 'PRIVILEGE_ESCALATION_METHODS',
'ANSI_SGR_PATTERN', 'CAN_CANCEL', 'ACTIVE_STATES'
]
CLOUD_PROVIDERS = ('azure_rm', 'ec2', 'gce', 'vmware', 'openstack', 'rhv', 'satellite6', 'cloudforms', 'tower')
SCHEDULEABLE_PROVIDERS = CLOUD_PROVIDERS + ('custom', 'scm',)
PRIVILEGE_ESCALATION_METHODS = [
('sudo', _('Sudo')), ('su', _('Su')), ('pbrun', _('Pbrun')), ('pfexec', _('Pfexec')),
('dzdo', _('DZDO')), ('pmrun', _('Pmrun')), ('runas', _('Runas'))]
('dzdo', _('DZDO')), ('pmrun', _('Pmrun')), ('runas', _('Runas')),
('enable', _('Enable')), ('doas', _('Doas')),
]
CHOICES_PRIVILEGE_ESCALATION_METHODS = [('', _('None'))] + PRIVILEGE_ESCALATION_METHODS
ANSI_SGR_PATTERN = re.compile(r'\x1b\[[0-9;]*m')
CAN_CANCEL = ('new', 'pending', 'waiting', 'running')
ACTIVE_STATES = CAN_CANCEL
TOKEN_CENSOR = '************'

View File

@@ -1,17 +1,11 @@
import json
import logging
import urllib
from channels import Group, channel_layers
from channels.sessions import channel_session
from channels.handler import AsgiRequest
from channels import Group
from channels.auth import channel_session_user_from_http, channel_session_user
from django.conf import settings
from django.core.serializers.json import DjangoJSONEncoder
from django.contrib.auth.models import User
from awx.main.models.organization import AuthToken
logger = logging.getLogger('awx.main.consumers')
@@ -22,51 +16,29 @@ def discard_groups(message):
Group(group).discard(message.reply_channel)
@channel_session
@channel_session_user_from_http
def ws_connect(message):
message.reply_channel.send({"accept": True})
message.content['method'] = 'FAKE'
request = AsgiRequest(message)
token = request.COOKIES.get('token', None)
if token is not None:
token = urllib.unquote(token).strip('"')
try:
auth_token = AuthToken.objects.get(key=token)
if auth_token.in_valid_tokens:
message.channel_session['user_id'] = auth_token.user_id
message.reply_channel.send({"text": json.dumps({"accept": True, "user": auth_token.user_id})})
return None
except AuthToken.DoesNotExist:
logger.error("auth_token provided was invalid.")
message.reply_channel.send({"close": True})
if message.user.is_authenticated():
message.reply_channel.send(
{"text": json.dumps({"accept": True, "user": message.user.id})}
)
else:
logger.error("Request user is not authenticated to use websocket.")
message.reply_channel.send({"close": True})
return None
@channel_session
@channel_session_user
def ws_disconnect(message):
discard_groups(message)
@channel_session
@channel_session_user
def ws_receive(message):
from awx.main.access import consumer_access
channel_layer_settings = channel_layers.configs[message.channel_layer.alias]
max_retries = channel_layer_settings.get('RECEIVE_MAX_RETRY', settings.CHANNEL_LAYER_RECEIVE_MAX_RETRY)
user_id = message.channel_session.get('user_id', None)
if user_id is None:
retries = message.content.get('connect_retries', 0) + 1
message.content['connect_retries'] = retries
message.reply_channel.send({"text": json.dumps({"error": "no valid user"})})
retries_left = max_retries - retries
if retries_left > 0:
message.channel_layer.send(message.channel.name, message.content)
else:
logger.error("No valid user found for websocket.")
return None
user = User.objects.get(pk=user_id)
user = message.user
raw_data = message.content['text']
data = json.loads(raw_data)

View File

@@ -1,6 +1,9 @@
# Copyright (c) 2018 Ansible by Red Hat
# All Rights Reserved.
import six
# Celery does not respect exception type when using a serializer different than pickle;
# and awx uses the json serializer
# https://github.com/celery/celery/issues/3586
@@ -9,7 +12,7 @@
class _AwxTaskError():
def build_exception(self, task, message=None):
if message is None:
message = "Execution error running {}".format(task.log_format)
message = six.text_type("Execution error running {}").format(task.log_format)
e = Exception(message)
e.task = task
e.is_awx_task_error = True
@@ -17,7 +20,7 @@ class _AwxTaskError():
def TaskCancel(self, task, rc):
"""Canceled flag caused run_pexpect to kill the job run"""
message="{} was canceled (rc={})".format(task.log_format, rc)
message=six.text_type("{} was canceled (rc={})").format(task.log_format, rc)
e = self.build_exception(task, message)
e.rc = rc
e.awx_task_error_type = "TaskCancel"
@@ -25,7 +28,7 @@ class _AwxTaskError():
def TaskError(self, task, rc):
"""Userspace error (non-zero exit code) in run_pexpect subprocess"""
message = "{} encountered an error (rc={}), please see task stdout for details.".format(task.log_format, rc)
message = six.text_type("{} encountered an error (rc={}), please see task stdout for details.").format(task.log_format, rc)
e = self.build_exception(task, message)
e.rc = rc
e.awx_task_error_type = "TaskError"
@@ -34,3 +37,4 @@ class _AwxTaskError():
AwxTaskError = _AwxTaskError()

View File

@@ -101,7 +101,7 @@ def run_pexpect(args, cwd, env, logfile,
child = pexpect.spawn(
args[0], args[1:], cwd=cwd, env=env, ignore_sighup=True,
encoding='utf-8', echo=False,
encoding='utf-8', echo=False, use_poll=True
)
child.logfile_read = logfile
canceled = False

View File

@@ -4,12 +4,13 @@
# Python
import copy
import json
import operator
import re
import six
import urllib
from jinja2 import Environment, StrictUndefined
from jinja2.exceptions import UndefinedError
from jinja2.exceptions import UndefinedError, TemplateSyntaxError
# Django
from django.core import exceptions as django_exceptions
@@ -42,19 +43,24 @@ from rest_framework import serializers
# AWX
from awx.main.utils.filters import SmartFilter
from awx.main.utils.encryption import encrypt_value, decrypt_value, get_encryption_key
from awx.main.validators import validate_ssh_private_key
from awx.main.models.rbac import batch_role_ancestor_rebuilding, Role
from awx.main.constants import CHOICES_PRIVILEGE_ESCALATION_METHODS
from awx.main import utils
__all__ = ['AutoOneToOneField', 'ImplicitRoleField', 'JSONField', 'SmartFilterField']
__all__ = ['AutoOneToOneField', 'ImplicitRoleField', 'JSONField',
'SmartFilterField', 'update_role_parentage_for_instance',
'is_implicit_parent']
# Provide a (better) custom error message for enum jsonschema validation
def __enum_validate__(validator, enums, instance, schema):
if instance not in enums:
yield jsonschema.exceptions.ValidationError(
_("'%s' is not one of ['%s']") % (instance, "', '".join(enums))
_("'{value}' is not one of ['{allowed_values}']").format(
value=instance, allowed_values="', '".join(enums))
)
@@ -180,6 +186,23 @@ def is_implicit_parent(parent_role, child_role):
return False
def update_role_parentage_for_instance(instance):
'''update_role_parentage_for_instance
updates the parents listing for all the roles
of a given instance if they have changed
'''
for implicit_role_field in getattr(instance.__class__, '__implicit_role_fields'):
cur_role = getattr(instance, implicit_role_field.name)
new_parents = implicit_role_field._resolve_parent_roles(instance)
cur_role.parents.set(new_parents)
new_parents_list = list(new_parents)
new_parents_list.sort()
new_parents_json = json.dumps(new_parents_list)
if cur_role.implicit_parents != new_parents_json:
cur_role.implicit_parents = new_parents_json
cur_role.save()
class ImplicitRoleDescriptor(ForwardManyToOneDescriptor):
pass
@@ -273,43 +296,37 @@ class ImplicitRoleField(models.ForeignKey):
Role_ = utils.get_current_apps().get_model('main', 'Role')
ContentType_ = utils.get_current_apps().get_model('contenttypes', 'ContentType')
ct_id = ContentType_.objects.get_for_model(instance).id
Model = utils.get_current_apps().get_model('main', instance.__class__.__name__)
latest_instance = Model.objects.get(pk=instance.pk)
with batch_role_ancestor_rebuilding():
# Create any missing role objects
missing_roles = []
for implicit_role_field in getattr(instance.__class__, '__implicit_role_fields'):
cur_role = getattr(instance, implicit_role_field.name, None)
for implicit_role_field in getattr(latest_instance.__class__, '__implicit_role_fields'):
cur_role = getattr(latest_instance, implicit_role_field.name, None)
if cur_role is None:
missing_roles.append(
Role_(
role_field=implicit_role_field.name,
content_type_id=ct_id,
object_id=instance.id
object_id=latest_instance.id
)
)
if len(missing_roles) > 0:
Role_.objects.bulk_create(missing_roles)
updates = {}
role_ids = []
for role in Role_.objects.filter(content_type_id=ct_id, object_id=instance.id):
setattr(instance, role.role_field, role)
for role in Role_.objects.filter(content_type_id=ct_id, object_id=latest_instance.id):
setattr(latest_instance, role.role_field, role)
updates[role.role_field] = role.id
role_ids.append(role.id)
type(instance).objects.filter(pk=instance.pk).update(**updates)
type(latest_instance).objects.filter(pk=latest_instance.pk).update(**updates)
Role.rebuild_role_ancestor_list(role_ids, [])
# Update parentage if necessary
for implicit_role_field in getattr(instance.__class__, '__implicit_role_fields'):
cur_role = getattr(instance, implicit_role_field.name)
original_parents = set(json.loads(cur_role.implicit_parents))
new_parents = implicit_role_field._resolve_parent_roles(instance)
cur_role.parents.remove(*list(original_parents - new_parents))
cur_role.parents.add(*list(new_parents - original_parents))
new_parents_list = list(new_parents)
new_parents_list.sort()
new_parents_json = json.dumps(new_parents_list)
if cur_role.implicit_parents != new_parents_json:
cur_role.implicit_parents = new_parents_json
cur_role.save()
update_role_parentage_for_instance(latest_instance)
instance.refresh_from_db()
def _resolve_parent_roles(self, instance):
@@ -391,7 +408,25 @@ class JSONSchemaField(JSONBField):
error.message = re.sub(r'\bu(\'|")', r'\1', error.message)
if error.validator == 'pattern' and 'error' in error.schema:
error.message = error.schema['error'] % error.instance
error.message = six.text_type(error.schema['error']).format(instance=error.instance)
elif error.validator == 'type':
expected_type = error.validator_value
if expected_type == 'object':
expected_type = 'dict'
if error.path:
error.message = _(
'{type} provided in relative path {path}, expected {expected_type}'
).format(path=list(error.path), type=type(error.instance).__name__,
expected_type=expected_type)
else:
error.message = _(
'{type} provided, expected {expected_type}'
).format(path=list(error.path), type=type(error.instance).__name__,
expected_type=expected_type)
elif error.validator == 'additionalProperties' and hasattr(error, 'path'):
error.message = _(
'Schema validation error in relative path {path} ({error})'
).format(path=list(error.path), error=error.message)
errors.append(error)
if errors:
@@ -474,6 +509,9 @@ class CredentialInputField(JSONSchemaField):
properties = {}
for field in model_instance.credential_type.inputs.get('fields', []):
field = field.copy()
if field['type'] == 'become_method':
field.pop('type')
field['choices'] = map(operator.itemgetter(0), CHOICES_PRIVILEGE_ESCALATION_METHODS)
properties[field['id']] = field
if field.get('choices', []):
field['enum'] = field['choices'][:]
@@ -523,7 +561,7 @@ class CredentialInputField(JSONSchemaField):
format_checker=self.format_checker
).iter_errors(decrypted_values):
if error.validator == 'pattern' and 'error' in error.schema:
error.message = error.schema['error'] % error.instance
error.message = six.text_type(error.schema['error']).format(instance=error.instance)
if error.validator == 'dependencies':
# replace the default error messaging w/ a better i18n string
# I wish there was a better way to determine the parameters of
@@ -617,7 +655,7 @@ class CredentialTypeInputField(JSONSchemaField):
'items': {
'type': 'object',
'properties': {
'type': {'enum': ['string', 'boolean']},
'type': {'enum': ['string', 'boolean', 'become_method']},
'format': {'enum': ['ssh_private_key']},
'choices': {
'type': 'array',
@@ -628,7 +666,7 @@ class CredentialTypeInputField(JSONSchemaField):
'id': {
'type': 'string',
'pattern': '^[a-zA-Z_]+[a-zA-Z0-9_]*$',
'error': '%s is an invalid variable name',
'error': '{instance} is an invalid variable name',
},
'label': {'type': 'string'},
'help_text': {'type': 'string'},
@@ -678,10 +716,22 @@ class CredentialTypeInputField(JSONSchemaField):
# If no type is specified, default to string
field['type'] = 'string'
if field['type'] == 'become_method':
if not model_instance.managed_by_tower:
raise django_exceptions.ValidationError(
_('become_method is a reserved type name'),
code='invalid',
params={'value': value},
)
else:
field.pop('type')
field['choices'] = CHOICES_PRIVILEGE_ESCALATION_METHODS
for key in ('choices', 'multiline', 'format', 'secret',):
if key in field and field['type'] != 'string':
raise django_exceptions.ValidationError(
_('%s not allowed for %s type (%s)' % (key, field['type'], field['id'])),
_('{sub_key} not allowed for {element_type} type ({element_id})'.format(
sub_key=key, element_type=field['type'], element_id=field['id'])),
code='invalid',
params={'value': value},
)
@@ -778,7 +828,15 @@ class CredentialTypeInjectorField(JSONSchemaField):
).from_string(tmpl).render(valid_namespace)
except UndefinedError as e:
raise django_exceptions.ValidationError(
_('%s uses an undefined field (%s)') % (key, e),
_('{sub_key} uses an undefined field ({error_msg})').format(
sub_key=key, error_msg=e),
code='invalid',
params={'value': value},
)
except TemplateSyntaxError as e:
raise django_exceptions.ValidationError(
_('Syntax error rendering template for {sub_key} inside of {type} ({error_msg})').format(
sub_key=key, type=type_, error_msg=e),
code='invalid',
params={'value': value},
)
@@ -801,3 +859,16 @@ class AskForField(models.BooleanField):
# self.name will be set by the model metaclass, not this field
raise Exception('Corresponding allows_field cannot be accessed until model is initialized.')
return self._allows_field
class OAuth2ClientSecretField(models.CharField):
def get_db_prep_value(self, value, connection, prepared=False):
return super(OAuth2ClientSecretField, self).get_db_prep_value(
encrypt_value(value), connection, prepared
)
def from_db_value(self, value, expression, connection, context):
if value and value.startswith('$encrypted$'):
return decrypt_value(get_encryption_key('value', pk=None), value)
return value

View File

@@ -1,35 +0,0 @@
# Copyright (c) 2015 Ansible, Inc.
# All Rights Reserved.
# Python
import logging
# Django
from django.db import transaction
from django.core.management.base import BaseCommand
from django.utils.timezone import now
# AWX
from awx.main.models import * # noqa
class Command(BaseCommand):
'''
Management command to cleanup expired auth tokens
'''
help = 'Cleanup expired auth tokens.'
def init_logging(self):
self.logger = logging.getLogger('awx.main.commands.cleanup_authtokens')
handler = logging.StreamHandler()
handler.setFormatter(logging.Formatter('%(message)s'))
self.logger.addHandler(handler)
self.logger.propagate = False
@transaction.atomic
def handle(self, *args, **options):
self.init_logging()
tokens_removed = AuthToken.objects.filter(expires__lt=now())
self.logger.log(99, "Removing %d expired auth tokens" % tokens_removed.count())
tokens_removed.delete()

View File

@@ -155,7 +155,7 @@ class AnsibleInventoryLoader(object):
if self.tmp_private_dir:
shutil.rmtree(self.tmp_private_dir, True)
if proc.returncode != 0 or 'file not found' in stderr:
if proc.returncode != 0:
raise RuntimeError('%s failed (rc=%d) with stdout:\n%s\nstderr:\n%s' % (
self.method, proc.returncode, stdout, stderr))
@@ -403,9 +403,7 @@ class Command(BaseCommand):
_eager_fields=dict(
job_args=json.dumps(sys.argv),
job_env=dict(os.environ.items()),
job_cwd=os.getcwd(),
execution_node=settings.CLUSTER_HOST_ID,
instance_group=InstanceGroup.objects.get(name='tower'))
job_cwd=os.getcwd())
)
# FIXME: Wait or raise error if inventory is being updated by another
@@ -904,6 +902,7 @@ class Command(BaseCommand):
new_count = Host.objects.active_count()
if time_remaining <= 0 and not license_info.get('demo', False):
logger.error(LICENSE_EXPIRED_MESSAGE)
raise CommandError("License has expired!")
if free_instances < 0:
d = {
'new_count': new_count,
@@ -913,6 +912,7 @@ class Command(BaseCommand):
logger.error(DEMO_LICENSE_MESSAGE % d)
else:
logger.error(LICENSE_MESSAGE % d)
raise CommandError('License count exceeded!')
def mark_license_failure(self, save=True):
self.inventory_update.license_error = True

View File

@@ -2,7 +2,6 @@
# All Rights Reserved
from awx.main.models import Instance
from awx.main.utils.pglock import advisory_lock
from django.conf import settings
from django.db import transaction
@@ -27,15 +26,12 @@ class Command(BaseCommand):
def _register_hostname(self, hostname):
if not hostname:
return
with advisory_lock('instance_registration_%s' % hostname):
instance = Instance.objects.filter(hostname=hostname)
if instance.exists():
print("Instance already registered {}".format(instance[0].hostname))
return
instance = Instance(uuid=self.uuid, hostname=hostname)
instance.save()
print('Successfully registered instance {}'.format(hostname))
self.changed = True
(changed, instance) = Instance.objects.register(uuid=self.uuid, hostname=hostname)
if changed:
print('Successfully registered instance {}'.format(hostname))
else:
print("Instance already registered {}".format(instance.hostname))
self.changed = changed
@transaction.atomic
def handle(self, **options):

View File

@@ -1,6 +1,7 @@
# Copyright (c) 2017 Ansible Tower by Red Hat
# All Rights Reserved.
import sys
import six
from awx.main.utils.pglock import advisory_lock
from awx.main.models import Instance, InstanceGroup
@@ -8,6 +9,13 @@ from awx.main.models import Instance, InstanceGroup
from django.core.management.base import BaseCommand, CommandError
class InstanceNotFound(Exception):
def __init__(self, message, changed, *args, **kwargs):
self.message = message
self.changed = changed
super(InstanceNotFound, self).__init__(*args, **kwargs)
class Command(BaseCommand):
def add_arguments(self, parser):
@@ -22,51 +30,95 @@ class Command(BaseCommand):
parser.add_argument('--instance_minimum', dest='instance_minimum', type=int, default=0,
help='The minimum number of instance that will be retained for this group from available instances')
def get_create_update_instance_group(self, queuename, instance_percent, instance_min):
ig = InstanceGroup.objects.filter(name=queuename)
created = False
changed = False
(ig, created) = InstanceGroup.objects.get_or_create(name=queuename)
if ig.policy_instance_percentage != instance_percent:
ig.policy_instance_percentage = instance_percent
changed = True
if ig.policy_instance_minimum != instance_min:
ig.policy_instance_minimum = instance_min
changed = True
return (ig, created, changed)
def update_instance_group_controller(self, ig, controller):
changed = False
control_ig = None
if controller:
control_ig = InstanceGroup.objects.filter(name=controller).first()
if control_ig and ig.controller_id != control_ig.pk:
ig.controller = control_ig
ig.save()
changed = True
return (control_ig, changed)
def add_instances_to_group(self, ig, hostname_list):
changed = False
instance_list_unique = set([x.strip() for x in hostname_list if x])
instances = []
for inst_name in instance_list_unique:
instance = Instance.objects.filter(hostname=inst_name)
if instance.exists():
instances.append(instance[0])
else:
raise InstanceNotFound(six.text_type("Instance does not exist: {}").format(inst_name), changed)
ig.instances = instances
instance_list_before = set(ig.policy_instance_list)
instance_list_after = set(instance_list_unique)
if len(instance_list_before) != len(instance_list_after) or \
len(set(instance_list_before) - set(instance_list_after)) != 0:
changed = True
ig.policy_instance_list = list(instance_list_unique)
ig.save()
return (instances, changed)
def handle(self, **options):
instance_not_found_err = None
queuename = options.get('queuename')
if not queuename:
raise CommandError("Specify `--queuename` to use this command.")
changed = False
ctrl = options.get('controller')
inst_per = options.get('instance_percent')
inst_min = options.get('instance_minimum')
hostname_list = []
if options.get('hostnames'):
hostname_list = options.get('hostnames').split(",")
with advisory_lock('instance_group_registration_%s' % queuename):
ig = InstanceGroup.objects.filter(name=queuename)
control_ig = None
if options.get('controller'):
control_ig = InstanceGroup.objects.filter(name=options.get('controller')).first()
if ig.exists():
print("Instance Group already registered {}".format(ig[0].name))
ig = ig[0]
if control_ig and ig.controller_id != control_ig.pk:
ig.controller = control_ig
ig.save()
print("Set controller group {} on {}.".format(control_ig.name, ig.name))
changed = True
else:
print("Creating instance group {}".format(queuename))
ig = InstanceGroup(name=queuename,
policy_instance_percentage=options.get('instance_percent'),
policy_instance_minimum=options.get('instance_minimum'))
if control_ig:
ig.controller = control_ig
ig.save()
changed = True
hostname_list = []
if options.get('hostnames'):
hostname_list = options.get('hostnames').split(",")
instance_list = [x.strip() for x in hostname_list if x]
for inst_name in instance_list:
instance = Instance.objects.filter(hostname=inst_name)
if instance.exists() and instance[0] not in ig.instances.all():
ig.instances.add(instance[0])
print("Added instance {} to {}".format(instance[0].hostname, ig.name))
changed = True
elif not instance.exists():
print("Instance does not exist: {}".format(inst_name))
if changed:
print('(changed: True)')
sys.exit(1)
else:
print("Instance already registered {}".format(instance[0].hostname))
ig.policy_instance_list = instance_list
ig.save()
if changed:
print('(changed: True)')
(ig, created, changed) = self.get_create_update_instance_group(queuename, inst_per, inst_min)
if created:
print(six.text_type("Creating instance group {}".format(ig.name)))
elif not created:
print(six.text_type("Instance Group already registered {}").format(ig.name))
if ctrl:
(ig_ctrl, changed) = self.update_instance_group_controller(ig, ctrl)
if changed:
print(six.text_type("Set controller group {} on {}.").format(ctrl, queuename))
try:
(instances, changed) = self.add_instances_to_group(ig, hostname_list)
for i in instances:
print(six.text_type("Added instance {} to {}").format(i.hostname, ig.name))
except InstanceNotFound as e:
instance_not_found_err = e
if changed:
print('(changed: True)')
if instance_not_found_err:
print(instance_not_found_err.message)
sys.exit(1)

View File

@@ -161,14 +161,35 @@ class CallbackBrokerWorker(ConsumerMixin):
break
if body.get('event') == 'EOF':
# EOF events are sent when stdout for the running task is
# closed. don't actually persist them to the database; we
# just use them to report `summary` websocket events as an
# approximation for when a job is "done"
emit_channel_notification(
'jobs-summary',
dict(group_name='jobs', unified_job_id=job_identifier)
)
try:
logger.info('Event processing is finished for Job {}, sending notifications'.format(job_identifier))
# EOF events are sent when stdout for the running task is
# closed. don't actually persist them to the database; we
# just use them to report `summary` websocket events as an
# approximation for when a job is "done"
emit_channel_notification(
'jobs-summary',
dict(group_name='jobs', unified_job_id=job_identifier)
)
# Additionally, when we've processed all events, we should
# have all the data we need to send out success/failure
# notification templates
uj = UnifiedJob.objects.get(pk=job_identifier)
if hasattr(uj, 'send_notification_templates'):
retries = 0
while retries < 5:
if uj.finished:
uj.send_notification_templates('succeeded' if uj.status == 'successful' else 'failed')
break
else:
# wait a few seconds to avoid a race where the
# events are persisted _before_ the UJ.status
# changes from running -> successful
retries += 1
time.sleep(1)
uj = UnifiedJob.objects.get(pk=job_identifier)
except Exception:
logger.exception('Worker failed to emit notifications: Job {}'.format(job_identifier))
continue
retries = 0
@@ -208,7 +229,7 @@ class Command(BaseCommand):
help = 'Launch the job callback receiver'
def handle(self, *arg, **options):
with Connection(settings.CELERY_BROKER_URL) as conn:
with Connection(settings.BROKER_URL) as conn:
try:
worker = CallbackBrokerWorker(conn)
worker.run()

View File

@@ -8,6 +8,7 @@ from django.db import models
from django.conf import settings
from awx.main.utils.filters import SmartFilter
from awx.main.utils.pglock import advisory_lock
___all__ = ['HostManager', 'InstanceManager', 'InstanceGroupManager']
@@ -86,6 +87,24 @@ class InstanceManager(models.Manager):
return node[0]
raise RuntimeError("No instance found with the current cluster host id")
def register(self, uuid=None, hostname=None):
if not uuid:
uuid = settings.SYSTEM_UUID
if not hostname:
hostname = settings.CLUSTER_HOST_ID
with advisory_lock('instance_registration_%s' % hostname):
instance = self.filter(hostname=hostname)
if instance.exists():
return (False, instance[0])
instance = self.create(uuid=uuid, hostname=hostname)
return (True, instance)
def get_or_register(self):
if settings.AWX_AUTO_DEPROVISION_INSTANCES:
return self.register()
else:
return (False, self.me())
def active_count(self):
"""Return count of active Tower nodes for licensing."""
return self.all().count()
@@ -94,6 +113,9 @@ class InstanceManager(models.Manager):
# NOTE: TODO: Likely to repurpose this once standalone ramparts are a thing
return "tower"
def all_non_isolated(self):
return self.exclude(rampart_groups__controller__isnull=False)
class InstanceGroupManager(models.Manager):
"""A custom manager class for the Instance model.
@@ -156,8 +178,6 @@ class InstanceGroupManager(models.Manager):
if t.status == 'waiting' or not t.execution_node:
# Subtract capacity from any peer groups that share instances
if not t.instance_group:
logger.warning('Excluded %s from capacity algorithm '
'(missing instance_group).', t.log_format)
impacted_groups = []
elif t.instance_group.name not in ig_ig_mapping:
# Waiting job in group with 0 capacity has no collateral impact

View File

@@ -20,9 +20,9 @@ from django.shortcuts import get_object_or_404, redirect
from django.apps import apps
from django.utils.translation import ugettext_lazy as _
from django.core.urlresolvers import reverse
from django.urls import resolve
from awx.main.models import ActivityStream
from awx.api.authentication import TokenAuthentication
from awx.main.utils.named_url_graph import generate_graph, GraphNode
from awx.conf import fields, register
@@ -119,21 +119,6 @@ class ActivityStreamMiddleware(threading.local):
self.instance_ids.append(instance.id)
class AuthTokenTimeoutMiddleware(object):
"""Presume that when the user includes the auth header, they go through the
authentication mechanism. Further, that mechanism is presumed to extend
the users session validity time by AUTH_TOKEN_EXPIRATION.
If the auth token is not supplied, then don't include the header
"""
def process_response(self, request, response):
if not TokenAuthentication._get_x_auth_token_header(request):
return response
response['Auth-Token-Timeout'] = int(settings.AUTH_TOKEN_EXPIRATION)
return response
def _customize_graph():
from awx.main.models import Instance, Schedule, UnifiedJobTemplate
for model in [Schedule, UnifiedJobTemplate]:
@@ -193,7 +178,7 @@ class URLModificationMiddleware(object):
return '/'.join(url_units)
def process_request(self, request):
if 'REQUEST_URI' in request.environ:
if hasattr(request, 'environ') and 'REQUEST_URI' in request.environ:
old_path = six.moves.urllib.parse.urlsplit(request.environ['REQUEST_URI']).path
old_path = old_path[request.path.find(request.path_info):]
else:
@@ -209,5 +194,6 @@ class MigrationRanCheckMiddleware(object):
def process_request(self, request):
executor = MigrationExecutor(connection)
plan = executor.migration_plan(executor.loader.graph.leaf_nodes())
if bool(plan) and 'migrations_notran' not in request.path:
if bool(plan) and \
getattr(resolve(request.path), 'url_name', '') != 'migrations_notran':
return redirect(reverse("ui:migrations_notran"))

View File

@@ -83,6 +83,6 @@ class Migration(migrations.Migration):
migrations.AlterField(
model_name='organization',
name='member_role',
field=awx.main.fields.ImplicitRoleField(null=b'True', on_delete=django.db.models.deletion.CASCADE, parent_role=[b'admin_role', b'project_admin_role', b'inventory_admin_role', b'workflow_admin_role', b'notification_admin_role', b'execute_role'], related_name='+', to='main.Role'),
field=awx.main.fields.ImplicitRoleField(null=b'True', on_delete=django.db.models.deletion.CASCADE, parent_role=[b'admin_role', b'project_admin_role', b'inventory_admin_role', b'workflow_admin_role', b'notification_admin_role', b'credential_admin_role', b'execute_role'], related_name='+', to='main.Role'),
),
]

View File

@@ -0,0 +1,32 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.7 on 2018-02-14 16:14
from __future__ import unicode_literals
from django.db import migrations
from awx.main.migrations import _migration_utils as migration_utils
from awx.main.migrations._multi_cred import (
migrate_inventory_source_cred,
migrate_inventory_source_cred_reverse
)
class Migration(migrations.Migration):
dependencies = [
('main', '0022_v330_create_new_rbac_roles'),
]
operations = [
# Run data migration before removing the old credential field
migrations.RunPython(migration_utils.set_current_apps_for_migrations, migrations.RunPython.noop),
migrations.RunPython(migrate_inventory_source_cred, migrate_inventory_source_cred_reverse),
migrations.RemoveField(
model_name='inventorysource',
name='credential',
),
migrations.RemoveField(
model_name='inventoryupdate',
name='credential',
),
]

View File

@@ -0,0 +1,28 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.7 on 2017-11-09 21:54
from __future__ import unicode_literals
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('sessions', '0001_initial'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('main', '0023_v330_inventory_multicred'),
]
operations = [
migrations.CreateModel(
name='UserSessionMembership',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('created', models.DateTimeField(default=None, editable=False)),
('session', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='+', to='sessions.Session')),
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='+', to=settings.AUTH_USER_MODEL)),
],
),
]

View File

@@ -0,0 +1,69 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.7 on 2017-12-04 19:49
from __future__ import unicode_literals
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
import oauth2_provider
import re
class Migration(migrations.Migration):
dependencies = [
('main', '0024_v330_create_user_session_membership'),
]
operations = [
migrations.CreateModel(
name='OAuth2Application',
fields=[
('id', models.BigAutoField(primary_key=True, serialize=False)),
('client_id', models.CharField(db_index=True, default=oauth2_provider.generators.generate_client_id, max_length=100, unique=True)),
('redirect_uris', models.TextField(blank=True, help_text='Allowed URIs list, space separated', validators=[oauth2_provider.validators.validate_uris])),
('client_type', models.CharField(choices=[('confidential', 'Confidential'), ('public', 'Public')], max_length=32)),
('authorization_grant_type', models.CharField(choices=[('authorization-code', 'Authorization code'), ('implicit', 'Implicit'), ('password', 'Resource owner password-based'), ('client-credentials', 'Client credentials')], max_length=32)),
('client_secret', models.CharField(blank=True, db_index=True, default=oauth2_provider.generators.generate_client_secret, max_length=255)),
('name', models.CharField(blank=True, max_length=255)),
('skip_authorization', models.BooleanField(default=False)),
('created', models.DateTimeField(auto_now_add=True)),
('updated', models.DateTimeField(auto_now=True)),
('description', models.TextField(blank=True, default=b'')),
('logo_data', models.TextField(default=b'', editable=False, validators=[django.core.validators.RegexValidator(re.compile(b'.*'))])),
('user', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='main_oauth2application', to=settings.AUTH_USER_MODEL)),
],
options={
'verbose_name': 'application',
},
),
migrations.CreateModel(
name='OAuth2AccessToken',
fields=[
('id', models.BigAutoField(primary_key=True, serialize=False)),
('token', models.CharField(max_length=255, unique=True)),
('expires', models.DateTimeField()),
('scope', models.TextField(blank=True)),
('created', models.DateTimeField(auto_now_add=True)),
('updated', models.DateTimeField(auto_now=True)),
('description', models.CharField(blank=True, default=b'', max_length=200)),
('last_used', models.DateTimeField(default=None, editable=False, null=True)),
('application', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.OAUTH2_PROVIDER_APPLICATION_MODEL)),
('user', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='main_oauth2accesstoken', to=settings.AUTH_USER_MODEL)),
],
options={
'verbose_name': 'access token',
},
),
migrations.AddField(
model_name='activitystream',
name='o_auth2_access_token',
field=models.ManyToManyField(to='main.OAuth2AccessToken', blank=True, related_name='main_o_auth2_accesstoken'),
),
migrations.AddField(
model_name='activitystream',
name='o_auth2_application',
field=models.ManyToManyField(to='main.OAuth2Application', blank=True, related_name='main_o_auth2_application'),
),
]

View File

@@ -0,0 +1,26 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.7 on 2018-02-27 17:58
from __future__ import unicode_literals
import awx.main.fields
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
# TODO: Squash all of these migrations with '0024_v330_add_oauth_activity_stream_registrar'
class Migration(migrations.Migration):
dependencies = [
('main', '0025_v330_add_oauth_activity_stream_registrar'),
]
operations = [
migrations.RemoveField(
model_name='authtoken',
name='user',
),
migrations.DeleteModel(
name='AuthToken',
),
]

View File

@@ -0,0 +1,20 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.11 on 2018-03-12 17:47
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('main', '0026_v330_delete_authtoken'),
]
operations = [
migrations.AddField(
model_name='unifiedjob',
name='emitted_events',
field=models.PositiveIntegerField(default=0, editable=False),
),
]

View File

@@ -0,0 +1,18 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
# AWX
from awx.main.migrations import _credentialtypes as credentialtypes
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('main', '0027_v330_emitted_events'),
]
operations = [
migrations.RunPython(credentialtypes.add_tower_verify_field),
]

View File

@@ -0,0 +1,23 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.11 on 2018-03-16 20:25
from __future__ import unicode_literals
import awx.main.fields
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('main', '0028_v330_add_tower_verify'),
]
operations = [
migrations.AddField(
model_name='oauth2application',
name='organization',
field=models.ForeignKey(help_text='Organization containing this application.', null=True, on_delete=django.db.models.deletion.CASCADE, related_name='applications', to='main.Organization'),
),
]

View File

@@ -0,0 +1,22 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.11 on 2018-04-03 20:48
from __future__ import unicode_literals
import awx.main.fields
from django.db import migrations
import oauth2_provider.generators
class Migration(migrations.Migration):
dependencies = [
('main', '0030_v330_modify_application'),
]
operations = [
migrations.AlterField(
model_name='oauth2application',
name='client_secret',
field=awx.main.fields.OAuth2ClientSecretField(blank=True, db_index=True, default=oauth2_provider.generators.generate_client_secret, max_length=1024),
),
]

View File

@@ -0,0 +1,21 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.11 on 2018-04-06 13:44
from __future__ import unicode_literals
import awx.main.utils.polymorphic
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('main', '0031_v330_encrypt_oauth2_secret'),
]
operations = [
migrations.AlterField(
model_name='unifiedjob',
name='instance_group',
field=models.ForeignKey(blank=True, default=None, help_text='The Rampart/Instance group the job was run under', null=True, on_delete=awx.main.utils.polymorphic.SET_NULL, to='main.InstanceGroup'),
),
]

View File

@@ -0,0 +1,50 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.11 on 2018-04-11 15:54
from __future__ import unicode_literals
import awx.main.fields
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
import oauth2_provider.generators
# TODO: Squash all of these migrations with '0024_v330_add_oauth_activity_stream_registrar'
class Migration(migrations.Migration):
dependencies = [
('main', '0032_v330_polymorphic_delete'),
]
operations = [
migrations.AlterField(
model_name='oauth2accesstoken',
name='scope',
field=models.TextField(blank=True, help_text="Allowed scopes, further restricts user's permissions."),
),
migrations.AlterField(
model_name='oauth2accesstoken',
name='user',
field=models.ForeignKey(blank=True, help_text='The user representing the token owner', null=True, on_delete=django.db.models.deletion.CASCADE, related_name='main_oauth2accesstoken', to=settings.AUTH_USER_MODEL),
),
migrations.AlterField(
model_name='oauth2application',
name='authorization_grant_type',
field=models.CharField(choices=[(b'authorization-code', 'Authorization code'), (b'implicit', 'Implicit'), (b'password', 'Resource owner password-based'), (b'client-credentials', 'Client credentials')], help_text='The Grant type the user must use for acquire tokens for this application.', max_length=32),
),
migrations.AlterField(
model_name='oauth2application',
name='client_secret',
field=awx.main.fields.OAuth2ClientSecretField(blank=True, db_index=True, default=oauth2_provider.generators.generate_client_secret, help_text='Used for more stringent verification of access to an application when creating a token.', max_length=1024),
),
migrations.AlterField(
model_name='oauth2application',
name='client_type',
field=models.CharField(choices=[(b'confidential', 'Confidential'), (b'public', 'Public')], help_text='Set to Public or Confidential depending on how secure the client device is.', max_length=32),
),
migrations.AlterField(
model_name='oauth2application',
name='skip_authorization',
field=models.BooleanField(default=False, help_text='Set True to skip authorization step for completely trusted applications.'),
),
]

View File

@@ -0,0 +1,22 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.11 on 2018-04-02 19:18
from __future__ import unicode_literals
from django.db import migrations
from awx.main.migrations import ActivityStreamDisabledMigration
from awx.main.migrations._rbac import delete_all_user_roles, rebuild_role_hierarchy
from awx.main.migrations import _migration_utils as migration_utils
class Migration(ActivityStreamDisabledMigration):
dependencies = [
('main', '0033_v330_oauth_help_text'),
]
operations = [
migrations.RunPython(migration_utils.set_current_apps_for_migrations),
migrations.RunPython(delete_all_user_roles),
migrations.RunPython(rebuild_role_hierarchy),
]

View File

@@ -0,0 +1,21 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.11 on 2018-04-17 18:36
from __future__ import unicode_literals
from django.db import migrations, models
# TODO: Squash all of these migrations with '0024_v330_add_oauth_activity_stream_registrar'
class Migration(migrations.Migration):
dependencies = [
('main', '0034_v330_delete_user_role'),
]
operations = [
migrations.AlterField(
model_name='oauth2accesstoken',
name='scope',
field=models.TextField(blank=True, help_text="Allowed scopes, further restricts user's permissions. Must be a simple space-separated string with allowed scopes ['read', 'write']."),
),
]

View File

@@ -0,0 +1,19 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
# AWX
from awx.main.migrations import _credentialtypes as credentialtypes
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('main', '0035_v330_more_oauth2_help_text'),
]
operations = [
migrations.RunPython(credentialtypes.remove_become_methods),
]

View File

@@ -0,0 +1,18 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
# AWX
from awx.main.migrations._scan_jobs import remove_legacy_fact_cleanup
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('main', '0036_v330_credtype_remove_become_methods'),
]
operations = [
migrations.RunPython(remove_legacy_fact_cleanup),
]

View File

@@ -184,8 +184,22 @@ def create_rhv_tower_credtype(apps, schema_editor):
CredentialType.setup_tower_managed_defaults()
def add_tower_verify_field(apps, schema_editor):
tower_credtype = CredentialType.objects.get(
kind='cloud', name='Ansible Tower', managed_by_tower=True
)
tower_credtype.inputs = CredentialType.defaults.get('tower')().inputs
tower_credtype.save()
def add_azure_cloud_environment_field(apps, schema_editor):
azure_rm_credtype = CredentialType.objects.get(kind='cloud',
name='Microsoft Azure Resource Manager')
azure_rm_credtype.inputs = CredentialType.defaults.get('azure_rm')().inputs
azure_rm_credtype.save()
def remove_become_methods(apps, schema_editor):
become_credtype = CredentialType.objects.filter(kind='ssh', managed_by_tower=True).first()
become_credtype.inputs = CredentialType.defaults.get('ssh')().inputs
become_credtype.save()

View File

@@ -32,3 +32,25 @@ def migrate_workflow_cred_reverse(app, schema_editor):
if cred:
node.credential = cred
node.save()
def migrate_inventory_source_cred(app, schema_editor):
InventoryUpdate = app.get_model('main', 'InventoryUpdate')
InventorySource = app.get_model('main', 'InventorySource')
for cls in (InventoryUpdate, InventorySource):
for obj in cls.objects.iterator():
if obj.credential:
obj.credentials.add(obj.credential)
def migrate_inventory_source_cred_reverse(app, schema_editor):
InventoryUpdate = app.get_model('main', 'InventoryUpdate')
InventorySource = app.get_model('main', 'InventorySource')
for cls in (InventoryUpdate, InventorySource):
for obj in cls.objects.iterator():
cred = obj.credentials.first()
if cred:
obj.credential = cred
obj.save()

View File

@@ -500,3 +500,12 @@ def infer_credential_org_from_team(apps, schema_editor):
_update_credential_parents(cred.deprecated_team.organization, cred)
except IntegrityError:
logger.info("Organization<{}> credential for old Team<{}> credential already created".format(cred.deprecated_team.organization.pk, cred.pk))
def delete_all_user_roles(apps, schema_editor):
ContentType = apps.get_model('contenttypes', "ContentType")
Role = apps.get_model('main', "Role")
User = apps.get_model('auth', "User")
user_content_type = ContentType.objects.get_for_model(User)
for role in Role.objects.filter(content_type=user_content_type).iterator():
role.delete()

View File

@@ -102,3 +102,11 @@ def remove_scan_type_nodes(apps, schema_editor):
prompts.pop('job_type')
node.char_prompts = prompts
node.save()
def remove_legacy_fact_cleanup(apps, schema_editor):
SystemJobTemplate = apps.get_model('main', 'SystemJobTemplate')
for job in SystemJobTemplate.objects.filter(job_type='cleanup_facts').all():
for sched in job.schedules.all():
sched.delete()
job.delete()

View File

@@ -24,6 +24,11 @@ from awx.main.models.fact import * # noqa
from awx.main.models.label import * # noqa
from awx.main.models.workflow import * # noqa
from awx.main.models.channels import * # noqa
from awx.api.versioning import reverse
from awx.main.models.oauth import * # noqa
from oauth2_provider.models import Grant, RefreshToken # noqa -- needed django-oauth-toolkit model migrations
# Monkeypatch Django serializer to ignore django-taggit fields (which break
# the dumpdata command; see https://github.com/alex/django-taggit/issues/155).
@@ -51,7 +56,6 @@ User.add_to_class('get_queryset', get_user_queryset)
User.add_to_class('can_access', check_user_access)
User.add_to_class('can_access_with_errors', check_user_access_with_errors)
User.add_to_class('accessible_objects', user_accessible_objects)
User.add_to_class('admin_role', user_admin_role)
@property
@@ -113,6 +117,23 @@ def user_is_in_enterprise_category(user, category):
User.add_to_class('is_in_enterprise_category', user_is_in_enterprise_category)
def o_auth2_application_get_absolute_url(self, request=None):
return reverse('api:o_auth2_application_detail', kwargs={'pk': self.pk}, request=request)
OAuth2Application.add_to_class('get_absolute_url', o_auth2_application_get_absolute_url)
def o_auth2_token_get_absolute_url(self, request=None):
return reverse('api:o_auth2_token_detail', kwargs={'pk': self.pk}, request=request)
OAuth2AccessToken.add_to_class('get_absolute_url', o_auth2_token_get_absolute_url)
# Import signal handlers only after models have been defined.
import awx.main.signals # noqa
@@ -143,6 +164,8 @@ activity_stream_registrar.connect(User)
activity_stream_registrar.connect(WorkflowJobTemplate)
activity_stream_registrar.connect(WorkflowJobTemplateNode)
activity_stream_registrar.connect(WorkflowJob)
activity_stream_registrar.connect(OAuth2Application)
activity_stream_registrar.connect(OAuth2AccessToken)
# prevent API filtering on certain Django-supplied sensitive fields
prevent_search(User._meta.get_field('password'))

View File

@@ -66,6 +66,10 @@ class ActivityStream(models.Model):
label = models.ManyToManyField("Label", blank=True)
role = models.ManyToManyField("Role", blank=True)
instance_group = models.ManyToManyField("InstanceGroup", blank=True)
o_auth2_application = models.ManyToManyField("OAuth2Application", blank=True)
o_auth2_access_token = models.ManyToManyField("OAuth2AccessToken", blank=True)
setting = JSONField(blank=True)

View File

@@ -93,10 +93,10 @@ class BaseModel(models.Model):
abstract = True
def __unicode__(self):
if hasattr(self, 'name'):
return u'%s-%s' % (self.name, self.id)
if 'name' in self.__dict__:
return u'%s-%s' % (self.name, self.pk)
else:
return u'%s-%s' % (self._meta.verbose_name, self.id)
return u'%s-%s' % (self._meta.verbose_name, self.pk)
def clean_fields(self, exclude=None):
'''
@@ -256,6 +256,7 @@ class PrimordialModel(CreatedModifiedModel):
def save(self, *args, **kwargs):
update_fields = kwargs.get('update_fields', [])
fields_are_specified = bool(update_fields)
user = get_current_user()
if user and not user.id:
user = None
@@ -263,9 +264,14 @@ class PrimordialModel(CreatedModifiedModel):
self.created_by = user
if 'created_by' not in update_fields:
update_fields.append('created_by')
self.modified_by = user
if 'modified_by' not in update_fields:
update_fields.append('modified_by')
# Update modified_by if not called with update_fields, or if any
# editable fields are present in update_fields
if (
(not fields_are_specified) or
any(getattr(self._meta.get_field(name), 'editable', True) for name in update_fields)):
self.modified_by = user
if 'modified_by' not in update_fields:
update_fields.append('modified_by')
super(PrimordialModel, self).save(*args, **kwargs)
def clean_description(self):

View File

@@ -2,13 +2,12 @@
# All Rights Reserved.
from collections import OrderedDict
import functools
import json
import logging
import operator
import os
import re
import stat
import tempfile
import six
# Jinja2
from jinja2 import Template
@@ -21,11 +20,11 @@ from django.utils.encoding import force_text
# AWX
from awx.api.versioning import reverse
from awx.main.constants import PRIVILEGE_ESCALATION_METHODS
from awx.main.fields import (ImplicitRoleField, CredentialInputField,
CredentialTypeInputField,
CredentialTypeInjectorField)
from awx.main.utils import decrypt_field
from awx.main.utils.safe_yaml import safe_dump
from awx.main.validators import validate_ssh_private_key
from awx.main.models.base import * # noqa
from awx.main.models.mixins import ResourceMixin
@@ -34,6 +33,7 @@ from awx.main.models.rbac import (
ROLE_SINGLETON_SYSTEM_AUDITOR,
)
from awx.main.utils import encrypt_field
from awx.main.constants import CHOICES_PRIVILEGE_ESCALATION_METHODS
from . import injectors as builtin_injectors
__all__ = ['Credential', 'CredentialType', 'V1Credential', 'build_safe_env']
@@ -164,7 +164,7 @@ class V1Credential(object):
max_length=32,
blank=True,
default='',
choices=[('', _('None'))] + PRIVILEGE_ESCALATION_METHODS,
choices=CHOICES_PRIVILEGE_ESCALATION_METHODS,
help_text=_('Privilege escalation method.')
),
'become_username': models.CharField(
@@ -415,9 +415,9 @@ class Credential(PasswordFieldsModel, CommonModelNameNotUnique, ResourceMixin):
type_alias = self.credential_type_id
if self.kind == 'vault' and self.inputs.get('vault_id', None):
if display:
fmt_str = '{} (id={})'
fmt_str = six.text_type('{} (id={})')
else:
fmt_str = '{}_{}'
fmt_str = six.text_type('{}_{}')
return fmt_str.format(type_alias, self.inputs.get('vault_id'))
return str(type_alias)
@@ -445,6 +445,7 @@ class CredentialType(CommonModelNameNotUnique):
'AD_HOC_COMMAND_ID', 'REST_API_URL', 'REST_API_TOKEN', 'MAX_EVENT_RES',
'CALLBACK_QUEUE', 'CALLBACK_CONNECTION', 'CACHE',
'JOB_CALLBACK_DEBUG', 'INVENTORY_HOSTVARS', 'FACT_QUEUE',
'AWX_HOST', 'PROJECT_REVISION'
))
class Meta:
@@ -514,7 +515,7 @@ class CredentialType(CommonModelNameNotUnique):
if field['id'] == field_id:
if 'choices' in field:
return field['choices'][0]
return {'string': '', 'boolean': False}[field['type']]
return {'string': '', 'boolean': False, 'become_method': ''}[field['type']]
@classmethod
def default(cls, f):
@@ -630,7 +631,7 @@ class CredentialType(CommonModelNameNotUnique):
data = Template(file_tmpl).render(**namespace)
_, path = tempfile.mkstemp(dir=private_data_dir)
with open(path, 'w') as f:
f.write(data)
f.write(data.encode('utf-8'))
os.chmod(path, stat.S_IRUSR | stat.S_IWUSR)
# determine if filename indicates single file or many
@@ -648,27 +649,24 @@ class CredentialType(CommonModelNameNotUnique):
env[env_var] = Template(tmpl).render(**namespace)
safe_env[env_var] = Template(tmpl).render(**safe_namespace)
extra_vars = {}
safe_extra_vars = {}
for var_name, tmpl in self.injectors.get('extra_vars', {}).items():
extra_vars[var_name] = Template(tmpl).render(**namespace)
safe_extra_vars[var_name] = Template(tmpl).render(**safe_namespace)
if 'INVENTORY_UPDATE_ID' not in env:
# awx-manage inventory_update does not support extra_vars via -e
extra_vars = {}
for var_name, tmpl in self.injectors.get('extra_vars', {}).items():
extra_vars[var_name] = Template(tmpl).render(**namespace)
def build_extra_vars_file(vars, private_dir):
handle, path = tempfile.mkstemp(dir = private_dir)
f = os.fdopen(handle, 'w')
f.write(json.dumps(vars))
f.close()
os.chmod(path, stat.S_IRUSR)
return path
def build_extra_vars_file(vars, private_dir):
handle, path = tempfile.mkstemp(dir = private_dir)
f = os.fdopen(handle, 'w')
f.write(safe_dump(vars))
f.close()
os.chmod(path, stat.S_IRUSR)
return path
if extra_vars:
path = build_extra_vars_file(extra_vars, private_data_dir)
args.extend(['-e', '@%s' % path])
if safe_extra_vars:
path = build_extra_vars_file(safe_extra_vars, private_data_dir)
safe_args.extend(['-e', '@%s' % path])
if extra_vars:
args.extend(['-e', '@%s' % path])
safe_args.extend(['-e', '@%s' % path])
@CredentialType.default
@@ -704,8 +702,7 @@ def ssh(cls):
}, {
'id': 'become_method',
'label': 'Privilege Escalation Method',
'choices': map(operator.itemgetter(0),
V1Credential.FIELDS['become_method'].choices),
'type': 'become_method',
'help_text': ('Specify a method for "become" operations. This is '
'equivalent to specifying the --become-method '
'Ansible parameter.')
@@ -1179,6 +1176,11 @@ def tower(cls):
'label': 'Password',
'type': 'string',
'secret': True,
}, {
'id': 'verify_ssl',
'label': 'Verify SSL',
'type': 'boolean',
'secret': False
}],
'required': ['host', 'username', 'password'],
},
@@ -1187,6 +1189,7 @@ def tower(cls):
'TOWER_HOST': '{{host}}',
'TOWER_USERNAME': '{{username}}',
'TOWER_PASSWORD': '{{password}}',
'TOWER_VERIFY_SSL': '{{verify_ssl}}'
}
},
)

View File

@@ -2,7 +2,7 @@ import datetime
import logging
from django.conf import settings
from django.db import models
from django.db import models, DatabaseError
from django.utils.dateparse import parse_datetime
from django.utils.timezone import utc
from django.utils.translation import ugettext_lazy as _
@@ -15,6 +15,8 @@ from awx.main.utils import ignore_inventory_computed_fields
analytics_logger = logging.getLogger('awx.analytics.job_events')
logger = logging.getLogger('awx.main.models.events')
__all__ = ['JobEvent', 'ProjectUpdateEvent', 'AdHocCommandEvent',
'InventoryUpdateEvent', 'SystemJobEvent']
@@ -235,12 +237,6 @@ class BasePlaybookEvent(CreatedModifiedModel):
if res.get('changed', False):
self.changed = True
updated_fields.add('changed')
# If we're not in verbose mode, wipe out any module arguments.
invocation = res.get('invocation', None)
if isinstance(invocation, dict) and self.job_verbosity == 0 and 'module_args' in invocation:
event_data['res']['invocation']['module_args'] = ''
self.event_data = event_data
updated_fields.add('event_data')
if self.event == 'playbook_on_stats':
try:
failures_dict = event_data.get('failures', {})
@@ -288,37 +284,8 @@ class BasePlaybookEvent(CreatedModifiedModel):
if key not in self.VALID_KEYS:
kwargs.pop(key)
event_data = kwargs.get('event_data', None)
artifact_dict = None
if event_data:
artifact_dict = event_data.pop('artifact_data', None)
job_event = self.objects.create(**kwargs)
analytics_logger.info('Event data saved.', extra=dict(python_objects=dict(job_event=job_event)))
# Save artifact data to parent job (if provided).
if artifact_dict:
if event_data and isinstance(event_data, dict):
# Note: Core has not added support for marking artifacts as
# sensitive yet. Going forward, core will not use
# _ansible_no_log to denote sensitive set_stats calls.
# Instead, they plan to add a flag outside of the traditional
# no_log mechanism. no_log will not work for this feature,
# in core, because sensitive data is scrubbed before sending
# data to the callback. The playbook_on_stats is the callback
# in which the set_stats data is used.
# Again, the sensitive artifact feature has not yet landed in
# core. The below is how we mark artifacts payload as
# senstive
# artifact_dict['_ansible_no_log'] = True
#
parent_job = self.objects.filter(pk=pk).first()
if hasattr(parent_job, 'artifacts') and parent_job.artifacts != artifact_dict:
parent_job.artifacts = artifact_dict
parent_job.save(update_fields=['artifacts'])
return job_event
@property
@@ -358,7 +325,10 @@ class BasePlaybookEvent(CreatedModifiedModel):
hostnames = self._hostnames()
self._update_host_summary_from_stats(hostnames)
self.job.inventory.update_computed_fields()
try:
self.job.inventory.update_computed_fields()
except DatabaseError:
logger.exception('Computed fields database error saving event {}'.format(self.pk))
@@ -476,6 +446,9 @@ class JobEvent(BasePlaybookEvent):
def _update_host_summary_from_stats(self, hostnames):
with ignore_inventory_computed_fields():
if not self.job or not self.job.inventory:
logger.info('Event {} missing job or inventory, host summaries not updated'.format(self.pk))
return
qs = self.job.inventory.hosts.filter(name__in=hostnames)
job = self.job
for host in hostnames:
@@ -599,6 +572,12 @@ class BaseCommandEvent(CreatedModifiedModel):
return self.objects.create(**kwargs)
def get_event_display(self):
'''
Needed for __unicode__
'''
return self.event
class AdHocCommandEvent(BaseCommandEvent):

View File

@@ -21,6 +21,7 @@ from awx.main.models.jobs import Job
from awx.main.models.projects import ProjectUpdate
from awx.main.models.unified_jobs import UnifiedJob
from awx.main.utils import get_cpu_capacity, get_mem_capacity, get_system_task_capacity
from awx.main.models.mixins import RelatedJobsMixin
__all__ = ('Instance', 'InstanceGroup', 'JobOrigin', 'TowerScheduleState',)
@@ -84,6 +85,10 @@ class Instance(models.Model):
# NOTE: TODO: Likely to repurpose this once standalone ramparts are a thing
return "awx"
@property
def jobs_running(self):
return UnifiedJob.objects.filter(execution_node=self.hostname, status__in=('running', 'waiting',)).count()
def is_lost(self, ref_time=None, isolated=False):
if ref_time is None:
ref_time = now()
@@ -110,7 +115,7 @@ class Instance(models.Model):
class InstanceGroup(models.Model):
class InstanceGroup(models.Model, RelatedJobsMixin):
"""A model representing a Queue/Group of AWX Instances."""
objects = InstanceGroupManager()
@@ -152,6 +157,13 @@ class InstanceGroup(models.Model):
def capacity(self):
return sum([inst.capacity for inst in self.instances.all()])
'''
RelatedJobsMixin
'''
def _get_related_jobs(self):
return UnifiedJob.objects.filter(instance_group=self)
class Meta:
app_label = 'main'
@@ -180,9 +192,8 @@ class JobOrigin(models.Model):
@receiver(post_save, sender=InstanceGroup)
def on_instance_group_saved(sender, instance, created=False, raw=False, **kwargs):
if created:
from awx.main.tasks import apply_cluster_membership_policies
connection.on_commit(lambda: apply_cluster_membership_policies.apply_async())
from awx.main.tasks import apply_cluster_membership_policies
connection.on_commit(lambda: apply_cluster_membership_policies.apply_async())
@receiver(post_save, sender=Instance)

View File

@@ -9,6 +9,7 @@ import copy
from urlparse import urljoin
import os.path
import six
from distutils.version import LooseVersion
# Django
from django.conf import settings
@@ -32,12 +33,17 @@ from awx.main.managers import HostManager
from awx.main.models.base import * # noqa
from awx.main.models.events import InventoryUpdateEvent
from awx.main.models.unified_jobs import * # noqa
from awx.main.models.mixins import ResourceMixin, TaskManagerInventoryUpdateMixin
from awx.main.models.mixins import (
ResourceMixin,
TaskManagerInventoryUpdateMixin,
RelatedJobsMixin,
)
from awx.main.models.notifications import (
NotificationTemplate,
JobNotificationMixin,
)
from awx.main.utils import _inventory_updates
from awx.main.utils import _inventory_updates, get_ansible_version, region_sorting
__all__ = ['Inventory', 'Host', 'Group', 'InventorySource', 'InventoryUpdate',
'CustomInventoryScript', 'SmartInventoryMembership']
@@ -45,7 +51,7 @@ __all__ = ['Inventory', 'Host', 'Group', 'InventorySource', 'InventoryUpdate',
logger = logging.getLogger('awx.main.models.inventory')
class Inventory(CommonModelNameNotUnique, ResourceMixin):
class Inventory(CommonModelNameNotUnique, ResourceMixin, RelatedJobsMixin):
'''
an inventory source contains lists and hosts.
'''
@@ -227,7 +233,7 @@ class Inventory(CommonModelNameNotUnique, ResourceMixin):
return {}
else:
all_group = data.setdefault('all', dict())
smart_hosts_qs = self.hosts.all()
smart_hosts_qs = self.hosts.filter(**hosts_q).all()
smart_hosts = list(smart_hosts_qs.values_list('name', flat=True))
all_group['hosts'] = smart_hosts
else:
@@ -487,6 +493,16 @@ class Inventory(CommonModelNameNotUnique, ResourceMixin):
self._update_host_smart_inventory_memeberships()
super(Inventory, self).delete(*args, **kwargs)
'''
RelatedJobsMixin
'''
def _get_related_jobs(self):
return UnifiedJob.objects.non_polymorphic().filter(
Q(Job___inventory=self) |
Q(InventoryUpdate___inventory_source__inventory=self) |
Q(AdHocCommand___inventory=self)
)
class SmartInventoryMembership(BaseModel):
'''
@@ -501,7 +517,7 @@ class SmartInventoryMembership(BaseModel):
host = models.ForeignKey('Host', related_name='+', on_delete=models.CASCADE)
class Host(CommonModelNameNotUnique):
class Host(CommonModelNameNotUnique, RelatedJobsMixin):
'''
A managed node
'''
@@ -594,9 +610,6 @@ class Host(CommonModelNameNotUnique):
objects = HostManager()
def __unicode__(self):
return self.name
def get_absolute_url(self, request=None):
return reverse('api:host_detail', kwargs={'pk': self.pk}, request=request)
@@ -690,8 +703,14 @@ class Host(CommonModelNameNotUnique):
self._update_host_smart_inventory_memeberships()
super(Host, self).delete(*args, **kwargs)
'''
RelatedJobsMixin
'''
def _get_related_jobs(self):
return self.inventory._get_related_jobs()
class Group(CommonModelNameNotUnique):
class Group(CommonModelNameNotUnique, RelatedJobsMixin):
'''
A group containing managed hosts. A group or host may belong to multiple
groups.
@@ -766,9 +785,6 @@ class Group(CommonModelNameNotUnique):
help_text=_('Inventory source(s) that created or modified this group.'),
)
def __unicode__(self):
return self.name
def get_absolute_url(self, request=None):
return reverse('api:group_detail', kwargs={'pk': self.pk}, request=request)
@@ -946,6 +962,15 @@ class Group(CommonModelNameNotUnique):
from awx.main.models.ad_hoc_commands import AdHocCommand
return AdHocCommand.objects.filter(hosts__in=self.all_hosts)
'''
RelatedJobsMixin
'''
def _get_related_jobs(self):
return UnifiedJob.objects.non_polymorphic().filter(
Q(Job___inventory=self.inventory) |
Q(InventoryUpdate___inventory_source__groups=self)
)
class InventorySourceOptions(BaseModel):
'''
@@ -1084,14 +1109,6 @@ class InventorySourceOptions(BaseModel):
default='',
help_text=_('Inventory source variables in YAML or JSON format.'),
)
credential = models.ForeignKey(
'Credential',
related_name='%(class)ss',
null=True,
default=None,
blank=True,
on_delete=models.SET_NULL,
)
source_regions = models.CharField(
max_length=1024,
blank=True,
@@ -1148,7 +1165,7 @@ class InventorySourceOptions(BaseModel):
label_parts.append(part)
label = ' '.join(label_parts)
regions.append((region.name, label))
return regions
return sorted(regions, key=region_sorting)
@classmethod
def get_ec2_group_by_choices(cls):
@@ -1158,6 +1175,7 @@ class InventorySourceOptions(BaseModel):
('aws_account', _('Account')),
('instance_id', _('Instance ID')),
('instance_state', _('Instance State')),
('platform', _('Platform')),
('instance_type', _('Instance Type')),
('key_pair', _('Key Name')),
('region', _('Region')),
@@ -1176,7 +1194,7 @@ class InventorySourceOptions(BaseModel):
# authenticating first. Therefore, use a list from settings.
regions = list(getattr(settings, 'GCE_REGION_CHOICES', []))
regions.insert(0, ('all', 'All'))
return regions
return sorted(regions, key=region_sorting)
@classmethod
def get_azure_rm_region_choices(self):
@@ -1189,7 +1207,7 @@ class InventorySourceOptions(BaseModel):
# settings.
regions = list(getattr(settings, 'AZURE_RM_REGION_CHOICES', []))
regions.insert(0, ('all', 'All'))
return regions
return sorted(regions, key=region_sorting)
@classmethod
def get_vmware_region_choices(self):
@@ -1223,30 +1241,56 @@ class InventorySourceOptions(BaseModel):
"""No region supprt"""
return [('all', 'All')]
def clean_credential(self):
if not self.source:
@staticmethod
def cloud_credential_validation(source, cred):
if not source:
return None
cred = self.credential
if cred and self.source not in ('custom', 'scm'):
if cred and source not in ('custom', 'scm'):
# If a credential was provided, it's important that it matches
# the actual inventory source being used (Amazon requires Amazon
# credentials; Rackspace requires Rackspace credentials; etc...)
if self.source.replace('ec2', 'aws') != cred.kind:
raise ValidationError(
_('Cloud-based inventory sources (such as %s) require '
'credentials for the matching cloud service.') % self.source
)
if source.replace('ec2', 'aws') != cred.kind:
return _('Cloud-based inventory sources (such as %s) require '
'credentials for the matching cloud service.') % source
# Allow an EC2 source to omit the credential. If Tower is running on
# an EC2 instance with an IAM Role assigned, boto will use credentials
# from the instance metadata instead of those explicitly provided.
elif self.source in CLOUD_PROVIDERS and self.source != 'ec2':
raise ValidationError(_('Credential is required for a cloud source.'))
elif self.source == 'custom' and cred and cred.credential_type.kind in ('scm', 'ssh', 'insights', 'vault'):
raise ValidationError(_(
elif source in CLOUD_PROVIDERS and source != 'ec2':
return _('Credential is required for a cloud source.')
elif source == 'custom' and cred and cred.credential_type.kind in ('scm', 'ssh', 'insights', 'vault'):
return _(
'Credentials of type machine, source control, insights and vault are '
'disallowed for custom inventory sources.'
))
return cred
)
return None
def get_inventory_plugin_name(self):
if self.source in CLOUD_PROVIDERS or self.source == 'custom':
# TODO: today, all vendored sources are scripts
# in future release inventory plugins will replace these
return 'script'
# in other cases we do not specify which plugin to use
return None
def get_deprecated_credential(self, kind):
for cred in self.credentials.all():
if cred.credential_type.kind == kind:
return cred
else:
return None
def get_cloud_credential(self):
credential = None
for cred in self.credentials.all():
if cred.credential_type.kind != 'vault':
credential = cred
return credential
@property
def credential(self):
cred = self.get_cloud_credential()
if cred is not None:
return cred.pk
def clean_source_regions(self):
regions = self.source_regions
@@ -1321,7 +1365,7 @@ class InventorySourceOptions(BaseModel):
return ''
class InventorySource(UnifiedJobTemplate, InventorySourceOptions):
class InventorySource(UnifiedJobTemplate, InventorySourceOptions, RelatedJobsMixin):
SOFT_UNIQUE_TOGETHER = [('polymorphic_ctype', 'name', 'inventory')]
@@ -1376,7 +1420,7 @@ class InventorySource(UnifiedJobTemplate, InventorySourceOptions):
@classmethod
def _get_unified_job_field_names(cls):
return set(f.name for f in InventorySourceOptions._meta.fields) | set(
['name', 'description', 'schedule']
['name', 'description', 'schedule', 'credentials']
)
def save(self, *args, **kwargs):
@@ -1529,9 +1573,10 @@ class InventorySource(UnifiedJobTemplate, InventorySourceOptions):
"Instead, configure the corresponding source project to update on launch."))
return self.update_on_launch
def clean_overwrite_vars(self):
def clean_overwrite_vars(self): # TODO: remove when Ansible 2.4 becomes unsupported, obviously
if self.source == 'scm' and not self.overwrite_vars:
raise ValidationError(_("SCM type sources must set `overwrite_vars` to `true`."))
if get_ansible_version() < LooseVersion('2.5'):
raise ValidationError(_("SCM type sources must set `overwrite_vars` to `true` until Ansible 2.5."))
return self.overwrite_vars
def clean_source_path(self):
@@ -1539,6 +1584,12 @@ class InventorySource(UnifiedJobTemplate, InventorySourceOptions):
raise ValidationError(_("Cannot set source_path if not SCM type."))
return self.source_path
'''
RelatedJobsMixin
'''
def _get_related_jobs(self):
return InventoryUpdate.objects.filter(inventory_source=self)
class InventoryUpdate(UnifiedJob, InventorySourceOptions, JobNotificationMixin, TaskManagerInventoryUpdateMixin):
'''
@@ -1621,7 +1672,7 @@ class InventoryUpdate(UnifiedJob, InventorySourceOptions, JobNotificationMixin,
return False
if (self.source not in ('custom', 'ec2', 'scm') and
not (self.credential)):
not (self.get_cloud_credential())):
return False
elif self.source == 'scm' and not self.inventory_source.source_project:
return False
@@ -1646,6 +1697,8 @@ class InventoryUpdate(UnifiedJob, InventorySourceOptions, JobNotificationMixin,
organization_groups = []
if self.inventory_source.inventory is not None:
inventory_groups = [x for x in self.inventory_source.inventory.instance_groups.all()]
else:
inventory_groups = []
selected_groups = inventory_groups + organization_groups
if not selected_groups:
return self.global_instance_groups

View File

@@ -35,7 +35,14 @@ from awx.main.models.notifications import (
)
from awx.main.utils import parse_yaml_or_json
from awx.main.fields import ImplicitRoleField
from awx.main.models.mixins import ResourceMixin, SurveyJobTemplateMixin, SurveyJobMixin, TaskManagerJobMixin, CustomVirtualEnvMixin
from awx.main.models.mixins import (
ResourceMixin,
SurveyJobTemplateMixin,
SurveyJobMixin,
TaskManagerJobMixin,
CustomVirtualEnvMixin,
RelatedJobsMixin,
)
from awx.main.fields import JSONField, AskForField
@@ -216,7 +223,7 @@ class JobOptions(BaseModel):
return needed
class JobTemplate(UnifiedJobTemplate, JobOptions, SurveyJobTemplateMixin, ResourceMixin, CustomVirtualEnvMixin):
class JobTemplate(UnifiedJobTemplate, JobOptions, SurveyJobTemplateMixin, ResourceMixin, CustomVirtualEnvMixin, RelatedJobsMixin):
'''
A job template is a reusable job definition for applying a project (with
playbook) to an inventory source with a given credential.
@@ -444,6 +451,12 @@ class JobTemplate(UnifiedJobTemplate, JobOptions, SurveyJobTemplateMixin, Resour
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))
'''
RelatedJobsMixin
'''
def _get_related_jobs(self):
return Job.objects.filter(job_template=self)
class Job(UnifiedJob, JobOptions, SurveyJobMixin, JobNotificationMixin, TaskManagerJobMixin):
'''
@@ -525,7 +538,7 @@ class Job(UnifiedJob, JobOptions, SurveyJobMixin, JobNotificationMixin, TaskMana
for virtualenv in (
self.job_template.custom_virtualenv if self.job_template else None,
self.project.custom_virtualenv,
self.project.organization.custom_virtualenv
self.project.organization.custom_virtualenv if self.project.organization else None
):
if virtualenv:
return virtualenv
@@ -746,7 +759,7 @@ class Job(UnifiedJob, JobOptions, SurveyJobMixin, JobNotificationMixin, TaskMana
def start_job_fact_cache(self, destination, modification_times, timeout=None):
destination = os.path.join(destination, 'facts')
os.makedirs(destination, mode=0700)
os.makedirs(destination, mode=0o700)
hosts = self._get_inventory_hosts()
if timeout is None:
timeout = settings.ANSIBLE_FACT_CACHE_TIMEOUT
@@ -760,7 +773,7 @@ class Job(UnifiedJob, JobOptions, SurveyJobMixin, JobNotificationMixin, TaskMana
system_tracking_logger.error('facts for host {} could not be cached'.format(smart_str(host.name)))
continue
with codecs.open(filepath, 'w', encoding='utf-8') as f:
os.chmod(f.name, 0600)
os.chmod(f.name, 0o600)
json.dump(host.ansible_facts, f)
# make note of the time we wrote the file so we can check if it changed later
modification_times[filepath] = os.path.getmtime(filepath)
@@ -950,7 +963,17 @@ class JobLaunchConfig(LaunchTimeConfig):
launching with those prompts
'''
prompts = self.prompts_dict()
for field_name, ask_field_name in template.get_ask_mapping().items():
ask_mapping = template.get_ask_mapping()
if template.survey_enabled and (not template.ask_variables_on_launch):
ask_mapping.pop('extra_vars')
provided_vars = set(prompts['extra_vars'].keys())
survey_vars = set(
element.get('variable') for element in
template.survey_spec.get('spec', {}) if 'variable' in element
)
if provided_vars - survey_vars:
return True
for field_name, ask_field_name in ask_mapping.items():
if field_name in prompts and not getattr(template, ask_field_name):
return True
else:

View File

@@ -6,12 +6,14 @@ from copy import copy, deepcopy
import six
# Django
from django.apps import apps
from django.conf import settings
from django.db import models
from django.contrib.contenttypes.models import ContentType
from django.contrib.auth.models import User # noqa
from django.utils.translation import ugettext_lazy as _
from django.core.exceptions import ValidationError
from django.db.models.query import QuerySet
# AWX
from awx.main.models.base import prevent_search
@@ -20,7 +22,9 @@ from awx.main.models.rbac import (
)
from awx.main.utils import parse_yaml_or_json, get_custom_venv_choices
from awx.main.utils.encryption import decrypt_value, get_encryption_key, is_encrypted
from awx.main.utils.polymorphic import build_polymorphic_ctypes_map
from awx.main.fields import JSONField, AskForField
from awx.main.constants import ACTIVE_STATES
__all__ = ['ResourceMixin', 'SurveyJobTemplateMixin', 'SurveyJobMixin',
@@ -441,4 +445,34 @@ class CustomVirtualEnvMixin(models.Model):
raise ValidationError(
_('{} is not a valid virtualenv in {}').format(value, settings.BASE_VENV_PATH)
)
return os.path.join(value or '', '')
if value:
return os.path.join(value, '')
return None
class RelatedJobsMixin(object):
'''
This method is intended to be overwritten.
Called by get_active_jobs()
Returns a list of active jobs (i.e. running) associated with the calling
resource (self). Expected to return a QuerySet
'''
def _get_related_jobs(self):
return self.objects.none()
def _get_active_jobs(self):
return self._get_related_jobs().filter(status__in=ACTIVE_STATES)
'''
Returns [{'id': '1', 'type': 'job'}, {'id': 2, 'type': 'project_update'}, ...]
'''
def get_active_jobs(self):
UnifiedJob = apps.get_model('main', 'UnifiedJob')
mapping = build_polymorphic_ctypes_map(UnifiedJob)
jobs = self._get_active_jobs()
if not isinstance(jobs, QuerySet):
raise RuntimeError("Programmer error. Expected _get_active_jobs() to return a QuerySet.")
return [dict(id=str(t[0]), type=mapping[t[1]]) for t in jobs.values_list('id', 'polymorphic_ctype_id')]

View File

@@ -20,6 +20,7 @@ from awx.main.notifications.pagerduty_backend import PagerDutyBackend
from awx.main.notifications.hipchat_backend import HipChatBackend
from awx.main.notifications.webhook_backend import WebhookBackend
from awx.main.notifications.mattermost_backend import MattermostBackend
from awx.main.notifications.rocketchat_backend import RocketChatBackend
from awx.main.notifications.irc_backend import IrcBackend
from awx.main.fields import JSONField
@@ -38,6 +39,7 @@ class NotificationTemplate(CommonModelNameNotUnique):
('hipchat', _('HipChat'), HipChatBackend),
('webhook', _('Webhook'), WebhookBackend),
('mattermost', _('Mattermost'), MattermostBackend),
('rocketchat', _('Rocket.Chat'), RocketChatBackend),
('irc', _('IRC'), IrcBackend)]
NOTIFICATION_TYPE_CHOICES = [(x[0], x[1]) for x in NOTIFICATION_TYPES]
CLASS_FOR_NOTIFICATION_TYPE = dict([(x[0], x[2]) for x in NOTIFICATION_TYPES])
@@ -209,3 +211,27 @@ class JobNotificationMixin(object):
def build_notification_failed_message(self):
return self._build_notification_message('failed')
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"))
try:
notification_templates = self.get_notification_templates()
except Exception:
logger.warn("No notification template defined for emitting notification")
notification_templates = None
if notification_templates:
if status_str == 'succeeded':
notification_template_type = 'success'
else:
notification_template_type = 'error'
all_notification_templates = set(notification_templates.get(notification_template_type, []) + notification_templates.get('any', []))
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)

121
awx/main/models/oauth.py Normal file
View File

@@ -0,0 +1,121 @@
# Python
import re
# Django
from django.core.validators import RegexValidator
from django.db import models
from django.utils.timezone import now
from django.utils.translation import ugettext_lazy as _
from django.conf import settings
# Django OAuth Toolkit
from oauth2_provider.models import AbstractApplication, AbstractAccessToken
from oauth2_provider.generators import generate_client_secret
from awx.main.fields import OAuth2ClientSecretField
DATA_URI_RE = re.compile(r'.*') # FIXME
__all__ = ['OAuth2AccessToken', 'OAuth2Application']
class OAuth2Application(AbstractApplication):
class Meta:
app_label = 'main'
verbose_name = _('application')
CLIENT_CONFIDENTIAL = "confidential"
CLIENT_PUBLIC = "public"
CLIENT_TYPES = (
(CLIENT_CONFIDENTIAL, _("Confidential")),
(CLIENT_PUBLIC, _("Public")),
)
GRANT_AUTHORIZATION_CODE = "authorization-code"
GRANT_IMPLICIT = "implicit"
GRANT_PASSWORD = "password"
GRANT_CLIENT_CREDENTIALS = "client-credentials"
GRANT_TYPES = (
(GRANT_AUTHORIZATION_CODE, _("Authorization code")),
(GRANT_IMPLICIT, _("Implicit")),
(GRANT_PASSWORD, _("Resource owner password-based")),
(GRANT_CLIENT_CREDENTIALS, _("Client credentials")),
)
description = models.TextField(
default='',
blank=True,
)
logo_data = models.TextField(
default='',
editable=False,
validators=[RegexValidator(DATA_URI_RE)],
)
organization = models.ForeignKey(
'Organization',
related_name='applications',
help_text=_('Organization containing this application.'),
on_delete=models.CASCADE,
null=True,
)
client_secret = OAuth2ClientSecretField(
max_length=1024,
blank=True,
default=generate_client_secret,
db_index=True,
help_text=_('Used for more stringent verification of access to an application when creating a token.')
)
client_type = models.CharField(
max_length=32,
choices=CLIENT_TYPES,
help_text=_('Set to Public or Confidential depending on how secure the client device is.')
)
skip_authorization = models.BooleanField(
default=False,
help_text=_('Set True to skip authorization step for completely trusted applications.')
)
authorization_grant_type = models.CharField(
max_length=32,
choices=GRANT_TYPES,
help_text=_('The Grant type the user must use for acquire tokens for this application.')
)
class OAuth2AccessToken(AbstractAccessToken):
class Meta:
app_label = 'main'
verbose_name = _('access token')
user = models.ForeignKey(
settings.AUTH_USER_MODEL,
on_delete=models.CASCADE,
blank=True,
null=True,
related_name="%(app_label)s_%(class)s",
help_text=_('The user representing the token owner')
)
description = models.CharField(
max_length=200,
default='',
blank=True,
)
last_used = models.DateTimeField(
null=True,
default=None,
editable=False,
)
scope = models.TextField(
blank=True,
help_text=_('Allowed scopes, further restricts user\'s permissions. Must be a simple space-separated string with allowed scopes [\'read\', \'write\'].')
)
def is_valid(self, scopes=None):
valid = super(OAuth2AccessToken, self).is_valid(scopes)
if valid:
self.last_used = now()
self.save(update_fields=['last_used'])
return valid

View File

@@ -1,20 +1,16 @@
# Copyright (c) 2015 Ansible, Inc.
# All Rights Reserved.
# Python
import datetime
import hashlib
import hmac
import uuid
# Django
from django.conf import settings
from django.db import models, connection
from django.db import models
from django.db.models import Q
from django.contrib.auth.models import User
from django.contrib.sessions.models import Session
from django.utils.timezone import now as tz_now
from django.utils.translation import ugettext_lazy as _
import six
# AWX
from awx.api.versioning import reverse
@@ -24,12 +20,13 @@ from awx.main.models.rbac import (
ROLE_SINGLETON_SYSTEM_ADMINISTRATOR,
ROLE_SINGLETON_SYSTEM_AUDITOR,
)
from awx.main.models.mixins import ResourceMixin, CustomVirtualEnvMixin
from awx.main.models.unified_jobs import UnifiedJob
from awx.main.models.mixins import ResourceMixin, CustomVirtualEnvMixin, RelatedJobsMixin
__all__ = ['Organization', 'Team', 'Profile', 'AuthToken']
__all__ = ['Organization', 'Team', 'Profile', 'UserSessionMembership']
class Organization(CommonModel, NotificationFieldsModel, ResourceMixin, CustomVirtualEnvMixin):
class Organization(CommonModel, NotificationFieldsModel, ResourceMixin, CustomVirtualEnvMixin, RelatedJobsMixin):
'''
An organization is the basic unit of multi-tenancy divisions
'''
@@ -69,7 +66,7 @@ class Organization(CommonModel, NotificationFieldsModel, ResourceMixin, CustomVi
member_role = ImplicitRoleField(
parent_role=['admin_role', 'execute_role', 'project_admin_role',
'inventory_admin_role', 'workflow_admin_role',
'notification_admin_role']
'notification_admin_role', 'credential_admin_role']
)
read_role = ImplicitRoleField(
parent_role=['member_role', 'auditor_role'],
@@ -79,8 +76,16 @@ class Organization(CommonModel, NotificationFieldsModel, ResourceMixin, CustomVi
def get_absolute_url(self, request=None):
return reverse('api:organization_detail', kwargs={'pk': self.pk}, request=request)
def __unicode__(self):
return self.name
'''
RelatedJobsMixin
'''
def _get_related_jobs(self):
project_ids = self.projects.all().values_list('id')
return UnifiedJob.objects.non_polymorphic().filter(
Q(Job___project__in=project_ids) |
Q(ProjectUpdate___project__in=project_ids) |
Q(InventoryUpdate___inventory_source__inventory__organization=self)
)
class Team(CommonModelNameNotUnique, ResourceMixin):
@@ -134,139 +139,40 @@ class Profile(CreatedModifiedModel):
)
"""
Since expiration and session expiration is event driven a token could be
invalidated for both reasons. Further, we only support a single reason for a
session token being invalid. For this case, mark the token as expired.
Note: Again, because the value of reason is event based. The reason may not be
set (i.e. may equal '') even though a session is expired or a limit is reached.
"""
class AuthToken(BaseModel):
class UserSessionMembership(BaseModel):
'''
Custom authentication tokens per user with expiration and request-specific
data.
A lookup table for session membership given user.
'''
REASON_CHOICES = [
('', _('Token not invalidated')),
('timeout_reached', _('Token is expired')),
('limit_reached', _('The maximum number of allowed sessions for this user has been exceeded.')),
# invalid_token is not a used data-base value, but is returned by the
# api when a token is not found
('invalid_token', _('Invalid token')),
]
class Meta:
app_label = 'main'
key = models.CharField(max_length=40, primary_key=True)
user = prevent_search(models.ForeignKey('auth.User',
related_name='auth_tokens', on_delete=models.CASCADE))
created = models.DateTimeField(auto_now_add=True)
modified = models.DateTimeField(auto_now=True)
expires = models.DateTimeField(default=tz_now)
request_hash = prevent_search(models.CharField(max_length=40, blank=True,
default=''))
reason = models.CharField(
max_length=1024,
blank=True,
default='',
help_text=_('Reason the auth token was invalidated.')
user = models.ForeignKey(
'auth.User', related_name='+', blank=False, null=False, on_delete=models.CASCADE
)
session = models.OneToOneField(
Session, related_name='+', blank=False, null=False, on_delete=models.CASCADE
)
created = models.DateTimeField(default=None, editable=False)
@staticmethod
def reason_long(reason):
for x in AuthToken.REASON_CHOICES:
if x[0] == reason:
return six.text_type(x[1])
return None
@classmethod
def get_request_hash(cls, request):
h = hashlib.sha1()
h.update(settings.SECRET_KEY)
for header in settings.REMOTE_HOST_HEADERS:
value = request.META.get(header, '').split(',')[0].strip()
if value:
h.update(value)
break
h.update(request.META.get('HTTP_USER_AGENT', ''))
return h.hexdigest()
def save(self, *args, **kwargs):
if not self.pk:
self.refresh(save=False)
if not self.key:
self.key = self.generate_key()
return super(AuthToken, self).save(*args, **kwargs)
def refresh(self, now=None, save=True):
if not now:
now = tz_now()
if not self.pk or not self.is_expired(now=now):
self.expires = now + datetime.timedelta(seconds=settings.AUTH_TOKEN_EXPIRATION)
if save:
connection.on_commit(lambda: self.save(update_fields=['expires']))
def invalidate(self, reason='timeout_reached', save=True):
if not AuthToken.reason_long(reason):
raise ValueError(_('Invalid reason specified'))
self.reason = reason
if save:
self.save()
return reason
@staticmethod
def get_tokens_over_limit(user, now=None):
def get_memberships_over_limit(user, now=None):
if settings.SESSIONS_PER_USER == -1:
return []
if now is None:
now = tz_now()
invalid_tokens = AuthToken.objects.none()
if settings.AUTH_TOKEN_PER_USER != -1:
invalid_tokens = AuthToken.objects.filter(
user=user,
expires__gt=now,
reason='',
).order_by('-created')[settings.AUTH_TOKEN_PER_USER:]
return invalid_tokens
query_set = UserSessionMembership.objects\
.select_related('session')\
.filter(user=user)\
.order_by('-created')
non_expire_memberships = [x for x in query_set if x.session.expire_date > now]
return non_expire_memberships[settings.SESSIONS_PER_USER:]
def generate_key(self):
unique = uuid.uuid4()
return hmac.new(unique.bytes, digestmod=hashlib.sha1).hexdigest()
def is_expired(self, now=None):
if not now:
now = tz_now()
return bool(self.expires < now)
@property
def invalidated(self):
return bool(self.reason != '')
"""
Token is valid if it's in the set of unexpired tokens.
The unexpired token set is:
* tokens not expired
* limited to number of tokens per-user
* sorted by created on date
"""
def in_valid_tokens(self, now=None):
if not now:
now = tz_now()
valid_n_tokens_qs = self.user.auth_tokens.filter(
expires__gt=now,
reason='',
).order_by('-created')
if settings.AUTH_TOKEN_PER_USER != -1:
valid_n_tokens_qs = valid_n_tokens_qs[0:settings.AUTH_TOKEN_PER_USER]
valid_n_tokens = valid_n_tokens_qs.values_list('key', flat=True)
return bool(self.key in valid_n_tokens)
def __unicode__(self):
return self.key
@staticmethod
def clear_session_for_user(user):
query_set = UserSessionMembership.objects.select_related('session').filter(user=user)
sessions_to_delete = [obj.session.pk for obj in query_set]
Session.objects.filter(pk__in=sessions_to_delete).delete()
# Add get_absolute_url method to User model if not present.

View File

@@ -25,8 +25,16 @@ from awx.main.models.notifications import (
NotificationTemplate,
JobNotificationMixin,
)
from awx.main.models.unified_jobs import * # noqa
from awx.main.models.mixins import ResourceMixin, TaskManagerProjectUpdateMixin, CustomVirtualEnvMixin
from awx.main.models.unified_jobs import (
UnifiedJob,
UnifiedJobTemplate,
)
from awx.main.models.mixins import (
ResourceMixin,
TaskManagerProjectUpdateMixin,
CustomVirtualEnvMixin,
RelatedJobsMixin
)
from awx.main.utils import update_scm_url
from awx.main.utils.ansible import skip_directory, could_be_inventory, could_be_playbook
from awx.main.fields import ImplicitRoleField
@@ -225,7 +233,7 @@ class ProjectOptions(models.Model):
return proj_path + '.lock'
class Project(UnifiedJobTemplate, ProjectOptions, ResourceMixin, CustomVirtualEnvMixin):
class Project(UnifiedJobTemplate, ProjectOptions, ResourceMixin, CustomVirtualEnvMixin, RelatedJobsMixin):
'''
A project represents a playbook git repo that can access a set of inventories
'''
@@ -442,6 +450,15 @@ class Project(UnifiedJobTemplate, ProjectOptions, ResourceMixin, CustomVirtualEn
def get_absolute_url(self, request=None):
return reverse('api:project_detail', kwargs={'pk': self.pk}, request=request)
'''
RelatedJobsMixin
'''
def _get_related_jobs(self):
return UnifiedJob.objects.non_polymorphic().filter(
models.Q(Job___project=self) |
models.Q(ProjectUpdate___project=self)
)
class ProjectUpdate(UnifiedJob, ProjectOptions, JobNotificationMixin, TaskManagerProjectUpdateMixin):
'''
@@ -533,7 +550,8 @@ class ProjectUpdate(UnifiedJob, ProjectOptions, JobNotificationMixin, TaskManage
res = super(ProjectUpdate, self).cancel(job_explanation=job_explanation, is_chain=is_chain)
if res and self.launch_type != 'sync':
for inv_src in self.scm_inventory_updates.filter(status='running'):
inv_src.cancel(job_explanation='Source project update `{}` was canceled.'.format(self.name))
inv_src.cancel(job_explanation=six.text_type(
'Source project update `{}` was canceled.').format(self.name))
return res
'''
@@ -556,3 +574,5 @@ class ProjectUpdate(UnifiedJob, ProjectOptions, JobNotificationMixin, TaskManage
if not selected_groups:
return self.global_instance_groups
return selected_groups

View File

@@ -153,6 +153,12 @@ class Role(models.Model):
object_id = models.PositiveIntegerField(null=True, default=None)
content_object = GenericForeignKey('content_type', 'object_id')
def __unicode__(self):
if 'role_field' in self.__dict__:
return u'%s-%s' % (self.name, self.pk)
else:
return u'%s-%s' % (self._meta.verbose_name, self.pk)
def save(self, *args, **kwargs):
super(Role, self).save(*args, **kwargs)
self.rebuild_role_ancestor_list([self.id], [])
@@ -478,13 +484,25 @@ def role_summary_fields_generator(content_object, role_field):
global role_names
summary = {}
description = role_descriptions[role_field]
model_name = None
content_type = ContentType.objects.get_for_model(content_object)
if '%s' in description and content_type:
if content_type:
model = content_object.__class__
model_name = re.sub(r'([a-z])([A-Z])', r'\1 \2', model.__name__).lower()
description = description % model_name
summary['description'] = description
value = description
if type(description) == dict:
value = None
if model_name:
value = description.get(model_name)
if value is None:
value = description.get('default')
if '%s' in value and model_name:
value = value % model_name
summary['description'] = value
summary['name'] = role_names[role_field]
summary['id'] = getattr(content_object, '{}_id'.format(role_field))
return summary

View File

@@ -1,11 +1,10 @@
# Copyright (c) 2015 Ansible, Inc.
# All Rights Reserved.
import re
import logging
import datetime
import dateutil.rrule
from dateutil.tz import gettz, datetime_exists
from dateutil.tz import datetime_exists
# Django
from django.db import models
@@ -57,10 +56,6 @@ class ScheduleManager(ScheduleFilterMethods, models.Manager):
class Schedule(CommonModel, LaunchTimeConfig):
TZID_REGEX = re.compile(
"^(DTSTART;TZID=(?P<tzid>[^:]+)(?P<stamp>\:[0-9]+T[0-9]+))(?P<rrule> .*)$"
)
class Meta:
app_label = 'main'
ordering = ['-next_run']
@@ -102,53 +97,16 @@ class Schedule(CommonModel, LaunchTimeConfig):
@classmethod
def rrulestr(cls, rrule, **kwargs):
"""
Apply our own custom rrule parsing logic to support TZID=
python-dateutil doesn't _natively_ support `DTSTART;TZID=`; this
function parses out the TZID= component and uses it to produce the
`tzinfos` keyword argument to `dateutil.rrule.rrulestr()`. In this
way, we translate:
DTSTART;TZID=America/New_York:20180601T120000 RRULE:FREQ=DAILY;INTERVAL=1
...into...
DTSTART:20180601T120000TZID RRULE:FREQ=DAILY;INTERVAL=1
...and we pass a hint about the local timezone to dateutil's parser:
`dateutil.rrule.rrulestr(rrule, {
'tzinfos': {
'TZID': dateutil.tz.gettz('America/New_York')
}
})`
it's likely that we can remove the custom code that performs this
parsing if TZID= gains support in upstream dateutil:
https://github.com/dateutil/dateutil/pull/619
Apply our own custom rrule parsing requirements
"""
kwargs['forceset'] = True
kwargs['tzinfos'] = {x: dateutil.tz.tzutc() for x in dateutil.parser.parserinfo().UTCZONE}
match = cls.TZID_REGEX.match(rrule)
if match is not None:
rrule = cls.TZID_REGEX.sub("DTSTART\g<stamp>TZI\g<rrule>", rrule)
timezone = gettz(match.group('tzid'))
kwargs['tzinfos']['TZI'] = timezone
x = dateutil.rrule.rrulestr(rrule, **kwargs)
for r in x._rrule:
if r._dtstart and r._until:
if all((
r._dtstart.tzinfo != dateutil.tz.tzlocal(),
r._until.tzinfo != dateutil.tz.tzutc(),
)):
# According to RFC5545 Section 3.3.10:
# https://tools.ietf.org/html/rfc5545#section-3.3.10
#
# > If the "DTSTART" property is specified as a date with UTC
# > time or a date with local time and time zone reference,
# > then the UNTIL rule part MUST be specified as a date with
# > UTC time.
raise ValueError('RRULE UNTIL values must be specified in UTC')
if r._dtstart and r._dtstart.tzinfo is None:
raise ValueError(
'A valid TZID must be provided (e.g., America/New_York)'
)
if 'MINUTELY' in rrule or 'HOURLY' in rrule:
try:

View File

@@ -28,7 +28,7 @@ from rest_framework.exceptions import ParseError
from polymorphic.models import PolymorphicModel
# Django-Celery
from django_celery_results.models import TaskResult
from djcelery.models import TaskMeta
# AWX
from awx.main.models.base import * # noqa
@@ -38,6 +38,8 @@ from awx.main.utils import (
copy_model_by_class, copy_m2m_relationships,
get_type_for_model, parse_yaml_or_json
)
from awx.main.utils import polymorphic
from awx.main.constants import ACTIVE_STATES, CAN_CANCEL
from awx.main.redact import UriCleaner, REPLACE_STR
from awx.main.consumers import emit_channel_notification
from awx.main.fields import JSONField, AskForField
@@ -46,8 +48,7 @@ __all__ = ['UnifiedJobTemplate', 'UnifiedJob', 'StdoutMaxBytesExceeded']
logger = logging.getLogger('awx.main.models.unified_jobs')
CAN_CANCEL = ('new', 'pending', 'waiting', 'running')
ACTIVE_STATES = CAN_CANCEL
# NOTE: ACTIVE_STATES moved to constants because it is used by parent modules
class UnifiedJobTemplate(PolymorphicModel, CommonModelNameNotUnique, NotificationFieldsModel):
@@ -89,9 +90,6 @@ class UnifiedJobTemplate(PolymorphicModel, CommonModelNameNotUnique, Notificatio
ALL_STATUS_CHOICES = OrderedDict(PROJECT_STATUS_CHOICES + INVENTORY_SOURCE_STATUS_CHOICES + JOB_TEMPLATE_STATUS_CHOICES + DEPRECATED_STATUS_CHOICES).items()
# NOTE: Working around a django-polymorphic issue: https://github.com/django-polymorphic/django-polymorphic/issues/229
base_manager_name = 'base_objects'
class Meta:
app_label = 'main'
# unique_together here is intentionally commented out. Please make sure sub-classes of this model
@@ -180,12 +178,6 @@ class UnifiedJobTemplate(PolymorphicModel, CommonModelNameNotUnique, Notificatio
else:
return super(UnifiedJobTemplate, self).unique_error_message(model_class, unique_check)
@classmethod
def invalid_user_capabilities_prefetch_models(cls):
if cls != UnifiedJobTemplate:
return []
return ['project', 'inventorysource', 'systemjobtemplate']
@classmethod
def _submodels_with_roles(cls):
ujt_classes = [c for c in cls.__subclasses__()
@@ -271,14 +263,7 @@ class UnifiedJobTemplate(PolymorphicModel, CommonModelNameNotUnique, Notificatio
if field not in update_fields:
update_fields.append(field)
# Do the actual save.
try:
super(UnifiedJobTemplate, self).save(*args, **kwargs)
except ValueError:
# A fix for https://trello.com/c/S4rU1F21
# Does not resolve the root cause. Tis merely a bandaid.
if 'scm_delete_on_next_update' in update_fields:
update_fields.remove('scm_delete_on_next_update')
super(UnifiedJobTemplate, self).save(*args, **kwargs)
super(UnifiedJobTemplate, self).save(*args, **kwargs)
def _get_current_status(self):
@@ -542,9 +527,6 @@ class UnifiedJob(PolymorphicModel, PasswordFieldsModel, CommonModelNameNotUnique
PASSWORD_FIELDS = ('start_args',)
# NOTE: Working around a django-polymorphic issue: https://github.com/django-polymorphic/django-polymorphic/issues/229
base_manager_name = 'base_objects'
class Meta:
app_label = 'main'
@@ -553,6 +535,10 @@ class UnifiedJob(PolymorphicModel, PasswordFieldsModel, CommonModelNameNotUnique
default=None,
editable=False,
)
emitted_events = models.PositiveIntegerField(
default=0,
editable=False,
)
unified_job_template = models.ForeignKey(
'UnifiedJobTemplate',
null=True, # Some jobs can be run without a template.
@@ -671,7 +657,7 @@ class UnifiedJob(PolymorphicModel, PasswordFieldsModel, CommonModelNameNotUnique
blank=True,
null=True,
default=None,
on_delete=models.SET_NULL,
on_delete=polymorphic.SET_NULL,
help_text=_('The Rampart/Instance group the job was run under'),
)
credentials = models.ManyToManyField(
@@ -729,7 +715,10 @@ class UnifiedJob(PolymorphicModel, PasswordFieldsModel, CommonModelNameNotUnique
def _get_parent_instance(self):
return getattr(self, self._get_parent_field_name(), None)
def _update_parent_instance_no_save(self, parent_instance, update_fields=[]):
def _update_parent_instance_no_save(self, parent_instance, update_fields=None):
if update_fields is None:
update_fields = []
def parent_instance_set(key, val):
setattr(parent_instance, key, val)
if key not in update_fields:
@@ -875,8 +864,11 @@ class UnifiedJob(PolymorphicModel, PasswordFieldsModel, CommonModelNameNotUnique
JobLaunchConfig = self._meta.get_field('launch_config').related_model
config = JobLaunchConfig(job=self)
valid_fields = self.unified_job_template.get_ask_mapping().keys()
# Special cases allowed for workflows
if hasattr(self, 'extra_vars'):
valid_fields.extend(['survey_passwords', 'extra_vars'])
else:
kwargs.pop('survey_passwords', None)
for field_name, value in kwargs.items():
if field_name not in valid_fields:
raise Exception('Unrecognized launch config field {}.'.format(field_name))
@@ -911,6 +903,33 @@ class UnifiedJob(PolymorphicModel, PasswordFieldsModel, CommonModelNameNotUnique
related.result_stdout_text = value
related.save()
@property
def event_parent_key(self):
tablename = self._meta.db_table
return {
'main_job': 'job_id',
'main_adhoccommand': 'ad_hoc_command_id',
'main_projectupdate': 'project_update_id',
'main_inventoryupdate': 'inventory_update_id',
'main_systemjob': 'system_job_id',
}[tablename]
def get_event_queryset(self):
return self.event_class.objects.filter(**{self.event_parent_key: self.id})
@property
def event_processing_finished(self):
'''
Returns True / False, whether all events from job have been saved
'''
if self.status in ACTIVE_STATES:
return False # tally of events is only available at end of run
try:
event_qs = self.get_event_queryset()
except NotImplementedError:
return True # Model without events, such as WFJT
return self.emitted_events == event_qs.count()
def result_stdout_raw_handle(self, enforce_max_bytes=True):
"""
This method returns a file-like object ready to be read which contains
@@ -966,20 +985,12 @@ class UnifiedJob(PolymorphicModel, PasswordFieldsModel, CommonModelNameNotUnique
# (`stdout`) directly to a file
with connection.cursor() as cursor:
tablename = self._meta.db_table
related_name = {
'main_job': 'job_id',
'main_adhoccommand': 'ad_hoc_command_id',
'main_projectupdate': 'project_update_id',
'main_inventoryupdate': 'inventory_update_id',
'main_systemjob': 'system_job_id',
}[tablename]
if enforce_max_bytes:
# detect the length of all stdout for this UnifiedJob, and
# if it exceeds settings.STDOUT_MAX_BYTES_DISPLAY bytes,
# don't bother actually fetching the data
total = self.event_class.objects.filter(**{related_name: self.id}).aggregate(
total = self.get_event_queryset().aggregate(
total=models.Sum(models.Func(models.F('stdout'), function='LENGTH'))
)['total']
if total > max_supported:
@@ -987,8 +998,8 @@ class UnifiedJob(PolymorphicModel, PasswordFieldsModel, CommonModelNameNotUnique
cursor.copy_expert(
"copy (select stdout from {} where {}={} order by start_line) to stdout".format(
tablename + 'event',
related_name,
self._meta.db_table + 'event',
self.event_parent_key,
self.id
),
fd
@@ -1093,8 +1104,8 @@ class UnifiedJob(PolymorphicModel, PasswordFieldsModel, CommonModelNameNotUnique
def celery_task(self):
try:
if self.celery_task_id:
return TaskResult.objects.get(task_id=self.celery_task_id)
except TaskResult.DoesNotExist:
return TaskMeta.objects.get(task_id=self.celery_task_id)
except TaskMeta.DoesNotExist:
pass
def get_passwords_needed_to_start(self):
@@ -1248,10 +1259,6 @@ class UnifiedJob(PolymorphicModel, PasswordFieldsModel, CommonModelNameNotUnique
if not all(opts.values()):
return False
# Sanity check: If we are running unit tests, then run synchronously.
if getattr(settings, 'CELERY_UNIT_TEST', False):
return self.start(None, None, **kwargs)
# Save the pending status, and inform the SocketIO listener.
self.update_fields(start_args=json.dumps(kwargs), status='pending')
self.websocket_emit_status("pending")
@@ -1335,7 +1342,7 @@ class UnifiedJob(PolymorphicModel, PasswordFieldsModel, CommonModelNameNotUnique
cancel_fields.append('job_explanation')
self.save(update_fields=cancel_fields)
self.websocket_emit_status("canceled")
if settings.CELERY_BROKER_URL.startswith('amqp://'):
if settings.BROKER_URL.startswith('amqp://'):
self._force_cancel()
return self.cancel_flag
@@ -1370,6 +1377,9 @@ class UnifiedJob(PolymorphicModel, PasswordFieldsModel, CommonModelNameNotUnique
for name in ('awx', 'tower'):
r['{}_user_id'.format(name)] = self.created_by.pk
r['{}_user_name'.format(name)] = self.created_by.username
r['{}_user_email'.format(name)] = self.created_by.email
r['{}_user_first_name'.format(name)] = self.created_by.first_name
r['{}_user_last_name'.format(name)] = self.created_by.last_name
else:
wj = self.get_workflow_job()
if wj:

View File

@@ -24,7 +24,12 @@ from awx.main.models.rbac import (
ROLE_SINGLETON_SYSTEM_AUDITOR
)
from awx.main.fields import ImplicitRoleField
from awx.main.models.mixins import ResourceMixin, SurveyJobTemplateMixin, SurveyJobMixin
from awx.main.models.mixins import (
ResourceMixin,
SurveyJobTemplateMixin,
SurveyJobMixin,
RelatedJobsMixin,
)
from awx.main.models.jobs import LaunchTimeConfig
from awx.main.models.credential import Credential
from awx.main.redact import REPLACE_STR
@@ -287,7 +292,7 @@ class WorkflowJobOptions(BaseModel):
return new_workflow_job
class WorkflowJobTemplate(UnifiedJobTemplate, WorkflowJobOptions, SurveyJobTemplateMixin, ResourceMixin):
class WorkflowJobTemplate(UnifiedJobTemplate, WorkflowJobOptions, SurveyJobTemplateMixin, ResourceMixin, RelatedJobsMixin):
SOFT_UNIQUE_TOGETHER = [('polymorphic_ctype', 'name', 'organization')]
FIELDS_TO_PRESERVE_AT_COPY = [
@@ -384,10 +389,7 @@ class WorkflowJobTemplate(UnifiedJobTemplate, WorkflowJobOptions, SurveyJobTempl
return prompted_fields, rejected_fields, errors_dict
def can_start_without_user_input(self):
return not bool(
self.variables_needed_to_start or
self.node_templates_missing() or
self.node_prompts_rejected())
return not bool(self.variables_needed_to_start)
def node_templates_missing(self):
return [node.pk for node in self.workflow_job_template_nodes.filter(
@@ -405,6 +407,12 @@ class WorkflowJobTemplate(UnifiedJobTemplate, WorkflowJobOptions, SurveyJobTempl
node_list.append(node.pk)
return node_list
'''
RelatedJobsMixin
'''
def _get_related_jobs(self):
return WorkflowJob.objects.filter(workflow_job_template=self)
class WorkflowJob(UnifiedJob, WorkflowJobOptions, SurveyJobMixin, JobNotificationMixin):
class Meta:
@@ -466,7 +474,7 @@ class WorkflowJob(UnifiedJob, WorkflowJobOptions, SurveyJobMixin, JobNotificatio
@property
def preferred_instance_groups(self):
return self.global_instance_groups
return []
'''
A WorkflowJob is a virtual job. It doesn't result in a celery task.

View File

@@ -0,0 +1,51 @@
# Copyright (c) 2016 Ansible, Inc.
# All Rights Reserved.
import logging
import requests
import json
from django.utils.encoding import smart_text
from django.utils.translation import ugettext_lazy as _
from awx.main.notifications.base import AWXBaseEmailBackend
logger = logging.getLogger('awx.main.notifications.rocketchat_backend')
class RocketChatBackend(AWXBaseEmailBackend):
init_parameters = {"rocketchat_url": {"label": "Target URL", "type": "string"},
"rocketchat_no_verify_ssl": {"label": "Verify SSL", "type": "bool"}}
recipient_parameter = "rocketchat_url"
sender_parameter = None
def __init__(self, rocketchat_no_verify_ssl=False, rocketchat_username=None, rocketchat_icon_url=None, fail_silently=False, **kwargs):
super(RocketChatBackend, self).__init__(fail_silently=fail_silently)
self.rocketchat_no_verify_ssl = rocketchat_no_verify_ssl
self.rocketchat_username = rocketchat_username
self.rocketchat_icon_url = rocketchat_icon_url
def format_body(self, body):
return body
def send_messages(self, messages):
sent_messages = 0
for m in messages:
payload = {"text": m.subject}
for opt, optval in {'rocketchat_icon_url': 'icon_url',
'rocketchat_username': 'username'}.iteritems():
optvalue = getattr(self, opt)
if optvalue is not None:
payload[optval] = optvalue.strip()
r = requests.post("{}".format(m.recipients()[0]),
data=json.dumps(payload), verify=(not self.rocketchat_no_verify_ssl))
if r.status_code >= 400:
logger.error(smart_text(
_("Error sending notification rocket.chat: {}").format(r.text)))
if not self.fail_silently:
raise Exception(smart_text(
_("Error sending notification rocket.chat: {}").format(r.text)))
sent_messages += 1
return sent_messages

View File

@@ -20,9 +20,12 @@ class SlackBackend(AWXBaseEmailBackend):
recipient_parameter = "channels"
sender_parameter = None
def __init__(self, token, fail_silently=False, **kwargs):
def __init__(self, token, hex_color="", fail_silently=False, **kwargs):
super(SlackBackend, self).__init__(fail_silently=fail_silently)
self.token = token
self.color = None
if hex_color.startswith("#") and (len(hex_color) == 4 or len(hex_color) == 7):
self.color = hex_color
self.connection = None
def open(self):
@@ -51,6 +54,37 @@ class SlackBackend(AWXBaseEmailBackend):
self.connection = None
def send_messages(self, messages):
if self.color:
return self._send_attachments(messages)
else:
return self._send_rtm_messages(messages)
def _send_attachments(self, messages):
connection = SlackClient(self.token)
sent_messages = 0
for m in messages:
try:
for r in m.recipients():
if r.startswith('#'):
r = r[1:]
ret = connection.api_call("chat.postMessage",
channel=r,
attachments=[{
"color": self.color,
"text": m.subject
}])
logger.debug(ret)
if ret['ok']:
sent_messages += 1
else:
raise RuntimeError("Slack Notification unable to send {}: {}".format(r, m.subject))
except Exception as e:
logger.error(smart_text(_("Exception sending messages: {}").format(e)))
if not self.fail_silently:
raise
return sent_messages
def _send_rtm_messages(self, messages):
if self.connection is None:
self.open()
sent_messages = 0

View File

@@ -19,7 +19,7 @@ __all__ = ['CallbackQueueDispatcher']
class CallbackQueueDispatcher(object):
def __init__(self):
self.callback_connection = getattr(settings, 'CELERY_BROKER_URL', None)
self.callback_connection = getattr(settings, 'BROKER_URL', None)
self.connection_queue = getattr(settings, 'CALLBACK_QUEUE', '')
self.connection = None
self.exchange = None

View File

@@ -6,8 +6,7 @@ REPLACE_STR = '$encrypted$'
class UriCleaner(object):
REPLACE_STR = REPLACE_STR
# https://regex101.com/r/sV2dO2/2
SENSITIVE_URI_PATTERN = re.compile(ur'(?i)\b((?:https?://|www\d{0,3}[.]|[a-z0-9.\-]+[.][a-z]{2,4}/)(?:[^\s()<>]+|\(([^\s()<>]+|(\([^\s()<>]+\)))*\))+(?:\(([^\s()<>]+|(\([^\s()<>]+\)))*\)|[^\s`!()\[\]{};:\'".,<>?\xab\xbb\u201c\u201d\u2018\u2019]))', re.MULTILINE) # NOQA
SENSITIVE_URI_PATTERN = re.compile(ur'(\w+:(\/?\/?)[^\s]+)', re.MULTILINE) # NOQA
@staticmethod
def remove_sensitive(cleartext):
@@ -17,38 +16,46 @@ class UriCleaner(object):
match = UriCleaner.SENSITIVE_URI_PATTERN.search(redactedtext, text_index)
if not match:
break
o = urlparse.urlsplit(match.group(1))
if not o.username and not o.password:
if o.netloc and ":" in o.netloc:
# Handle the special case url http://username:password that can appear in SCM url
# on account of a bug? in ansible redaction
(username, password) = o.netloc.split(':')
try:
uri_str = match.group(1)
# May raise a ValueError if invalid URI for one reason or another
o = urlparse.urlsplit(uri_str)
if not o.username and not o.password:
if o.netloc and ":" in o.netloc:
# Handle the special case url http://username:password that can appear in SCM url
# on account of a bug? in ansible redaction
(username, password) = o.netloc.split(':')
else:
text_index += len(match.group(1))
continue
else:
text_index += len(match.group(1))
continue
else:
username = o.username
password = o.password
username = o.username
password = o.password
# Given a python MatchObject, with respect to redactedtext, find and
# replace the first occurance of username and the first and second
# occurance of password
# Given a python MatchObject, with respect to redactedtext, find and
# replace the first occurance of username and the first and second
# occurance of password
uri_str = redactedtext[match.start():match.end()]
if username:
uri_str = uri_str.replace(username, UriCleaner.REPLACE_STR, 1)
# 2, just in case the password is $encrypted$
if password:
uri_str = uri_str.replace(password, UriCleaner.REPLACE_STR, 2)
uri_str = redactedtext[match.start():match.end()]
if username:
uri_str = uri_str.replace(username, UriCleaner.REPLACE_STR, 1)
# 2, just in case the password is $encrypted$
if password:
uri_str = uri_str.replace(password, UriCleaner.REPLACE_STR, 2)
t = redactedtext[:match.start()] + uri_str
text_index = len(t)
if (match.end() < len(redactedtext)):
t += redactedtext[match.end():]
t = redactedtext[:match.start()] + uri_str
text_index = len(t)
if (match.end() < len(redactedtext)):
t += redactedtext[match.end():]
redactedtext = t
if text_index >= len(redactedtext):
text_index = len(redactedtext) - 1
redactedtext = t
if text_index >= len(redactedtext):
text_index = len(redactedtext) - 1
except ValueError:
# Invalid URI, redact the whole URI to be safe
redactedtext = redactedtext[:match.start()] + UriCleaner.REPLACE_STR + redactedtext[match.end():]
text_index = match.start() + len(UriCleaner.REPLACE_STR)
return redactedtext

View File

@@ -1,4 +1,5 @@
from channels.routing import route
from awx.network_ui.routing import channel_routing as network_ui_routing
channel_routing = [
@@ -6,3 +7,6 @@ channel_routing = [
route("websocket.disconnect", "awx.main.consumers.ws_disconnect", path=r'^/websocket/$'),
route("websocket.receive", "awx.main.consumers.ws_receive", path=r'^/websocket/$'),
]
channel_routing += network_ui_routing

View File

@@ -6,6 +6,7 @@ from datetime import datetime, timedelta
import logging
import uuid
import json
import six
from sets import Set
# Django
@@ -37,7 +38,6 @@ from awx.main.utils import get_type_for_model
from awx.main.signals import disable_activity_stream
from awx.main.scheduler.dependency_graph import DependencyGraph
from awx.main import tasks as awx_tasks
from awx.main.utils import decrypt_field
# Celery
@@ -133,7 +133,7 @@ class TaskManager():
def get_active_tasks(self):
if not hasattr(settings, 'IGNORE_CELERY_INSPECTOR'):
app = Celery('awx')
app.config_from_object('django.conf:settings', namespace='CELERY')
app.config_from_object('django.conf:settings')
inspector = Inspect(app=app)
active_task_queues = inspector.active()
else:
@@ -153,8 +153,7 @@ class TaskManager():
queue_name = queue_name[1 if len(queue_name) > 1 else 0]
queues[queue_name] = active_tasks
else:
if not hasattr(settings, 'CELERY_UNIT_TEST'):
return (None, None)
return (None, None)
return (active_task_queues, queues)
@@ -260,7 +259,8 @@ class TaskManager():
else:
if type(task) is WorkflowJob:
task.status = 'running'
if not task.supports_isolation() and rampart_group.controller_id:
logger.info('Transitioning %s to running status.', task.log_format)
elif not task.supports_isolation() and rampart_group.controller_id:
# non-Ansible jobs on isolated instances run on controller
task.instance_group = rampart_group.controller
logger.info('Submitting isolated %s to queue %s via %s.',
@@ -272,17 +272,22 @@ class TaskManager():
task.celery_task_id = str(uuid.uuid4())
task.save()
self.consume_capacity(task, rampart_group.name)
if rampart_group is not None:
self.consume_capacity(task, rampart_group.name)
def post_commit():
task.websocket_emit_status(task.status)
if task.status != 'failed':
task.start_celery_task(opts, error_callback=error_handler, success_callback=success_handler, queue=rampart_group.name)
if rampart_group is not None:
actual_queue=rampart_group.name
else:
actual_queue=settings.CELERY_DEFAULT_QUEUE
task.start_celery_task(opts, error_callback=error_handler, success_callback=success_handler, queue=actual_queue)
connection.on_commit(post_commit)
def process_running_tasks(self, running_tasks):
map(lambda task: self.graph[task.instance_group.name]['graph'].add_job(task), running_tasks)
map(lambda task: self.graph[task.instance_group.name]['graph'].add_job(task) if task.instance_group else None, running_tasks)
def create_project_update(self, task):
project_task = Project.objects.get(id=task.project_id).create_project_update(
@@ -422,50 +427,53 @@ class TaskManager():
def process_dependencies(self, dependent_task, dependency_tasks):
for task in dependency_tasks:
if self.is_job_blocked(task):
logger.debug("Dependent %s is blocked from running", task.log_format)
logger.debug(six.text_type("Dependent {} is blocked from running").format(task.log_format))
continue
preferred_instance_groups = task.preferred_instance_groups
found_acceptable_queue = False
for rampart_group in preferred_instance_groups:
if self.get_remaining_capacity(rampart_group.name) <= 0:
logger.debug("Skipping group %s capacity <= 0", rampart_group.name)
logger.debug(six.text_type("Skipping group {} capacity <= 0").format(rampart_group.name))
continue
if not self.would_exceed_capacity(task, rampart_group.name):
logger.debug("Starting dependent %s in group %s", task.log_format, rampart_group.name)
logger.debug(six.text_type("Starting dependent {} in group {}").format(task.log_format, rampart_group.name))
self.graph[rampart_group.name]['graph'].add_job(task)
tasks_to_fail = filter(lambda t: t != task, dependency_tasks)
tasks_to_fail += [dependent_task]
self.start_task(task, rampart_group, tasks_to_fail)
found_acceptable_queue = True
if not found_acceptable_queue:
logger.debug("Dependent %s couldn't be scheduled on graph, waiting for next cycle", task.log_format)
logger.debug(six.text_type("Dependent {} couldn't be scheduled on graph, waiting for next cycle").format(task.log_format))
def process_pending_tasks(self, pending_tasks):
for task in pending_tasks:
self.process_dependencies(task, self.generate_dependencies(task))
if self.is_job_blocked(task):
logger.debug("%s is blocked from running", task.log_format)
logger.debug(six.text_type("{} is blocked from running").format(task.log_format))
continue
preferred_instance_groups = task.preferred_instance_groups
found_acceptable_queue = False
if isinstance(task, WorkflowJob):
self.start_task(task, None, task.get_jobs_fail_chain())
continue
for rampart_group in preferred_instance_groups:
remaining_capacity = self.get_remaining_capacity(rampart_group.name)
if remaining_capacity <= 0:
logger.debug("Skipping group %s, remaining_capacity %s <= 0",
rampart_group.name, remaining_capacity)
logger.debug(six.text_type("Skipping group {}, remaining_capacity {} <= 0").format(
rampart_group.name, remaining_capacity))
continue
if not self.would_exceed_capacity(task, rampart_group.name):
logger.debug("Starting %s in group %s (remaining_capacity=%s)",
task.log_format, rampart_group.name, remaining_capacity)
logger.debug(six.text_type("Starting {} in group {} (remaining_capacity={})").format(
task.log_format, rampart_group.name, remaining_capacity))
self.graph[rampart_group.name]['graph'].add_job(task)
self.start_task(task, rampart_group, task.get_jobs_fail_chain())
found_acceptable_queue = True
break
else:
logger.debug("Not enough capacity to run %s on %s (remaining_capacity=%s)",
task.log_format, rampart_group.name, remaining_capacity)
logger.debug(six.text_type("Not enough capacity to run {} on {} (remaining_capacity={})").format(
task.log_format, rampart_group.name, remaining_capacity))
if not found_acceptable_queue:
logger.debug("%s couldn't be scheduled on graph, waiting for next cycle", task.log_format)
logger.debug(six.text_type("{} couldn't be scheduled on graph, waiting for next cycle").format(task.log_format))
def fail_jobs_if_not_in_celery(self, node_jobs, active_tasks, celery_task_start_time,
isolated=False):
@@ -498,7 +506,8 @@ class TaskManager():
except DatabaseError:
logger.error("Task {} DB error in marking failed. Job possibly deleted.".format(task.log_format))
continue
awx_tasks._send_notification_templates(task, 'failed')
if hasattr(task, 'send_notification_templates'):
task.send_notification_templates('failed')
task.websocket_emit_status(new_status)
logger.error("{}Task {} has no record in celery. Marking as failed".format(
'Isolated ' if isolated else '', task.log_format))
@@ -576,9 +585,9 @@ class TaskManager():
return (task.task_impact + current_capacity > capacity_total)
def consume_capacity(self, task, instance_group):
logger.debug('%s consumed %s capacity units from %s with prior total of %s',
logger.debug(six.text_type('{} consumed {} capacity units from {} with prior total of {}').format(
task.log_format, task.task_impact, instance_group,
self.graph[instance_group]['consumed_capacity'])
self.graph[instance_group]['consumed_capacity']))
self.graph[instance_group]['consumed_capacity'] += task.task_impact
def get_remaining_capacity(self, instance_group):
@@ -629,4 +638,4 @@ class TaskManager():
# Operations whose queries rely on modifications made during the atomic scheduling session
for wfj in WorkflowJob.objects.filter(id__in=finished_wfjs):
awx_tasks._send_notification_templates(wfj, 'succeeded' if wfj.status == 'successful' else 'failed')
wfj.send_notification_templates('succeeded' if wfj.status == 'successful' else 'failed')

View File

@@ -3,7 +3,7 @@
import logging
# Celery
from celery import Task, shared_task
from celery import shared_task
# AWX
from awx.main.scheduler import TaskManager
@@ -15,23 +15,17 @@ logger = logging.getLogger('awx.main.scheduler')
# updated model, the call to schedule() may get stale data.
class LogErrorsTask(Task):
def on_failure(self, exc, task_id, args, kwargs, einfo):
logger.exception('Task {} encountered exception.'.format(self.name), exc_info=exc)
super(LogErrorsTask, self).on_failure(exc, task_id, args, kwargs, einfo)
@shared_task(base=LogErrorsTask)
@shared_task()
def run_job_launch(job_id):
TaskManager().schedule()
@shared_task(base=LogErrorsTask)
@shared_task()
def run_job_complete(job_id):
TaskManager().schedule()
@shared_task(base=LogErrorsTask)
@shared_task()
def run_task_manager():
logger.debug("Running Tower task manager.")
TaskManager().schedule()

View File

@@ -9,8 +9,17 @@ import json
# Django
from django.conf import settings
from django.db.models.signals import post_save, pre_delete, post_delete, m2m_changed
from django.db.models.signals import (
post_init,
post_save,
pre_delete,
post_delete,
m2m_changed,
)
from django.dispatch import receiver
from django.contrib.auth import SESSION_KEY
from django.utils import timezone
from django.utils.translation import ugettext_lazy as _
# Django-CRUM
from crum import get_current_request, get_current_user
@@ -20,11 +29,16 @@ import six
# AWX
from awx.main.models import * # noqa
from django.contrib.sessions.models import Session
from awx.api.serializers import * # noqa
from awx.main.constants import TOKEN_CENSOR
from awx.main.utils import model_instance_diff, model_to_dict, camelcase_to_underscore
from awx.main.utils import ignore_inventory_computed_fields, ignore_inventory_group_removal, _inventory_updates
from awx.main.tasks import update_inventory_computed_fields
from awx.main.fields import is_implicit_parent
from awx.main.fields import (
is_implicit_parent,
update_role_parentage_for_instance,
)
from awx.main import consumers
@@ -158,39 +172,6 @@ def sync_superuser_status_to_rbac(instance, **kwargs):
Role.singleton(ROLE_SINGLETON_SYSTEM_ADMINISTRATOR).members.remove(instance)
def create_user_role(instance, **kwargs):
if not kwargs.get('created', True):
return
try:
Role.objects.get(
content_type=ContentType.objects.get_for_model(instance),
object_id=instance.id,
role_field='admin_role'
)
except Role.DoesNotExist:
role = Role.objects.create(
role_field='admin_role',
content_object = instance,
)
role.members.add(instance)
def org_admin_edit_members(instance, action, model, reverse, pk_set, **kwargs):
content_type = ContentType.objects.get_for_model(Organization)
if reverse:
return
else:
if instance.content_type == content_type and \
instance.content_object.member_role.id == instance.id:
items = model.objects.filter(pk__in=pk_set).all()
for user in items:
if action == 'post_add':
instance.content_object.admin_role.children.add(user.admin_role)
if action == 'pre_remove':
instance.content_object.admin_role.children.remove(user.admin_role)
def rbac_activity_stream(instance, sender, **kwargs):
user_type = ContentType.objects.get_for_model(User)
# Only if we are associating/disassociating
@@ -219,6 +200,29 @@ def cleanup_detached_labels_on_deleted_parent(sender, instance, **kwargs):
l.delete()
def set_original_organization(sender, instance, **kwargs):
'''set_original_organization is used to set the original, or
pre-save organization, so we can later determine if the organization
field is dirty.
'''
instance.__original_org = instance.organization
def save_related_job_templates(sender, instance, **kwargs):
'''save_related_job_templates loops through all of the
job templates that use an Inventory or Project that have had their
Organization updated. This triggers the rebuilding of the RBAC hierarchy
and ensures the proper access restrictions.
'''
if sender not in (Project, Inventory):
raise ValueError('This signal callback is only intended for use with Project or Inventory')
if instance.__original_org != instance.organization:
jtq = JobTemplate.objects.filter(**{sender.__name__.lower(): instance})
for jt in jtq:
update_role_parentage_for_instance(jt)
def connect_computed_field_signals():
post_save.connect(emit_update_inventory_on_created_or_deleted, sender=Host)
post_delete.connect(emit_update_inventory_on_created_or_deleted, sender=Host)
@@ -236,18 +240,19 @@ def connect_computed_field_signals():
connect_computed_field_signals()
post_init.connect(set_original_organization, sender=Project)
post_init.connect(set_original_organization, sender=Inventory)
post_save.connect(save_related_job_templates, sender=Project)
post_save.connect(save_related_job_templates, sender=Inventory)
post_save.connect(emit_job_event_detail, sender=JobEvent)
post_save.connect(emit_ad_hoc_command_event_detail, sender=AdHocCommandEvent)
post_save.connect(emit_project_update_event_detail, sender=ProjectUpdateEvent)
post_save.connect(emit_inventory_update_event_detail, sender=InventoryUpdateEvent)
post_save.connect(emit_system_job_event_detail, sender=SystemJobEvent)
m2m_changed.connect(rebuild_role_ancestor_list, Role.parents.through)
m2m_changed.connect(org_admin_edit_members, Role.members.through)
m2m_changed.connect(rbac_activity_stream, Role.members.through)
m2m_changed.connect(rbac_activity_stream, Role.parents.through)
post_save.connect(sync_superuser_status_to_rbac, sender=User)
post_save.connect(create_user_role, sender=User)
pre_delete.connect(cleanup_detached_labels_on_deleted_parent, sender=UnifiedJob)
pre_delete.connect(cleanup_detached_labels_on_deleted_parent, sender=UnifiedJobTemplate)
@@ -396,6 +401,14 @@ model_serializer_mapping = {
AdHocCommand: AdHocCommandSerializer,
NotificationTemplate: NotificationTemplateSerializer,
Notification: NotificationSerializer,
CredentialType: CredentialTypeSerializer,
Schedule: ScheduleSerializer,
Label: LabelSerializer,
WorkflowJobTemplate: WorkflowJobTemplateSerializer,
WorkflowJobTemplateNode: WorkflowJobTemplateNodeSerializer,
WorkflowJob: WorkflowJobSerializer,
OAuth2AccessToken: OAuth2TokenSerializer,
OAuth2Application: OAuth2ApplicationSerializer,
}
@@ -414,6 +427,8 @@ def activity_stream_create(sender, instance, created, **kwargs):
if type(instance) == Job:
if 'extra_vars' in changes:
changes['extra_vars'] = instance.display_extra_vars()
if type(instance) == OAuth2AccessToken:
changes['token'] = TOKEN_CENSOR
activity_entry = ActivityStream(
operation='create',
object1=object1,
@@ -581,3 +596,36 @@ def delete_inventory_for_org(sender, instance, **kwargs):
inventory.schedule_deletion(user_id=getattr(user, 'id', None))
except RuntimeError as e:
logger.debug(e)
@receiver(post_save, sender=Session)
def save_user_session_membership(sender, **kwargs):
session = kwargs.get('instance', None)
if not session:
return
user = session.get_decoded().get(SESSION_KEY, None)
if not user:
return
user = User.objects.get(pk=user)
if UserSessionMembership.objects.filter(user=user, session=session).exists():
return
UserSessionMembership.objects.create(user=user, session=session, created=timezone.now())
for membership in UserSessionMembership.get_memberships_over_limit(user):
consumers.emit_channel_notification(
'control-limit_reached',
dict(group_name='control',
reason=unicode(_('limit_reached')),
session_key=membership.session.session_key)
)
@receiver(post_save, sender=OAuth2AccessToken)
def create_access_token_user_if_missing(sender, **kwargs):
obj = kwargs['instance']
if obj.application and obj.application.user:
obj.user = obj.application.user
post_save.disconnect(create_access_token_user_if_missing, sender=OAuth2AccessToken)
obj.save()
post_save.connect(create_access_token_user_if_missing, sender=OAuth2AccessToken)

View File

@@ -1,872 +0,0 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2015 Ansible, Inc.
# All Rights Reserved.
import datetime
from south.db import db
from south.v2 import SchemaMigration
from django.db import models
class Migration(SchemaMigration):
'''Complete initial migration for AWX 1.2-b1 release.'''
def forwards(self, orm):
# Adding model 'Tag'
db.create_table(u'main_tag', (
(u'id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
('name', self.gf('django.db.models.fields.CharField')(max_length=512)),
))
db.send_create_signal('main', ['Tag'])
# Adding model 'AuditTrail'
db.create_table(u'main_audittrail', (
(u'id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
('resource_type', self.gf('django.db.models.fields.CharField')(max_length=64)),
('modified_by', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['auth.User'], null=True, on_delete=models.SET_NULL, blank=True)),
('delta', self.gf('django.db.models.fields.TextField')()),
('detail', self.gf('django.db.models.fields.TextField')()),
('comment', self.gf('django.db.models.fields.TextField')()),
('tag', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['main.Tag'], null=True, on_delete=models.SET_NULL, blank=True)),
))
db.send_create_signal('main', ['AuditTrail'])
# Adding model 'Organization'
db.create_table(u'main_organization', (
(u'id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
('description', self.gf('django.db.models.fields.TextField')(default='', blank=True)),
('created_by', self.gf('django.db.models.fields.related.ForeignKey')(related_name="{'class': 'organization', 'app_label': 'main'}(class)s_created", null=True, on_delete=models.SET_NULL, to=orm['auth.User'])),
('created', self.gf('django.db.models.fields.DateTimeField')(auto_now_add=True, blank=True)),
('active', self.gf('django.db.models.fields.BooleanField')(default=True)),
('name', self.gf('django.db.models.fields.CharField')(unique=True, max_length=512)),
))
db.send_create_signal('main', ['Organization'])
# Adding M2M table for field tags on 'Organization'
db.create_table(u'main_organization_tags', (
('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)),
('organization', models.ForeignKey(orm['main.organization'], null=False)),
('tag', models.ForeignKey(orm['main.tag'], null=False))
))
db.create_unique(u'main_organization_tags', ['organization_id', 'tag_id'])
# Adding M2M table for field audit_trail on 'Organization'
db.create_table(u'main_organization_audit_trail', (
('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)),
('organization', models.ForeignKey(orm['main.organization'], null=False)),
('audittrail', models.ForeignKey(orm['main.audittrail'], null=False))
))
db.create_unique(u'main_organization_audit_trail', ['organization_id', 'audittrail_id'])
# Adding M2M table for field users on 'Organization'
db.create_table(u'main_organization_users', (
('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)),
('organization', models.ForeignKey(orm['main.organization'], null=False)),
('user', models.ForeignKey(orm[u'auth.user'], null=False))
))
db.create_unique(u'main_organization_users', ['organization_id', 'user_id'])
# Adding M2M table for field admins on 'Organization'
db.create_table(u'main_organization_admins', (
('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)),
('organization', models.ForeignKey(orm['main.organization'], null=False)),
('user', models.ForeignKey(orm[u'auth.user'], null=False))
))
db.create_unique(u'main_organization_admins', ['organization_id', 'user_id'])
# Adding M2M table for field projects on 'Organization'
db.create_table(u'main_organization_projects', (
('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)),
('organization', models.ForeignKey(orm['main.organization'], null=False)),
('project', models.ForeignKey(orm[u'main.project'], null=False))
))
db.create_unique(u'main_organization_projects', ['organization_id', 'project_id'])
# Adding model 'Inventory'
db.create_table(u'main_inventory', (
(u'id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
('description', self.gf('django.db.models.fields.TextField')(default='', blank=True)),
('created_by', self.gf('django.db.models.fields.related.ForeignKey')(related_name="{'class': 'inventory', 'app_label': 'main'}(class)s_created", null=True, on_delete=models.SET_NULL, to=orm['auth.User'])),
('created', self.gf('django.db.models.fields.DateTimeField')(auto_now_add=True, blank=True)),
('active', self.gf('django.db.models.fields.BooleanField')(default=True)),
('name', self.gf('django.db.models.fields.CharField')(unique=True, max_length=512)),
('organization', self.gf('django.db.models.fields.related.ForeignKey')(related_name='inventories', to=orm['main.Organization'])),
))
db.send_create_signal('main', ['Inventory'])
# Adding unique constraint on 'Inventory', fields ['name', 'organization']
db.create_unique(u'main_inventory', ['name', 'organization_id'])
# Adding M2M table for field tags on 'Inventory'
db.create_table(u'main_inventory_tags', (
('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)),
('inventory', models.ForeignKey(orm['main.inventory'], null=False)),
('tag', models.ForeignKey(orm['main.tag'], null=False))
))
db.create_unique(u'main_inventory_tags', ['inventory_id', 'tag_id'])
# Adding M2M table for field audit_trail on 'Inventory'
db.create_table(u'main_inventory_audit_trail', (
('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)),
('inventory', models.ForeignKey(orm['main.inventory'], null=False)),
('audittrail', models.ForeignKey(orm['main.audittrail'], null=False))
))
db.create_unique(u'main_inventory_audit_trail', ['inventory_id', 'audittrail_id'])
# Adding model 'Host'
db.create_table(u'main_host', (
(u'id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
('description', self.gf('django.db.models.fields.TextField')(default='', blank=True)),
('created_by', self.gf('django.db.models.fields.related.ForeignKey')(related_name="{'class': 'host', 'app_label': 'main'}(class)s_created", null=True, on_delete=models.SET_NULL, to=orm['auth.User'])),
('created', self.gf('django.db.models.fields.DateTimeField')(auto_now_add=True, blank=True)),
('active', self.gf('django.db.models.fields.BooleanField')(default=True)),
('name', self.gf('django.db.models.fields.CharField')(max_length=512)),
('variable_data', self.gf('django.db.models.fields.related.OneToOneField')(related_name='host', unique=True, on_delete=models.SET_NULL, default=None, to=orm['main.VariableData'], blank=True, null=True)),
('inventory', self.gf('django.db.models.fields.related.ForeignKey')(related_name='hosts', to=orm['main.Inventory'])),
('last_job', self.gf('django.db.models.fields.related.ForeignKey')(related_name='hosts_as_last_job+', on_delete=models.SET_NULL, default=None, to=orm['main.Job'], blank=True, null=True)),
('last_job_host_summary', self.gf('django.db.models.fields.related.ForeignKey')(related_name='hosts_as_last_job_summary+', on_delete=models.SET_NULL, default=None, to=orm['main.JobHostSummary'], blank=True, null=True)),
))
db.send_create_signal('main', ['Host'])
# Adding unique constraint on 'Host', fields ['name', 'inventory']
db.create_unique(u'main_host', ['name', 'inventory_id'])
# Adding M2M table for field tags on 'Host'
db.create_table(u'main_host_tags', (
('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)),
('host', models.ForeignKey(orm['main.host'], null=False)),
('tag', models.ForeignKey(orm['main.tag'], null=False))
))
db.create_unique(u'main_host_tags', ['host_id', 'tag_id'])
# Adding M2M table for field audit_trail on 'Host'
db.create_table(u'main_host_audit_trail', (
('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)),
('host', models.ForeignKey(orm['main.host'], null=False)),
('audittrail', models.ForeignKey(orm['main.audittrail'], null=False))
))
db.create_unique(u'main_host_audit_trail', ['host_id', 'audittrail_id'])
# Adding model 'Group'
db.create_table(u'main_group', (
(u'id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
('description', self.gf('django.db.models.fields.TextField')(default='', blank=True)),
('created_by', self.gf('django.db.models.fields.related.ForeignKey')(related_name="{'class': 'group', 'app_label': 'main'}(class)s_created", null=True, on_delete=models.SET_NULL, to=orm['auth.User'])),
('created', self.gf('django.db.models.fields.DateTimeField')(auto_now_add=True, blank=True)),
('active', self.gf('django.db.models.fields.BooleanField')(default=True)),
('name', self.gf('django.db.models.fields.CharField')(max_length=512)),
('inventory', self.gf('django.db.models.fields.related.ForeignKey')(related_name='groups', to=orm['main.Inventory'])),
('variable_data', self.gf('django.db.models.fields.related.OneToOneField')(related_name='group', unique=True, on_delete=models.SET_NULL, default=None, to=orm['main.VariableData'], blank=True, null=True)),
))
db.send_create_signal('main', ['Group'])
# Adding unique constraint on 'Group', fields ['name', 'inventory']
db.create_unique(u'main_group', ['name', 'inventory_id'])
# Adding M2M table for field tags on 'Group'
db.create_table(u'main_group_tags', (
('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)),
('group', models.ForeignKey(orm['main.group'], null=False)),
('tag', models.ForeignKey(orm['main.tag'], null=False))
))
db.create_unique(u'main_group_tags', ['group_id', 'tag_id'])
# Adding M2M table for field audit_trail on 'Group'
db.create_table(u'main_group_audit_trail', (
('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)),
('group', models.ForeignKey(orm['main.group'], null=False)),
('audittrail', models.ForeignKey(orm['main.audittrail'], null=False))
))
db.create_unique(u'main_group_audit_trail', ['group_id', 'audittrail_id'])
# Adding M2M table for field parents on 'Group'
db.create_table(u'main_group_parents', (
('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)),
('from_group', models.ForeignKey(orm['main.group'], null=False)),
('to_group', models.ForeignKey(orm['main.group'], null=False))
))
db.create_unique(u'main_group_parents', ['from_group_id', 'to_group_id'])
# Adding M2M table for field hosts on 'Group'
db.create_table(u'main_group_hosts', (
('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)),
('group', models.ForeignKey(orm['main.group'], null=False)),
('host', models.ForeignKey(orm['main.host'], null=False))
))
db.create_unique(u'main_group_hosts', ['group_id', 'host_id'])
# Adding model 'VariableData'
db.create_table(u'main_variabledata', (
(u'id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
('description', self.gf('django.db.models.fields.TextField')(default='', blank=True)),
('created_by', self.gf('django.db.models.fields.related.ForeignKey')(related_name="{'class': 'variabledata', 'app_label': 'main'}(class)s_created", null=True, on_delete=models.SET_NULL, to=orm['auth.User'])),
('created', self.gf('django.db.models.fields.DateTimeField')(auto_now_add=True, blank=True)),
('active', self.gf('django.db.models.fields.BooleanField')(default=True)),
('name', self.gf('django.db.models.fields.CharField')(max_length=512)),
('data', self.gf('django.db.models.fields.TextField')(default='')),
))
db.send_create_signal('main', ['VariableData'])
# Adding M2M table for field tags on 'VariableData'
db.create_table(u'main_variabledata_tags', (
('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)),
('variabledata', models.ForeignKey(orm['main.variabledata'], null=False)),
('tag', models.ForeignKey(orm['main.tag'], null=False))
))
db.create_unique(u'main_variabledata_tags', ['variabledata_id', 'tag_id'])
# Adding M2M table for field audit_trail on 'VariableData'
db.create_table(u'main_variabledata_audit_trail', (
('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)),
('variabledata', models.ForeignKey(orm['main.variabledata'], null=False)),
('audittrail', models.ForeignKey(orm['main.audittrail'], null=False))
))
db.create_unique(u'main_variabledata_audit_trail', ['variabledata_id', 'audittrail_id'])
# Adding model 'Credential'
db.create_table(u'main_credential', (
(u'id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
('description', self.gf('django.db.models.fields.TextField')(default='', blank=True)),
('created_by', self.gf('django.db.models.fields.related.ForeignKey')(related_name="{'class': 'credential', 'app_label': 'main'}(class)s_created", null=True, on_delete=models.SET_NULL, to=orm['auth.User'])),
('created', self.gf('django.db.models.fields.DateTimeField')(auto_now_add=True, blank=True)),
('active', self.gf('django.db.models.fields.BooleanField')(default=True)),
('name', self.gf('django.db.models.fields.CharField')(max_length=512)),
('user', self.gf('django.db.models.fields.related.ForeignKey')(related_name='credentials', on_delete=models.SET_NULL, default=None, to=orm['auth.User'], blank=True, null=True)),
('team', self.gf('django.db.models.fields.related.ForeignKey')(related_name='credentials', on_delete=models.SET_NULL, default=None, to=orm['main.Team'], blank=True, null=True)),
('ssh_username', self.gf('django.db.models.fields.CharField')(default='', max_length=1024, blank=True)),
('ssh_password', self.gf('django.db.models.fields.CharField')(default='', max_length=1024, blank=True)),
('ssh_key_data', self.gf('django.db.models.fields.TextField')(default='', blank=True)),
('ssh_key_unlock', self.gf('django.db.models.fields.CharField')(default='', max_length=1024, blank=True)),
('sudo_username', self.gf('django.db.models.fields.CharField')(default='', max_length=1024, blank=True)),
('sudo_password', self.gf('django.db.models.fields.CharField')(default='', max_length=1024, blank=True)),
))
db.send_create_signal('main', ['Credential'])
# Adding M2M table for field tags on 'Credential'
db.create_table(u'main_credential_tags', (
('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)),
('credential', models.ForeignKey(orm['main.credential'], null=False)),
('tag', models.ForeignKey(orm['main.tag'], null=False))
))
db.create_unique(u'main_credential_tags', ['credential_id', 'tag_id'])
# Adding M2M table for field audit_trail on 'Credential'
db.create_table(u'main_credential_audit_trail', (
('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)),
('credential', models.ForeignKey(orm['main.credential'], null=False)),
('audittrail', models.ForeignKey(orm['main.audittrail'], null=False))
))
db.create_unique(u'main_credential_audit_trail', ['credential_id', 'audittrail_id'])
# Adding model 'Team'
db.create_table(u'main_team', (
(u'id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
('description', self.gf('django.db.models.fields.TextField')(default='', blank=True)),
('created_by', self.gf('django.db.models.fields.related.ForeignKey')(related_name="{'class': 'team', 'app_label': 'main'}(class)s_created", null=True, on_delete=models.SET_NULL, to=orm['auth.User'])),
('created', self.gf('django.db.models.fields.DateTimeField')(auto_now_add=True, blank=True)),
('active', self.gf('django.db.models.fields.BooleanField')(default=True)),
('name', self.gf('django.db.models.fields.CharField')(unique=True, max_length=512)),
('organization', self.gf('django.db.models.fields.related.ForeignKey')(related_name='teams', null=True, on_delete=models.SET_NULL, to=orm['main.Organization'])),
))
db.send_create_signal('main', ['Team'])
# Adding M2M table for field tags on 'Team'
db.create_table(u'main_team_tags', (
('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)),
('team', models.ForeignKey(orm['main.team'], null=False)),
('tag', models.ForeignKey(orm['main.tag'], null=False))
))
db.create_unique(u'main_team_tags', ['team_id', 'tag_id'])
# Adding M2M table for field audit_trail on 'Team'
db.create_table(u'main_team_audit_trail', (
('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)),
('team', models.ForeignKey(orm['main.team'], null=False)),
('audittrail', models.ForeignKey(orm['main.audittrail'], null=False))
))
db.create_unique(u'main_team_audit_trail', ['team_id', 'audittrail_id'])
# Adding M2M table for field projects on 'Team'
db.create_table(u'main_team_projects', (
('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)),
('team', models.ForeignKey(orm['main.team'], null=False)),
('project', models.ForeignKey(orm[u'main.project'], null=False))
))
db.create_unique(u'main_team_projects', ['team_id', 'project_id'])
# Adding M2M table for field users on 'Team'
db.create_table(u'main_team_users', (
('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)),
('team', models.ForeignKey(orm['main.team'], null=False)),
('user', models.ForeignKey(orm[u'auth.user'], null=False))
))
db.create_unique(u'main_team_users', ['team_id', 'user_id'])
# Adding model 'Project'
db.create_table(u'main_project', (
(u'id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
('description', self.gf('django.db.models.fields.TextField')(default='', blank=True)),
('created_by', self.gf('django.db.models.fields.related.ForeignKey')(related_name="{'class': 'project', 'app_label': u'main'}(class)s_created", null=True, on_delete=models.SET_NULL, to=orm['auth.User'])),
('created', self.gf('django.db.models.fields.DateTimeField')(auto_now_add=True, blank=True)),
('active', self.gf('django.db.models.fields.BooleanField')(default=True)),
('name', self.gf('django.db.models.fields.CharField')(unique=True, max_length=512)),
('local_path', self.gf('django.db.models.fields.CharField')(max_length=1024)),
))
db.send_create_signal(u'main', ['Project'])
# Adding M2M table for field tags on 'Project'
db.create_table(u'main_project_tags', (
('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)),
('project', models.ForeignKey(orm[u'main.project'], null=False)),
('tag', models.ForeignKey(orm['main.tag'], null=False))
))
db.create_unique(u'main_project_tags', ['project_id', 'tag_id'])
# Adding M2M table for field audit_trail on 'Project'
db.create_table(u'main_project_audit_trail', (
('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)),
('project', models.ForeignKey(orm[u'main.project'], null=False)),
('audittrail', models.ForeignKey(orm['main.audittrail'], null=False))
))
db.create_unique(u'main_project_audit_trail', ['project_id', 'audittrail_id'])
# Adding model 'Permission'
db.create_table(u'main_permission', (
(u'id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
('description', self.gf('django.db.models.fields.TextField')(default='', blank=True)),
('created_by', self.gf('django.db.models.fields.related.ForeignKey')(related_name="{'class': 'permission', 'app_label': 'main'}(class)s_created", null=True, on_delete=models.SET_NULL, to=orm['auth.User'])),
('created', self.gf('django.db.models.fields.DateTimeField')(auto_now_add=True, blank=True)),
('active', self.gf('django.db.models.fields.BooleanField')(default=True)),
('name', self.gf('django.db.models.fields.CharField')(max_length=512)),
('user', self.gf('django.db.models.fields.related.ForeignKey')(blank=True, related_name='permissions', null=True, on_delete=models.SET_NULL, to=orm['auth.User'])),
('team', self.gf('django.db.models.fields.related.ForeignKey')(blank=True, related_name='permissions', null=True, on_delete=models.SET_NULL, to=orm['main.Team'])),
('project', self.gf('django.db.models.fields.related.ForeignKey')(blank=True, related_name='permissions', null=True, on_delete=models.SET_NULL, to=orm['main.Project'])),
('inventory', self.gf('django.db.models.fields.related.ForeignKey')(related_name='permissions', null=True, on_delete=models.SET_NULL, to=orm['main.Inventory'])),
('permission_type', self.gf('django.db.models.fields.CharField')(max_length=64)),
))
db.send_create_signal('main', ['Permission'])
# Adding M2M table for field tags on 'Permission'
db.create_table(u'main_permission_tags', (
('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)),
('permission', models.ForeignKey(orm['main.permission'], null=False)),
('tag', models.ForeignKey(orm['main.tag'], null=False))
))
db.create_unique(u'main_permission_tags', ['permission_id', 'tag_id'])
# Adding M2M table for field audit_trail on 'Permission'
db.create_table(u'main_permission_audit_trail', (
('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)),
('permission', models.ForeignKey(orm['main.permission'], null=False)),
('audittrail', models.ForeignKey(orm['main.audittrail'], null=False))
))
db.create_unique(u'main_permission_audit_trail', ['permission_id', 'audittrail_id'])
# Adding model 'JobTemplate'
db.create_table(u'main_jobtemplate', (
(u'id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
('description', self.gf('django.db.models.fields.TextField')(default='', blank=True)),
('created_by', self.gf('django.db.models.fields.related.ForeignKey')(related_name="{'class': 'jobtemplate', 'app_label': 'main'}(class)s_created", null=True, on_delete=models.SET_NULL, to=orm['auth.User'])),
('created', self.gf('django.db.models.fields.DateTimeField')(auto_now_add=True, blank=True)),
('active', self.gf('django.db.models.fields.BooleanField')(default=True)),
('name', self.gf('django.db.models.fields.CharField')(unique=True, max_length=512)),
('job_type', self.gf('django.db.models.fields.CharField')(max_length=64)),
('inventory', self.gf('django.db.models.fields.related.ForeignKey')(related_name='job_templates', null=True, on_delete=models.SET_NULL, to=orm['main.Inventory'])),
('project', self.gf('django.db.models.fields.related.ForeignKey')(related_name='job_templates', null=True, on_delete=models.SET_NULL, to=orm['main.Project'])),
('playbook', self.gf('django.db.models.fields.CharField')(default='', max_length=1024)),
('credential', self.gf('django.db.models.fields.related.ForeignKey')(related_name='job_templates', on_delete=models.SET_NULL, default=None, to=orm['main.Credential'], blank=True, null=True)),
('forks', self.gf('django.db.models.fields.PositiveIntegerField')(default=0, blank=True)),
('limit', self.gf('django.db.models.fields.CharField')(default='', max_length=1024, blank=True)),
('verbosity', self.gf('django.db.models.fields.PositiveIntegerField')(default=0, blank=True)),
('extra_vars', self.gf('django.db.models.fields.TextField')(default='', blank=True)),
))
db.send_create_signal('main', ['JobTemplate'])
# Adding M2M table for field tags on 'JobTemplate'
db.create_table(u'main_jobtemplate_tags', (
('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)),
('jobtemplate', models.ForeignKey(orm['main.jobtemplate'], null=False)),
('tag', models.ForeignKey(orm['main.tag'], null=False))
))
db.create_unique(u'main_jobtemplate_tags', ['jobtemplate_id', 'tag_id'])
# Adding M2M table for field audit_trail on 'JobTemplate'
db.create_table(u'main_jobtemplate_audit_trail', (
('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)),
('jobtemplate', models.ForeignKey(orm['main.jobtemplate'], null=False)),
('audittrail', models.ForeignKey(orm['main.audittrail'], null=False))
))
db.create_unique(u'main_jobtemplate_audit_trail', ['jobtemplate_id', 'audittrail_id'])
# Adding model 'Job'
db.create_table(u'main_job', (
(u'id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
('description', self.gf('django.db.models.fields.TextField')(default='', blank=True)),
('created_by', self.gf('django.db.models.fields.related.ForeignKey')(related_name="{'class': 'job', 'app_label': 'main'}(class)s_created", null=True, on_delete=models.SET_NULL, to=orm['auth.User'])),
('created', self.gf('django.db.models.fields.DateTimeField')(auto_now_add=True, blank=True)),
('active', self.gf('django.db.models.fields.BooleanField')(default=True)),
('name', self.gf('django.db.models.fields.CharField')(unique=True, max_length=512)),
('job_template', self.gf('django.db.models.fields.related.ForeignKey')(related_name='jobs', on_delete=models.SET_NULL, default=None, to=orm['main.JobTemplate'], blank=True, null=True)),
('job_type', self.gf('django.db.models.fields.CharField')(max_length=64)),
('inventory', self.gf('django.db.models.fields.related.ForeignKey')(related_name='jobs', null=True, on_delete=models.SET_NULL, to=orm['main.Inventory'])),
('credential', self.gf('django.db.models.fields.related.ForeignKey')(related_name='jobs', null=True, on_delete=models.SET_NULL, to=orm['main.Credential'])),
('project', self.gf('django.db.models.fields.related.ForeignKey')(related_name='jobs', null=True, on_delete=models.SET_NULL, to=orm['main.Project'])),
('playbook', self.gf('django.db.models.fields.CharField')(max_length=1024)),
('forks', self.gf('django.db.models.fields.PositiveIntegerField')(default=0, blank=True)),
('limit', self.gf('django.db.models.fields.CharField')(default='', max_length=1024, blank=True)),
('verbosity', self.gf('django.db.models.fields.PositiveIntegerField')(default=0, blank=True)),
('extra_vars', self.gf('django.db.models.fields.TextField')(default='', blank=True)),
('cancel_flag', self.gf('django.db.models.fields.BooleanField')(default=False)),
('status', self.gf('django.db.models.fields.CharField')(default='new', max_length=20)),
('failed', self.gf('django.db.models.fields.BooleanField')(default=False)),
('result_stdout', self.gf('django.db.models.fields.TextField')(default='', blank=True)),
('result_traceback', self.gf('django.db.models.fields.TextField')(default='', blank=True)),
('celery_task_id', self.gf('django.db.models.fields.CharField')(default='', max_length=100, blank=True)),
))
db.send_create_signal('main', ['Job'])
# Adding M2M table for field tags on 'Job'
db.create_table(u'main_job_tags', (
('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)),
('job', models.ForeignKey(orm['main.job'], null=False)),
('tag', models.ForeignKey(orm['main.tag'], null=False))
))
db.create_unique(u'main_job_tags', ['job_id', 'tag_id'])
# Adding M2M table for field audit_trail on 'Job'
db.create_table(u'main_job_audit_trail', (
('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)),
('job', models.ForeignKey(orm['main.job'], null=False)),
('audittrail', models.ForeignKey(orm['main.audittrail'], null=False))
))
db.create_unique(u'main_job_audit_trail', ['job_id', 'audittrail_id'])
# Adding model 'JobHostSummary'
db.create_table(u'main_jobhostsummary', (
(u'id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
('job', self.gf('django.db.models.fields.related.ForeignKey')(related_name='job_host_summaries', to=orm['main.Job'])),
('host', self.gf('django.db.models.fields.related.ForeignKey')(related_name='job_host_summaries', to=orm['main.Host'])),
('changed', self.gf('django.db.models.fields.PositiveIntegerField')(default=0)),
('dark', self.gf('django.db.models.fields.PositiveIntegerField')(default=0)),
('failures', self.gf('django.db.models.fields.PositiveIntegerField')(default=0)),
('ok', self.gf('django.db.models.fields.PositiveIntegerField')(default=0)),
('processed', self.gf('django.db.models.fields.PositiveIntegerField')(default=0)),
('skipped', self.gf('django.db.models.fields.PositiveIntegerField')(default=0)),
))
db.send_create_signal(u'main', ['JobHostSummary'])
# Adding unique constraint on 'JobHostSummary', fields ['job', 'host']
db.create_unique(u'main_jobhostsummary', ['job_id', 'host_id'])
# Adding model 'JobEvent'
db.create_table(u'main_jobevent', (
(u'id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
('job', self.gf('django.db.models.fields.related.ForeignKey')(related_name='job_events', to=orm['main.Job'])),
('created', self.gf('django.db.models.fields.DateTimeField')(auto_now_add=True, blank=True)),
('event', self.gf('django.db.models.fields.CharField')(max_length=100)),
('event_data', self.gf('jsonfield.fields.JSONField')(default={}, blank=True)),
('failed', self.gf('django.db.models.fields.BooleanField')(default=False)),
('host', self.gf('django.db.models.fields.related.ForeignKey')(related_name='job_events', on_delete=models.SET_NULL, default=None, to=orm['main.Host'], blank=True, null=True)),
))
db.send_create_signal('main', ['JobEvent'])
def backwards(self, orm):
# Removing unique constraint on 'JobHostSummary', fields ['job', 'host']
db.delete_unique(u'main_jobhostsummary', ['job_id', 'host_id'])
# Removing unique constraint on 'Group', fields ['name', 'inventory']
db.delete_unique(u'main_group', ['name', 'inventory_id'])
# Removing unique constraint on 'Host', fields ['name', 'inventory']
db.delete_unique(u'main_host', ['name', 'inventory_id'])
# Removing unique constraint on 'Inventory', fields ['name', 'organization']
db.delete_unique(u'main_inventory', ['name', 'organization_id'])
# Deleting model 'Tag'
db.delete_table(u'main_tag')
# Deleting model 'AuditTrail'
db.delete_table(u'main_audittrail')
# Deleting model 'Organization'
db.delete_table(u'main_organization')
# Removing M2M table for field tags on 'Organization'
db.delete_table('main_organization_tags')
# Removing M2M table for field audit_trail on 'Organization'
db.delete_table('main_organization_audit_trail')
# Removing M2M table for field users on 'Organization'
db.delete_table('main_organization_users')
# Removing M2M table for field admins on 'Organization'
db.delete_table('main_organization_admins')
# Removing M2M table for field projects on 'Organization'
db.delete_table('main_organization_projects')
# Deleting model 'Inventory'
db.delete_table(u'main_inventory')
# Removing M2M table for field tags on 'Inventory'
db.delete_table('main_inventory_tags')
# Removing M2M table for field audit_trail on 'Inventory'
db.delete_table('main_inventory_audit_trail')
# Deleting model 'Host'
db.delete_table(u'main_host')
# Removing M2M table for field tags on 'Host'
db.delete_table('main_host_tags')
# Removing M2M table for field audit_trail on 'Host'
db.delete_table('main_host_audit_trail')
# Deleting model 'Group'
db.delete_table(u'main_group')
# Removing M2M table for field tags on 'Group'
db.delete_table('main_group_tags')
# Removing M2M table for field audit_trail on 'Group'
db.delete_table('main_group_audit_trail')
# Removing M2M table for field parents on 'Group'
db.delete_table('main_group_parents')
# Removing M2M table for field hosts on 'Group'
db.delete_table('main_group_hosts')
# Deleting model 'VariableData'
db.delete_table(u'main_variabledata')
# Removing M2M table for field tags on 'VariableData'
db.delete_table('main_variabledata_tags')
# Removing M2M table for field audit_trail on 'VariableData'
db.delete_table('main_variabledata_audit_trail')
# Deleting model 'Credential'
db.delete_table(u'main_credential')
# Removing M2M table for field tags on 'Credential'
db.delete_table('main_credential_tags')
# Removing M2M table for field audit_trail on 'Credential'
db.delete_table('main_credential_audit_trail')
# Deleting model 'Team'
db.delete_table(u'main_team')
# Removing M2M table for field tags on 'Team'
db.delete_table('main_team_tags')
# Removing M2M table for field audit_trail on 'Team'
db.delete_table('main_team_audit_trail')
# Removing M2M table for field projects on 'Team'
db.delete_table('main_team_projects')
# Removing M2M table for field users on 'Team'
db.delete_table('main_team_users')
# Deleting model 'Project'
db.delete_table(u'main_project')
# Removing M2M table for field tags on 'Project'
db.delete_table('main_project_tags')
# Removing M2M table for field audit_trail on 'Project'
db.delete_table('main_project_audit_trail')
# Deleting model 'Permission'
db.delete_table(u'main_permission')
# Removing M2M table for field tags on 'Permission'
db.delete_table('main_permission_tags')
# Removing M2M table for field audit_trail on 'Permission'
db.delete_table('main_permission_audit_trail')
# Deleting model 'JobTemplate'
db.delete_table(u'main_jobtemplate')
# Removing M2M table for field tags on 'JobTemplate'
db.delete_table('main_jobtemplate_tags')
# Removing M2M table for field audit_trail on 'JobTemplate'
db.delete_table('main_jobtemplate_audit_trail')
# Deleting model 'Job'
db.delete_table(u'main_job')
# Removing M2M table for field tags on 'Job'
db.delete_table('main_job_tags')
# Removing M2M table for field audit_trail on 'Job'
db.delete_table('main_job_audit_trail')
# Deleting model 'JobHostSummary'
db.delete_table(u'main_jobhostsummary')
# Deleting model 'JobEvent'
db.delete_table(u'main_jobevent')
models = {
u'auth.group': {
'Meta': {'object_name': 'Group'},
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
},
u'auth.permission': {
'Meta': {'ordering': "(u'content_type__app_label', u'content_type__model', u'codename')", 'unique_together': "((u'content_type', u'codename'),)", 'object_name': 'Permission'},
'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['contenttypes.ContentType']"}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
},
u'auth.user': {
'Meta': {'object_name': 'User'},
'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
},
u'contenttypes.contenttype': {
'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
},
'main.audittrail': {
'Meta': {'object_name': 'AuditTrail'},
'comment': ('django.db.models.fields.TextField', [], {}),
'delta': ('django.db.models.fields.TextField', [], {}),
'detail': ('django.db.models.fields.TextField', [], {}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'modified_by': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['auth.User']", 'null': 'True', 'on_delete': 'models.SET_NULL', 'blank': 'True'}),
'resource_type': ('django.db.models.fields.CharField', [], {'max_length': '64'}),
'tag': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['main.Tag']", 'null': 'True', 'on_delete': 'models.SET_NULL', 'blank': 'True'})
},
'main.credential': {
'Meta': {'object_name': 'Credential'},
'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'audit_trail': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'credential_by_audit_trail'", 'blank': 'True', 'to': "orm['main.AuditTrail']"}),
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'created_by': ('django.db.models.fields.related.ForeignKey', [], {'related_name': '"{\'class\': \'credential\', \'app_label\': \'main\'}(class)s_created"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
'description': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '512'}),
'ssh_key_data': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
'ssh_key_unlock': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
'ssh_password': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
'ssh_username': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
'sudo_password': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
'sudo_username': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
'tags': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'credential_by_tag'", 'blank': 'True', 'to': "orm['main.Tag']"}),
'team': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'credentials'", 'on_delete': 'models.SET_NULL', 'default': 'None', 'to': "orm['main.Team']", 'blank': 'True', 'null': 'True'}),
'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'credentials'", 'on_delete': 'models.SET_NULL', 'default': 'None', 'to': u"orm['auth.User']", 'blank': 'True', 'null': 'True'})
},
'main.group': {
'Meta': {'unique_together': "(('name', 'inventory'),)", 'object_name': 'Group'},
'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'audit_trail': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'group_by_audit_trail'", 'blank': 'True', 'to': "orm['main.AuditTrail']"}),
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'created_by': ('django.db.models.fields.related.ForeignKey', [], {'related_name': '"{\'class\': \'group\', \'app_label\': \'main\'}(class)s_created"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
'description': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
'hosts': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'groups'", 'blank': 'True', 'to': "orm['main.Host']"}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'inventory': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'groups'", 'to': "orm['main.Inventory']"}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '512'}),
'parents': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'children'", 'blank': 'True', 'to': "orm['main.Group']"}),
'tags': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'group_by_tag'", 'blank': 'True', 'to': "orm['main.Tag']"}),
'variable_data': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'group'", 'unique': 'True', 'on_delete': 'models.SET_NULL', 'default': 'None', 'to': "orm['main.VariableData']", 'blank': 'True', 'null': 'True'})
},
'main.host': {
'Meta': {'unique_together': "(('name', 'inventory'),)", 'object_name': 'Host'},
'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'audit_trail': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'host_by_audit_trail'", 'blank': 'True', 'to': "orm['main.AuditTrail']"}),
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'created_by': ('django.db.models.fields.related.ForeignKey', [], {'related_name': '"{\'class\': \'host\', \'app_label\': \'main\'}(class)s_created"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
'description': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'inventory': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'hosts'", 'to': "orm['main.Inventory']"}),
'last_job': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'hosts_as_last_job+'", 'on_delete': 'models.SET_NULL', 'default': 'None', 'to': "orm['main.Job']", 'blank': 'True', 'null': 'True'}),
'last_job_host_summary': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'hosts_as_last_job_summary+'", 'on_delete': 'models.SET_NULL', 'default': 'None', 'to': u"orm['main.JobHostSummary']", 'blank': 'True', 'null': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '512'}),
'tags': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'host_by_tag'", 'blank': 'True', 'to': "orm['main.Tag']"}),
'variable_data': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'host'", 'unique': 'True', 'on_delete': 'models.SET_NULL', 'default': 'None', 'to': "orm['main.VariableData']", 'blank': 'True', 'null': 'True'})
},
'main.inventory': {
'Meta': {'unique_together': "(('name', 'organization'),)", 'object_name': 'Inventory'},
'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'audit_trail': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'inventory_by_audit_trail'", 'blank': 'True', 'to': "orm['main.AuditTrail']"}),
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'created_by': ('django.db.models.fields.related.ForeignKey', [], {'related_name': '"{\'class\': \'inventory\', \'app_label\': \'main\'}(class)s_created"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
'description': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '512'}),
'organization': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'inventories'", 'to': "orm['main.Organization']"}),
'tags': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'inventory_by_tag'", 'blank': 'True', 'to': "orm['main.Tag']"})
},
'main.job': {
'Meta': {'object_name': 'Job'},
'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'audit_trail': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'job_by_audit_trail'", 'blank': 'True', 'to': "orm['main.AuditTrail']"}),
'cancel_flag': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'celery_task_id': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '100', 'blank': 'True'}),
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'created_by': ('django.db.models.fields.related.ForeignKey', [], {'related_name': '"{\'class\': \'job\', \'app_label\': \'main\'}(class)s_created"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
'credential': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'jobs'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': "orm['main.Credential']"}),
'description': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
'extra_vars': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
'failed': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'forks': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0', 'blank': 'True'}),
'hosts': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'jobs'", 'blank': 'True', 'through': u"orm['main.JobHostSummary']", 'to': "orm['main.Host']"}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'inventory': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'jobs'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': "orm['main.Inventory']"}),
'job_template': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'jobs'", 'on_delete': 'models.SET_NULL', 'default': 'None', 'to': "orm['main.JobTemplate']", 'blank': 'True', 'null': 'True'}),
'job_type': ('django.db.models.fields.CharField', [], {'max_length': '64'}),
'limit': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '512'}),
'playbook': ('django.db.models.fields.CharField', [], {'max_length': '1024'}),
'project': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'jobs'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['main.Project']"}),
'result_stdout': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
'result_traceback': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
'status': ('django.db.models.fields.CharField', [], {'default': "'new'", 'max_length': '20'}),
'tags': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'job_by_tag'", 'blank': 'True', 'to': "orm['main.Tag']"}),
'verbosity': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0', 'blank': 'True'})
},
'main.jobevent': {
'Meta': {'ordering': "('pk',)", 'object_name': 'JobEvent'},
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'event': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'event_data': ('jsonfield.fields.JSONField', [], {'default': '{}', 'blank': 'True'}),
'failed': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'host': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'job_events'", 'on_delete': 'models.SET_NULL', 'default': 'None', 'to': "orm['main.Host']", 'blank': 'True', 'null': 'True'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'job': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'job_events'", 'to': "orm['main.Job']"})
},
u'main.jobhostsummary': {
'Meta': {'ordering': "('-pk',)", 'unique_together': "[('job', 'host')]", 'object_name': 'JobHostSummary'},
'changed': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
'dark': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
'failures': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
'host': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'job_host_summaries'", 'to': "orm['main.Host']"}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'job': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'job_host_summaries'", 'to': "orm['main.Job']"}),
'ok': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
'processed': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
'skipped': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'})
},
'main.jobtemplate': {
'Meta': {'object_name': 'JobTemplate'},
'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'audit_trail': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'jobtemplate_by_audit_trail'", 'blank': 'True', 'to': "orm['main.AuditTrail']"}),
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'created_by': ('django.db.models.fields.related.ForeignKey', [], {'related_name': '"{\'class\': \'jobtemplate\', \'app_label\': \'main\'}(class)s_created"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
'credential': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'job_templates'", 'on_delete': 'models.SET_NULL', 'default': 'None', 'to': "orm['main.Credential']", 'blank': 'True', 'null': 'True'}),
'description': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
'extra_vars': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
'forks': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0', 'blank': 'True'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'inventory': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'job_templates'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': "orm['main.Inventory']"}),
'job_type': ('django.db.models.fields.CharField', [], {'max_length': '64'}),
'limit': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '512'}),
'playbook': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024'}),
'project': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'job_templates'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['main.Project']"}),
'tags': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'jobtemplate_by_tag'", 'blank': 'True', 'to': "orm['main.Tag']"}),
'verbosity': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0', 'blank': 'True'})
},
'main.organization': {
'Meta': {'object_name': 'Organization'},
'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'admins': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'admin_of_organizations'", 'blank': 'True', 'to': u"orm['auth.User']"}),
'audit_trail': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'organization_by_audit_trail'", 'blank': 'True', 'to': "orm['main.AuditTrail']"}),
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'created_by': ('django.db.models.fields.related.ForeignKey', [], {'related_name': '"{\'class\': \'organization\', \'app_label\': \'main\'}(class)s_created"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
'description': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '512'}),
'projects': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'organizations'", 'blank': 'True', 'to': u"orm['main.Project']"}),
'tags': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'organization_by_tag'", 'blank': 'True', 'to': "orm['main.Tag']"}),
'users': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'organizations'", 'blank': 'True', 'to': u"orm['auth.User']"})
},
'main.permission': {
'Meta': {'object_name': 'Permission'},
'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'audit_trail': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'permission_by_audit_trail'", 'blank': 'True', 'to': "orm['main.AuditTrail']"}),
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'created_by': ('django.db.models.fields.related.ForeignKey', [], {'related_name': '"{\'class\': \'permission\', \'app_label\': \'main\'}(class)s_created"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
'description': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'inventory': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'permissions'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': "orm['main.Inventory']"}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '512'}),
'permission_type': ('django.db.models.fields.CharField', [], {'max_length': '64'}),
'project': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'permissions'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['main.Project']"}),
'tags': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'permission_by_tag'", 'blank': 'True', 'to': "orm['main.Tag']"}),
'team': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'permissions'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': "orm['main.Team']"}),
'user': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'permissions'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"})
},
u'main.project': {
'Meta': {'object_name': 'Project'},
'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'audit_trail': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'project_by_audit_trail'", 'blank': 'True', 'to': "orm['main.AuditTrail']"}),
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'created_by': ('django.db.models.fields.related.ForeignKey', [], {'related_name': '"{\'class\': \'project\', \'app_label\': u\'main\'}(class)s_created"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
'description': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'local_path': ('django.db.models.fields.CharField', [], {'max_length': '1024'}),
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '512'}),
'tags': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'project_by_tag'", 'blank': 'True', 'to': "orm['main.Tag']"})
},
'main.tag': {
'Meta': {'object_name': 'Tag'},
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '512'})
},
'main.team': {
'Meta': {'object_name': 'Team'},
'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'audit_trail': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'team_by_audit_trail'", 'blank': 'True', 'to': "orm['main.AuditTrail']"}),
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'created_by': ('django.db.models.fields.related.ForeignKey', [], {'related_name': '"{\'class\': \'team\', \'app_label\': \'main\'}(class)s_created"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
'description': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '512'}),
'organization': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'teams'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': "orm['main.Organization']"}),
'projects': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'teams'", 'blank': 'True', 'to': u"orm['main.Project']"}),
'tags': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'team_by_tag'", 'blank': 'True', 'to': "orm['main.Tag']"}),
'users': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'teams'", 'blank': 'True', 'to': u"orm['auth.User']"})
},
'main.variabledata': {
'Meta': {'object_name': 'VariableData'},
'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'audit_trail': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'variabledata_by_audit_trail'", 'blank': 'True', 'to': "orm['main.AuditTrail']"}),
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'created_by': ('django.db.models.fields.related.ForeignKey', [], {'related_name': '"{\'class\': \'variabledata\', \'app_label\': \'main\'}(class)s_created"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
'data': ('django.db.models.fields.TextField', [], {'default': "''"}),
'description': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '512'}),
'tags': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'variabledata_by_tag'", 'blank': 'True', 'to': "orm['main.Tag']"})
}
}
complete_apps = ['main']

View File

@@ -1,664 +0,0 @@
# -*- coding: utf-8 -*-
import datetime
from south.db import db
from south.v2 import SchemaMigration
from django.db import models
class Migration(SchemaMigration):
'''
Schema migration for AWX 1.2-b2 release.
- Adds variables field on Host and Group models.
- Adds job_tags and host_config_key fields on JobTemplate.
- Adds job_tags, job_args, job_cwd, job_env fields on Job.
- Adds failed field on JobHostSummary.
- Adds play, task, parent and hosts fields on JobEvent.
NOTE: This migration has been manually edited!
'''
def forwards(self, orm):
# Adding field 'Host.variables'
db.add_column(u'main_host', 'variables',
self.gf('django.db.models.fields.TextField')(default='', blank=True, null=True),
keep_default=False)
# Adding field 'Group.variables'
db.add_column(u'main_group', 'variables',
self.gf('django.db.models.fields.TextField')(default='', blank=True, null=True),
keep_default=False)
# Adding field 'JobTemplate.job_tags'
db.add_column(u'main_jobtemplate', 'job_tags',
self.gf('django.db.models.fields.CharField')(default='', max_length=1024, null=True, blank=True),
keep_default=False)
# Adding field 'JobTemplate.host_config_key'
db.add_column(u'main_jobtemplate', 'host_config_key',
self.gf('django.db.models.fields.CharField')(default='', max_length=1024, blank=True, null=True),
keep_default=False)
# Adding field 'Job.job_tags'
db.add_column(u'main_job', 'job_tags',
self.gf('django.db.models.fields.CharField')(default='', max_length=1024, null=True, blank=True),
keep_default=False)
# Adding field 'Job.job_args'
db.add_column(u'main_job', 'job_args',
self.gf('django.db.models.fields.CharField')(default='', max_length=1024, null=True, blank=True),
keep_default=False)
# Adding field 'Job.job_cwd'
db.add_column(u'main_job', 'job_cwd',
self.gf('django.db.models.fields.CharField')(default='', max_length=1024, null=True, blank=True),
keep_default=False)
# Adding field 'Job.job_env'
db.add_column(u'main_job', 'job_env',
self.gf('jsonfield.fields.JSONField')(default={}, null=True, blank=True),
keep_default=False)
# Adding field 'JobHostSummary.failed'
db.add_column(u'main_jobhostsummary', 'failed',
self.gf('django.db.models.fields.BooleanField')(default=False),
keep_default=False)
# Adding field 'JobEvent.play'
db.add_column(u'main_jobevent', 'play',
self.gf('django.db.models.fields.CharField')(default='', max_length=1024, blank=True, null=True),
keep_default=False)
# Adding field 'JobEvent.task'
db.add_column(u'main_jobevent', 'task',
self.gf('django.db.models.fields.CharField')(default='', max_length=1024, blank=True, null=True),
keep_default=False)
# Adding field 'JobEvent.parent'
db.add_column(u'main_jobevent', 'parent',
self.gf('django.db.models.fields.related.ForeignKey')(related_name='children', on_delete=models.SET_NULL, default=None, to=orm['main.JobEvent'], blank=True, null=True),
keep_default=False)
# Adding M2M table for field hosts on 'JobEvent'
m2m_table_name = db.shorten_name(u'main_jobevent_hosts')
db.create_table(m2m_table_name, (
('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)),
('jobevent', models.ForeignKey(orm['main.jobevent'], null=False)),
('host', models.ForeignKey(orm['main.host'], null=False))
))
db.create_unique(m2m_table_name, ['jobevent_id', 'host_id'])
# Removing M2M table for field tags on 'Job'
db.delete_table(db.shorten_name(u'main_job_tags'))
# Removing M2M table for field audit_trail on 'Job'
db.delete_table(db.shorten_name(u'main_job_audit_trail'))
# Removing M2M table for field tags on 'Inventory'
db.delete_table(db.shorten_name(u'main_inventory_tags'))
# Removing M2M table for field audit_trail on 'Inventory'
db.delete_table(db.shorten_name(u'main_inventory_audit_trail'))
# Removing M2M table for field tags on 'Host'
db.delete_table(db.shorten_name(u'main_host_tags'))
# Removing M2M table for field audit_trail on 'Host'
db.delete_table(db.shorten_name(u'main_host_audit_trail'))
# Removing M2M table for field tags on 'Group'
db.delete_table(db.shorten_name(u'main_group_tags'))
# Removing M2M table for field audit_trail on 'Group'
db.delete_table(db.shorten_name(u'main_group_audit_trail'))
# Removing M2M table for field audit_trail on 'Credential'
db.delete_table(db.shorten_name(u'main_credential_audit_trail'))
# Removing M2M table for field tags on 'Credential'
db.delete_table(db.shorten_name(u'main_credential_tags'))
# Removing M2M table for field tags on 'JobTemplate'
db.delete_table(db.shorten_name(u'main_jobtemplate_tags'))
# Removing M2M table for field audit_trail on 'JobTemplate'
db.delete_table(db.shorten_name(u'main_jobtemplate_audit_trail'))
# Removing M2M table for field tags on 'Team'
db.delete_table(db.shorten_name(u'main_team_tags'))
# Removing M2M table for field audit_trail on 'Team'
db.delete_table(db.shorten_name(u'main_team_audit_trail'))
# Removing M2M table for field tags on 'Project'
db.delete_table(db.shorten_name(u'main_project_tags'))
# Removing M2M table for field audit_trail on 'Project'
db.delete_table(db.shorten_name(u'main_project_audit_trail'))
# Removing M2M table for field tags on 'Permission'
db.delete_table(db.shorten_name(u'main_permission_tags'))
# Removing M2M table for field audit_trail on 'Permission'
db.delete_table(db.shorten_name(u'main_permission_audit_trail'))
# Removing M2M table for field tags on 'VariableData'
db.delete_table(db.shorten_name(u'main_variabledata_tags'))
# Removing M2M table for field audit_trail on 'VariableData'
db.delete_table(db.shorten_name(u'main_variabledata_audit_trail'))
# Removing M2M table for field tags on 'Organization'
db.delete_table(db.shorten_name(u'main_organization_tags'))
# Removing M2M table for field audit_trail on 'Organization'
db.delete_table(db.shorten_name(u'main_organization_audit_trail'))
# Deleting model 'Tag'
db.delete_table(u'main_tag')
# Deleting model 'AuditTrail'
db.delete_table(u'main_audittrail')
def backwards(self, orm):
# Deleting field 'Host.variables'
db.delete_column(u'main_host', 'variables')
# Deleting field 'Group.variables'
db.delete_column(u'main_group', 'variables')
# Deleting field 'JobTemplate.job_tags'
db.delete_column(u'main_jobtemplate', 'job_tags')
# Deleting field 'JobTemplate.host_config_key'
db.delete_column(u'main_jobtemplate', 'host_config_key')
# Deleting field 'Job.job_tags'
db.delete_column(u'main_job', 'job_tags')
# Deleting field 'Job.job_args'
db.delete_column(u'main_job', 'job_args')
# Deleting field 'Job.job_cwd'
db.delete_column(u'main_job', 'job_cwd')
# Deleting field 'Job.job_env'
db.delete_column(u'main_job', 'job_env')
# Deleting field 'JobHostSummary.failed'
db.delete_column(u'main_jobhostsummary', 'failed')
# Deleting field 'JobEvent.play'
db.delete_column(u'main_jobevent', 'play')
# Deleting field 'JobEvent.task'
db.delete_column(u'main_jobevent', 'task')
# Deleting field 'JobEvent.parent'
db.delete_column(u'main_jobevent', 'parent_id')
# Removing M2M table for field hosts on 'JobEvent'
db.delete_table(db.shorten_name(u'main_jobevent_hosts'))
# Adding model 'AuditTrail'
db.create_table(u'main_audittrail', (
('comment', self.gf('django.db.models.fields.TextField')()),
('modified_by', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['auth.User'], null=True, on_delete=models.SET_NULL, blank=True)),
('delta', self.gf('django.db.models.fields.TextField')()),
('tag', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['main.Tag'], null=True, on_delete=models.SET_NULL, blank=True)),
('detail', self.gf('django.db.models.fields.TextField')()),
('resource_type', self.gf('django.db.models.fields.CharField')(max_length=64)),
(u'id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
))
db.send_create_signal('main', ['AuditTrail'])
# Adding model 'Tag'
db.create_table(u'main_tag', (
(u'id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
('name', self.gf('django.db.models.fields.CharField')(max_length=512)),
))
db.send_create_signal('main', ['Tag'])
# Adding M2M table for field tags on 'Job'
m2m_table_name = db.shorten_name(u'main_job_tags')
db.create_table(m2m_table_name, (
('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)),
('job', models.ForeignKey(orm['main.job'], null=False)),
('tag', models.ForeignKey(orm['main.tag'], null=False))
))
db.create_unique(m2m_table_name, ['job_id', 'tag_id'])
# Adding M2M table for field audit_trail on 'Job'
m2m_table_name = db.shorten_name(u'main_job_audit_trail')
db.create_table(m2m_table_name, (
('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)),
('job', models.ForeignKey(orm['main.job'], null=False)),
('audittrail', models.ForeignKey(orm['main.audittrail'], null=False))
))
db.create_unique(m2m_table_name, ['job_id', 'audittrail_id'])
# Adding M2M table for field tags on 'Inventory'
m2m_table_name = db.shorten_name(u'main_inventory_tags')
db.create_table(m2m_table_name, (
('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)),
('inventory', models.ForeignKey(orm['main.inventory'], null=False)),
('tag', models.ForeignKey(orm['main.tag'], null=False))
))
db.create_unique(m2m_table_name, ['inventory_id', 'tag_id'])
# Adding M2M table for field audit_trail on 'Inventory'
m2m_table_name = db.shorten_name(u'main_inventory_audit_trail')
db.create_table(m2m_table_name, (
('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)),
('inventory', models.ForeignKey(orm['main.inventory'], null=False)),
('audittrail', models.ForeignKey(orm['main.audittrail'], null=False))
))
db.create_unique(m2m_table_name, ['inventory_id', 'audittrail_id'])
# Adding M2M table for field tags on 'Host'
m2m_table_name = db.shorten_name(u'main_host_tags')
db.create_table(m2m_table_name, (
('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)),
('host', models.ForeignKey(orm['main.host'], null=False)),
('tag', models.ForeignKey(orm['main.tag'], null=False))
))
db.create_unique(m2m_table_name, ['host_id', 'tag_id'])
# Adding M2M table for field audit_trail on 'Host'
m2m_table_name = db.shorten_name(u'main_host_audit_trail')
db.create_table(m2m_table_name, (
('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)),
('host', models.ForeignKey(orm['main.host'], null=False)),
('audittrail', models.ForeignKey(orm['main.audittrail'], null=False))
))
db.create_unique(m2m_table_name, ['host_id', 'audittrail_id'])
# Adding M2M table for field tags on 'Group'
m2m_table_name = db.shorten_name(u'main_group_tags')
db.create_table(m2m_table_name, (
('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)),
('group', models.ForeignKey(orm['main.group'], null=False)),
('tag', models.ForeignKey(orm['main.tag'], null=False))
))
db.create_unique(m2m_table_name, ['group_id', 'tag_id'])
# Adding M2M table for field audit_trail on 'Group'
m2m_table_name = db.shorten_name(u'main_group_audit_trail')
db.create_table(m2m_table_name, (
('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)),
('group', models.ForeignKey(orm['main.group'], null=False)),
('audittrail', models.ForeignKey(orm['main.audittrail'], null=False))
))
db.create_unique(m2m_table_name, ['group_id', 'audittrail_id'])
# Adding M2M table for field audit_trail on 'Credential'
m2m_table_name = db.shorten_name(u'main_credential_audit_trail')
db.create_table(m2m_table_name, (
('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)),
('credential', models.ForeignKey(orm['main.credential'], null=False)),
('audittrail', models.ForeignKey(orm['main.audittrail'], null=False))
))
db.create_unique(m2m_table_name, ['credential_id', 'audittrail_id'])
# Adding M2M table for field tags on 'Credential'
m2m_table_name = db.shorten_name(u'main_credential_tags')
db.create_table(m2m_table_name, (
('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)),
('credential', models.ForeignKey(orm['main.credential'], null=False)),
('tag', models.ForeignKey(orm['main.tag'], null=False))
))
db.create_unique(m2m_table_name, ['credential_id', 'tag_id'])
# Adding M2M table for field tags on 'JobTemplate'
m2m_table_name = db.shorten_name(u'main_jobtemplate_tags')
db.create_table(m2m_table_name, (
('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)),
('jobtemplate', models.ForeignKey(orm['main.jobtemplate'], null=False)),
('tag', models.ForeignKey(orm['main.tag'], null=False))
))
db.create_unique(m2m_table_name, ['jobtemplate_id', 'tag_id'])
# Adding M2M table for field audit_trail on 'JobTemplate'
m2m_table_name = db.shorten_name(u'main_jobtemplate_audit_trail')
db.create_table(m2m_table_name, (
('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)),
('jobtemplate', models.ForeignKey(orm['main.jobtemplate'], null=False)),
('audittrail', models.ForeignKey(orm['main.audittrail'], null=False))
))
db.create_unique(m2m_table_name, ['jobtemplate_id', 'audittrail_id'])
# Adding M2M table for field tags on 'Team'
m2m_table_name = db.shorten_name(u'main_team_tags')
db.create_table(m2m_table_name, (
('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)),
('team', models.ForeignKey(orm['main.team'], null=False)),
('tag', models.ForeignKey(orm['main.tag'], null=False))
))
db.create_unique(m2m_table_name, ['team_id', 'tag_id'])
# Adding M2M table for field audit_trail on 'Team'
m2m_table_name = db.shorten_name(u'main_team_audit_trail')
db.create_table(m2m_table_name, (
('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)),
('team', models.ForeignKey(orm['main.team'], null=False)),
('audittrail', models.ForeignKey(orm['main.audittrail'], null=False))
))
db.create_unique(m2m_table_name, ['team_id', 'audittrail_id'])
# Adding M2M table for field tags on 'Project'
m2m_table_name = db.shorten_name(u'main_project_tags')
db.create_table(m2m_table_name, (
('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)),
('project', models.ForeignKey(orm[u'main.project'], null=False)),
('tag', models.ForeignKey(orm['main.tag'], null=False))
))
db.create_unique(m2m_table_name, ['project_id', 'tag_id'])
# Adding M2M table for field audit_trail on 'Project'
m2m_table_name = db.shorten_name(u'main_project_audit_trail')
db.create_table(m2m_table_name, (
('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)),
('project', models.ForeignKey(orm[u'main.project'], null=False)),
('audittrail', models.ForeignKey(orm['main.audittrail'], null=False))
))
db.create_unique(m2m_table_name, ['project_id', 'audittrail_id'])
# Adding M2M table for field tags on 'Permission'
m2m_table_name = db.shorten_name(u'main_permission_tags')
db.create_table(m2m_table_name, (
('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)),
('permission', models.ForeignKey(orm['main.permission'], null=False)),
('tag', models.ForeignKey(orm['main.tag'], null=False))
))
db.create_unique(m2m_table_name, ['permission_id', 'tag_id'])
# Adding M2M table for field audit_trail on 'Permission'
m2m_table_name = db.shorten_name(u'main_permission_audit_trail')
db.create_table(m2m_table_name, (
('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)),
('permission', models.ForeignKey(orm['main.permission'], null=False)),
('audittrail', models.ForeignKey(orm['main.audittrail'], null=False))
))
db.create_unique(m2m_table_name, ['permission_id', 'audittrail_id'])
# Adding M2M table for field tags on 'VariableData'
m2m_table_name = db.shorten_name(u'main_variabledata_tags')
db.create_table(m2m_table_name, (
('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)),
('variabledata', models.ForeignKey(orm['main.variabledata'], null=False)),
('tag', models.ForeignKey(orm['main.tag'], null=False))
))
db.create_unique(m2m_table_name, ['variabledata_id', 'tag_id'])
# Adding M2M table for field audit_trail on 'VariableData'
m2m_table_name = db.shorten_name(u'main_variabledata_audit_trail')
db.create_table(m2m_table_name, (
('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)),
('variabledata', models.ForeignKey(orm['main.variabledata'], null=False)),
('audittrail', models.ForeignKey(orm['main.audittrail'], null=False))
))
db.create_unique(m2m_table_name, ['variabledata_id', 'audittrail_id'])
# Adding M2M table for field tags on 'Organization'
m2m_table_name = db.shorten_name(u'main_organization_tags')
db.create_table(m2m_table_name, (
('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)),
('organization', models.ForeignKey(orm['main.organization'], null=False)),
('tag', models.ForeignKey(orm['main.tag'], null=False))
))
db.create_unique(m2m_table_name, ['organization_id', 'tag_id'])
# Adding M2M table for field audit_trail on 'Organization'
m2m_table_name = db.shorten_name(u'main_organization_audit_trail')
db.create_table(m2m_table_name, (
('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)),
('organization', models.ForeignKey(orm['main.organization'], null=False)),
('audittrail', models.ForeignKey(orm['main.audittrail'], null=False))
))
db.create_unique(m2m_table_name, ['organization_id', 'audittrail_id'])
models = {
u'auth.group': {
'Meta': {'object_name': 'Group'},
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
},
u'auth.permission': {
'Meta': {'ordering': "(u'content_type__app_label', u'content_type__model', u'codename')", 'unique_together': "((u'content_type', u'codename'),)", 'object_name': 'Permission'},
'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['contenttypes.ContentType']"}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
},
u'auth.user': {
'Meta': {'object_name': 'User'},
'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
},
u'contenttypes.contenttype': {
'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
},
'main.credential': {
'Meta': {'object_name': 'Credential'},
'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'created_by': ('django.db.models.fields.related.ForeignKey', [], {'related_name': '"{\'class\': \'credential\', \'app_label\': \'main\'}(class)s_created"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
'description': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '512'}),
'ssh_key_data': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
'ssh_key_unlock': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
'ssh_password': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
'ssh_username': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
'sudo_password': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
'sudo_username': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
'team': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'credentials'", 'on_delete': 'models.SET_NULL', 'default': 'None', 'to': "orm['main.Team']", 'blank': 'True', 'null': 'True'}),
'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'credentials'", 'on_delete': 'models.SET_NULL', 'default': 'None', 'to': u"orm['auth.User']", 'blank': 'True', 'null': 'True'})
},
'main.group': {
'Meta': {'unique_together': "(('name', 'inventory'),)", 'object_name': 'Group'},
'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'created_by': ('django.db.models.fields.related.ForeignKey', [], {'related_name': '"{\'class\': \'group\', \'app_label\': \'main\'}(class)s_created"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
'description': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
'hosts': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'groups'", 'blank': 'True', 'to': "orm['main.Host']"}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'inventory': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'groups'", 'to': "orm['main.Inventory']"}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '512'}),
'parents': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'children'", 'blank': 'True', 'to': "orm['main.Group']"}),
'variables': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True', 'null': 'True'}),
'variable_data': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'group'", 'unique': 'True', 'on_delete': 'models.SET_NULL', 'default': 'None', 'to': "orm['main.VariableData']", 'blank': 'True', 'null': 'True'})
},
'main.host': {
'Meta': {'unique_together': "(('name', 'inventory'),)", 'object_name': 'Host'},
'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'created_by': ('django.db.models.fields.related.ForeignKey', [], {'related_name': '"{\'class\': \'host\', \'app_label\': \'main\'}(class)s_created"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
'description': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'inventory': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'hosts'", 'to': "orm['main.Inventory']"}),
'last_job': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'hosts_as_last_job+'", 'on_delete': 'models.SET_NULL', 'default': 'None', 'to': "orm['main.Job']", 'blank': 'True', 'null': 'True'}),
'last_job_host_summary': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'hosts_as_last_job_summary+'", 'on_delete': 'models.SET_NULL', 'default': 'None', 'to': u"orm['main.JobHostSummary']", 'blank': 'True', 'null': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '512'}),
'variables': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True', 'null': 'True'}),
'variable_data': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'host'", 'unique': 'True', 'on_delete': 'models.SET_NULL', 'default': 'None', 'to': "orm['main.VariableData']", 'blank': 'True', 'null': 'True'})
},
'main.inventory': {
'Meta': {'unique_together': "(('name', 'organization'),)", 'object_name': 'Inventory'},
'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'created_by': ('django.db.models.fields.related.ForeignKey', [], {'related_name': '"{\'class\': \'inventory\', \'app_label\': \'main\'}(class)s_created"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
'description': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '512'}),
'organization': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'inventories'", 'to': "orm['main.Organization']"})
},
'main.job': {
'Meta': {'object_name': 'Job'},
'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'cancel_flag': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'celery_task_id': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '100', 'blank': 'True'}),
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'created_by': ('django.db.models.fields.related.ForeignKey', [], {'related_name': '"{\'class\': \'job\', \'app_label\': \'main\'}(class)s_created"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
'credential': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'jobs'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': "orm['main.Credential']"}),
'description': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
'extra_vars': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
'failed': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'forks': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0', 'blank': 'True'}),
'hosts': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'jobs'", 'blank': 'True', 'through': u"orm['main.JobHostSummary']", 'to': "orm['main.Host']"}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'inventory': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'jobs'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': "orm['main.Inventory']"}),
'job_args': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'null': 'True', 'blank': 'True'}),
'job_cwd': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'null': 'True', 'blank': 'True'}),
'job_env': ('jsonfield.fields.JSONField', [], {'default': '{}', 'null': 'True', 'blank': 'True'}),
'job_tags': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'null': 'True', 'blank': 'True'}),
'job_template': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'jobs'", 'on_delete': 'models.SET_NULL', 'default': 'None', 'to': "orm['main.JobTemplate']", 'blank': 'True', 'null': 'True'}),
'job_type': ('django.db.models.fields.CharField', [], {'max_length': '64'}),
'limit': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '512'}),
'playbook': ('django.db.models.fields.CharField', [], {'max_length': '1024'}),
'project': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'jobs'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['main.Project']"}),
'result_stdout': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
'result_traceback': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
'status': ('django.db.models.fields.CharField', [], {'default': "'new'", 'max_length': '20'}),
'verbosity': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0', 'blank': 'True'})
},
'main.jobevent': {
'Meta': {'ordering': "('pk',)", 'object_name': 'JobEvent'},
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'event': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'event_data': ('jsonfield.fields.JSONField', [], {'default': '{}', 'blank': 'True'}),
'failed': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'host': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'job_events'", 'on_delete': 'models.SET_NULL', 'default': 'None', 'to': "orm['main.Host']", 'blank': 'True', 'null': 'True'}),
'hosts': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'job_events'", 'blank': 'True', 'to': "orm['main.Host']"}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'job': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'job_events'", 'to': "orm['main.Job']"}),
'parent': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'children'", 'on_delete': 'models.SET_NULL', 'default': 'None', 'to': "orm['main.JobEvent']", 'blank': 'True', 'null': 'True'}),
'play': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True', 'null': 'True'}),
'task': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True', 'null': 'True'})
},
u'main.jobhostsummary': {
'Meta': {'ordering': "('-pk',)", 'unique_together': "[('job', 'host')]", 'object_name': 'JobHostSummary'},
'changed': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
'dark': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
'failed': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'failures': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
'host': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'job_host_summaries'", 'to': "orm['main.Host']"}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'job': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'job_host_summaries'", 'to': "orm['main.Job']"}),
'ok': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
'processed': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
'skipped': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'})
},
'main.jobtemplate': {
'Meta': {'object_name': 'JobTemplate'},
'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'created_by': ('django.db.models.fields.related.ForeignKey', [], {'related_name': '"{\'class\': \'jobtemplate\', \'app_label\': \'main\'}(class)s_created"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
'credential': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'job_templates'", 'on_delete': 'models.SET_NULL', 'default': 'None', 'to': "orm['main.Credential']", 'blank': 'True', 'null': 'True'}),
'description': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
'extra_vars': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
'forks': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0', 'blank': 'True'}),
'host_config_key': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True', 'null': 'True'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'inventory': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'job_templates'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': "orm['main.Inventory']"}),
'job_tags': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'null': 'True', 'blank': 'True'}),
'job_type': ('django.db.models.fields.CharField', [], {'max_length': '64'}),
'limit': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '512'}),
'playbook': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024'}),
'project': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'job_templates'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['main.Project']"}),
'verbosity': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0', 'blank': 'True'})
},
'main.organization': {
'Meta': {'object_name': 'Organization'},
'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'admins': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'admin_of_organizations'", 'blank': 'True', 'to': u"orm['auth.User']"}),
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'created_by': ('django.db.models.fields.related.ForeignKey', [], {'related_name': '"{\'class\': \'organization\', \'app_label\': \'main\'}(class)s_created"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
'description': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '512'}),
'projects': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'organizations'", 'blank': 'True', 'to': u"orm['main.Project']"}),
'users': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'organizations'", 'blank': 'True', 'to': u"orm['auth.User']"})
},
'main.permission': {
'Meta': {'object_name': 'Permission'},
'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'created_by': ('django.db.models.fields.related.ForeignKey', [], {'related_name': '"{\'class\': \'permission\', \'app_label\': \'main\'}(class)s_created"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
'description': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'inventory': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'permissions'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': "orm['main.Inventory']"}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '512'}),
'permission_type': ('django.db.models.fields.CharField', [], {'max_length': '64'}),
'project': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'permissions'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['main.Project']"}),
'team': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'permissions'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': "orm['main.Team']"}),
'user': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'permissions'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"})
},
u'main.project': {
'Meta': {'object_name': 'Project'},
'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'created_by': ('django.db.models.fields.related.ForeignKey', [], {'related_name': '"{\'class\': \'project\', \'app_label\': u\'main\'}(class)s_created"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
'description': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'local_path': ('django.db.models.fields.CharField', [], {'max_length': '1024'}),
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '512'})
},
'main.team': {
'Meta': {'object_name': 'Team'},
'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'created_by': ('django.db.models.fields.related.ForeignKey', [], {'related_name': '"{\'class\': \'team\', \'app_label\': \'main\'}(class)s_created"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
'description': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '512'}),
'organization': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'teams'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': "orm['main.Organization']"}),
'projects': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'teams'", 'blank': 'True', 'to': u"orm['main.Project']"}),
'users': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'teams'", 'blank': 'True', 'to': u"orm['auth.User']"})
},
'main.variabledata': {
'Meta': {'object_name': 'VariableData'},
'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'created_by': ('django.db.models.fields.related.ForeignKey', [], {'related_name': '"{\'class\': \'variabledata\', \'app_label\': \'main\'}(class)s_created"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
'data': ('django.db.models.fields.TextField', [], {'default': "''"}),
'description': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '512'})
},
u'taggit.tag': {
'Meta': {'object_name': 'Tag'},
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '100'}),
'slug': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '100'})
},
u'taggit.taggeditem': {
'Meta': {'object_name': 'TaggedItem'},
'content_type': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "u'taggit_taggeditem_tagged_items'", 'to': u"orm['contenttypes.ContentType']"}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'object_id': ('django.db.models.fields.IntegerField', [], {'db_index': 'True'}),
'tag': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "u'taggit_taggeditem_items'", 'to': u"orm['taggit.Tag']"})
}
}
complete_apps = ['main']

View File

@@ -1,387 +0,0 @@
# -*- coding: utf-8 -*-
import datetime
from south.db import db
from south.v2 import DataMigration
from django.db import models
class Migration(DataMigration):
'''
Data migration for AWX 1.2-b2 release.
- Update variables from VariableData.data for Host and Group models.
- Update new char/text field values to be empty string if they are null.
- Update failed flag for existing JobHostSummary models.
- Update parent field for existing JobEvent models.
- Update hosts for existing JobEvent models.
'''
def forwards(self, orm):
for host in orm.Host.objects.all():
if host.variable_data:
host.variables = host.variable_data.data
else:
host.variables = ''
host.save()
for group in orm.Group.objects.all():
if group.variable_data:
group.variables = group.variable_data.data
else:
group.variables = ''
group.save()
for job_template in orm.JobTemplate.objects.all():
changed = False
if job_template.host_config_key is None:
job_template.host_config_key = ''
changed = True
if job_template.job_tags is None:
job_template.job_tags = ''
changed = True
if changed:
job_template.save()
for job in orm.Job.objects.all():
changed = False
if job.job_tags is None:
job.job_tags = ''
changed = True
if job.job_args is None:
job.job_args = ''
changed = True
if job.job_cwd is None:
job.job_cwd = ''
changed = True
if job.job_env is None:
job.job_env = ''
changed = True
if changed:
job.save()
for job_host_summary in orm.JobHostSummary.objects.all():
if job_host_summary.failures or job_host_summary.dark:
job_host_summary.failed = True
job_host_summary.save()
for job_event in orm.JobEvent.objects.order_by('pk'):
job_event.play = job_event.event_data.get('play', '')
job_event.task = job_event.event_data.get('task', '')
job_event.parent = None
parent_events = set()
if job_event.event in ('playbook_on_play_start',
'playbook_on_stats',
'playbook_on_vars_prompt'):
parent_events.add('playbook_on_start')
elif job_event.event in ('playbook_on_notify', 'playbook_on_setup',
'playbook_on_task_start',
'playbook_on_no_hosts_matched',
'playbook_on_no_hosts_remaining',
'playbook_on_import_for_host',
'playbook_on_not_import_for_host'):
parent_events.add('playbook_on_play_start')
elif job_event.event.startswith('runner_on_'):
parent_events.add('playbook_on_setup')
parent_events.add('playbook_on_task_start')
if parent_events:
try:
qs = job_event.job.job_events.all()
qs = qs.filter(pk__lt=job_event.pk,
event__in=parent_events)
job_event.parent = qs.order_by('-pk')[0]
except IndexError:
pass
job_event.save()
def update_job_event_hosts(orm, job_event, extra_hosts=None):
extra_hosts = extra_hosts or []
hostnames = set()
if job_event.event_data.get('host', ''):
hostnames.add(job_event.event_data['host'])
if job_event.event == 'playbook_on_stats':
try:
for v in job_event.event_data.values():
hostnames.update(v.keys())
except AttributeError:
pass
if job_event.host:
job_event.hosts.add(job_event.host)
for hostname in hostnames:
try:
host = job_event.job.inventory.hosts.get(name=hostname)
except orm.Host.DoesNotExist:
continue
job_event.hosts.add(host)
for host in extra_hosts:
job_event.hosts.add(host)
if job_event.parent:
update_job_event_hosts(orm, job_event.parent,
job_event.hosts.all())
for job_event in orm.JobEvent.objects.all():
update_job_event_hosts(orm, job_event)
def backwards(self, orm):
for host in orm.Host.objects.all():
if host.variable_data:
variable_data = host.variable_data
variable_data.data = host.variables
variable_data.save()
else:
host.variable_data = orm.VariableData.objects.create(data=host.variables)
host.save()
for group in orm.Group.objects.all():
if group.variable_data:
variable_data = group.variable_data
variable_data.data = group.variables
variable_data.save()
else:
group.variable_data = orm.VariableData.objects.create(data=group.variables)
group.save()
models = {
u'auth.group': {
'Meta': {'object_name': 'Group'},
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
},
u'auth.permission': {
'Meta': {'ordering': "(u'content_type__app_label', u'content_type__model', u'codename')", 'unique_together': "((u'content_type', u'codename'),)", 'object_name': 'Permission'},
'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['contenttypes.ContentType']"}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
},
u'auth.user': {
'Meta': {'object_name': 'User'},
'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
},
u'contenttypes.contenttype': {
'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
},
'main.credential': {
'Meta': {'object_name': 'Credential'},
'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'created_by': ('django.db.models.fields.related.ForeignKey', [], {'related_name': '"{\'class\': \'credential\', \'app_label\': \'main\'}(class)s_created"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
'description': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '512'}),
'ssh_key_data': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
'ssh_key_unlock': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
'ssh_password': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
'ssh_username': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
'sudo_password': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
'sudo_username': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
'team': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'credentials'", 'on_delete': 'models.SET_NULL', 'default': 'None', 'to': "orm['main.Team']", 'blank': 'True', 'null': 'True'}),
'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'credentials'", 'on_delete': 'models.SET_NULL', 'default': 'None', 'to': u"orm['auth.User']", 'blank': 'True', 'null': 'True'})
},
'main.group': {
'Meta': {'unique_together': "(('name', 'inventory'),)", 'object_name': 'Group'},
'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'created_by': ('django.db.models.fields.related.ForeignKey', [], {'related_name': '"{\'class\': \'group\', \'app_label\': \'main\'}(class)s_created"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
'description': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
'hosts': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'groups'", 'blank': 'True', 'to': "orm['main.Host']"}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'inventory': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'groups'", 'to': "orm['main.Inventory']"}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '512'}),
'parents': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'children'", 'blank': 'True', 'to': "orm['main.Group']"}),
'variable_data': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'group'", 'unique': 'True', 'on_delete': 'models.SET_NULL', 'default': 'None', 'to': "orm['main.VariableData']", 'blank': 'True', 'null': 'True'}),
'variables': ('django.db.models.fields.TextField', [], {'default': "''", 'null': 'True', 'blank': 'True'})
},
'main.host': {
'Meta': {'unique_together': "(('name', 'inventory'),)", 'object_name': 'Host'},
'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'created_by': ('django.db.models.fields.related.ForeignKey', [], {'related_name': '"{\'class\': \'host\', \'app_label\': \'main\'}(class)s_created"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
'description': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'inventory': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'hosts'", 'to': "orm['main.Inventory']"}),
'last_job': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'hosts_as_last_job+'", 'on_delete': 'models.SET_NULL', 'default': 'None', 'to': "orm['main.Job']", 'blank': 'True', 'null': 'True'}),
'last_job_host_summary': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'hosts_as_last_job_summary+'", 'on_delete': 'models.SET_NULL', 'default': 'None', 'to': u"orm['main.JobHostSummary']", 'blank': 'True', 'null': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '512'}),
'variable_data': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'host'", 'unique': 'True', 'on_delete': 'models.SET_NULL', 'default': 'None', 'to': "orm['main.VariableData']", 'blank': 'True', 'null': 'True'}),
'variables': ('django.db.models.fields.TextField', [], {'default': "''", 'null': 'True', 'blank': 'True'})
},
'main.inventory': {
'Meta': {'unique_together': "(('name', 'organization'),)", 'object_name': 'Inventory'},
'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'created_by': ('django.db.models.fields.related.ForeignKey', [], {'related_name': '"{\'class\': \'inventory\', \'app_label\': \'main\'}(class)s_created"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
'description': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '512'}),
'organization': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'inventories'", 'to': "orm['main.Organization']"})
},
'main.job': {
'Meta': {'object_name': 'Job'},
'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'cancel_flag': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'celery_task_id': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '100', 'blank': 'True'}),
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'created_by': ('django.db.models.fields.related.ForeignKey', [], {'related_name': '"{\'class\': \'job\', \'app_label\': \'main\'}(class)s_created"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
'credential': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'jobs'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': "orm['main.Credential']"}),
'description': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
'extra_vars': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
'failed': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'forks': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0', 'blank': 'True'}),
'hosts': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'jobs'", 'blank': 'True', 'through': u"orm['main.JobHostSummary']", 'to': "orm['main.Host']"}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'inventory': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'jobs'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': "orm['main.Inventory']"}),
'job_args': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'null': 'True', 'blank': 'True'}),
'job_cwd': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'null': 'True', 'blank': 'True'}),
'job_env': ('jsonfield.fields.JSONField', [], {'default': '{}', 'null': 'True', 'blank': 'True'}),
'job_tags': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'null': 'True', 'blank': 'True'}),
'job_template': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'jobs'", 'on_delete': 'models.SET_NULL', 'default': 'None', 'to': "orm['main.JobTemplate']", 'blank': 'True', 'null': 'True'}),
'job_type': ('django.db.models.fields.CharField', [], {'max_length': '64'}),
'limit': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '512'}),
'playbook': ('django.db.models.fields.CharField', [], {'max_length': '1024'}),
'project': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'jobs'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['main.Project']"}),
'result_stdout': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
'result_traceback': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
'status': ('django.db.models.fields.CharField', [], {'default': "'new'", 'max_length': '20'}),
'verbosity': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0', 'blank': 'True'})
},
'main.jobevent': {
'Meta': {'ordering': "('pk',)", 'object_name': 'JobEvent'},
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'event': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'event_data': ('jsonfield.fields.JSONField', [], {'default': '{}', 'blank': 'True'}),
'failed': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'host': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'job_events'", 'on_delete': 'models.SET_NULL', 'default': 'None', 'to': "orm['main.Host']", 'blank': 'True', 'null': 'True'}),
'hosts': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'job_events'", 'blank': 'True', 'to': "orm['main.Host']"}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'job': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'job_events'", 'to': "orm['main.Job']"}),
'parent': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'children'", 'on_delete': 'models.SET_NULL', 'default': 'None', 'to': "orm['main.JobEvent']", 'blank': 'True', 'null': 'True'}),
'play': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'null': 'True', 'blank': 'True'}),
'task': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'null': 'True', 'blank': 'True'})
},
u'main.jobhostsummary': {
'Meta': {'ordering': "('-pk',)", 'unique_together': "[('job', 'host')]", 'object_name': 'JobHostSummary'},
'changed': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
'dark': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
'failed': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'failures': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
'host': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'job_host_summaries'", 'to': "orm['main.Host']"}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'job': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'job_host_summaries'", 'to': "orm['main.Job']"}),
'ok': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
'processed': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
'skipped': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'})
},
'main.jobtemplate': {
'Meta': {'object_name': 'JobTemplate'},
'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'created_by': ('django.db.models.fields.related.ForeignKey', [], {'related_name': '"{\'class\': \'jobtemplate\', \'app_label\': \'main\'}(class)s_created"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
'credential': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'job_templates'", 'on_delete': 'models.SET_NULL', 'default': 'None', 'to': "orm['main.Credential']", 'blank': 'True', 'null': 'True'}),
'description': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
'extra_vars': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
'forks': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0', 'blank': 'True'}),
'host_config_key': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'null': 'True', 'blank': 'True'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'inventory': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'job_templates'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': "orm['main.Inventory']"}),
'job_tags': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'null': 'True', 'blank': 'True'}),
'job_type': ('django.db.models.fields.CharField', [], {'max_length': '64'}),
'limit': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '512'}),
'playbook': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024'}),
'project': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'job_templates'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['main.Project']"}),
'verbosity': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0', 'blank': 'True'})
},
'main.organization': {
'Meta': {'object_name': 'Organization'},
'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'admins': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'admin_of_organizations'", 'blank': 'True', 'to': u"orm['auth.User']"}),
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'created_by': ('django.db.models.fields.related.ForeignKey', [], {'related_name': '"{\'class\': \'organization\', \'app_label\': \'main\'}(class)s_created"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
'description': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '512'}),
'projects': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'organizations'", 'blank': 'True', 'to': u"orm['main.Project']"}),
'users': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'organizations'", 'blank': 'True', 'to': u"orm['auth.User']"})
},
'main.permission': {
'Meta': {'object_name': 'Permission'},
'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'created_by': ('django.db.models.fields.related.ForeignKey', [], {'related_name': '"{\'class\': \'permission\', \'app_label\': \'main\'}(class)s_created"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
'description': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'inventory': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'permissions'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': "orm['main.Inventory']"}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '512'}),
'permission_type': ('django.db.models.fields.CharField', [], {'max_length': '64'}),
'project': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'permissions'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['main.Project']"}),
'team': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'permissions'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': "orm['main.Team']"}),
'user': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'permissions'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"})
},
u'main.project': {
'Meta': {'object_name': 'Project'},
'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'created_by': ('django.db.models.fields.related.ForeignKey', [], {'related_name': '"{\'class\': \'project\', \'app_label\': u\'main\'}(class)s_created"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
'description': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'local_path': ('django.db.models.fields.CharField', [], {'max_length': '1024'}),
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '512'})
},
'main.team': {
'Meta': {'object_name': 'Team'},
'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'created_by': ('django.db.models.fields.related.ForeignKey', [], {'related_name': '"{\'class\': \'team\', \'app_label\': \'main\'}(class)s_created"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
'description': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '512'}),
'organization': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'teams'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': "orm['main.Organization']"}),
'projects': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'teams'", 'blank': 'True', 'to': u"orm['main.Project']"}),
'users': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'teams'", 'blank': 'True', 'to': u"orm['auth.User']"})
},
'main.variabledata': {
'Meta': {'object_name': 'VariableData'},
'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'created_by': ('django.db.models.fields.related.ForeignKey', [], {'related_name': '"{\'class\': \'variabledata\', \'app_label\': \'main\'}(class)s_created"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
'data': ('django.db.models.fields.TextField', [], {'default': "''"}),
'description': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '512'})
},
u'taggit.tag': {
'Meta': {'object_name': 'Tag'},
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '100'}),
'slug': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '100'})
},
u'taggit.taggeditem': {
'Meta': {'object_name': 'TaggedItem'},
'content_type': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "u'taggit_taggeditem_tagged_items'", 'to': u"orm['contenttypes.ContentType']"}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'object_id': ('django.db.models.fields.IntegerField', [], {'db_index': 'True'}),
'tag': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "u'taggit_taggeditem_items'", 'to': u"orm['taggit.Tag']"})
}
}
complete_apps = ['main']
symmetrical = True

View File

@@ -1,343 +0,0 @@
# -*- coding: utf-8 -*-
import datetime
from south.db import db
from south.v2 import SchemaMigration
from django.db import models
class Migration(SchemaMigration):
'''
Schema migration for AWX 1.2-b2 release.
- Remove variable_data field on Host and Group models.
- Remove VariableData model.
- Remove null=True on new char fields previously added.
NOTE: This migration has been manually edited!
'''
def forwards(self, orm):
# Changing field 'Job.job_cwd'
db.alter_column(u'main_job', 'job_cwd', self.gf('django.db.models.fields.CharField')(max_length=1024))
# Changing field 'Job.job_tags'
db.alter_column(u'main_job', 'job_tags', self.gf('django.db.models.fields.CharField')(max_length=1024))
# Changing field 'Job.job_env'
db.alter_column(u'main_job', 'job_env', self.gf('jsonfield.fields.JSONField')())
# Changing field 'Job.job_args'
db.alter_column(u'main_job', 'job_args', self.gf('django.db.models.fields.CharField')(max_length=1024))
# Deleting field 'Host.variable_data'
db.delete_column(u'main_host', 'variable_data_id')
# Changing field 'Host.variables'
db.alter_column(u'main_host', 'variables', self.gf('django.db.models.fields.TextField')())
# Deleting field 'Group.variable_data'
db.delete_column(u'main_group', 'variable_data_id')
# Changing field 'Group.variables'
db.alter_column(u'main_group', 'variables', self.gf('django.db.models.fields.TextField')())
# Changing field 'JobTemplate.job_tags'
db.alter_column(u'main_jobtemplate', 'job_tags', self.gf('django.db.models.fields.CharField')(max_length=1024))
# Changing field 'JobTemplate.host_config_key'
db.alter_column(u'main_jobtemplate', 'host_config_key', self.gf('django.db.models.fields.CharField')(max_length=1024))
# Changing field 'JobEvent.play'
db.alter_column(u'main_jobevent', 'play', self.gf('django.db.models.fields.CharField')(max_length=1024))
# Changing field 'JobEvent.task'
db.alter_column(u'main_jobevent', 'task', self.gf('django.db.models.fields.CharField')(max_length=1024))
# Deleting model 'VariableData'
db.delete_table(u'main_variabledata')
def backwards(self, orm):
# Adding model 'VariableData'
db.create_table(u'main_variabledata', (
('description', self.gf('django.db.models.fields.TextField')(default='', blank=True)),
('created', self.gf('django.db.models.fields.DateTimeField')(auto_now_add=True, blank=True)),
('active', self.gf('django.db.models.fields.BooleanField')(default=True)),
('data', self.gf('django.db.models.fields.TextField')(default='')),
(u'id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
('created_by', self.gf('django.db.models.fields.related.ForeignKey')(related_name="{'class': 'variabledata', 'app_label': 'main'}(class)s_created", null=True, on_delete=models.SET_NULL, to=orm['auth.User'])),
('name', self.gf('django.db.models.fields.CharField')(max_length=512)),
))
db.send_create_signal('main', ['VariableData'])
# Changing field 'Job.job_cwd'
db.alter_column(u'main_job', 'job_cwd', self.gf('django.db.models.fields.CharField')(max_length=1024, null=True))
# Changing field 'Job.job_tags'
db.alter_column(u'main_job', 'job_tags', self.gf('django.db.models.fields.CharField')(max_length=1024, null=True))
# Changing field 'Job.job_env'
db.alter_column(u'main_job', 'job_env', self.gf('jsonfield.fields.JSONField')(null=True))
# Changing field 'Job.job_args'
db.alter_column(u'main_job', 'job_args', self.gf('django.db.models.fields.CharField')(max_length=1024, null=True))
# Adding field 'Host.variable_data'
db.add_column(u'main_host', 'variable_data',
self.gf('django.db.models.fields.related.OneToOneField')(related_name='host', null=True, on_delete=models.SET_NULL, default=None, to=orm['main.VariableData'], blank=True, unique=True),
keep_default=False)
# Changing field 'Host.variables'
db.alter_column(u'main_host', 'variables', self.gf('django.db.models.fields.TextField')(null=True))
# Adding field 'Group.variable_data'
db.add_column(u'main_group', 'variable_data',
self.gf('django.db.models.fields.related.OneToOneField')(related_name='group', null=True, on_delete=models.SET_NULL, default=None, to=orm['main.VariableData'], blank=True, unique=True),
keep_default=False)
# Changing field 'Group.variables'
db.alter_column(u'main_group', 'variables', self.gf('django.db.models.fields.TextField')(null=True))
# Changing field 'JobTemplate.job_tags'
db.alter_column(u'main_jobtemplate', 'job_tags', self.gf('django.db.models.fields.CharField')(max_length=1024, null=True))
# Changing field 'JobTemplate.host_config_key'
db.alter_column(u'main_jobtemplate', 'host_config_key', self.gf('django.db.models.fields.CharField')(max_length=1024, null=True))
# Changing field 'JobEvent.play'
db.alter_column(u'main_jobevent', 'play', self.gf('django.db.models.fields.CharField')(max_length=1024, null=True))
# Changing field 'JobEvent.task'
db.alter_column(u'main_jobevent', 'task', self.gf('django.db.models.fields.CharField')(max_length=1024, null=True))
models = {
u'auth.group': {
'Meta': {'object_name': 'Group'},
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
},
u'auth.permission': {
'Meta': {'ordering': "(u'content_type__app_label', u'content_type__model', u'codename')", 'unique_together': "((u'content_type', u'codename'),)", 'object_name': 'Permission'},
'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['contenttypes.ContentType']"}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
},
u'auth.user': {
'Meta': {'object_name': 'User'},
'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
},
u'contenttypes.contenttype': {
'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
},
'main.credential': {
'Meta': {'object_name': 'Credential'},
'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'created_by': ('django.db.models.fields.related.ForeignKey', [], {'related_name': '"{\'class\': \'credential\', \'app_label\': \'main\'}(class)s_created"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
'description': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '512'}),
'ssh_key_data': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
'ssh_key_unlock': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
'ssh_password': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
'ssh_username': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
'sudo_password': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
'sudo_username': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
'team': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'credentials'", 'on_delete': 'models.SET_NULL', 'default': 'None', 'to': "orm['main.Team']", 'blank': 'True', 'null': 'True'}),
'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'credentials'", 'on_delete': 'models.SET_NULL', 'default': 'None', 'to': u"orm['auth.User']", 'blank': 'True', 'null': 'True'})
},
'main.group': {
'Meta': {'unique_together': "(('name', 'inventory'),)", 'object_name': 'Group'},
'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'created_by': ('django.db.models.fields.related.ForeignKey', [], {'related_name': '"{\'class\': \'group\', \'app_label\': \'main\'}(class)s_created"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
'description': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
'hosts': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'groups'", 'blank': 'True', 'to': "orm['main.Host']"}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'inventory': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'groups'", 'to': "orm['main.Inventory']"}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '512'}),
'parents': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'children'", 'blank': 'True', 'to': "orm['main.Group']"}),
'variables': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'})
},
'main.host': {
'Meta': {'unique_together': "(('name', 'inventory'),)", 'object_name': 'Host'},
'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'created_by': ('django.db.models.fields.related.ForeignKey', [], {'related_name': '"{\'class\': \'host\', \'app_label\': \'main\'}(class)s_created"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
'description': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'inventory': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'hosts'", 'to': "orm['main.Inventory']"}),
'last_job': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'hosts_as_last_job+'", 'on_delete': 'models.SET_NULL', 'default': 'None', 'to': "orm['main.Job']", 'blank': 'True', 'null': 'True'}),
'last_job_host_summary': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'hosts_as_last_job_summary+'", 'on_delete': 'models.SET_NULL', 'default': 'None', 'to': u"orm['main.JobHostSummary']", 'blank': 'True', 'null': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '512'}),
'variables': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'})
},
'main.inventory': {
'Meta': {'unique_together': "(('name', 'organization'),)", 'object_name': 'Inventory'},
'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'created_by': ('django.db.models.fields.related.ForeignKey', [], {'related_name': '"{\'class\': \'inventory\', \'app_label\': \'main\'}(class)s_created"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
'description': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '512'}),
'organization': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'inventories'", 'to': "orm['main.Organization']"})
},
'main.job': {
'Meta': {'object_name': 'Job'},
'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'cancel_flag': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'celery_task_id': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '100', 'blank': 'True'}),
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'created_by': ('django.db.models.fields.related.ForeignKey', [], {'related_name': '"{\'class\': \'job\', \'app_label\': \'main\'}(class)s_created"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
'credential': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'jobs'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': "orm['main.Credential']"}),
'description': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
'extra_vars': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
'failed': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'forks': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0', 'blank': 'True'}),
'hosts': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'jobs'", 'blank': 'True', 'through': u"orm['main.JobHostSummary']", 'to': "orm['main.Host']"}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'inventory': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'jobs'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': "orm['main.Inventory']"}),
'job_args': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
'job_cwd': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
'job_env': ('jsonfield.fields.JSONField', [], {'default': '{}', 'blank': 'True'}),
'job_tags': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
'job_template': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'jobs'", 'on_delete': 'models.SET_NULL', 'default': 'None', 'to': "orm['main.JobTemplate']", 'blank': 'True', 'null': 'True'}),
'job_type': ('django.db.models.fields.CharField', [], {'max_length': '64'}),
'limit': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '512'}),
'playbook': ('django.db.models.fields.CharField', [], {'max_length': '1024'}),
'project': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'jobs'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['main.Project']"}),
'result_stdout': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
'result_traceback': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
'status': ('django.db.models.fields.CharField', [], {'default': "'new'", 'max_length': '20'}),
'verbosity': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0', 'blank': 'True'})
},
'main.jobevent': {
'Meta': {'ordering': "('pk',)", 'object_name': 'JobEvent'},
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'event': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'event_data': ('jsonfield.fields.JSONField', [], {'default': '{}', 'blank': 'True'}),
'failed': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'host': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'job_events_as_primary_host'", 'on_delete': 'models.SET_NULL', 'default': 'None', 'to': "orm['main.Host']", 'blank': 'True', 'null': 'True'}),
'hosts': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'job_events'", 'blank': 'True', 'to': "orm['main.Host']"}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'job': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'job_events'", 'to': "orm['main.Job']"}),
'parent': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'children'", 'on_delete': 'models.SET_NULL', 'default': 'None', 'to': "orm['main.JobEvent']", 'blank': 'True', 'null': 'True'}),
'play': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
'task': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'})
},
u'main.jobhostsummary': {
'Meta': {'ordering': "('-pk',)", 'unique_together': "[('job', 'host')]", 'object_name': 'JobHostSummary'},
'changed': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
'dark': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
'failed': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'failures': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
'host': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'job_host_summaries'", 'to': "orm['main.Host']"}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'job': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'job_host_summaries'", 'to': "orm['main.Job']"}),
'ok': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
'processed': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
'skipped': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'})
},
'main.jobtemplate': {
'Meta': {'object_name': 'JobTemplate'},
'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'created_by': ('django.db.models.fields.related.ForeignKey', [], {'related_name': '"{\'class\': \'jobtemplate\', \'app_label\': \'main\'}(class)s_created"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
'credential': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'job_templates'", 'on_delete': 'models.SET_NULL', 'default': 'None', 'to': "orm['main.Credential']", 'blank': 'True', 'null': 'True'}),
'description': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
'extra_vars': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
'forks': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0', 'blank': 'True'}),
'host_config_key': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'inventory': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'job_templates'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': "orm['main.Inventory']"}),
'job_tags': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
'job_type': ('django.db.models.fields.CharField', [], {'max_length': '64'}),
'limit': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '512'}),
'playbook': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024'}),
'project': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'job_templates'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['main.Project']"}),
'verbosity': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0', 'blank': 'True'})
},
'main.organization': {
'Meta': {'object_name': 'Organization'},
'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'admins': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'admin_of_organizations'", 'blank': 'True', 'to': u"orm['auth.User']"}),
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'created_by': ('django.db.models.fields.related.ForeignKey', [], {'related_name': '"{\'class\': \'organization\', \'app_label\': \'main\'}(class)s_created"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
'description': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '512'}),
'projects': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'organizations'", 'blank': 'True', 'to': u"orm['main.Project']"}),
'users': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'organizations'", 'blank': 'True', 'to': u"orm['auth.User']"})
},
'main.permission': {
'Meta': {'object_name': 'Permission'},
'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'created_by': ('django.db.models.fields.related.ForeignKey', [], {'related_name': '"{\'class\': \'permission\', \'app_label\': \'main\'}(class)s_created"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
'description': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'inventory': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'permissions'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': "orm['main.Inventory']"}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '512'}),
'permission_type': ('django.db.models.fields.CharField', [], {'max_length': '64'}),
'project': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'permissions'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['main.Project']"}),
'team': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'permissions'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': "orm['main.Team']"}),
'user': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'permissions'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"})
},
u'main.project': {
'Meta': {'object_name': 'Project'},
'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'created_by': ('django.db.models.fields.related.ForeignKey', [], {'related_name': '"{\'class\': \'project\', \'app_label\': u\'main\'}(class)s_created"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
'description': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'local_path': ('django.db.models.fields.CharField', [], {'max_length': '1024'}),
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '512'})
},
'main.team': {
'Meta': {'object_name': 'Team'},
'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'created_by': ('django.db.models.fields.related.ForeignKey', [], {'related_name': '"{\'class\': \'team\', \'app_label\': \'main\'}(class)s_created"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
'description': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '512'}),
'organization': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'teams'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': "orm['main.Organization']"}),
'projects': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'teams'", 'blank': 'True', 'to': u"orm['main.Project']"}),
'users': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'teams'", 'blank': 'True', 'to': u"orm['auth.User']"})
},
u'taggit.tag': {
'Meta': {'object_name': 'Tag'},
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '100'}),
'slug': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '100'})
},
u'taggit.taggeditem': {
'Meta': {'object_name': 'TaggedItem'},
'content_type': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "u'taggit_taggeditem_tagged_items'", 'to': u"orm['contenttypes.ContentType']"}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'object_id': ('django.db.models.fields.IntegerField', [], {'db_index': 'True'}),
'tag': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "u'taggit_taggeditem_items'", 'to': u"orm['taggit.Tag']"})
}
}
complete_apps = ['main']

View File

@@ -1,273 +0,0 @@
# -*- coding: utf-8 -*-
import datetime
from south.db import db
from south.v2 import SchemaMigration
from django.db import models
class Migration(SchemaMigration):
'''
Schema migration for AWX 1.2-b2 release.
- Add has_active_failures field on Inventory, Group and Host models.
'''
def forwards(self, orm):
# Adding field 'Inventory.has_active_failures'
db.add_column(u'main_inventory', 'has_active_failures',
self.gf('django.db.models.fields.BooleanField')(default=False),
keep_default=False)
# Adding field 'Host.has_active_failures'
db.add_column(u'main_host', 'has_active_failures',
self.gf('django.db.models.fields.BooleanField')(default=False),
keep_default=False)
# Adding field 'Group.has_active_failures'
db.add_column(u'main_group', 'has_active_failures',
self.gf('django.db.models.fields.BooleanField')(default=False),
keep_default=False)
def backwards(self, orm):
# Deleting field 'Inventory.has_active_failures'
db.delete_column(u'main_inventory', 'has_active_failures')
# Deleting field 'Host.has_active_failures'
db.delete_column(u'main_host', 'has_active_failures')
# Deleting field 'Group.has_active_failures'
db.delete_column(u'main_group', 'has_active_failures')
models = {
u'auth.group': {
'Meta': {'object_name': 'Group'},
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
},
u'auth.permission': {
'Meta': {'ordering': "(u'content_type__app_label', u'content_type__model', u'codename')", 'unique_together': "((u'content_type', u'codename'),)", 'object_name': 'Permission'},
'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['contenttypes.ContentType']"}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
},
u'auth.user': {
'Meta': {'object_name': 'User'},
'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
},
u'contenttypes.contenttype': {
'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
},
'main.credential': {
'Meta': {'object_name': 'Credential'},
'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'created_by': ('django.db.models.fields.related.ForeignKey', [], {'related_name': '"{\'class\': \'credential\', \'app_label\': \'main\'}(class)s_created"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
'description': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '512'}),
'ssh_key_data': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
'ssh_key_unlock': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
'ssh_password': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
'ssh_username': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
'sudo_password': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
'sudo_username': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
'team': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'credentials'", 'on_delete': 'models.SET_NULL', 'default': 'None', 'to': "orm['main.Team']", 'blank': 'True', 'null': 'True'}),
'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'credentials'", 'on_delete': 'models.SET_NULL', 'default': 'None', 'to': u"orm['auth.User']", 'blank': 'True', 'null': 'True'})
},
'main.group': {
'Meta': {'unique_together': "(('name', 'inventory'),)", 'object_name': 'Group'},
'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'created_by': ('django.db.models.fields.related.ForeignKey', [], {'related_name': '"{\'class\': \'group\', \'app_label\': \'main\'}(class)s_created"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
'description': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
'has_active_failures': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'hosts': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'groups'", 'blank': 'True', 'to': "orm['main.Host']"}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'inventory': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'groups'", 'to': "orm['main.Inventory']"}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '512'}),
'parents': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'children'", 'blank': 'True', 'to': "orm['main.Group']"}),
'variables': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'})
},
'main.host': {
'Meta': {'unique_together': "(('name', 'inventory'),)", 'object_name': 'Host'},
'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'created_by': ('django.db.models.fields.related.ForeignKey', [], {'related_name': '"{\'class\': \'host\', \'app_label\': \'main\'}(class)s_created"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
'description': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
'has_active_failures': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'inventory': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'hosts'", 'to': "orm['main.Inventory']"}),
'last_job': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'hosts_as_last_job+'", 'on_delete': 'models.SET_NULL', 'default': 'None', 'to': "orm['main.Job']", 'blank': 'True', 'null': 'True'}),
'last_job_host_summary': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'hosts_as_last_job_summary+'", 'on_delete': 'models.SET_NULL', 'default': 'None', 'to': u"orm['main.JobHostSummary']", 'blank': 'True', 'null': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '512'}),
'variables': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'})
},
'main.inventory': {
'Meta': {'unique_together': "(('name', 'organization'),)", 'object_name': 'Inventory'},
'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'created_by': ('django.db.models.fields.related.ForeignKey', [], {'related_name': '"{\'class\': \'inventory\', \'app_label\': \'main\'}(class)s_created"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
'description': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
'has_active_failures': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '512'}),
'organization': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'inventories'", 'to': "orm['main.Organization']"})
},
'main.job': {
'Meta': {'object_name': 'Job'},
'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'cancel_flag': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'celery_task_id': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '100', 'blank': 'True'}),
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'created_by': ('django.db.models.fields.related.ForeignKey', [], {'related_name': '"{\'class\': \'job\', \'app_label\': \'main\'}(class)s_created"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
'credential': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'jobs'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': "orm['main.Credential']"}),
'description': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
'extra_vars': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
'failed': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'forks': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0', 'blank': 'True'}),
'hosts': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'jobs'", 'blank': 'True', 'through': u"orm['main.JobHostSummary']", 'to': "orm['main.Host']"}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'inventory': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'jobs'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': "orm['main.Inventory']"}),
'job_args': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
'job_cwd': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
'job_env': ('jsonfield.fields.JSONField', [], {'default': '{}', 'blank': 'True'}),
'job_tags': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
'job_template': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'jobs'", 'on_delete': 'models.SET_NULL', 'default': 'None', 'to': "orm['main.JobTemplate']", 'blank': 'True', 'null': 'True'}),
'job_type': ('django.db.models.fields.CharField', [], {'max_length': '64'}),
'limit': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '512'}),
'playbook': ('django.db.models.fields.CharField', [], {'max_length': '1024'}),
'project': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'jobs'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['main.Project']"}),
'result_stdout': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
'result_traceback': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
'status': ('django.db.models.fields.CharField', [], {'default': "'new'", 'max_length': '20'}),
'verbosity': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0', 'blank': 'True'})
},
'main.jobevent': {
'Meta': {'ordering': "('pk',)", 'object_name': 'JobEvent'},
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'event': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'event_data': ('jsonfield.fields.JSONField', [], {'default': '{}', 'blank': 'True'}),
'failed': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'host': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'job_events_as_primary_host'", 'on_delete': 'models.SET_NULL', 'default': 'None', 'to': "orm['main.Host']", 'blank': 'True', 'null': 'True'}),
'hosts': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'job_events'", 'blank': 'True', 'to': "orm['main.Host']"}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'job': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'job_events'", 'to': "orm['main.Job']"}),
'parent': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'children'", 'on_delete': 'models.SET_NULL', 'default': 'None', 'to': "orm['main.JobEvent']", 'blank': 'True', 'null': 'True'}),
'play': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
'task': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'})
},
u'main.jobhostsummary': {
'Meta': {'ordering': "('-pk',)", 'unique_together': "[('job', 'host')]", 'object_name': 'JobHostSummary'},
'changed': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
'dark': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
'failed': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'failures': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
'host': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'job_host_summaries'", 'to': "orm['main.Host']"}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'job': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'job_host_summaries'", 'to': "orm['main.Job']"}),
'ok': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
'processed': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
'skipped': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'})
},
'main.jobtemplate': {
'Meta': {'object_name': 'JobTemplate'},
'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'created_by': ('django.db.models.fields.related.ForeignKey', [], {'related_name': '"{\'class\': \'jobtemplate\', \'app_label\': \'main\'}(class)s_created"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
'credential': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'job_templates'", 'on_delete': 'models.SET_NULL', 'default': 'None', 'to': "orm['main.Credential']", 'blank': 'True', 'null': 'True'}),
'description': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
'extra_vars': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
'forks': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0', 'blank': 'True'}),
'host_config_key': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'inventory': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'job_templates'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': "orm['main.Inventory']"}),
'job_tags': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
'job_type': ('django.db.models.fields.CharField', [], {'max_length': '64'}),
'limit': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '512'}),
'playbook': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024'}),
'project': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'job_templates'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['main.Project']"}),
'verbosity': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0', 'blank': 'True'})
},
'main.organization': {
'Meta': {'object_name': 'Organization'},
'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'admins': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'admin_of_organizations'", 'blank': 'True', 'to': u"orm['auth.User']"}),
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'created_by': ('django.db.models.fields.related.ForeignKey', [], {'related_name': '"{\'class\': \'organization\', \'app_label\': \'main\'}(class)s_created"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
'description': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '512'}),
'projects': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'organizations'", 'blank': 'True', 'to': u"orm['main.Project']"}),
'users': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'organizations'", 'blank': 'True', 'to': u"orm['auth.User']"})
},
'main.permission': {
'Meta': {'object_name': 'Permission'},
'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'created_by': ('django.db.models.fields.related.ForeignKey', [], {'related_name': '"{\'class\': \'permission\', \'app_label\': \'main\'}(class)s_created"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
'description': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'inventory': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'permissions'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': "orm['main.Inventory']"}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '512'}),
'permission_type': ('django.db.models.fields.CharField', [], {'max_length': '64'}),
'project': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'permissions'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['main.Project']"}),
'team': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'permissions'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': "orm['main.Team']"}),
'user': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'permissions'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"})
},
u'main.project': {
'Meta': {'object_name': 'Project'},
'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'created_by': ('django.db.models.fields.related.ForeignKey', [], {'related_name': '"{\'class\': \'project\', \'app_label\': u\'main\'}(class)s_created"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
'description': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'local_path': ('django.db.models.fields.CharField', [], {'max_length': '1024'}),
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '512'})
},
'main.team': {
'Meta': {'object_name': 'Team'},
'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'created_by': ('django.db.models.fields.related.ForeignKey', [], {'related_name': '"{\'class\': \'team\', \'app_label\': \'main\'}(class)s_created"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
'description': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '512'}),
'organization': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'teams'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': "orm['main.Organization']"}),
'projects': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'teams'", 'blank': 'True', 'to': u"orm['main.Project']"}),
'users': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'teams'", 'blank': 'True', 'to': u"orm['auth.User']"})
},
u'taggit.tag': {
'Meta': {'object_name': 'Tag'},
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '100'}),
'slug': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '100'})
},
u'taggit.taggeditem': {
'Meta': {'object_name': 'TaggedItem'},
'content_type': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "u'taggit_taggeditem_tagged_items'", 'to': u"orm['contenttypes.ContentType']"}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'object_id': ('django.db.models.fields.IntegerField', [], {'db_index': 'True'}),
'tag': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "u'taggit_taggeditem_items'", 'to': u"orm['taggit.Tag']"})
}
}
complete_apps = ['main']

View File

@@ -1,258 +0,0 @@
# -*- coding: utf-8 -*-
import datetime
from south.db import db
from south.v2 import SchemaMigration
from django.db import models
class Migration(SchemaMigration):
'''
Schema migration for AWX 1.2-b2 release.
- Add variables field on Inventory model.
'''
def forwards(self, orm):
# Adding field 'Inventory.variables'
db.add_column(u'main_inventory', 'variables',
self.gf('django.db.models.fields.TextField')(default='', null=True, blank=True),
keep_default=False)
def backwards(self, orm):
# Deleting field 'Inventory.variables'
db.delete_column(u'main_inventory', 'variables')
models = {
u'auth.group': {
'Meta': {'object_name': 'Group'},
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
},
u'auth.permission': {
'Meta': {'ordering': "(u'content_type__app_label', u'content_type__model', u'codename')", 'unique_together': "((u'content_type', u'codename'),)", 'object_name': 'Permission'},
'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['contenttypes.ContentType']"}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
},
u'auth.user': {
'Meta': {'object_name': 'User'},
'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
},
u'contenttypes.contenttype': {
'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
},
'main.credential': {
'Meta': {'object_name': 'Credential'},
'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'created_by': ('django.db.models.fields.related.ForeignKey', [], {'related_name': '"{\'class\': \'credential\', \'app_label\': \'main\'}(class)s_created"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
'description': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '512'}),
'ssh_key_data': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
'ssh_key_unlock': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
'ssh_password': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
'ssh_username': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
'sudo_password': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
'sudo_username': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
'team': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'credentials'", 'on_delete': 'models.SET_NULL', 'default': 'None', 'to': "orm['main.Team']", 'blank': 'True', 'null': 'True'}),
'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'credentials'", 'on_delete': 'models.SET_NULL', 'default': 'None', 'to': u"orm['auth.User']", 'blank': 'True', 'null': 'True'})
},
'main.group': {
'Meta': {'unique_together': "(('name', 'inventory'),)", 'object_name': 'Group'},
'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'created_by': ('django.db.models.fields.related.ForeignKey', [], {'related_name': '"{\'class\': \'group\', \'app_label\': \'main\'}(class)s_created"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
'description': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
'has_active_failures': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'hosts': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'groups'", 'blank': 'True', 'to': "orm['main.Host']"}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'inventory': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'groups'", 'to': "orm['main.Inventory']"}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '512'}),
'parents': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'children'", 'blank': 'True', 'to': "orm['main.Group']"}),
'variables': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'})
},
'main.host': {
'Meta': {'unique_together': "(('name', 'inventory'),)", 'object_name': 'Host'},
'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'created_by': ('django.db.models.fields.related.ForeignKey', [], {'related_name': '"{\'class\': \'host\', \'app_label\': \'main\'}(class)s_created"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
'description': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
'has_active_failures': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'inventory': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'hosts'", 'to': "orm['main.Inventory']"}),
'last_job': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'hosts_as_last_job+'", 'on_delete': 'models.SET_NULL', 'default': 'None', 'to': "orm['main.Job']", 'blank': 'True', 'null': 'True'}),
'last_job_host_summary': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'hosts_as_last_job_summary+'", 'on_delete': 'models.SET_NULL', 'default': 'None', 'to': u"orm['main.JobHostSummary']", 'blank': 'True', 'null': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '512'}),
'variables': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'})
},
'main.inventory': {
'Meta': {'unique_together': "(('name', 'organization'),)", 'object_name': 'Inventory'},
'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'created_by': ('django.db.models.fields.related.ForeignKey', [], {'related_name': '"{\'class\': \'inventory\', \'app_label\': \'main\'}(class)s_created"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
'description': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
'has_active_failures': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '512'}),
'organization': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'inventories'", 'to': "orm['main.Organization']"}),
'variables': ('django.db.models.fields.TextField', [], {'default': "''", 'null': 'True', 'blank': 'True'})
},
'main.job': {
'Meta': {'object_name': 'Job'},
'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'cancel_flag': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'celery_task_id': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '100', 'blank': 'True'}),
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'created_by': ('django.db.models.fields.related.ForeignKey', [], {'related_name': '"{\'class\': \'job\', \'app_label\': \'main\'}(class)s_created"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
'credential': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'jobs'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': "orm['main.Credential']"}),
'description': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
'extra_vars': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
'failed': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'forks': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0', 'blank': 'True'}),
'hosts': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'jobs'", 'blank': 'True', 'through': u"orm['main.JobHostSummary']", 'to': "orm['main.Host']"}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'inventory': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'jobs'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': "orm['main.Inventory']"}),
'job_args': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
'job_cwd': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
'job_env': ('jsonfield.fields.JSONField', [], {'default': '{}', 'blank': 'True'}),
'job_tags': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
'job_template': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'jobs'", 'on_delete': 'models.SET_NULL', 'default': 'None', 'to': "orm['main.JobTemplate']", 'blank': 'True', 'null': 'True'}),
'job_type': ('django.db.models.fields.CharField', [], {'max_length': '64'}),
'limit': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '512'}),
'playbook': ('django.db.models.fields.CharField', [], {'max_length': '1024'}),
'project': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'jobs'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['main.Project']"}),
'result_stdout': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
'result_traceback': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
'status': ('django.db.models.fields.CharField', [], {'default': "'new'", 'max_length': '20'}),
'verbosity': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0', 'blank': 'True'})
},
'main.jobevent': {
'Meta': {'ordering': "('pk',)", 'object_name': 'JobEvent'},
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'event': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'event_data': ('jsonfield.fields.JSONField', [], {'default': '{}', 'blank': 'True'}),
'failed': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'host': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'job_events_as_primary_host'", 'on_delete': 'models.SET_NULL', 'default': 'None', 'to': "orm['main.Host']", 'blank': 'True', 'null': 'True'}),
'hosts': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'job_events'", 'blank': 'True', 'to': "orm['main.Host']"}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'job': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'job_events'", 'to': "orm['main.Job']"}),
'parent': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'children'", 'on_delete': 'models.SET_NULL', 'default': 'None', 'to': "orm['main.JobEvent']", 'blank': 'True', 'null': 'True'}),
'play': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
'task': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'})
},
u'main.jobhostsummary': {
'Meta': {'ordering': "('-pk',)", 'unique_together': "[('job', 'host')]", 'object_name': 'JobHostSummary'},
'changed': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
'dark': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
'failed': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'failures': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
'host': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'job_host_summaries'", 'to': "orm['main.Host']"}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'job': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'job_host_summaries'", 'to': "orm['main.Job']"}),
'ok': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
'processed': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
'skipped': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'})
},
'main.jobtemplate': {
'Meta': {'object_name': 'JobTemplate'},
'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'created_by': ('django.db.models.fields.related.ForeignKey', [], {'related_name': '"{\'class\': \'jobtemplate\', \'app_label\': \'main\'}(class)s_created"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
'credential': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'job_templates'", 'on_delete': 'models.SET_NULL', 'default': 'None', 'to': "orm['main.Credential']", 'blank': 'True', 'null': 'True'}),
'description': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
'extra_vars': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
'forks': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0', 'blank': 'True'}),
'host_config_key': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'inventory': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'job_templates'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': "orm['main.Inventory']"}),
'job_tags': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
'job_type': ('django.db.models.fields.CharField', [], {'max_length': '64'}),
'limit': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '512'}),
'playbook': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024'}),
'project': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'job_templates'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['main.Project']"}),
'verbosity': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0', 'blank': 'True'})
},
'main.organization': {
'Meta': {'object_name': 'Organization'},
'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'admins': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'admin_of_organizations'", 'blank': 'True', 'to': u"orm['auth.User']"}),
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'created_by': ('django.db.models.fields.related.ForeignKey', [], {'related_name': '"{\'class\': \'organization\', \'app_label\': \'main\'}(class)s_created"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
'description': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '512'}),
'projects': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'organizations'", 'blank': 'True', 'to': u"orm['main.Project']"}),
'users': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'organizations'", 'blank': 'True', 'to': u"orm['auth.User']"})
},
'main.permission': {
'Meta': {'object_name': 'Permission'},
'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'created_by': ('django.db.models.fields.related.ForeignKey', [], {'related_name': '"{\'class\': \'permission\', \'app_label\': \'main\'}(class)s_created"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
'description': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'inventory': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'permissions'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': "orm['main.Inventory']"}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '512'}),
'permission_type': ('django.db.models.fields.CharField', [], {'max_length': '64'}),
'project': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'permissions'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['main.Project']"}),
'team': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'permissions'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': "orm['main.Team']"}),
'user': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'permissions'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"})
},
u'main.project': {
'Meta': {'object_name': 'Project'},
'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'created_by': ('django.db.models.fields.related.ForeignKey', [], {'related_name': '"{\'class\': \'project\', \'app_label\': u\'main\'}(class)s_created"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
'description': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'local_path': ('django.db.models.fields.CharField', [], {'max_length': '1024'}),
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '512'})
},
'main.team': {
'Meta': {'object_name': 'Team'},
'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'created_by': ('django.db.models.fields.related.ForeignKey', [], {'related_name': '"{\'class\': \'team\', \'app_label\': \'main\'}(class)s_created"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
'description': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '512'}),
'organization': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'teams'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': "orm['main.Organization']"}),
'projects': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'teams'", 'blank': 'True', 'to': u"orm['main.Project']"}),
'users': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'teams'", 'blank': 'True', 'to': u"orm['auth.User']"})
},
u'taggit.tag': {
'Meta': {'object_name': 'Tag'},
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '100'}),
'slug': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '100'})
},
u'taggit.taggeditem': {
'Meta': {'object_name': 'TaggedItem'},
'content_type': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "u'taggit_taggeditem_tagged_items'", 'to': u"orm['contenttypes.ContentType']"}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'object_id': ('django.db.models.fields.IntegerField', [], {'db_index': 'True'}),
'tag': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "u'taggit_taggeditem_items'", 'to': u"orm['taggit.Tag']"})
}
}
complete_apps = ['main']

View File

@@ -1,271 +0,0 @@
# -*- coding: utf-8 -*-
import datetime
from south.db import db
from south.v2 import SchemaMigration
from django.db import models
class Migration(SchemaMigration):
'''
Schema migration for AWX 1.2-b2 release.
- Add launch_type field on Job model.
- Add changed flag on JobEvent model.
'''
def forwards(self, orm):
# Adding field 'Job.launch_type'
db.add_column(u'main_job', 'launch_type',
self.gf('django.db.models.fields.CharField')(default='manual', max_length=20),
keep_default=False)
# Adding field 'JobEvent.changed'
db.add_column(u'main_jobevent', 'changed',
self.gf('django.db.models.fields.BooleanField')(default=False),
keep_default=False)
def backwards(self, orm):
# Deleting field 'Job.launch_type'
db.delete_column(u'main_job', 'launch_type')
# Deleting field 'JobEvent.changed'
db.delete_column(u'main_jobevent', 'changed')
models = {
u'auth.group': {
'Meta': {'object_name': 'Group'},
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
},
u'auth.permission': {
'Meta': {'ordering': "(u'content_type__app_label', u'content_type__model', u'codename')", 'unique_together': "((u'content_type', u'codename'),)", 'object_name': 'Permission'},
'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['contenttypes.ContentType']"}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
},
u'auth.user': {
'Meta': {'object_name': 'User'},
'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
},
u'contenttypes.contenttype': {
'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
},
'main.credential': {
'Meta': {'object_name': 'Credential'},
'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'created_by': ('django.db.models.fields.related.ForeignKey', [], {'related_name': '"{\'class\': \'credential\', \'app_label\': \'main\'}(class)s_created"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
'description': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '512'}),
'ssh_key_data': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
'ssh_key_unlock': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
'ssh_password': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
'ssh_username': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
'sudo_password': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
'sudo_username': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
'team': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'credentials'", 'on_delete': 'models.SET_NULL', 'default': 'None', 'to': "orm['main.Team']", 'blank': 'True', 'null': 'True'}),
'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'credentials'", 'on_delete': 'models.SET_NULL', 'default': 'None', 'to': u"orm['auth.User']", 'blank': 'True', 'null': 'True'})
},
'main.group': {
'Meta': {'unique_together': "(('name', 'inventory'),)", 'object_name': 'Group'},
'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'created_by': ('django.db.models.fields.related.ForeignKey', [], {'related_name': '"{\'class\': \'group\', \'app_label\': \'main\'}(class)s_created"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
'description': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
'has_active_failures': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'hosts': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'groups'", 'blank': 'True', 'to': "orm['main.Host']"}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'inventory': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'groups'", 'to': "orm['main.Inventory']"}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '512'}),
'parents': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'children'", 'blank': 'True', 'to': "orm['main.Group']"}),
'variables': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'})
},
'main.host': {
'Meta': {'unique_together': "(('name', 'inventory'),)", 'object_name': 'Host'},
'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'created_by': ('django.db.models.fields.related.ForeignKey', [], {'related_name': '"{\'class\': \'host\', \'app_label\': \'main\'}(class)s_created"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
'description': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
'has_active_failures': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'inventory': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'hosts'", 'to': "orm['main.Inventory']"}),
'last_job': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'hosts_as_last_job+'", 'on_delete': 'models.SET_NULL', 'default': 'None', 'to': "orm['main.Job']", 'blank': 'True', 'null': 'True'}),
'last_job_host_summary': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'hosts_as_last_job_summary+'", 'on_delete': 'models.SET_NULL', 'default': 'None', 'to': u"orm['main.JobHostSummary']", 'blank': 'True', 'null': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '512'}),
'variables': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'})
},
'main.inventory': {
'Meta': {'unique_together': "(('name', 'organization'),)", 'object_name': 'Inventory'},
'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'created_by': ('django.db.models.fields.related.ForeignKey', [], {'related_name': '"{\'class\': \'inventory\', \'app_label\': \'main\'}(class)s_created"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
'description': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
'has_active_failures': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '512'}),
'organization': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'inventories'", 'to': "orm['main.Organization']"}),
'variables': ('django.db.models.fields.TextField', [], {'default': "''", 'null': 'True', 'blank': 'True'})
},
'main.job': {
'Meta': {'object_name': 'Job'},
'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'cancel_flag': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'celery_task_id': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '100', 'blank': 'True'}),
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'created_by': ('django.db.models.fields.related.ForeignKey', [], {'related_name': '"{\'class\': \'job\', \'app_label\': \'main\'}(class)s_created"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
'credential': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'jobs'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': "orm['main.Credential']"}),
'description': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
'extra_vars': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
'failed': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'forks': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0', 'blank': 'True'}),
'hosts': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'jobs'", 'blank': 'True', 'through': u"orm['main.JobHostSummary']", 'to': "orm['main.Host']"}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'inventory': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'jobs'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': "orm['main.Inventory']"}),
'job_args': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
'job_cwd': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
'job_env': ('jsonfield.fields.JSONField', [], {'default': '{}', 'blank': 'True'}),
'job_tags': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
'job_template': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'jobs'", 'on_delete': 'models.SET_NULL', 'default': 'None', 'to': "orm['main.JobTemplate']", 'blank': 'True', 'null': 'True'}),
'job_type': ('django.db.models.fields.CharField', [], {'max_length': '64'}),
'launch_type': ('django.db.models.fields.CharField', [], {'default': "'manual'", 'max_length': '20'}),
'limit': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '512'}),
'playbook': ('django.db.models.fields.CharField', [], {'max_length': '1024'}),
'project': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'jobs'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['main.Project']"}),
'result_stdout': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
'result_traceback': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
'status': ('django.db.models.fields.CharField', [], {'default': "'new'", 'max_length': '20'}),
'verbosity': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0', 'blank': 'True'})
},
'main.jobevent': {
'Meta': {'ordering': "('pk',)", 'object_name': 'JobEvent'},
'changed': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'event': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'event_data': ('jsonfield.fields.JSONField', [], {'default': '{}', 'blank': 'True'}),
'failed': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'host': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'job_events_as_primary_host'", 'on_delete': 'models.SET_NULL', 'default': 'None', 'to': "orm['main.Host']", 'blank': 'True', 'null': 'True'}),
'hosts': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'job_events'", 'blank': 'True', 'to': "orm['main.Host']"}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'job': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'job_events'", 'to': "orm['main.Job']"}),
'parent': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'children'", 'on_delete': 'models.SET_NULL', 'default': 'None', 'to': "orm['main.JobEvent']", 'blank': 'True', 'null': 'True'}),
'play': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
'task': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'})
},
u'main.jobhostsummary': {
'Meta': {'ordering': "('-pk',)", 'unique_together': "[('job', 'host')]", 'object_name': 'JobHostSummary'},
'changed': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
'dark': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
'failed': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'failures': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
'host': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'job_host_summaries'", 'to': "orm['main.Host']"}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'job': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'job_host_summaries'", 'to': "orm['main.Job']"}),
'ok': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
'processed': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
'skipped': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'})
},
'main.jobtemplate': {
'Meta': {'object_name': 'JobTemplate'},
'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'created_by': ('django.db.models.fields.related.ForeignKey', [], {'related_name': '"{\'class\': \'jobtemplate\', \'app_label\': \'main\'}(class)s_created"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
'credential': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'job_templates'", 'on_delete': 'models.SET_NULL', 'default': 'None', 'to': "orm['main.Credential']", 'blank': 'True', 'null': 'True'}),
'description': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
'extra_vars': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
'forks': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0', 'blank': 'True'}),
'host_config_key': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'inventory': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'job_templates'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': "orm['main.Inventory']"}),
'job_tags': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
'job_type': ('django.db.models.fields.CharField', [], {'max_length': '64'}),
'limit': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '512'}),
'playbook': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024'}),
'project': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'job_templates'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['main.Project']"}),
'verbosity': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0', 'blank': 'True'})
},
'main.organization': {
'Meta': {'object_name': 'Organization'},
'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'admins': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'admin_of_organizations'", 'blank': 'True', 'to': u"orm['auth.User']"}),
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'created_by': ('django.db.models.fields.related.ForeignKey', [], {'related_name': '"{\'class\': \'organization\', \'app_label\': \'main\'}(class)s_created"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
'description': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '512'}),
'projects': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'organizations'", 'blank': 'True', 'to': u"orm['main.Project']"}),
'users': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'organizations'", 'blank': 'True', 'to': u"orm['auth.User']"})
},
'main.permission': {
'Meta': {'object_name': 'Permission'},
'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'created_by': ('django.db.models.fields.related.ForeignKey', [], {'related_name': '"{\'class\': \'permission\', \'app_label\': \'main\'}(class)s_created"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
'description': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'inventory': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'permissions'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': "orm['main.Inventory']"}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '512'}),
'permission_type': ('django.db.models.fields.CharField', [], {'max_length': '64'}),
'project': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'permissions'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['main.Project']"}),
'team': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'permissions'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': "orm['main.Team']"}),
'user': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'permissions'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"})
},
u'main.project': {
'Meta': {'object_name': 'Project'},
'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'created_by': ('django.db.models.fields.related.ForeignKey', [], {'related_name': '"{\'class\': \'project\', \'app_label\': u\'main\'}(class)s_created"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
'description': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'local_path': ('django.db.models.fields.CharField', [], {'max_length': '1024'}),
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '512'})
},
'main.team': {
'Meta': {'object_name': 'Team'},
'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'created_by': ('django.db.models.fields.related.ForeignKey', [], {'related_name': '"{\'class\': \'team\', \'app_label\': \'main\'}(class)s_created"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
'description': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '512'}),
'organization': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'teams'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': "orm['main.Organization']"}),
'projects': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'teams'", 'blank': 'True', 'to': u"orm['main.Project']"}),
'users': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'teams'", 'blank': 'True', 'to': u"orm['auth.User']"})
},
u'taggit.tag': {
'Meta': {'object_name': 'Tag'},
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '100'}),
'slug': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '100'})
},
u'taggit.taggeditem': {
'Meta': {'object_name': 'TaggedItem'},
'content_type': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "u'taggit_taggeditem_tagged_items'", 'to': u"orm['contenttypes.ContentType']"}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'object_id': ('django.db.models.fields.IntegerField', [], {'db_index': 'True'}),
'tag': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "u'taggit_taggeditem_items'", 'to': u"orm['taggit.Tag']"})
}
}
complete_apps = ['main']

View File

@@ -1,260 +0,0 @@
# -*- coding: utf-8 -*-
import datetime
from south.db import db
from south.v2 import SchemaMigration
from django.db import models
class Migration(SchemaMigration):
'''
Schema migration for AWX 1.2 release.
- Removed unique constraint on job name.
'''
def forwards(self, orm):
# Removing unique constraint on 'Job', fields ['name']
db.delete_unique(u'main_job', ['name'])
def backwards(self, orm):
# Adding unique constraint on 'Job', fields ['name']
db.create_unique(u'main_job', ['name'])
models = {
u'auth.group': {
'Meta': {'object_name': 'Group'},
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
},
u'auth.permission': {
'Meta': {'ordering': "(u'content_type__app_label', u'content_type__model', u'codename')", 'unique_together': "((u'content_type', u'codename'),)", 'object_name': 'Permission'},
'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['contenttypes.ContentType']"}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
},
u'auth.user': {
'Meta': {'object_name': 'User'},
'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
},
u'contenttypes.contenttype': {
'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
},
'main.credential': {
'Meta': {'object_name': 'Credential'},
'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'created_by': ('django.db.models.fields.related.ForeignKey', [], {'related_name': '"{\'class\': \'credential\', \'app_label\': \'main\'}(class)s_created"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
'description': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '512'}),
'ssh_key_data': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
'ssh_key_unlock': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
'ssh_password': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
'ssh_username': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
'sudo_password': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
'sudo_username': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
'team': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'credentials'", 'on_delete': 'models.SET_NULL', 'default': 'None', 'to': "orm['main.Team']", 'blank': 'True', 'null': 'True'}),
'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'credentials'", 'on_delete': 'models.SET_NULL', 'default': 'None', 'to': u"orm['auth.User']", 'blank': 'True', 'null': 'True'})
},
'main.group': {
'Meta': {'unique_together': "(('name', 'inventory'),)", 'object_name': 'Group'},
'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'created_by': ('django.db.models.fields.related.ForeignKey', [], {'related_name': '"{\'class\': \'group\', \'app_label\': \'main\'}(class)s_created"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
'description': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
'has_active_failures': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'hosts': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'groups'", 'blank': 'True', 'to': "orm['main.Host']"}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'inventory': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'groups'", 'to': "orm['main.Inventory']"}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '512'}),
'parents': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'children'", 'blank': 'True', 'to': "orm['main.Group']"}),
'variables': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'})
},
'main.host': {
'Meta': {'unique_together': "(('name', 'inventory'),)", 'object_name': 'Host'},
'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'created_by': ('django.db.models.fields.related.ForeignKey', [], {'related_name': '"{\'class\': \'host\', \'app_label\': \'main\'}(class)s_created"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
'description': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
'has_active_failures': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'inventory': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'hosts'", 'to': "orm['main.Inventory']"}),
'last_job': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'hosts_as_last_job+'", 'on_delete': 'models.SET_NULL', 'default': 'None', 'to': "orm['main.Job']", 'blank': 'True', 'null': 'True'}),
'last_job_host_summary': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'hosts_as_last_job_summary+'", 'on_delete': 'models.SET_NULL', 'default': 'None', 'to': u"orm['main.JobHostSummary']", 'blank': 'True', 'null': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '512'}),
'variables': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'})
},
'main.inventory': {
'Meta': {'unique_together': "(('name', 'organization'),)", 'object_name': 'Inventory'},
'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'created_by': ('django.db.models.fields.related.ForeignKey', [], {'related_name': '"{\'class\': \'inventory\', \'app_label\': \'main\'}(class)s_created"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
'description': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
'has_active_failures': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '512'}),
'organization': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'inventories'", 'to': "orm['main.Organization']"}),
'variables': ('django.db.models.fields.TextField', [], {'default': "''", 'null': 'True', 'blank': 'True'})
},
'main.job': {
'Meta': {'object_name': 'Job'},
'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'cancel_flag': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'celery_task_id': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '100', 'blank': 'True'}),
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'created_by': ('django.db.models.fields.related.ForeignKey', [], {'related_name': '"{\'class\': \'job\', \'app_label\': \'main\'}(class)s_created"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
'credential': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'jobs'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': "orm['main.Credential']"}),
'description': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
'extra_vars': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
'failed': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'forks': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0', 'blank': 'True'}),
'hosts': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'jobs'", 'blank': 'True', 'through': u"orm['main.JobHostSummary']", 'to': "orm['main.Host']"}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'inventory': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'jobs'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': "orm['main.Inventory']"}),
'job_args': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
'job_cwd': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
'job_env': ('jsonfield.fields.JSONField', [], {'default': '{}', 'blank': 'True'}),
'job_tags': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
'job_template': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'jobs'", 'on_delete': 'models.SET_NULL', 'default': 'None', 'to': "orm['main.JobTemplate']", 'blank': 'True', 'null': 'True'}),
'job_type': ('django.db.models.fields.CharField', [], {'max_length': '64'}),
'launch_type': ('django.db.models.fields.CharField', [], {'default': "'manual'", 'max_length': '20'}),
'limit': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '512'}),
'playbook': ('django.db.models.fields.CharField', [], {'max_length': '1024'}),
'project': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'jobs'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['main.Project']"}),
'result_stdout': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
'result_traceback': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
'status': ('django.db.models.fields.CharField', [], {'default': "'new'", 'max_length': '20'}),
'verbosity': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0', 'blank': 'True'})
},
'main.jobevent': {
'Meta': {'ordering': "('pk',)", 'object_name': 'JobEvent'},
'changed': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'event': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'event_data': ('jsonfield.fields.JSONField', [], {'default': '{}', 'blank': 'True'}),
'failed': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'host': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'job_events_as_primary_host'", 'on_delete': 'models.SET_NULL', 'default': 'None', 'to': "orm['main.Host']", 'blank': 'True', 'null': 'True'}),
'hosts': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'job_events'", 'blank': 'True', 'to': "orm['main.Host']"}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'job': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'job_events'", 'to': "orm['main.Job']"}),
'parent': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'children'", 'on_delete': 'models.SET_NULL', 'default': 'None', 'to': "orm['main.JobEvent']", 'blank': 'True', 'null': 'True'}),
'play': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
'task': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'})
},
u'main.jobhostsummary': {
'Meta': {'ordering': "('-pk',)", 'unique_together': "[('job', 'host')]", 'object_name': 'JobHostSummary'},
'changed': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
'dark': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
'failed': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'failures': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
'host': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'job_host_summaries'", 'to': "orm['main.Host']"}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'job': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'job_host_summaries'", 'to': "orm['main.Job']"}),
'ok': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
'processed': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
'skipped': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'})
},
'main.jobtemplate': {
'Meta': {'object_name': 'JobTemplate'},
'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'created_by': ('django.db.models.fields.related.ForeignKey', [], {'related_name': '"{\'class\': \'jobtemplate\', \'app_label\': \'main\'}(class)s_created"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
'credential': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'job_templates'", 'on_delete': 'models.SET_NULL', 'default': 'None', 'to': "orm['main.Credential']", 'blank': 'True', 'null': 'True'}),
'description': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
'extra_vars': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
'forks': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0', 'blank': 'True'}),
'host_config_key': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'inventory': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'job_templates'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': "orm['main.Inventory']"}),
'job_tags': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
'job_type': ('django.db.models.fields.CharField', [], {'max_length': '64'}),
'limit': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '512'}),
'playbook': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024'}),
'project': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'job_templates'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['main.Project']"}),
'verbosity': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0', 'blank': 'True'})
},
'main.organization': {
'Meta': {'object_name': 'Organization'},
'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'admins': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'admin_of_organizations'", 'blank': 'True', 'to': u"orm['auth.User']"}),
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'created_by': ('django.db.models.fields.related.ForeignKey', [], {'related_name': '"{\'class\': \'organization\', \'app_label\': \'main\'}(class)s_created"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
'description': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '512'}),
'projects': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'organizations'", 'blank': 'True', 'to': u"orm['main.Project']"}),
'users': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'organizations'", 'blank': 'True', 'to': u"orm['auth.User']"})
},
'main.permission': {
'Meta': {'object_name': 'Permission'},
'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'created_by': ('django.db.models.fields.related.ForeignKey', [], {'related_name': '"{\'class\': \'permission\', \'app_label\': \'main\'}(class)s_created"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
'description': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'inventory': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'permissions'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': "orm['main.Inventory']"}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '512'}),
'permission_type': ('django.db.models.fields.CharField', [], {'max_length': '64'}),
'project': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'permissions'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['main.Project']"}),
'team': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'permissions'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': "orm['main.Team']"}),
'user': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'permissions'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"})
},
u'main.project': {
'Meta': {'object_name': 'Project'},
'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'created_by': ('django.db.models.fields.related.ForeignKey', [], {'related_name': '"{\'class\': \'project\', \'app_label\': u\'main\'}(class)s_created"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
'description': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'local_path': ('django.db.models.fields.CharField', [], {'max_length': '1024'}),
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '512'})
},
'main.team': {
'Meta': {'object_name': 'Team'},
'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'created_by': ('django.db.models.fields.related.ForeignKey', [], {'related_name': '"{\'class\': \'team\', \'app_label\': \'main\'}(class)s_created"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
'description': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '512'}),
'organization': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'teams'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': "orm['main.Organization']"}),
'projects': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'teams'", 'blank': 'True', 'to': u"orm['main.Project']"}),
'users': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'teams'", 'blank': 'True', 'to': u"orm['auth.User']"})
},
u'taggit.tag': {
'Meta': {'object_name': 'Tag'},
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '100'}),
'slug': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '100'})
},
u'taggit.taggeditem': {
'Meta': {'object_name': 'TaggedItem'},
'content_type': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "u'taggit_taggeditem_tagged_items'", 'to': u"orm['contenttypes.ContentType']"}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'object_id': ('django.db.models.fields.IntegerField', [], {'db_index': 'True'}),
'tag': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "u'taggit_taggeditem_items'", 'to': u"orm['taggit.Tag']"})
}
}
complete_apps = ['main']

Some files were not shown because too many files have changed in this diff Show More