Merge branch 'release_3.1.2' into devel

* release_3.1.2: (33 commits)
  updating changelog for 3.1.2
  Add back SRC_ONLY_PKGS
  Fix ubuntu 14 restart service list pt 2
  Make sure the insight playbook fetch doesn't quote user and pass
  Add requirements/vendor to gitignore
  Conditionally install from local python dependencies in spec file
  Remove requirements/vendor on make clean.
  Update brew-srpm target to generate local requirements files
  Get offline pip installs working
  Add clean-dist target
  Navigate back to the jobDetails state when the user clicks outside the host event modal.
  Don't use jinja quote filter on insights username or password
  fix legacy standard out
  Fixed permissions typo
  add test, restore old behavior in api test
  create _survey_element_validation and use it for updating extra_vars
  Do not set the default if the field was not passed in to kwargs_extra_vars
  Remove log aggregator port required mark.
  Modify according to review feedback.
  Host Event json should be read-only
  ...
This commit is contained in:
Matthew Jones
2017-03-31 10:36:02 -04:00
26 changed files with 240 additions and 111 deletions

1
.gitignore vendored
View File

@@ -108,6 +108,7 @@ reports
*.results *.results
local/ local/
*.mo *.mo
requirements/vendor
# AWX python libs populated by requirements.txt # AWX python libs populated by requirements.txt
awx/lib/.deps_built awx/lib/.deps_built

View File

@@ -225,15 +225,18 @@ clean-tmp:
clean-venv: clean-venv:
rm -rf venv/ rm -rf venv/
clean-dist:
rm -rf dist
# Remove temporary build files, compiled Python files. # Remove temporary build files, compiled Python files.
clean: clean-rpm clean-deb clean-ui clean-tar clean-packer clean-bundle clean: clean-rpm clean-deb clean-ui clean-tar clean-packer clean-bundle clean-dist
rm -rf awx/public rm -rf awx/public
rm -rf awx/lib/site-packages rm -rf awx/lib/site-packages
rm -rf dist/*
rm -rf awx/job_status rm -rf awx/job_status
rm -rf awx/job_output rm -rf awx/job_output
rm -rf reports rm -rf reports
rm -f awx/awx_test.sqlite3 rm -f awx/awx_test.sqlite3
rm -rf requirements/vendor
rm -rf tmp rm -rf tmp
mkdir tmp mkdir tmp
rm -rf build $(NAME)-$(VERSION) *.egg-info rm -rf build $(NAME)-$(VERSION) *.egg-info
@@ -282,7 +285,11 @@ virtualenv_tower:
fi fi
requirements_ansible: virtualenv_ansible requirements_ansible: virtualenv_ansible
$(VENV_BASE)/ansible/bin/pip install $(PIP_OPTIONS) --ignore-installed --no-binary $(SRC_ONLY_PKGS) -r requirements/requirements_ansible.txt if [[ "$(PIP_OPTIONS)" == *"--no-index"* ]]; then \
cat requirements/requirements_ansible.txt requirements/requirements_ansible_local.txt | $(VENV_BASE)/ansible/bin/pip install $(PIP_OPTIONS) --ignore-installed -r /dev/stdin ; \
else \
cat requirements/requirements_ansible.txt requirements/requirements_ansible_git.txt | $(VENV_BASE)/ansible/bin/pip install $(PIP_OPTIONS) --no-binary $(SRC_ONLY_PKGS) --ignore-installed -r /dev/stdin ; \
fi
$(VENV_BASE)/ansible/bin/pip uninstall --yes -r requirements/requirements_ansible_uninstall.txt $(VENV_BASE)/ansible/bin/pip uninstall --yes -r requirements/requirements_ansible_uninstall.txt
requirements_ansible_dev: requirements_ansible_dev:
@@ -292,7 +299,11 @@ requirements_ansible_dev:
# Install third-party requirements needed for Tower's environment. # Install third-party requirements needed for Tower's environment.
requirements_tower: virtualenv_tower requirements_tower: virtualenv_tower
$(VENV_BASE)/tower/bin/pip install $(PIP_OPTIONS) --ignore-installed --no-binary $(SRC_ONLY_PKGS) -r requirements/requirements.txt if [[ "$(PIP_OPTIONS)" == *"--no-index"* ]]; then \
cat requirements/requirements.txt requirements/requirements_local.txt | $(VENV_BASE)/tower/bin/pip install $(PIP_OPTIONS) --ignore-installed -r /dev/stdin ; \
else \
cat requirements/requirements.txt requirements/requirements_git.txt | $(VENV_BASE)/tower/bin/pip install $(PIP_OPTIONS) --no-binary $(SRC_ONLY_PKGS) --ignore-installed -r /dev/stdin ; \
fi
$(VENV_BASE)/tower/bin/pip uninstall --yes -r requirements/requirements_tower_uninstall.txt $(VENV_BASE)/tower/bin/pip uninstall --yes -r requirements/requirements_tower_uninstall.txt
requirements_tower_dev: requirements_tower_dev:
@@ -690,39 +701,44 @@ setup_bundle_tarball: setup-bundle-build setup-bundle-build/$(OFFLINE_TAR_FILE)
rpm-build: rpm-build:
mkdir -p $@ mkdir -p $@
rpm-build/$(SDIST_TAR_FILE): rpm-build dist/$(SDIST_TAR_FILE) rpm-build/$(SDIST_TAR_FILE): rpm-build dist/$(SDIST_TAR_FILE) tar-build/$(SETUP_TAR_FILE)
cp packaging/rpm/$(NAME).spec rpm-build/ cp packaging/rpm/$(NAME).spec rpm-build/
cp packaging/rpm/tower.te rpm-build/ cp packaging/rpm/tower.te rpm-build/
cp packaging/rpm/tower.fc rpm-build/ cp packaging/rpm/tower.fc rpm-build/
cp packaging/rpm/$(NAME).sysconfig rpm-build/ cp packaging/rpm/$(NAME).sysconfig rpm-build/
cp packaging/remove_tower_source.py rpm-build/ cp packaging/remove_tower_source.py rpm-build/
cp packaging/bytecompile.sh rpm-build/ cp packaging/bytecompile.sh rpm-build/
cp tar-build/$(SETUP_TAR_FILE) rpm-build/
if [ "$(OFFICIAL)" != "yes" ] ; then \ if [ "$(OFFICIAL)" != "yes" ] ; then \
(cd dist/ && tar zxf $(SDIST_TAR_FILE)) ; \ (cd dist/ && tar zxf $(SDIST_TAR_FILE)) ; \
(cd dist/ && mv $(NAME)-$(VERSION)-$(BUILD) $(NAME)-$(VERSION)) ; \ (cd dist/ && mv $(NAME)-$(VERSION)-$(BUILD) $(NAME)-$(VERSION)) ; \
(cd dist/ && tar czf ../rpm-build/$(SDIST_TAR_FILE) $(NAME)-$(VERSION)) ; \ (cd dist/ && tar czf ../rpm-build/$(SDIST_TAR_FILE) $(NAME)-$(VERSION)) ; \
ln -sf $(SDIST_TAR_FILE) rpm-build/$(NAME)-$(VERSION).tar.gz ; \ ln -sf $(SDIST_TAR_FILE) rpm-build/$(NAME)-$(VERSION).tar.gz ; \
(cd tar-build/ && tar zxf $(SETUP_TAR_FILE)) ; \
(cd tar-build/ && mv $(NAME)-setup-$(VERSION)-$(BUILD) $(NAME)-setup-$(VERSION)) ; \
(cd tar-build/ && tar czf ../rpm-build/$(SETUP_TAR_FILE) $(NAME)-setup-$(VERSION)) ; \
ln -sf $(SETUP_TAR_FILE) rpm-build/$(NAME)-setup-$(VERSION).tar.gz ; \
else \ else \
cp -a dist/$(SDIST_TAR_FILE) rpm-build/ ; \ cp -a dist/$(SDIST_TAR_FILE) rpm-build/ ; \
fi fi
rpmtar: sdist rpm-build/$(SDIST_TAR_FILE) rpmtar: sdist rpm-build/$(SDIST_TAR_FILE)
brewrpmtar: rpm-build/python-deps.tar.gz rpmtar brewrpmtar: rpm-build/python-deps.tar.gz requirements/requirements_local.txt requirements/requirements_ansible_local.txt rpmtar
rpm-build/python-deps.tar.gz: requirements/vendor rpm-build rpm-build/python-deps.tar.gz: requirements/vendor rpm-build
tar czf rpm-build/python-deps.tar.gz requirements/vendor tar czf rpm-build/python-deps.tar.gz requirements/vendor
requirements/vendor: requirements/vendor:
pip download \ cat requirements/requirements.txt requirements/requirements_git.txt | pip download \
--no-binary=:all: \ --no-binary=:all: \
--requirement=requirements/requirements_ansible.txt \ --requirement=/dev/stdin \
--dest=$@ \ --dest=$@ \
--exists-action=i --exists-action=i
pip download \ cat requirements/requirements_ansible.txt requirements/requirements_ansible_git.txt | pip download \
--no-binary=:all: \ --no-binary=:all: \
--requirement=requirements/requirements.txt \ --requirement=/dev/stdin \
--dest=$@ \ --dest=$@ \
--exists-action=i --exists-action=i
@@ -732,6 +748,21 @@ requirements/vendor:
--dest=$@ \ --dest=$@ \
--exists-action=i --exists-action=i
requirements/requirements_local.txt:
@echo "This is going to take a while..."
pip download \
--requirement=requirements/requirements_git.txt \
--no-deps \
--exists-action=w \
--dest=requirements/vendor 2>/dev/null | sed -n 's/^\s*Saved\s*//p' > $@
requirements/requirements_ansible_local.txt:
pip download \
--requirement=requirements/requirements_ansible_git.txt \
--no-deps \
--exists-action=w \
--dest=requirements/vendor 2>/dev/null | sed -n 's/^\s*Saved\s*//p' > $@
rpm-build/$(RPM_NVR).src.rpm: /etc/mock/$(MOCK_CFG).cfg rpm-build/$(RPM_NVR).src.rpm: /etc/mock/$(MOCK_CFG).cfg
$(MOCK_BIN) -r $(MOCK_CFG) --resultdir rpm-build --buildsrpm --spec rpm-build/$(NAME).spec --sources rpm-build \ $(MOCK_BIN) -r $(MOCK_CFG) --resultdir rpm-build --buildsrpm --spec rpm-build/$(NAME).spec --sources rpm-build \
--define "tower_version $(VERSION)" --define "tower_release $(RELEASE)" $(SCL_DEFINES) --define "tower_version $(VERSION)" --define "tower_release $(RELEASE)" $(SCL_DEFINES)

View File

@@ -60,7 +60,7 @@ def executor(tmpdir_factory, request):
cli = PlaybookCLI(['', 'playbook.yml']) cli = PlaybookCLI(['', 'playbook.yml'])
cli.parse() cli.parse()
options = cli.parser.parse_args([])[0] options = cli.parser.parse_args(['-v'])[0]
loader = DataLoader() loader = DataLoader()
variable_manager = VariableManager() variable_manager = VariableManager()
inventory = Inventory(loader=loader, variable_manager=variable_manager, inventory = Inventory(loader=loader, variable_manager=variable_manager,

View File

@@ -30,6 +30,8 @@ from ansible.plugins.callback.default import CallbackModule as DefaultCallbackMo
from .events import event_context from .events import event_context
from .minimal import CallbackModule as MinimalCallbackModule from .minimal import CallbackModule as MinimalCallbackModule
CENSORED = "the output has been hidden due to the fact that 'no_log: true' was specified for this result" # noqa
class BaseCallbackModule(CallbackBase): class BaseCallbackModule(CallbackBase):
''' '''
@@ -69,8 +71,12 @@ class BaseCallbackModule(CallbackBase):
else: else:
task = None task = None
if event_data.get('res') and event_data['res'].get('_ansible_no_log', False): if event_data.get('res'):
event_data['res'] = {'censored': "the output has been hidden due to the fact that 'no_log: true' was specified for this result"} # noqa if event_data['res'].get('_ansible_no_log', False):
event_data['res'] = {'censored': CENSORED}
for i, item in enumerate(event_data['res'].get('results', [])):
if event_data['res']['results'][i].get('_ansible_no_log', False):
event_data['res']['results'][i] = {'censored': CENSORED}
with event_context.display_lock: with event_context.display_lock:
try: try:

View File

@@ -242,9 +242,11 @@ register(
field_class=fields.IntegerField, field_class=fields.IntegerField,
allow_null=True, allow_null=True,
label=_('Logging Aggregator Port'), label=_('Logging Aggregator Port'),
help_text=_('Port on Logging Aggregator to send logs to (if required).'), help_text=_('Port on Logging Aggregator to send logs to (if required and not'
' provided in Logging Aggregator).'),
category=_('Logging'), category=_('Logging'),
category_slug='logging', category_slug='logging',
required=False
) )
register( register(
'LOG_AGGREGATOR_TYPE', 'LOG_AGGREGATOR_TYPE',

View File

@@ -130,13 +130,18 @@ class SurveyJobTemplateMixin(models.Model):
for survey_element in self.survey_spec.get("spec", []): for survey_element in self.survey_spec.get("spec", []):
default = survey_element.get('default') default = survey_element.get('default')
variable_key = survey_element.get('variable') variable_key = survey_element.get('variable')
if survey_element.get('type') == 'password': if survey_element.get('type') == 'password':
if variable_key in kwargs_extra_vars and default: if variable_key in kwargs_extra_vars and default:
kw_value = kwargs_extra_vars[variable_key] kw_value = kwargs_extra_vars[variable_key]
if kw_value.startswith('$encrypted$') and kw_value != default: if kw_value.startswith('$encrypted$') and kw_value != default:
kwargs_extra_vars[variable_key] = default kwargs_extra_vars[variable_key] = default
if default is not None: if default is not None:
extra_vars[variable_key] = default data = {variable_key: default}
errors = self._survey_element_validation(survey_element, data)
if not errors:
extra_vars[variable_key] = default
# Overwrite job template extra vars with explicit job extra vars # Overwrite job template extra vars with explicit job extra vars
# and add on job extra vars # and add on job extra vars
@@ -144,6 +149,65 @@ class SurveyJobTemplateMixin(models.Model):
kwargs['extra_vars'] = json.dumps(extra_vars) kwargs['extra_vars'] = json.dumps(extra_vars)
return kwargs return kwargs
def _survey_element_validation(self, survey_element, data):
errors = []
if survey_element['variable'] not in data and survey_element['required']:
errors.append("'%s' value missing" % survey_element['variable'])
elif survey_element['type'] in ["textarea", "text", "password"]:
if survey_element['variable'] in data:
if type(data[survey_element['variable']]) not in (str, unicode):
errors.append("Value %s for '%s' expected to be a string." % (data[survey_element['variable']],
survey_element['variable']))
return errors
if 'min' in survey_element and survey_element['min'] not in ["", None] and len(data[survey_element['variable']]) < int(survey_element['min']):
errors.append("'%s' value %s is too small (length is %s must be at least %s)." %
(survey_element['variable'], data[survey_element['variable']], len(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']]) > int(survey_element['max']):
errors.append("'%s' value %s is too large (must be no more than %s)." %
(survey_element['variable'], data[survey_element['variable']], survey_element['max']))
elif survey_element['type'] == 'integer':
if survey_element['variable'] in data:
if type(data[survey_element['variable']]) != int:
errors.append("Value %s for '%s' expected to be an integer." % (data[survey_element['variable']],
survey_element['variable']))
return errors
if 'min' in survey_element and survey_element['min'] not in ["", None] and survey_element['variable'] in data and \
data[survey_element['variable']] < int(survey_element['min']):
errors.append("'%s' value %s is too small (must be at least %s)." %
(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 \
data[survey_element['variable']] > int(survey_element['max']):
errors.append("'%s' value %s is too large (must be no more than %s)." %
(survey_element['variable'], data[survey_element['variable']], survey_element['max']))
elif survey_element['type'] == 'float':
if survey_element['variable'] in data:
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']],
survey_element['variable']))
return errors
if 'min' in survey_element and survey_element['min'] not in ["", None] and data[survey_element['variable']] < float(survey_element['min']):
errors.append("'%s' value %s is too small (must be at least %s)." %
(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']] > float(survey_element['max']):
errors.append("'%s' value %s is too large (must be no more than %s)." %
(survey_element['variable'], data[survey_element['variable']], survey_element['max']))
elif survey_element['type'] == 'multiselect':
if survey_element['variable'] in data:
if type(data[survey_element['variable']]) != list:
errors.append("'%s' value is expected to be a list." % survey_element['variable'])
else:
for val in data[survey_element['variable']]:
if val not in survey_element['choices']:
errors.append("Value %s for '%s' expected to be one of %s." % (val, survey_element['variable'],
survey_element['choices']))
elif survey_element['type'] == 'multiplechoice':
if survey_element['variable'] in data:
if data[survey_element['variable']] not in survey_element['choices']:
errors.append("Value %s for '%s' expected to be one of %s." % (data[survey_element['variable']],
survey_element['variable'],
survey_element['choices']))
return errors
def survey_variable_validation(self, data): def survey_variable_validation(self, data):
errors = [] errors = []
if not self.survey_enabled: if not self.survey_enabled:
@@ -153,62 +217,7 @@ class SurveyJobTemplateMixin(models.Model):
if 'description' not in self.survey_spec: if 'description' not in self.survey_spec:
errors.append("'description' missing from survey spec.") errors.append("'description' missing from survey spec.")
for survey_element in self.survey_spec.get("spec", []): for survey_element in self.survey_spec.get("spec", []):
if survey_element['variable'] not in data and \ errors += self._survey_element_validation(survey_element, data)
survey_element['required']:
errors.append("'%s' value missing" % survey_element['variable'])
elif survey_element['type'] in ["textarea", "text", "password"]:
if survey_element['variable'] in data:
if type(data[survey_element['variable']]) not in (str, unicode):
errors.append("Value %s for '%s' expected to be a string." % (data[survey_element['variable']],
survey_element['variable']))
continue
if 'min' in survey_element and survey_element['min'] not in ["", None] and len(data[survey_element['variable']]) < int(survey_element['min']):
errors.append("'%s' value %s is too small (length is %s must be at least %s)." %
(survey_element['variable'], data[survey_element['variable']], len(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']]) > int(survey_element['max']):
errors.append("'%s' value %s is too large (must be no more than %s)." %
(survey_element['variable'], data[survey_element['variable']], survey_element['max']))
elif survey_element['type'] == 'integer':
if survey_element['variable'] in data:
if type(data[survey_element['variable']]) != int:
errors.append("Value %s for '%s' expected to be an integer." % (data[survey_element['variable']],
survey_element['variable']))
continue
if 'min' in survey_element and survey_element['min'] not in ["", None] and survey_element['variable'] in data and \
data[survey_element['variable']] < int(survey_element['min']):
errors.append("'%s' value %s is too small (must be at least %s)." %
(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 \
data[survey_element['variable']] > int(survey_element['max']):
errors.append("'%s' value %s is too large (must be no more than %s)." %
(survey_element['variable'], data[survey_element['variable']], survey_element['max']))
elif survey_element['type'] == 'float':
if survey_element['variable'] in data:
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']],
survey_element['variable']))
continue
if 'min' in survey_element and survey_element['min'] not in ["", None] and data[survey_element['variable']] < float(survey_element['min']):
errors.append("'%s' value %s is too small (must be at least %s)." %
(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']] > float(survey_element['max']):
errors.append("'%s' value %s is too large (must be no more than %s)." %
(survey_element['variable'], data[survey_element['variable']], survey_element['max']))
elif survey_element['type'] == 'multiselect':
if survey_element['variable'] in data:
if type(data[survey_element['variable']]) != list:
errors.append("'%s' value is expected to be a list." % survey_element['variable'])
else:
for val in data[survey_element['variable']]:
if val not in survey_element['choices']:
errors.append("Value %s for '%s' expected to be one of %s." % (val, survey_element['variable'],
survey_element['choices']))
elif survey_element['type'] == 'multiplechoice':
if survey_element['variable'] in data:
if data[survey_element['variable']] not in survey_element['choices']:
errors.append("Value %s for '%s' expected to be one of %s." % (data[survey_element['variable']],
survey_element['variable'],
survey_element['choices']))
return errors return errors

View File

@@ -4,6 +4,7 @@ import json
from awx.main.tasks import RunJob from awx.main.tasks import RunJob
from awx.main.models import ( from awx.main.models import (
Job, Job,
JobTemplate,
WorkflowJobTemplate WorkflowJobTemplate
) )
@@ -78,6 +79,18 @@ def test_job_args_unredacted_passwords(job):
assert extra_vars['secret_key'] == 'my_password' assert extra_vars['secret_key'] == 'my_password'
def test_update_kwargs_survey_invalid_default(survey_spec_factory):
spec = survey_spec_factory('var2')
spec['spec'][0]['required'] = False
spec['spec'][0]['min'] = 3
spec['spec'][0]['default'] = 1
jt = JobTemplate(name="test-jt", survey_spec=spec, survey_enabled=True, extra_vars="var2: 2")
defaulted_extra_vars = jt._update_unified_job_kwargs()
assert 'extra_vars' in defaulted_extra_vars
# Make sure we did not set the invalid default of 1
assert json.loads(defaulted_extra_vars['extra_vars'])['var2'] == 2
class TestWorkflowSurveys: class TestWorkflowSurveys:
def test_update_kwargs_survey_defaults(self, survey_spec_factory): def test_update_kwargs_survey_defaults(self, survey_spec_factory):
"Assure that the survey default over-rides a JT variable" "Assure that the survey default over-rides a JT variable"

View File

@@ -147,7 +147,17 @@ def test_https_logging_handler_splunk_auth_info():
('http://localhost', None, 'http://localhost'), ('http://localhost', None, 'http://localhost'),
('http://localhost', 80, 'http://localhost'), ('http://localhost', 80, 'http://localhost'),
('http://localhost', 8080, 'http://localhost:8080'), ('http://localhost', 8080, 'http://localhost:8080'),
('https://localhost', 443, 'https://localhost:443') ('https://localhost', 443, 'https://localhost:443'),
('ftp://localhost', 443, 'ftp://localhost:443'),
('https://localhost:550', 443, 'https://localhost:550'),
('https://localhost:yoho/foobar', 443, 'https://localhost:443/foobar'),
('https://localhost:yoho/foobar', None, 'https://localhost:yoho/foobar'),
('http://splunk.server:8088/services/collector/event', 80,
'http://splunk.server:8088/services/collector/event'),
('http://splunk.server/services/collector/event', 80,
'http://splunk.server/services/collector/event'),
('http://splunk.server/services/collector/event', 8088,
'http://splunk.server:8088/services/collector/event'),
]) ])
def test_https_logging_handler_http_host_format(host, port, normalized): def test_https_logging_handler_http_host_format(host, port, normalized):
handler = HTTPSHandler(host=host, port=port) handler = HTTPSHandler(host=host, port=port)

View File

@@ -6,6 +6,7 @@ import logging
import json import json
import requests import requests
import time import time
import urlparse
from concurrent.futures import ThreadPoolExecutor from concurrent.futures import ThreadPoolExecutor
from copy import copy from copy import copy
from requests.exceptions import RequestException from requests.exceptions import RequestException
@@ -148,10 +149,21 @@ class BaseHTTPSHandler(logging.Handler):
def get_http_host(self): def get_http_host(self):
host = self.host or '' host = self.host or ''
if not host.startswith('http'): # urlparse requires scheme to be provided, default to use http if
host = 'http://%s' % self.host # missing
if self.port != 80 and self.port is not None: if not urlparse.urlsplit(host).scheme:
host = '%s:%s' % (host, str(self.port)) host = 'http://%s' % host
parsed = urlparse.urlsplit(host)
# Insert self.port if its special and port number is either not
# given in host or given as non-numerical
try:
port = parsed.port or self.port
except ValueError:
port = self.port
if port not in (80, None):
new_netloc = '%s:%s' % (parsed.hostname, port)
return urlparse.urlunsplit((parsed.scheme, new_netloc, parsed.path,
parsed.query, parsed.fragment))
return host return host
def get_post_kwargs(self, payload_input): def get_post_kwargs(self, payload_input):

View File

@@ -108,8 +108,8 @@
- name: update project using insights - name: update project using insights
uri: uri:
url: "{{insights_url}}/r/insights/v1/maintenance?ansible=true" url: "{{insights_url}}/r/insights/v1/maintenance?ansible=true"
user: "{{scm_username|quote}}" user: "{{scm_username}}"
password: "{{scm_password|quote}}" password: "{{scm_password}}"
force_basic_auth: yes force_basic_auth: yes
when: scm_type == 'insights' when: scm_type == 'insights'
register: insights_output register: insights_output
@@ -124,8 +124,8 @@
get_url: get_url:
url: "{{insights_url}}/r/insights/v3/maintenance/{{item.maintenance_id}}/playbook" url: "{{insights_url}}/r/insights/v3/maintenance/{{item.maintenance_id}}/playbook"
dest: "{{project_path|quote}}/{{item.name}}-{{item.maintenance_id}}.yml" dest: "{{project_path|quote}}/{{item.name}}-{{item.maintenance_id}}.yml"
url_username: "{{scm_username|quote}}" url_username: "{{scm_username}}"
url_password: "{{scm_password|quote}}" url_password: "{{scm_password}}"
force_basic_auth: yes force_basic_auth: yes
force: yes force: yes
when: scm_type == 'insights' and item.name != None when: scm_type == 'insights' and item.name != None
@@ -136,8 +136,8 @@
get_url: get_url:
url: "{{insights_url}}/r/insights/v3/maintenance/{{item.maintenance_id}}/playbook" url: "{{insights_url}}/r/insights/v3/maintenance/{{item.maintenance_id}}/playbook"
dest: "{{project_path|quote}}/insights-plan-{{item.maintenance_id}}.yml" dest: "{{project_path|quote}}/insights-plan-{{item.maintenance_id}}.yml"
url_username: "{{scm_username|quote}}" url_username: "{{scm_username}}"
url_password: "{{scm_password|quote}}" url_password: "{{scm_password}}"
force_basic_auth: yes force_basic_auth: yes
force: yes force: yes
when: scm_type == 'insights' and item.name == None when: scm_type == 'insights' and item.name == None

View File

@@ -9,7 +9,7 @@
</a> </a>
<span class="HostEvent-title">{{event.host_name}}</span> <span class="HostEvent-title">{{event.host_name}}</span>
<!-- close --> <!-- close -->
<button ui-sref="jobResult" type="button" class="close"> <button ng-click="closeHostEvent()" type="button" class="close">
<i class="fa fa-times-circle"></i> <i class="fa fa-times-circle"></i>
</button> </button>
</div> </div>
@@ -64,7 +64,7 @@
<!-- controls --> <!-- controls -->
<div class="HostEvent-controls"> <div class="HostEvent-controls">
<button ui-sref="jobResult" class="btn btn-sm btn-default HostEvent-close">Close</button> <button ng-click="closeHostEvent()" class="btn btn-sm btn-default HostEvent-close">Close</button>
</div> </div>
</div> </div>
</div> </div>

View File

@@ -19,7 +19,8 @@
var container = document.getElementById(el); var container = document.getElementById(el);
var editor = CodeMirror.fromTextArea(container, { // jshint ignore:line var editor = CodeMirror.fromTextArea(container, { // jshint ignore:line
lineNumbers: true, lineNumbers: true,
mode: mode mode: mode,
readOnly: true
}); });
editor.setSize("100%", 200); editor.setSize("100%", 200);
editor.getDoc().setValue(data); editor.getDoc().setValue(data);
@@ -29,6 +30,19 @@
return $state.current.name === name; return $state.current.name === name;
}; };
$scope.getActiveHostIndex = function(){
var result = $scope.hostResults.filter(function( obj ) {
return obj.id === $scope.event.id;
});
return $scope.hostResults.indexOf(result[0]);
};
$scope.closeHostEvent = function() {
// Unbind the listener so it doesn't fire when we close the modal via navigation
$('#HostEvent').off('hidden.bs.modal');
$state.go('jobDetail');
};
var init = function(){ var init = function(){
hostEvent.event_name = hostEvent.event; hostEvent.event_name = hostEvent.event;
$scope.event = _.cloneDeep(hostEvent); $scope.event = _.cloneDeep(hostEvent);
@@ -81,6 +95,10 @@
} }
} }
$('#HostEvent').modal('show'); $('#HostEvent').modal('show');
$('#HostEvent').on('hidden.bs.modal', function () {
$scope.closeHostEvent();
});
}; };
init(); init();
}]; }];

View File

@@ -38,7 +38,8 @@ function(jobData, jobDataOptions, jobLabels, jobFinished, count, $scope, ParseTy
// used for tag search // used for tag search
$scope.list = { $scope.list = {
basePath: jobData.related.job_events basePath: jobData.related.job_events,
name: 'job_events'
}; };
// used for tag search // used for tag search
@@ -450,13 +451,6 @@ function(jobData, jobDataOptions, jobLabels, jobFinished, count, $scope, ParseTy
var getSkeleton = function(url) { var getSkeleton = function(url) {
jobResultsService.getEvents(url) jobResultsService.getEvents(url)
.then(events => { .then(events => {
// old job check: if the job is complete, there is result stdout, and
// there are no job events, it's an old job
if ($scope.jobFinished) {
$scope.showLegacyJobErrorMessage = $scope.job.result_stdout.length &&
!events.results.length;
}
events.results.forEach(event => { events.results.forEach(event => {
if (event.start_line === 0 && event.end_line === 0) { if (event.start_line === 0 && event.end_line === 0) {
$scope.isOld++; $scope.isOld++;

View File

@@ -1368,4 +1368,33 @@ function(ConfigurationUtils, i18n, $rootScope) {
}); });
} }
}; };
}])
.directive('awRequireMultiple', [function() {
return {
require: 'ngModel',
link: function postLink(scope, element, attrs, ngModel) {
// Watch for changes to the required attribute
attrs.$observe('required', function(value) {
if(value) {
ngModel.$validators.required = function (value) {
if(angular.isArray(value)) {
if(value.length === 0) {
return false;
}
else {
return (!value[0] || value[0] === "") ? false : true;
}
}
else {
return false;
}
};
}
else {
delete ngModel.$validators.required;
}
});
}
};
}]); }]);

View File

@@ -1,5 +1,5 @@
<div> <div>
<select class="form-control SurveyMaker-previewSelect" ng-model="selectedValue" multi-select ng-required="isRequired" ng-disabled="isDisabled"> <select class="form-control SurveyMaker-previewSelect" ng-model="selectedValue" multi-select ng-required="isRequired" ng-disabled="isDisabled" aw-require-multiple>
<option ng-repeat="choice in choices" value="{{choice}}">{{choice}}</option> <option ng-repeat="choice in choices" value="{{choice}}">{{choice}}</option>
</select> </select>
</div> </div>

View File

@@ -207,7 +207,7 @@ export default ['$state','moment', '$timeout', '$window',
} }
function update() { function update() {
let userCanAddEdit = (scope.workflowJobTemplateObjt && scope.workflowJobTemplateObj.summary_fields && scope.workflowJobTemplateObj.summary_fields.user_capabilities && scope.workflowJobTemplateObj.summary_fields.user_capabilities.edit) || scope.canAddWorkflowJobTemplate; let userCanAddEdit = (scope.workflowJobTemplateObj && scope.workflowJobTemplateObj.summary_fields && scope.workflowJobTemplateObj.summary_fields.user_capabilities && scope.workflowJobTemplateObj.summary_fields.user_capabilities.edit) || scope.canAddWorkflowJobTemplate;
if(scope.dimensionsSet) { if(scope.dimensionsSet) {
// Declare the nodes // Declare the nodes
let nodes = tree.nodes(scope.treeData), let nodes = tree.nodes(scope.treeData),
@@ -813,7 +813,7 @@ export default ['$state','moment', '$timeout', '$window',
function add_node() { function add_node() {
this.on("click", function(d) { this.on("click", function(d) {
if((scope.workflowJobTemplateObjt && scope.workflowJobTemplateObj.summary_fields && scope.workflowJobTemplateObj.summary_fields.user_capabilities && scope.workflowJobTemplateObj.summary_fields.user_capabilities.edit) || scope.canAddWorkflowJobTemplate) { if((scope.workflowJobTemplateObj && scope.workflowJobTemplateObj.summary_fields && scope.workflowJobTemplateObj.summary_fields.user_capabilities && scope.workflowJobTemplateObj.summary_fields.user_capabilities.edit) || scope.canAddWorkflowJobTemplate) {
scope.addNode({ scope.addNode({
parent: d, parent: d,
betweenTwoNodes: false betweenTwoNodes: false
@@ -824,7 +824,7 @@ export default ['$state','moment', '$timeout', '$window',
function add_node_between() { function add_node_between() {
this.on("click", function(d) { this.on("click", function(d) {
if((scope.workflowJobTemplateObjt && scope.workflowJobTemplateObj.summary_fields && scope.workflowJobTemplateObj.summary_fields.user_capabilities && scope.workflowJobTemplateObj.summary_fields.user_capabilities.edit) || scope.canAddWorkflowJobTemplate) { if((scope.workflowJobTemplateObj && scope.workflowJobTemplateObj.summary_fields && scope.workflowJobTemplateObj.summary_fields.user_capabilities && scope.workflowJobTemplateObj.summary_fields.user_capabilities.edit) || scope.canAddWorkflowJobTemplate) {
scope.addNode({ scope.addNode({
parent: d, parent: d,
betweenTwoNodes: true betweenTwoNodes: true
@@ -835,7 +835,7 @@ export default ['$state','moment', '$timeout', '$window',
function remove_node() { function remove_node() {
this.on("click", function(d) { this.on("click", function(d) {
if((scope.workflowJobTemplateObjt && scope.workflowJobTemplateObj.summary_fields && scope.workflowJobTemplateObj.summary_fields.user_capabilities && scope.workflowJobTemplateObj.summary_fields.user_capabilities.edit) || scope.canAddWorkflowJobTemplate) { if((scope.workflowJobTemplateObj && scope.workflowJobTemplateObj.summary_fields && scope.workflowJobTemplateObj.summary_fields.user_capabilities && scope.workflowJobTemplateObj.summary_fields.user_capabilities.edit) || scope.canAddWorkflowJobTemplate) {
scope.deleteNode({ scope.deleteNode({
nodeToDelete: d nodeToDelete: d
}); });

View File

@@ -1,8 +1,3 @@
-e git+https://github.com/ansible/ansiconv.git@tower_1.0.0#egg=ansiconv
-e git+https://github.com/ansible/django-jsonbfield@fix-sqlite_serialization#egg=jsonbfield
-e git+https://github.com/ansible/django-qsstats-magic.git@tower_0.7.2#egg=django-qsstats-magic
-e git+https://github.com/ansible/dm.xmlsec.binding.git@master#egg=dm.xmlsec.binding
-e git+https://github.com/chrismeyersfsu/pyrax@tower#egg=pyrax
apache-libcloud==1.3.0 apache-libcloud==1.3.0
appdirs==1.4.2 appdirs==1.4.2
asgi-amqp==0.4.1 asgi-amqp==0.4.1

View File

@@ -4,11 +4,6 @@
# #
# pip-compile --output-file requirements/requirements.txt requirements/requirements.in # pip-compile --output-file requirements/requirements.txt requirements/requirements.in
# #
git+https://github.com/ansible/ansiconv.git@tower_1.0.0#egg=ansiconv
git+https://github.com/ansible/django-qsstats-magic.git@tower_0.7.2#egg=django-qsstats-magic
git+https://github.com/ansible/dm.xmlsec.binding.git@master#egg=dm.xmlsec.binding
git+https://github.com/ansible/django-jsonbfield@fix-sqlite_serialization#egg=jsonbfield
git+https://github.com/chrismeyersfsu/pyrax@tower#egg=pyrax
adal==0.4.3 # via msrestazure adal==0.4.3 # via msrestazure
amqp==1.4.9 # via kombu amqp==1.4.9 # via kombu
anyjson==0.3.3 # via kombu anyjson==0.3.3 # via kombu

View File

@@ -1,4 +1,3 @@
-e git+https://github.com/chrismeyersfsu/pyrax@tower#egg=pyrax
apache-libcloud==1.3.0 apache-libcloud==1.3.0
azure==2.0.0rc6 azure==2.0.0rc6
backports.ssl-match-hostname==3.5.0.1 backports.ssl-match-hostname==3.5.0.1

View File

@@ -4,7 +4,6 @@
# #
# pip-compile --output-file requirements/requirements_ansible.txt requirements/requirements_ansible.in # pip-compile --output-file requirements/requirements_ansible.txt requirements/requirements_ansible.in
# #
git+https://github.com/chrismeyersfsu/pyrax@tower#egg=pyrax
adal==0.4.3 # via msrestazure adal==0.4.3 # via msrestazure
amqp==1.4.9 # via kombu amqp==1.4.9 # via kombu
anyjson==0.3.3 # via kombu anyjson==0.3.3 # via kombu

View File

@@ -0,0 +1 @@
git+https://github.com/chrismeyersfsu/pyrax@tower#egg=pyrax

View File

@@ -0,0 +1,5 @@
git+https://github.com/ansible/ansiconv.git@tower_1.0.0#egg=ansiconv
git+https://github.com/ansible/django-qsstats-magic.git@tower_0.7.2#egg=django-qsstats-magic
git+https://github.com/ansible/dm.xmlsec.binding.git@master#egg=dm.xmlsec.binding
git+https://github.com/ansible/django-jsonbfield@fix-sqlite_serialization#egg=jsonbfield
git+https://github.com/chrismeyersfsu/pyrax@tower#egg=pyrax

View File

@@ -126,7 +126,8 @@ setup(
("%s" % docdir, ["docs/licenses/*",]), ("%s" % docdir, ["docs/licenses/*",]),
("%s" % bindir, ["tools/scripts/ansible-tower-service", ("%s" % bindir, ["tools/scripts/ansible-tower-service",
"tools/scripts/failure-event-handler", "tools/scripts/failure-event-handler",
"tools/scripts/tower-python"]), "tools/scripts/tower-python",
"tools/scripts/ansible-tower-setup"]),
("%s" % sosconfig, ["tools/sosreport/tower.py"])]), ("%s" % sosconfig, ["tools/sosreport/tower.py"])]),
options = { options = {
'egg_info': { 'egg_info': {

View File

@@ -67,3 +67,7 @@ services:
image: postgres:9.4.1 image: postgres:9.4.1
memcached: memcached:
image: memcached:alpine image: memcached:alpine
logstash:
build:
context: ./docker-compose
dockerfile: Dockerfile-logstash

View File

@@ -3,7 +3,9 @@ FROM centos:7
ADD Makefile /tmp/Makefile ADD Makefile /tmp/Makefile
RUN mkdir /tmp/requirements RUN mkdir /tmp/requirements
ADD requirements/requirements.txt \ ADD requirements/requirements.txt \
requirements/requirements_git.txt \
requirements/requirements_ansible.txt \ requirements/requirements_ansible.txt \
requirements/requirements_ansible_git.txt \
requirements/requirements_dev.txt \ requirements/requirements_dev.txt \
requirements/requirements_ansible_uninstall.txt \ requirements/requirements_ansible_uninstall.txt \
requirements/requirements_tower_uninstall.txt \ requirements/requirements_tower_uninstall.txt \

View File

@@ -0,0 +1,3 @@
#!/bin/bash
exec /var/lib/awx/setup/setup.sh "$@"