Merge branch 'release_2.4.0' into stable

This commit is contained in:
James Laska
2015-11-18 15:13:09 -05:00
4259 changed files with 203192 additions and 33870 deletions
+1 -1
View File
@@ -1,3 +1,3 @@
{ {
"directory": "awx/ui/static/lib" "directory": "awx/ui/client/lib"
} }
+2
View File
@@ -0,0 +1,2 @@
PYTHONUNBUFFERED=true
+9 -5
View File
@@ -12,18 +12,19 @@ awx/public/media
awx/public/static awx/public/static
awx/ui/tests/test-results.xml awx/ui/tests/test-results.xml
awx/ui/static/js/awx.min.js awx/ui/static/js/awx.min.js
awx/ui/static/js/local_config.js awx/ui/static/js/local_settings.json
awx/ui/client/src/local_settings.json
awx/ui/static/css/awx.min.css awx/ui/static/css/awx.min.css
awx/main/fixtures awx/main/fixtures
awx/*.log awx/*.log
tower/tower_warnings.log tower/tower_warnings.log
celerybeat-schedule celerybeat-schedule
awx/ui/static/docs awx/ui/static
awx/ui/dist awx/ui/build_test
# Python & setuptools # Python & setuptools
__pycache__ __pycache__
build /build
/deb-build /deb-build
/rpm-build /rpm-build
/tar-build /tar-build
@@ -37,12 +38,13 @@ build
/Brocfile.js /Brocfile.js
/bower.json /bower.json
/package.json /package.json
/testem.yml
node_modules/** node_modules/**
/tmp /tmp
npm-debug.log npm-debug.log
# UI build debugging # UI build debugging
/DEBUG-* /DEBUG
# Testing # Testing
.coverage .coverage
@@ -50,6 +52,8 @@ npm-debug.log
coverage.xml coverage.xml
htmlcov htmlcov
pep8.txt pep8.txt
scratch
testem.log
# Mac OS X # Mac OS X
*.DS_Store *.DS_Store
+3 -1
View File
@@ -3,7 +3,7 @@ recursive-include awx/static *.ico
recursive-include awx/templates *.html recursive-include awx/templates *.html
recursive-include awx/api/templates *.md *.html recursive-include awx/api/templates *.md *.html
recursive-include awx/ui/templates *.html recursive-include awx/ui/templates *.html
recursive-include awx/ui/dist * recursive-include awx/ui/static *
recursive-include awx/playbooks *.yml recursive-include awx/playbooks *.yml
recursive-include awx/lib/site-packages * recursive-include awx/lib/site-packages *
recursive-include requirements *.txt recursive-include requirements *.txt
@@ -12,8 +12,10 @@ recursive-include docs/licenses *
recursive-exclude awx devonly.py* recursive-exclude awx devonly.py*
recursive-exclude awx/api/tests * recursive-exclude awx/api/tests *
recursive-exclude awx/main/tests * recursive-exclude awx/main/tests *
recursive-exclude awx/ui/client *
recursive-exclude awx/settings local_settings.py* recursive-exclude awx/settings local_settings.py*
include tools/scripts/request_tower_configuration.sh include tools/scripts/request_tower_configuration.sh
include tools/scripts/request_tower_configuration.ps1
include tools/scripts/ansible-tower-service include tools/scripts/ansible-tower-service
include tools/munin_monitors/* include tools/munin_monitors/*
include tools/sosreport/* include tools/sosreport/*
+117 -41
View File
@@ -2,12 +2,18 @@ PYTHON = python
SITELIB=$(shell $(PYTHON) -c "from distutils.sysconfig import get_python_lib; print get_python_lib()") SITELIB=$(shell $(PYTHON) -c "from distutils.sysconfig import get_python_lib; print get_python_lib()")
OFFICIAL ?= no OFFICIAL ?= no
PACKER ?= packer PACKER ?= packer
PACKER_BUILD_OPTS ?= -var 'official=$(OFFICIAL)' -var 'aw_repo_url=$(AW_REPO_URL)'
GRUNT ?= $(shell [ -t 0 ] && echo "grunt" || echo "grunt --no-color") GRUNT ?= $(shell [ -t 0 ] && echo "grunt" || echo "grunt --no-color")
BROCCOLI ?= ./node_modules/.bin/broccoli TESTEM ?= ./node_modules/.bin/testem
TESTEM_DEBUG_BROWSER ?= Chrome
BROCCOLI_BIN ?= ./node_modules/.bin/broccoli
MOCHA_BIN ?= ./node_modules/.bin/mocha
NODE ?= node NODE ?= node
NPM_BIN ?= npm NPM_BIN ?= npm
DEPS_SCRIPT ?= packaging/bundle/deps.py DEPS_SCRIPT ?= packaging/bundle/deps.py
AW_REPO_URL ?= "http://releases.ansible.com/ansible-tower" GIT_BRANCH ?= $(shell git rev-parse --abbrev-ref HEAD)
CLIENT_TEST_DIR ?= build_test
# Determine appropriate shasum command # Determine appropriate shasum command
UNAME_S := $(shell uname -s) UNAME_S := $(shell uname -s)
@@ -28,8 +34,10 @@ GIT_REMOTE_URL = $(shell git config --get remote.origin.url)
BUILD = 0.git$(DATE) BUILD = 0.git$(DATE)
ifeq ($(OFFICIAL),yes) ifeq ($(OFFICIAL),yes)
RELEASE ?= 1 RELEASE ?= 1
AW_REPO_URL ?= http://releases.ansible.com/ansible-tower
else else
RELEASE ?= $(BUILD) RELEASE ?= $(BUILD)
AW_REPO_URL ?= http://jenkins.testing.ansible.com/ansible-tower_nightlies_RTYUIOPOIUYTYU/$(GIT_BRANCH)
endif endif
# Allow AMI license customization # Allow AMI license customization
@@ -52,11 +60,9 @@ endif
ifeq ($(OFFICIAL),yes) ifeq ($(OFFICIAL),yes)
SETUP_TAR_NAME=$(NAME)-setup-$(VERSION) SETUP_TAR_NAME=$(NAME)-setup-$(VERSION)
SDIST_TAR_NAME=$(NAME)-$(VERSION) SDIST_TAR_NAME=$(NAME)-$(VERSION)
PACKER_BUILD_OPTS=-var-file=vars-release.json
else else
SETUP_TAR_NAME=$(NAME)-setup-$(VERSION)-$(RELEASE) SETUP_TAR_NAME=$(NAME)-setup-$(VERSION)-$(RELEASE)
SDIST_TAR_NAME=$(NAME)-$(VERSION)-$(RELEASE) SDIST_TAR_NAME=$(NAME)-$(VERSION)-$(RELEASE)
PACKER_BUILD_OPTS=-var-file=vars-nightly.json
endif endif
SDIST_TAR_FILE=$(SDIST_TAR_NAME).tar.gz SDIST_TAR_FILE=$(SDIST_TAR_NAME).tar.gz
SETUP_TAR_FILE=$(SETUP_TAR_NAME).tar.gz SETUP_TAR_FILE=$(SETUP_TAR_NAME).tar.gz
@@ -98,14 +104,15 @@ MOCK_BIN ?= mock
MOCK_CFG ?= MOCK_CFG ?=
RPM_SPECDIR= packaging/rpm RPM_SPECDIR= packaging/rpm
RPM_SPEC = $(RPM_SPECDIR)/$(NAME).spec RPM_SPEC = $(RPM_SPECDIR)/$(NAME).spec
# Provide a fallback value for RPM_DIST
RPM_DIST ?= $(shell rpm --eval '%{?dist}' 2>/dev/null) RPM_DIST ?= $(shell rpm --eval '%{?dist}' 2>/dev/null)
# Provide a fallback value for RPM_DIST
ifeq ($(RPM_DIST),) ifeq ($(RPM_DIST),)
RPM_DIST = .el6 RPM_DIST = .el6
endif endif
RPM_ARCH ?= $(shell rpm --eval '%{_arch}' 2>/dev/null) RPM_ARCH ?= $(shell rpm --eval '%{_arch}' 2>/dev/null)
# Provide a fallback value for RPM_ARCH
ifeq ($(RPM_ARCH),) ifeq ($(RPM_ARCH),)
RPM_ARCH = $(shell uname -m) RPM_ARCH = $(shell uname -m)
endif endif
RPM_NVR = $(NAME)-$(VERSION)-$(RELEASE)$(RPM_DIST) RPM_NVR = $(NAME)-$(VERSION)-$(RELEASE)$(RPM_DIST)
@@ -147,7 +154,7 @@ endif
.PHONY: clean rebase push requirements requirements_dev requirements_jenkins \ .PHONY: clean rebase push requirements requirements_dev requirements_jenkins \
real-requirements real-requirements_dev real-requirements_jenkins \ real-requirements real-requirements_dev real-requirements_jenkins \
develop refresh adduser syncdb migrate dbchange dbshell runserver celeryd \ develop refresh adduser syncdb migrate dbchange dbshell runserver celeryd \
receiver test test_coverage coverage_html ui_analysis_report test_ui test_jenkins dev_build \ receiver test test_coverage coverage_html ui_analysis_report test_jenkins dev_build \
release_build release_clean sdist rpmtar mock-rpm mock-srpm rpm-sign \ release_build release_clean sdist rpmtar mock-rpm mock-srpm rpm-sign \
devjs minjs testjs testjs_ci node-tests browser-tests jshint ngdocs sync_ui \ devjs minjs testjs testjs_ci node-tests browser-tests jshint ngdocs sync_ui \
deb deb-src debian debsign pbuilder reprepro setup_tarball \ deb deb-src debian debsign pbuilder reprepro setup_tarball \
@@ -173,17 +180,20 @@ clean-grunt:
# Remove UI build files # Remove UI build files
clean-ui: clean-ui:
rm -rf awx/ui/static/dist rm -rf DEBUG
rm -rf awx/ui/build_test
rm -rf awx/ui/static/
rm -rf awx/ui/dist rm -rf awx/ui/dist
rm -rf awx/ui/static/docs
# Remove packer artifacts # Remove packer artifacts
clean-packer: clean-packer:
rm -rf packer_cache rm -rf packer_cache
rm -rf packaging/packer/packer_cache rm -rf packaging/packer/packer_cache
rm -rf packaging/packer/output-virtualbox-iso/ rm -rf packaging/packer/output-virtualbox-iso/
rm -rf packaging/packer/output-vmware-iso
rm -f packaging/packer/ansible-tower-*.box rm -f packaging/packer/ansible-tower-*.box
rm -rf packaging/packer/ansible-tower*-ova rm -rf packaging/packer/ansible-tower*-ova
rm -rf packaging/packer/ansible-tower*-vmx
rm -f Vagrantfile rm -f Vagrantfile
clean-bundle: clean-bundle:
@@ -192,7 +202,10 @@ clean-bundle:
# Remove temporary build files, compiled Python files. # Remove temporary build files, compiled Python files.
clean: clean-rpm clean-deb clean-grunt clean-ui clean-tar clean-packer clean-bundle clean: clean-rpm clean-deb clean-grunt clean-ui clean-tar clean-packer clean-bundle
rm -rf awx/lib/site-packages rm -rf awx/lib/site-packages
rm -rf awx/lib/.deps_built
rm -rf dist/* rm -rf dist/*
rm -rf tmp
mkdir tmp
rm -rf build $(NAME)-$(VERSION) *.egg-info rm -rf build $(NAME)-$(VERSION) *.egg-info
find . -type f -regex ".*\.py[co]$$" -delete find . -type f -regex ".*\.py[co]$$" -delete
@@ -295,6 +308,11 @@ server: server_noattach
servercc: server_noattach servercc: server_noattach
tmux -2 -CC attach-session -t tower tmux -2 -CC attach-session -t tower
# Alternate approach to tmux to run all development tasks specified in
# Procfile. https://youtu.be/OPMgaibszjk
honcho:
honcho start
# Run the built-in development webserver (by default on http://localhost:8013). # Run the built-in development webserver (by default on http://localhost:8013).
runserver: runserver:
$(PYTHON) manage.py runserver $(PYTHON) manage.py runserver
@@ -345,17 +363,6 @@ test_coverage:
coverage_html: coverage_html:
coverage html coverage html
ui_analysis_report: reports/ui_code node_modules Gruntfile.js
$(GRUNT) plato:report
reports/ui_code: node_modules clean-ui Brocfile.js bower.json Gruntfile.js
rm -rf reports/ui_code
$(BROCCOLI) build reports/ui_code -- --no-concat --no-tests --no-styles --no-sourcemaps
# Run UI unit tests
test_ui: node_modules minjs_ci Gruntfile.js
$(GRUNT) karma:ci
# Run API unit tests across multiple Python/Django versions with Tox. # Run API unit tests across multiple Python/Django versions with Tox.
test_tox: test_tox:
tox -v tox -v
@@ -364,44 +371,90 @@ test_tox:
test_jenkins: test_jenkins:
$(PYTHON) manage.py jenkins -v2 --enable-coverage --project-apps-tests $(PYTHON) manage.py jenkins -v2 --enable-coverage --project-apps-tests
Gruntfile.js: packaging/grunt/Gruntfile.js # UI TASKS
# --------------------------------------
Gruntfile.js: packaging/node/Gruntfile.js
cp $< $@ cp $< $@
Brocfile.js: packaging/grunt/Brocfile.js Brocfile.js: packaging/node/Brocfile.js
cp $< $@ cp $< $@
bower.json: packaging/grunt/bower.json bower.json: packaging/node/bower.json
cp $< $@ cp $< $@
package.json: packaging/grunt/package.template package.json: packaging/node/package.template
sed -e 's#%NAME%#$(NAME)#;s#%VERSION%#$(VERSION)#;s#%GIT_REMOTE_URL%#$(GIT_REMOTE_URL)#;' $< > $@ sed -e 's#%NAME%#$(NAME)#;s#%VERSION%#$(VERSION)#;s#%GIT_REMOTE_URL%#$(GIT_REMOTE_URL)#;' $< > $@
sync_ui: node_modules Brocfile.js testem.yml: packaging/node/testem.yml
$(NODE) tools/ui/timepiece.js awx/ui/dist -- --debug cp $< $@
# Update local npm install # Update local npm install
node_modules: package.json node_modules: package.json
$(NPM_BIN) install $(NPM_BIN) install
touch $@ touch $@
devjs: node_modules clean-ui Brocfile.js bower.json Gruntfile.js awx/ui/%: node_modules clean-ui Brocfile.js bower.json
$(BROCCOLI) build awx/ui/dist -- --debug $(BROCCOLI_BIN) build $@ -- $(UI_FLAGS)
# Build minified JS/CSS. # Concatenated, non-minified build; contains debug code and sourcemaps; does not include any tests
minjs: awx/ui/dist/tower.min.css.gz devjs: awx/ui/static
awx/ui/dist/tower.min.css.gz: node_modules Brocfile.js
$(BROCCOLI) build awx/ui/dist -- --silent --no-debug --no-tests --compress --no-docs --no-sourcemaps
minjs_ci: node_modules clean-ui Brocfile.js # Concatenated, minified, compressed (production) build with no sourcemaps or tests
$(BROCCOLI) build awx/ui/dist -- --no-debug --compress --no-docs minjs: UI_FLAGS=--silent --compress --no-docs --no-debug --no-sourcemaps $(EXTRA_UI_FLAGS)
minjs: awx/ui/static
# Performs build to awx/ui/build_test and runs node tests via mocha
testjs: UI_FLAGS=--node-tests --no-concat --no-styles $(EXTRA_UI_FLAGS)
testjs: awx/ui/build_test node-tests
# Performs nonminified, noncompressed build to awx/ui/static and runs browsers tests with testem ci
testjs_ci: UI_FLAGS=--no-styles --no-compress --browser-tests --no-node-tests --no-sourcemaps $(EXTRA_UI_FLAGS)
testjs_ci: awx/ui/static testem.yml browser-tests-ci
# Performs nonminified, noncompressed build to awx/ui/static and runs browsers tests with testem ci in Chrome
testjs_debug: UI_FLAGS=--no-styles --no-compress --browser-tests --no-node-tests --no-sourcemaps $(EXTRA_UI_FLAGS)
testjs_debug: awx/ui/static testem.yml browser-tests-debug
# Runs node tests via mocha without building
node-tests:
NODE_PATH=awx/ui/build_test $(MOCHA_BIN) --full-trace $(shell find awx/ui/build_test -name '*-test.js') $(MOCHA_FLAGS)
# Runs browser tests on PhantomJS. Outputs the results in a consumable manner for Jenkins.
browser-tests-ci:
PATH=./node_modules/.bin:$(PATH) $(TESTEM) ci --file testem.yml -p 7359 -R xunit
# Runs browser tests using settings from `testem.yml` you can pass in the browser you'd
# like to run the tests on (Defaults to Chrome, other options Safari, Firefox, and PhantomJS).
# If you want to run the tests in Node (which is the quickest, but also more difficult to debug),
# make sure to run the testjs/node-tests targets
browser-tests-debug:
PATH=./node_modules/.bin:$(PATH) $(TESTEM) --file testem.yml -l $(TESTEM_DEBUG_BROWSER)
# Check .js files for errors and lint # Check .js files for errors and lint
jshint: node_modules Gruntfile.js jshint: node_modules Gruntfile.js
$(GRUNT) $@ $(GRUNT) $@
# Generate UI code documentation
ngdocs: devjs Gruntfile.js ngdocs: devjs Gruntfile.js
$(GRUNT) $@ $(GRUNT) $@
# Launch watcher for build process
sync_ui: node_modules Brocfile.js testem.yml
$(NODE) tools/ui/timepiece.js awx/ui/static $(WATCHER_FLAGS) -- $(UI_FLAGS)
# Build code complexity report for UI code
ui_analysis_report: reports/ui_code node_modules Gruntfile.js
$(GRUNT) plato:report
# Non-concatenated, non-minified build with no tests, no debug code, no sourcemaps for plato reports
reports/ui_code: node_modules clean-ui Brocfile.js bower.json Gruntfile.js
rm -rf reports/ui_code
$(BROCCOLI_BIN) build reports/ui_code -- --no-concat --no-debug --no-styles --no-sourcemaps
# END UI TASKS
# --------------------------------------
# Build a pip-installable package into dist/ with a timestamped version number. # Build a pip-installable package into dist/ with a timestamped version number.
dev_build: dev_build:
$(PYTHON) setup.py dev_build $(PYTHON) setup.py dev_build
@@ -438,7 +491,7 @@ release_clean:
-(rm *.tar) -(rm *.tar)
-(rm -rf ($RELEASE)) -(rm -rf ($RELEASE))
dist/$(SDIST_TAR_FILE): awx/ui/dist/tower.min.css.gz dist/$(SDIST_TAR_FILE): minjs
BUILD="$(BUILD)" $(PYTHON) setup.py sdist BUILD="$(BUILD)" $(PYTHON) setup.py sdist
sdist: dist/$(SDIST_TAR_FILE) sdist: dist/$(SDIST_TAR_FILE)
@@ -598,7 +651,7 @@ reprepro: deb-build/$(DEB_NVRA).deb reprepro/conf
$(REPREPRO_BIN) $(REPREPRO_OPTS) clearvanished $(REPREPRO_BIN) $(REPREPRO_OPTS) clearvanished
for COMPONENT in non-free $(VERSION); do \ for COMPONENT in non-free $(VERSION); do \
$(REPREPRO_BIN) $(REPREPRO_OPTS) -C $$COMPONENT remove $(DEB_DIST) $(NAME) ; \ $(REPREPRO_BIN) $(REPREPRO_OPTS) -C $$COMPONENT remove $(DEB_DIST) $(NAME) ; \
$(REPREPRO_BIN) $(REPREPRO_OPTS) -C $$COMPONENT --keepunreferencedfiles --ignore=brokenold includedeb $(DEB_DIST) deb-build/$(DEB_NVRA).deb ; \ $(REPREPRO_BIN) $(REPREPRO_OPTS) -C $$COMPONENT --ignore=brokenold includedeb $(DEB_DIST) deb-build/$(DEB_NVRA).deb ; \
done done
@@ -609,6 +662,7 @@ reprepro: deb-build/$(DEB_NVRA).deb reprepro/conf
amazon-ebs: amazon-ebs:
cd packaging/packer && $(PACKER) build -only $@ $(PACKER_BUILD_OPTS) -var "aws_instance_count=$(AWS_INSTANCE_COUNT)" -var "product_version=$(VERSION)" packer-$(NAME).json cd packaging/packer && $(PACKER) build -only $@ $(PACKER_BUILD_OPTS) -var "aws_instance_count=$(AWS_INSTANCE_COUNT)" -var "product_version=$(VERSION)" packer-$(NAME).json
# virtualbox
virtualbox-ovf: packaging/packer/ansible-tower-$(VERSION)-virtualbox.box virtualbox-ovf: packaging/packer/ansible-tower-$(VERSION)-virtualbox.box
packaging/packer/ansible-tower-$(VERSION)-virtualbox.box: packaging/packer/output-virtualbox-iso/centos-7.ovf packaging/packer/ansible-tower-$(VERSION)-virtualbox.box: packaging/packer/output-virtualbox-iso/centos-7.ovf
@@ -617,12 +671,22 @@ packaging/packer/ansible-tower-$(VERSION)-virtualbox.box: packaging/packer/outpu
packaging/packer/output-virtualbox-iso/centos-6.ovf: packaging/packer/output-virtualbox-iso/centos-6.ovf:
cd packaging/packer && $(PACKER) build packer-centos-6.json cd packaging/packer && $(PACKER) build packer-centos-6.json
virtualbox-centos-6: packaging/packer/output-virtualbox-iso/centos-6.ovf
packaging/packer/output-virtualbox-iso/centos-7.ovf: packaging/packer/output-virtualbox-iso/centos-7.ovf:
cd packaging/packer && $(PACKER) build packer-centos-7.json cd packaging/packer && $(PACKER) build -only virtualbox-iso packer-centos-7.json
virtualbox-centos-7: packaging/packer/output-virtualbox-iso/centos-7.ovf # virtualbox-iso: packaging/packer/output-virtualbox-iso/centos-6.ovf
virtualbox-iso: packaging/packer/output-virtualbox-iso/centos-7.ovf
# vmware
packaging/packer/output-vmware-iso/centos-7.vmx:
cd packaging/packer && $(PACKER) build -only vmware-iso packer-centos-7.json
vmware-iso: packaging/packer/output-vmware-iso/centos-7.vmx
vmware-vmx: packaging/packer/ansible-tower-$(VERSION)-vmx/ansible-tower-$(VERSION).vmx
packaging/packer/ansible-tower-$(VERSION)-vmx/ansible-tower-$(VERSION).vmx: packaging/packer/output-vmware-iso/centos-7.vmx
cd packaging/packer && $(PACKER) build -only vmware-vmx $(PACKER_BUILD_OPTS) -var "aws_instance_count=$(AWS_INSTANCE_COUNT)" -var "product_version=$(VERSION)" packer-$(NAME).json
# TODO - figure out how to build the front-end and python requirements with # TODO - figure out how to build the front-end and python requirements with
# 'build' # 'build'
@@ -635,3 +699,15 @@ install:
# Docker Compose Development environment # Docker Compose Development environment
docker-compose: docker-compose:
docker-compose -f tools/docker-compose.yml up --no-recreate docker-compose -f tools/docker-compose.yml up --no-recreate
docker-compose-test:
cd tools && docker-compose run --rm --service-ports tower /bin/bash
mongo-debug-ui:
docker run -it --rm --name mongo-express --link tools_mongo_1:mongo -e ME_CONFIG_OPTIONS_EDITORTHEME=ambiance -e ME_CONFIG_BASICAUTH_USERNAME=admin -e ME_CONFIG_BASICAUTH_PASSWORD=password -p 8081:8081 knickers/mongo-express
mongo-container:
docker run -it --link tools_mongo_1:mongo --rm mongo sh -c 'exec mongo "$MONGO_PORT_27017_TCP_ADDR:$MONGO_PORT_27017_TCP_PORT/system_tracking_dev"'
psql-container:
docker run -it --link tools_postgres_1:postgres --rm postgres:9.4.1 sh -c 'exec psql -h "$$POSTGRES_PORT_5432_TCP_ADDR" -p "$$POSTGRES_PORT_5432_TCP_PORT" -U postgres'
+6
View File
@@ -0,0 +1,6 @@
runserver: make runserver
celeryd: make celeryd
taskmanager: make taskmanager
receiver: make receiver
socketservice: make socketservice
factcacher: make factcacher
+7 -1
View File
@@ -4,7 +4,7 @@ Ansible Tower
Tower provides a web-based user interface, REST API and task engine built on top of Tower provides a web-based user interface, REST API and task engine built on top of
Ansible. Ansible.
The current version under development is 2.1.0. The current version under development is 2.2.1.
Development releases always use the 'master' branch. Development releases always use the 'master' branch.
@@ -31,10 +31,14 @@ Release History
* 2.1.2, March 25, 2015 * 2.1.2, March 25, 2015
* 2.1.3, April 15, 2015 * 2.1.3, April 15, 2015
* 2.1.4, June 12, 2015 * 2.1.4, June 12, 2015
* 2.1.5, June 15, 2015
* 2.1.6, June 23, 2015
* 2.2.0, July 14, 2015 * 2.2.0, July 14, 2015
* 2.2.1, August 12, 2015 * 2.2.1, August 12, 2015
* 2.2.2, August 19, 2015 * 2.2.2, August 19, 2015
* 2.3.0, September 22, 2015 * 2.3.0, September 22, 2015
* 2.3.1, October 2, 2015
* 2.4.0, November 14, 2015
Any fixes should be applied on the appropriate release branch and be cherry-picked to Any fixes should be applied on the appropriate release branch and be cherry-picked to
master. master.
@@ -49,3 +53,5 @@ Refer to `setup/README.md` to get started deploying Tower.
Refer to `docs/build_system.md` for more about Jenkins and installing nightly builds (as opposed to running from source). Refer to `docs/build_system.md` for more about Jenkins and installing nightly builds (as opposed to running from source).
Refer to `docs/release_process.md` for information on the steps involved in creating a release. Refer to `docs/release_process.md` for information on the steps involved in creating a release.
Refer to http://docs.ansible.com/ansible-tower/index.html for information on installing/upgrading, setup, troubleshooting, and much more.
+1 -1
View File
@@ -6,7 +6,7 @@ import sys
import warnings import warnings
import site import site
__version__ = '2.3.1' __version__ = '2.4.0'
__all__ = ['__version__'] __all__ = ['__version__']
+55 -12
View File
@@ -1,6 +1,13 @@
# Copyright (c) 2015 Ansible, Inc. # Copyright (c) 2015 Ansible, Inc.
# All Rights Reserved. # All Rights Reserved.
# Python
import urllib
# Django
from django.utils.timezone import now as tz_now
from django.conf import settings
# Django REST Framework # Django REST Framework
from rest_framework import authentication from rest_framework import authentication
from rest_framework import exceptions from rest_framework import exceptions
@@ -18,24 +25,38 @@ class TokenAuthentication(authentication.TokenAuthentication):
model = AuthToken model = AuthToken
def _get_x_auth_token_header(self, request): @staticmethod
def _get_x_auth_token_header(request):
auth = request.META.get('HTTP_X_AUTH_TOKEN', '') auth = request.META.get('HTTP_X_AUTH_TOKEN', '')
if isinstance(auth, type('')): if isinstance(auth, type('')):
# Work around django test client oddness # Work around django test client oddness
auth = auth.encode(HTTP_HEADER_ENCODING) auth = auth.encode(HTTP_HEADER_ENCODING)
return auth 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): def authenticate(self, request):
self.request = request self.request = request
# Prefer the custom X-Auth-Token header over the Authorization header, # Prefer the custom X-Auth-Token header over the Authorization header,
# to handle cases where the browser submits saved Basic auth and # to handle cases where the browser submits saved Basic auth and
# overrides the UI's normal use of the Authorization header. # overrides the UI's normal use of the Authorization header.
auth = self._get_x_auth_token_header(request).split() auth = TokenAuthentication._get_x_auth_token_header(request).split()
if not auth or auth[0].lower() != 'token': if not auth or auth[0].lower() != 'token':
auth = authentication.get_authorization_header(request).split() auth = authentication.get_authorization_header(request).split()
if not auth or auth[0].lower() != 'token': # Prefer basic auth over cookie token
if auth and auth[0].lower() == 'basic':
return None 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: if len(auth) == 1:
msg = 'Invalid token header. No credentials provided.' msg = 'Invalid token header. No credentials provided.'
@@ -47,6 +68,7 @@ class TokenAuthentication(authentication.TokenAuthentication):
return self.authenticate_credentials(auth[1]) return self.authenticate_credentials(auth[1])
def authenticate_credentials(self, key): def authenticate_credentials(self, key):
now = tz_now()
# Retrieve the request hash and token. # Retrieve the request hash and token.
try: try:
request_hash = self.model.get_request_hash(self.request) request_hash = self.model.get_request_hash(self.request)
@@ -55,25 +77,46 @@ class TokenAuthentication(authentication.TokenAuthentication):
request_hash=request_hash, request_hash=request_hash,
) )
except self.model.DoesNotExist: except self.model.DoesNotExist:
raise exceptions.AuthenticationFailed('Invalid token') raise exceptions.AuthenticationFailed(AuthToken.reason_long('invalid_token'))
# Sanity check: Ensure that the token is still valid. # Tell the user why their token was previously invalidated.
# Tokens expire if they are not used for 30 minutes. if token.invalidated:
if token.expired: raise exceptions.AuthenticationFailed(AuthToken.reason_long(token.reason))
raise exceptions.AuthenticationFailed('Token is expired')
# Sanity check: If the user is inactive, then return an error. # 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: if not token.user.is_active:
raise exceptions.AuthenticationFailed('User inactive or deleted') raise exceptions.AuthenticationFailed('User inactive or deleted')
# Refresh the token. # Refresh the token.
# This updates the time that the token was last used, meaning that # The token is extended from "right now" + configurable setting amount.
# now the token is valid for 30 minutes from "right now". token.refresh(now=now)
token.refresh()
# Return the user object and the token. # Return the user object and the token.
return (token.user, 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 TaskAuthentication(authentication.BaseAuthentication): class TaskAuthentication(authentication.BaseAuthentication):
''' '''
Custom authentication used for views accessed by the inventory and callback Custom authentication used for views accessed by the inventory and callback
+3 -1
View File
@@ -142,6 +142,8 @@ class APIView(views.APIView):
'new_in_200': getattr(self, 'new_in_200', False), 'new_in_200': getattr(self, 'new_in_200', False),
'new_in_210': getattr(self, 'new_in_210', False), 'new_in_210': getattr(self, 'new_in_210', False),
'new_in_220': getattr(self, 'new_in_220', False), 'new_in_220': getattr(self, 'new_in_220', False),
'new_in_230': getattr(self, 'new_in_230', False),
'new_in_240': getattr(self, 'new_in_240', False),
} }
def get_description(self, html=False): def get_description(self, html=False):
@@ -158,7 +160,7 @@ class APIView(views.APIView):
''' '''
ret = super(APIView, self).metadata(request) ret = super(APIView, self).metadata(request)
added_in_version = '1.2' added_in_version = '1.2'
for version in ('2.2.0', '2.1.0', '2.0.0', '1.4.8', '1.4.5', '1.4', '1.3'): for version in ('2.4.0', '2.3.0', '2.2.0', '2.1.0', '2.0.0', '1.4.8', '1.4.5', '1.4', '1.3'):
if getattr(self, 'new_in_%s' % version.replace('.', ''), False): if getattr(self, 'new_in_%s' % version.replace('.', ''), False):
added_in_version = version added_in_version = version
break break
+14 -1
View File
@@ -31,4 +31,17 @@ def feature_enabled(name):
return False return False
# Return the correct feature flag. # Return the correct feature flag.
return get_license()['features'].get(name, False) return license['features'].get(name, False)
def feature_exists(name):
"""Return True if the requested feature is enabled, False otherwise.
If the feature does not exist, raise KeyError.
"""
license = get_license()
# Sanity check: If there is no license, the feature is considered
# to be off.
if 'features' not in license:
return False
return name in license['features']
+19 -2
View File
@@ -601,6 +601,8 @@ class UserSerializer(BaseSerializer):
ret = super(UserSerializer, self).to_native(obj) ret = super(UserSerializer, self).to_native(obj)
ret.pop('password', None) ret.pop('password', None)
ret.fields.pop('password', None) ret.fields.pop('password', None)
if obj:
ret['auth'] = obj.social_auth.values('provider', 'uid')
return ret return ret
def get_validation_exclusions(self): def get_validation_exclusions(self):
@@ -628,6 +630,12 @@ class UserSerializer(BaseSerializer):
new_password = None new_password = None
except AttributeError: except AttributeError:
pass pass
if (getattr(settings, 'SOCIAL_AUTH_GOOGLE_OAUTH2_KEY', None) or
getattr(settings, 'SOCIAL_AUTH_GITHUB_KEY', None) or
getattr(settings, 'SOCIAL_AUTH_GITHUB_ORG_KEY', None) or
getattr(settings, 'SOCIAL_AUTH_GITHUB_TEAM_KEY', None) or
getattr(settings, 'SOCIAL_AUTH_SAML_ENABLED_IDPS', None)) and obj.social_auth.all():
new_password = None
if new_password: if new_password:
obj.set_password(new_password) obj.set_password(new_password)
if not obj.password: if not obj.password:
@@ -1349,6 +1357,7 @@ class CredentialSerializer(BaseSerializer):
# FIXME: may want to make some of these filtered based on user accessing # FIXME: may want to make some of these filtered based on user accessing
password = serializers.CharField(required=False, default='') password = serializers.CharField(required=False, default='')
security_token = serializers.CharField(required=False, default='')
ssh_key_data = serializers.CharField(required=False, default='') ssh_key_data = serializers.CharField(required=False, default='')
ssh_key_unlock = serializers.CharField(required=False, default='') ssh_key_unlock = serializers.CharField(required=False, default='')
become_password = serializers.CharField(required=False, default='') become_password = serializers.CharField(required=False, default='')
@@ -1357,7 +1366,7 @@ class CredentialSerializer(BaseSerializer):
class Meta: class Meta:
model = Credential model = Credential
fields = ('*', 'user', 'team', 'kind', 'cloud', 'host', 'username', fields = ('*', 'user', 'team', 'kind', 'cloud', 'host', 'username',
'password', 'project', 'ssh_key_data', 'ssh_key_unlock', 'password', 'security_token', 'project', 'ssh_key_data', 'ssh_key_unlock',
'become_method', 'become_username', 'become_password', 'become_method', 'become_username', 'become_password',
'vault_password') 'vault_password')
@@ -2001,11 +2010,12 @@ class ScheduleSerializer(BaseSerializer):
class ActivityStreamSerializer(BaseSerializer): class ActivityStreamSerializer(BaseSerializer):
changes = serializers.SerializerMethodField('get_changes') changes = serializers.SerializerMethodField('get_changes')
object_association = serializers.SerializerMethodField('get_object_association')
class Meta: class Meta:
model = ActivityStream model = ActivityStream
fields = ('*', '-name', '-description', '-created', '-modified', fields = ('*', '-name', '-description', '-created', '-modified',
'timestamp', 'operation', 'changes', 'object1', 'object2') 'timestamp', 'operation', 'changes', 'object1', 'object2', 'object_association')
def get_fields(self): def get_fields(self):
ret = super(ActivityStreamSerializer, self).get_fields() ret = super(ActivityStreamSerializer, self).get_fields()
@@ -2030,6 +2040,13 @@ class ActivityStreamSerializer(BaseSerializer):
logger.warn("Error deserializing activity stream json changes") logger.warn("Error deserializing activity stream json changes")
return {} return {}
def get_object_association(self, obj):
try:
return obj.object_relationship_type.split(".")[-1].split("_")[1]
except:
pass
return ""
def get_related(self, obj): def get_related(self, obj):
rel = {} rel = {}
if obj.actor is not None: if obj.actor is not None:
+3 -1
View File
@@ -3,4 +3,6 @@
{% if new_in_145 %}> _Added in Ansible Tower 1.4.5_{% endif %} {% if new_in_145 %}> _Added in Ansible Tower 1.4.5_{% endif %}
{% if new_in_148 %}> _Added in Ansible Tower 1.4.8_{% endif %} {% if new_in_148 %}> _Added in Ansible Tower 1.4.8_{% endif %}
{% if new_in_200 %}> _New in Ansible Tower 2.0.0_{% endif %} {% if new_in_200 %}> _New in Ansible Tower 2.0.0_{% endif %}
{% if new_in_220 %}> _New in Ansible Tower 2.2.0_{% endif %} {% if new_in_220 %}> _New in Ansible Tower 2.2.0_{% endif %}
{% if new_in_230 %}> _New in Ansible Tower 2.3.0_{% endif %}
{% if new_in_240 %}> _New in Ansible Tower 2.4.0_{% endif %}
+1
View File
@@ -224,6 +224,7 @@ v1_urls = patterns('awx.api.views',
url(r'^$', 'api_v1_root_view'), url(r'^$', 'api_v1_root_view'),
url(r'^ping/$', 'api_v1_ping_view'), url(r'^ping/$', 'api_v1_ping_view'),
url(r'^config/$', 'api_v1_config_view'), url(r'^config/$', 'api_v1_config_view'),
url(r'^auth/$', 'auth_view'),
url(r'^authtoken/$', 'auth_token_view'), url(r'^authtoken/$', 'auth_token_view'),
url(r'^me/$', 'user_me_list'), url(r'^me/$', 'user_me_list'),
url(r'^dashboard/$', 'dashboard_view'), url(r'^dashboard/$', 'dashboard_view'),
+78 -12
View File
@@ -11,6 +11,7 @@ import time
import socket import socket
import sys import sys
import errno import errno
from base64 import b64encode
# Django # Django
from django.conf import settings from django.conf import settings
@@ -47,23 +48,27 @@ import qsstats
# ANSIConv # ANSIConv
import ansiconv import ansiconv
# Python Social Auth
from social.backends.utils import load_backends
# AWX # AWX
from awx.main.task_engine import TaskSerializer, TASK_FILE, TEMPORARY_TASK_FILE from awx.main.task_engine import TaskSerializer, TASK_FILE, TEMPORARY_TASK_FILE
from awx.main.tasks import mongodb_control from awx.main.tasks import mongodb_control
from awx.main.access import get_user_queryset from awx.main.access import get_user_queryset
from awx.main.ha import is_ha_environment from awx.main.ha import is_ha_environment
from awx.api.authentication import TaskAuthentication from awx.api.authentication import TaskAuthentication, TokenGetAuthentication
from awx.api.utils.decorators import paginated from awx.api.utils.decorators import paginated
from awx.api.filters import MongoFilterBackend from awx.api.filters import MongoFilterBackend
from awx.api.generics import get_view_name from awx.api.generics import get_view_name
from awx.api.generics import * # noqa from awx.api.generics import * # noqa
from awx.api.license import feature_enabled, LicenseForbids from awx.api.license import feature_enabled, feature_exists, LicenseForbids
from awx.main.models import * # noqa from awx.main.models import * # noqa
from awx.main.utils import * # noqa from awx.main.utils import * # noqa
from awx.api.permissions import * # noqa from awx.api.permissions import * # noqa
from awx.api.renderers import * # noqa from awx.api.renderers import * # noqa
from awx.api.serializers import * # noqa from awx.api.serializers import * # noqa
from awx.fact.models import * # noqa from awx.fact.models import * # noqa
from awx.main.utils import emit_websocket_notification
def api_exception_handler(exc): def api_exception_handler(exc):
''' '''
@@ -186,12 +191,15 @@ class ApiV1ConfigView(APIView):
license_reader = TaskSerializer() license_reader = TaskSerializer()
license_data = license_reader.from_file(show_key=request.user.is_superuser) license_data = license_reader.from_file(show_key=request.user.is_superuser)
pendo_state = settings.PENDO_TRACKING_STATE if settings.PENDO_TRACKING_STATE in ('off', 'anonymous', 'detailed') else 'off'
data = dict( data = dict(
time_zone=settings.TIME_ZONE, time_zone=settings.TIME_ZONE,
license_info=license_data, license_info=license_data,
version=get_awx_version(), version=get_awx_version(),
ansible_version=get_ansible_version(), ansible_version=get_ansible_version(),
eula=render_to_string("eula.md"), eula=render_to_string("eula.md"),
analytics_status=pendo_state
) )
# If LDAP is enabled, user_ldap_fields will return a list of field # If LDAP is enabled, user_ldap_fields will return a list of field
@@ -510,6 +518,45 @@ class ScheduleUnifiedJobsList(SubListAPIView):
view_name = 'Schedule Jobs List' view_name = 'Schedule Jobs List'
new_in_148 = True new_in_148 = True
class AuthView(APIView):
authentication_classes = []
permission_classes = (AllowAny,)
new_in_240 = True
def get(self, request):
data = SortedDict()
err_backend, err_message = request.session.get('social_auth_error', (None, None))
auth_backends = load_backends(settings.AUTHENTICATION_BACKENDS).items()
# Return auth backends in consistent order: Google, GitHub, SAML.
auth_backends.sort(key=lambda x: 'g' if x[0] == 'google-oauth2' else x[0])
for name, backend in auth_backends:
if (not feature_exists('enterprise_auth') and
not feature_enabled('ldap')) or \
(not feature_enabled('enterprise_auth') and
name in ['saml', 'radius']):
continue
login_url = reverse('social:begin', args=(name,))
complete_url = request.build_absolute_uri(reverse('social:complete', args=(name,)))
backend_data = {
'login_url': login_url,
'complete_url': complete_url,
}
if name == 'saml':
backend_data['metadata_url'] = reverse('sso:saml_metadata')
for idp in sorted(settings.SOCIAL_AUTH_SAML_ENABLED_IDPS.keys()):
saml_backend_data = dict(backend_data.items())
saml_backend_data['login_url'] = '%s?idp=%s' % (login_url, idp)
full_backend_name = '%s:%s' % (name, idp)
if err_backend == full_backend_name and err_message:
saml_backend_data['error'] = err_message
data[full_backend_name] = saml_backend_data
else:
if err_backend == name and err_message:
backend_data['error'] = err_message
data[name] = backend_data
return Response(data)
class AuthTokenView(APIView): class AuthTokenView(APIView):
authentication_classes = [] authentication_classes = []
@@ -524,12 +571,30 @@ class AuthTokenView(APIView):
try: try:
token = AuthToken.objects.filter(user=serializer.object['user'], token = AuthToken.objects.filter(user=serializer.object['user'],
request_hash=request_hash, request_hash=request_hash,
expires__gt=now())[0] expires__gt=now(),
reason='')[0]
token.refresh() token.refresh()
except IndexError: except IndexError:
token = AuthToken.objects.create(user=serializer.object['user'], token = AuthToken.objects.create(user=serializer.object['user'],
request_hash=request_hash) request_hash=request_hash)
return Response({'token': token.key, 'expires': token.expires}) # 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.object['user'])
for t in invalid_tokens:
# TODO: send socket notification
emit_websocket_notification('/socket.io/control',
'limit_reached',
dict(reason=unicode(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)
}
return Response({'token': token.key, 'expires': token.expires}, headers=headers)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
class OrganizationList(ListCreateAPIView): class OrganizationList(ListCreateAPIView):
@@ -2092,12 +2157,6 @@ class SystemJobTemplateSchedulesList(SubListCreateAttachDetachAPIView):
relationship = 'schedules' relationship = 'schedules'
parent_key = 'unified_job_template' parent_key = 'unified_job_template'
def post(self, request, *args, **kwargs):
system_job = self.get_parent_object()
if system_job.schedules.filter(active=True).count() > 0:
return Response({"error": "Multiple schedules for Systems Jobs is not allowed"}, status=status.HTTP_400_BAD_REQUEST)
return super(SystemJobTemplateSchedulesList, self).post(request, *args, **kwargs)
class SystemJobTemplateJobsList(SubListAPIView): class SystemJobTemplateJobsList(SubListAPIView):
model = SystemJob model = SystemJob
@@ -2207,6 +2266,7 @@ class JobRelaunch(RetrieveAPIView, GenericAPIView):
if not serializer.is_valid(): if not serializer.is_valid():
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
obj.launch_type = 'relaunch'
new_job = obj.copy() new_job = obj.copy()
result = new_job.signal_start(**request.DATA) result = new_job.signal_start(**request.DATA)
if not result: if not result:
@@ -2788,6 +2848,7 @@ class UnifiedJobList(ListAPIView):
class UnifiedJobStdout(RetrieveAPIView): class UnifiedJobStdout(RetrieveAPIView):
authentication_classes = [TokenGetAuthentication] + api_settings.DEFAULT_AUTHENTICATION_CLASSES
serializer_class = UnifiedJobStdoutSerializer serializer_class = UnifiedJobStdoutSerializer
renderer_classes = [BrowsableAPIRenderer, renderers.StaticHTMLRenderer, renderer_classes = [BrowsableAPIRenderer, renderers.StaticHTMLRenderer,
PlainTextRenderer, AnsiTextRenderer, PlainTextRenderer, AnsiTextRenderer,
@@ -2804,8 +2865,10 @@ class UnifiedJobStdout(RetrieveAPIView):
return Response({'range': {'start': 0, 'end': 1, 'absolute_end': 1}, 'content': response_message}) return Response({'range': {'start': 0, 'end': 1, 'absolute_end': 1}, 'content': response_message})
else: else:
return Response(response_message) return Response(response_message)
if request.accepted_renderer.format in ('html', 'api', 'json'): if request.accepted_renderer.format in ('html', 'api', 'json'):
content_format = request.QUERY_PARAMS.get('content_format', 'html')
content_encoding = request.QUERY_PARAMS.get('content_encoding', None)
start_line = request.QUERY_PARAMS.get('start_line', 0) start_line = request.QUERY_PARAMS.get('start_line', 0)
end_line = request.QUERY_PARAMS.get('end_line', None) end_line = request.QUERY_PARAMS.get('end_line', None)
dark_val = request.QUERY_PARAMS.get('dark', '') dark_val = request.QUERY_PARAMS.get('dark', '')
@@ -2826,7 +2889,10 @@ class UnifiedJobStdout(RetrieveAPIView):
if request.accepted_renderer.format == 'api': if request.accepted_renderer.format == 'api':
return Response(mark_safe(data)) return Response(mark_safe(data))
if request.accepted_renderer.format == 'json': if request.accepted_renderer.format == 'json':
return Response({'range': {'start': start, 'end': end, 'absolute_end': absolute_end}, 'content': body}) if content_encoding == 'base64' and content_format == 'ansi':
return Response({'range': {'start': start, 'end': end, 'absolute_end': absolute_end}, 'content': b64encode(content)})
elif content_format == 'html':
return Response({'range': {'start': start, 'end': end, 'absolute_end': absolute_end}, 'content': body})
return Response(data) return Response(data)
elif request.accepted_renderer.format == 'ansi': elif request.accepted_renderer.format == 'ansi':
return Response(unified_job.result_stdout_raw) return Response(unified_job.result_stdout_raw)
-29
View File
@@ -1,31 +1,2 @@
# Copyright (c) 2015 Ansible, Inc. # Copyright (c) 2015 Ansible, Inc.
# All Rights Reserved. # All Rights Reserved.
from __future__ import absolute_import
import logging
from django.conf import settings
from mongoengine import connect
from mongoengine.connection import get_db, ConnectionError
from .utils.dbtransform import register_key_transform
logger = logging.getLogger('awx.fact')
# Connect to Mongo
try:
# Sanity check: If we have intentionally invalid settings, then we
# know we cannot connect.
if settings.MONGO_HOST == NotImplemented:
raise ConnectionError
# Attempt to connect to the MongoDB database.
connect(settings.MONGO_DB,
host=settings.MONGO_HOST,
port=int(settings.MONGO_PORT),
username=settings.MONGO_USERNAME,
password=settings.MONGO_PASSWORD,
tz_aware=settings.USE_TZ)
register_key_transform(get_db())
except ConnectionError:
logger.info('Failed to establish connect to MongoDB')
+35 -1
View File
@@ -1,12 +1,46 @@
# Copyright (c) 2015 Ansible, Inc. # Copyright (c) 2015 Ansible, Inc.
# All Rights Reserved # All Rights Reserved
from mongoengine import connect
from mongoengine.base import BaseField from mongoengine.base import BaseField
from mongoengine import Document, DateTimeField, ReferenceField, StringField, IntField from mongoengine import Document, DateTimeField, ReferenceField, StringField, IntField
from awx.fact.utils.dbtransform import KeyTransform from mongoengine.connection import get_db, ConnectionError
from awx.fact.utils.dbtransform import register_key_transform, KeyTransform
from django.conf import settings
import logging
logger = logging.getLogger('awx.fact.models.fact')
key_transform = KeyTransform([('.', '\uff0E'), ('$', '\uff04')]) key_transform = KeyTransform([('.', '\uff0E'), ('$', '\uff04')])
# NOTE: I think it might be better to use register_connection here: https://github.com/MongoEngine/mongoengine/blob/0.9/mongoengine/connection.py#L21
# but I'm not doing that because I don't see how we can also register the key transform as needed or set the tz_aware preference
@classmethod
def _get_db_monkeypatched(cls):
""" Override the default _get_db mechanism to start a connection to the database """
# Connect to Mongo
try:
# Sanity check: If we have intentionally invalid settings, then we
# know we cannot connect.
if settings.MONGO_HOST == NotImplemented:
raise ConnectionError
# Attempt to connect to the MongoDB database.
connect(settings.MONGO_DB,
host=settings.MONGO_HOST,
port=int(settings.MONGO_PORT),
username=settings.MONGO_USERNAME,
password=settings.MONGO_PASSWORD,
tz_aware=settings.USE_TZ)
register_key_transform(get_db())
except ConnectionError:
logger.info('Failed to establish connect to MongoDB')
return get_db(cls._meta.get("db_alias", "default"))
Document._get_db = _get_db_monkeypatched
class TransformField(BaseField): class TransformField(BaseField):
def to_python(self, value): def to_python(self, value):
return key_transform.transform_outgoing(value, None) return key_transform.transform_outgoing(value, None)
File diff suppressed because it is too large Load Diff
+32 -2
View File
@@ -3,10 +3,12 @@
# Python # Python
from __future__ import absolute_import from __future__ import absolute_import
from django.utils.timezone import now import os
from dateutil.relativedelta import relativedelta import json
# Django # Django
from django.utils.timezone import now
from dateutil.relativedelta import relativedelta
# AWX # AWX
from awx.fact.models.fact import * # noqa from awx.fact.models.fact import * # noqa
@@ -14,6 +16,13 @@ from awx.fact.tests.base import BaseFactTest, FactScanBuilder, TEST_FACT_PACKAGE
__all__ = ['FactHostTest', 'FactTest', 'FactGetHostVersionTest', 'FactGetHostTimelineTest'] __all__ = ['FactHostTest', 'FactTest', 'FactGetHostVersionTest', 'FactGetHostTimelineTest']
# damn you python 2.6
def timedelta_total_seconds(timedelta):
return (
timedelta.microseconds + 0.0 +
(timedelta.seconds + timedelta.days * 24 * 3600) * 10 ** 6) / 10 ** 6
class FactHostTest(BaseFactTest): class FactHostTest(BaseFactTest):
def test_create_host(self): def test_create_host(self):
host = FactHost(hostname='hosty', inventory_id=1) host = FactHost(hostname='hosty', inventory_id=1)
@@ -57,6 +66,27 @@ class FactTest(BaseFactTest):
self.assertEqual(v.fact.id, f_obj.id) self.assertEqual(v.fact.id, f_obj.id)
self.assertEqual(v.fact.module, 'packages') self.assertEqual(v.fact.module, 'packages')
# Note: Take the failure of this with a grain of salt.
# The test almost entirely depends on the specs of the system running on.
def test_add_fact_performance_4mb_file(self):
timestamp = now().replace(microsecond=0)
host = FactHost(hostname="hosty", inventory_id=1).save()
from awx.fact import tests
with open('%s/data/file_scan.json' % os.path.dirname(os.path.realpath(tests.__file__))) as f:
data = json.load(f)
t1 = now()
(f_obj, v_obj) = Fact.add_fact(host=host, timestamp=timestamp, module='packages', fact=data)
t2 = now()
diff = timedelta_total_seconds(t2 - t1)
print("add_fact save time: %s (s)" % diff)
# Note: 20 is realllly high. This should complete in < 2 seconds
self.assertLessEqual(diff, 20)
Fact.objects.get(id=f_obj.id)
FactVersion.objects.get(id=v_obj.id)
class FactGetHostVersionTest(BaseFactTest): class FactGetHostVersionTest(BaseFactTest):
def setUp(self): def setUp(self):
super(FactGetHostVersionTest, self).setUp() super(FactGetHostVersionTest, self).setUp()
+45 -38
View File
@@ -146,7 +146,7 @@ class BaseAccess(object):
def can_unattach(self, obj, sub_obj, relationship): def can_unattach(self, obj, sub_obj, relationship):
return self.can_change(obj, None) return self.can_change(obj, None)
def check_license(self, add_host=False, feature=None): def check_license(self, add_host=False, feature=None, check_expiration=True):
reader = TaskSerializer() reader = TaskSerializer()
validation_info = reader.from_file() validation_info = reader.from_file()
if ('test' in sys.argv or 'jenkins' in sys.argv) and not os.environ.get('SKIP_LICENSE_FIXUP_FOR_TEST', ''): if ('test' in sys.argv or 'jenkins' in sys.argv) and not os.environ.get('SKIP_LICENSE_FIXUP_FOR_TEST', ''):
@@ -154,9 +154,9 @@ class BaseAccess(object):
validation_info['time_remaining'] = 99999999 validation_info['time_remaining'] = 99999999
validation_info['grace_period_remaining'] = 99999999 validation_info['grace_period_remaining'] = 99999999
if validation_info.get('time_remaining', None) is None: if check_expiration and validation_info.get('time_remaining', None) is None:
raise PermissionDenied("license is missing") raise PermissionDenied("license is missing")
if validation_info.get("grace_period_remaining") <= 0: if check_expiration and validation_info.get("grace_period_remaining") <= 0:
raise PermissionDenied("license has expired") raise PermissionDenied("license has expired")
free_instances = validation_info.get('free_instances', 0) free_instances = validation_info.get('free_instances', 0)
@@ -262,6 +262,7 @@ class OrganizationAccess(BaseAccess):
self.user in obj.admins.all()) self.user in obj.admins.all())
def can_delete(self, obj): def can_delete(self, obj):
self.check_license(feature='multiple_organizations', check_expiration=False)
return self.can_change(obj, None) return self.can_change(obj, None)
class InventoryAccess(BaseAccess): class InventoryAccess(BaseAccess):
@@ -672,22 +673,22 @@ class ProjectAccess(BaseAccess):
- I am on a team associated with the project. - I am on a team associated with the project.
- I have been explicitly granted permission to run/check jobs using the - I have been explicitly granted permission to run/check jobs using the
project. project.
- I created it (for now?). - I created the project but it isn't associated with an organization
I can change/delete when: I can change/delete when:
- I am a superuser. - I am a superuser.
- I am an admin in an organization associated with the project. - I am an admin in an organization associated with the project.
- I created it (for now?). - I created the project but it isn't associated with an organization
''' '''
model = Project model = Project
def get_queryset(self): def get_queryset(self):
qs = Project.objects.filter(active=True).distinct() qs = Project.objects.filter(active=True).distinct()
qs = qs.select_related('created_by', 'modified_by', 'credential', 'current_update', 'last_update') qs = qs.select_related('modified_by', 'credential', 'current_update', 'last_update')
if self.user.is_superuser: if self.user.is_superuser:
return qs return qs
team_ids = set(Team.objects.filter(users__in=[self.user]).values_list('id', flat=True)) team_ids = set(Team.objects.filter(users__in=[self.user]).values_list('id', flat=True))
qs = qs.filter(Q(created_by=self.user) | qs = qs.filter(Q(created_by=self.user, organizations__isnull=True) |
Q(organizations__admins__in=[self.user], organizations__active=True) | Q(organizations__admins__in=[self.user], organizations__active=True) |
Q(organizations__users__in=[self.user], organizations__active=True) | Q(organizations__users__in=[self.user], organizations__active=True) |
Q(teams__in=team_ids)) Q(teams__in=team_ids))
@@ -719,7 +720,7 @@ class ProjectAccess(BaseAccess):
def can_change(self, obj, data): def can_change(self, obj, data):
if self.user.is_superuser: if self.user.is_superuser:
return True return True
if obj.created_by == self.user: if obj.created_by == self.user and not obj.organizations.filter(active=True).count():
return True return True
if obj.organizations.filter(active=True, admins__in=[self.user]).exists(): if obj.organizations.filter(active=True, admins__in=[self.user]).exists():
return True return True
@@ -863,52 +864,55 @@ class JobTemplateAccess(BaseAccess):
'credential', 'cloud_credential', 'next_schedule') 'credential', 'cloud_credential', 'next_schedule')
if self.user.is_superuser: if self.user.is_superuser:
return qs return qs
credential_ids = set(self.user.get_queryset(Credential).values_list('id', flat=True)) credential_ids = self.user.get_queryset(Credential)
inventory_ids = set(self.user.get_queryset(Inventory).values_list('id', flat=True)) inventory_ids = self.user.get_queryset(Inventory)
base_qs = qs.filter( base_qs = qs.filter(
Q(credential_id__in=credential_ids) | Q(credential__isnull=True), Q(credential_id__in=credential_ids) | Q(credential__isnull=True),
Q(cloud_credential_id__in=credential_ids) | Q(cloud_credential__isnull=True), Q(cloud_credential_id__in=credential_ids) | Q(cloud_credential__isnull=True),
) )
org_admin_ids = set(base_qs.filter( org_admin_ids = base_qs.filter(
Q(project__organizations__admins__in=[self.user]) | Q(project__organizations__admins__in=[self.user]) |
(Q(project__isnull=True) & Q(job_type=PERM_INVENTORY_SCAN) & Q(inventory__organization__admins__in=[self.user])) (Q(project__isnull=True) & Q(job_type=PERM_INVENTORY_SCAN) & Q(inventory__organization__admins__in=[self.user]))
).values_list('id', flat=True)) )
allowed_deploy = [PERM_JOBTEMPLATE_CREATE, PERM_INVENTORY_DEPLOY] allowed_deploy = [PERM_JOBTEMPLATE_CREATE, PERM_INVENTORY_DEPLOY]
allowed_check = [PERM_JOBTEMPLATE_CREATE, PERM_INVENTORY_DEPLOY, PERM_INVENTORY_CHECK] allowed_check = [PERM_JOBTEMPLATE_CREATE, PERM_INVENTORY_DEPLOY, PERM_INVENTORY_CHECK]
team_ids = set(Team.objects.filter(users__in=[self.user]).values_list('id', flat=True)) team_ids = Team.objects.filter(users__in=[self.user])
# TODO: I think the below queries can be combined # TODO: I think the below queries can be combined
deploy_permissions_ids = set(Permission.objects.filter( deploy_permissions_ids = Permission.objects.filter(
Q(user=self.user) | Q(team_id__in=team_ids), Q(user=self.user) | Q(team_id__in=team_ids),
active=True, active=True,
permission_type__in=allowed_deploy, permission_type__in=allowed_deploy,
).values_list('id', flat=True)) )
check_permissions_ids = set(Permission.objects.filter( check_permissions_ids = Permission.objects.filter(
Q(user=self.user) | Q(team_id__in=team_ids), Q(user=self.user) | Q(team_id__in=team_ids),
active=True, active=True,
permission_type__in=allowed_check, permission_type__in=allowed_check,
).values_list('id', flat=True)) )
perm_deploy_ids = set(base_qs.filter( perm_deploy_ids = base_qs.filter(
job_type=PERM_INVENTORY_DEPLOY, job_type=PERM_INVENTORY_DEPLOY,
inventory__permissions__in=deploy_permissions_ids, inventory__permissions__in=deploy_permissions_ids,
project__permissions__in=deploy_permissions_ids, project__permissions__in=deploy_permissions_ids,
inventory__permissions__pk=F('project__permissions__pk'), inventory__permissions__pk=F('project__permissions__pk'),
inventory_id__in=inventory_ids, inventory_id__in=inventory_ids,
).values_list('id', flat=True)) )
perm_check_ids = set(base_qs.filter( perm_check_ids = base_qs.filter(
job_type=PERM_INVENTORY_CHECK, job_type=PERM_INVENTORY_CHECK,
inventory__permissions__in=check_permissions_ids, inventory__permissions__in=check_permissions_ids,
project__permissions__in=check_permissions_ids, project__permissions__in=check_permissions_ids,
inventory__permissions__pk=F('project__permissions__pk'), inventory__permissions__pk=F('project__permissions__pk'),
inventory_id__in=inventory_ids, inventory_id__in=inventory_ids,
).values_list('id', flat=True)) )
base_ids = org_admin_ids.union(perm_deploy_ids).union(perm_check_ids) return base_qs.filter(
return base_qs.filter(id__in=base_ids) Q(id__in=org_admin_ids) |
Q(id__in=perm_deploy_ids) |
Q(id__in=perm_check_ids)
)
def can_read(self, obj): def can_read(self, obj):
# you can only see the job templates that you have permission to launch. # you can only see the job templates that you have permission to launch.
@@ -1079,47 +1083,50 @@ class JobAccess(BaseAccess):
qs = qs.prefetch_related('unified_job_template') qs = qs.prefetch_related('unified_job_template')
if self.user.is_superuser: if self.user.is_superuser:
return qs return qs
credential_ids = set(self.user.get_queryset(Credential).values_list('id', flat=True)) credential_ids = self.user.get_queryset(Credential)
base_qs = qs.filter( base_qs = qs.filter(
credential_id__in=credential_ids, credential_id__in=credential_ids,
) )
org_admin_ids = set(base_qs.filter( org_admin_ids = base_qs.filter(
Q(project__organizations__admins__in=[self.user]) | Q(project__organizations__admins__in=[self.user]) |
(Q(project__isnull=True) & Q(job_type=PERM_INVENTORY_SCAN) & Q(inventory__organization__admins__in=[self.user])) (Q(project__isnull=True) & Q(job_type=PERM_INVENTORY_SCAN) & Q(inventory__organization__admins__in=[self.user]))
).values_list('id', flat=True)) )
allowed_deploy = [PERM_JOBTEMPLATE_CREATE, PERM_INVENTORY_DEPLOY] allowed_deploy = [PERM_JOBTEMPLATE_CREATE, PERM_INVENTORY_DEPLOY]
allowed_check = [PERM_JOBTEMPLATE_CREATE, PERM_INVENTORY_DEPLOY, PERM_INVENTORY_CHECK] allowed_check = [PERM_JOBTEMPLATE_CREATE, PERM_INVENTORY_DEPLOY, PERM_INVENTORY_CHECK]
team_ids = set(Team.objects.filter(users__in=[self.user]).values_list('id', flat=True)) team_ids = Team.objects.filter(users__in=[self.user])
# TODO: I think the below queries can be combined # TODO: I think the below queries can be combined
deploy_permissions_ids = set(Permission.objects.filter( deploy_permissions_ids = Permission.objects.filter(
Q(user=self.user) | Q(team__in=team_ids), Q(user=self.user) | Q(team__in=team_ids),
active=True, active=True,
permission_type__in=allowed_deploy, permission_type__in=allowed_deploy,
).values_list('id', flat=True)) )
check_permissions_ids = set(Permission.objects.filter( check_permissions_ids = Permission.objects.filter(
Q(user=self.user) | Q(team__in=team_ids), Q(user=self.user) | Q(team__in=team_ids),
active=True, active=True,
permission_type__in=allowed_check, permission_type__in=allowed_check,
).values_list('id', flat=True)) )
perm_deploy_ids = set(base_qs.filter( perm_deploy_ids = base_qs.filter(
job_type=PERM_INVENTORY_DEPLOY, job_type=PERM_INVENTORY_DEPLOY,
inventory__permissions__in=deploy_permissions_ids, inventory__permissions__in=deploy_permissions_ids,
project__permissions__in=deploy_permissions_ids, project__permissions__in=deploy_permissions_ids,
inventory__permissions__pk=F('project__permissions__pk'), inventory__permissions__pk=F('project__permissions__pk'),
).values_list('id', flat=True)) )
perm_check_ids = set(base_qs.filter( perm_check_ids = base_qs.filter(
job_type=PERM_INVENTORY_CHECK, job_type=PERM_INVENTORY_CHECK,
inventory__permissions__in=check_permissions_ids, inventory__permissions__in=check_permissions_ids,
project__permissions__in=check_permissions_ids, project__permissions__in=check_permissions_ids,
inventory__permissions__pk=F('project__permissions__pk'), inventory__permissions__pk=F('project__permissions__pk'),
).values_list('id', flat=True)) )
base_ids = org_admin_ids.union(perm_deploy_ids).union(perm_check_ids) return base_qs.filter(
return base_qs.filter(id__in=base_ids) Q(id__in=org_admin_ids) |
Q(id__in=perm_deploy_ids) |
Q(id__in=perm_check_ids)
)
def can_add(self, data): def can_add(self, data):
if not data or '_method' in data: # So the browseable API will work? if not data or '_method' in data: # So the browseable API will work?
@@ -1599,7 +1606,7 @@ class CustomInventoryScriptAccess(BaseAccess):
model = CustomInventoryScript model = CustomInventoryScript
def get_queryset(self): def get_queryset(self):
qs = self.model.objects.filter(active=True, organization__active=True).distinct() qs = self.model.objects.filter(active=True).distinct()
if not self.user.is_superuser: if not self.user.is_superuser:
qs = qs.filter(Q(organization__admins__in=[self.user]) | Q(organization__users__in=[self.user])) qs = qs.filter(Q(organization__admins__in=[self.user]) | Q(organization__users__in=[self.user]))
return qs return qs
-128
View File
@@ -1,128 +0,0 @@
# Copyright (c) 2015 Ansible, Inc.
# All Rights Reserved.
# Django
from django.dispatch import receiver
# django-auth-ldap
from django_auth_ldap.backend import LDAPSettings as BaseLDAPSettings
from django_auth_ldap.backend import LDAPBackend as BaseLDAPBackend
from django_auth_ldap.backend import populate_user
# Ansible Tower
from awx.api.license import feature_enabled
class LDAPSettings(BaseLDAPSettings):
defaults = dict(BaseLDAPSettings.defaults.items() + {
'ORGANIZATION_MAP': {},
'TEAM_MAP': {},
}.items())
class LDAPBackend(BaseLDAPBackend):
'''
Custom LDAP backend for AWX.
'''
settings_prefix = 'AUTH_LDAP_'
def _get_settings(self):
if self._settings is None:
self._settings = LDAPSettings(self.settings_prefix)
return self._settings
def _set_settings(self, settings):
self._settings = settings
settings = property(_get_settings, _set_settings)
def authenticate(self, username, password):
if not self.settings.SERVER_URI or not feature_enabled('ldap'):
return None
return super(LDAPBackend, self).authenticate(username, password)
def get_user(self, user_id):
if not self.settings.SERVER_URI or not feature_enabled('ldap'):
return None
return super(LDAPBackend, self).get_user(user_id)
# Disable any LDAP based authorization / permissions checking.
def has_perm(self, user, perm, obj=None):
return False
def has_module_perms(self, user, app_label):
return False
def get_all_permissions(self, user, obj=None):
return set()
def get_group_permissions(self, user, obj=None):
return set()
def _update_m2m_from_groups(user, ldap_user, rel, opts, remove=False):
'''
Hepler function to update m2m relationship based on LDAP group membership.
'''
should_add = False
if opts is None:
return
elif not opts:
pass
elif opts is True:
should_add = True
else:
if isinstance(opts, basestring):
opts = [opts]
for group_dn in opts:
if not isinstance(group_dn, basestring):
continue
if ldap_user._get_groups().is_member_of(group_dn):
should_add = True
if should_add:
rel.add(user)
elif remove:
rel.remove(user)
@receiver(populate_user)
def on_populate_user(sender, **kwargs):
'''
Handle signal from LDAP backend to populate the user object. Update user
organization/team memberships according to their LDAP groups.
'''
from awx.main.models import Organization, Team
user = kwargs['user']
ldap_user = kwargs['ldap_user']
backend = ldap_user.backend
# Update organization membership based on group memberships.
org_map = getattr(backend.settings, 'ORGANIZATION_MAP', {})
for org_name, org_opts in org_map.items():
org, created = Organization.objects.get_or_create(name=org_name)
remove = bool(org_opts.get('remove', False))
admins_opts = org_opts.get('admins', None)
remove_admins = bool(org_opts.get('remove_admins', remove))
_update_m2m_from_groups(user, ldap_user, org.admins, admins_opts,
remove_admins)
users_opts = org_opts.get('users', None)
remove_users = bool(org_opts.get('remove_users', remove))
_update_m2m_from_groups(user, ldap_user, org.users, users_opts,
remove_users)
# Update team membership based on group memberships.
team_map = getattr(backend.settings, 'TEAM_MAP', {})
for team_name, team_opts in team_map.items():
if 'organization' not in team_opts:
continue
org, created = Organization.objects.get_or_create(name=team_opts['organization'])
team, created = Team.objects.get_or_create(name=team_name, organization=org)
users_opts = team_opts.get('users', None)
remove = bool(team_opts.get('remove', False))
_update_m2m_from_groups(user, ldap_user, team.users, users_opts,
remove)
# Update user profile to store LDAP DN.
profile = user.profile
if profile.ldap_dn != ldap_user.dn:
profile.ldap_dn = ldap_user.dn
profile.save()
+14 -1
View File
@@ -21,6 +21,7 @@ class BaseCommandInstance(BaseCommand):
def __init__(self): def __init__(self):
super(BaseCommandInstance, self).__init__() super(BaseCommandInstance, self).__init__()
self.enforce_primary_role = False
self.enforce_roles = False self.enforce_roles = False
self.enforce_hostname_set = False self.enforce_hostname_set = False
self.enforce_unique_find = False self.enforce_unique_find = False
@@ -70,8 +71,13 @@ class BaseCommandInstance(BaseCommand):
default='', default='',
help='Find instance by specified uuid.') help='Find instance by specified uuid.')
def include_option_primary_role(self):
BaseCommand.option_list += ( BaseCommandInstance.generate_option_primary(), )
self.enforce_primary_role = True
def include_options_roles(self): def include_options_roles(self):
BaseCommand.option_list += ( BaseCommandInstance.generate_option_primary(), BaseCommandInstance.generate_option_secondary(), ) self.include_option_primary_role()
BaseCommand.option_list += ( BaseCommandInstance.generate_option_secondary(), )
self.enforce_roles = True self.enforce_roles = True
def include_option_hostname_set(self): def include_option_hostname_set(self):
@@ -107,6 +113,8 @@ class BaseCommandInstance(BaseCommand):
return CommandError('--hostname and one of --primary or --secondary is required.') return CommandError('--hostname and one of --primary or --secondary is required.')
elif self.enforce_hostname_set: elif self.enforce_hostname_set:
return CommandError('--hostname is required.') return CommandError('--hostname is required.')
elif self.enforce_primary_role:
return CommandError('--primary is required.')
elif self.enforce_roles: elif self.enforce_roles:
return CommandError('One of --primary or --secondary is required.') return CommandError('One of --primary or --secondary is required.')
@@ -120,6 +128,11 @@ class BaseCommandInstance(BaseCommand):
if self.is_option_primary() and self.is_option_secondary() or not (self.is_option_primary() or self.is_option_secondary()): if self.is_option_primary() and self.is_option_secondary() or not (self.is_option_primary() or self.is_option_secondary()):
raise self.usage_error raise self.usage_error
elif self.enforce_primary_role:
if options['primary']:
self.option_primary = options['primary']
else:
raise self.usage_error
if self.enforce_hostname_set: if self.enforce_hostname_set:
if options['hostname']: if options['hostname']:
@@ -0,0 +1,34 @@
# 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()
@@ -118,3 +118,10 @@ class Command(BaseCommand):
self.logger.log(99, "Removed %d items", n_deleted_items) self.logger.log(99, "Removed %d items", n_deleted_items)
else: else:
self.logger.log(99, "Would have removed %d items", n_deleted_items) self.logger.log(99, "Would have removed %d items", n_deleted_items)
tokens_removed = AuthToken.objects.filter(expires__lt=now())
if not self.dry_run:
self.logger.log(99, "Removed %d expired auth tokens" % tokens_removed.count())
tokens_removed.delete()
else:
self.logger.log(99, "Would have removed %d expired auth tokens" % tokens_removed.count())
@@ -883,14 +883,14 @@ class Command(NoArgsCommand):
all_obj = self.inventory all_obj = self.inventory
all_name = 'inventory' all_name = 'inventory'
db_variables = all_obj.variables_dict db_variables = all_obj.variables_dict
if self.overwrite_vars or self.overwrite: if self.overwrite_vars:
db_variables = self.all_group.variables db_variables = self.all_group.variables
else: else:
db_variables.update(self.all_group.variables) db_variables.update(self.all_group.variables)
if db_variables != all_obj.variables_dict: if db_variables != all_obj.variables_dict:
all_obj.variables = json.dumps(db_variables) all_obj.variables = json.dumps(db_variables)
all_obj.save(update_fields=['variables']) all_obj.save(update_fields=['variables'])
if self.overwrite_vars or self.overwrite: if self.overwrite_vars:
self.logger.info('%s variables replaced from "all" group', all_name.capitalize()) self.logger.info('%s variables replaced from "all" group', all_name.capitalize())
else: else:
self.logger.info('%s variables updated from "all" group', all_name.capitalize()) self.logger.info('%s variables updated from "all" group', all_name.capitalize())
@@ -920,14 +920,14 @@ class Command(NoArgsCommand):
for group in self.inventory.groups.filter(name__in=group_names): for group in self.inventory.groups.filter(name__in=group_names):
mem_group = self.all_group.all_groups[group.name] mem_group = self.all_group.all_groups[group.name]
db_variables = group.variables_dict db_variables = group.variables_dict
if self.overwrite_vars or self.overwrite: if self.overwrite_vars:
db_variables = mem_group.variables db_variables = mem_group.variables
else: else:
db_variables.update(mem_group.variables) db_variables.update(mem_group.variables)
if db_variables != group.variables_dict: if db_variables != group.variables_dict:
group.variables = json.dumps(db_variables) group.variables = json.dumps(db_variables)
group.save(update_fields=['variables']) group.save(update_fields=['variables'])
if self.overwrite_vars or self.overwrite: if self.overwrite_vars:
self.logger.info('Group "%s" variables replaced', group.name) self.logger.info('Group "%s" variables replaced', group.name)
else: else:
self.logger.info('Group "%s" variables updated', group.name) self.logger.info('Group "%s" variables updated', group.name)
@@ -959,7 +959,7 @@ class Command(NoArgsCommand):
def _update_db_host_from_mem_host(self, db_host, mem_host): def _update_db_host_from_mem_host(self, db_host, mem_host):
# Update host variables. # Update host variables.
db_variables = db_host.variables_dict db_variables = db_host.variables_dict
if self.overwrite_vars or self.overwrite: if self.overwrite_vars:
db_variables = mem_host.variables db_variables = mem_host.variables
else: else:
db_variables.update(mem_host.variables) db_variables.update(mem_host.variables)
@@ -994,7 +994,7 @@ class Command(NoArgsCommand):
else: else:
self.logger.info('Host "%s" instance_id added', mem_host.name) self.logger.info('Host "%s" instance_id added', mem_host.name)
if 'variables' in update_fields: if 'variables' in update_fields:
if self.overwrite_vars or self.overwrite: if self.overwrite_vars:
self.logger.info('Host "%s" variables replaced', mem_host.name) self.logger.info('Host "%s" variables replaced', mem_host.name)
else: else:
self.logger.info('Host "%s" variables updated', mem_host.name) self.logger.info('Host "%s" variables updated', mem_host.name)
@@ -130,6 +130,7 @@ class CallbackReceiver(object):
'playbook_on_task_start', 'playbook_on_task_start',
'playbook_on_no_hosts_matched', 'playbook_on_no_hosts_matched',
'playbook_on_no_hosts_remaining', 'playbook_on_no_hosts_remaining',
'playbook_on_include',
'playbook_on_import_for_host', 'playbook_on_import_for_host',
'playbook_on_not_import_for_host'): 'playbook_on_not_import_for_host'):
parent = job_parent_events.get('playbook_on_play_start', None) parent = job_parent_events.get('playbook_on_play_start', None)
@@ -5,6 +5,7 @@
import os import os
import logging import logging
import urllib import urllib
import weakref
from optparse import make_option from optparse import make_option
from threading import Thread from threading import Thread
@@ -24,51 +25,157 @@ from socketio.namespace import BaseNamespace
logger = logging.getLogger('awx.main.commands.run_socketio_service') logger = logging.getLogger('awx.main.commands.run_socketio_service')
valid_sockets = [] class SocketSession(object):
def __init__(self, session_id, token_key, socket):
self.socket = weakref.ref(socket)
self.session_id = session_id
self.token_key = token_key
self._valid = True
def is_valid(self):
return bool(self._valid)
def invalidate(self):
self._valid = False
def is_db_token_valid(self):
auth_token = AuthToken.objects.filter(key=self.token_key, reason='')
if not auth_token.exists():
return False
auth_token = auth_token[0]
return bool(not auth_token.is_expired())
class SocketSessionManager(object):
def __init__(self):
self.SESSIONS_MAX = 1000
self.socket_sessions = []
self.socket_session_token_key_map = {}
def _prune(self):
if len(self.socket_sessions) > self.SESSIONS_MAX:
session = self.socket_sessions[0]
entries = self.socket_session_token_key_map[session.token_key]
del entries[session.session_id]
if len(entries) == 0:
del self.socket_session_token_key_map[session.token_key]
self.socket_sessions.pop(0)
'''
Returns an dict of sessions <session_id, session>
'''
def lookup(self, token_key=None):
if not token_key:
raise ValueError("token_key required")
return self.socket_session_token_key_map.get(token_key, None)
def add_session(self, session):
self.socket_sessions.append(session)
entries = self.socket_session_token_key_map.get(session.token_key, None)
if not entries:
entries = {}
self.socket_session_token_key_map[session.token_key] = entries
entries[session.session_id] = session
self._prune()
return session
class SocketController(object):
def __init__(self, SocketSessionManager):
self.server = None
self.SocketSessionManager = SocketSessionManager
def add_session(self, session):
return self.SocketSessionManager.add_session(session)
def broadcast_packet(self, packet):
# Broadcast message to everyone at endpoint
# Loop over the 'raw' list of sockets (don't trust our list)
for session_id, socket in list(self.server.sockets.iteritems()):
socket_session = socket.session.get('socket_session', None)
if socket_session and socket_session.is_valid():
try:
socket.send_packet(packet)
except Exception, e:
logger.error("Error sending client packet to %s: %s" % (str(session_id), str(packet)))
logger.error("Error was: " + str(e))
def send_packet(self, packet, token_key):
if not token_key:
raise ValueError("token_key is required")
socket_sessions = self.SocketSessionManager.lookup(token_key=token_key)
# We may not find the socket_session if the user disconnected
# (it's actually more compliciated than that because of our prune logic)
if not socket_sessions:
return None
for session_id, socket_session in socket_sessions.iteritems():
logger.warn("Maybe sending packet to %s" % session_id)
if socket_session and socket_session.is_valid():
logger.warn("Sending packet to %s" % session_id)
socket = socket_session.socket()
if socket:
try:
socket.send_packet(packet)
except Exception, e:
logger.error("Error sending client packet to %s: %s" % (str(socket_session.session_id), str(packet)))
logger.error("Error was: " + str(e))
def set_server(self, server):
self.server = server
return server
socketController = SocketController(SocketSessionManager())
#
# Socket session is attached to self.session['socket_session']
# self.session and self.socket.session point to the same dict
#
class TowerBaseNamespace(BaseNamespace): class TowerBaseNamespace(BaseNamespace):
def get_allowed_methods(self): def get_allowed_methods(self):
return ['recv_disconnect'] return ['recv_disconnect']
def get_initial_acl(self): def get_initial_acl(self):
global valid_sockets request_token = self._get_request_token()
v_user = self.valid_user() if request_token:
self.is_valid_connection = False # (1) This is the first time the socket has been seen (first
if v_user: # namespace joined).
if self.socket.sessid not in valid_sockets: # (2) This socket has already been seen (already joined and maybe
valid_sockets.append(self.socket.sessid) # left a namespace)
self.is_valid_connection = True #
if len(valid_sockets) > 1000: # Note: Assume that the user token is valid if the session is found
valid_sockets = valid_sockets[1:] socket_session = self.session.get('socket_session', None)
if not socket_session:
socket_session = SocketSession(self.socket.sessid, request_token, self.socket)
if socket_session.is_db_token_valid():
self.session['socket_session'] = socket_session
socketController.add_session(socket_session)
else:
socket_session.invalidate()
return set(['recv_connect'] + self.get_allowed_methods()) return set(['recv_connect'] + self.get_allowed_methods())
else: else:
logger.warn("Authentication Failure validating user") logger.warn("Authentication Failure validating user")
self.emit("connect_failed", "Authentication failed") self.emit("connect_failed", "Authentication failed")
return set(['recv_connect']) return set(['recv_connect'])
def valid_user(self): def _get_request_token(self):
if 'QUERY_STRING' not in self.environ: if 'QUERY_STRING' not in self.environ:
return False return False
else:
try: try:
k, v = self.environ['QUERY_STRING'].split("=") k, v = self.environ['QUERY_STRING'].split("=")
if k == "Token": if k == "Token":
token_actual = urllib.unquote_plus(v).decode().replace("\"","") token_actual = urllib.unquote_plus(v).decode().replace("\"","")
auth_token = AuthToken.objects.filter(key=token_actual) return token_actual
if not auth_token.exists(): except Exception, e:
return False logger.error("Exception validating user: " + str(e))
auth_token = auth_token[0] return False
if not auth_token.expired: return False
return auth_token.user
else:
return False
except Exception, e:
logger.error("Exception validating user: " + str(e))
return False
def recv_connect(self): def recv_connect(self):
if not self.is_valid_connection: socket_session = self.session.get('socket_session', None)
if socket_session and not socket_session.is_valid():
self.disconnect(silent=False) self.disconnect(silent=False)
class TestNamespace(TowerBaseNamespace): class TestNamespace(TowerBaseNamespace):
@@ -106,6 +213,14 @@ class ScheduleNamespace(TowerBaseNamespace):
logger.info("Received client connect for schedule namespace from %s" % str(self.environ['REMOTE_ADDR'])) logger.info("Received client connect for schedule namespace from %s" % str(self.environ['REMOTE_ADDR']))
super(ScheduleNamespace, self).recv_connect() super(ScheduleNamespace, self).recv_connect()
# Catch-all namespace.
# Deliver 'global' events over this namespace
class ControlNamespace(TowerBaseNamespace):
def recv_connect(self):
logger.warn("Received client connect for control namespace from %s" % str(self.environ['REMOTE_ADDR']))
super(ControlNamespace, self).recv_connect()
class TowerSocket(object): class TowerSocket(object):
def __call__(self, environ, start_response): def __call__(self, environ, start_response):
@@ -115,7 +230,8 @@ class TowerSocket(object):
'/socket.io/jobs': JobNamespace, '/socket.io/jobs': JobNamespace,
'/socket.io/job_events': JobEventNamespace, '/socket.io/job_events': JobEventNamespace,
'/socket.io/ad_hoc_command_events': AdHocCommandEventNamespace, '/socket.io/ad_hoc_command_events': AdHocCommandEventNamespace,
'/socket.io/schedules': ScheduleNamespace}) '/socket.io/schedules': ScheduleNamespace,
'/socket.io/control': ControlNamespace})
else: else:
logger.warn("Invalid connect path received: " + path) logger.warn("Invalid connect path received: " + path)
start_response('404 Not Found', []) start_response('404 Not Found', [])
@@ -130,13 +246,12 @@ def notification_handler(server):
'name': message['event'], 'name': message['event'],
'type': 'event', 'type': 'event',
} }
for session_id, socket in list(server.sockets.iteritems()):
if session_id in valid_sockets: if 'token_key' in message:
try: # Best practice not to send the token over the socket
socket.send_packet(packet) socketController.send_packet(packet, message.pop('token_key'))
except Exception, e: else:
logger.error("Error sending client packet to %s: %s" % (str(session_id), str(packet))) socketController.broadcast_packet(packet)
logger.error("Error was: " + str(e))
class Command(NoArgsCommand): class Command(NoArgsCommand):
''' '''
@@ -164,6 +279,7 @@ class Command(NoArgsCommand):
logger.info('Listening on port http://0.0.0.0:' + str(socketio_listen_port)) logger.info('Listening on port http://0.0.0.0:' + str(socketio_listen_port))
server = SocketIOServer(('0.0.0.0', socketio_listen_port), TowerSocket(), resource='socket.io') server = SocketIOServer(('0.0.0.0', socketio_listen_port), TowerSocket(), resource='socket.io')
socketController.set_server(server)
handler_thread = Thread(target=notification_handler, args=(server,)) handler_thread = Thread(target=notification_handler, args=(server,))
handler_thread.daemon = True handler_thread.daemon = True
handler_thread.start() handler_thread.start()
@@ -26,7 +26,7 @@ class Command(BaseCommandInstance):
def __init__(self): def __init__(self):
super(Command, self).__init__() super(Command, self).__init__()
self.include_options_roles() self.include_option_primary_role()
self.include_option_hostname_uuid_find() self.include_option_hostname_uuid_find()
@transaction.atomic @transaction.atomic
@@ -18,6 +18,7 @@ import mongoengine
# awx # awx
from awx.fact.models.fact import * # noqa from awx.fact.models.fact import * # noqa
from awx.main.models import * # noqa from awx.main.models import * # noqa
from awx.main.utils import timedelta_total_seconds
TEST_FACT_ANSIBLE = { TEST_FACT_ANSIBLE = {
"ansible_swapfree_mb" : 4092, "ansible_swapfree_mb" : 4092,
@@ -181,7 +182,7 @@ FACT_FIXTURES = {
'ansible': TEST_FACT_ANSIBLE, 'ansible': TEST_FACT_ANSIBLE,
'packages': TEST_FACT_PACKAGES, 'packages': TEST_FACT_PACKAGES,
'services': TEST_FACT_SERVICES, 'services': TEST_FACT_SERVICES,
'files': TEST_FACT_ANSIBLE, 'files': TEST_FACT_FILES,
} }
EXPERIMENT_DEFAULT = { EXPERIMENT_DEFAULT = {
@@ -198,12 +199,6 @@ EXPERIMENT_DEFAULT = {
] ]
} }
# damn you python 2.6
def timedelta_total_seconds(timedelta):
return (
timedelta.microseconds + 0.0 +
(timedelta.seconds + timedelta.days * 24 * 3600) * 10 ** 6) / 10 ** 6
class Experiment(object): class Experiment(object):
def __init__(self, exp, fact_fixtures, raw_db, mongoengine_db): def __init__(self, exp, fact_fixtures, raw_db, mongoengine_db):
self.db = raw_db self.db = raw_db
+23 -3
View File
@@ -5,15 +5,17 @@ import logging
import threading import threading
import uuid import uuid
from django.contrib.auth.models import User from django.contrib.auth.models import User, AnonymousUser
from django.db.models.signals import post_save from django.db.models.signals import post_save
from django.db import IntegrityError from django.db import IntegrityError
from django.http import HttpResponseRedirect from django.http import HttpResponseRedirect
from django.template.response import TemplateResponse from django.template.response import TemplateResponse
from django.utils.functional import curry from django.utils.functional import curry
from django.conf import settings
from awx import __version__ as version from awx import __version__ as version
from awx.main.models import ActivityStream, Instance from awx.main.models import ActivityStream, Instance
from awx.api.authentication import TokenAuthentication
logger = logging.getLogger('awx.main.middleware') logger = logging.getLogger('awx.main.middleware')
@@ -60,8 +62,11 @@ class ActivityStreamMiddleware(threading.local):
def set_actor(self, user, sender, instance, **kwargs): def set_actor(self, user, sender, instance, **kwargs):
if sender == ActivityStream: if sender == ActivityStream:
if isinstance(user, User) and instance.actor is None: if isinstance(user, User) and instance.actor is None:
instance.actor = user user = User.objects.filter(id=user.id)
instance.save(update_fields=['actor']) if user.exists():
user = user[0]
instance.actor = user
instance.save(update_fields=['actor'])
else: else:
if instance.id not in self.instance_ids: if instance.id not in self.instance_ids:
self.instance_ids.append(instance.id) self.instance_ids.append(instance.id)
@@ -100,3 +105,18 @@ class HAMiddleware(object):
# Redirect to the base page of the primary instance. # Redirect to the base page of the primary instance.
return HttpResponseRedirect('http://%s%s' % (primary.hostname, request.path)) return HttpResponseRedirect('http://%s%s' % (primary.hostname, request.path))
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
+538
View File
@@ -0,0 +1,538 @@
# -*- coding: utf-8 -*-
from south.utils import datetime_utils as datetime
from south.db import db
from south.v2 import DataMigration
from django.db import models
from django.utils.timezone import now
from awx.api.license import feature_enabled
class Migration(DataMigration):
def forwards(self, orm):
nowtime_actual = now()
nowtime = nowtime_actual.strftime("%Y%m%dT%H%M%SZ")
for stj in orm.SystemJobTemplate.objects.all():
if not stj.schedules.count():
if stj.name == "Cleanup Job Details":
sched = orm.Schedule(name="Cleanup Job Schedule", rrule="DTSTART:%s RRULE:FREQ=WEEKLY;INTERVAL=1;BYDAY=SU" % nowtime,
description="Automatically Generated Schedule", enabled=True, extra_data={"days": "120"})
elif stj.name == "Cleanup Deleted Data":
sched = orm.Schedule(name="Cleanup Deleted Data Schedule", rrule="DTSTART:%s RRULE:FREQ=WEEKLY;INTERVAL=1;BYDAY=MO" % nowtime,
description="Automatically Generated Schedule", enabled=True, extra_data={"days": "30"})
elif stj.name == "Cleanup Activity Stream":
sched = orm.Schedule(name="Cleanup Activity Schedule", rrule="DTSTART:%s RRULE:FREQ=WEEKLY;INTERVAL=1;BYDAY=TU" % nowtime,
description="Automatically Generated Schedule", enabled=True, extra_data={"days": "355"})
elif stj.name == "Cleanup Fact Details" and feature_enabled('system_tracking'):
sched = orm.Schedule(name="Cleanup Fact Schedule", rrule="DTSTART:%s RRULE:FREQ=MONTHLY;INTERVAL=1;BYMONTHDAY=1" % nowtime,
description="Automatically Generated Schedule", enabled=True, extra_data={'older_than': '120d', 'granularity': '1w'})
else:
continue
sched.unified_job_template = stj
sched.created = nowtime_actual
sched.modified = nowtime_actual
sched.save()
def backwards(self, orm):
"Write your backwards methods here."
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', [], {'symmetrical': 'False', 'related_name': "u'user_set'", 'blank': 'True', 'to': u"orm['auth.Group']"}),
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', [], {'symmetrical': 'False', 'related_name': "u'user_set'", 'blank': 'True', 'to': u"orm['auth.Permission']"}),
'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.activitystream': {
'Meta': {'object_name': 'ActivityStream'},
'actor': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'activity_stream'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
'ad_hoc_command': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['main.AdHocCommand']", 'symmetrical': 'False', 'blank': 'True'}),
'changes': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'credential': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['main.Credential']", 'symmetrical': 'False', 'blank': 'True'}),
'custom_inventory_script': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['main.CustomInventoryScript']", 'symmetrical': 'False', 'blank': 'True'}),
'group': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['main.Group']", 'symmetrical': 'False', 'blank': 'True'}),
'host': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['main.Host']", 'symmetrical': 'False', 'blank': 'True'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'inventory': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['main.Inventory']", 'symmetrical': 'False', 'blank': 'True'}),
'inventory_source': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['main.InventorySource']", 'symmetrical': 'False', 'blank': 'True'}),
'inventory_update': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['main.InventoryUpdate']", 'symmetrical': 'False', 'blank': 'True'}),
'job': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['main.Job']", 'symmetrical': 'False', 'blank': 'True'}),
'job_template': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['main.JobTemplate']", 'symmetrical': 'False', 'blank': 'True'}),
'object1': ('django.db.models.fields.TextField', [], {}),
'object2': ('django.db.models.fields.TextField', [], {}),
'object_relationship_type': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'operation': ('django.db.models.fields.CharField', [], {'max_length': '13'}),
'organization': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['main.Organization']", 'symmetrical': 'False', 'blank': 'True'}),
'permission': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['main.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
'project': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['main.Project']", 'symmetrical': 'False', 'blank': 'True'}),
'project_update': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['main.ProjectUpdate']", 'symmetrical': 'False', 'blank': 'True'}),
'schedule': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['main.Schedule']", 'symmetrical': 'False', 'blank': 'True'}),
'team': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['main.Team']", 'symmetrical': 'False', 'blank': 'True'}),
'timestamp': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'unified_job': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'activity_stream_as_unified_job+'", 'blank': 'True', 'to': "orm['main.UnifiedJob']"}),
'unified_job_template': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'activity_stream_as_unified_job_template+'", 'blank': 'True', 'to': "orm['main.UnifiedJobTemplate']"}),
'user': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.User']", 'symmetrical': 'False', 'blank': 'True'})
},
'main.adhoccommand': {
'Meta': {'object_name': 'AdHocCommand', '_ormbases': ['main.UnifiedJob']},
'become_enabled': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'credential': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': "'ad_hoc_commands'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': "orm['main.Credential']"}),
'forks': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0', 'blank': 'True'}),
'hosts': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'ad_hoc_commands'", 'symmetrical': 'False', 'through': "orm['main.AdHocCommandEvent']", 'to': "orm['main.Host']"}),
'inventory': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'ad_hoc_commands'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': "orm['main.Inventory']"}),
'job_type': ('django.db.models.fields.CharField', [], {'default': "'run'", 'max_length': '64'}),
'limit': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
'module_args': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
'module_name': ('django.db.models.fields.CharField', [], {'default': "'command'", 'max_length': '1024', 'blank': 'True'}),
u'unifiedjob_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['main.UnifiedJob']", 'unique': 'True', 'primary_key': 'True'}),
'verbosity': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0', 'blank': 'True'})
},
'main.adhoccommandevent': {
'Meta': {'ordering': "('-pk',)", 'unique_together': "[('ad_hoc_command', 'host_name')]", 'object_name': 'AdHocCommandEvent'},
'ad_hoc_command': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'ad_hoc_command_events'", 'to': "orm['main.AdHocCommand']"}),
'changed': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'counter': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
'created': ('django.db.models.fields.DateTimeField', [], {'default': 'None'}),
'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', [], {'default': 'None', 'related_name': "'ad_hoc_command_events'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': "orm['main.Host']"}),
'host_name': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'modified': ('django.db.models.fields.DateTimeField', [], {'default': 'None'})
},
'main.authtoken': {
'Meta': {'object_name': 'AuthToken'},
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'expires': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'key': ('django.db.models.fields.CharField', [], {'max_length': '40', 'primary_key': 'True'}),
'modified': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
'request_hash': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '40', 'blank': 'True'}),
'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'auth_tokens'", 'to': u"orm['auth.User']"})
},
'main.credential': {
'Meta': {'ordering': "('kind', 'name')", 'unique_together': "[('user', 'team', 'kind', 'name')]", 'object_name': 'Credential'},
'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'become_method': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '32', 'blank': 'True'}),
'become_password': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
'become_username': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
'cloud': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'created': ('django.db.models.fields.DateTimeField', [], {'default': 'None'}),
'created_by': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', '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'}),
'host': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'kind': ('django.db.models.fields.CharField', [], {'default': "'ssh'", 'max_length': '32'}),
'modified': ('django.db.models.fields.DateTimeField', [], {'default': 'None'}),
'modified_by': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': '"{\'class\': \'credential\', \'app_label\': \'main\'}(class)s_modified+"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '512'}),
'password': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
'project': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '100', 'blank': 'True'}),
'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'}),
'team': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': "'credentials'", 'null': 'True', 'blank': 'True', 'to': "orm['main.Team']"}),
'user': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': "'credentials'", 'null': 'True', 'blank': 'True', 'to': u"orm['auth.User']"}),
'username': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
'vault_password': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'})
},
'main.custominventoryscript': {
'Meta': {'ordering': "('name',)", 'unique_together': "[('name', 'organization')]", 'object_name': 'CustomInventoryScript'},
'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'created': ('django.db.models.fields.DateTimeField', [], {'default': 'None'}),
'created_by': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': '"{\'class\': \'custominventoryscript\', \'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'}),
'modified': ('django.db.models.fields.DateTimeField', [], {'default': 'None'}),
'modified_by': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': '"{\'class\': \'custominventoryscript\', \'app_label\': \'main\'}(class)s_modified+"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '512'}),
'organization': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'custom_inventory_scripts'", 'to': "orm['main.Organization']"}),
'script': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'})
},
'main.group': {
'Meta': {'ordering': "('name',)", 'unique_together': "(('name', 'inventory'),)", 'object_name': 'Group'},
'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'created': ('django.db.models.fields.DateTimeField', [], {'default': 'None'}),
'created_by': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', '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'}),
'groups_with_active_failures': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
'has_active_failures': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'has_inventory_sources': ('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']"}),
'hosts_with_active_failures': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'inventory': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'groups'", 'to': "orm['main.Inventory']"}),
'inventory_sources': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'groups'", 'symmetrical': 'False', 'to': "orm['main.InventorySource']"}),
'modified': ('django.db.models.fields.DateTimeField', [], {'default': 'None'}),
'modified_by': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': '"{\'class\': \'group\', \'app_label\': \'main\'}(class)s_modified+"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
'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']"}),
'total_groups': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
'total_hosts': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
'variables': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'})
},
'main.host': {
'Meta': {'ordering': "('inventory', 'name')", 'unique_together': "(('name', 'inventory'),)", 'object_name': 'Host'},
'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'created': ('django.db.models.fields.DateTimeField', [], {'default': 'None'}),
'created_by': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', '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'}),
'enabled': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'has_active_failures': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'has_inventory_sources': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'instance_id': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '100', 'blank': 'True'}),
'inventory': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'hosts'", 'to': "orm['main.Inventory']"}),
'inventory_sources': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'hosts'", 'symmetrical': 'False', 'to': "orm['main.InventorySource']"}),
'last_job': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': "'hosts_as_last_job+'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': "orm['main.Job']"}),
'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': "orm['main.JobHostSummary']", 'blank': 'True', 'null': 'True'}),
'modified': ('django.db.models.fields.DateTimeField', [], {'default': 'None'}),
'modified_by': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': '"{\'class\': \'host\', \'app_label\': \'main\'}(class)s_modified+"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '512'}),
'variables': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'})
},
'main.instance': {
'Meta': {'object_name': 'Instance'},
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'hostname': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '250'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'modified': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
'primary': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'uuid': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '40'})
},
'main.inventory': {
'Meta': {'ordering': "('name',)", 'unique_together': "[('name', 'organization')]", 'object_name': 'Inventory'},
'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'created': ('django.db.models.fields.DateTimeField', [], {'default': 'None'}),
'created_by': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', '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'}),
'groups_with_active_failures': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
'has_active_failures': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'has_inventory_sources': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'hosts_with_active_failures': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'inventory_sources_with_failures': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
'modified': ('django.db.models.fields.DateTimeField', [], {'default': 'None'}),
'modified_by': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': '"{\'class\': \'inventory\', \'app_label\': \'main\'}(class)s_modified+"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
'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']"}),
'total_groups': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
'total_hosts': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
'total_inventory_sources': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
'variables': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'})
},
'main.inventorysource': {
'Meta': {'object_name': 'InventorySource', '_ormbases': ['main.UnifiedJobTemplate']},
'credential': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'inventorysources'", 'on_delete': 'models.SET_NULL', 'default': 'None', 'to': "orm['main.Credential']", 'blank': 'True', 'null': 'True'}),
'group': ('awx.main.fields.AutoOneToOneField', [], {'default': 'None', 'related_name': "'inventory_source'", 'unique': 'True', 'null': 'True', 'to': "orm['main.Group']"}),
'group_by': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
'instance_filters': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
'inventory': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': "'inventory_sources'", 'null': 'True', 'to': "orm['main.Inventory']"}),
'overwrite': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'overwrite_vars': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'source': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '32', 'blank': 'True'}),
'source_path': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
'source_regions': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
'source_script': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'to': "orm['main.CustomInventoryScript']", 'null': 'True', 'on_delete': 'models.SET_NULL', 'blank': 'True'}),
'source_vars': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
u'unifiedjobtemplate_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['main.UnifiedJobTemplate']", 'unique': 'True', 'primary_key': 'True'}),
'update_cache_timeout': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
'update_on_launch': ('django.db.models.fields.BooleanField', [], {'default': 'False'})
},
'main.inventoryupdate': {
'Meta': {'object_name': 'InventoryUpdate', '_ormbases': ['main.UnifiedJob']},
'credential': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'inventoryupdates'", 'on_delete': 'models.SET_NULL', 'default': 'None', 'to': "orm['main.Credential']", 'blank': 'True', 'null': 'True'}),
'group_by': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
'instance_filters': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
'inventory_source': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'inventory_updates'", 'to': "orm['main.InventorySource']"}),
'license_error': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'overwrite': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'overwrite_vars': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'source': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '32', 'blank': 'True'}),
'source_path': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
'source_regions': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
'source_script': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'to': "orm['main.CustomInventoryScript']", 'null': 'True', 'on_delete': 'models.SET_NULL', 'blank': 'True'}),
'source_vars': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
u'unifiedjob_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['main.UnifiedJob']", 'unique': 'True', 'primary_key': 'True'})
},
'main.job': {
'Meta': {'ordering': "('id',)", 'object_name': 'Job', '_ormbases': ['main.UnifiedJob']},
'become_enabled': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'cloud_credential': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'jobs_as_cloud_credential+'", 'on_delete': 'models.SET_NULL', 'default': 'None', 'to': "orm['main.Credential']", 'blank': 'True', 'null': 'True'}),
'credential': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'jobs'", 'on_delete': 'models.SET_NULL', 'default': 'None', 'to': "orm['main.Credential']", 'blank': 'True', 'null': 'True'}),
'extra_vars': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
'force_handlers': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'forks': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0', 'blank': 'True'}),
'hosts': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'jobs'", 'symmetrical': 'False', 'through': "orm['main.JobHostSummary']", 'to': "orm['main.Host']"}),
'inventory': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'jobs'", '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_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', [], {'default': "'run'", 'max_length': '64'}),
'limit': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
'playbook': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
'project': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'jobs'", 'on_delete': 'models.SET_NULL', 'default': 'None', 'to': "orm['main.Project']", 'blank': 'True', 'null': 'True'}),
'skip_tags': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
'start_at_task': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
u'unifiedjob_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['main.UnifiedJob']", 'unique': 'True', 'primary_key': 'True'}),
'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'}),
'counter': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
'created': ('django.db.models.fields.DateTimeField', [], {'default': 'None'}),
'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', [], {'default': 'None', 'related_name': "'job_events_as_primary_host'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': "orm['main.Host']"}),
'host_name': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024'}),
'hosts': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'job_events'", 'symmetrical': 'False', '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']"}),
'modified': ('django.db.models.fields.DateTimeField', [], {'default': 'None'}),
'parent': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': "'children'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': "orm['main.JobEvent']"}),
'play': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024'}),
'role': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024'}),
'task': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024'})
},
'main.jobhostsummary': {
'Meta': {'ordering': "('-pk',)", 'unique_together': "[('job', 'host_name')]", 'object_name': 'JobHostSummary'},
'changed': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
'created': ('django.db.models.fields.DateTimeField', [], {'default': 'None'}),
'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', [], {'default': 'None', 'related_name': "'job_host_summaries'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': "orm['main.Host']"}),
'host_name': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024'}),
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']"}),
'modified': ('django.db.models.fields.DateTimeField', [], {'default': 'None'}),
'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.joborigin': {
'Meta': {'object_name': 'JobOrigin'},
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'instance': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['main.Instance']"}),
'modified': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
'unified_job': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'job_origin'", 'unique': 'True', 'to': "orm['main.UnifiedJob']"})
},
'main.jobtemplate': {
'Meta': {'ordering': "('name',)", 'object_name': 'JobTemplate', '_ormbases': ['main.UnifiedJobTemplate']},
'ask_variables_on_launch': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'become_enabled': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'cloud_credential': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'jobtemplates_as_cloud_credential+'", 'on_delete': 'models.SET_NULL', 'default': 'None', 'to': "orm['main.Credential']", 'blank': 'True', 'null': 'True'}),
'credential': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'jobtemplates'", 'on_delete': 'models.SET_NULL', 'default': 'None', 'to': "orm['main.Credential']", 'blank': 'True', 'null': 'True'}),
'extra_vars': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
'force_handlers': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'forks': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0', 'blank': 'True'}),
'host_config_key': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
'inventory': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'jobtemplates'", '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', [], {'default': "'run'", 'max_length': '64'}),
'limit': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
'playbook': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
'project': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'jobtemplates'", 'on_delete': 'models.SET_NULL', 'default': 'None', 'to': "orm['main.Project']", 'blank': 'True', 'null': 'True'}),
'skip_tags': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
'start_at_task': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
'survey_enabled': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'survey_spec': ('jsonfield.fields.JSONField', [], {'default': '{}', 'blank': 'True'}),
u'unifiedjobtemplate_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['main.UnifiedJobTemplate']", 'unique': 'True', 'primary_key': 'True'}),
'verbosity': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0', 'blank': 'True'})
},
'main.organization': {
'Meta': {'ordering': "('name',)", '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', [], {'default': 'None'}),
'created_by': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', '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'}),
'modified': ('django.db.models.fields.DateTimeField', [], {'default': 'None'}),
'modified_by': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': '"{\'class\': \'organization\', \'app_label\': \'main\'}(class)s_modified+"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
'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': "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', [], {'default': 'None'}),
'created_by': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', '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']"}),
'modified': ('django.db.models.fields.DateTimeField', [], {'default': 'None'}),
'modified_by': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': '"{\'class\': \'permission\', \'app_label\': \'main\'}(class)s_modified+"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
'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': "orm['main.Project']"}),
'run_ad_hoc_commands': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'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']"})
},
'main.profile': {
'Meta': {'object_name': 'Profile'},
'created': ('django.db.models.fields.DateTimeField', [], {'default': 'None'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'ldap_dn': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024'}),
'modified': ('django.db.models.fields.DateTimeField', [], {'default': 'None'}),
'user': ('awx.main.fields.AutoOneToOneField', [], {'related_name': "'profile'", 'unique': 'True', 'to': u"orm['auth.User']"})
},
'main.project': {
'Meta': {'ordering': "('id',)", 'object_name': 'Project', '_ormbases': ['main.UnifiedJobTemplate']},
'credential': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'projects'", 'on_delete': 'models.SET_NULL', 'default': 'None', 'to': "orm['main.Credential']", 'blank': 'True', 'null': 'True'}),
'local_path': ('django.db.models.fields.CharField', [], {'max_length': '1024', 'blank': 'True'}),
'scm_branch': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '256', 'blank': 'True'}),
'scm_clean': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'scm_delete_on_next_update': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'scm_delete_on_update': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'scm_type': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '8', 'blank': 'True'}),
'scm_update_cache_timeout': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0', 'blank': 'True'}),
'scm_update_on_launch': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'scm_url': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
u'unifiedjobtemplate_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['main.UnifiedJobTemplate']", 'unique': 'True', 'primary_key': 'True'})
},
'main.projectupdate': {
'Meta': {'object_name': 'ProjectUpdate', '_ormbases': ['main.UnifiedJob']},
'credential': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'projectupdates'", 'on_delete': 'models.SET_NULL', 'default': 'None', 'to': "orm['main.Credential']", 'blank': 'True', 'null': 'True'}),
'local_path': ('django.db.models.fields.CharField', [], {'max_length': '1024', 'blank': 'True'}),
'project': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'project_updates'", 'to': "orm['main.Project']"}),
'scm_branch': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '256', 'blank': 'True'}),
'scm_clean': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'scm_delete_on_update': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'scm_type': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '8', 'blank': 'True'}),
'scm_url': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
u'unifiedjob_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['main.UnifiedJob']", 'unique': 'True', 'primary_key': 'True'})
},
'main.schedule': {
'Meta': {'ordering': "['-next_run']", 'object_name': 'Schedule'},
'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'created': ('django.db.models.fields.DateTimeField', [], {'default': 'None'}),
'created_by': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': '"{\'class\': \'schedule\', \'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'}),
'dtend': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True'}),
'dtstart': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True'}),
'enabled': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'extra_data': ('jsonfield.fields.JSONField', [], {'default': '{}', 'blank': 'True'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'modified': ('django.db.models.fields.DateTimeField', [], {'default': 'None'}),
'modified_by': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': '"{\'class\': \'schedule\', \'app_label\': \'main\'}(class)s_modified+"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '512'}),
'next_run': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True'}),
'rrule': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'unified_job_template': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'schedules'", 'to': "orm['main.UnifiedJobTemplate']"})
},
'main.systemjob': {
'Meta': {'ordering': "('id',)", 'object_name': 'SystemJob', '_ormbases': ['main.UnifiedJob']},
'extra_vars': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
'job_type': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '32', 'blank': 'True'}),
'system_job_template': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'jobs'", 'on_delete': 'models.SET_NULL', 'default': 'None', 'to': "orm['main.SystemJobTemplate']", 'blank': 'True', 'null': 'True'}),
u'unifiedjob_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['main.UnifiedJob']", 'unique': 'True', 'primary_key': 'True'})
},
'main.systemjobtemplate': {
'Meta': {'object_name': 'SystemJobTemplate', '_ormbases': ['main.UnifiedJobTemplate']},
'job_type': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '32', 'blank': 'True'}),
u'unifiedjobtemplate_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['main.UnifiedJobTemplate']", 'unique': 'True', 'primary_key': 'True'})
},
'main.team': {
'Meta': {'ordering': "('organization__name', 'name')", 'unique_together': "[('organization', 'name')]", 'object_name': 'Team'},
'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'created': ('django.db.models.fields.DateTimeField', [], {'default': 'None'}),
'created_by': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', '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'}),
'modified': ('django.db.models.fields.DateTimeField', [], {'default': 'None'}),
'modified_by': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': '"{\'class\': \'team\', \'app_label\': \'main\'}(class)s_modified+"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
'name': ('django.db.models.fields.CharField', [], {'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': "orm['main.Project']"}),
'users': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'teams'", 'blank': 'True', 'to': u"orm['auth.User']"})
},
'main.unifiedjob': {
'Meta': {'object_name': 'UnifiedJob'},
'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', [], {'default': 'None'}),
'created_by': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': '"{\'class\': \'unifiedjob\', \'app_label\': \'main\'}(class)s_created+"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
'dependent_jobs': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'dependent_jobs_rel_+'", 'to': "orm['main.UnifiedJob']"}),
'description': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
'elapsed': ('django.db.models.fields.DecimalField', [], {'max_digits': '12', 'decimal_places': '3'}),
'failed': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'finished': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'job_args': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
'job_cwd': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
'job_env': ('jsonfield.fields.JSONField', [], {'default': '{}', 'blank': 'True'}),
'job_explanation': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
'launch_type': ('django.db.models.fields.CharField', [], {'default': "'manual'", 'max_length': '20'}),
'modified': ('django.db.models.fields.DateTimeField', [], {'default': 'None'}),
'modified_by': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': '"{\'class\': \'unifiedjob\', \'app_label\': \'main\'}(class)s_modified+"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '512'}),
'old_pk': ('django.db.models.fields.PositiveIntegerField', [], {'default': 'None', 'null': 'True'}),
'polymorphic_ctype': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'polymorphic_main.unifiedjob_set'", 'null': 'True', 'to': u"orm['contenttypes.ContentType']"}),
'result_stdout_file': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
'result_stdout_text': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
'result_traceback': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
'schedule': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'to': "orm['main.Schedule']", 'null': 'True', 'on_delete': 'models.SET_NULL'}),
'start_args': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
'started': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True'}),
'status': ('django.db.models.fields.CharField', [], {'default': "'new'", 'max_length': '20'}),
'unified_job_template': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': "'unifiedjob_unified_jobs'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': "orm['main.UnifiedJobTemplate']"})
},
'main.unifiedjobtemplate': {
'Meta': {'unique_together': "[('polymorphic_ctype', 'name')]", 'object_name': 'UnifiedJobTemplate'},
'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'created': ('django.db.models.fields.DateTimeField', [], {'default': 'None'}),
'created_by': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': '"{\'class\': \'unifiedjobtemplate\', \'app_label\': \'main\'}(class)s_created+"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
'current_job': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': "'unifiedjobtemplate_as_current_job+'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': "orm['main.UnifiedJob']"}),
'description': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
'has_schedules': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'last_job': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': "'unifiedjobtemplate_as_last_job+'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': "orm['main.UnifiedJob']"}),
'last_job_failed': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'last_job_run': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True'}),
'modified': ('django.db.models.fields.DateTimeField', [], {'default': 'None'}),
'modified_by': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': '"{\'class\': \'unifiedjobtemplate\', \'app_label\': \'main\'}(class)s_modified+"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '512'}),
'next_job_run': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True'}),
'next_schedule': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': "'unifiedjobtemplate_as_next_schedule+'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': "orm['main.Schedule']"}),
'old_pk': ('django.db.models.fields.PositiveIntegerField', [], {'default': 'None', 'null': 'True'}),
'polymorphic_ctype': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'polymorphic_main.unifiedjobtemplate_set'", 'null': 'True', 'to': u"orm['contenttypes.ContentType']"}),
'status': ('django.db.models.fields.CharField', [], {'default': "'ok'", 'max_length': '32'})
}
}
complete_apps = ['main']
symmetrical = True
+522
View File
@@ -0,0 +1,522 @@
# -*- coding: utf-8 -*-
from south.utils import datetime_utils as datetime
from south.db import db
from south.v2 import SchemaMigration
from django.db import models
class Migration(SchemaMigration):
def forwards(self, orm):
# Adding field 'AuthToken.reason'
db.add_column(u'main_authtoken', 'reason',
self.gf('django.db.models.fields.CharField')(default='', max_length=1024, blank=True),
keep_default=False)
def backwards(self, orm):
# Deleting field 'AuthToken.reason'
db.delete_column(u'main_authtoken', 'reason')
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', [], {'symmetrical': 'False', 'related_name': "u'user_set'", 'blank': 'True', 'to': u"orm['auth.Group']"}),
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', [], {'symmetrical': 'False', 'related_name': "u'user_set'", 'blank': 'True', 'to': u"orm['auth.Permission']"}),
'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.activitystream': {
'Meta': {'object_name': 'ActivityStream'},
'actor': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'activity_stream'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
'ad_hoc_command': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['main.AdHocCommand']", 'symmetrical': 'False', 'blank': 'True'}),
'changes': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'credential': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['main.Credential']", 'symmetrical': 'False', 'blank': 'True'}),
'custom_inventory_script': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['main.CustomInventoryScript']", 'symmetrical': 'False', 'blank': 'True'}),
'group': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['main.Group']", 'symmetrical': 'False', 'blank': 'True'}),
'host': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['main.Host']", 'symmetrical': 'False', 'blank': 'True'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'inventory': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['main.Inventory']", 'symmetrical': 'False', 'blank': 'True'}),
'inventory_source': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['main.InventorySource']", 'symmetrical': 'False', 'blank': 'True'}),
'inventory_update': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['main.InventoryUpdate']", 'symmetrical': 'False', 'blank': 'True'}),
'job': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['main.Job']", 'symmetrical': 'False', 'blank': 'True'}),
'job_template': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['main.JobTemplate']", 'symmetrical': 'False', 'blank': 'True'}),
'object1': ('django.db.models.fields.TextField', [], {}),
'object2': ('django.db.models.fields.TextField', [], {}),
'object_relationship_type': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'operation': ('django.db.models.fields.CharField', [], {'max_length': '13'}),
'organization': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['main.Organization']", 'symmetrical': 'False', 'blank': 'True'}),
'permission': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['main.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
'project': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['main.Project']", 'symmetrical': 'False', 'blank': 'True'}),
'project_update': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['main.ProjectUpdate']", 'symmetrical': 'False', 'blank': 'True'}),
'schedule': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['main.Schedule']", 'symmetrical': 'False', 'blank': 'True'}),
'team': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['main.Team']", 'symmetrical': 'False', 'blank': 'True'}),
'timestamp': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'unified_job': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'activity_stream_as_unified_job+'", 'blank': 'True', 'to': "orm['main.UnifiedJob']"}),
'unified_job_template': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'activity_stream_as_unified_job_template+'", 'blank': 'True', 'to': "orm['main.UnifiedJobTemplate']"}),
'user': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.User']", 'symmetrical': 'False', 'blank': 'True'})
},
'main.adhoccommand': {
'Meta': {'object_name': 'AdHocCommand', '_ormbases': ['main.UnifiedJob']},
'become_enabled': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'credential': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': "'ad_hoc_commands'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': "orm['main.Credential']"}),
'forks': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0', 'blank': 'True'}),
'hosts': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'ad_hoc_commands'", 'symmetrical': 'False', 'through': "orm['main.AdHocCommandEvent']", 'to': "orm['main.Host']"}),
'inventory': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'ad_hoc_commands'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': "orm['main.Inventory']"}),
'job_type': ('django.db.models.fields.CharField', [], {'default': "'run'", 'max_length': '64'}),
'limit': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
'module_args': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
'module_name': ('django.db.models.fields.CharField', [], {'default': "'command'", 'max_length': '1024', 'blank': 'True'}),
u'unifiedjob_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['main.UnifiedJob']", 'unique': 'True', 'primary_key': 'True'}),
'verbosity': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0', 'blank': 'True'})
},
'main.adhoccommandevent': {
'Meta': {'ordering': "('-pk',)", 'unique_together': "[('ad_hoc_command', 'host_name')]", 'object_name': 'AdHocCommandEvent'},
'ad_hoc_command': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'ad_hoc_command_events'", 'to': "orm['main.AdHocCommand']"}),
'changed': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'counter': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
'created': ('django.db.models.fields.DateTimeField', [], {'default': 'None'}),
'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', [], {'default': 'None', 'related_name': "'ad_hoc_command_events'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': "orm['main.Host']"}),
'host_name': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'modified': ('django.db.models.fields.DateTimeField', [], {'default': 'None'})
},
'main.authtoken': {
'Meta': {'object_name': 'AuthToken'},
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'expires': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'key': ('django.db.models.fields.CharField', [], {'max_length': '40', 'primary_key': 'True'}),
'modified': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
'reason': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
'request_hash': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '40', 'blank': 'True'}),
'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'auth_tokens'", 'to': u"orm['auth.User']"})
},
'main.credential': {
'Meta': {'ordering': "('kind', 'name')", 'unique_together': "[('user', 'team', 'kind', 'name')]", 'object_name': 'Credential'},
'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'become_method': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '32', 'blank': 'True'}),
'become_password': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
'become_username': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
'cloud': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'created': ('django.db.models.fields.DateTimeField', [], {'default': 'None'}),
'created_by': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', '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'}),
'host': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'kind': ('django.db.models.fields.CharField', [], {'default': "'ssh'", 'max_length': '32'}),
'modified': ('django.db.models.fields.DateTimeField', [], {'default': 'None'}),
'modified_by': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': '"{\'class\': \'credential\', \'app_label\': \'main\'}(class)s_modified+"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '512'}),
'password': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
'project': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '100', 'blank': 'True'}),
'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'}),
'team': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': "'credentials'", 'null': 'True', 'blank': 'True', 'to': "orm['main.Team']"}),
'user': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': "'credentials'", 'null': 'True', 'blank': 'True', 'to': u"orm['auth.User']"}),
'username': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
'vault_password': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'})
},
'main.custominventoryscript': {
'Meta': {'ordering': "('name',)", 'unique_together': "[('name', 'organization')]", 'object_name': 'CustomInventoryScript'},
'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'created': ('django.db.models.fields.DateTimeField', [], {'default': 'None'}),
'created_by': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': '"{\'class\': \'custominventoryscript\', \'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'}),
'modified': ('django.db.models.fields.DateTimeField', [], {'default': 'None'}),
'modified_by': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': '"{\'class\': \'custominventoryscript\', \'app_label\': \'main\'}(class)s_modified+"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '512'}),
'organization': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'custom_inventory_scripts'", 'to': "orm['main.Organization']"}),
'script': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'})
},
'main.group': {
'Meta': {'ordering': "('name',)", 'unique_together': "(('name', 'inventory'),)", 'object_name': 'Group'},
'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'created': ('django.db.models.fields.DateTimeField', [], {'default': 'None'}),
'created_by': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', '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'}),
'groups_with_active_failures': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
'has_active_failures': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'has_inventory_sources': ('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']"}),
'hosts_with_active_failures': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'inventory': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'groups'", 'to': "orm['main.Inventory']"}),
'inventory_sources': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'groups'", 'symmetrical': 'False', 'to': "orm['main.InventorySource']"}),
'modified': ('django.db.models.fields.DateTimeField', [], {'default': 'None'}),
'modified_by': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': '"{\'class\': \'group\', \'app_label\': \'main\'}(class)s_modified+"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
'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']"}),
'total_groups': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
'total_hosts': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
'variables': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'})
},
'main.host': {
'Meta': {'ordering': "('inventory', 'name')", 'unique_together': "(('name', 'inventory'),)", 'object_name': 'Host'},
'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'created': ('django.db.models.fields.DateTimeField', [], {'default': 'None'}),
'created_by': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', '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'}),
'enabled': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'has_active_failures': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'has_inventory_sources': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'instance_id': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '100', 'blank': 'True'}),
'inventory': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'hosts'", 'to': "orm['main.Inventory']"}),
'inventory_sources': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'hosts'", 'symmetrical': 'False', 'to': "orm['main.InventorySource']"}),
'last_job': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': "'hosts_as_last_job+'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': "orm['main.Job']"}),
'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': "orm['main.JobHostSummary']", 'blank': 'True', 'null': 'True'}),
'modified': ('django.db.models.fields.DateTimeField', [], {'default': 'None'}),
'modified_by': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': '"{\'class\': \'host\', \'app_label\': \'main\'}(class)s_modified+"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '512'}),
'variables': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'})
},
'main.instance': {
'Meta': {'object_name': 'Instance'},
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'hostname': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '250'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'modified': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
'primary': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'uuid': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '40'})
},
'main.inventory': {
'Meta': {'ordering': "('name',)", 'unique_together': "[('name', 'organization')]", 'object_name': 'Inventory'},
'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'created': ('django.db.models.fields.DateTimeField', [], {'default': 'None'}),
'created_by': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', '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'}),
'groups_with_active_failures': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
'has_active_failures': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'has_inventory_sources': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'hosts_with_active_failures': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'inventory_sources_with_failures': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
'modified': ('django.db.models.fields.DateTimeField', [], {'default': 'None'}),
'modified_by': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': '"{\'class\': \'inventory\', \'app_label\': \'main\'}(class)s_modified+"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
'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']"}),
'total_groups': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
'total_hosts': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
'total_inventory_sources': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
'variables': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'})
},
'main.inventorysource': {
'Meta': {'object_name': 'InventorySource', '_ormbases': ['main.UnifiedJobTemplate']},
'credential': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'inventorysources'", 'on_delete': 'models.SET_NULL', 'default': 'None', 'to': "orm['main.Credential']", 'blank': 'True', 'null': 'True'}),
'group': ('awx.main.fields.AutoOneToOneField', [], {'default': 'None', 'related_name': "'inventory_source'", 'unique': 'True', 'null': 'True', 'to': "orm['main.Group']"}),
'group_by': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
'instance_filters': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
'inventory': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': "'inventory_sources'", 'null': 'True', 'to': "orm['main.Inventory']"}),
'overwrite': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'overwrite_vars': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'source': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '32', 'blank': 'True'}),
'source_path': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
'source_regions': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
'source_script': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'to': "orm['main.CustomInventoryScript']", 'null': 'True', 'on_delete': 'models.SET_NULL', 'blank': 'True'}),
'source_vars': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
u'unifiedjobtemplate_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['main.UnifiedJobTemplate']", 'unique': 'True', 'primary_key': 'True'}),
'update_cache_timeout': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
'update_on_launch': ('django.db.models.fields.BooleanField', [], {'default': 'False'})
},
'main.inventoryupdate': {
'Meta': {'object_name': 'InventoryUpdate', '_ormbases': ['main.UnifiedJob']},
'credential': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'inventoryupdates'", 'on_delete': 'models.SET_NULL', 'default': 'None', 'to': "orm['main.Credential']", 'blank': 'True', 'null': 'True'}),
'group_by': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
'instance_filters': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
'inventory_source': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'inventory_updates'", 'to': "orm['main.InventorySource']"}),
'license_error': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'overwrite': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'overwrite_vars': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'source': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '32', 'blank': 'True'}),
'source_path': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
'source_regions': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
'source_script': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'to': "orm['main.CustomInventoryScript']", 'null': 'True', 'on_delete': 'models.SET_NULL', 'blank': 'True'}),
'source_vars': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
u'unifiedjob_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['main.UnifiedJob']", 'unique': 'True', 'primary_key': 'True'})
},
'main.job': {
'Meta': {'ordering': "('id',)", 'object_name': 'Job', '_ormbases': ['main.UnifiedJob']},
'become_enabled': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'cloud_credential': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'jobs_as_cloud_credential+'", 'on_delete': 'models.SET_NULL', 'default': 'None', 'to': "orm['main.Credential']", 'blank': 'True', 'null': 'True'}),
'credential': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'jobs'", 'on_delete': 'models.SET_NULL', 'default': 'None', 'to': "orm['main.Credential']", 'blank': 'True', 'null': 'True'}),
'extra_vars': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
'force_handlers': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'forks': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0', 'blank': 'True'}),
'hosts': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'jobs'", 'symmetrical': 'False', 'through': "orm['main.JobHostSummary']", 'to': "orm['main.Host']"}),
'inventory': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'jobs'", '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_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', [], {'default': "'run'", 'max_length': '64'}),
'limit': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
'playbook': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
'project': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'jobs'", 'on_delete': 'models.SET_NULL', 'default': 'None', 'to': "orm['main.Project']", 'blank': 'True', 'null': 'True'}),
'skip_tags': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
'start_at_task': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
u'unifiedjob_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['main.UnifiedJob']", 'unique': 'True', 'primary_key': 'True'}),
'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'}),
'counter': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
'created': ('django.db.models.fields.DateTimeField', [], {'default': 'None'}),
'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', [], {'default': 'None', 'related_name': "'job_events_as_primary_host'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': "orm['main.Host']"}),
'host_name': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024'}),
'hosts': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'job_events'", 'symmetrical': 'False', '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']"}),
'modified': ('django.db.models.fields.DateTimeField', [], {'default': 'None'}),
'parent': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': "'children'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': "orm['main.JobEvent']"}),
'play': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024'}),
'role': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024'}),
'task': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024'})
},
'main.jobhostsummary': {
'Meta': {'ordering': "('-pk',)", 'unique_together': "[('job', 'host_name')]", 'object_name': 'JobHostSummary'},
'changed': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
'created': ('django.db.models.fields.DateTimeField', [], {'default': 'None'}),
'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', [], {'default': 'None', 'related_name': "'job_host_summaries'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': "orm['main.Host']"}),
'host_name': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024'}),
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']"}),
'modified': ('django.db.models.fields.DateTimeField', [], {'default': 'None'}),
'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.joborigin': {
'Meta': {'object_name': 'JobOrigin'},
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'instance': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['main.Instance']"}),
'modified': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
'unified_job': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'job_origin'", 'unique': 'True', 'to': "orm['main.UnifiedJob']"})
},
'main.jobtemplate': {
'Meta': {'ordering': "('name',)", 'object_name': 'JobTemplate', '_ormbases': ['main.UnifiedJobTemplate']},
'ask_variables_on_launch': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'become_enabled': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'cloud_credential': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'jobtemplates_as_cloud_credential+'", 'on_delete': 'models.SET_NULL', 'default': 'None', 'to': "orm['main.Credential']", 'blank': 'True', 'null': 'True'}),
'credential': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'jobtemplates'", 'on_delete': 'models.SET_NULL', 'default': 'None', 'to': "orm['main.Credential']", 'blank': 'True', 'null': 'True'}),
'extra_vars': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
'force_handlers': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'forks': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0', 'blank': 'True'}),
'host_config_key': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
'inventory': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'jobtemplates'", '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', [], {'default': "'run'", 'max_length': '64'}),
'limit': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
'playbook': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
'project': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'jobtemplates'", 'on_delete': 'models.SET_NULL', 'default': 'None', 'to': "orm['main.Project']", 'blank': 'True', 'null': 'True'}),
'skip_tags': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
'start_at_task': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
'survey_enabled': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'survey_spec': ('jsonfield.fields.JSONField', [], {'default': '{}', 'blank': 'True'}),
u'unifiedjobtemplate_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['main.UnifiedJobTemplate']", 'unique': 'True', 'primary_key': 'True'}),
'verbosity': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0', 'blank': 'True'})
},
'main.organization': {
'Meta': {'ordering': "('name',)", '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', [], {'default': 'None'}),
'created_by': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', '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'}),
'modified': ('django.db.models.fields.DateTimeField', [], {'default': 'None'}),
'modified_by': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': '"{\'class\': \'organization\', \'app_label\': \'main\'}(class)s_modified+"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
'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': "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', [], {'default': 'None'}),
'created_by': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', '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']"}),
'modified': ('django.db.models.fields.DateTimeField', [], {'default': 'None'}),
'modified_by': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': '"{\'class\': \'permission\', \'app_label\': \'main\'}(class)s_modified+"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
'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': "orm['main.Project']"}),
'run_ad_hoc_commands': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'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']"})
},
'main.profile': {
'Meta': {'object_name': 'Profile'},
'created': ('django.db.models.fields.DateTimeField', [], {'default': 'None'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'ldap_dn': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024'}),
'modified': ('django.db.models.fields.DateTimeField', [], {'default': 'None'}),
'user': ('awx.main.fields.AutoOneToOneField', [], {'related_name': "'profile'", 'unique': 'True', 'to': u"orm['auth.User']"})
},
'main.project': {
'Meta': {'ordering': "('id',)", 'object_name': 'Project', '_ormbases': ['main.UnifiedJobTemplate']},
'credential': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'projects'", 'on_delete': 'models.SET_NULL', 'default': 'None', 'to': "orm['main.Credential']", 'blank': 'True', 'null': 'True'}),
'local_path': ('django.db.models.fields.CharField', [], {'max_length': '1024', 'blank': 'True'}),
'scm_branch': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '256', 'blank': 'True'}),
'scm_clean': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'scm_delete_on_next_update': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'scm_delete_on_update': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'scm_type': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '8', 'blank': 'True'}),
'scm_update_cache_timeout': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0', 'blank': 'True'}),
'scm_update_on_launch': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'scm_url': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
u'unifiedjobtemplate_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['main.UnifiedJobTemplate']", 'unique': 'True', 'primary_key': 'True'})
},
'main.projectupdate': {
'Meta': {'object_name': 'ProjectUpdate', '_ormbases': ['main.UnifiedJob']},
'credential': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'projectupdates'", 'on_delete': 'models.SET_NULL', 'default': 'None', 'to': "orm['main.Credential']", 'blank': 'True', 'null': 'True'}),
'local_path': ('django.db.models.fields.CharField', [], {'max_length': '1024', 'blank': 'True'}),
'project': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'project_updates'", 'to': "orm['main.Project']"}),
'scm_branch': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '256', 'blank': 'True'}),
'scm_clean': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'scm_delete_on_update': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'scm_type': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '8', 'blank': 'True'}),
'scm_url': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
u'unifiedjob_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['main.UnifiedJob']", 'unique': 'True', 'primary_key': 'True'})
},
'main.schedule': {
'Meta': {'ordering': "['-next_run']", 'object_name': 'Schedule'},
'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'created': ('django.db.models.fields.DateTimeField', [], {'default': 'None'}),
'created_by': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': '"{\'class\': \'schedule\', \'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'}),
'dtend': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True'}),
'dtstart': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True'}),
'enabled': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'extra_data': ('jsonfield.fields.JSONField', [], {'default': '{}', 'blank': 'True'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'modified': ('django.db.models.fields.DateTimeField', [], {'default': 'None'}),
'modified_by': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': '"{\'class\': \'schedule\', \'app_label\': \'main\'}(class)s_modified+"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '512'}),
'next_run': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True'}),
'rrule': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'unified_job_template': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'schedules'", 'to': "orm['main.UnifiedJobTemplate']"})
},
'main.systemjob': {
'Meta': {'ordering': "('id',)", 'object_name': 'SystemJob', '_ormbases': ['main.UnifiedJob']},
'extra_vars': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
'job_type': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '32', 'blank': 'True'}),
'system_job_template': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'jobs'", 'on_delete': 'models.SET_NULL', 'default': 'None', 'to': "orm['main.SystemJobTemplate']", 'blank': 'True', 'null': 'True'}),
u'unifiedjob_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['main.UnifiedJob']", 'unique': 'True', 'primary_key': 'True'})
},
'main.systemjobtemplate': {
'Meta': {'object_name': 'SystemJobTemplate', '_ormbases': ['main.UnifiedJobTemplate']},
'job_type': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '32', 'blank': 'True'}),
u'unifiedjobtemplate_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['main.UnifiedJobTemplate']", 'unique': 'True', 'primary_key': 'True'})
},
'main.team': {
'Meta': {'ordering': "('organization__name', 'name')", 'unique_together': "[('organization', 'name')]", 'object_name': 'Team'},
'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'created': ('django.db.models.fields.DateTimeField', [], {'default': 'None'}),
'created_by': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', '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'}),
'modified': ('django.db.models.fields.DateTimeField', [], {'default': 'None'}),
'modified_by': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': '"{\'class\': \'team\', \'app_label\': \'main\'}(class)s_modified+"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
'name': ('django.db.models.fields.CharField', [], {'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': "orm['main.Project']"}),
'users': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'teams'", 'blank': 'True', 'to': u"orm['auth.User']"})
},
'main.unifiedjob': {
'Meta': {'object_name': 'UnifiedJob'},
'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', [], {'default': 'None'}),
'created_by': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': '"{\'class\': \'unifiedjob\', \'app_label\': \'main\'}(class)s_created+"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
'dependent_jobs': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'dependent_jobs_rel_+'", 'to': "orm['main.UnifiedJob']"}),
'description': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
'elapsed': ('django.db.models.fields.DecimalField', [], {'max_digits': '12', 'decimal_places': '3'}),
'failed': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'finished': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'job_args': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
'job_cwd': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
'job_env': ('jsonfield.fields.JSONField', [], {'default': '{}', 'blank': 'True'}),
'job_explanation': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
'launch_type': ('django.db.models.fields.CharField', [], {'default': "'manual'", 'max_length': '20'}),
'modified': ('django.db.models.fields.DateTimeField', [], {'default': 'None'}),
'modified_by': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': '"{\'class\': \'unifiedjob\', \'app_label\': \'main\'}(class)s_modified+"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '512'}),
'old_pk': ('django.db.models.fields.PositiveIntegerField', [], {'default': 'None', 'null': 'True'}),
'polymorphic_ctype': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'polymorphic_main.unifiedjob_set'", 'null': 'True', 'to': u"orm['contenttypes.ContentType']"}),
'result_stdout_file': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
'result_stdout_text': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
'result_traceback': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
'schedule': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'to': "orm['main.Schedule']", 'null': 'True', 'on_delete': 'models.SET_NULL'}),
'start_args': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
'started': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True'}),
'status': ('django.db.models.fields.CharField', [], {'default': "'new'", 'max_length': '20'}),
'unified_job_template': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': "'unifiedjob_unified_jobs'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': "orm['main.UnifiedJobTemplate']"})
},
'main.unifiedjobtemplate': {
'Meta': {'unique_together': "[('polymorphic_ctype', 'name')]", 'object_name': 'UnifiedJobTemplate'},
'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'created': ('django.db.models.fields.DateTimeField', [], {'default': 'None'}),
'created_by': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': '"{\'class\': \'unifiedjobtemplate\', \'app_label\': \'main\'}(class)s_created+"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
'current_job': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': "'unifiedjobtemplate_as_current_job+'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': "orm['main.UnifiedJob']"}),
'description': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
'has_schedules': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'last_job': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': "'unifiedjobtemplate_as_last_job+'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': "orm['main.UnifiedJob']"}),
'last_job_failed': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'last_job_run': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True'}),
'modified': ('django.db.models.fields.DateTimeField', [], {'default': 'None'}),
'modified_by': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': '"{\'class\': \'unifiedjobtemplate\', \'app_label\': \'main\'}(class)s_modified+"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '512'}),
'next_job_run': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True'}),
'next_schedule': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': "'unifiedjobtemplate_as_next_schedule+'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': "orm['main.Schedule']"}),
'old_pk': ('django.db.models.fields.PositiveIntegerField', [], {'default': 'None', 'null': 'True'}),
'polymorphic_ctype': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'polymorphic_main.unifiedjobtemplate_set'", 'null': 'True', 'to': u"orm['contenttypes.ContentType']"}),
'status': ('django.db.models.fields.CharField', [], {'default': "'ok'", 'max_length': '32'})
}
}
complete_apps = ['main']
+523
View File
@@ -0,0 +1,523 @@
# -*- coding: utf-8 -*-
from south.utils import datetime_utils as datetime
from south.db import db
from south.v2 import SchemaMigration
from django.db import models
class Migration(SchemaMigration):
def forwards(self, orm):
# Adding field 'Credential.security_token'
db.add_column(u'main_credential', 'security_token',
self.gf('django.db.models.fields.CharField')(default='', max_length=1024, blank=True),
keep_default=False)
def backwards(self, orm):
# Deleting field 'Credential.security_token'
db.delete_column(u'main_credential', 'security_token')
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', [], {'symmetrical': 'False', 'related_name': "u'user_set'", 'blank': 'True', 'to': u"orm['auth.Group']"}),
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', [], {'symmetrical': 'False', 'related_name': "u'user_set'", 'blank': 'True', 'to': u"orm['auth.Permission']"}),
'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.activitystream': {
'Meta': {'object_name': 'ActivityStream'},
'actor': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'activity_stream'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
'ad_hoc_command': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['main.AdHocCommand']", 'symmetrical': 'False', 'blank': 'True'}),
'changes': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'credential': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['main.Credential']", 'symmetrical': 'False', 'blank': 'True'}),
'custom_inventory_script': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['main.CustomInventoryScript']", 'symmetrical': 'False', 'blank': 'True'}),
'group': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['main.Group']", 'symmetrical': 'False', 'blank': 'True'}),
'host': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['main.Host']", 'symmetrical': 'False', 'blank': 'True'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'inventory': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['main.Inventory']", 'symmetrical': 'False', 'blank': 'True'}),
'inventory_source': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['main.InventorySource']", 'symmetrical': 'False', 'blank': 'True'}),
'inventory_update': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['main.InventoryUpdate']", 'symmetrical': 'False', 'blank': 'True'}),
'job': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['main.Job']", 'symmetrical': 'False', 'blank': 'True'}),
'job_template': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['main.JobTemplate']", 'symmetrical': 'False', 'blank': 'True'}),
'object1': ('django.db.models.fields.TextField', [], {}),
'object2': ('django.db.models.fields.TextField', [], {}),
'object_relationship_type': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'operation': ('django.db.models.fields.CharField', [], {'max_length': '13'}),
'organization': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['main.Organization']", 'symmetrical': 'False', 'blank': 'True'}),
'permission': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['main.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
'project': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['main.Project']", 'symmetrical': 'False', 'blank': 'True'}),
'project_update': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['main.ProjectUpdate']", 'symmetrical': 'False', 'blank': 'True'}),
'schedule': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['main.Schedule']", 'symmetrical': 'False', 'blank': 'True'}),
'team': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['main.Team']", 'symmetrical': 'False', 'blank': 'True'}),
'timestamp': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'unified_job': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'activity_stream_as_unified_job+'", 'blank': 'True', 'to': "orm['main.UnifiedJob']"}),
'unified_job_template': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'activity_stream_as_unified_job_template+'", 'blank': 'True', 'to': "orm['main.UnifiedJobTemplate']"}),
'user': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.User']", 'symmetrical': 'False', 'blank': 'True'})
},
'main.adhoccommand': {
'Meta': {'object_name': 'AdHocCommand', '_ormbases': ['main.UnifiedJob']},
'become_enabled': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'credential': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': "'ad_hoc_commands'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': "orm['main.Credential']"}),
'forks': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0', 'blank': 'True'}),
'hosts': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'ad_hoc_commands'", 'symmetrical': 'False', 'through': "orm['main.AdHocCommandEvent']", 'to': "orm['main.Host']"}),
'inventory': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'ad_hoc_commands'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': "orm['main.Inventory']"}),
'job_type': ('django.db.models.fields.CharField', [], {'default': "'run'", 'max_length': '64'}),
'limit': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
'module_args': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
'module_name': ('django.db.models.fields.CharField', [], {'default': "'command'", 'max_length': '1024', 'blank': 'True'}),
u'unifiedjob_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['main.UnifiedJob']", 'unique': 'True', 'primary_key': 'True'}),
'verbosity': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0', 'blank': 'True'})
},
'main.adhoccommandevent': {
'Meta': {'ordering': "('-pk',)", 'unique_together': "[('ad_hoc_command', 'host_name')]", 'object_name': 'AdHocCommandEvent'},
'ad_hoc_command': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'ad_hoc_command_events'", 'to': "orm['main.AdHocCommand']"}),
'changed': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'counter': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
'created': ('django.db.models.fields.DateTimeField', [], {'default': 'None'}),
'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', [], {'default': 'None', 'related_name': "'ad_hoc_command_events'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': "orm['main.Host']"}),
'host_name': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'modified': ('django.db.models.fields.DateTimeField', [], {'default': 'None'})
},
'main.authtoken': {
'Meta': {'object_name': 'AuthToken'},
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'expires': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'key': ('django.db.models.fields.CharField', [], {'max_length': '40', 'primary_key': 'True'}),
'modified': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
'reason': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
'request_hash': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '40', 'blank': 'True'}),
'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'auth_tokens'", 'to': u"orm['auth.User']"})
},
'main.credential': {
'Meta': {'ordering': "('kind', 'name')", 'unique_together': "[('user', 'team', 'kind', 'name')]", 'object_name': 'Credential'},
'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'become_method': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '32', 'blank': 'True'}),
'become_password': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
'become_username': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
'cloud': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'created': ('django.db.models.fields.DateTimeField', [], {'default': 'None'}),
'created_by': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', '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'}),
'host': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'kind': ('django.db.models.fields.CharField', [], {'default': "'ssh'", 'max_length': '32'}),
'modified': ('django.db.models.fields.DateTimeField', [], {'default': 'None'}),
'modified_by': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': '"{\'class\': \'credential\', \'app_label\': \'main\'}(class)s_modified+"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '512'}),
'password': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
'project': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '100', 'blank': 'True'}),
'security_token': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
'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'}),
'team': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': "'credentials'", 'null': 'True', 'blank': 'True', 'to': "orm['main.Team']"}),
'user': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': "'credentials'", 'null': 'True', 'blank': 'True', 'to': u"orm['auth.User']"}),
'username': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
'vault_password': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'})
},
'main.custominventoryscript': {
'Meta': {'ordering': "('name',)", 'unique_together': "[('name', 'organization')]", 'object_name': 'CustomInventoryScript'},
'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'created': ('django.db.models.fields.DateTimeField', [], {'default': 'None'}),
'created_by': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': '"{\'class\': \'custominventoryscript\', \'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'}),
'modified': ('django.db.models.fields.DateTimeField', [], {'default': 'None'}),
'modified_by': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': '"{\'class\': \'custominventoryscript\', \'app_label\': \'main\'}(class)s_modified+"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '512'}),
'organization': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'custom_inventory_scripts'", 'to': "orm['main.Organization']"}),
'script': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'})
},
'main.group': {
'Meta': {'ordering': "('name',)", 'unique_together': "(('name', 'inventory'),)", 'object_name': 'Group'},
'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'created': ('django.db.models.fields.DateTimeField', [], {'default': 'None'}),
'created_by': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', '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'}),
'groups_with_active_failures': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
'has_active_failures': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'has_inventory_sources': ('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']"}),
'hosts_with_active_failures': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'inventory': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'groups'", 'to': "orm['main.Inventory']"}),
'inventory_sources': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'groups'", 'symmetrical': 'False', 'to': "orm['main.InventorySource']"}),
'modified': ('django.db.models.fields.DateTimeField', [], {'default': 'None'}),
'modified_by': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': '"{\'class\': \'group\', \'app_label\': \'main\'}(class)s_modified+"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
'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']"}),
'total_groups': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
'total_hosts': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
'variables': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'})
},
'main.host': {
'Meta': {'ordering': "('inventory', 'name')", 'unique_together': "(('name', 'inventory'),)", 'object_name': 'Host'},
'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'created': ('django.db.models.fields.DateTimeField', [], {'default': 'None'}),
'created_by': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', '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'}),
'enabled': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'has_active_failures': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'has_inventory_sources': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'instance_id': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '100', 'blank': 'True'}),
'inventory': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'hosts'", 'to': "orm['main.Inventory']"}),
'inventory_sources': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'hosts'", 'symmetrical': 'False', 'to': "orm['main.InventorySource']"}),
'last_job': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': "'hosts_as_last_job+'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': "orm['main.Job']"}),
'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': "orm['main.JobHostSummary']", 'blank': 'True', 'null': 'True'}),
'modified': ('django.db.models.fields.DateTimeField', [], {'default': 'None'}),
'modified_by': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': '"{\'class\': \'host\', \'app_label\': \'main\'}(class)s_modified+"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '512'}),
'variables': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'})
},
'main.instance': {
'Meta': {'object_name': 'Instance'},
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'hostname': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '250'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'modified': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
'primary': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'uuid': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '40'})
},
'main.inventory': {
'Meta': {'ordering': "('name',)", 'unique_together': "[('name', 'organization')]", 'object_name': 'Inventory'},
'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'created': ('django.db.models.fields.DateTimeField', [], {'default': 'None'}),
'created_by': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', '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'}),
'groups_with_active_failures': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
'has_active_failures': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'has_inventory_sources': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'hosts_with_active_failures': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'inventory_sources_with_failures': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
'modified': ('django.db.models.fields.DateTimeField', [], {'default': 'None'}),
'modified_by': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': '"{\'class\': \'inventory\', \'app_label\': \'main\'}(class)s_modified+"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
'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']"}),
'total_groups': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
'total_hosts': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
'total_inventory_sources': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
'variables': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'})
},
'main.inventorysource': {
'Meta': {'object_name': 'InventorySource', '_ormbases': ['main.UnifiedJobTemplate']},
'credential': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'inventorysources'", 'on_delete': 'models.SET_NULL', 'default': 'None', 'to': "orm['main.Credential']", 'blank': 'True', 'null': 'True'}),
'group': ('awx.main.fields.AutoOneToOneField', [], {'default': 'None', 'related_name': "'inventory_source'", 'unique': 'True', 'null': 'True', 'to': "orm['main.Group']"}),
'group_by': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
'instance_filters': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
'inventory': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': "'inventory_sources'", 'null': 'True', 'to': "orm['main.Inventory']"}),
'overwrite': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'overwrite_vars': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'source': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '32', 'blank': 'True'}),
'source_path': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
'source_regions': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
'source_script': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'to': "orm['main.CustomInventoryScript']", 'null': 'True', 'on_delete': 'models.SET_NULL', 'blank': 'True'}),
'source_vars': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
u'unifiedjobtemplate_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['main.UnifiedJobTemplate']", 'unique': 'True', 'primary_key': 'True'}),
'update_cache_timeout': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
'update_on_launch': ('django.db.models.fields.BooleanField', [], {'default': 'False'})
},
'main.inventoryupdate': {
'Meta': {'object_name': 'InventoryUpdate', '_ormbases': ['main.UnifiedJob']},
'credential': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'inventoryupdates'", 'on_delete': 'models.SET_NULL', 'default': 'None', 'to': "orm['main.Credential']", 'blank': 'True', 'null': 'True'}),
'group_by': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
'instance_filters': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
'inventory_source': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'inventory_updates'", 'to': "orm['main.InventorySource']"}),
'license_error': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'overwrite': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'overwrite_vars': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'source': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '32', 'blank': 'True'}),
'source_path': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
'source_regions': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
'source_script': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'to': "orm['main.CustomInventoryScript']", 'null': 'True', 'on_delete': 'models.SET_NULL', 'blank': 'True'}),
'source_vars': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
u'unifiedjob_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['main.UnifiedJob']", 'unique': 'True', 'primary_key': 'True'})
},
'main.job': {
'Meta': {'ordering': "('id',)", 'object_name': 'Job', '_ormbases': ['main.UnifiedJob']},
'become_enabled': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'cloud_credential': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'jobs_as_cloud_credential+'", 'on_delete': 'models.SET_NULL', 'default': 'None', 'to': "orm['main.Credential']", 'blank': 'True', 'null': 'True'}),
'credential': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'jobs'", 'on_delete': 'models.SET_NULL', 'default': 'None', 'to': "orm['main.Credential']", 'blank': 'True', 'null': 'True'}),
'extra_vars': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
'force_handlers': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'forks': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0', 'blank': 'True'}),
'hosts': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'jobs'", 'symmetrical': 'False', 'through': "orm['main.JobHostSummary']", 'to': "orm['main.Host']"}),
'inventory': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'jobs'", '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_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', [], {'default': "'run'", 'max_length': '64'}),
'limit': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
'playbook': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
'project': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'jobs'", 'on_delete': 'models.SET_NULL', 'default': 'None', 'to': "orm['main.Project']", 'blank': 'True', 'null': 'True'}),
'skip_tags': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
'start_at_task': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
u'unifiedjob_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['main.UnifiedJob']", 'unique': 'True', 'primary_key': 'True'}),
'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'}),
'counter': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
'created': ('django.db.models.fields.DateTimeField', [], {'default': 'None'}),
'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', [], {'default': 'None', 'related_name': "'job_events_as_primary_host'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': "orm['main.Host']"}),
'host_name': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024'}),
'hosts': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'job_events'", 'symmetrical': 'False', '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']"}),
'modified': ('django.db.models.fields.DateTimeField', [], {'default': 'None'}),
'parent': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': "'children'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': "orm['main.JobEvent']"}),
'play': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024'}),
'role': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024'}),
'task': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024'})
},
'main.jobhostsummary': {
'Meta': {'ordering': "('-pk',)", 'unique_together': "[('job', 'host_name')]", 'object_name': 'JobHostSummary'},
'changed': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
'created': ('django.db.models.fields.DateTimeField', [], {'default': 'None'}),
'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', [], {'default': 'None', 'related_name': "'job_host_summaries'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': "orm['main.Host']"}),
'host_name': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024'}),
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']"}),
'modified': ('django.db.models.fields.DateTimeField', [], {'default': 'None'}),
'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.joborigin': {
'Meta': {'object_name': 'JobOrigin'},
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'instance': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['main.Instance']"}),
'modified': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
'unified_job': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'job_origin'", 'unique': 'True', 'to': "orm['main.UnifiedJob']"})
},
'main.jobtemplate': {
'Meta': {'ordering': "('name',)", 'object_name': 'JobTemplate', '_ormbases': ['main.UnifiedJobTemplate']},
'ask_variables_on_launch': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'become_enabled': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'cloud_credential': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'jobtemplates_as_cloud_credential+'", 'on_delete': 'models.SET_NULL', 'default': 'None', 'to': "orm['main.Credential']", 'blank': 'True', 'null': 'True'}),
'credential': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'jobtemplates'", 'on_delete': 'models.SET_NULL', 'default': 'None', 'to': "orm['main.Credential']", 'blank': 'True', 'null': 'True'}),
'extra_vars': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
'force_handlers': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'forks': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0', 'blank': 'True'}),
'host_config_key': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
'inventory': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'jobtemplates'", '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', [], {'default': "'run'", 'max_length': '64'}),
'limit': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
'playbook': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
'project': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'jobtemplates'", 'on_delete': 'models.SET_NULL', 'default': 'None', 'to': "orm['main.Project']", 'blank': 'True', 'null': 'True'}),
'skip_tags': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
'start_at_task': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
'survey_enabled': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'survey_spec': ('jsonfield.fields.JSONField', [], {'default': '{}', 'blank': 'True'}),
u'unifiedjobtemplate_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['main.UnifiedJobTemplate']", 'unique': 'True', 'primary_key': 'True'}),
'verbosity': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0', 'blank': 'True'})
},
'main.organization': {
'Meta': {'ordering': "('name',)", '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', [], {'default': 'None'}),
'created_by': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', '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'}),
'modified': ('django.db.models.fields.DateTimeField', [], {'default': 'None'}),
'modified_by': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': '"{\'class\': \'organization\', \'app_label\': \'main\'}(class)s_modified+"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
'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': "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', [], {'default': 'None'}),
'created_by': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', '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']"}),
'modified': ('django.db.models.fields.DateTimeField', [], {'default': 'None'}),
'modified_by': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': '"{\'class\': \'permission\', \'app_label\': \'main\'}(class)s_modified+"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
'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': "orm['main.Project']"}),
'run_ad_hoc_commands': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'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']"})
},
'main.profile': {
'Meta': {'object_name': 'Profile'},
'created': ('django.db.models.fields.DateTimeField', [], {'default': 'None'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'ldap_dn': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024'}),
'modified': ('django.db.models.fields.DateTimeField', [], {'default': 'None'}),
'user': ('awx.main.fields.AutoOneToOneField', [], {'related_name': "'profile'", 'unique': 'True', 'to': u"orm['auth.User']"})
},
'main.project': {
'Meta': {'ordering': "('id',)", 'object_name': 'Project', '_ormbases': ['main.UnifiedJobTemplate']},
'credential': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'projects'", 'on_delete': 'models.SET_NULL', 'default': 'None', 'to': "orm['main.Credential']", 'blank': 'True', 'null': 'True'}),
'local_path': ('django.db.models.fields.CharField', [], {'max_length': '1024', 'blank': 'True'}),
'scm_branch': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '256', 'blank': 'True'}),
'scm_clean': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'scm_delete_on_next_update': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'scm_delete_on_update': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'scm_type': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '8', 'blank': 'True'}),
'scm_update_cache_timeout': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0', 'blank': 'True'}),
'scm_update_on_launch': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'scm_url': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
u'unifiedjobtemplate_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['main.UnifiedJobTemplate']", 'unique': 'True', 'primary_key': 'True'})
},
'main.projectupdate': {
'Meta': {'object_name': 'ProjectUpdate', '_ormbases': ['main.UnifiedJob']},
'credential': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'projectupdates'", 'on_delete': 'models.SET_NULL', 'default': 'None', 'to': "orm['main.Credential']", 'blank': 'True', 'null': 'True'}),
'local_path': ('django.db.models.fields.CharField', [], {'max_length': '1024', 'blank': 'True'}),
'project': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'project_updates'", 'to': "orm['main.Project']"}),
'scm_branch': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '256', 'blank': 'True'}),
'scm_clean': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'scm_delete_on_update': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'scm_type': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '8', 'blank': 'True'}),
'scm_url': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
u'unifiedjob_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['main.UnifiedJob']", 'unique': 'True', 'primary_key': 'True'})
},
'main.schedule': {
'Meta': {'ordering': "['-next_run']", 'object_name': 'Schedule'},
'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'created': ('django.db.models.fields.DateTimeField', [], {'default': 'None'}),
'created_by': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': '"{\'class\': \'schedule\', \'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'}),
'dtend': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True'}),
'dtstart': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True'}),
'enabled': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'extra_data': ('jsonfield.fields.JSONField', [], {'default': '{}', 'blank': 'True'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'modified': ('django.db.models.fields.DateTimeField', [], {'default': 'None'}),
'modified_by': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': '"{\'class\': \'schedule\', \'app_label\': \'main\'}(class)s_modified+"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '512'}),
'next_run': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True'}),
'rrule': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'unified_job_template': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'schedules'", 'to': "orm['main.UnifiedJobTemplate']"})
},
'main.systemjob': {
'Meta': {'ordering': "('id',)", 'object_name': 'SystemJob', '_ormbases': ['main.UnifiedJob']},
'extra_vars': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
'job_type': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '32', 'blank': 'True'}),
'system_job_template': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'jobs'", 'on_delete': 'models.SET_NULL', 'default': 'None', 'to': "orm['main.SystemJobTemplate']", 'blank': 'True', 'null': 'True'}),
u'unifiedjob_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['main.UnifiedJob']", 'unique': 'True', 'primary_key': 'True'})
},
'main.systemjobtemplate': {
'Meta': {'object_name': 'SystemJobTemplate', '_ormbases': ['main.UnifiedJobTemplate']},
'job_type': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '32', 'blank': 'True'}),
u'unifiedjobtemplate_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['main.UnifiedJobTemplate']", 'unique': 'True', 'primary_key': 'True'})
},
'main.team': {
'Meta': {'ordering': "('organization__name', 'name')", 'unique_together': "[('organization', 'name')]", 'object_name': 'Team'},
'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'created': ('django.db.models.fields.DateTimeField', [], {'default': 'None'}),
'created_by': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', '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'}),
'modified': ('django.db.models.fields.DateTimeField', [], {'default': 'None'}),
'modified_by': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': '"{\'class\': \'team\', \'app_label\': \'main\'}(class)s_modified+"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
'name': ('django.db.models.fields.CharField', [], {'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': "orm['main.Project']"}),
'users': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'teams'", 'blank': 'True', 'to': u"orm['auth.User']"})
},
'main.unifiedjob': {
'Meta': {'object_name': 'UnifiedJob'},
'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', [], {'default': 'None'}),
'created_by': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': '"{\'class\': \'unifiedjob\', \'app_label\': \'main\'}(class)s_created+"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
'dependent_jobs': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'dependent_jobs_rel_+'", 'to': "orm['main.UnifiedJob']"}),
'description': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
'elapsed': ('django.db.models.fields.DecimalField', [], {'max_digits': '12', 'decimal_places': '3'}),
'failed': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'finished': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'job_args': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
'job_cwd': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
'job_env': ('jsonfield.fields.JSONField', [], {'default': '{}', 'blank': 'True'}),
'job_explanation': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
'launch_type': ('django.db.models.fields.CharField', [], {'default': "'manual'", 'max_length': '20'}),
'modified': ('django.db.models.fields.DateTimeField', [], {'default': 'None'}),
'modified_by': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': '"{\'class\': \'unifiedjob\', \'app_label\': \'main\'}(class)s_modified+"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '512'}),
'old_pk': ('django.db.models.fields.PositiveIntegerField', [], {'default': 'None', 'null': 'True'}),
'polymorphic_ctype': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'polymorphic_main.unifiedjob_set'", 'null': 'True', 'to': u"orm['contenttypes.ContentType']"}),
'result_stdout_file': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
'result_stdout_text': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
'result_traceback': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
'schedule': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'to': "orm['main.Schedule']", 'null': 'True', 'on_delete': 'models.SET_NULL'}),
'start_args': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
'started': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True'}),
'status': ('django.db.models.fields.CharField', [], {'default': "'new'", 'max_length': '20'}),
'unified_job_template': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': "'unifiedjob_unified_jobs'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': "orm['main.UnifiedJobTemplate']"})
},
'main.unifiedjobtemplate': {
'Meta': {'unique_together': "[('polymorphic_ctype', 'name')]", 'object_name': 'UnifiedJobTemplate'},
'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'created': ('django.db.models.fields.DateTimeField', [], {'default': 'None'}),
'created_by': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': '"{\'class\': \'unifiedjobtemplate\', \'app_label\': \'main\'}(class)s_created+"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
'current_job': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': "'unifiedjobtemplate_as_current_job+'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': "orm['main.UnifiedJob']"}),
'description': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
'has_schedules': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'last_job': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': "'unifiedjobtemplate_as_last_job+'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': "orm['main.UnifiedJob']"}),
'last_job_failed': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'last_job_run': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True'}),
'modified': ('django.db.models.fields.DateTimeField', [], {'default': 'None'}),
'modified_by': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': '"{\'class\': \'unifiedjobtemplate\', \'app_label\': \'main\'}(class)s_modified+"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '512'}),
'next_job_run': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True'}),
'next_schedule': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': "'unifiedjobtemplate_as_next_schedule+'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': "orm['main.Schedule']"}),
'old_pk': ('django.db.models.fields.PositiveIntegerField', [], {'default': 'None', 'null': 'True'}),
'polymorphic_ctype': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'polymorphic_main.unifiedjobtemplate_set'", 'null': 'True', 'to': u"orm['contenttypes.ContentType']"}),
'status': ('django.db.models.fields.CharField', [], {'default': "'ok'", 'max_length': '32'})
}
}
complete_apps = ['main']
+519
View File
@@ -0,0 +1,519 @@
# -*- coding: utf-8 -*-
from south.utils import datetime_utils as datetime
from south.db import db
from south.v2 import SchemaMigration
from django.db import models
class Migration(SchemaMigration):
def forwards(self, orm):
# Changing field 'CustomInventoryScript.organization'
db.alter_column(u'main_custominventoryscript', 'organization_id', self.gf('django.db.models.fields.related.ForeignKey')(null=True, on_delete=models.SET_NULL, to=orm['main.Organization']))
def backwards(self, orm):
# Changing field 'CustomInventoryScript.organization'
db.alter_column(u'main_custominventoryscript', 'organization_id', self.gf('django.db.models.fields.related.ForeignKey')(default=None, to=orm['main.Organization']))
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', [], {'symmetrical': 'False', 'related_name': "u'user_set'", 'blank': 'True', 'to': u"orm['auth.Group']"}),
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', [], {'symmetrical': 'False', 'related_name': "u'user_set'", 'blank': 'True', 'to': u"orm['auth.Permission']"}),
'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.activitystream': {
'Meta': {'object_name': 'ActivityStream'},
'actor': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'activity_stream'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
'ad_hoc_command': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['main.AdHocCommand']", 'symmetrical': 'False', 'blank': 'True'}),
'changes': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'credential': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['main.Credential']", 'symmetrical': 'False', 'blank': 'True'}),
'custom_inventory_script': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['main.CustomInventoryScript']", 'symmetrical': 'False', 'blank': 'True'}),
'group': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['main.Group']", 'symmetrical': 'False', 'blank': 'True'}),
'host': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['main.Host']", 'symmetrical': 'False', 'blank': 'True'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'inventory': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['main.Inventory']", 'symmetrical': 'False', 'blank': 'True'}),
'inventory_source': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['main.InventorySource']", 'symmetrical': 'False', 'blank': 'True'}),
'inventory_update': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['main.InventoryUpdate']", 'symmetrical': 'False', 'blank': 'True'}),
'job': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['main.Job']", 'symmetrical': 'False', 'blank': 'True'}),
'job_template': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['main.JobTemplate']", 'symmetrical': 'False', 'blank': 'True'}),
'object1': ('django.db.models.fields.TextField', [], {}),
'object2': ('django.db.models.fields.TextField', [], {}),
'object_relationship_type': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'operation': ('django.db.models.fields.CharField', [], {'max_length': '13'}),
'organization': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['main.Organization']", 'symmetrical': 'False', 'blank': 'True'}),
'permission': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['main.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
'project': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['main.Project']", 'symmetrical': 'False', 'blank': 'True'}),
'project_update': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['main.ProjectUpdate']", 'symmetrical': 'False', 'blank': 'True'}),
'schedule': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['main.Schedule']", 'symmetrical': 'False', 'blank': 'True'}),
'team': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['main.Team']", 'symmetrical': 'False', 'blank': 'True'}),
'timestamp': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'unified_job': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'activity_stream_as_unified_job+'", 'blank': 'True', 'to': "orm['main.UnifiedJob']"}),
'unified_job_template': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'activity_stream_as_unified_job_template+'", 'blank': 'True', 'to': "orm['main.UnifiedJobTemplate']"}),
'user': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.User']", 'symmetrical': 'False', 'blank': 'True'})
},
'main.adhoccommand': {
'Meta': {'object_name': 'AdHocCommand', '_ormbases': ['main.UnifiedJob']},
'become_enabled': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'credential': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': "'ad_hoc_commands'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': "orm['main.Credential']"}),
'forks': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0', 'blank': 'True'}),
'hosts': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'ad_hoc_commands'", 'symmetrical': 'False', 'through': "orm['main.AdHocCommandEvent']", 'to': "orm['main.Host']"}),
'inventory': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'ad_hoc_commands'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': "orm['main.Inventory']"}),
'job_type': ('django.db.models.fields.CharField', [], {'default': "'run'", 'max_length': '64'}),
'limit': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
'module_args': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
'module_name': ('django.db.models.fields.CharField', [], {'default': "'command'", 'max_length': '1024', 'blank': 'True'}),
u'unifiedjob_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['main.UnifiedJob']", 'unique': 'True', 'primary_key': 'True'}),
'verbosity': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0', 'blank': 'True'})
},
'main.adhoccommandevent': {
'Meta': {'ordering': "('-pk',)", 'unique_together': "[('ad_hoc_command', 'host_name')]", 'object_name': 'AdHocCommandEvent'},
'ad_hoc_command': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'ad_hoc_command_events'", 'to': "orm['main.AdHocCommand']"}),
'changed': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'counter': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
'created': ('django.db.models.fields.DateTimeField', [], {'default': 'None'}),
'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', [], {'default': 'None', 'related_name': "'ad_hoc_command_events'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': "orm['main.Host']"}),
'host_name': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'modified': ('django.db.models.fields.DateTimeField', [], {'default': 'None'})
},
'main.authtoken': {
'Meta': {'object_name': 'AuthToken'},
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'expires': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'key': ('django.db.models.fields.CharField', [], {'max_length': '40', 'primary_key': 'True'}),
'modified': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
'reason': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
'request_hash': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '40', 'blank': 'True'}),
'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'auth_tokens'", 'to': u"orm['auth.User']"})
},
'main.credential': {
'Meta': {'ordering': "('kind', 'name')", 'unique_together': "[('user', 'team', 'kind', 'name')]", 'object_name': 'Credential'},
'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'become_method': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '32', 'blank': 'True'}),
'become_password': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
'become_username': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
'cloud': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'created': ('django.db.models.fields.DateTimeField', [], {'default': 'None'}),
'created_by': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', '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'}),
'host': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'kind': ('django.db.models.fields.CharField', [], {'default': "'ssh'", 'max_length': '32'}),
'modified': ('django.db.models.fields.DateTimeField', [], {'default': 'None'}),
'modified_by': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': '"{\'class\': \'credential\', \'app_label\': \'main\'}(class)s_modified+"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '512'}),
'password': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
'project': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '100', 'blank': 'True'}),
'security_token': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
'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'}),
'team': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': "'credentials'", 'null': 'True', 'blank': 'True', 'to': "orm['main.Team']"}),
'user': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': "'credentials'", 'null': 'True', 'blank': 'True', 'to': u"orm['auth.User']"}),
'username': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
'vault_password': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'})
},
'main.custominventoryscript': {
'Meta': {'ordering': "('name',)", 'unique_together': "[('name', 'organization')]", 'object_name': 'CustomInventoryScript'},
'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'created': ('django.db.models.fields.DateTimeField', [], {'default': 'None'}),
'created_by': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': '"{\'class\': \'custominventoryscript\', \'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'}),
'modified': ('django.db.models.fields.DateTimeField', [], {'default': 'None'}),
'modified_by': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': '"{\'class\': \'custominventoryscript\', \'app_label\': \'main\'}(class)s_modified+"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '512'}),
'organization': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'custom_inventory_scripts'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': "orm['main.Organization']"}),
'script': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'})
},
'main.group': {
'Meta': {'ordering': "('name',)", 'unique_together': "(('name', 'inventory'),)", 'object_name': 'Group'},
'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'created': ('django.db.models.fields.DateTimeField', [], {'default': 'None'}),
'created_by': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', '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'}),
'groups_with_active_failures': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
'has_active_failures': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'has_inventory_sources': ('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']"}),
'hosts_with_active_failures': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'inventory': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'groups'", 'to': "orm['main.Inventory']"}),
'inventory_sources': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'groups'", 'symmetrical': 'False', 'to': "orm['main.InventorySource']"}),
'modified': ('django.db.models.fields.DateTimeField', [], {'default': 'None'}),
'modified_by': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': '"{\'class\': \'group\', \'app_label\': \'main\'}(class)s_modified+"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
'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']"}),
'total_groups': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
'total_hosts': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
'variables': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'})
},
'main.host': {
'Meta': {'ordering': "('inventory', 'name')", 'unique_together': "(('name', 'inventory'),)", 'object_name': 'Host'},
'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'created': ('django.db.models.fields.DateTimeField', [], {'default': 'None'}),
'created_by': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', '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'}),
'enabled': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'has_active_failures': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'has_inventory_sources': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'instance_id': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '100', 'blank': 'True'}),
'inventory': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'hosts'", 'to': "orm['main.Inventory']"}),
'inventory_sources': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'hosts'", 'symmetrical': 'False', 'to': "orm['main.InventorySource']"}),
'last_job': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': "'hosts_as_last_job+'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': "orm['main.Job']"}),
'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': "orm['main.JobHostSummary']", 'blank': 'True', 'null': 'True'}),
'modified': ('django.db.models.fields.DateTimeField', [], {'default': 'None'}),
'modified_by': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': '"{\'class\': \'host\', \'app_label\': \'main\'}(class)s_modified+"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '512'}),
'variables': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'})
},
'main.instance': {
'Meta': {'object_name': 'Instance'},
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'hostname': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '250'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'modified': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
'primary': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'uuid': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '40'})
},
'main.inventory': {
'Meta': {'ordering': "('name',)", 'unique_together': "[('name', 'organization')]", 'object_name': 'Inventory'},
'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'created': ('django.db.models.fields.DateTimeField', [], {'default': 'None'}),
'created_by': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', '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'}),
'groups_with_active_failures': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
'has_active_failures': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'has_inventory_sources': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'hosts_with_active_failures': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'inventory_sources_with_failures': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
'modified': ('django.db.models.fields.DateTimeField', [], {'default': 'None'}),
'modified_by': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': '"{\'class\': \'inventory\', \'app_label\': \'main\'}(class)s_modified+"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
'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']"}),
'total_groups': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
'total_hosts': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
'total_inventory_sources': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
'variables': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'})
},
'main.inventorysource': {
'Meta': {'object_name': 'InventorySource', '_ormbases': ['main.UnifiedJobTemplate']},
'credential': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'inventorysources'", 'on_delete': 'models.SET_NULL', 'default': 'None', 'to': "orm['main.Credential']", 'blank': 'True', 'null': 'True'}),
'group': ('awx.main.fields.AutoOneToOneField', [], {'default': 'None', 'related_name': "'inventory_source'", 'unique': 'True', 'null': 'True', 'to': "orm['main.Group']"}),
'group_by': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
'instance_filters': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
'inventory': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': "'inventory_sources'", 'null': 'True', 'to': "orm['main.Inventory']"}),
'overwrite': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'overwrite_vars': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'source': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '32', 'blank': 'True'}),
'source_path': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
'source_regions': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
'source_script': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'to': "orm['main.CustomInventoryScript']", 'null': 'True', 'on_delete': 'models.SET_NULL', 'blank': 'True'}),
'source_vars': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
u'unifiedjobtemplate_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['main.UnifiedJobTemplate']", 'unique': 'True', 'primary_key': 'True'}),
'update_cache_timeout': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
'update_on_launch': ('django.db.models.fields.BooleanField', [], {'default': 'False'})
},
'main.inventoryupdate': {
'Meta': {'object_name': 'InventoryUpdate', '_ormbases': ['main.UnifiedJob']},
'credential': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'inventoryupdates'", 'on_delete': 'models.SET_NULL', 'default': 'None', 'to': "orm['main.Credential']", 'blank': 'True', 'null': 'True'}),
'group_by': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
'instance_filters': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
'inventory_source': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'inventory_updates'", 'to': "orm['main.InventorySource']"}),
'license_error': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'overwrite': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'overwrite_vars': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'source': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '32', 'blank': 'True'}),
'source_path': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
'source_regions': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
'source_script': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'to': "orm['main.CustomInventoryScript']", 'null': 'True', 'on_delete': 'models.SET_NULL', 'blank': 'True'}),
'source_vars': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
u'unifiedjob_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['main.UnifiedJob']", 'unique': 'True', 'primary_key': 'True'})
},
'main.job': {
'Meta': {'ordering': "('id',)", 'object_name': 'Job', '_ormbases': ['main.UnifiedJob']},
'become_enabled': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'cloud_credential': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'jobs_as_cloud_credential+'", 'on_delete': 'models.SET_NULL', 'default': 'None', 'to': "orm['main.Credential']", 'blank': 'True', 'null': 'True'}),
'credential': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'jobs'", 'on_delete': 'models.SET_NULL', 'default': 'None', 'to': "orm['main.Credential']", 'blank': 'True', 'null': 'True'}),
'extra_vars': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
'force_handlers': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'forks': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0', 'blank': 'True'}),
'hosts': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'jobs'", 'symmetrical': 'False', 'through': "orm['main.JobHostSummary']", 'to': "orm['main.Host']"}),
'inventory': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'jobs'", '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_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', [], {'default': "'run'", 'max_length': '64'}),
'limit': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
'playbook': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
'project': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'jobs'", 'on_delete': 'models.SET_NULL', 'default': 'None', 'to': "orm['main.Project']", 'blank': 'True', 'null': 'True'}),
'skip_tags': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
'start_at_task': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
u'unifiedjob_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['main.UnifiedJob']", 'unique': 'True', 'primary_key': 'True'}),
'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'}),
'counter': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
'created': ('django.db.models.fields.DateTimeField', [], {'default': 'None'}),
'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', [], {'default': 'None', 'related_name': "'job_events_as_primary_host'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': "orm['main.Host']"}),
'host_name': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024'}),
'hosts': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'job_events'", 'symmetrical': 'False', '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']"}),
'modified': ('django.db.models.fields.DateTimeField', [], {'default': 'None'}),
'parent': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': "'children'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': "orm['main.JobEvent']"}),
'play': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024'}),
'role': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024'}),
'task': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024'})
},
'main.jobhostsummary': {
'Meta': {'ordering': "('-pk',)", 'unique_together': "[('job', 'host_name')]", 'object_name': 'JobHostSummary'},
'changed': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
'created': ('django.db.models.fields.DateTimeField', [], {'default': 'None'}),
'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', [], {'default': 'None', 'related_name': "'job_host_summaries'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': "orm['main.Host']"}),
'host_name': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024'}),
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']"}),
'modified': ('django.db.models.fields.DateTimeField', [], {'default': 'None'}),
'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.joborigin': {
'Meta': {'object_name': 'JobOrigin'},
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'instance': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['main.Instance']"}),
'modified': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
'unified_job': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'job_origin'", 'unique': 'True', 'to': "orm['main.UnifiedJob']"})
},
'main.jobtemplate': {
'Meta': {'ordering': "('name',)", 'object_name': 'JobTemplate', '_ormbases': ['main.UnifiedJobTemplate']},
'ask_variables_on_launch': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'become_enabled': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'cloud_credential': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'jobtemplates_as_cloud_credential+'", 'on_delete': 'models.SET_NULL', 'default': 'None', 'to': "orm['main.Credential']", 'blank': 'True', 'null': 'True'}),
'credential': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'jobtemplates'", 'on_delete': 'models.SET_NULL', 'default': 'None', 'to': "orm['main.Credential']", 'blank': 'True', 'null': 'True'}),
'extra_vars': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
'force_handlers': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'forks': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0', 'blank': 'True'}),
'host_config_key': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
'inventory': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'jobtemplates'", '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', [], {'default': "'run'", 'max_length': '64'}),
'limit': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
'playbook': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
'project': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'jobtemplates'", 'on_delete': 'models.SET_NULL', 'default': 'None', 'to': "orm['main.Project']", 'blank': 'True', 'null': 'True'}),
'skip_tags': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
'start_at_task': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
'survey_enabled': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'survey_spec': ('jsonfield.fields.JSONField', [], {'default': '{}', 'blank': 'True'}),
u'unifiedjobtemplate_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['main.UnifiedJobTemplate']", 'unique': 'True', 'primary_key': 'True'}),
'verbosity': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0', 'blank': 'True'})
},
'main.organization': {
'Meta': {'ordering': "('name',)", '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', [], {'default': 'None'}),
'created_by': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', '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'}),
'modified': ('django.db.models.fields.DateTimeField', [], {'default': 'None'}),
'modified_by': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': '"{\'class\': \'organization\', \'app_label\': \'main\'}(class)s_modified+"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
'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': "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', [], {'default': 'None'}),
'created_by': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', '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']"}),
'modified': ('django.db.models.fields.DateTimeField', [], {'default': 'None'}),
'modified_by': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': '"{\'class\': \'permission\', \'app_label\': \'main\'}(class)s_modified+"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
'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': "orm['main.Project']"}),
'run_ad_hoc_commands': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'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']"})
},
'main.profile': {
'Meta': {'object_name': 'Profile'},
'created': ('django.db.models.fields.DateTimeField', [], {'default': 'None'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'ldap_dn': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024'}),
'modified': ('django.db.models.fields.DateTimeField', [], {'default': 'None'}),
'user': ('awx.main.fields.AutoOneToOneField', [], {'related_name': "'profile'", 'unique': 'True', 'to': u"orm['auth.User']"})
},
'main.project': {
'Meta': {'ordering': "('id',)", 'object_name': 'Project', '_ormbases': ['main.UnifiedJobTemplate']},
'credential': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'projects'", 'on_delete': 'models.SET_NULL', 'default': 'None', 'to': "orm['main.Credential']", 'blank': 'True', 'null': 'True'}),
'local_path': ('django.db.models.fields.CharField', [], {'max_length': '1024', 'blank': 'True'}),
'scm_branch': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '256', 'blank': 'True'}),
'scm_clean': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'scm_delete_on_next_update': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'scm_delete_on_update': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'scm_type': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '8', 'blank': 'True'}),
'scm_update_cache_timeout': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0', 'blank': 'True'}),
'scm_update_on_launch': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'scm_url': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
u'unifiedjobtemplate_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['main.UnifiedJobTemplate']", 'unique': 'True', 'primary_key': 'True'})
},
'main.projectupdate': {
'Meta': {'object_name': 'ProjectUpdate', '_ormbases': ['main.UnifiedJob']},
'credential': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'projectupdates'", 'on_delete': 'models.SET_NULL', 'default': 'None', 'to': "orm['main.Credential']", 'blank': 'True', 'null': 'True'}),
'local_path': ('django.db.models.fields.CharField', [], {'max_length': '1024', 'blank': 'True'}),
'project': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'project_updates'", 'to': "orm['main.Project']"}),
'scm_branch': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '256', 'blank': 'True'}),
'scm_clean': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'scm_delete_on_update': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'scm_type': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '8', 'blank': 'True'}),
'scm_url': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
u'unifiedjob_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['main.UnifiedJob']", 'unique': 'True', 'primary_key': 'True'})
},
'main.schedule': {
'Meta': {'ordering': "['-next_run']", 'object_name': 'Schedule'},
'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'created': ('django.db.models.fields.DateTimeField', [], {'default': 'None'}),
'created_by': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': '"{\'class\': \'schedule\', \'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'}),
'dtend': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True'}),
'dtstart': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True'}),
'enabled': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'extra_data': ('jsonfield.fields.JSONField', [], {'default': '{}', 'blank': 'True'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'modified': ('django.db.models.fields.DateTimeField', [], {'default': 'None'}),
'modified_by': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': '"{\'class\': \'schedule\', \'app_label\': \'main\'}(class)s_modified+"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '512'}),
'next_run': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True'}),
'rrule': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'unified_job_template': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'schedules'", 'to': "orm['main.UnifiedJobTemplate']"})
},
'main.systemjob': {
'Meta': {'ordering': "('id',)", 'object_name': 'SystemJob', '_ormbases': ['main.UnifiedJob']},
'extra_vars': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
'job_type': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '32', 'blank': 'True'}),
'system_job_template': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'jobs'", 'on_delete': 'models.SET_NULL', 'default': 'None', 'to': "orm['main.SystemJobTemplate']", 'blank': 'True', 'null': 'True'}),
u'unifiedjob_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['main.UnifiedJob']", 'unique': 'True', 'primary_key': 'True'})
},
'main.systemjobtemplate': {
'Meta': {'object_name': 'SystemJobTemplate', '_ormbases': ['main.UnifiedJobTemplate']},
'job_type': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '32', 'blank': 'True'}),
u'unifiedjobtemplate_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['main.UnifiedJobTemplate']", 'unique': 'True', 'primary_key': 'True'})
},
'main.team': {
'Meta': {'ordering': "('organization__name', 'name')", 'unique_together': "[('organization', 'name')]", 'object_name': 'Team'},
'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'created': ('django.db.models.fields.DateTimeField', [], {'default': 'None'}),
'created_by': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', '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'}),
'modified': ('django.db.models.fields.DateTimeField', [], {'default': 'None'}),
'modified_by': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': '"{\'class\': \'team\', \'app_label\': \'main\'}(class)s_modified+"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
'name': ('django.db.models.fields.CharField', [], {'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': "orm['main.Project']"}),
'users': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'teams'", 'blank': 'True', 'to': u"orm['auth.User']"})
},
'main.unifiedjob': {
'Meta': {'object_name': 'UnifiedJob'},
'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', [], {'default': 'None'}),
'created_by': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': '"{\'class\': \'unifiedjob\', \'app_label\': \'main\'}(class)s_created+"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
'dependent_jobs': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'dependent_jobs_rel_+'", 'to': "orm['main.UnifiedJob']"}),
'description': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
'elapsed': ('django.db.models.fields.DecimalField', [], {'max_digits': '12', 'decimal_places': '3'}),
'failed': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'finished': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'job_args': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
'job_cwd': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '1024', 'blank': 'True'}),
'job_env': ('jsonfield.fields.JSONField', [], {'default': '{}', 'blank': 'True'}),
'job_explanation': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
'launch_type': ('django.db.models.fields.CharField', [], {'default': "'manual'", 'max_length': '20'}),
'modified': ('django.db.models.fields.DateTimeField', [], {'default': 'None'}),
'modified_by': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': '"{\'class\': \'unifiedjob\', \'app_label\': \'main\'}(class)s_modified+"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '512'}),
'old_pk': ('django.db.models.fields.PositiveIntegerField', [], {'default': 'None', 'null': 'True'}),
'polymorphic_ctype': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'polymorphic_main.unifiedjob_set'", 'null': 'True', 'to': u"orm['contenttypes.ContentType']"}),
'result_stdout_file': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
'result_stdout_text': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
'result_traceback': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
'schedule': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'to': "orm['main.Schedule']", 'null': 'True', 'on_delete': 'models.SET_NULL'}),
'start_args': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
'started': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True'}),
'status': ('django.db.models.fields.CharField', [], {'default': "'new'", 'max_length': '20'}),
'unified_job_template': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': "'unifiedjob_unified_jobs'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': "orm['main.UnifiedJobTemplate']"})
},
'main.unifiedjobtemplate': {
'Meta': {'unique_together': "[('polymorphic_ctype', 'name')]", 'object_name': 'UnifiedJobTemplate'},
'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'created': ('django.db.models.fields.DateTimeField', [], {'default': 'None'}),
'created_by': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': '"{\'class\': \'unifiedjobtemplate\', \'app_label\': \'main\'}(class)s_created+"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
'current_job': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': "'unifiedjobtemplate_as_current_job+'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': "orm['main.UnifiedJob']"}),
'description': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
'has_schedules': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'last_job': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': "'unifiedjobtemplate_as_last_job+'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': "orm['main.UnifiedJob']"}),
'last_job_failed': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'last_job_run': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True'}),
'modified': ('django.db.models.fields.DateTimeField', [], {'default': 'None'}),
'modified_by': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': '"{\'class\': \'unifiedjobtemplate\', \'app_label\': \'main\'}(class)s_modified+"', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': u"orm['auth.User']"}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '512'}),
'next_job_run': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True'}),
'next_schedule': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': "'unifiedjobtemplate_as_next_schedule+'", 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': "orm['main.Schedule']"}),
'old_pk': ('django.db.models.fields.PositiveIntegerField', [], {'default': 'None', 'null': 'True'}),
'polymorphic_ctype': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'polymorphic_main.unifiedjobtemplate_set'", 'null': 'True', 'to': u"orm['contenttypes.ContentType']"}),
'status': ('django.db.models.fields.CharField', [], {'default': "'ok'", 'max_length': '32'})
}
}
complete_apps = ['main']
+8 -1
View File
@@ -46,7 +46,7 @@ class Credential(PasswordFieldsModel, CommonModelNameNotUnique):
#('runas', _('Runas')), #('runas', _('Runas')),
] ]
PASSWORD_FIELDS = ('password', 'ssh_key_data', 'ssh_key_unlock', PASSWORD_FIELDS = ('password', 'security_token', 'ssh_key_data', 'ssh_key_unlock',
'become_password', 'vault_password') 'become_password', 'vault_password')
class Meta: class Meta:
@@ -101,6 +101,13 @@ class Credential(PasswordFieldsModel, CommonModelNameNotUnique):
help_text=_('Password for this credential (or "ASK" to prompt the ' help_text=_('Password for this credential (or "ASK" to prompt the '
'user for machine credentials).'), 'user for machine credentials).'),
) )
security_token = models.CharField(
blank=True,
default='',
max_length=1024,
verbose_name=_('Security Token'),
help_text=_('Security Token for this credential'),
)
project = models.CharField( project = models.CharField(
blank=True, blank=True,
default='', default='',
+4 -2
View File
@@ -60,7 +60,7 @@ class Inventory(CommonModel):
total_hosts = models.PositiveIntegerField( total_hosts = models.PositiveIntegerField(
default=0, default=0,
editable=False, editable=False,
help_text=_('Total mumber of hosts in this inventory.'), help_text=_('Total number of hosts in this inventory.'),
) )
hosts_with_active_failures = models.PositiveIntegerField( hosts_with_active_failures = models.PositiveIntegerField(
default=0, default=0,
@@ -1281,7 +1281,9 @@ class CustomInventoryScript(CommonModelNameNotUnique):
'Organization', 'Organization',
related_name='custom_inventory_scripts', related_name='custom_inventory_scripts',
help_text=_('Organization owning this inventory script'), help_text=_('Organization owning this inventory script'),
on_delete=models.CASCADE, blank=False,
null=True,
on_delete=models.SET_NULL,
) )
def get_absolute_url(self): def get_absolute_url(self):
+37 -5
View File
@@ -240,7 +240,7 @@ class JobTemplate(UnifiedJobTemplate, JobOptions):
errors.append("'%s' value %s is too small (must be at least %s)" % errors.append("'%s' value %s is too small (must be at least %s)" %
(survey_element['variable'], data[survey_element['variable']], survey_element['min'])) (survey_element['variable'], data[survey_element['variable']], survey_element['min']))
if 'max' in survey_element and survey_element['max'] not in ["", None] and len(data[survey_element['variable']]) > survey_element['max']: if 'max' in survey_element and survey_element['max'] not in ["", None] and len(data[survey_element['variable']]) > survey_element['max']:
errors.append("'%s' value %s is too large (must be no more than%s)" % errors.append("'%s' value %s is too large (must be no more than %s)" %
(survey_element['variable'], data[survey_element['variable']], survey_element['max'])) (survey_element['variable'], data[survey_element['variable']], survey_element['max']))
elif survey_element['type'] == 'integer': elif survey_element['type'] == 'integer':
if survey_element['variable'] in data: if survey_element['variable'] in data:
@@ -250,7 +250,7 @@ class JobTemplate(UnifiedJobTemplate, JobOptions):
(survey_element['variable'], data[survey_element['variable']], survey_element['min'])) (survey_element['variable'], data[survey_element['variable']], survey_element['min']))
if 'max' in survey_element and survey_element['max'] not in ["", None] and survey_element['variable'] in data and \ if 'max' in survey_element and survey_element['max'] not in ["", None] and survey_element['variable'] in data and \
data[survey_element['variable']] > survey_element['max']: data[survey_element['variable']] > survey_element['max']:
errors.append("'%s' value %s is too large (must be no more than%s)" % errors.append("'%s' value %s is too large (must be no more than %s)" %
(survey_element['variable'], data[survey_element['variable']], survey_element['max'])) (survey_element['variable'], data[survey_element['variable']], survey_element['max']))
if type(data[survey_element['variable']]) != int: if type(data[survey_element['variable']]) != int:
errors.append("Value %s for %s expected to be an integer" % (data[survey_element['variable']], errors.append("Value %s for %s expected to be an integer" % (data[survey_element['variable']],
@@ -261,7 +261,7 @@ class JobTemplate(UnifiedJobTemplate, JobOptions):
errors.append("'%s' value %s is too small (must be at least %s)" % errors.append("'%s' value %s is too small (must be at least %s)" %
(survey_element['variable'], data[survey_element['variable']], survey_element['min'])) (survey_element['variable'], data[survey_element['variable']], survey_element['min']))
if 'max' in survey_element and survey_element['max'] not in ["", None] and data[survey_element['variable']] > survey_element['max']: if 'max' in survey_element and survey_element['max'] not in ["", None] and data[survey_element['variable']] > survey_element['max']:
errors.append("'%s' value %s is too large (must be no more than%s)" % errors.append("'%s' value %s is too large (must be no more than %s)" %
(survey_element['variable'], data[survey_element['variable']], survey_element['max'])) (survey_element['variable'], data[survey_element['variable']], survey_element['max']))
if type(data[survey_element['variable']]) not in (float, int): if type(data[survey_element['variable']]) not in (float, int):
errors.append("Value %s for %s expected to be a numeric type" % (data[survey_element['variable']], errors.append("Value %s for %s expected to be a numeric type" % (data[survey_element['variable']],
@@ -283,6 +283,39 @@ class JobTemplate(UnifiedJobTemplate, JobOptions):
survey_element['choices'])) survey_element['choices']))
return errors return errors
def _update_unified_job_kwargs(self, **kwargs):
if 'launch_type' in kwargs and kwargs['launch_type'] == 'relaunch':
return kwargs
# Job Template extra_vars
extra_vars = self.extra_vars_dict
# Overwrite with job template extra vars with survey default vars
if self.survey_enabled and 'spec' in self.survey_spec:
for survey_element in self.survey_spec.get("spec", []):
if survey_element['default']:
extra_vars[survey_element['variable']] = survey_element['default']
# transform to dict
if 'extra_vars' in kwargs:
kwargs_extra_vars = kwargs['extra_vars']
if not isinstance(kwargs_extra_vars, dict):
try:
kwargs_extra_vars = json.loads(kwargs_extra_vars)
except Exception:
try:
yaml.safe_load(kwargs_extra_vars)
except:
kwargs_extra_vars = {}
else:
kwargs_extra_vars = {}
# Overwrite job template extra vars with explicit job extra vars
# and add on job extra vars
extra_vars.update(kwargs_extra_vars)
kwargs['extra_vars'] = json.dumps(extra_vars)
return kwargs
@property @property
def cache_timeout_blocked(self): def cache_timeout_blocked(self):
if Job.objects.filter(job_template=self, status__in=['pending', 'waiting', 'running']).count() > getattr(settings, 'SCHEDULE_MAX_JOBS', 10): if Job.objects.filter(job_template=self, status__in=['pending', 'waiting', 'running']).count() > getattr(settings, 'SCHEDULE_MAX_JOBS', 10):
@@ -826,7 +859,7 @@ class JobEvent(CreatedModifiedModel):
try: try:
failures_dict = self.event_data.get('failures', {}) failures_dict = self.event_data.get('failures', {})
dark_dict = self.event_data.get('dark', {}) dark_dict = self.event_data.get('dark', {})
self.failed = bool(sum(failures_dict.values()) + self.failed = bool(sum(failures_dict.values()) +
sum(dark_dict.values())) sum(dark_dict.values()))
if 'failed' not in update_fields: if 'failed' not in update_fields:
update_fields.append('failed') update_fields.append('failed')
@@ -1052,4 +1085,3 @@ class SystemJob(UnifiedJob, SystemJobOptions):
@property @property
def task_impact(self): def task_impact(self):
return 150 return 150
+91 -14
View File
@@ -12,7 +12,8 @@ from django.conf import settings
from django.db import models from django.db import models
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
from django.contrib.auth.models import User from django.contrib.auth.models import User
from django.utils.timezone import now from django.utils.timezone import now as tz_now
from django.utils.translation import ugettext_lazy as _
# AWX # AWX
from awx.main.fields import AutoOneToOneField from awx.main.fields import AutoOneToOneField
@@ -52,6 +53,12 @@ class Organization(CommonModel):
def __unicode__(self): def __unicode__(self):
return self.name return self.name
def mark_inactive(self, save=True):
for script in self.custom_inventory_scripts.all():
script.organization = None
script.save()
super(Organization, self).mark_inactive(save=save)
class Team(CommonModelNameNotUnique): class Team(CommonModelNameNotUnique):
''' '''
@@ -128,7 +135,8 @@ class Permission(CommonModelNameNotUnique):
# the project parameter is not used when dealing with READ, WRITE, or ADMIN permissions. # the project parameter is not used when dealing with READ, WRITE, or ADMIN permissions.
permission_type = models.CharField(max_length=64, choices=PERMISSION_TYPE_CHOICES) permission_type = models.CharField(max_length=64, choices=PERMISSION_TYPE_CHOICES)
run_ad_hoc_commands = models.BooleanField(default=False) run_ad_hoc_commands = models.BooleanField(default=False,
help_text=_('Execute Commands on the Inventory'))
def __unicode__(self): def __unicode__(self):
return unicode("Permission(name=%s,ON(user=%s,team=%s),FOR(project=%s,inventory=%s,type=%s%s))" % ( return unicode("Permission(name=%s,ON(user=%s,team=%s),FOR(project=%s,inventory=%s,type=%s%s))" % (
@@ -164,13 +172,29 @@ class Profile(CreatedModifiedModel):
default='', default='',
) )
"""
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 AuthToken(BaseModel):
''' '''
Custom authentication tokens per user with expiration and request-specific Custom authentication tokens per user with expiration and request-specific
data. data.
''' '''
REASON_CHOICES = [
('', _('Token not invalidated')),
('timeout_reached', _('Token is expired')),
('limit_reached', _('Maximum per-user sessions reached')),
# 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: class Meta:
app_label = 'main' app_label = 'main'
@@ -179,8 +203,21 @@ class AuthToken(BaseModel):
on_delete=models.CASCADE) on_delete=models.CASCADE)
created = models.DateTimeField(auto_now_add=True) created = models.DateTimeField(auto_now_add=True)
modified = models.DateTimeField(auto_now=True) modified = models.DateTimeField(auto_now=True)
expires = models.DateTimeField(default=now) expires = models.DateTimeField(default=tz_now)
request_hash = models.CharField(max_length=40, blank=True, default='') request_hash = 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.')
)
@staticmethod
def reason_long(reason):
for x in AuthToken.REASON_CHOICES:
if x[0] == reason:
return unicode(x[1])
return None
@classmethod @classmethod
def get_request_hash(cls, request): def get_request_hash(cls, request):
@@ -201,25 +238,65 @@ class AuthToken(BaseModel):
self.key = self.generate_key() self.key = self.generate_key()
return super(AuthToken, self).save(*args, **kwargs) return super(AuthToken, self).save(*args, **kwargs)
def refresh(self, save=True): def refresh(self, now=None, save=True):
if not self.pk or not self.expired: if not now:
self.expires = now() + datetime.timedelta(seconds=settings.AUTH_TOKEN_EXPIRATION) 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: if save:
self.save() self.save()
def invalidate(self, save=True): def invalidate(self, reason='timeout_reached', save=True):
if not self.expired: if not AuthToken.reason_long(reason):
self.expires = now() - datetime.timedelta(seconds=1) raise ValueError('Invalid reason specified')
if save: self.reason = reason
self.save() if save:
self.save()
return reason
@staticmethod
def get_tokens_over_limit(user, now=None):
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
def generate_key(self): def generate_key(self):
unique = uuid.uuid4() unique = uuid.uuid4()
return hmac.new(unique.bytes, digestmod=hashlib.sha1).hexdigest() 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 @property
def expired(self): def invalidated(self):
return bool(self.expires < now()) 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')[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): def __unicode__(self):
return self.key return self.key
@@ -231,7 +308,7 @@ def user_mark_inactive(user, save=True):
if user.is_active: if user.is_active:
# Set timestamp to datetime.isoformat() but without the time zone # Set timestamp to datetime.isoformat() but without the time zone
# offset to stay withint the 30 character username limit. # offset to stay withint the 30 character username limit.
dtnow = now() dtnow = tz_now()
deleted_ts = dtnow.strftime('%Y-%m-%dT%H:%M:%S.%f') deleted_ts = dtnow.strftime('%Y-%m-%dT%H:%M:%S.%f')
user.username = '_d_%s' % deleted_ts user.username = '_d_%s' % deleted_ts
user.is_active = False user.is_active = False
+1
View File
@@ -343,6 +343,7 @@ class UnifiedJob(PolymorphicModel, PasswordFieldsModel, CommonModelNameNotUnique
LAUNCH_TYPE_CHOICES = [ LAUNCH_TYPE_CHOICES = [
('manual', _('Manual')), # Job was started manually by a user. ('manual', _('Manual')), # Job was started manually by a user.
('relaunch', _('Relaunch')), # Job was started via relaunch.
('callback', _('Callback')), # Job was started via host callback. ('callback', _('Callback')), # Job was started via host callback.
('scheduled', _('Scheduled')), # Job was started from a schedule. ('scheduled', _('Scheduled')), # Job was started from a schedule.
('dependency', _('Dependency')), # Job was started as a dependency of another job. ('dependency', _('Dependency')), # Job was started as a dependency of another job.
+48 -9
View File
@@ -162,6 +162,7 @@ def handle_work_error(self, task_id, subtasks=None):
print('Executing error task id %s, subtasks: %s' % print('Executing error task id %s, subtasks: %s' %
(str(self.request.id), str(subtasks))) (str(self.request.id), str(subtasks)))
first_task = None first_task = None
first_task_id = None
first_task_type = '' first_task_type = ''
first_task_name = '' first_task_name = ''
if subtasks is not None: if subtasks is not None:
@@ -169,10 +170,10 @@ def handle_work_error(self, task_id, subtasks=None):
instance_name = '' instance_name = ''
if each_task['type'] == 'project_update': if each_task['type'] == 'project_update':
instance = ProjectUpdate.objects.get(id=each_task['id']) instance = ProjectUpdate.objects.get(id=each_task['id'])
instance_name = instance.project.name instance_name = instance.name
elif each_task['type'] == 'inventory_update': elif each_task['type'] == 'inventory_update':
instance = InventoryUpdate.objects.get(id=each_task['id']) instance = InventoryUpdate.objects.get(id=each_task['id'])
instance_name = instance.inventory_source.inventory.name instance_name = instance.name
elif each_task['type'] == 'job': elif each_task['type'] == 'job':
instance = Job.objects.get(id=each_task['id']) instance = Job.objects.get(id=each_task['id'])
instance_name = instance.job_template.name instance_name = instance.job_template.name
@@ -184,13 +185,14 @@ def handle_work_error(self, task_id, subtasks=None):
break break
if first_task is None: if first_task is None:
first_task = instance first_task = instance
first_task_id = instance.id
first_task_type = each_task['type'] first_task_type = each_task['type']
first_task_name = instance_name first_task_name = instance_name
if instance.celery_task_id != task_id: if instance.celery_task_id != task_id:
instance.status = 'failed' instance.status = 'failed'
instance.failed = True instance.failed = True
instance.job_explanation = "Previous Task Failed: %s for %s with celery task id: %s" % \ instance.job_explanation = 'Previous Task Failed: {"job_type": "%s", "job_name": "%s", "job_id": "%s"}' % \
(first_task_type, first_task_name, task_id) (first_task_type, first_task_name, first_task_id)
instance.save() instance.save()
instance.socketio_emit_status("failed") instance.socketio_emit_status("failed")
@@ -345,6 +347,8 @@ class BaseTask(Task):
if local_site_packages not in python_paths: if local_site_packages not in python_paths:
python_paths.insert(0, local_site_packages) python_paths.insert(0, local_site_packages)
env['PYTHONPATH'] = os.pathsep.join(python_paths) env['PYTHONPATH'] = os.pathsep.join(python_paths)
if self.should_use_proot:
env['PROOT_TMP_DIR'] = settings.AWX_PROOT_BASE_PATH
return env return env
def build_safe_env(self, instance, **kwargs): def build_safe_env(self, instance, **kwargs):
@@ -423,6 +427,15 @@ class BaseTask(Task):
''' '''
logfile = stdout_handle logfile = stdout_handle
logfile_pos = logfile.tell() logfile_pos = logfile.tell()
if hasattr(instance, "extra_vars_dict") and "PEXPECT_SLEEP" in instance.extra_vars_dict:
pexpect_sleep = int(instance.extra_vars_dict['PEXPECT_SLEEP'])
elif 'PEXPECT_SLEEP' in os.environ:
pexpect_sleep = int(os.environ['PEXPECT_SLEEP'])
else:
pexpect_sleep = None
if pexpect_sleep is not None:
logger.info("Suspending Job Execution for QA Work")
time.sleep(pexpect_sleep)
child = pexpect.spawnu(args[0], args[1:], cwd=cwd, env=env) child = pexpect.spawnu(args[0], args[1:], cwd=cwd, env=env)
child.logfile_read = logfile child.logfile_read = logfile
canceled = False canceled = False
@@ -601,6 +614,21 @@ class RunJob(BaseTask):
if credential.ssh_key_data not in (None, ''): if credential.ssh_key_data not in (None, ''):
private_data[cred_name] = decrypt_field(credential, 'ssh_key_data') or '' private_data[cred_name] = decrypt_field(credential, 'ssh_key_data') or ''
if job.cloud_credential and job.cloud_credential.kind == 'openstack':
credential = job.cloud_credential
openstack_auth = dict(auth_url=credential.host,
username=credential.username,
password=decrypt_field(credential, "password"),
project_name=credential.project)
openstack_data = {
'clouds': {
'devstack': {
'auth': openstack_auth,
},
},
}
private_data['cloud_credential'] = yaml.safe_dump(openstack_data, default_flow_style=False, allow_unicode=True)
return private_data return private_data
def build_passwords(self, job, **kwargs): def build_passwords(self, job, **kwargs):
@@ -625,12 +653,17 @@ class RunJob(BaseTask):
Build environment dictionary for ansible-playbook. Build environment dictionary for ansible-playbook.
''' '''
plugin_dir = self.get_path_to('..', 'plugins', 'callback') plugin_dir = self.get_path_to('..', 'plugins', 'callback')
plugin_dirs = [plugin_dir]
if hasattr(settings, 'AWX_ANSIBLE_CALLBACK_PLUGINS') and \
settings.AWX_ANSIBLE_CALLBACK_PLUGINS:
plugin_dirs.append(settings.AWX_ANSIBLE_CALLBACK_PLUGINS)
plugin_path = ':'.join(plugin_dirs)
env = super(RunJob, self).build_env(job, **kwargs) env = super(RunJob, self).build_env(job, **kwargs)
# Set environment variables needed for inventory and job event # Set environment variables needed for inventory and job event
# callbacks to work. # callbacks to work.
env['JOB_ID'] = str(job.pk) env['JOB_ID'] = str(job.pk)
env['INVENTORY_ID'] = str(job.inventory.pk) env['INVENTORY_ID'] = str(job.inventory.pk)
env['ANSIBLE_CALLBACK_PLUGINS'] = plugin_dir env['ANSIBLE_CALLBACK_PLUGINS'] = plugin_path
env['REST_API_URL'] = settings.INTERNAL_API_URL env['REST_API_URL'] = settings.INTERNAL_API_URL
env['REST_API_TOKEN'] = job.task_auth_token or '' env['REST_API_TOKEN'] = job.task_auth_token or ''
env['CALLBACK_CONSUMER_PORT'] = str(settings.CALLBACK_CONSUMER_PORT) env['CALLBACK_CONSUMER_PORT'] = str(settings.CALLBACK_CONSUMER_PORT)
@@ -654,6 +687,8 @@ class RunJob(BaseTask):
if cloud_cred and cloud_cred.kind == 'aws': if cloud_cred and cloud_cred.kind == 'aws':
env['AWS_ACCESS_KEY'] = cloud_cred.username env['AWS_ACCESS_KEY'] = cloud_cred.username
env['AWS_SECRET_KEY'] = decrypt_field(cloud_cred, 'password') env['AWS_SECRET_KEY'] = decrypt_field(cloud_cred, 'password')
if len(cloud_cred.security_token) > 0:
env['AWS_SECURITY_TOKEN'] = decrypt_field(cloud_cred, 'security_token')
# FIXME: Add EC2_URL, maybe EC2_REGION! # FIXME: Add EC2_URL, maybe EC2_REGION!
elif cloud_cred and cloud_cred.kind == 'rax': elif cloud_cred and cloud_cred.kind == 'rax':
env['RAX_USERNAME'] = cloud_cred.username env['RAX_USERNAME'] = cloud_cred.username
@@ -669,6 +704,8 @@ class RunJob(BaseTask):
env['VMWARE_USER'] = cloud_cred.username env['VMWARE_USER'] = cloud_cred.username
env['VMWARE_PASSWORD'] = decrypt_field(cloud_cred, 'password') env['VMWARE_PASSWORD'] = decrypt_field(cloud_cred, 'password')
env['VMWARE_HOST'] = cloud_cred.host env['VMWARE_HOST'] = cloud_cred.host
elif cloud_cred and cloud_cred.kind == 'openstack':
env['OS_CLIENT_CONFIG_FILE'] = kwargs.get('private_data_files', {}).get('cloud_credential', '')
# Set environment variables related to scan jobs # Set environment variables related to scan jobs
if job.job_type == PERM_INVENTORY_SCAN: if job.job_type == PERM_INVENTORY_SCAN:
@@ -1116,7 +1153,7 @@ class RunInventoryUpdate(BaseTask):
if credential: if credential:
for subkey in ('username', 'host', 'project'): for subkey in ('username', 'host', 'project'):
passwords['source_%s' % subkey] = getattr(credential, subkey) passwords['source_%s' % subkey] = getattr(credential, subkey)
for passkey in ('password', 'ssh_key_data'): for passkey in ('password', 'ssh_key_data', 'security_token'):
k = 'source_%s' % passkey k = 'source_%s' % passkey
passwords[k] = decrypt_field(credential, passkey) passwords[k] = decrypt_field(credential, passkey)
return passwords return passwords
@@ -1149,6 +1186,8 @@ class RunInventoryUpdate(BaseTask):
if passwords.get('source_username', '') and passwords.get('source_password', ''): if passwords.get('source_username', '') and passwords.get('source_password', ''):
env['AWS_ACCESS_KEY_ID'] = passwords['source_username'] env['AWS_ACCESS_KEY_ID'] = passwords['source_username']
env['AWS_SECRET_ACCESS_KEY'] = passwords['source_password'] env['AWS_SECRET_ACCESS_KEY'] = passwords['source_password']
if len(passwords['source_security_token']) > 0:
env['AWS_SECURITY_TOKEN'] = passwords['source_security_token']
env['EC2_INI_PATH'] = cloud_credential env['EC2_INI_PATH'] = cloud_credential
elif inventory_update.source == 'rax': elif inventory_update.source == 'rax':
env['RAX_CREDS_FILE'] = cloud_credential env['RAX_CREDS_FILE'] = cloud_credential
@@ -1169,7 +1208,7 @@ class RunInventoryUpdate(BaseTask):
env['GCE_PROJECT'] = passwords.get('source_project', '') env['GCE_PROJECT'] = passwords.get('source_project', '')
env['GCE_PEM_FILE_PATH'] = cloud_credential env['GCE_PEM_FILE_PATH'] = cloud_credential
elif inventory_update.source == 'openstack': elif inventory_update.source == 'openstack':
env['OPENSTACK_CONFIG_FILE'] = cloud_credential env['OS_CLIENT_CONFIG_FILE'] = cloud_credential
elif inventory_update.source == 'file': elif inventory_update.source == 'file':
# FIXME: Parse source_env to dict, update env. # FIXME: Parse source_env to dict, update env.
pass pass
@@ -1188,7 +1227,7 @@ class RunInventoryUpdate(BaseTask):
inventory = inventory_source.group.inventory inventory = inventory_source.group.inventory
# Piece together the initial command to run via. the shell. # Piece together the initial command to run via. the shell.
args = ['awx-manage', 'inventory_import'] args = ['tower-manage', 'inventory_import']
args.extend(['--inventory-id', str(inventory.pk)]) args.extend(['--inventory-id', str(inventory.pk)])
# Add appropriate arguments for overwrite if the inventory_update # Add appropriate arguments for overwrite if the inventory_update
@@ -1450,7 +1489,7 @@ class RunSystemJob(BaseTask):
model = SystemJob model = SystemJob
def build_args(self, system_job, **kwargs): def build_args(self, system_job, **kwargs):
args = ['awx-manage', system_job.job_type] args = ['tower-manage', system_job.job_type]
try: try:
json_vars = json.loads(system_job.extra_vars) json_vars = json.loads(system_job.extra_vars)
if 'days' in json_vars and system_job.job_type != 'cleanup_facts': if 'days' in json_vars and system_job.job_type != 'cleanup_facts':
+1 -1
View File
@@ -1,7 +1,7 @@
# Copyright (c) 2015 Ansible, Inc. # Copyright (c) 2015 Ansible, Inc.
# All Rights Reserved. # All Rights Reserved.
from awx.main.tests.organizations import OrganizationsTest # noqa from awx.main.tests.organizations import * # noqa
from awx.main.tests.users import * # noqa from awx.main.tests.users import * # noqa
from awx.main.tests.inventory import * # noqa from awx.main.tests.inventory import * # noqa
from awx.main.tests.projects import ProjectsTest, ProjectUpdatesTest # noqa from awx.main.tests.projects import ProjectsTest, ProjectUpdatesTest # noqa
+25 -6
View File
@@ -28,11 +28,11 @@ from django.test.utils import override_settings
# AWX # AWX
from awx.main.models import * # noqa from awx.main.models import * # noqa
from awx.main.backend import LDAPSettings
from awx.main.management.commands.run_callback_receiver import CallbackReceiver from awx.main.management.commands.run_callback_receiver import CallbackReceiver
from awx.main.management.commands.run_task_system import run_taskmanager from awx.main.management.commands.run_task_system import run_taskmanager
from awx.main.utils import get_ansible_version from awx.main.utils import get_ansible_version
from awx.main.task_engine import TaskEngager as LicenseWriter from awx.main.task_engine import TaskEngager as LicenseWriter
from awx.sso.backends import LDAPSettings
TEST_PLAYBOOK = '''- hosts: mygroup TEST_PLAYBOOK = '''- hosts: mygroup
gather_facts: false gather_facts: false
@@ -56,8 +56,12 @@ class QueueTestMixin(object):
def start_redis(self): def start_redis(self):
if not getattr(self, 'redis_process', None): if not getattr(self, 'redis_process', None):
self.redis_process = Popen('redis-server --port 16379 > /dev/null', # Centos 6.5 redis is runnable by non-root user but is not in a normal users path by default
shell=True, executable='/bin/bash') env = dict(os.environ)
env['PATH'] = '%s:/usr/sbin/' % env['PATH']
self.redis_process = Popen('echo "port 16379" | redis-server - > /dev/null',
shell=True, executable='/bin/bash',
env=env)
def stop_redis(self): def stop_redis(self):
if getattr(self, 'redis_process', None): if getattr(self, 'redis_process', None):
@@ -188,6 +192,20 @@ class BaseTestMixin(QueueTestMixin, MockCommonlySlowTestMixin):
self._temp_paths.append(license_path) self._temp_paths.append(license_path)
os.environ['AWX_LICENSE_FILE'] = license_path os.environ['AWX_LICENSE_FILE'] = license_path
def create_basic_license_file(self, instance_count=100, license_date=int(time.time() + 3600)):
writer = LicenseWriter(
company_name='AWX',
contact_name='AWX Admin',
contact_email='awx@example.com',
license_date=license_date,
instance_count=instance_count,
license_type='basic')
handle, license_path = tempfile.mkstemp(suffix='.json')
os.close(handle)
writer.write_file(license_path)
self._temp_paths.append(license_path)
os.environ['AWX_LICENSE_FILE'] = license_path
def create_expired_license_file(self, instance_count=1000, grace_period=False): def create_expired_license_file(self, instance_count=1000, grace_period=False):
license_date = time.time() - 1 license_date = time.time() - 1
if not grace_period: if not grace_period:
@@ -456,8 +474,8 @@ class BaseTestMixin(QueueTestMixin, MockCommonlySlowTestMixin):
assert response.status_code == expect, "expected status %s, got %s for url=%s as auth=%s: %s" % (expect, response.status_code, url, auth, response.content) assert response.status_code == expect, "expected status %s, got %s for url=%s as auth=%s: %s" % (expect, response.status_code, url, auth, response.content)
if method_name == 'head': if method_name == 'head':
self.assertFalse(response.content) self.assertFalse(response.content)
#if return_response_object: if return_response_object:
# return response return response
if response.status_code not in [204, 405] and method_name != 'head' and response.content: if response.status_code not in [204, 405] and method_name != 'head' and response.content:
# no JSON responses in these at least for now, 409 should probably return some (FIXME) # no JSON responses in these at least for now, 409 should probably return some (FIXME)
if response['Content-Type'].startswith('application/json'): if response['Content-Type'].startswith('application/json'):
@@ -700,7 +718,8 @@ class BaseLiveServerTest(BaseTestMixin, django.test.LiveServerTestCase):
@override_settings(CELERY_ALWAYS_EAGER=True, @override_settings(CELERY_ALWAYS_EAGER=True,
CELERY_EAGER_PROPAGATES_EXCEPTIONS=True, CELERY_EAGER_PROPAGATES_EXCEPTIONS=True,
ANSIBLE_TRANSPORT='local') ANSIBLE_TRANSPORT='local',
DEBUG=True)
class BaseJobExecutionTest(QueueStartStopTestMixin, BaseLiveServerTest): class BaseJobExecutionTest(QueueStartStopTestMixin, BaseLiveServerTest):
''' '''
Base class for celery task tests. Base class for celery task tests.
+3
View File
@@ -7,3 +7,6 @@ from .run_fact_cache_receiver import * # noqa
from .commands_monolithic import * # noqa from .commands_monolithic import * # noqa
from .cleanup_facts import * # noqa from .cleanup_facts import * # noqa
from .age_deleted import * # noqa from .age_deleted import * # noqa
from .remove_instance import * # noqa
from .run_socketio_service import * # noqa
@@ -11,7 +11,7 @@ import sys
import tempfile import tempfile
import time import time
import urlparse import urlparse
import unittest import unittest2 as unittest
# Django # Django
import django import django
@@ -26,9 +26,6 @@ from django.test.utils import override_settings
from awx.main.models import * # noqa from awx.main.models import * # noqa
from awx.main.tests.base import BaseTest, BaseLiveServerTest from awx.main.tests.base import BaseTest, BaseLiveServerTest
if not hasattr(unittest, 'skipIf'):
import unittest2 as unittest
__all__ = ['CreateDefaultOrgTest', 'DumpDataTest', 'CleanupDeletedTest', __all__ = ['CreateDefaultOrgTest', 'DumpDataTest', 'CleanupDeletedTest',
'CleanupJobsTest', 'CleanupActivityStreamTest', 'CleanupJobsTest', 'CleanupActivityStreamTest',
'InventoryImportTest'] 'InventoryImportTest']
@@ -831,7 +828,7 @@ class InventoryImportTest(BaseCommandMixin, BaseLiveServerTest):
host_names = set(new_inv.hosts.filter(active=True).values_list('name', flat=True)) host_names = set(new_inv.hosts.filter(active=True).values_list('name', flat=True))
self.assertEqual(expected_host_names, host_names) self.assertEqual(expected_host_names, host_names)
expected_inv_vars = {'vara': 'A', 'varc': 'C'} expected_inv_vars = {'vara': 'A', 'varc': 'C'}
if overwrite or overwrite_vars: if overwrite_vars:
expected_inv_vars.pop('varc') expected_inv_vars.pop('varc')
self.assertEqual(new_inv.variables_dict, expected_inv_vars) self.assertEqual(new_inv.variables_dict, expected_inv_vars)
for host in new_inv.hosts.filter(active=True): for host in new_inv.hosts.filter(active=True):
@@ -849,7 +846,7 @@ class InventoryImportTest(BaseCommandMixin, BaseLiveServerTest):
for group in new_inv.groups.filter(active=True): for group in new_inv.groups.filter(active=True):
if group.name == 'servers': if group.name == 'servers':
expected_vars = {'varb': 'B', 'vard': 'D'} expected_vars = {'varb': 'B', 'vard': 'D'}
if overwrite or overwrite_vars: if overwrite_vars:
expected_vars.pop('vard') expected_vars.pop('vard')
self.assertEqual(group.variables_dict, expected_vars) self.assertEqual(group.variables_dict, expected_vars)
children = set(group.children.filter(active=True).values_list('name', flat=True)) children = set(group.children.filter(active=True).values_list('name', flat=True))
@@ -0,0 +1,39 @@
# Copyright (c) 2015 Ansible, Inc.
# All Rights Reserved
# Python
import uuid
# AWX
from awx.main.tests.base import BaseTest
from awx.main.tests.commands.base import BaseCommandMixin
from awx.main.models import * # noqa
__all__ = ['RemoveInstanceCommandFunctionalTest']
class RemoveInstanceCommandFunctionalTest(BaseCommandMixin, BaseTest):
uuids = []
instances = []
def setup_instances(self):
self.uuids = [uuid.uuid4().hex for x in range(0, 3)]
self.instances.append(Instance(uuid=settings.SYSTEM_UUID, primary=True, hostname='127.0.0.1'))
self.instances.append(Instance(uuid=self.uuids[0], primary=False, hostname='127.0.0.2'))
self.instances.append(Instance(uuid=self.uuids[1], primary=False, hostname='127.0.0.3'))
self.instances.append(Instance(uuid=self.uuids[2], primary=False, hostname='127.0.0.4'))
for x in self.instances:
x.save()
def setUp(self):
super(RemoveInstanceCommandFunctionalTest, self).setUp()
self.create_test_license_file()
self.setup_instances()
self.setup_users()
def test_default(self):
self.assertEqual(Instance.objects.filter(hostname="127.0.0.2").count(), 1)
result, stdout, stderr = self.run_command('remove_instance', hostname='127.0.0.2')
self.assertIsNone(result)
self.assertEqual(stdout, 'Successfully removed instance (uuid="%s",hostname="127.0.0.2",role="secondary").\n' % (self.uuids[0]))
self.assertEqual(Instance.objects.filter(hostname="127.0.0.2").count(), 0)
@@ -5,7 +5,7 @@
import time import time
from datetime import datetime from datetime import datetime
import mock import mock
import unittest import unittest2 as unittest
from copy import deepcopy from copy import deepcopy
from mock import MagicMock from mock import MagicMock
@@ -0,0 +1,116 @@
# Copyright (c) 2015 Ansible, Inc.
# All Rights Reserved
# Python
from mock import MagicMock, Mock
# Django
from django.test import SimpleTestCase
# AWX
from awx.fact.models.fact import * # noqa
from awx.main.management.commands.run_socketio_service import SocketSessionManager, SocketSession, SocketController
__all__ = ['SocketSessionManagerUnitTest', 'SocketControllerUnitTest',]
class WeakRefable():
pass
class SocketSessionManagerUnitTest(SimpleTestCase):
def setUp(self):
self.session_manager = SocketSessionManager()
super(SocketSessionManagerUnitTest, self).setUp()
def create_sessions(self, count, token_key=None):
self.sessions = []
self.count = count
for i in range(0, count):
self.sessions.append(SocketSession(i, token_key or i, WeakRefable()))
self.session_manager.add_session(self.sessions[i])
def test_multiple_session_diff_token(self):
self.create_sessions(10)
for s in self.sessions:
self.assertIn(s.token_key, self.session_manager.socket_session_token_key_map)
self.assertEqual(s, self.session_manager.socket_session_token_key_map[s.token_key][s.session_id])
def test_multiple_session_same_token(self):
self.create_sessions(10, token_key='foo')
sessions_dict = self.session_manager.lookup("foo")
self.assertEqual(len(sessions_dict), 10)
for s in self.sessions:
self.assertIn(s.session_id, sessions_dict)
self.assertEqual(s, sessions_dict[s.session_id])
def test_prune_sessions_max(self):
self.create_sessions(self.session_manager.SESSIONS_MAX + 10)
self.assertEqual(len(self.session_manager.socket_sessions), self.session_manager.SESSIONS_MAX)
class SocketControllerUnitTest(SimpleTestCase):
def setUp(self):
self.socket_controller = SocketController(SocketSessionManager())
server = Mock()
self.socket_controller.set_server(server)
super(SocketControllerUnitTest, self).setUp()
def create_clients(self, count, token_key=None):
self.sessions = []
self.sockets =[]
self.count = count
self.sockets_dict = {}
for i in range(0, count):
if isinstance(token_key, list):
token_key_actual = token_key[i]
else:
token_key_actual = token_key or i
socket = MagicMock(session=dict())
socket_session = SocketSession(i, token_key_actual, socket)
self.sockets.append(socket)
self.sessions.append(socket_session)
self.sockets_dict[i] = socket
self.socket_controller.add_session(socket_session)
socket.session['socket_session'] = socket_session
socket.send_packet = Mock()
self.socket_controller.server.sockets = self.sockets_dict
def test_broadcast_packet(self):
self.create_clients(10)
packet = {
"hello": "world"
}
self.socket_controller.broadcast_packet(packet)
for s in self.sockets:
s.send_packet.assert_called_with(packet)
def test_send_packet(self):
self.create_clients(5, token_key=[0, 1, 2, 3, 4])
packet = {
"hello": "world"
}
self.socket_controller.send_packet(packet, 2)
self.assertEqual(0, len(self.sockets[0].send_packet.mock_calls))
self.assertEqual(0, len(self.sockets[1].send_packet.mock_calls))
self.sockets[2].send_packet.assert_called_once_with(packet)
self.assertEqual(0, len(self.sockets[3].send_packet.mock_calls))
self.assertEqual(0, len(self.sockets[4].send_packet.mock_calls))
def test_send_packet_multiple_sessions_one_token(self):
self.create_clients(5, token_key=[0, 1, 1, 1, 2])
packet = {
"hello": "world"
}
self.socket_controller.send_packet(packet, 1)
self.assertEqual(0, len(self.sockets[0].send_packet.mock_calls))
self.sockets[1].send_packet.assert_called_once_with(packet)
self.sockets[2].send_packet.assert_called_once_with(packet)
self.sockets[3].send_packet.assert_called_once_with(packet)
self.assertEqual(0, len(self.sockets[4].send_packet.mock_calls))
+1 -1
View File
@@ -2,7 +2,7 @@
# All Rights Reserved # All Rights Reserved
# Python # Python
import unittest import unittest2 as unittest
# Django # Django
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
+48
View File
@@ -1665,6 +1665,54 @@ class InventoryUpdatesTest(BaseTransactionTest):
inventory_source.save() inventory_source.save()
self.check_inventory_source(inventory_source, initial=False) self.check_inventory_source(inventory_source, initial=False)
def test_update_from_ec2_sts_iam(self):
source_username = getattr(settings, 'TEST_AWS_ACCESS_KEY_ID', '')
source_password = getattr(settings, 'TEST_AWS_SECRET_ACCESS_KEY', '')
source_regions = getattr(settings, 'TEST_AWS_REGIONS', 'all')
source_token = getattr(settings, 'TEST_AWS_SECURITY_TOKEN', '')
if not all([source_username, source_password, source_token]):
self.skipTest('no test ec2 sts credentials defined!')
self.create_test_license_file()
credential = Credential.objects.create(kind='aws',
user=self.super_django_user,
username=source_username,
password=source_password,
security_token=source_token)
# Set parent group name to one that might be created by the sync.
group = self.group
group.name = 'ec2'
group.save()
self.group = group
cache_path = tempfile.mkdtemp(prefix='awx_ec2_')
self._temp_paths.append(cache_path)
inventory_source = self.update_inventory_source(self.group,
source='ec2', credential=credential, source_regions=source_regions,
source_vars='---\n\nnested_groups: false\ncache_path: %s\n' % cache_path)
self.check_inventory_source(inventory_source)
def test_update_from_ec2_sts_iam_bad_token(self):
source_username = getattr(settings, 'TEST_AWS_ACCESS_KEY_ID', '')
source_password = getattr(settings, 'TEST_AWS_SECRET_ACCESS_KEY', '')
source_regions = getattr(settings, 'TEST_AWS_REGIONS', 'all')
self.create_test_license_file()
credential = Credential.objects.create(kind='aws',
user=self.super_django_user,
username=source_username,
password=source_password,
security_token="BADTOKEN")
# Set parent group name to one that might be created by the sync.
group = self.group
group.name = 'ec2'
group.save()
self.group = group
cache_path = tempfile.mkdtemp(prefix='awx_ec2_')
self._temp_paths.append(cache_path)
inventory_source = self.update_inventory_source(self.group,
source='ec2', credential=credential, source_regions=source_regions,
source_vars='---\n\nnested_groups: false\ncache_path: %s\n' % cache_path)
self.check_inventory_update(inventory_source, should_fail=True)
def test_update_from_ec2_without_credential(self): def test_update_from_ec2_without_credential(self):
self.create_test_license_file() self.create_test_license_file()
group = self.group group = self.group
+1
View File
@@ -5,6 +5,7 @@ from __future__ import absolute_import
from .jobs_monolithic import * # noqa from .jobs_monolithic import * # noqa
from .job_launch import * # noqa from .job_launch import * # noqa
from .job_relaunch import * # noqa
from .survey_password import * # noqa from .survey_password import * # noqa
from .start_cancel import * # noqa from .start_cancel import * # noqa
from .base import * # noqa from .base import * # noqa
+75
View File
@@ -0,0 +1,75 @@
# Copyright (c) 2015 Ansible, Inc.
# All Rights Reserved
# Python
from __future__ import absolute_import
import json
# Django
from django.core.urlresolvers import reverse
# AWX
from awx.main.models import * # noqa
from awx.main.tests.base import BaseLiveServerTest
from .base import BaseJobTestMixin
__all__ = ['JobRelaunchTest',]
class JobRelaunchTest(BaseJobTestMixin, BaseLiveServerTest):
def test_job_relaunch(self):
job = self.make_job(self.jt_ops_east_run, self.user_sue, 'success')
url = reverse('api:job_relaunch', args=(job.pk,))
with self.current_user(self.user_sue):
response = self.post(url, {}, expect=201)
j = Job.objects.get(pk=response['job'])
self.assertTrue(j.status == 'successful')
self.assertEqual(j.launch_type, 'relaunch')
# Test with a job that prompts for SSH and sudo passwords.
job = self.make_job(self.jt_sup_run, self.user_sue, 'success')
url = reverse('api:job_start', args=(job.pk,))
with self.current_user(self.user_sue):
response = self.get(url)
self.assertEqual(set(response['passwords_needed_to_start']),
set(['ssh_password', 'become_password']))
data = dict()
response = self.post(url, data, expect=400)
data['ssh_password'] = 'sshpass'
response = self.post(url, data, expect=400)
data2 = dict(become_password='sudopass')
response = self.post(url, data2, expect=400)
data.update(data2)
response = self.post(url, data, expect=202)
job = Job.objects.get(pk=job.pk)
# Create jt with no extra_vars
# Launch j1 with runtime extra_vars
# Assign extra_vars to jt backing job
# Relaunch j1
# j2 should not contain jt extra_vars
def test_relaunch_job_does_not_inherit_jt_extra_vars(self):
jt_extra_vars = {
"hello": "world"
}
j_extra_vars = {
"goodbye": "cruel universe"
}
job = self.make_job(self.jt_ops_east_run, self.user_sue, 'success', extra_vars=j_extra_vars)
url = reverse('api:job_relaunch', args=(job.pk,))
with self.current_user(self.user_sue):
response = self.post(url, {}, expect=201)
j = Job.objects.get(pk=response['job'])
self.assertTrue(j.status == 'successful')
self.jt_ops_east_run.extra_vars = jt_extra_vars
self.jt_ops_east_run.save()
response = self.post(url, {}, expect=201)
j = Job.objects.get(pk=response['job'])
self.assertTrue(j.status == 'successful')
resp_extra_vars = json.loads(response['extra_vars'])
self.assertNotIn("hello", resp_extra_vars)
self.assertEqual(resp_extra_vars, j_extra_vars)
-24
View File
@@ -115,30 +115,6 @@ class JobStartCancelTest(BaseJobTestMixin, BaseLiveServerTest):
# FIXME: Test with other users, test when passwords are required. # FIXME: Test with other users, test when passwords are required.
def test_job_relaunch(self):
job = self.make_job(self.jt_ops_east_run, self.user_sue, 'success')
url = reverse('api:job_relaunch', args=(job.pk,))
with self.current_user(self.user_sue):
response = self.post(url, {}, expect=201)
j = Job.objects.get(pk=response['job'])
self.assertTrue(j.status == 'successful')
# Test with a job that prompts for SSH and sudo passwords.
job = self.make_job(self.jt_sup_run, self.user_sue, 'success')
url = reverse('api:job_start', args=(job.pk,))
with self.current_user(self.user_sue):
response = self.get(url)
self.assertEqual(set(response['passwords_needed_to_start']),
set(['ssh_password', 'become_password']))
data = dict()
response = self.post(url, data, expect=400)
data['ssh_password'] = 'sshpass'
response = self.post(url, data, expect=400)
data2 = dict(become_password='sudopass')
response = self.post(url, data2, expect=400)
data.update(data2)
response = self.post(url, data, expect=202)
job = Job.objects.get(pk=job.pk)
def test_job_cancel(self): def test_job_cancel(self):
#job = self.job_ops_east_run #job = self.job_ops_east_run
job = self.make_job(self.jt_ops_east_run, self.user_sue, 'new') job = self.make_job(self.jt_ops_east_run, self.user_sue, 'new')
+55
View File
@@ -1,10 +1,56 @@
# Copyright (c) 2015 Ansible, Inc. # Copyright (c) 2015 Ansible, Inc.
# All Rights Reserved. # All Rights Reserved.
# Python
from datetime import timedelta
# Django
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
from django.test.utils import override_settings
from django.contrib.auth.models import User
from django.utils.timezone import now as tz_now
# AWX
from awx.main.models import * # noqa from awx.main.models import * # noqa
from awx.main.tests.base import BaseTest from awx.main.tests.base import BaseTest
__all__ = ['AuthTokenLimitUnitTest', 'OrganizationsTest']
class AuthTokenLimitUnitTest(BaseTest):
def setUp(self):
self.now = tz_now()
# Times are relative to now
# (key, created on in seconds , expiration in seconds)
self.test_data = [
# a is implicitly expired
("a", -1000, -10),
# b's are invalid due to session limit of 3
("b", -100, 60),
("bb", -100, 60),
("c", -90, 70),
("d", -80, 80),
("e", -70, 90),
]
self.user = User.objects.create_superuser('admin', 'foo@bar.com', 'password')
for key, t_create, t_expire in self.test_data:
AuthToken.objects.create(
user=self.user,
key=key,
request_hash='this_is_a_hash',
created=self.now + timedelta(seconds=t_create),
expires=self.now + timedelta(seconds=t_expire),
)
super(AuthTokenLimitUnitTest, self).setUp()
@override_settings(AUTH_TOKEN_PER_USER=3)
def test_get_tokens_over_limit(self):
invalid_tokens = AuthToken.get_tokens_over_limit(self.user, now=self.now)
invalid_keys = [x.key for x in invalid_tokens]
self.assertEqual(len(invalid_keys), 2)
self.assertIn('b', invalid_keys)
self.assertIn('bb', invalid_keys)
class OrganizationsTest(BaseTest): class OrganizationsTest(BaseTest):
def collection(self): def collection(self):
@@ -221,6 +267,11 @@ class OrganizationsTest(BaseTest):
# look at what we got back from the post, make sure we added an org # look at what we got back from the post, make sure we added an org
last_org = Organization.objects.order_by('-pk')[0] last_org = Organization.objects.order_by('-pk')[0]
self.assertTrue(data1['url'].endswith("/%d/" % last_org.pk)) self.assertTrue(data1['url'].endswith("/%d/" % last_org.pk))
# Test that not even super users can create an organization with a basic license
self.create_basic_license_file()
cant_org = dict(name='silly user org', description='4815162342')
self.post(self.collection(), cant_org, expect=402, auth=self.get_super_credentials())
def test_post_item_subobjects_projects(self): def test_post_item_subobjects_projects(self):
@@ -391,6 +442,10 @@ class OrganizationsTest(BaseTest):
# also check that DELETE on the collection doesn't work # also check that DELETE on the collection doesn't work
self.delete(self.collection(), expect=405, auth=self.get_super_credentials()) self.delete(self.collection(), expect=405, auth=self.get_super_credentials())
# Test that not even super users can delete an organization with a basic license
self.create_basic_license_file()
self.delete(urls[2], expect=402, auth=self.get_super_credentials())
def test_invalid_post_data(self): def test_invalid_post_data(self):
url = reverse('api:organization_list') url = reverse('api:organization_list')
# API should gracefully handle data of an invalid type. # API should gracefully handle data of an invalid type.
+12 -1
View File
@@ -209,7 +209,7 @@ class ProjectsTest(BaseTransactionTest):
self.assertEquals(results['count'], 10) self.assertEquals(results['count'], 10)
# org admin # org admin
results = self.get(projects, expect=200, auth=self.get_normal_credentials()) results = self.get(projects, expect=200, auth=self.get_normal_credentials())
self.assertEquals(results['count'], 10) self.assertEquals(results['count'], 9)
# user on a team # user on a team
results = self.get(projects, expect=200, auth=self.get_other_credentials()) results = self.get(projects, expect=200, auth=self.get_other_credentials())
self.assertEquals(results['count'], 5) self.assertEquals(results['count'], 5)
@@ -300,6 +300,17 @@ class ProjectsTest(BaseTransactionTest):
got = self.get(proj_orgs, expect=200, auth=self.get_super_credentials()) got = self.get(proj_orgs, expect=200, auth=self.get_super_credentials())
self.assertEquals(got['count'], 2) self.assertEquals(got['count'], 2)
# Verify that creatorship doesn't imply access if access is removed
a_new_proj = self.make_project(created_by=self.other_django_user, playbook_content=TEST_PLAYBOOK)
self.organizations[0].admins.add(self.other_django_user)
self.organizations[0].projects.add(a_new_proj)
proj_detail = reverse('api:project_detail', args=(a_new_proj.pk,))
self.patch(proj_detail, data=dict(description="test"), expect=200, auth=self.get_other_credentials())
self.organizations[0].admins.remove(self.other_django_user)
self.patch(proj_detail, data=dict(description="test_now"), expect=403, auth=self.get_other_credentials())
self.delete(proj_detail, expect=403, auth=self.get_other_credentials())
a_new_proj.delete()
# ===================================================================== # =====================================================================
# TEAMS # TEAMS
+1 -1
View File
@@ -9,7 +9,7 @@ import os
import shutil import shutil
import subprocess import subprocess
import tempfile import tempfile
import unittest import unittest2 as unittest
# Django # Django
from django.conf import settings from django.conf import settings
+60 -2
View File
@@ -4,18 +4,76 @@
# Python # Python
import datetime import datetime
import urllib import urllib
from mock import patch
# Django # Django
from django.conf import settings from django.conf import settings
from django.contrib.auth.models import User, Group from django.contrib.auth.models import User, Group
from django.db.models import Q from django.db.models import Q
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
from django.test.utils import override_settings
# AWX # AWX
from awx.main.models import * # noqa from awx.main.models import * # noqa
from awx.main.tests.base import BaseTest from awx.main.tests.base import BaseTest
__all__ = ['AuthTokenProxyTest', 'UsersTest', 'LdapTest'] __all__ = ['AuthTokenTimeoutTest', 'AuthTokenLimitTest', 'AuthTokenProxyTest', 'UsersTest', 'LdapTest']
class AuthTokenTimeoutTest(BaseTest):
def setUp(self):
super(AuthTokenTimeoutTest, self).setUp()
self.setup_users()
self.setup_instances()
def test_auth_token_timeout_exists(self):
auth_token_url = reverse('api:auth_token_view')
dashboard_url = reverse('api:dashboard_view')
data = dict(zip(('username', 'password'), self.get_super_credentials()))
auth = self.post(auth_token_url, data, expect=200)
kwargs = {
'HTTP_X_AUTH_TOKEN': 'Token %s' % auth['token']
}
response = self._generic_rest(dashboard_url, expect=200, method='get', return_response_object=True, client_kwargs=kwargs)
self.assertIn('Auth-Token-Timeout', response)
self.assertEqual(response['Auth-Token-Timeout'], str(settings.AUTH_TOKEN_EXPIRATION))
class AuthTokenLimitTest(BaseTest):
def setUp(self):
super(AuthTokenLimitTest, self).setUp()
self.setup_users()
self.setup_instances()
@override_settings(AUTH_TOKEN_PER_USER=1)
@patch.object(awx.main.models.organization.AuthToken, 'get_request_hash')
def test_invalidate_first_session(self, mock_get_request_hash):
auth_token_url = reverse('api:auth_token_view')
user_me_url = reverse('api:user_me_list')
data = dict(zip(('username', 'password'), self.get_normal_credentials()))
mock_get_request_hash.return_value = "session_1"
response = self.post(auth_token_url, data, expect=200, auth=None)
auth_token1 = {
'token': response['token']
}
self.get(user_me_url, expect=200, auth=auth_token1)
mock_get_request_hash.return_value = "session_2"
response = self.post(auth_token_url, data, expect=200, auth=None)
auth_token2 = {
'token': response['token']
}
self.get(user_me_url, expect=200, auth=auth_token2)
# Ensure our get_request_hash mock is working
self.assertNotEqual(auth_token1['token'], auth_token2['token'])
mock_get_request_hash.return_value = "session_1"
response = self.get(user_me_url, expect=401, auth=auth_token1)
self.assertEqual(AuthToken.reason_long('limit_reached'), response['detail'])
''' '''
Ensure ips from the X-Forwarded-For get honored and used in auth tokens Ensure ips from the X-Forwarded-For get honored and used in auth tokens
@@ -204,7 +262,7 @@ class UsersTest(BaseTest):
remote_addr = '127.0.0.2' remote_addr = '127.0.0.2'
response = self.get(user_me_url, expect=401, auth=auth_token, response = self.get(user_me_url, expect=401, auth=auth_token,
remote_addr=remote_addr) remote_addr=remote_addr)
self.assertEqual(response['detail'], 'Invalid token') self.assertEqual(response['detail'], AuthToken.reason_long('invalid_token'))
# The WWW-Authenticate header should specify Token auth, since that # The WWW-Authenticate header should specify Token auth, since that
# auth method was used in the request. # auth method was used in the request.
+11 -1
View File
@@ -389,11 +389,13 @@ def get_system_task_capacity():
return 50 + ((int(total_mem_value) / 1024) - 2) * 75 return 50 + ((int(total_mem_value) / 1024) - 2) * 75
def emit_websocket_notification(endpoint, event, payload): def emit_websocket_notification(endpoint, event, payload, token_key=None):
from awx.main.socket import Socket from awx.main.socket import Socket
try: try:
with Socket('websocket', 'w', nowait=True, logger=logger) as websocket: with Socket('websocket', 'w', nowait=True, logger=logger) as websocket:
if token_key:
payload['token_key'] = token_key
payload['event'] = event payload['event'] = event
payload['endpoint'] = endpoint payload['endpoint'] = endpoint
websocket.publish(payload) websocket.publish(payload)
@@ -512,3 +514,11 @@ def timestamp_apiformat(timestamp):
if timestamp.endswith('+00:00'): if timestamp.endswith('+00:00'):
timestamp = timestamp[:-6] + 'Z' timestamp = timestamp[:-6] + 'Z'
return timestamp return timestamp
# damn you python 2.6
def timedelta_total_seconds(timedelta):
return (
timedelta.microseconds + 0.0 +
(timedelta.seconds + timedelta.days * 24 * 3600) * 10 ** 6) / 10 ** 6
@@ -426,6 +426,8 @@ class JobCallbackModule(BaseCallbackModule):
def v2_playbook_on_stats(self, stats): def v2_playbook_on_stats(self, stats):
self.playbook_on_stats(stats) self.playbook_on_stats(stats)
def v2_playbook_on_include(self, included_file):
self._log_event('playbook_on_include', included_file=included_file)
class AdHocCommandCallbackModule(BaseCallbackModule): class AdHocCommandCallbackModule(BaseCallbackModule):
''' '''
+6 -5
View File
@@ -35,7 +35,10 @@ import time
import datetime import datetime
from copy import deepcopy from copy import deepcopy
from ansible import constants as C from ansible import constants as C
from ansible.cache.base import BaseCacheModule try:
from ansible.cache.base import BaseCacheModule
except:
from ansible.plugins.cache.base import BaseCacheModule
try: try:
import zmq import zmq
@@ -46,7 +49,6 @@ except ImportError:
class CacheModule(BaseCacheModule): class CacheModule(BaseCacheModule):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
# Basic in-memory caching for typical runs # Basic in-memory caching for typical runs
self._cache = {} self._cache = {}
self._cache_prev = {} self._cache_prev = {}
@@ -108,16 +110,15 @@ class CacheModule(BaseCacheModule):
module = self.identify_new_module(key, value) module = self.identify_new_module(key, value)
# Assume ansible fact triggered the set if no new module found # Assume ansible fact triggered the set if no new module found
facts = self.filter_ansible_facts(value) if not module else dict({ module : value[module]}) facts = self.filter_ansible_facts(value) if not module else dict({ module : value[module]})
self._cache_prev = deepcopy(self._cache)
self._cache[key] = value self._cache[key] = value
self._cache_prev = deepcopy(self._cache)
packet = { packet = {
'host': key, 'host': key,
'inventory_id': os.environ['INVENTORY_ID'], 'inventory_id': os.environ['INVENTORY_ID'],
'facts': facts, 'facts': facts,
'date_key': self.date_key, 'date_key': self.date_key,
} }
# Emit fact data to tower for processing # Emit fact data to tower for processing
self.socket.send_json(packet) self.socket.send_json(packet)
self.socket.recv() self.socket.recv()
+524 -23
View File
@@ -22,6 +22,12 @@ you need to define:
export EC2_URL=http://hostname_of_your_cc:port/services/Eucalyptus export EC2_URL=http://hostname_of_your_cc:port/services/Eucalyptus
If you're using boto profiles (requires boto>=2.24.0) you can choose a profile
using the --boto-profile command line argument (e.g. ec2.py --boto-profile prod) or using
the AWS_PROFILE variable:
AWS_PROFILE=prod ansible-playbook -i ec2.py myplaybook.yml
For more details, see: http://docs.pythonboto.org/en/latest/boto_config_tut.html For more details, see: http://docs.pythonboto.org/en/latest/boto_config_tut.html
When run against a specific host, this script returns the following variables: When run against a specific host, this script returns the following variables:
@@ -121,6 +127,7 @@ from time import time
import boto import boto
from boto import ec2 from boto import ec2
from boto import rds from boto import rds
from boto import elasticache
from boto import route53 from boto import route53
import six import six
@@ -147,9 +154,18 @@ class Ec2Inventory(object):
# Index of hostname (address) to instance ID # Index of hostname (address) to instance ID
self.index = {} self.index = {}
# Boto profile to use (if any)
self.boto_profile = None
# Read settings and parse CLI arguments # Read settings and parse CLI arguments
self.read_settings()
self.parse_cli_args() self.parse_cli_args()
self.read_settings()
# Make sure that profile_name is not passed at all if not set
# as pre 2.24 boto will fall over otherwise
if self.boto_profile:
if not hasattr(boto.ec2.EC2Connection, 'profile_name'):
self.fail_with_error("boto version must be >= 2.24 to use profile")
# Cache # Cache
if self.args.refresh_cache: if self.args.refresh_cache:
@@ -186,12 +202,12 @@ class Ec2Inventory(object):
def read_settings(self): def read_settings(self):
''' Reads the settings from the ec2.ini file ''' ''' Reads the settings from the ec2.ini file '''
if six.PY2: if six.PY3:
config = configparser.SafeConfigParser()
else:
config = configparser.ConfigParser() config = configparser.ConfigParser()
else:
config = configparser.SafeConfigParser()
ec2_default_ini_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'ec2.ini') ec2_default_ini_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'ec2.ini')
ec2_ini_path = os.environ.get('EC2_INI_PATH', ec2_default_ini_path) ec2_ini_path = os.path.expanduser(os.path.expandvars(os.environ.get('EC2_INI_PATH', ec2_default_ini_path)))
config.read(ec2_ini_path) config.read(ec2_ini_path)
# is eucalyptus? # is eucalyptus?
@@ -232,18 +248,72 @@ class Ec2Inventory(object):
if config.has_option('ec2', 'rds'): if config.has_option('ec2', 'rds'):
self.rds_enabled = config.getboolean('ec2', 'rds') self.rds_enabled = config.getboolean('ec2', 'rds')
# Return all EC2 and RDS instances (if RDS is enabled) # Include ElastiCache instances?
self.elasticache_enabled = True
if config.has_option('ec2', 'elasticache'):
self.elasticache_enabled = config.getboolean('ec2', 'elasticache')
# Return all EC2 instances?
if config.has_option('ec2', 'all_instances'): if config.has_option('ec2', 'all_instances'):
self.all_instances = config.getboolean('ec2', 'all_instances') self.all_instances = config.getboolean('ec2', 'all_instances')
else: else:
self.all_instances = False self.all_instances = False
# Instance states to be gathered in inventory. Default is 'running'.
# Setting 'all_instances' to 'yes' overrides this option.
ec2_valid_instance_states = [
'pending',
'running',
'shutting-down',
'terminated',
'stopping',
'stopped'
]
self.ec2_instance_states = []
if self.all_instances:
self.ec2_instance_states = ec2_valid_instance_states
elif config.has_option('ec2', 'instance_states'):
for instance_state in config.get('ec2', 'instance_states').split(','):
instance_state = instance_state.strip()
if instance_state not in ec2_valid_instance_states:
continue
self.ec2_instance_states.append(instance_state)
else:
self.ec2_instance_states = ['running']
# Return all RDS instances? (if RDS is enabled)
if config.has_option('ec2', 'all_rds_instances') and self.rds_enabled: if config.has_option('ec2', 'all_rds_instances') and self.rds_enabled:
self.all_rds_instances = config.getboolean('ec2', 'all_rds_instances') self.all_rds_instances = config.getboolean('ec2', 'all_rds_instances')
else: else:
self.all_rds_instances = False self.all_rds_instances = False
# Return all ElastiCache replication groups? (if ElastiCache is enabled)
if config.has_option('ec2', 'all_elasticache_replication_groups') and self.elasticache_enabled:
self.all_elasticache_replication_groups = config.getboolean('ec2', 'all_elasticache_replication_groups')
else:
self.all_elasticache_replication_groups = False
# Return all ElastiCache clusters? (if ElastiCache is enabled)
if config.has_option('ec2', 'all_elasticache_clusters') and self.elasticache_enabled:
self.all_elasticache_clusters = config.getboolean('ec2', 'all_elasticache_clusters')
else:
self.all_elasticache_clusters = False
# Return all ElastiCache nodes? (if ElastiCache is enabled)
if config.has_option('ec2', 'all_elasticache_nodes') and self.elasticache_enabled:
self.all_elasticache_nodes = config.getboolean('ec2', 'all_elasticache_nodes')
else:
self.all_elasticache_nodes = False
# boto configuration profile (prefer CLI argument)
self.boto_profile = self.args.boto_profile
if config.has_option('ec2', 'boto_profile') and not self.boto_profile:
self.boto_profile = config.get('ec2', 'boto_profile')
# Cache related # Cache related
cache_dir = os.path.expanduser(config.get('ec2', 'cache_path')) cache_dir = os.path.expanduser(config.get('ec2', 'cache_path'))
if self.boto_profile:
cache_dir = os.path.join(cache_dir, 'profile_' + self.boto_profile)
if not os.path.exists(cache_dir): if not os.path.exists(cache_dir):
os.makedirs(cache_dir) os.makedirs(cache_dir)
@@ -257,6 +327,12 @@ class Ec2Inventory(object):
else: else:
self.nested_groups = False self.nested_groups = False
# Replace dash or not in group names
if config.has_option('ec2', 'replace_dash_in_groups'):
self.replace_dash_in_groups = config.getboolean('ec2', 'replace_dash_in_groups')
else:
self.replace_dash_in_groups = True
# Configure which groups should be created. # Configure which groups should be created.
group_by_options = [ group_by_options = [
'group_by_instance_id', 'group_by_instance_id',
@@ -272,6 +348,10 @@ class Ec2Inventory(object):
'group_by_route53_names', 'group_by_route53_names',
'group_by_rds_engine', 'group_by_rds_engine',
'group_by_rds_parameter_group', 'group_by_rds_parameter_group',
'group_by_elasticache_engine',
'group_by_elasticache_cluster',
'group_by_elasticache_parameter_group',
'group_by_elasticache_replication_group',
] ]
for option in group_by_options: for option in group_by_options:
if config.has_option('ec2', option): if config.has_option('ec2', option):
@@ -286,7 +366,7 @@ class Ec2Inventory(object):
self.pattern_include = re.compile(pattern_include) self.pattern_include = re.compile(pattern_include)
else: else:
self.pattern_include = None self.pattern_include = None
except configparser.NoOptionError as e: except configparser.NoOptionError:
self.pattern_include = None self.pattern_include = None
# Do we need to exclude hosts that match a pattern? # Do we need to exclude hosts that match a pattern?
@@ -296,7 +376,7 @@ class Ec2Inventory(object):
self.pattern_exclude = re.compile(pattern_exclude) self.pattern_exclude = re.compile(pattern_exclude)
else: else:
self.pattern_exclude = None self.pattern_exclude = None
except configparser.NoOptionError as e: except configparser.NoOptionError:
self.pattern_exclude = None self.pattern_exclude = None
# Instance filters (see boto and EC2 API docs). Ignore invalid filters. # Instance filters (see boto and EC2 API docs). Ignore invalid filters.
@@ -321,6 +401,8 @@ class Ec2Inventory(object):
help='Get all the variables about a specific instance') help='Get all the variables about a specific instance')
parser.add_argument('--refresh-cache', action='store_true', default=False, parser.add_argument('--refresh-cache', action='store_true', default=False,
help='Force refresh of cache by making API requests to EC2 (default: False - use cache files)') help='Force refresh of cache by making API requests to EC2 (default: False - use cache files)')
parser.add_argument('--boto-profile', action='store',
help='Use boto profile for connections to EC2')
self.args = parser.parse_args() self.args = parser.parse_args()
@@ -334,6 +416,9 @@ class Ec2Inventory(object):
self.get_instances_by_region(region) self.get_instances_by_region(region)
if self.rds_enabled: if self.rds_enabled:
self.get_rds_instances_by_region(region) self.get_rds_instances_by_region(region)
if self.elasticache_enabled:
self.get_elasticache_clusters_by_region(region)
self.get_elasticache_replication_groups_by_region(region)
self.write_to_cache(self.inventory, self.cache_path_cache) self.write_to_cache(self.inventory, self.cache_path_cache)
self.write_to_cache(self.index, self.cache_path_index) self.write_to_cache(self.index, self.cache_path_index)
@@ -344,7 +429,25 @@ class Ec2Inventory(object):
conn = boto.connect_euca(host=self.eucalyptus_host) conn = boto.connect_euca(host=self.eucalyptus_host)
conn.APIVersion = '2010-08-31' conn.APIVersion = '2010-08-31'
else: else:
conn = ec2.connect_to_region(region) conn = self.connect_to_aws(ec2, region)
return conn
def boto_fix_security_token_in_profile(self, connect_args):
''' monkey patch for boto issue boto/boto#2100 '''
profile = 'profile ' + self.boto_profile
if boto.config.has_option(profile, 'aws_security_token'):
connect_args['security_token'] = boto.config.get(profile, 'aws_security_token')
return connect_args
def connect_to_aws(self, module, region):
connect_args = {}
# only pass the profile name if it's set (as it is not supported by older boto versions)
if self.boto_profile:
connect_args['profile_name'] = self.boto_profile
self.boto_fix_security_token_in_profile(connect_args)
conn = module.connect_to_region(region, **connect_args)
# connect_to_region will fail "silently" by returning None if the region name is wrong or not supported # connect_to_region will fail "silently" by returning None if the region name is wrong or not supported
if conn is None: if conn is None:
self.fail_with_error("region name: %s likely not supported, or AWS is down. connection to region failed." % region) self.fail_with_error("region name: %s likely not supported, or AWS is down. connection to region failed." % region)
@@ -373,26 +476,96 @@ class Ec2Inventory(object):
else: else:
backend = 'Eucalyptus' if self.eucalyptus else 'AWS' backend = 'Eucalyptus' if self.eucalyptus else 'AWS'
error = "Error connecting to %s backend.\n%s" % (backend, e.message) error = "Error connecting to %s backend.\n%s" % (backend, e.message)
self.fail_with_error(error) self.fail_with_error(error, 'getting EC2 instances')
def get_rds_instances_by_region(self, region): def get_rds_instances_by_region(self, region):
''' Makes an AWS API call to the list of RDS instances in a particular ''' Makes an AWS API call to the list of RDS instances in a particular
region ''' region '''
try: try:
conn = rds.connect_to_region(region) conn = self.connect_to_aws(rds, region)
if conn: if conn:
instances = conn.get_all_dbinstances() instances = conn.get_all_dbinstances()
for instance in instances: for instance in instances:
self.add_rds_instance(instance, region) self.add_rds_instance(instance, region)
except boto.exception.BotoServerError as e: except boto.exception.BotoServerError as e:
error = e.reason error = e.reason
if e.error_code == 'AuthFailure': if e.error_code == 'AuthFailure':
error = self.get_auth_error_message() error = self.get_auth_error_message()
if not e.reason == "Forbidden": if not e.reason == "Forbidden":
error = "Looks like AWS RDS is down:\n%s" % e.message error = "Looks like AWS RDS is down:\n%s" % e.message
self.fail_with_error(error) self.fail_with_error(error, 'getting RDS instances')
def get_elasticache_clusters_by_region(self, region):
''' Makes an AWS API call to the list of ElastiCache clusters (with
nodes' info) in a particular region.'''
# ElastiCache boto module doesn't provide a get_all_intances method,
# that's why we need to call describe directly (it would be called by
# the shorthand method anyway...)
try:
conn = elasticache.connect_to_region(region)
if conn:
# show_cache_node_info = True
# because we also want nodes' information
response = conn.describe_cache_clusters(None, None, None, True)
except boto.exception.BotoServerError as e:
error = e.reason
if e.error_code == 'AuthFailure':
error = self.get_auth_error_message()
if not e.reason == "Forbidden":
error = "Looks like AWS ElastiCache is down:\n%s" % e.message
self.fail_with_error(error, 'getting ElastiCache clusters')
try:
# Boto also doesn't provide wrapper classes to CacheClusters or
# CacheNodes. Because of that wo can't make use of the get_list
# method in the AWSQueryConnection. Let's do the work manually
clusters = response['DescribeCacheClustersResponse']['DescribeCacheClustersResult']['CacheClusters']
except KeyError as e:
error = "ElastiCache query to AWS failed (unexpected format)."
self.fail_with_error(error, 'getting ElastiCache clusters')
for cluster in clusters:
self.add_elasticache_cluster(cluster, region)
def get_elasticache_replication_groups_by_region(self, region):
''' Makes an AWS API call to the list of ElastiCache replication groups
in a particular region.'''
# ElastiCache boto module doesn't provide a get_all_intances method,
# that's why we need to call describe directly (it would be called by
# the shorthand method anyway...)
try:
conn = elasticache.connect_to_region(region)
if conn:
response = conn.describe_replication_groups()
except boto.exception.BotoServerError as e:
error = e.reason
if e.error_code == 'AuthFailure':
error = self.get_auth_error_message()
if not e.reason == "Forbidden":
error = "Looks like AWS ElastiCache [Replication Groups] is down:\n%s" % e.message
self.fail_with_error(error, 'getting ElastiCache clusters')
try:
# Boto also doesn't provide wrapper classes to ReplicationGroups
# Because of that wo can't make use of the get_list method in the
# AWSQueryConnection. Let's do the work manually
replication_groups = response['DescribeReplicationGroupsResponse']['DescribeReplicationGroupsResult']['ReplicationGroups']
except KeyError as e:
error = "ElastiCache [Replication Groups] query to AWS failed (unexpected format)."
self.fail_with_error(error, 'getting ElastiCache clusters')
for replication_group in replication_groups:
self.add_elasticache_replication_group(replication_group, region)
def get_auth_error_message(self): def get_auth_error_message(self):
''' create an informative error message if there is an issue authenticating''' ''' create an informative error message if there is an issue authenticating'''
@@ -410,9 +583,12 @@ class Ec2Inventory(object):
errors.append(" - No Boto config found at any expected location '%s'" % ', '.join(boto_paths)) errors.append(" - No Boto config found at any expected location '%s'" % ', '.join(boto_paths))
return '\n'.join(errors) return '\n'.join(errors)
def fail_with_error(self, err_msg): def fail_with_error(self, err_msg, err_operation=None):
'''log an error to std err for ansible-playbook to consume and exit''' '''log an error to std err for ansible-playbook to consume and exit'''
if err_operation:
err_msg = 'ERROR: "{err_msg}", while: {err_operation}'.format(
err_msg=err_msg, err_operation=err_operation)
sys.stderr.write(err_msg) sys.stderr.write(err_msg)
sys.exit(1) sys.exit(1)
@@ -428,8 +604,8 @@ class Ec2Inventory(object):
''' Adds an instance to the inventory and index, as long as it is ''' Adds an instance to the inventory and index, as long as it is
addressable ''' addressable '''
# Only want running instances unless all_instances is True # Only return instances with desired instance states
if not self.all_instances and instance.state != 'running': if instance.state not in self.ec2_instance_states:
return return
# Select the best destination address # Select the best destination address
@@ -633,6 +809,243 @@ class Ec2Inventory(object):
self.inventory["_meta"]["hostvars"][dest] = self.get_host_info_dict_from_instance(instance) self.inventory["_meta"]["hostvars"][dest] = self.get_host_info_dict_from_instance(instance)
def add_elasticache_cluster(self, cluster, region):
''' Adds an ElastiCache cluster to the inventory and index, as long as
it's nodes are addressable '''
# Only want available clusters unless all_elasticache_clusters is True
if not self.all_elasticache_clusters and cluster['CacheClusterStatus'] != 'available':
return
# Select the best destination address
if 'ConfigurationEndpoint' in cluster and cluster['ConfigurationEndpoint']:
# Memcached cluster
dest = cluster['ConfigurationEndpoint']['Address']
is_redis = False
else:
# Redis sigle node cluster
# Because all Redis clusters are single nodes, we'll merge the
# info from the cluster with info about the node
dest = cluster['CacheNodes'][0]['Endpoint']['Address']
is_redis = True
if not dest:
# Skip clusters we cannot address (e.g. private VPC subnet)
return
# Add to index
self.index[dest] = [region, cluster['CacheClusterId']]
# Inventory: Group by instance ID (always a group of 1)
if self.group_by_instance_id:
self.inventory[cluster['CacheClusterId']] = [dest]
if self.nested_groups:
self.push_group(self.inventory, 'instances', cluster['CacheClusterId'])
# Inventory: Group by region
if self.group_by_region and not is_redis:
self.push(self.inventory, region, dest)
if self.nested_groups:
self.push_group(self.inventory, 'regions', region)
# Inventory: Group by availability zone
if self.group_by_availability_zone and not is_redis:
self.push(self.inventory, cluster['PreferredAvailabilityZone'], dest)
if self.nested_groups:
if self.group_by_region:
self.push_group(self.inventory, region, cluster['PreferredAvailabilityZone'])
self.push_group(self.inventory, 'zones', cluster['PreferredAvailabilityZone'])
# Inventory: Group by node type
if self.group_by_instance_type and not is_redis:
type_name = self.to_safe('type_' + cluster['CacheNodeType'])
self.push(self.inventory, type_name, dest)
if self.nested_groups:
self.push_group(self.inventory, 'types', type_name)
# Inventory: Group by VPC (information not available in the current
# AWS API version for ElastiCache)
# Inventory: Group by security group
if self.group_by_security_group and not is_redis:
# Check for the existence of the 'SecurityGroups' key and also if
# this key has some value. When the cluster is not placed in a SG
# the query can return None here and cause an error.
if 'SecurityGroups' in cluster and cluster['SecurityGroups'] is not None:
for security_group in cluster['SecurityGroups']:
key = self.to_safe("security_group_" + security_group['SecurityGroupId'])
self.push(self.inventory, key, dest)
if self.nested_groups:
self.push_group(self.inventory, 'security_groups', key)
# Inventory: Group by engine
if self.group_by_elasticache_engine and not is_redis:
self.push(self.inventory, self.to_safe("elasticache_" + cluster['Engine']), dest)
if self.nested_groups:
self.push_group(self.inventory, 'elasticache_engines', self.to_safe(cluster['Engine']))
# Inventory: Group by parameter group
if self.group_by_elasticache_parameter_group:
self.push(self.inventory, self.to_safe("elasticache_parameter_group_" + cluster['CacheParameterGroup']['CacheParameterGroupName']), dest)
if self.nested_groups:
self.push_group(self.inventory, 'elasticache_parameter_groups', self.to_safe(cluster['CacheParameterGroup']['CacheParameterGroupName']))
# Inventory: Group by replication group
if self.group_by_elasticache_replication_group and 'ReplicationGroupId' in cluster and cluster['ReplicationGroupId']:
self.push(self.inventory, self.to_safe("elasticache_replication_group_" + cluster['ReplicationGroupId']), dest)
if self.nested_groups:
self.push_group(self.inventory, 'elasticache_replication_groups', self.to_safe(cluster['ReplicationGroupId']))
# Global Tag: all ElastiCache clusters
self.push(self.inventory, 'elasticache_clusters', cluster['CacheClusterId'])
host_info = self.get_host_info_dict_from_describe_dict(cluster)
self.inventory["_meta"]["hostvars"][dest] = host_info
# Add the nodes
for node in cluster['CacheNodes']:
self.add_elasticache_node(node, cluster, region)
def add_elasticache_node(self, node, cluster, region):
''' Adds an ElastiCache node to the inventory and index, as long as
it is addressable '''
# Only want available nodes unless all_elasticache_nodes is True
if not self.all_elasticache_nodes and node['CacheNodeStatus'] != 'available':
return
# Select the best destination address
dest = node['Endpoint']['Address']
if not dest:
# Skip nodes we cannot address (e.g. private VPC subnet)
return
node_id = self.to_safe(cluster['CacheClusterId'] + '_' + node['CacheNodeId'])
# Add to index
self.index[dest] = [region, node_id]
# Inventory: Group by node ID (always a group of 1)
if self.group_by_instance_id:
self.inventory[node_id] = [dest]
if self.nested_groups:
self.push_group(self.inventory, 'instances', node_id)
# Inventory: Group by region
if self.group_by_region:
self.push(self.inventory, region, dest)
if self.nested_groups:
self.push_group(self.inventory, 'regions', region)
# Inventory: Group by availability zone
if self.group_by_availability_zone:
self.push(self.inventory, cluster['PreferredAvailabilityZone'], dest)
if self.nested_groups:
if self.group_by_region:
self.push_group(self.inventory, region, cluster['PreferredAvailabilityZone'])
self.push_group(self.inventory, 'zones', cluster['PreferredAvailabilityZone'])
# Inventory: Group by node type
if self.group_by_instance_type:
type_name = self.to_safe('type_' + cluster['CacheNodeType'])
self.push(self.inventory, type_name, dest)
if self.nested_groups:
self.push_group(self.inventory, 'types', type_name)
# Inventory: Group by VPC (information not available in the current
# AWS API version for ElastiCache)
# Inventory: Group by security group
if self.group_by_security_group:
# Check for the existence of the 'SecurityGroups' key and also if
# this key has some value. When the cluster is not placed in a SG
# the query can return None here and cause an error.
if 'SecurityGroups' in cluster and cluster['SecurityGroups'] is not None:
for security_group in cluster['SecurityGroups']:
key = self.to_safe("security_group_" + security_group['SecurityGroupId'])
self.push(self.inventory, key, dest)
if self.nested_groups:
self.push_group(self.inventory, 'security_groups', key)
# Inventory: Group by engine
if self.group_by_elasticache_engine:
self.push(self.inventory, self.to_safe("elasticache_" + cluster['Engine']), dest)
if self.nested_groups:
self.push_group(self.inventory, 'elasticache_engines', self.to_safe("elasticache_" + cluster['Engine']))
# Inventory: Group by parameter group (done at cluster level)
# Inventory: Group by replication group (done at cluster level)
# Inventory: Group by ElastiCache Cluster
if self.group_by_elasticache_cluster:
self.push(self.inventory, self.to_safe("elasticache_cluster_" + cluster['CacheClusterId']), dest)
# Global Tag: all ElastiCache nodes
self.push(self.inventory, 'elasticache_nodes', dest)
host_info = self.get_host_info_dict_from_describe_dict(node)
if dest in self.inventory["_meta"]["hostvars"]:
self.inventory["_meta"]["hostvars"][dest].update(host_info)
else:
self.inventory["_meta"]["hostvars"][dest] = host_info
def add_elasticache_replication_group(self, replication_group, region):
''' Adds an ElastiCache replication group to the inventory and index '''
# Only want available clusters unless all_elasticache_replication_groups is True
if not self.all_elasticache_replication_groups and replication_group['Status'] != 'available':
return
# Select the best destination address (PrimaryEndpoint)
dest = replication_group['NodeGroups'][0]['PrimaryEndpoint']['Address']
if not dest:
# Skip clusters we cannot address (e.g. private VPC subnet)
return
# Add to index
self.index[dest] = [region, replication_group['ReplicationGroupId']]
# Inventory: Group by ID (always a group of 1)
if self.group_by_instance_id:
self.inventory[replication_group['ReplicationGroupId']] = [dest]
if self.nested_groups:
self.push_group(self.inventory, 'instances', replication_group['ReplicationGroupId'])
# Inventory: Group by region
if self.group_by_region:
self.push(self.inventory, region, dest)
if self.nested_groups:
self.push_group(self.inventory, 'regions', region)
# Inventory: Group by availability zone (doesn't apply to replication groups)
# Inventory: Group by node type (doesn't apply to replication groups)
# Inventory: Group by VPC (information not available in the current
# AWS API version for replication groups
# Inventory: Group by security group (doesn't apply to replication groups)
# Check this value in cluster level
# Inventory: Group by engine (replication groups are always Redis)
if self.group_by_elasticache_engine:
self.push(self.inventory, 'elasticache_redis', dest)
if self.nested_groups:
self.push_group(self.inventory, 'elasticache_engines', 'redis')
# Global Tag: all ElastiCache clusters
self.push(self.inventory, 'elasticache_replication_groups', replication_group['ReplicationGroupId'])
host_info = self.get_host_info_dict_from_describe_dict(replication_group)
self.inventory["_meta"]["hostvars"][dest] = host_info
def get_route53_records(self): def get_route53_records(self):
''' Get and store the map of resource records to domain names that ''' Get and store the map of resource records to domain names that
@@ -681,7 +1094,6 @@ class Ec2Inventory(object):
return list(name_list) return list(name_list)
def get_host_info_dict_from_instance(self, instance): def get_host_info_dict_from_instance(self, instance):
instance_vars = {} instance_vars = {}
for key in vars(instance): for key in vars(instance):
@@ -727,6 +1139,91 @@ class Ec2Inventory(object):
return instance_vars return instance_vars
def get_host_info_dict_from_describe_dict(self, describe_dict):
''' Parses the dictionary returned by the API call into a flat list
of parameters. This method should be used only when 'describe' is
used directly because Boto doesn't provide specific classes. '''
# I really don't agree with prefixing everything with 'ec2'
# because EC2, RDS and ElastiCache are different services.
# I'm just following the pattern used until now to not break any
# compatibility.
host_info = {}
for key in describe_dict:
value = describe_dict[key]
key = self.to_safe('ec2_' + self.uncammelize(key))
# Handle complex types
# Target: Memcached Cache Clusters
if key == 'ec2_configuration_endpoint' and value:
host_info['ec2_configuration_endpoint_address'] = value['Address']
host_info['ec2_configuration_endpoint_port'] = value['Port']
# Target: Cache Nodes and Redis Cache Clusters (single node)
if key == 'ec2_endpoint' and value:
host_info['ec2_endpoint_address'] = value['Address']
host_info['ec2_endpoint_port'] = value['Port']
# Target: Redis Replication Groups
if key == 'ec2_node_groups' and value:
host_info['ec2_endpoint_address'] = value[0]['PrimaryEndpoint']['Address']
host_info['ec2_endpoint_port'] = value[0]['PrimaryEndpoint']['Port']
replica_count = 0
for node in value[0]['NodeGroupMembers']:
if node['CurrentRole'] == 'primary':
host_info['ec2_primary_cluster_address'] = node['ReadEndpoint']['Address']
host_info['ec2_primary_cluster_port'] = node['ReadEndpoint']['Port']
host_info['ec2_primary_cluster_id'] = node['CacheClusterId']
elif node['CurrentRole'] == 'replica':
host_info['ec2_replica_cluster_address_'+ str(replica_count)] = node['ReadEndpoint']['Address']
host_info['ec2_replica_cluster_port_'+ str(replica_count)] = node['ReadEndpoint']['Port']
host_info['ec2_replica_cluster_id_'+ str(replica_count)] = node['CacheClusterId']
replica_count += 1
# Target: Redis Replication Groups
if key == 'ec2_member_clusters' and value:
host_info['ec2_member_clusters'] = ','.join([str(i) for i in value])
# Target: All Cache Clusters
elif key == 'ec2_cache_parameter_group':
host_info["ec2_cache_node_ids_to_reboot"] = ','.join([str(i) for i in value['CacheNodeIdsToReboot']])
host_info['ec2_cache_parameter_group_name'] = value['CacheParameterGroupName']
host_info['ec2_cache_parameter_apply_status'] = value['ParameterApplyStatus']
# Target: Almost everything
elif key == 'ec2_security_groups':
# Skip if SecurityGroups is None
# (it is possible to have the key defined but no value in it).
if value is not None:
sg_ids = []
for sg in value:
sg_ids.append(sg['SecurityGroupId'])
host_info["ec2_security_group_ids"] = ','.join([str(i) for i in sg_ids])
# Target: Everything
# Preserve booleans and integers
elif type(value) in [int, bool]:
host_info[key] = value
# Target: Everything
# Sanitize string values
elif isinstance(value, six.string_types):
host_info[key] = value.strip()
# Target: Everything
# Replace None by an empty string
elif type(value) == type(None):
host_info[key] = ''
else:
# Remove non-processed complex types
pass
return host_info
def get_host_info(self): def get_host_info(self):
''' Get variables about a specific host ''' ''' Get variables about a specific host '''
@@ -790,13 +1287,16 @@ class Ec2Inventory(object):
cache.write(json_data) cache.write(json_data)
cache.close() cache.close()
def uncammelize(self, key):
temp = re.sub('(.)([A-Z][a-z]+)', r'\1_\2', key)
return re.sub('([a-z0-9])([A-Z])', r'\1_\2', temp).lower()
def to_safe(self, word): def to_safe(self, word):
''' Converts 'bad' characters in a string to underscores so they can be ''' Converts 'bad' characters in a string to underscores so they can be used as Ansible groups '''
used as Ansible groups ''' regex = "[^A-Za-z0-9\_"
if self.replace_dash_in_groups:
return re.sub("[^A-Za-z0-9\_]", "_", word) regex += "\-"
return re.sub(regex + "]", "_", word)
def json_format_dict(self, data, pretty=False): def json_format_dict(self, data, pretty=False):
''' Converts a dict to a JSON object and dumps it as a formatted ''' Converts a dict to a JSON object and dumps it as a formatted
@@ -810,3 +1310,4 @@ class Ec2Inventory(object):
# Run the script # Run the script
Ec2Inventory() Ec2Inventory()
+8 -5
View File
@@ -66,7 +66,7 @@ Examples:
$ ansible -i gce.py us-central1-a -m shell -a "/bin/uname -a" $ ansible -i gce.py us-central1-a -m shell -a "/bin/uname -a"
Use the GCE inventory script to print out instance specific information Use the GCE inventory script to print out instance specific information
$ plugins/inventory/gce.py --host my_instance $ contrib/inventory/gce.py --host my_instance
Author: Eric Johnson <erjohnso@google.com> Author: Eric Johnson <erjohnso@google.com>
Version: 0.0.1 Version: 0.0.1
@@ -112,9 +112,9 @@ class GceInventory(object):
# Just display data for specific host # Just display data for specific host
if self.args.host: if self.args.host:
print self.json_format_dict(self.node_to_dict( print(self.json_format_dict(self.node_to_dict(
self.get_instance(self.args.host)), self.get_instance(self.args.host)),
pretty=self.args.pretty) pretty=self.args.pretty))
sys.exit(0) sys.exit(0)
# Otherwise, assume user wants all instances grouped # Otherwise, assume user wants all instances grouped
@@ -237,7 +237,7 @@ class GceInventory(object):
'''Gets details about a specific instance ''' '''Gets details about a specific instance '''
try: try:
return self.driver.ex_get_node(instance_name) return self.driver.ex_get_node(instance_name)
except Exception, e: except Exception as e:
return None return None
def group_instances(self): def group_instances(self):
@@ -257,7 +257,10 @@ class GceInventory(object):
tags = node.extra['tags'] tags = node.extra['tags']
for t in tags: for t in tags:
tag = 'tag_%s' % t if t.startswith('group-'):
tag = t[6:]
else:
tag = 'tag_%s' % t
if groups.has_key(tag): groups[tag].append(name) if groups.has_key(tag): groups[tag].append(name)
else: groups[tag] = [name] else: groups[tag] = [name]
+2 -3
View File
@@ -52,8 +52,6 @@ class OpenStackInventory(object):
def __init__(self, private=False, refresh=False): def __init__(self, private=False, refresh=False):
config_files = os_client_config.config.CONFIG_FILES config_files = os_client_config.config.CONFIG_FILES
if os.environ.get('OPENSTACK_CONFIG_FILE', None):
config_files.insert(0, os.environ['OPENSTACK_CONFIG_FILE'])
config_files.append('/etc/ansible/openstack.yml') config_files.append('/etc/ansible/openstack.yml')
self.openstack_config = os_client_config.config.OpenStackConfig( self.openstack_config = os_client_config.config.OpenStackConfig(
config_files) config_files)
@@ -156,7 +154,8 @@ def main():
elif args.host: elif args.host:
inventory.get_host(args.host) inventory.get_host(args.host)
except shade.OpenStackCloudException as e: except shade.OpenStackCloudException as e:
sys.exit(e.message) sys.stderr.write('%s\n' % e.message)
sys.exit(1)
sys.exit(0) sys.exit(0)
+38 -6
View File
@@ -153,6 +153,8 @@ import warnings
import collections import collections
import ConfigParser import ConfigParser
from six import iteritems
from ansible.constants import get_config, mk_boolean from ansible.constants import get_config, mk_boolean
try: try:
@@ -167,6 +169,9 @@ except ImportError:
print('pyrax is required for this module') print('pyrax is required for this module')
sys.exit(1) sys.exit(1)
from time import time
NON_CALLABLES = (basestring, bool, dict, int, list, type(None)) NON_CALLABLES = (basestring, bool, dict, int, list, type(None))
@@ -214,7 +219,7 @@ def host(regions, hostname):
print(json.dumps(hostvars, sort_keys=True, indent=4)) print(json.dumps(hostvars, sort_keys=True, indent=4))
def _list(regions): def _list_into_cache(regions):
groups = collections.defaultdict(list) groups = collections.defaultdict(list)
hostvars = collections.defaultdict(dict) hostvars = collections.defaultdict(dict)
images = {} images = {}
@@ -242,7 +247,7 @@ def _list(regions):
if cs is None: if cs is None:
warnings.warn( warnings.warn(
'Connecting to Rackspace region "%s" has caused Pyrax to ' 'Connecting to Rackspace region "%s" has caused Pyrax to '
'return a NoneType. Is this a valid region?' % region, 'return None. Is this a valid region?' % region,
RuntimeWarning) RuntimeWarning)
continue continue
for server in cs.servers.list(): for server in cs.servers.list():
@@ -264,7 +269,7 @@ def _list(regions):
hostvars[server.name]['rax_region'] = region hostvars[server.name]['rax_region'] = region
for key, value in server.metadata.iteritems(): for key, value in iteritems(server.metadata):
groups['%s_%s_%s' % (prefix, key, value)].append(server.name) groups['%s_%s_%s' % (prefix, key, value)].append(server.name)
groups['instance-%s' % server.id].append(server.name) groups['instance-%s' % server.id].append(server.name)
@@ -334,7 +339,31 @@ def _list(regions):
if hostvars: if hostvars:
groups['_meta'] = {'hostvars': hostvars} groups['_meta'] = {'hostvars': hostvars}
print(json.dumps(groups, sort_keys=True, indent=4))
with open(get_cache_file_path(regions), 'w') as cache_file:
json.dump(groups, cache_file)
def get_cache_file_path(regions):
regions_str = '.'.join([reg.strip().lower() for reg in regions])
ansible_tmp_path = os.path.join(os.path.expanduser("~"), '.ansible', 'tmp')
if not os.path.exists(ansible_tmp_path):
os.makedirs(ansible_tmp_path)
return os.path.join(ansible_tmp_path,
'ansible-rax-%s-%s.cache' % (
pyrax.identity.username, regions_str))
def _list(regions, refresh_cache=True):
if (not os.path.exists(get_cache_file_path(regions)) or
refresh_cache or
(time() - os.stat(get_cache_file_path(regions))[-1]) > 600):
# Cache file doesn't exist or older than 10m or refresh cache requested
_list_into_cache(regions)
with open(get_cache_file_path(regions), 'r') as cache_file:
groups = json.load(cache_file)
print(json.dumps(groups, sort_keys=True, indent=4))
def parse_args(): def parse_args():
@@ -344,6 +373,9 @@ def parse_args():
group.add_argument('--list', action='store_true', group.add_argument('--list', action='store_true',
help='List active servers') help='List active servers')
group.add_argument('--host', help='List details about the specific host') group.add_argument('--host', help='List details about the specific host')
parser.add_argument('--refresh-cache', action='store_true', default=False,
help=('Force refresh of cache, making API requests to'
'RackSpace (default: False - use cache files)'))
return parser.parse_args() return parser.parse_args()
@@ -382,7 +414,7 @@ def setup():
pyrax.keyring_auth(keyring_username, region=region) pyrax.keyring_auth(keyring_username, region=region)
else: else:
pyrax.set_credential_file(creds_file, region=region) pyrax.set_credential_file(creds_file, region=region)
except Exception, e: except Exception as e:
sys.stderr.write("%s: %s\n" % (e, e.message)) sys.stderr.write("%s: %s\n" % (e, e.message))
sys.exit(1) sys.exit(1)
@@ -410,7 +442,7 @@ def main():
args = parse_args() args = parse_args()
regions = setup() regions = setup()
if args.list: if args.list:
_list(regions) _list(regions, refresh_cache=args.refresh_cache)
elif args.host: elif args.host:
host(regions, args.host) host(regions, args.host)
sys.exit(0) sys.exit(0)
+10 -6
View File
@@ -28,6 +28,8 @@ take precedence over options present in the INI file. An INI file is not
required if these options are specified using environment variables. required if these options are specified using environment variables.
''' '''
from __future__ import print_function
import collections import collections
import json import json
import logging import logging
@@ -37,6 +39,8 @@ import sys
import time import time
import ConfigParser import ConfigParser
from six import text_type
# Disable logging message trigged by pSphere/suds. # Disable logging message trigged by pSphere/suds.
try: try:
from logging import NullHandler from logging import NullHandler
@@ -95,7 +99,7 @@ class VMwareInventory(object):
Saves the value to cache with the name given. Saves the value to cache with the name given.
''' '''
if self.config.has_option('defaults', 'cache_dir'): if self.config.has_option('defaults', 'cache_dir'):
cache_dir = self.config.get('defaults', 'cache_dir') cache_dir = os.path.expanduser(self.config.get('defaults', 'cache_dir'))
if not os.path.exists(cache_dir): if not os.path.exists(cache_dir):
os.makedirs(cache_dir) os.makedirs(cache_dir)
cache_file = os.path.join(cache_dir, name) cache_file = os.path.join(cache_dir, name)
@@ -115,7 +119,7 @@ class VMwareInventory(object):
else: else:
cache_max_age = 0 cache_max_age = 0
cache_stat = os.stat(cache_file) cache_stat = os.stat(cache_file)
if (cache_stat.st_mtime + cache_max_age) < time.time(): if (cache_stat.st_mtime + cache_max_age) >= time.time():
with open(cache_file) as cache: with open(cache_file) as cache:
return json.load(cache) return json.load(cache)
return default return default
@@ -147,7 +151,7 @@ class VMwareInventory(object):
seen = seen or set() seen = seen or set()
if isinstance(obj, ManagedObject): if isinstance(obj, ManagedObject):
try: try:
obj_unicode = unicode(getattr(obj, 'name')) obj_unicode = text_type(getattr(obj, 'name'))
except AttributeError: except AttributeError:
obj_unicode = () obj_unicode = ()
if obj in seen: if obj in seen:
@@ -164,7 +168,7 @@ class VMwareInventory(object):
obj_info = self._get_obj_info(val, depth - 1, seen) obj_info = self._get_obj_info(val, depth - 1, seen)
if obj_info != (): if obj_info != ():
d[attr] = obj_info d[attr] = obj_info
except Exception, e: except Exception as e:
pass pass
return d return d
elif isinstance(obj, SudsObject): elif isinstance(obj, SudsObject):
@@ -207,8 +211,8 @@ class VMwareInventory(object):
host_info[k] = v host_info[k] = v
try: try:
host_info['ipAddress'] = host.config.network.vnic[0].spec.ip.ipAddress host_info['ipAddress'] = host.config.network.vnic[0].spec.ip.ipAddress
except Exception, e: except Exception as e:
print >> sys.stderr, e print(e, file=sys.stderr)
host_info = self._flatten_dict(host_info, prefix) host_info = self._flatten_dict(host_info, prefix)
if ('%s_ipAddress' % prefix) in host_info: if ('%s_ipAddress' % prefix) in host_info:
host_info['ansible_ssh_host'] = host_info['%s_ipAddress' % prefix] host_info['ansible_ssh_host'] = host_info['%s_ipAddress' % prefix]
+15 -19
View File
@@ -51,7 +51,7 @@ try:
from azure import WindowsAzureError from azure import WindowsAzureError
from azure.servicemanagement import ServiceManagementService from azure.servicemanagement import ServiceManagementService
except ImportError as e: except ImportError as e:
print "failed=True msg='`azure` library required for this script'" print("failed=True msg='`azure` library required for this script'")
sys.exit(1) sys.exit(1)
@@ -70,8 +70,8 @@ class AzureInventory(object):
# Cache setting defaults. # Cache setting defaults.
# These can be overridden in settings (see `read_settings`). # These can be overridden in settings (see `read_settings`).
cache_dir = os.path.expanduser('~') cache_dir = os.path.expanduser('~')
self.cache_path_cache = '%s/.ansible-azure.cache' % cache_dir self.cache_path_cache = os.path.join(cache_dir, '.ansible-azure.cache')
self.cache_path_index = '%s/.ansible-azure.index' % cache_dir self.cache_path_index = os.path.join(cache_dir, '.ansible-azure.index')
self.cache_max_age = 0 self.cache_max_age = 0
# Read settings and parse CLI arguments # Read settings and parse CLI arguments
@@ -103,15 +103,13 @@ class AzureInventory(object):
# Add the `['_meta']['hostvars']` information. # Add the `['_meta']['hostvars']` information.
hostvars = {} hostvars = {}
if len(data) > 0: if len(data) > 0:
for host in set(reduce(lambda x, y: x + y, for host in set([h for hosts in data.values() for h in hosts if h]):
[i for i in data.values()])): hostvars[host] = self.get_host(host, jsonify=False)
if host is not None:
hostvars[host] = self.get_host(host, jsonify=False)
data['_meta'] = {'hostvars': hostvars} data['_meta'] = {'hostvars': hostvars}
# JSONify the data. # JSONify the data.
data_to_print = self.json_format_dict(data, pretty=True) data_to_print = self.json_format_dict(data, pretty=True)
print data_to_print print(data_to_print)
def get_host(self, hostname, jsonify=True): def get_host(self, hostname, jsonify=True):
"""Return information about the given hostname, based on what """Return information about the given hostname, based on what
@@ -153,9 +151,9 @@ class AzureInventory(object):
# Cache related # Cache related
if config.has_option('azure', 'cache_path'): if config.has_option('azure', 'cache_path'):
cache_path = os.path.expanduser(config.get('azure', 'cache_path')) cache_path = os.path.expandvars(os.path.expanduser(config.get('azure', 'cache_path')))
self.cache_path_cache = cache_path + '/ansible-azure.cache' self.cache_path_cache = os.path.join(cache_path, 'ansible-azure.cache')
self.cache_path_index = cache_path + '/ansible-azure.index' self.cache_path_index = os.path.join(cache_path, 'ansible-azure.index')
if config.has_option('azure', 'cache_max_age'): if config.has_option('azure', 'cache_max_age'):
self.cache_max_age = config.getint('azure', 'cache_max_age') self.cache_max_age = config.getint('azure', 'cache_max_age')
@@ -197,9 +195,9 @@ class AzureInventory(object):
for cloud_service in self.sms.list_hosted_services(): for cloud_service in self.sms.list_hosted_services():
self.add_deployments(cloud_service) self.add_deployments(cloud_service)
except WindowsAzureError as e: except WindowsAzureError as e:
print "Looks like Azure's API is down:" print("Looks like Azure's API is down:")
print print("")
print e print(e)
sys.exit(1) sys.exit(1)
def add_deployments(self, cloud_service): def add_deployments(self, cloud_service):
@@ -209,12 +207,10 @@ class AzureInventory(object):
try: try:
for deployment in self.sms.get_hosted_service_properties(cloud_service.service_name,embed_detail=True).deployments.deployments: for deployment in self.sms.get_hosted_service_properties(cloud_service.service_name,embed_detail=True).deployments.deployments:
self.add_deployment(cloud_service, deployment) self.add_deployment(cloud_service, deployment)
#if deployment.deployment_slot == "Production":
# self.add_deployment(cloud_service, deployment)
except WindowsAzureError as e: except WindowsAzureError as e:
print "Looks like Azure's API is down:" print("Looks like Azure's API is down:")
print print("")
print e print(e)
sys.exit(1) sys.exit(1)
def add_deployment(self, cloud_service, deployment): def add_deployment(self, cloud_service, deployment):
+126 -7
View File
@@ -2,6 +2,7 @@
# All Rights Reserved. # All Rights Reserved.
import os import os
import re # noqa
import sys import sys
import djcelery import djcelery
from datetime import timedelta from datetime import timedelta
@@ -63,7 +64,7 @@ USE_L10N = True
USE_TZ = True USE_TZ = True
STATICFILES_DIRS = ( STATICFILES_DIRS = (
os.path.join(BASE_DIR, 'ui', 'dist'), os.path.join(BASE_DIR, 'ui', 'static'),
os.path.join(BASE_DIR, 'static'), os.path.join(BASE_DIR, 'static'),
) )
@@ -118,16 +119,32 @@ REMOTE_HOST_HEADERS = ['REMOTE_ADDR', 'REMOTE_HOST']
STDOUT_MAX_BYTES_DISPLAY = 1048576 STDOUT_MAX_BYTES_DISPLAY = 1048576
TEMPLATE_CONTEXT_PROCESSORS += ( # NOQA TEMPLATE_CONTEXT_PROCESSORS = ( # NOQA
'django.contrib.auth.context_processors.auth',
'django.core.context_processors.debug',
'django.core.context_processors.i18n',
'django.core.context_processors.media',
'django.core.context_processors.static',
'django.core.context_processors.tz',
'django.contrib.messages.context_processors.messages',
'django.core.context_processors.request', 'django.core.context_processors.request',
'awx.ui.context_processors.settings', 'awx.ui.context_processors.settings',
'awx.ui.context_processors.version', 'awx.ui.context_processors.version',
'social.apps.django_app.context_processors.backends',
'social.apps.django_app.context_processors.login_redirect',
) )
MIDDLEWARE_CLASSES += ( # NOQA MIDDLEWARE_CLASSES = ( # NOQA
'django.middleware.common.CommonMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'awx.main.middleware.HAMiddleware', 'awx.main.middleware.HAMiddleware',
'awx.main.middleware.ActivityStreamMiddleware', 'awx.main.middleware.ActivityStreamMiddleware',
'awx.sso.middleware.SocialAuthMiddleware',
'crum.CurrentRequestUserMiddleware', 'crum.CurrentRequestUserMiddleware',
'awx.main.middleware.AuthTokenTimeoutMiddleware',
) )
TEMPLATE_DIRS = ( TEMPLATE_DIRS = (
@@ -159,10 +176,12 @@ INSTALLED_APPS = (
'kombu.transport.django', 'kombu.transport.django',
'polymorphic', 'polymorphic',
'taggit', 'taggit',
'social.apps.django_app.default',
'awx.main', 'awx.main',
'awx.api', 'awx.api',
'awx.ui', 'awx.ui',
'awx.fact', 'awx.fact',
'awx.sso',
) )
INTERNAL_IPS = ('127.0.0.1',) INTERNAL_IPS = ('127.0.0.1',)
@@ -199,16 +218,35 @@ REST_FRAMEWORK = {
} }
AUTHENTICATION_BACKENDS = ( AUTHENTICATION_BACKENDS = (
'awx.main.backend.LDAPBackend', 'awx.sso.backends.LDAPBackend',
'awx.sso.backends.RADIUSBackend',
'social.backends.google.GoogleOAuth2',
'social.backends.github.GithubOAuth2',
'social.backends.github.GithubOrganizationOAuth2',
'social.backends.github.GithubTeamOAuth2',
'awx.sso.backends.SAMLAuth',
'django.contrib.auth.backends.ModelBackend', 'django.contrib.auth.backends.ModelBackend',
) )
# LDAP server (default to None to skip using LDAP authentication). # LDAP server (default to None to skip using LDAP authentication).
AUTH_LDAP_SERVER_URI = None AUTH_LDAP_SERVER_URI = None
# Radius server settings (default to empty string to skip using Radius auth).
RADIUS_SERVER = ''
RADIUS_PORT = 1812
RADIUS_SECRET = ''
# Seconds before auth tokens expire. # Seconds before auth tokens expire.
AUTH_TOKEN_EXPIRATION = 1800 AUTH_TOKEN_EXPIRATION = 1800
# Maximum number of per-user valid, concurrent tokens.
# -1 is unlimited
AUTH_TOKEN_PER_USER = -1
# Enable / Disable HTTP Basic Authentication used in the API browser
# Note: Session limits are not enforced when using HTTP Basic Authentication.
AUTH_BASIC_ENABLED = True
# If set, serve only minified JS for UI. # If set, serve only minified JS for UI.
USE_MINIFIED_JS = False USE_MINIFIED_JS = False
@@ -307,6 +345,70 @@ CELERYBEAT_SCHEDULE = {
}, },
} }
# Social Auth configuration.
SOCIAL_AUTH_STRATEGY = 'social.strategies.django_strategy.DjangoStrategy'
SOCIAL_AUTH_STORAGE = 'social.apps.django_app.default.models.DjangoStorage'
SOCIAL_AUTH_USER_MODEL = AUTH_USER_MODEL # noqa
SOCIAL_AUTH_PIPELINE = (
'social.pipeline.social_auth.social_details',
'social.pipeline.social_auth.social_uid',
'social.pipeline.social_auth.auth_allowed',
'social.pipeline.social_auth.social_user',
'social.pipeline.user.get_username',
'social.pipeline.social_auth.associate_by_email',
'social.pipeline.user.create_user',
'awx.sso.pipeline.check_user_found_or_created',
'social.pipeline.social_auth.associate_user',
'social.pipeline.social_auth.load_extra_data',
'awx.sso.pipeline.set_is_active_for_new_user',
'social.pipeline.user.user_details',
'awx.sso.pipeline.prevent_inactive_login',
'awx.sso.pipeline.update_user_orgs',
'awx.sso.pipeline.update_user_teams',
)
SOCIAL_AUTH_GOOGLE_OAUTH2_KEY = ''
SOCIAL_AUTH_GOOGLE_OAUTH2_SECRET = ''
SOCIAL_AUTH_GOOGLE_OAUTH2_SCOPE = ['profile']
SOCIAL_AUTH_GITHUB_KEY = ''
SOCIAL_AUTH_GITHUB_SECRET = ''
SOCIAL_AUTH_GITHUB_SCOPE = ['user:email', 'read:org']
SOCIAL_AUTH_GITHUB_ORG_KEY = ''
SOCIAL_AUTH_GITHUB_ORG_SECRET = ''
SOCIAL_AUTH_GITHUB_ORG_NAME = ''
SOCIAL_AUTH_GITHUB_ORG_SCOPE = ['user:email', 'read:org']
SOCIAL_AUTH_GITHUB_TEAM_KEY = ''
SOCIAL_AUTH_GITHUB_TEAM_SECRET = ''
SOCIAL_AUTH_GITHUB_TEAM_ID = ''
SOCIAL_AUTH_GITHUB_TEAM_SCOPE = ['user:email', 'read:org']
SOCIAL_AUTH_SAML_SP_ENTITY_ID = ''
SOCIAL_AUTH_SAML_SP_PUBLIC_CERT = ''
SOCIAL_AUTH_SAML_SP_PRIVATE_KEY = ''
SOCIAL_AUTH_SAML_ORG_INFO = {}
SOCIAL_AUTH_SAML_TECHNICAL_CONTACT = {}
SOCIAL_AUTH_SAML_SUPPORT_CONTACT = {}
SOCIAL_AUTH_SAML_ENABLED_IDPS = {}
SOCIAL_AUTH_LOGIN_URL = '/'
SOCIAL_AUTH_LOGIN_REDIRECT_URL = '/sso/complete/'
SOCIAL_AUTH_LOGIN_ERROR_URL = '/sso/error/'
SOCIAL_AUTH_INACTIVE_USER_URL = '/sso/inactive/'
SOCIAL_AUTH_RAISE_EXCEPTIONS = False
SOCIAL_AUTH_USERNAME_IS_FULL_EMAIL = False
SOCIAL_AUTH_SLUGIFY_USERNAMES = True
SOCIAL_AUTH_CLEAN_USERNAMES = True
SOCIAL_AUTH_SANITIZE_REDIRECTS = True
SOCIAL_AUTH_REDIRECT_IS_HTTPS = False
SOCIAL_AUTH_ORGANIZATION_MAP = {}
SOCIAL_AUTH_TEAM_MAP = {}
# Any ANSIBLE_* settings will be passed to the subprocess environment by the # Any ANSIBLE_* settings will be passed to the subprocess environment by the
# celery task. # celery task.
@@ -355,6 +457,12 @@ AWX_JOB_TEMPLATE_HISTORY = 10
# The directory in which proot will create new temporary directories for its root # The directory in which proot will create new temporary directories for its root
AWX_PROOT_BASE_PATH = "/tmp" AWX_PROOT_BASE_PATH = "/tmp"
# User definable ansible callback plugins
AWX_ANSIBLE_CALLBACK_PLUGINS = ""
# Enable Pendo on the UI, possible values are 'off', 'anonymous', and 'detailed'
PENDO_TRACKING_STATE = "off"
# Default list of modules allowed for ad hoc commands. # Default list of modules allowed for ad hoc commands.
AD_HOC_COMMANDS = [ AD_HOC_COMMANDS = [
'command', 'command',
@@ -473,13 +581,19 @@ VMWARE_EXCLUDE_EMPTY_GROUPS = True
# provide a list here. # provide a list here.
# Source: https://developers.google.com/compute/docs/zones # Source: https://developers.google.com/compute/docs/zones
GCE_REGION_CHOICES = [ GCE_REGION_CHOICES = [
('us-east1-b', 'US East (B)'),
('us-east1-c', 'US East (C)'),
('us-east1-d', 'US East (D)'),
('us-central1-a', 'US Central (A)'), ('us-central1-a', 'US Central (A)'),
('us-central1-b', 'US Central (B)'), ('us-central1-b', 'US Central (B)'),
('us-central1-c', 'US Central (C)'),
('us-central1-f', 'US Central (F)'), ('us-central1-f', 'US Central (F)'),
('europe-west1-a', 'Europe West (A)'),
('europe-west1-b', 'Europe West (B)'), ('europe-west1-b', 'Europe West (B)'),
('europe-west1-c', 'Europe West (C)'),
('europe-west1-d', 'Europe West (D)'),
('asia-east1-a', 'Asia East (A)'), ('asia-east1-a', 'Asia East (A)'),
('asia-east1-b', 'Asia East (B)'), ('asia-east1-b', 'Asia East (B)'),
('asia-east1-c', 'Asia East (C)'),
] ]
GCE_REGIONS_BLACKLIST = [] GCE_REGIONS_BLACKLIST = []
@@ -616,7 +730,7 @@ LOGGING = {
'level': 'WARNING', 'level': 'WARNING',
'class':'logging.handlers.RotatingFileHandler', 'class':'logging.handlers.RotatingFileHandler',
'filters': ['require_debug_false'], 'filters': ['require_debug_false'],
'filename': os.path.join(LOG_ROOT, 'tower_warnings.log'), 'filename': os.path.join(LOG_ROOT, 'tower.log'),
'maxBytes': 1024 * 1024 * 5, # 5 MB 'maxBytes': 1024 * 1024 * 5, # 5 MB
'backupCount': 5, 'backupCount': 5,
'formatter':'simple', 'formatter':'simple',
@@ -708,7 +822,12 @@ LOGGING = {
'propagate': False, 'propagate': False,
}, },
'django_auth_ldap': { 'django_auth_ldap': {
'handlers': ['null'], 'handlers': ['console', 'file', 'tower_warnings'],
'level': 'DEBUG',
},
'social': {
'handlers': ['console', 'file', 'tower_warnings'],
'level': 'DEBUG',
}, },
} }
} }
+4 -4
View File
@@ -32,6 +32,8 @@ CALLBACK_QUEUE_PORT = "ipc:///tmp/callback_receiver_dev.ipc"
# Enable PROOT for tower-qa integration tests # Enable PROOT for tower-qa integration tests
AWX_PROOT_ENABLED = True AWX_PROOT_ENABLED = True
PENDO_TRACKING_STATE = "off"
# Use Django-Jenkins if installed. Only run tests for awx.main app. # Use Django-Jenkins if installed. Only run tests for awx.main app.
try: try:
import django_jenkins import django_jenkins
@@ -74,10 +76,8 @@ include(optional('/etc/tower/conf.d/*.py'), scope=locals())
# default settings for development. If not present, we can still run using # default settings for development. If not present, we can still run using
# only the defaults. # only the defaults.
try: try:
include( include(optional('local_*.py'), scope=locals())
optional('local_*.py'), include('postprocess.py', scope=locals())
scope=locals(),
)
except ImportError: except ImportError:
traceback.print_exc() traceback.print_exc()
sys.exit(1) sys.exit(1)
@@ -0,0 +1,629 @@
# Copyright (c) 2015 Ansible, Inc. (formerly AnsibleWorks, Inc.)
# All Rights Reserved.
# Local Django settings for AWX project. Rename to "local_settings.py" and
# edit as needed for your development environment.
# All variables defined in awx/settings/development.py will already be loaded
# into the global namespace before this file is loaded, to allow for reading
# and updating the default settings as needed.
###############################################################################
# MISC PROJECT SETTINGS
###############################################################################
ADMINS = (
# ('Your Name', 'your_email@domain.com'),
)
MANAGERS = ADMINS
# Database settings to use PostgreSQL for development.
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql_psycopg2',
'NAME': 'awx-dev',
'USER': 'awx-dev',
'PASSWORD': 'AWXsome1',
'HOST': 'postgres',
'PORT': '',
}
}
# Use SQLite for unit tests instead of PostgreSQL. If the lines below are
# commented out, Django will create the test_awx-dev database in PostgreSQL to
# run unit tests.
if len(sys.argv) >= 2 and sys.argv[1] == 'test':
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': os.path.join(BASE_DIR, 'awx.sqlite3'),
# Test database cannot be :memory: for celery/inventory tests.
'TEST_NAME': os.path.join(BASE_DIR, 'awx_test.sqlite3'),
}
}
MONGO_DB = 'system_tracking_test'
# Celery AMQP configuration.
BROKER_URL = 'redis://redis/'
# Mongo host configuration
MONGO_HOST = 'mongo'
# Set True to enable additional logging from the job_event_callback plugin
JOB_CALLBACK_DEBUG = False
# Absolute filesystem path to the directory to host projects (with playbooks).
# This directory should NOT be web-accessible.
PROJECTS_ROOT = os.path.join(BASE_DIR, 'projects')
# Absolute filesystem path to the directory for job status stdout
# This directory should not be web-accessible
JOBOUTPUT_ROOT = os.path.join(BASE_DIR, 'job_status')
# The UUID of the system, for HA.
SYSTEM_UUID = '00000000-0000-0000-0000-000000000000'
# Local time zone for this installation. Choices can be found here:
# http://en.wikipedia.org/wiki/List_of_tz_zones_by_name
# although not all choices may be available on all operating systems.
# On Unix systems, a value of None will cause Django to use the same
# timezone as the operating system.
# If running in a Windows environment this must be set to the same as your
# system time zone.
TIME_ZONE = 'America/New_York'
# Language code for this installation. All choices can be found here:
# http://www.i18nguy.com/unicode/language-identifiers.html
LANGUAGE_CODE = 'en-us'
# SECURITY WARNING: keep the secret key used in production secret!
# Hardcoded values can leak through source control. Consider loading
# the secret key from an environment variable or a file instead.
SECRET_KEY = 'p7z7g1ql4%6+(6nlebb6hdk7sd^&fnjpal308%n%+p^_e6vo1y'
# HTTP headers and meta keys to search to determine remote host name or IP. Add
# additional items to this list, such as "HTTP_X_FORWARDED_FOR", if behind a
# reverse proxy.
REMOTE_HOST_HEADERS = ['REMOTE_ADDR', 'REMOTE_HOST']
# Define additional environment variables to be passed to subprocess started by
# the celery task.
#AWX_TASK_ENV['FOO'] = 'BAR'
# If set, use -vvv for project updates instead of -v for more output.
# PROJECT_UPDATE_VVV=True
# Set verbosity for inventory import command when running inventory updates.
# INVENTORY_UPDATE_VERBOSITY=1
###############################################################################
# EMAIL SETTINGS
###############################################################################
# Email address that error messages come from.
SERVER_EMAIL = 'root@localhost'
# The email backend to use. For possible shortcuts see django.core.mail.
# The default is to use the SMTP backend.
# Third-party backends can be specified by providing a Python path
# to a module that defines an EmailBackend class.
EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
# Host for sending email.
EMAIL_HOST = 'localhost'
# Port for sending email.
EMAIL_PORT = 25
# Optional SMTP authentication information for EMAIL_HOST.
EMAIL_HOST_USER = ''
EMAIL_HOST_PASSWORD = ''
EMAIL_USE_TLS = False
# Default email address to use for various automated correspondence from
# the site managers.
DEFAULT_FROM_EMAIL = 'webmaster@localhost'
# Subject-line prefix for email messages send with django.core.mail.mail_admins
# or ...mail_managers. Make sure to include the trailing space.
EMAIL_SUBJECT_PREFIX = '[AWX] '
###############################################################################
# LOGGING SETTINGS
###############################################################################
# Enable logging to syslog. Setting level to ERROR captures 500 errors,
# WARNING also logs 4xx responses.
LOGGING['handlers']['syslog'] = {
'level': 'WARNING',
'filters': ['require_debug_false'],
'class': 'django.utils.log.NullHandler',
'formatter': 'simple',
}
# Enable the following lines to also log to a file.
#LOGGING['handlers']['file'] = {
# 'class': 'logging.FileHandler',
# 'filename': os.path.join(BASE_DIR, 'awx.log'),
# 'formatter': 'simple',
#}
# Enable the following lines to turn on lots of permissions-related logging.
#LOGGING['loggers']['awx.main.access']['propagate'] = True
#LOGGING['loggers']['awx.main.signals']['propagate'] = True
#LOGGING['loggers']['awx.main.permissions']['propagate'] = True
# Enable the following lines to turn on LDAP auth logging.
#LOGGING['loggers']['django_auth_ldap']['handlers'] = ['console']
#LOGGING['loggers']['django_auth_ldap']['level'] = 'DEBUG'
###############################################################################
# LDAP AUTHENTICATION SETTINGS
###############################################################################
# Refer to django-auth-ldap docs for more details:
# http://pythonhosted.org/django-auth-ldap/authentication.html
# LDAP server URI, such as "ldap://ldap.example.com:389" (non-SSL) or
# "ldaps://ldap.example.com:636" (SSL). LDAP authentication is disable if this
# parameter is empty.
AUTH_LDAP_SERVER_URI = ''
# DN of user to bind for all search queries. Normally in the format
# "CN=Some User,OU=Users,DC=example,DC=com" but may also be specified as
# "DOMAIN\username" for Active Directory.
AUTH_LDAP_BIND_DN = ''
# Password using to bind above user account.
AUTH_LDAP_BIND_PASSWORD = ''
# Enable TLS when the connection is not using SSL.
AUTH_LDAP_START_TLS = False
# Imports needed for remaining LDAP configuration.
import ldap
from django_auth_ldap.config import LDAPSearch, LDAPSearchUnion
from django_auth_ldap.config import ActiveDirectoryGroupType
# LDAP search query to find users.
AUTH_LDAP_USER_SEARCH = LDAPSearch(
'OU=Users,DC=example,DC=com', # Base DN
ldap.SCOPE_SUBTREE, # SCOPE_BASE, SCOPE_ONELEVEL, SCOPE_SUBTREE
'(sAMAccountName=%(user)s)', # Query
)
# Alternative to user search, if user DNs are all of the same format.
#AUTH_LDAP_USER_DN_TEMPLATE = 'uid=%(user)s,OU=Users,DC=example,DC=com'
# Mapping of LDAP to user atrributes (key is user attribute name, value is LDAP
# attribute name).
AUTH_LDAP_USER_ATTR_MAP = {
'first_name': 'givenName',
'last_name': 'sn',
'email': 'mail',
}
# LDAP search query to find groups. Does not support LDAPSearchUnion.
AUTH_LDAP_GROUP_SEARCH = LDAPSearch(
'DC=example,DC=com', # Base DN
ldap.SCOPE_SUBTREE, # SCOPE_BASE, SCOPE_ONELEVEL, SCOPE_SUBTREE
'(objectClass=group)', # Query
)
# Type of group returned by the search above. Should be one of the types
# listed at: http://pythonhosted.org/django-auth-ldap/groups.html#types-of-groups
AUTH_LDAP_GROUP_TYPE = ActiveDirectoryGroupType()
# Group DN required to login. If specified, user must be a member of this
# group to login via LDAP.
#AUTH_LDAP_REQUIRE_GROUP = ''
# Group DN denied from login. If specified, user will not be allowed to login
# if a member of this group.
#AUTH_LDAP_DENY_GROUP = ''
# User profile flags updated from group membership (key is user attribute name,
# value is group DN).
AUTH_LDAP_USER_FLAGS_BY_GROUP = {
#'is_superuser': 'CN=Domain Admins,CN=Users,DC=example,DC=com',
}
# Mapping between organization admins/users and LDAP groups. Keys are
# organization names (will be created if not present). Values are dictionaries
# of options for each organization's membership, where each can contain the
# following parameters:
# - remove: True/False. Defaults to False. Specifies the default for
# remove_admins or remove_users if those parameters aren't explicitly set.
# - admins: None, True/False, string or list/tuple of strings.
# If None, organization admins will not be updated.
# If True/False, all LDAP users will be added/removed as admins.
# If a string or list of strings, specifies the group DN(s). User will be
# added as an org admin if the user is a member of ANY of these groups.
# - remove_admins: True/False. Defaults to False. If True, a user who is not a
# member of the given groups will be removed from the organization's admins.
# - users: None, True/False, string or list/tuple of strings. Same rules apply
# as for admins.
# - remove_users: True/False. Defaults to False. If True, a user who is not a
# member of the given groups will be removed from the organization's users.
AUTH_LDAP_ORGANIZATION_MAP = {
#'Test Org': {
# 'admins': 'CN=Domain Admins,CN=Users,DC=example,DC=com',
# 'users': ['CN=Domain Users,CN=Users,DC=example,DC=com'],
#},
#'Test Org 2': {
# 'admins': ['CN=Administrators,CN=Builtin,DC=example,DC=com'],
# 'users': True,
#},
}
# Mapping between team members (users) and LDAP groups. Keys are team names
# (will be created if not present). Values are dictionaries of options for
# each team's membership, where each can contain the following parameters:
# - organization: string. The name of the organization to which the team
# belongs. The team will be created if the combination of organization and
# team name does not exist. The organization will first be created if it
# does not exist.
# - users: None, True/False, string or list/tuple of strings.
# If None, team members will not be updated.
# If True/False, all LDAP users will be added/removed as team members.
# If a string or list of strings, specifies the group DN(s). User will be
# added as a team member if the user is a member of ANY of these groups.
# - remove: True/False. Defaults to False. If True, a user who is not a member
# of the given groups will be removed from the team.
AUTH_LDAP_TEAM_MAP = {
'My Team': {
'organization': 'Test Org',
'users': ['CN=Domain Users,CN=Users,DC=example,DC=com'],
'remove': True,
},
'Other Team': {
'organization': 'Test Org 2',
'users': 'CN=Other Users,CN=Users,DC=example,DC=com',
'remove': False,
},
}
###############################################################################
# SCM TEST SETTINGS
###############################################################################
# Define these variables to enable more complete testing of project support for
# SCM updates. The test repositories listed do not have to contain any valid
# playbooks.
try:
path = os.path.expanduser(os.path.expandvars('~/.ssh/id_rsa'))
TEST_SSH_KEY_DATA = file(path, 'rb').read()
except IOError:
TEST_SSH_KEY_DATA = ''
TEST_GIT_USERNAME = ''
TEST_GIT_PASSWORD = ''
TEST_GIT_KEY_DATA = TEST_SSH_KEY_DATA
TEST_GIT_PUBLIC_HTTPS = 'https://github.com/ansible/ansible.github.com.git'
TEST_GIT_PRIVATE_HTTPS = 'https://github.com/ansible/product-docs.git'
TEST_GIT_PRIVATE_SSH = 'git@github.com:ansible/product-docs.git'
TEST_HG_USERNAME = ''
TEST_HG_PASSWORD = ''
TEST_HG_KEY_DATA = TEST_SSH_KEY_DATA
TEST_HG_PUBLIC_HTTPS = 'https://bitbucket.org/cchurch/django-hotrunner'
TEST_HG_PRIVATE_HTTPS = ''
TEST_HG_PRIVATE_SSH = ''
TEST_SVN_USERNAME = ''
TEST_SVN_PASSWORD = ''
TEST_SVN_PUBLIC_HTTPS = 'https://github.com/ansible/ansible.github.com'
TEST_SVN_PRIVATE_HTTPS = 'https://github.com/ansible/product-docs'
# To test repo access via SSH login to localhost.
import getpass
TEST_SSH_LOOPBACK_USERNAME = getpass.getuser()
TEST_SSH_LOOPBACK_PASSWORD = ''
###############################################################################
# LDAP TEST SETTINGS
###############################################################################
# LDAP connection and authentication settings for unit tests only. LDAP tests
# will be skipped if TEST_AUTH_LDAP_SERVER_URI is not configured.
TEST_AUTH_LDAP_SERVER_URI = ''
TEST_AUTH_LDAP_BIND_DN = ''
TEST_AUTH_LDAP_BIND_PASSWORD = ''
TEST_AUTH_LDAP_START_TLS = False
# LDAP username/password for testing authentication.
TEST_AUTH_LDAP_USERNAME = ''
TEST_AUTH_LDAP_PASSWORD = ''
# LDAP search query to find users.
TEST_AUTH_LDAP_USER_SEARCH = LDAPSearch(
'CN=Users,DC=example,DC=com',
ldap.SCOPE_SUBTREE,
'(sAMAccountName=%(user)s)',
)
# Alternative to user search.
#TEST_AUTH_LDAP_USER_DN_TEMPLATE = 'sAMAccountName=%(user)s,OU=Users,DC=example,DC=com'
# Mapping of LDAP attributes to user attributes.
TEST_AUTH_LDAP_USER_ATTR_MAP = {
'first_name': 'givenName',
'last_name': 'sn',
'email': 'mail',
}
# LDAP search query for finding groups.
TEST_AUTH_LDAP_GROUP_SEARCH = LDAPSearch(
'DC=example,DC=com',
ldap.SCOPE_SUBTREE,
'(objectClass=group)',
)
# Type of group returned by the search above.
TEST_AUTH_LDAP_GROUP_TYPE = ActiveDirectoryGroupType()
# Test DNs for a group required to login. User should be a member of the first
# group, but not a member of the second.
TEST_AUTH_LDAP_REQUIRE_GROUP = 'CN=Domain Admins,CN=Users,DC=example,DC=com'
TEST_AUTH_LDAP_REQUIRE_GROUP_FAIL = 'CN=Guest,CN=Users,DC=example,DC=com'
# Test DNs for a group denied from login. User should not be a member of the
# first group, but should be a member of the second.
TEST_AUTH_LDAP_DENY_GROUP = 'CN=Guest,CN=Users,DC=example,DC=com'
TEST_AUTH_LDAP_DENY_GROUP_FAIL = 'CN=Domain Admins,CN=Users,DC=example,DC=com'
# User profile flags updated from group membership. Test user should be a
# member of the group.
TEST_AUTH_LDAP_USER_FLAGS_BY_GROUP = {
'is_superuser': 'CN=Domain Admins,CN=Users,DC=example,DC=com',
}
# Test mapping between organization admins/users and LDAP groups.
TEST_AUTH_LDAP_ORGANIZATION_MAP = {
'Test Org': {
'admins': 'CN=Domain Admins,CN=Users,DC=example,DC=com',
'users': ['CN=Domain Users,CN=Users,DC=example,DC=com'],
},
'Test Org 2': {
'admins': ['CN=Administrators,CN=Builtin,DC=example,DC=com'],
'users': True,
},
}
# Expected results from organization mapping. After login, should user be an
# admin/user in the given organization?
TEST_AUTH_LDAP_ORGANIZATION_MAP_RESULT = {
'Test Org': {'admins': True, 'users': False},
'Test Org 2': {'admins': False, 'users': True},
}
# Second test mapping to test remove parameters.
TEST_AUTH_LDAP_ORGANIZATION_MAP_2 = {
'Test Org': {
'admins': 'CN=Domain Users,CN=Users,DC=example,DC=com',
'users': True,
'remove_admins': True,
'remove_users': False,
},
'Test Org 2': {
'admins': ['CN=Domain Admins,CN=Users,DC=example,DC=com',
'CN=Administrators,CN=Builtin,DC=example,DC=com'],
'users': False,
'remove': True,
},
}
# Expected results from second organization mapping.
TEST_AUTH_LDAP_ORGANIZATION_MAP_2_RESULT = {
'Test Org': {'admins': False, 'users': True},
'Test Org 2': {'admins': True, 'users': False},
}
# Test mapping between team users and LDAP groups.
TEST_AUTH_LDAP_TEAM_MAP = {
'Domain Users Team': {
'organization': 'Test Org',
'users': ['CN=Domain Users,CN=Users,DC=example,DC=com'],
'remove': False,
},
'Admins Team': {
'organization': 'Admins Org',
'users': 'CN=Domain Admins,CN=Users,DC=example,DC=com',
'remove': True,
},
'Everyone Team': {
'organization': 'Test Org 2',
'users': True,
},
}
# Expected results from team mapping. After login, should user be a member of
# the given team?
TEST_AUTH_LDAP_TEAM_MAP_RESULT = {
'Domain Users Team': {'users': False},
'Admins Team': {'users': True},
'Everyone Team': {'users': True},
}
# Second test mapping for teams to remove user.
TEST_AUTH_LDAP_TEAM_MAP_2 = {
'Domain Users Team': {
'organization': 'Test Org',
'users': ['CN=Domain Users,CN=Users,DC=example,DC=com'],
'remove': False,
},
'Admins Team': {
'organization': 'Admins Org',
'users': 'CN=Administrators,CN=Builtin,DC=example,DC=com',
'remove': True,
},
'Everyone Team': {
'organization': 'Test Org 2',
'users': False,
'remove': False,
},
}
# Expected results from second team mapping. After login, should user be a
# member of the given team?
TEST_AUTH_LDAP_TEAM_MAP_2_RESULT = {
'Domain Users Team': {'users': False},
'Admins Team': {'users': False},
'Everyone Team': {'users': True},
}
###############################################################################
# RADIUS AUTH SETTINGS
###############################################################################
RADIUS_SERVER = ''
RADIUS_PORT = 1812
RADIUS_SECRET = ''
###############################################################################
# SOCIAL AUTH SETTINGS
###############################################################################
SOCIAL_AUTH_GOOGLE_OAUTH2_KEY = ''
SOCIAL_AUTH_GOOGLE_OAUTH2_SECRET = ''
#SOCIAL_AUTH_GOOGLE_OAUTH2_SCOPE = ['profile']
#SOCIAL_AUTH_GOOGLE_OAUTH2_WHITELISTED_DOMAINS = ['example.com']
#SOCIAL_AUTH_GOOGLE_OAUTH2_AUTH_EXTRA_ARGUMENTS = {'hd': 'example.com'}
SOCIAL_AUTH_GITHUB_KEY = ''
SOCIAL_AUTH_GITHUB_SECRET = ''
SOCIAL_AUTH_GITHUB_ORG_KEY = ''
SOCIAL_AUTH_GITHUB_ORG_SECRET = ''
SOCIAL_AUTH_GITHUB_ORG_NAME = ''
SOCIAL_AUTH_GITHUB_TEAM_KEY = ''
SOCIAL_AUTH_GITHUB_TEAM_SECRET = ''
SOCIAL_AUTH_GITHUB_TEAM_ID = ''
SOCIAL_AUTH_SAML_SP_ENTITY_ID = ''
SOCIAL_AUTH_SAML_SP_PUBLIC_CERT = ''
SOCIAL_AUTH_SAML_SP_PRIVATE_KEY = ''
SOCIAL_AUTH_SAML_ORG_INFO = {
'en-US': {
'name': 'example',
'displayname': 'Example',
'url': 'http://www.example.com',
},
}
SOCIAL_AUTH_SAML_TECHNICAL_CONTACT = {
'givenName': 'Some User',
'emailAddress': 'suser@example.com',
}
SOCIAL_AUTH_SAML_SUPPORT_CONTACT = {
'givenName': 'Some User',
'emailAddress': 'suser@example.com',
}
SOCIAL_AUTH_SAML_ENABLED_IDPS = {
#'myidp': {
# 'entity_id': 'https://idp.example.com',
# 'url': 'https://myidp.example.com/sso',
# 'x509cert': '',
#},
#'onelogin': {
# 'entity_id': 'https://app.onelogin.com/saml/metadata/123456',
# 'url': 'https://example.onelogin.com/trust/saml2/http-post/sso/123456',
# 'x509cert': '',
# 'attr_user_permanent_id': 'name_id',
# 'attr_first_name': 'User.FirstName',
# 'attr_last_name': 'User.LastName',
# 'attr_username': 'User.email',
# 'attr_email': 'User.email',
#},
}
SOCIAL_AUTH_ORGANIZATION_MAP = {
# Add all users to the default organization.
'Default': {
'users': True,
},
#'Test Org': {
# 'admins': ['admin@example.com'],
# 'users': True,
#},
#'Test Org 2': {
# 'admins': ['admin@example.com', re.compile(r'^tower-[^@]+*?@.*$],
# 'users': re.compile(r'^[^@].*?@example\.com$'),
#},
}
#SOCIAL_AUTH_GOOGLE_OAUTH2_ORGANIZATION_MAP = {}
#SOCIAL_AUTH_GITHUB_ORGANIZATION_MAP = {}
#SOCIAL_AUTH_GITHUB_ORG_ORGANIZATION_MAP = {}
#SOCIAL_AUTH_GITHUB_TEAM_ORGANIZATION_MAP = {}
#SOCIAL_AUTH_SAML_ORGANIZATION_MAP = {}
SOCIAL_AUTH_TEAM_MAP = {
#'My Team': {
# 'organization': 'Test Org',
# 'users': ['re.compile(r'^[^@]+?@test\.example\.com$')'],
# 'remove': True,
#},
#'Other Team': {
# 'organization': 'Test Org 2',
# 'users': re.compile(r'^[^@]+?@test2\.example\.com$'),
# 'remove': False,
#},
}
#SOCIAL_AUTH_GOOGLE_OAUTH2_TEAM_MAP = {}
#SOCIAL_AUTH_GITHUB_TEAM_MAP = {}
#SOCIAL_AUTH_GITHUB_ORG_TEAM_MAP = {}
#SOCIAL_AUTH_GITHUB_TEAM_TEAM_MAP = {}
#SOCIAL_AUTH_SAML_TEAM_MAP = {}
# Uncomment the line below (i.e. set SOCIAL_AUTH_USER_FIELDS to an empty list)
# to prevent new user accounts from being created. Only users who have
# previously logged in using social auth or have a user account with a matching
# email address will be able to login.
#SOCIAL_AUTH_USER_FIELDS = []
# It is also possible to add custom functions to the social auth pipeline for
# more advanced organization and team mapping. Use at your own risk.
#def custom_social_auth_pipeline_function(backend, details, user=None, *args, **kwargs):
# print 'custom:', backend, details, user, args, kwargs
#SOCIAL_AUTH_PIPELINE += (
# 'awx.settings.development.custom_social_auth_pipeline_function',
#)
###############################################################################
# INVENTORY IMPORT TEST SETTINGS
###############################################################################
# Define these variables to enable more complete testing of inventory import
# from cloud providers.
# EC2 credentials
TEST_AWS_ACCESS_KEY_ID = ''
TEST_AWS_SECRET_ACCESS_KEY = ''
TEST_AWS_REGIONS = 'all'
# Check IAM STS credentials
TEST_AWS_SECURITY_TOKEN = ''
# Rackspace credentials
TEST_RACKSPACE_USERNAME = ''
TEST_RACKSPACE_API_KEY = ''
TEST_RACKSPACE_REGIONS = 'all'
# VMware credentials
TEST_VMWARE_HOST = ''
TEST_VMWARE_USER = ''
TEST_VMWARE_PASSWORD = ''
# OpenStack credentials
TEST_OPENSTACK_HOST = ''
TEST_OPENSTACK_USER = ''
TEST_OPENSTACK_PASSWORD = ''
TEST_OPENSTACK_PROJECT = ''
# Azure credentials.
TEST_AZURE_USERNAME = ''
TEST_AZURE_KEY_DATA = ''
+125
View File
@@ -470,6 +470,128 @@ TEST_AUTH_LDAP_TEAM_MAP_2_RESULT = {
'Everyone Team': {'users': True}, 'Everyone Team': {'users': True},
} }
###############################################################################
# RADIUS AUTH SETTINGS
###############################################################################
RADIUS_SERVER = ''
RADIUS_PORT = 1812
RADIUS_SECRET = ''
###############################################################################
# SOCIAL AUTH SETTINGS
###############################################################################
SOCIAL_AUTH_GOOGLE_OAUTH2_KEY = ''
SOCIAL_AUTH_GOOGLE_OAUTH2_SECRET = ''
#SOCIAL_AUTH_GOOGLE_OAUTH2_SCOPE = ['profile']
#SOCIAL_AUTH_GOOGLE_OAUTH2_WHITELISTED_DOMAINS = ['example.com']
#SOCIAL_AUTH_GOOGLE_OAUTH2_AUTH_EXTRA_ARGUMENTS = {'hd': 'example.com'}
SOCIAL_AUTH_GITHUB_KEY = ''
SOCIAL_AUTH_GITHUB_SECRET = ''
SOCIAL_AUTH_GITHUB_ORG_KEY = ''
SOCIAL_AUTH_GITHUB_ORG_SECRET = ''
SOCIAL_AUTH_GITHUB_ORG_NAME = ''
SOCIAL_AUTH_GITHUB_TEAM_KEY = ''
SOCIAL_AUTH_GITHUB_TEAM_SECRET = ''
SOCIAL_AUTH_GITHUB_TEAM_ID = ''
SOCIAL_AUTH_SAML_SP_ENTITY_ID = ''
SOCIAL_AUTH_SAML_SP_PUBLIC_CERT = ''
SOCIAL_AUTH_SAML_SP_PRIVATE_KEY = ''
SOCIAL_AUTH_SAML_ORG_INFO = {
'en-US': {
'name': 'example',
'displayname': 'Example',
'url': 'http://www.example.com',
},
}
SOCIAL_AUTH_SAML_TECHNICAL_CONTACT = {
'givenName': 'Some User',
'emailAddress': 'suser@example.com',
}
SOCIAL_AUTH_SAML_SUPPORT_CONTACT = {
'givenName': 'Some User',
'emailAddress': 'suser@example.com',
}
SOCIAL_AUTH_SAML_ENABLED_IDPS = {
#'myidp': {
# 'entity_id': 'https://idp.example.com',
# 'url': 'https://myidp.example.com/sso',
# 'x509cert': '',
#},
#'onelogin': {
# 'entity_id': 'https://app.onelogin.com/saml/metadata/123456',
# 'url': 'https://example.onelogin.com/trust/saml2/http-post/sso/123456',
# 'x509cert': '',
# 'attr_user_permanent_id': 'name_id',
# 'attr_first_name': 'User.FirstName',
# 'attr_last_name': 'User.LastName',
# 'attr_username': 'User.email',
# 'attr_email': 'User.email',
#},
}
SOCIAL_AUTH_ORGANIZATION_MAP = {
# Add all users to the default organization.
'Default': {
'users': True,
},
#'Test Org': {
# 'admins': ['admin@example.com'],
# 'users': True,
#},
#'Test Org 2': {
# 'admins': ['admin@example.com', re.compile(r'^tower-[^@]+*?@.*$],
# 'users': re.compile(r'^[^@].*?@example\.com$'),
#},
}
#SOCIAL_AUTH_GOOGLE_OAUTH2_ORGANIZATION_MAP = {}
#SOCIAL_AUTH_GITHUB_ORGANIZATION_MAP = {}
#SOCIAL_AUTH_GITHUB_ORG_ORGANIZATION_MAP = {}
#SOCIAL_AUTH_GITHUB_TEAM_ORGANIZATION_MAP = {}
#SOCIAL_AUTH_SAML_ORGANIZATION_MAP = {}
SOCIAL_AUTH_TEAM_MAP = {
#'My Team': {
# 'organization': 'Test Org',
# 'users': ['re.compile(r'^[^@]+?@test\.example\.com$')'],
# 'remove': True,
#},
#'Other Team': {
# 'organization': 'Test Org 2',
# 'users': re.compile(r'^[^@]+?@test2\.example\.com$'),
# 'remove': False,
#},
}
#SOCIAL_AUTH_GOOGLE_OAUTH2_TEAM_MAP = {}
#SOCIAL_AUTH_GITHUB_TEAM_MAP = {}
#SOCIAL_AUTH_GITHUB_ORG_TEAM_MAP = {}
#SOCIAL_AUTH_GITHUB_TEAM_TEAM_MAP = {}
#SOCIAL_AUTH_SAML_TEAM_MAP = {}
# Uncomment the line below (i.e. set SOCIAL_AUTH_USER_FIELDS to an empty list)
# to prevent new user accounts from being created. Only users who have
# previously logged in using social auth or have a user account with a matching
# email address will be able to login.
#SOCIAL_AUTH_USER_FIELDS = []
# It is also possible to add custom functions to the social auth pipeline for
# more advanced organization and team mapping. Use at your own risk.
#def custom_social_auth_pipeline_function(backend, details, user=None, *args, **kwargs):
# print 'custom:', backend, details, user, args, kwargs
#SOCIAL_AUTH_PIPELINE += (
# 'awx.settings.development.custom_social_auth_pipeline_function',
#)
############################################################################### ###############################################################################
# INVENTORY IMPORT TEST SETTINGS # INVENTORY IMPORT TEST SETTINGS
############################################################################### ###############################################################################
@@ -481,6 +603,9 @@ TEST_AUTH_LDAP_TEAM_MAP_2_RESULT = {
TEST_AWS_ACCESS_KEY_ID = '' TEST_AWS_ACCESS_KEY_ID = ''
TEST_AWS_SECRET_ACCESS_KEY = '' TEST_AWS_SECRET_ACCESS_KEY = ''
TEST_AWS_REGIONS = 'all' TEST_AWS_REGIONS = 'all'
# Check IAM STS credentials
TEST_AWS_SECURITY_TOKEN = ''
# Rackspace credentials # Rackspace credentials
TEST_RACKSPACE_USERNAME = '' TEST_RACKSPACE_USERNAME = ''
+34
View File
@@ -0,0 +1,34 @@
# Copyright (c) 2015 Ansible, Inc.
# All Rights Reserved.
# flake8: noqa
# Runs after all configuration files have been loaded to fix/check/update
# settings as needed.
if not AUTH_LDAP_SERVER_URI:
AUTHENTICATION_BACKENDS = [x for x in AUTHENTICATION_BACKENDS if x != 'awx.sso.backends.LDAPBackend']
if not RADIUS_SERVER:
AUTHENTICATION_BACKENDS = [x for x in AUTHENTICATION_BACKENDS if x != 'awx.sso.backends.RADIUSBackend']
if not all([SOCIAL_AUTH_GOOGLE_OAUTH2_KEY, SOCIAL_AUTH_GOOGLE_OAUTH2_SECRET]):
AUTHENTICATION_BACKENDS = [x for x in AUTHENTICATION_BACKENDS if x != 'social.backends.google.GoogleOAuth2']
if not all([SOCIAL_AUTH_GITHUB_KEY, SOCIAL_AUTH_GITHUB_SECRET]):
AUTHENTICATION_BACKENDS = [x for x in AUTHENTICATION_BACKENDS if x != 'social.backends.github.GithubOAuth2']
if not all([SOCIAL_AUTH_GITHUB_ORG_KEY, SOCIAL_AUTH_GITHUB_ORG_SECRET, SOCIAL_AUTH_GITHUB_ORG_NAME]):
AUTHENTICATION_BACKENDS = [x for x in AUTHENTICATION_BACKENDS if x != 'social.backends.github.GithubOrganizationOAuth2']
if not all([SOCIAL_AUTH_GITHUB_TEAM_KEY, SOCIAL_AUTH_GITHUB_TEAM_SECRET, SOCIAL_AUTH_GITHUB_TEAM_ID]):
AUTHENTICATION_BACKENDS = [x for x in AUTHENTICATION_BACKENDS if x != 'social.backends.github.GithubTeamOAuth2']
if not all([SOCIAL_AUTH_SAML_SP_ENTITY_ID, SOCIAL_AUTH_SAML_SP_PUBLIC_CERT,
SOCIAL_AUTH_SAML_SP_PRIVATE_KEY, SOCIAL_AUTH_SAML_ORG_INFO,
SOCIAL_AUTH_SAML_TECHNICAL_CONTACT, SOCIAL_AUTH_SAML_SUPPORT_CONTACT,
SOCIAL_AUTH_SAML_ENABLED_IDPS]):
AUTHENTICATION_BACKENDS = [x for x in AUTHENTICATION_BACKENDS if x != 'awx.sso.backends.SAMLAuth']
if not AUTH_BASIC_ENABLED:
REST_FRAMEWORK['DEFAULT_AUTHENTICATION_CLASSES'] = [x for x in REST_FRAMEWORK['DEFAULT_AUTHENTICATION_CLASSES'] if x != 'rest_framework.authentication.BasicAuthentication']
+2 -5
View File
@@ -108,11 +108,8 @@ settings_file = os.environ.get('AWX_SETTINGS_FILE',
# Attempt to load settings from /etc/tower/settings.py first, followed by # Attempt to load settings from /etc/tower/settings.py first, followed by
# /etc/tower/conf.d/*.py. # /etc/tower/conf.d/*.py.
try: try:
include( include(settings_file, optional(settings_files), scope=locals())
settings_file, include('postprocess.py', scope=locals())
optional(settings_files),
scope=locals(),
)
except ImportError: except ImportError:
traceback.print_exc() traceback.print_exc()
sys.exit(1) sys.exit(1)
+2
View File
@@ -0,0 +1,2 @@
# Copyright (c) 2015 Ansible, Inc.
# All Rights Reserved.
+232
View File
@@ -0,0 +1,232 @@
# Copyright (c) 2015 Ansible, Inc.
# All Rights Reserved.
# Python
import logging
# Django
from django.dispatch import receiver
from django.conf import settings as django_settings
# django-auth-ldap
from django_auth_ldap.backend import LDAPSettings as BaseLDAPSettings
from django_auth_ldap.backend import LDAPBackend as BaseLDAPBackend
from django_auth_ldap.backend import populate_user
# radiusauth
from radiusauth.backends import RADIUSBackend as BaseRADIUSBackend
# social
from social.backends.saml import OID_USERID
from social.backends.saml import SAMLAuth as BaseSAMLAuth
from social.backends.saml import SAMLIdentityProvider as BaseSAMLIdentityProvider
# Ansible Tower
from awx.api.license import feature_enabled
logger = logging.getLogger('awx.sso.backends')
class LDAPSettings(BaseLDAPSettings):
defaults = dict(BaseLDAPSettings.defaults.items() + {
'ORGANIZATION_MAP': {},
'TEAM_MAP': {},
}.items())
class LDAPBackend(BaseLDAPBackend):
'''
Custom LDAP backend for AWX.
'''
settings_prefix = 'AUTH_LDAP_'
def _get_settings(self):
if self._settings is None:
self._settings = LDAPSettings(self.settings_prefix)
return self._settings
def _set_settings(self, settings):
self._settings = settings
settings = property(_get_settings, _set_settings)
def authenticate(self, username, password):
if not self.settings.SERVER_URI:
return None
if not feature_enabled('ldap'):
logger.error("Unable to authenticate, license does not support LDAP authentication")
return None
return super(LDAPBackend, self).authenticate(username, password)
def get_user(self, user_id):
if not self.settings.SERVER_URI:
return None
if not feature_enabled('ldap'):
logger.error("Unable to get_user, license does not support LDAP authentication")
return None
return super(LDAPBackend, self).get_user(user_id)
# Disable any LDAP based authorization / permissions checking.
def has_perm(self, user, perm, obj=None):
return False
def has_module_perms(self, user, app_label):
return False
def get_all_permissions(self, user, obj=None):
return set()
def get_group_permissions(self, user, obj=None):
return set()
class RADIUSBackend(BaseRADIUSBackend):
'''
Custom Radius backend to verify license status
'''
def authenticate(self, username, password):
if not django_settings.RADIUS_SERVER:
return None
if not feature_enabled('enterprise_auth'):
logger.error("Unable to authenticate, license does not support RADIUS authentication")
return None
return super(RADIUSBackend, self).authenticate(username, password)
def get_user(self, user_id):
if not django_settings.RADIUS_SERVER:
return None
if not feature_enabled('enterprise_auth'):
logger.error("Unable to get_user, license does not support RADIUS authentication")
return None
return super(RADIUSBackend, self).get_user(user_id)
class TowerSAMLIdentityProvider(BaseSAMLIdentityProvider):
'''
Custom Identity Provider to make attributes to what we expect.
'''
def get_user_permanent_id(self, attributes):
uid = attributes[self.conf.get('attr_user_permanent_id', OID_USERID)]
if isinstance(uid, basestring):
return uid
return uid[0]
def get_attr(self, attributes, conf_key, default_attribute):
"""
Get the attribute 'default_attribute' out of the attributes,
unless self.conf[conf_key] overrides the default by specifying
another attribute to use.
"""
key = self.conf.get(conf_key, default_attribute)
value = attributes[key][0] if key in attributes else None
if conf_key in ('attr_first_name', 'attr_last_name', 'attr_username', 'attr_email') and value is None:
logger.warn("Could not map user detail '%s' from SAML attribute '%s'; "
"update SOCIAL_AUTH_SAML_ENABLED_IDPS['%s']['%s'] with the correct SAML attribute.",
conf_key[5:], key, self.name, conf_key)
return unicode(value) if value is not None else value
class SAMLAuth(BaseSAMLAuth):
'''
Custom SAMLAuth backend to verify license status
'''
def get_idp(self, idp_name):
idp_config = self.setting('ENABLED_IDPS')[idp_name]
return TowerSAMLIdentityProvider(idp_name, **idp_config)
def authenticate(self, *args, **kwargs):
if not all([django_settings.SOCIAL_AUTH_SAML_SP_ENTITY_ID, django_settings.SOCIAL_AUTH_SAML_SP_PUBLIC_CERT,
django_settings.SOCIAL_AUTH_SAML_SP_PRIVATE_KEY, django_settings.SOCIAL_AUTH_SAML_ORG_INFO,
django_settings.SOCIAL_AUTH_SAML_TECHNICAL_CONTACT, django_settings.SOCIAL_AUTH_SAML_SUPPORT_CONTACT,
django_settings.SOCIAL_AUTH_SAML_ENABLED_IDPS]):
return None
if not feature_enabled('enterprise_auth'):
logger.error("Unable to authenticate, license does not support SAML authentication")
return None
return super(SAMLAuth, self).authenticate(*args, **kwargs)
def get_user(self, user_id):
if not all([django_settings.SOCIAL_AUTH_SAML_SP_ENTITY_ID, django_settings.SOCIAL_AUTH_SAML_SP_PUBLIC_CERT,
django_settings.SOCIAL_AUTH_SAML_SP_PRIVATE_KEY, django_settings.SOCIAL_AUTH_SAML_ORG_INFO,
django_settings.SOCIAL_AUTH_SAML_TECHNICAL_CONTACT, django_settings.SOCIAL_AUTH_SAML_SUPPORT_CONTACT,
django_settings.SOCIAL_AUTH_SAML_ENABLED_IDPS]):
return None
if not feature_enabled('enterprise_auth'):
logger.error("Unable to get_user, license does not support SAML authentication")
return None
return super(SAMLAuth, self).get_user(user_id)
def _update_m2m_from_groups(user, ldap_user, rel, opts, remove=False):
'''
Hepler function to update m2m relationship based on LDAP group membership.
'''
should_add = False
if opts is None:
return
elif not opts:
pass
elif opts is True:
should_add = True
else:
if isinstance(opts, basestring):
opts = [opts]
for group_dn in opts:
if not isinstance(group_dn, basestring):
continue
if ldap_user._get_groups().is_member_of(group_dn):
should_add = True
if should_add:
rel.add(user)
elif remove:
rel.remove(user)
@receiver(populate_user)
def on_populate_user(sender, **kwargs):
'''
Handle signal from LDAP backend to populate the user object. Update user
organization/team memberships according to their LDAP groups.
'''
from awx.main.models import Organization, Team
user = kwargs['user']
ldap_user = kwargs['ldap_user']
backend = ldap_user.backend
# Update organization membership based on group memberships.
org_map = getattr(backend.settings, 'ORGANIZATION_MAP', {})
for org_name, org_opts in org_map.items():
org, created = Organization.objects.get_or_create(name=org_name)
remove = bool(org_opts.get('remove', False))
admins_opts = org_opts.get('admins', None)
remove_admins = bool(org_opts.get('remove_admins', remove))
_update_m2m_from_groups(user, ldap_user, org.admins, admins_opts,
remove_admins)
users_opts = org_opts.get('users', None)
remove_users = bool(org_opts.get('remove_users', remove))
_update_m2m_from_groups(user, ldap_user, org.users, users_opts,
remove_users)
# Update team membership based on group memberships.
team_map = getattr(backend.settings, 'TEAM_MAP', {})
for team_name, team_opts in team_map.items():
if 'organization' not in team_opts:
continue
org, created = Organization.objects.get_or_create(name=team_opts['organization'])
team, created = Team.objects.get_or_create(name=team_name, organization=org)
users_opts = team_opts.get('users', None)
remove = bool(team_opts.get('remove', False))
_update_m2m_from_groups(user, ldap_user, team.users, users_opts,
remove)
# Update user profile to store LDAP DN.
profile = user.profile
if profile.ldap_dn != ldap_user.dn:
profile.ldap_dn = ldap_user.dn
profile.save()
+91
View File
@@ -0,0 +1,91 @@
# Copyright (c) 2015 Ansible, Inc.
# All Rights Reserved.
# Python
import urllib
# Six
import six
# Django
from django.contrib.auth import login, logout
from django.shortcuts import redirect
from django.utils.timezone import now
# Python Social Auth
from social.exceptions import SocialAuthBaseException
from social.utils import social_logger
from social.apps.django_app.middleware import SocialAuthExceptionMiddleware
# Ansible Tower
from awx.main.models import AuthToken
class SocialAuthMiddleware(SocialAuthExceptionMiddleware):
def process_request(self, request):
token_key = request.COOKIES.get('token', '')
token_key = urllib.quote(urllib.unquote(token_key).strip('"'))
if not hasattr(request, 'successful_authenticator'):
request.successful_authenticator = None
if not request.path.startswith('/sso/'):
# If token isn't present but we still have a user logged in via Django
# sessions, log them out.
if not token_key and request.user and request.user.is_authenticated():
logout(request)
# If a token is present, make sure it matches a valid one in the
# database, and log the user via Django session if necessary.
# Otherwise, log the user out via Django sessions.
elif token_key:
try:
auth_token = AuthToken.objects.filter(key=token_key, expires__gt=now())[0]
except IndexError:
auth_token = None
if not auth_token and request.user and request.user.is_authenticated():
logout(request)
elif auth_token and request.user != auth_token.user:
logout(request)
auth_token.user.backend = ''
login(request, auth_token.user)
auth_token.refresh()
if auth_token and request.user and request.user.is_authenticated():
request.session.pop('social_auth_error', None)
def process_exception(self, request, exception):
strategy = getattr(request, 'social_strategy', None)
if strategy is None or self.raise_exception(request, exception):
return
if isinstance(exception, SocialAuthBaseException) or request.path.startswith('/sso/'):
backend = getattr(request, 'backend', None)
backend_name = getattr(backend, 'name', 'unknown-backend')
full_backend_name = backend_name
try:
idp_name = strategy.request_data()['RelayState']
full_backend_name = '%s:%s' % (backend_name, idp_name)
except KeyError:
pass
message = self.get_message(request, exception)
social_logger.error(message)
url = self.get_redirect_uri(request, exception)
request.session['social_auth_error'] = (full_backend_name, message)
return redirect(url)
def get_message(self, request, exception):
msg = six.text_type(exception)
if msg and msg[-1] not in '.?!':
msg = msg + '.'
return msg
def get_redirect_uri(self, request, exception):
strategy = getattr(request, 'social_strategy', None)
return strategy.session_get('next', '') or strategy.setting('LOGIN_ERROR_URL')
+2
View File
@@ -0,0 +1,2 @@
# Copyright (c) 2015 Ansible, Inc.
# All Rights Reserved.
+137
View File
@@ -0,0 +1,137 @@
# Copyright (c) 2015 Ansible, Inc.
# All Rights Reserved.
# Python
import re
# Python Social Auth
from social.exceptions import AuthException
# Tower
from awx.api.license import feature_enabled
class AuthNotFound(AuthException):
def __init__(self, backend, email_or_uid, *args, **kwargs):
self.email_or_uid = email_or_uid
super(AuthNotFound, self).__init__(backend, *args, **kwargs)
def __str__(self):
return 'An account cannot be found for {0}'.format(self.email_or_uid)
class AuthInactive(AuthException):
def __str__(self):
return 'Your account is inactive'
def check_user_found_or_created(backend, details, user=None, *args, **kwargs):
if not user:
email_or_uid = details.get('email') or kwargs.get('email') or kwargs.get('uid') or '???'
raise AuthNotFound(backend, email_or_uid)
def set_is_active_for_new_user(strategy, details, user=None, *args, **kwargs):
if kwargs.get('is_new', False):
details['is_active'] = True
return {'details': details}
def prevent_inactive_login(backend, details, user=None, *args, **kwargs):
if user and not user.is_active:
raise AuthInactive(backend)
def _update_m2m_from_expression(user, rel, expr, remove=False):
'''
Helper function to update m2m relationship based on user matching one or
more expressions.
'''
should_add = False
if expr is None:
return
elif not expr:
pass
elif expr is True:
should_add = True
else:
if isinstance(expr, (basestring, type(re.compile('')))):
expr = [expr]
for ex in expr:
if isinstance(ex, basestring):
if user.username == ex or user.email == ex:
should_add = True
elif isinstance(ex, type(re.compile(''))):
if ex.match(user.username) or ex.match(user.email):
should_add = True
if should_add:
rel.add(user)
elif remove:
rel.remove(user)
def update_user_orgs(backend, details, user=None, *args, **kwargs):
'''
Update organization memberships for the given user based on mapping rules
defined in settings.
'''
if not user:
return
from awx.main.models import Organization
multiple_orgs = feature_enabled('multiple_organizations')
org_map = backend.setting('ORGANIZATION_MAP') or {}
for org_name, org_opts in org_map.items():
# Get or create the org to update. If the license only allows for one
# org, always use the first active org, unless no org exists.
if multiple_orgs:
org = Organization.objects.get_or_create(name=org_name)[0]
else:
try:
org = Organization.objects.filter(active=True).order_by('pk')[0]
except IndexError:
continue
# Update org admins from expression(s).
remove = bool(org_opts.get('remove', False))
admins_expr = org_opts.get('admins', None)
remove_admins = bool(org_opts.get('remove_admins', remove))
_update_m2m_from_expression(user, org.admins, admins_expr, remove_admins)
# Update org users from expression(s).
users_expr = org_opts.get('users', None)
remove_users = bool(org_opts.get('remove_users', remove))
_update_m2m_from_expression(user, org.users, users_expr, remove_users)
def update_user_teams(backend, details, user=None, *args, **kwargs):
'''
Update team memberships for the given user based on mapping rules defined
in settings.
'''
if not user:
return
from awx.main.models import Organization, Team
multiple_orgs = feature_enabled('multiple_organizations')
team_map = backend.setting('TEAM_MAP') or {}
for team_name, team_opts in team_map.items():
# Get or create the org to update. If the license only allows for one
# org, always use the first active org, unless no org exists.
if multiple_orgs:
if 'organization' not in team_opts:
continue
org = Organization.objects.get_or_create(name=team_opts['organization'])[0]
else:
try:
org = Organization.objects.filter(active=True).order_by('pk')[0]
except IndexError:
continue
# Update team members from expression(s).
team = Team.objects.get_or_create(name=team_name, organization=org)[0]
users_expr = team_opts.get('users', None)
remove = bool(team_opts.get('remove', False))
_update_m2m_from_expression(user, team.users, users_expr, remove)
+13
View File
@@ -0,0 +1,13 @@
# Copyright (c) 2015 Ansible, Inc.
# All Rights Reserved.
# Django
from django.conf.urls import patterns, url
urlpatterns = patterns(
'awx.sso.views',
url(r'^complete/$', 'sso_complete', name='sso_complete'),
url(r'^error/$', 'sso_error', name='sso_error'),
url(r'^inactive/$', 'sso_inactive', name='sso_inactive'),
url(r'^metadata/saml/$', 'saml_metadata', name='saml_metadata'),
)
+84
View File
@@ -0,0 +1,84 @@
# Copyright (c) 2015 Ansible, Inc.
# All Rights Reserved.
# Python
import urllib
# Django
from django.core.urlresolvers import reverse
from django.http import HttpResponse
from django.utils.timezone import now, utc
from django.views.generic import View
from django.views.generic.base import RedirectView
# Django REST Framework
from rest_framework.renderers import JSONRenderer
# Ansible Tower
from awx.main.models import AuthToken
from awx.api.serializers import UserSerializer
class BaseRedirectView(RedirectView):
def get_redirect_url(self, *args, **kwargs):
last_path = self.request.COOKIES.get('lastPath', '')
last_path = urllib.quote(urllib.unquote(last_path).strip('"'))
url = reverse('ui:index')
if last_path:
return '%s#%s' % (url, last_path)
else:
return url
sso_error = BaseRedirectView.as_view()
sso_inactive = BaseRedirectView.as_view()
class CompleteView(BaseRedirectView):
def dispatch(self, request, *args, **kwargs):
response = super(CompleteView, self).dispatch(request, *args, **kwargs)
if self.request.user and self.request.user.is_authenticated():
request_hash = AuthToken.get_request_hash(self.request)
try:
token = AuthToken.objects.filter(user=request.user,
request_hash=request_hash,
expires__gt=now())[0]
token.refresh()
except IndexError:
token = AuthToken.objects.create(user=request.user,
request_hash=request_hash)
request.session['auth_token_key'] = token.key
token_key = urllib.quote('"%s"' % token.key)
response.set_cookie('token', token_key)
token_expires = token.expires.astimezone(utc).strftime('%Y-%m-%dT%H:%M:%S')
token_expires = '%s.%03dZ' % (token_expires, token.expires.microsecond / 1000)
token_expires = urllib.quote('"%s"' % token_expires)
response.set_cookie('token_expires', token_expires)
response.set_cookie('userLoggedIn', 'true')
current_user = UserSerializer(self.request.user)
current_user = JSONRenderer().render(current_user.data)
current_user = urllib.quote('%s' % current_user, '')
response.set_cookie('current_user', current_user)
return response
sso_complete = CompleteView.as_view()
class MetadataView(View):
def get(self, request, *args, **kwargs):
from social.apps.django_app.utils import load_backend, load_strategy
complete_url = reverse('social:complete', args=('saml', ))
saml_backend = load_backend(
load_strategy(request),
'saml',
redirect_uri=complete_url,
)
metadata, errors = saml_backend.generate_metadata_xml()
if not errors:
return HttpResponse(content=metadata, content_type='text/xml')
else:
return HttpResponse(content=str(errors), content_type='text/plain')
saml_metadata = MetadataView.as_view()

Before

Width:  |  Height:  |  Size: 5.3 KiB

After

Width:  |  Height:  |  Size: 5.3 KiB

Before

Width:  |  Height:  |  Size: 2.0 KiB

After

Width:  |  Height:  |  Size: 2.0 KiB

Before

Width:  |  Height:  |  Size: 3.0 KiB

After

Width:  |  Height:  |  Size: 3.0 KiB

+3 -3
View File
@@ -5,7 +5,7 @@
{% block style %} {% block style %}
{{ block.super }} {{ block.super }}
<link href="{{ STATIC_URL }}img/favicon.ico" rel="shortcut icon" /> <link href="{{ STATIC_URL }}assets/favicon.ico" rel="shortcut icon" />
<style type="text/css"> <style type="text/css">
html body { html body {
background: #ddd; background: #ddd;
@@ -183,7 +183,7 @@ html body .dropdown-submenu:hover>a {
{% endblock %} {% endblock %}
{% block branding %} {% block branding %}
<a class="brand" href="/api/"><img class="logo" src="{{ STATIC_URL }}img/tower_console_logo.png">{% block branding_title %}{% trans 'REST API' %}{% endblock %}</a> <a class="brand" href="/api/"><img class="logo" src="{{ STATIC_URL }}assets/tower_console_logo.png">{% block branding_title %}{% trans 'REST API' %}{% endblock %}</a>
{% endblock %} {% endblock %}
{% block userlinks %} {% block userlinks %}
@@ -196,7 +196,7 @@ html body .dropdown-submenu:hover>a {
{% block footer %} {% block footer %}
<div id="footer"> <div id="footer">
<a href="http://www.ansible.com" target="_blank"><img class="towerlogo" src="{{ STATIC_URL }}img/tower_console_bug.png" /></a><br/> <a href="http://www.ansible.com" target="_blank"><img class="towerlogo" src="{{ STATIC_URL }}assets/tower_console_bug.png" /></a><br/>
Copyright &copy; 2015 <a href="http://www.ansible.com" target="_blank">Ansible, Inc.</a> All rights reserved. Copyright &copy; 2015 <a href="http://www.ansible.com" target="_blank">Ansible, Inc.</a> All rights reserved.
</div> </div>
{% endblock %} {% endblock %}

Before

Width:  |  Height:  |  Size: 3.3 KiB

After

Width:  |  Height:  |  Size: 3.3 KiB

Before

Width:  |  Height:  |  Size: 898 B

After

Width:  |  Height:  |  Size: 898 B

Before

Width:  |  Height:  |  Size: 2.4 KiB

After

Width:  |  Height:  |  Size: 2.4 KiB

Before

Width:  |  Height:  |  Size: 2.5 KiB

After

Width:  |  Height:  |  Size: 2.5 KiB

Before

Width:  |  Height:  |  Size: 2.2 KiB

After

Width:  |  Height:  |  Size: 2.2 KiB

Before

Width:  |  Height:  |  Size: 5.1 KiB

After

Width:  |  Height:  |  Size: 5.1 KiB

Before

Width:  |  Height:  |  Size: 3.0 KiB

After

Width:  |  Height:  |  Size: 3.0 KiB

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