mirror of
https://github.com/ansible/awx.git
synced 2026-03-27 22:05:07 -02:30
Merge branch 'release_3.0.2' into 3081
This commit is contained in:
@@ -1983,7 +1983,7 @@ class JobSerializer(UnifiedJobSerializer, JobOptionsSerializer):
|
|||||||
return ret
|
return ret
|
||||||
if 'job_template' in ret and not obj.job_template:
|
if 'job_template' in ret and not obj.job_template:
|
||||||
ret['job_template'] = None
|
ret['job_template'] = None
|
||||||
if obj.job_template and obj.job_template.survey_enabled and 'extra_vars' in ret:
|
if 'extra_vars' in ret:
|
||||||
ret['extra_vars'] = obj.display_extra_vars()
|
ret['extra_vars'] = obj.display_extra_vars()
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
|
|
||||||
# Copyright (c) 2015 Ansible, Inc.
|
# Copyright (c) 2015 Ansible, Inc.
|
||||||
# All Rights Reserved.
|
# All Rights Reserved.
|
||||||
|
|
||||||
@@ -2987,7 +2986,7 @@ class JobJobTasksList(BaseJobEventsList):
|
|||||||
# need stats on grandchildren, sorted by child.
|
# need stats on grandchildren, sorted by child.
|
||||||
queryset = (JobEvent.objects.filter(parent__parent=parent_task,
|
queryset = (JobEvent.objects.filter(parent__parent=parent_task,
|
||||||
parent__event__in=STARTING_EVENTS)
|
parent__event__in=STARTING_EVENTS)
|
||||||
.values('parent__id', 'event', 'changed')
|
.values('parent__id', 'event', 'changed', 'failed')
|
||||||
.annotate(num=Count('event'))
|
.annotate(num=Count('event'))
|
||||||
.order_by('parent__id'))
|
.order_by('parent__id'))
|
||||||
|
|
||||||
@@ -3048,10 +3047,13 @@ class JobJobTasksList(BaseJobEventsList):
|
|||||||
# make appropriate changes to the task data.
|
# make appropriate changes to the task data.
|
||||||
for child_data in data.get(task_start_event.id, []):
|
for child_data in data.get(task_start_event.id, []):
|
||||||
if child_data['event'] == 'runner_on_failed':
|
if child_data['event'] == 'runner_on_failed':
|
||||||
task_data['failed'] = True
|
|
||||||
task_data['host_count'] += child_data['num']
|
task_data['host_count'] += child_data['num']
|
||||||
task_data['reported_hosts'] += child_data['num']
|
task_data['reported_hosts'] += child_data['num']
|
||||||
task_data['failed_count'] += child_data['num']
|
if child_data['failed']:
|
||||||
|
task_data['failed'] = True
|
||||||
|
task_data['failed_count'] += child_data['num']
|
||||||
|
else:
|
||||||
|
task_data['skipped_count'] += child_data['num']
|
||||||
elif child_data['event'] == 'runner_on_ok':
|
elif child_data['event'] == 'runner_on_ok':
|
||||||
task_data['host_count'] += child_data['num']
|
task_data['host_count'] += child_data['num']
|
||||||
task_data['reported_hosts'] += child_data['num']
|
task_data['reported_hosts'] += child_data['num']
|
||||||
|
|||||||
20
awx/main/migrations/0030_v302_job_survey_passwords.py
Normal file
20
awx/main/migrations/0030_v302_job_survey_passwords.py
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
import jsonfield.fields
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('main', '0029_v302_add_ask_skip_tags'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='job',
|
||||||
|
name='survey_passwords',
|
||||||
|
field=jsonfield.fields.JSONField(default={}, editable=False, blank=True),
|
||||||
|
),
|
||||||
|
]
|
||||||
18
awx/main/migrations/0031_v302_migrate_survey_passwords.py
Normal file
18
awx/main/migrations/0031_v302_migrate_survey_passwords.py
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from awx.main.migrations import _save_password_keys
|
||||||
|
from awx.main.migrations import _migration_utils as migration_utils
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('main', '0030_v302_job_survey_passwords'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RunPython(migration_utils.set_current_apps_for_migrations),
|
||||||
|
migrations.RunPython(_save_password_keys.migrate_survey_passwords),
|
||||||
|
]
|
||||||
27
awx/main/migrations/_save_password_keys.py
Normal file
27
awx/main/migrations/_save_password_keys.py
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
def survey_password_variables(survey_spec):
|
||||||
|
vars = []
|
||||||
|
# Get variables that are type password
|
||||||
|
if 'spec' not in survey_spec:
|
||||||
|
return vars
|
||||||
|
for survey_element in survey_spec['spec']:
|
||||||
|
if 'type' in survey_element and survey_element['type'] == 'password':
|
||||||
|
vars.append(survey_element['variable'])
|
||||||
|
return vars
|
||||||
|
|
||||||
|
|
||||||
|
def migrate_survey_passwords(apps, schema_editor):
|
||||||
|
'''Take the output of the Job Template password list for all that
|
||||||
|
have a survey enabled, and then save it into the job model.
|
||||||
|
'''
|
||||||
|
Job = apps.get_model('main', 'Job')
|
||||||
|
for job in Job.objects.iterator():
|
||||||
|
if not job.job_template:
|
||||||
|
continue
|
||||||
|
jt = job.job_template
|
||||||
|
if jt.survey_spec is not None and jt.survey_enabled:
|
||||||
|
password_list = survey_password_variables(jt.survey_spec)
|
||||||
|
hide_password_dict = {}
|
||||||
|
for password in password_list:
|
||||||
|
hide_password_dict[password] = "$encrypted$"
|
||||||
|
job.survey_passwords = hide_password_dict
|
||||||
|
job.save()
|
||||||
@@ -27,7 +27,7 @@ from awx.main.models.unified_jobs import * # noqa
|
|||||||
from awx.main.models.notifications import NotificationTemplate
|
from awx.main.models.notifications import NotificationTemplate
|
||||||
from awx.main.utils import decrypt_field, ignore_inventory_computed_fields
|
from awx.main.utils import decrypt_field, ignore_inventory_computed_fields
|
||||||
from awx.main.utils import emit_websocket_notification
|
from awx.main.utils import emit_websocket_notification
|
||||||
from awx.main.redact import PlainTextCleaner, REPLACE_STR
|
from awx.main.redact import PlainTextCleaner
|
||||||
from awx.main.conf import tower_settings
|
from awx.main.conf import tower_settings
|
||||||
from awx.main.fields import ImplicitRoleField
|
from awx.main.fields import ImplicitRoleField
|
||||||
from awx.main.models.mixins import ResourceMixin
|
from awx.main.models.mixins import ResourceMixin
|
||||||
@@ -249,7 +249,7 @@ class JobTemplate(UnifiedJobTemplate, JobOptions, ResourceMixin):
|
|||||||
'playbook', 'credential', 'cloud_credential', 'network_credential', 'forks', 'schedule',
|
'playbook', 'credential', 'cloud_credential', 'network_credential', 'forks', 'schedule',
|
||||||
'limit', 'verbosity', 'job_tags', 'extra_vars', 'launch_type',
|
'limit', 'verbosity', 'job_tags', 'extra_vars', 'launch_type',
|
||||||
'force_handlers', 'skip_tags', 'start_at_task', 'become_enabled',
|
'force_handlers', 'skip_tags', 'start_at_task', 'become_enabled',
|
||||||
'labels',]
|
'labels', 'survey_passwords']
|
||||||
|
|
||||||
def resource_validation_data(self):
|
def resource_validation_data(self):
|
||||||
'''
|
'''
|
||||||
@@ -447,11 +447,21 @@ class JobTemplate(UnifiedJobTemplate, JobOptions, ResourceMixin):
|
|||||||
if field == 'extra_vars' and self.survey_enabled and self.survey_spec:
|
if field == 'extra_vars' and self.survey_enabled and self.survey_spec:
|
||||||
# Accept vars defined in the survey and no others
|
# Accept vars defined in the survey and no others
|
||||||
survey_vars = [question['variable'] for question in self.survey_spec.get('spec', [])]
|
survey_vars = [question['variable'] for question in self.survey_spec.get('spec', [])]
|
||||||
for key in kwargs[field]:
|
extra_vars = kwargs[field]
|
||||||
|
if isinstance(extra_vars, basestring):
|
||||||
|
try:
|
||||||
|
extra_vars = json.loads(extra_vars)
|
||||||
|
except (ValueError, TypeError):
|
||||||
|
try:
|
||||||
|
extra_vars = yaml.safe_load(extra_vars)
|
||||||
|
assert isinstance(extra_vars, dict)
|
||||||
|
except (yaml.YAMLError, TypeError, AttributeError, AssertionError):
|
||||||
|
extra_vars = {}
|
||||||
|
for key in extra_vars:
|
||||||
if key in survey_vars:
|
if key in survey_vars:
|
||||||
prompted_fields[field][key] = kwargs[field][key]
|
prompted_fields[field][key] = extra_vars[key]
|
||||||
else:
|
else:
|
||||||
ignored_fields[field][key] = kwargs[field][key]
|
ignored_fields[field][key] = extra_vars[key]
|
||||||
else:
|
else:
|
||||||
ignored_fields[field] = kwargs[field]
|
ignored_fields[field] = kwargs[field]
|
||||||
|
|
||||||
@@ -514,6 +524,11 @@ class Job(UnifiedJob, JobOptions):
|
|||||||
editable=False,
|
editable=False,
|
||||||
through='JobHostSummary',
|
through='JobHostSummary',
|
||||||
)
|
)
|
||||||
|
survey_passwords = JSONField(
|
||||||
|
blank=True,
|
||||||
|
default={},
|
||||||
|
editable=False,
|
||||||
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _get_parent_field_name(cls):
|
def _get_parent_field_name(cls):
|
||||||
@@ -730,16 +745,12 @@ class Job(UnifiedJob, JobOptions):
|
|||||||
'''
|
'''
|
||||||
Hides fields marked as passwords in survey.
|
Hides fields marked as passwords in survey.
|
||||||
'''
|
'''
|
||||||
if self.extra_vars and self.job_template and self.job_template.survey_enabled:
|
if self.survey_passwords:
|
||||||
try:
|
extra_vars = json.loads(self.extra_vars)
|
||||||
extra_vars = json.loads(self.extra_vars)
|
extra_vars.update(self.survey_passwords)
|
||||||
for key in self.job_template.survey_password_variables():
|
return json.dumps(extra_vars)
|
||||||
if key in extra_vars:
|
else:
|
||||||
extra_vars[key] = REPLACE_STR
|
return self.extra_vars
|
||||||
return json.dumps(extra_vars)
|
|
||||||
except ValueError:
|
|
||||||
pass
|
|
||||||
return self.extra_vars
|
|
||||||
|
|
||||||
def _survey_search_and_replace(self, content):
|
def _survey_search_and_replace(self, content):
|
||||||
# Use job template survey spec to identify password fields.
|
# Use job template survey spec to identify password fields.
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ from djcelery.models import TaskMeta
|
|||||||
from awx.main.models.base import * # noqa
|
from awx.main.models.base import * # noqa
|
||||||
from awx.main.models.schedules import Schedule
|
from awx.main.models.schedules import Schedule
|
||||||
from awx.main.utils import decrypt_field, emit_websocket_notification, _inventory_updates
|
from awx.main.utils import decrypt_field, emit_websocket_notification, _inventory_updates
|
||||||
from awx.main.redact import UriCleaner
|
from awx.main.redact import UriCleaner, REPLACE_STR
|
||||||
|
|
||||||
__all__ = ['UnifiedJobTemplate', 'UnifiedJob']
|
__all__ = ['UnifiedJobTemplate', 'UnifiedJob']
|
||||||
|
|
||||||
@@ -343,6 +343,14 @@ class UnifiedJobTemplate(PolymorphicModel, CommonModelNameNotUnique, Notificatio
|
|||||||
create_kwargs[field_name] = getattr(self, field_name)
|
create_kwargs[field_name] = getattr(self, field_name)
|
||||||
new_kwargs = self._update_unified_job_kwargs(**create_kwargs)
|
new_kwargs = self._update_unified_job_kwargs(**create_kwargs)
|
||||||
unified_job = unified_job_class(**new_kwargs)
|
unified_job = unified_job_class(**new_kwargs)
|
||||||
|
# For JobTemplate-based jobs with surveys, save list for perma-redaction
|
||||||
|
if (hasattr(self, 'survey_spec') and getattr(self, 'survey_enabled', False) and
|
||||||
|
not getattr(unified_job, 'survey_passwords', False)):
|
||||||
|
password_list = self.survey_password_variables()
|
||||||
|
hide_password_dict = {}
|
||||||
|
for password in password_list:
|
||||||
|
hide_password_dict[password] = REPLACE_STR
|
||||||
|
unified_job.survey_passwords = hide_password_dict
|
||||||
unified_job.save()
|
unified_job.save()
|
||||||
for field_name, src_field_value in m2m_fields.iteritems():
|
for field_name, src_field_value in m2m_fields.iteritems():
|
||||||
dest_field = getattr(unified_job, field_name)
|
dest_field = getattr(unified_job, field_name)
|
||||||
|
|||||||
@@ -26,16 +26,16 @@ def survey_spec_factory():
|
|||||||
return create_survey_spec
|
return create_survey_spec
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def job_with_secret_key_factory(job_template_factory):
|
def job_template_with_survey_passwords_factory(job_template_factory):
|
||||||
def rf(persisted):
|
def rf(persisted):
|
||||||
"Returns job with linked JT survey with password survey questions"
|
"Returns job with linked JT survey with password survey questions"
|
||||||
objects = job_template_factory('jt', organization='org1', survey=[
|
objects = job_template_factory('jt', organization='org1', survey=[
|
||||||
{'variable': 'submitter_email', 'type': 'text', 'default': 'foobar@redhat.com'},
|
{'variable': 'submitter_email', 'type': 'text', 'default': 'foobar@redhat.com'},
|
||||||
{'variable': 'secret_key', 'default': '6kQngg3h8lgiSTvIEb21', 'type': 'password'},
|
{'variable': 'secret_key', 'default': '6kQngg3h8lgiSTvIEb21', 'type': 'password'},
|
||||||
{'variable': 'SSN', 'type': 'password'}], jobs=[1], persisted=persisted)
|
{'variable': 'SSN', 'type': 'password'}], persisted=persisted)
|
||||||
return objects.jobs[1]
|
return objects.job_template
|
||||||
return rf
|
return rf
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def job_with_secret_key_unit(job_with_secret_key_factory):
|
def job_template_with_survey_passwords_unit(job_template_with_survey_passwords_factory):
|
||||||
return job_with_secret_key_factory(persisted=False)
|
return job_template_with_survey_passwords_factory(persisted=False)
|
||||||
|
|||||||
@@ -3,12 +3,14 @@ import mock
|
|||||||
|
|
||||||
# AWX
|
# AWX
|
||||||
from awx.api.serializers import JobTemplateSerializer, JobLaunchSerializer
|
from awx.api.serializers import JobTemplateSerializer, JobLaunchSerializer
|
||||||
from awx.main.models.jobs import JobTemplate
|
from awx.main.models.jobs import JobTemplate, Job
|
||||||
from awx.main.models.projects import ProjectOptions
|
from awx.main.models.projects import ProjectOptions
|
||||||
|
from awx.main.migrations import _save_password_keys as save_password_keys
|
||||||
|
|
||||||
# Django
|
# Django
|
||||||
from django.test.client import RequestFactory
|
from django.test.client import RequestFactory
|
||||||
from django.core.urlresolvers import reverse
|
from django.core.urlresolvers import reverse
|
||||||
|
from django.apps import apps
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def project_playbooks(self):
|
def project_playbooks(self):
|
||||||
@@ -348,3 +350,20 @@ def test_disallow_template_delete_on_running_job(job_template_factory, delete, a
|
|||||||
objects.job_template.create_unified_job()
|
objects.job_template.create_unified_job()
|
||||||
delete_response = delete(reverse('api:job_template_detail', args=[objects.job_template.pk]), user=admin_user)
|
delete_response = delete(reverse('api:job_template_detail', args=[objects.job_template.pk]), user=admin_user)
|
||||||
assert delete_response.status_code == 409
|
assert delete_response.status_code == 409
|
||||||
|
|
||||||
|
@pytest.mark.django_db
|
||||||
|
def test_save_survey_passwords_to_job(job_template_with_survey_passwords):
|
||||||
|
"""Test that when a new job is created, the survey_passwords field is
|
||||||
|
given all of the passwords that exist in the JT survey"""
|
||||||
|
job = job_template_with_survey_passwords.create_unified_job()
|
||||||
|
assert job.survey_passwords == {'SSN': '$encrypted$', 'secret_key': '$encrypted$'}
|
||||||
|
|
||||||
|
@pytest.mark.django_db
|
||||||
|
def test_save_survey_passwords_on_migration(job_template_with_survey_passwords):
|
||||||
|
"""Test that when upgrading to 3.0.2, the jobs connected to a JT that has
|
||||||
|
a survey with passwords in it, the survey passwords get saved to the
|
||||||
|
job survey_passwords field."""
|
||||||
|
Job.objects.create(job_template=job_template_with_survey_passwords)
|
||||||
|
save_password_keys.migrate_survey_passwords(apps, None)
|
||||||
|
job = job_template_with_survey_passwords.jobs.all()[0]
|
||||||
|
assert job.survey_passwords == {'SSN': '$encrypted$', 'secret_key': '$encrypted$'}
|
||||||
|
|||||||
@@ -193,7 +193,8 @@ def test_launch_with_non_empty_survey_spec_no_license(job_template_factory, post
|
|||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
@pytest.mark.survey
|
@pytest.mark.survey
|
||||||
def test_redact_survey_passwords_in_activity_stream(job_with_secret_key):
|
def test_redact_survey_passwords_in_activity_stream(job_template_with_survey_passwords):
|
||||||
|
job_template_with_survey_passwords.create_unified_job()
|
||||||
AS_record = ActivityStream.objects.filter(object1='job').all()[0]
|
AS_record = ActivityStream.objects.filter(object1='job').all()[0]
|
||||||
changes_dict = json.loads(AS_record.changes)
|
changes_dict = json.loads(AS_record.changes)
|
||||||
extra_vars = json.loads(changes_dict['extra_vars'])
|
extra_vars = json.loads(changes_dict['extra_vars'])
|
||||||
|
|||||||
@@ -206,8 +206,8 @@ def notification(notification_template):
|
|||||||
subject='email subject')
|
subject='email subject')
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def job_with_secret_key(job_with_secret_key_factory):
|
def job_template_with_survey_passwords(job_template_with_survey_passwords_factory):
|
||||||
return job_with_secret_key_factory(persisted=True)
|
return job_template_with_survey_passwords_factory(persisted=True)
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def admin(user):
|
def admin(user):
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import pytest
|
import pytest
|
||||||
|
import json
|
||||||
|
|
||||||
|
|
||||||
def test_missing_project_error(job_template_factory):
|
def test_missing_project_error(job_template_factory):
|
||||||
@@ -34,7 +35,18 @@ def test_inventory_credential_contradictions(job_template_factory):
|
|||||||
assert 'inventory' in validation_errors
|
assert 'inventory' in validation_errors
|
||||||
assert 'credential' in validation_errors
|
assert 'credential' in validation_errors
|
||||||
|
|
||||||
|
def test_survey_answers_as_string(job_template_factory):
|
||||||
|
objects = job_template_factory(
|
||||||
|
'job-template-with-survey',
|
||||||
|
survey=['var1'],
|
||||||
|
persisted=False)
|
||||||
|
jt = objects.job_template
|
||||||
|
user_extra_vars = json.dumps({'var1': 'asdf'})
|
||||||
|
accepted, ignored = jt._accept_or_ignore_job_kwargs(extra_vars=user_extra_vars)
|
||||||
|
assert 'var1' in accepted['extra_vars']
|
||||||
|
|
||||||
@pytest.mark.survey
|
@pytest.mark.survey
|
||||||
def test_survey_password_list(job_with_secret_key_unit):
|
def test_job_template_survey_password_redaction(job_template_with_survey_passwords_unit):
|
||||||
"""Verify that survey_password_variables method gives a list of survey passwords"""
|
"""Tests the JobTemplate model's funciton to redact passwords from
|
||||||
assert job_with_secret_key_unit.job_template.survey_password_variables() == ['secret_key', 'SSN']
|
extra_vars - used when creating a new job"""
|
||||||
|
assert job_template_with_survey_passwords_unit.survey_password_variables() == ['secret_key', 'SSN']
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import pytest
|
|||||||
import json
|
import json
|
||||||
|
|
||||||
from awx.main.tasks import RunJob
|
from awx.main.tasks import RunJob
|
||||||
|
from awx.main.models import Job
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
@@ -14,9 +15,19 @@ def job(mocker):
|
|||||||
'launch_type': 'manual'})
|
'launch_type': 'manual'})
|
||||||
|
|
||||||
@pytest.mark.survey
|
@pytest.mark.survey
|
||||||
def test_job_redacted_extra_vars(job_with_secret_key_unit):
|
def test_job_survey_password_redaction():
|
||||||
"""Verify that this method redacts vars marked as passwords in a survey"""
|
"""Tests the Job model's funciton to redact passwords from
|
||||||
assert json.loads(job_with_secret_key_unit.display_extra_vars()) == {
|
extra_vars - used when displaying job information"""
|
||||||
|
job = Job(
|
||||||
|
name="test-job-with-passwords",
|
||||||
|
extra_vars=json.dumps({
|
||||||
|
'submitter_email': 'foobar@redhat.com',
|
||||||
|
'secret_key': '6kQngg3h8lgiSTvIEb21',
|
||||||
|
'SSN': '123-45-6789'}),
|
||||||
|
survey_passwords={
|
||||||
|
'secret_key': '$encrypted$',
|
||||||
|
'SSN': '$encrypted$'})
|
||||||
|
assert json.loads(job.display_extra_vars()) == {
|
||||||
'submitter_email': 'foobar@redhat.com',
|
'submitter_email': 'foobar@redhat.com',
|
||||||
'secret_key': '$encrypted$',
|
'secret_key': '$encrypted$',
|
||||||
'SSN': '$encrypted$'}
|
'SSN': '$encrypted$'}
|
||||||
|
|||||||
@@ -18,10 +18,11 @@ import json
|
|||||||
# http://urllib3.readthedocs.org/en/latest/security.html#disabling-warnings
|
# http://urllib3.readthedocs.org/en/latest/security.html#disabling-warnings
|
||||||
requests.packages.urllib3.disable_warnings()
|
requests.packages.urllib3.disable_warnings()
|
||||||
|
|
||||||
|
|
||||||
class CloudFormsInventory(object):
|
class CloudFormsInventory(object):
|
||||||
|
|
||||||
def _empty_inventory(self):
|
def _empty_inventory(self):
|
||||||
return {"_meta" : {"hostvars" : {}}}
|
return {"_meta": {"hostvars": {}}}
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
''' Main execution path '''
|
''' Main execution path '''
|
||||||
@@ -43,7 +44,7 @@ class CloudFormsInventory(object):
|
|||||||
|
|
||||||
# This doesn't exist yet and needs to be added
|
# This doesn't exist yet and needs to be added
|
||||||
if self.args.host:
|
if self.args.host:
|
||||||
data2 = { }
|
data2 = {}
|
||||||
print json.dumps(data2, indent=2)
|
print json.dumps(data2, indent=2)
|
||||||
|
|
||||||
def parse_cli_args(self):
|
def parse_cli_args(self):
|
||||||
@@ -51,9 +52,9 @@ class CloudFormsInventory(object):
|
|||||||
|
|
||||||
parser = argparse.ArgumentParser(description='Produce an Ansible Inventory file based on CloudForms')
|
parser = argparse.ArgumentParser(description='Produce an Ansible Inventory file based on CloudForms')
|
||||||
parser.add_argument('--list', action='store_true', default=False,
|
parser.add_argument('--list', action='store_true', default=False,
|
||||||
help='List instances (default: False)')
|
help='List instances (default: False)')
|
||||||
parser.add_argument('--host', action='store',
|
parser.add_argument('--host', action='store',
|
||||||
help='Get all the variables about a specific instance')
|
help='Get all the variables about a specific instance')
|
||||||
self.args = parser.parse_args()
|
self.args = parser.parse_args()
|
||||||
|
|
||||||
def read_settings(self):
|
def read_settings(self):
|
||||||
@@ -97,30 +98,47 @@ class CloudFormsInventory(object):
|
|||||||
|
|
||||||
def get_hosts(self):
|
def get_hosts(self):
|
||||||
''' Gets host from CloudForms '''
|
''' Gets host from CloudForms '''
|
||||||
r = requests.get("https://" + self.cloudforms_hostname + "/api/vms?expand=resources&attributes=name,power_state", auth=(self.cloudforms_username,self.cloudforms_password), verify=False)
|
r = requests.get("https://{0}/api/vms?expand=resources&attributes=all".format(self.cloudforms_hostname),
|
||||||
|
auth=(self.cloudforms_username, self.cloudforms_password), verify=False)
|
||||||
obj = r.json()
|
obj = r.json()
|
||||||
|
|
||||||
#Remove objects that don't matter
|
# Create groups+hosts based on host data
|
||||||
del obj["count"]
|
for resource in obj.get('resources', []):
|
||||||
del obj["subcount"]
|
|
||||||
del obj["name"]
|
|
||||||
|
|
||||||
#Create a new list to grab VMs with power_state on to add to a new list
|
# Maintain backwards compat by creating `Dynamic_CloudForms` group
|
||||||
#I'm sure there is a cleaner way to do this
|
if 'Dynamic_CloudForms' not in self.inventory:
|
||||||
newlist = []
|
self.inventory['Dynamic_CloudForms'] = []
|
||||||
getnext = False
|
self.inventory['Dynamic_CloudForms'].append(resource['name'])
|
||||||
for x in obj.items():
|
|
||||||
for y in x[1]:
|
# Add host to desired groups
|
||||||
for z in y.items():
|
for key in ('vendor', 'type', 'location'):
|
||||||
if getnext == True:
|
if key in resource:
|
||||||
newlist.append(z[1])
|
# Create top-level group
|
||||||
getnext = False
|
if key not in self.inventory:
|
||||||
if ( z[0] == "power_state" and z[1] == "on" ):
|
self.inventory[key] = dict(children=[], vars={}, hosts=[])
|
||||||
getnext = True
|
# if resource['name'] not in self.inventory[key]['hosts']:
|
||||||
newdict = {'hosts': newlist}
|
# self.inventory[key]['hosts'].append(resource['name'])
|
||||||
newdict2 = {'Dynamic_CloudForms': newdict}
|
|
||||||
print json.dumps(newdict2, indent=2)
|
# Create sub-group
|
||||||
|
if resource[key] not in self.inventory:
|
||||||
|
self.inventory[resource[key]] = dict(children=[], vars={}, hosts=[])
|
||||||
|
# self.inventory[resource[key]]['hosts'].append(resource['name'])
|
||||||
|
|
||||||
|
# Add sub-group, as a child of top-level
|
||||||
|
if resource[key] not in self.inventory[key]['children']:
|
||||||
|
self.inventory[key]['children'].append(resource[key])
|
||||||
|
|
||||||
|
# Add host to sub-group
|
||||||
|
if resource['name'] not in self.inventory[resource[key]]:
|
||||||
|
self.inventory[resource[key]]['hosts'].append(resource['name'])
|
||||||
|
|
||||||
|
# Delete 'actions' key
|
||||||
|
del resource['actions']
|
||||||
|
|
||||||
|
# Add _meta hostvars
|
||||||
|
self.inventory['_meta']['hostvars'][resource['name']] = resource
|
||||||
|
|
||||||
|
print json.dumps(self.inventory, indent=2)
|
||||||
|
|
||||||
# Run the script
|
# Run the script
|
||||||
CloudFormsInventory()
|
CloudFormsInventory()
|
||||||
|
|||||||
@@ -682,6 +682,16 @@ SATELLITE6_HOST_FILTER = r'^.+$'
|
|||||||
SATELLITE6_EXCLUDE_EMPTY_GROUPS = True
|
SATELLITE6_EXCLUDE_EMPTY_GROUPS = True
|
||||||
SATELLITE6_INSTANCE_ID_VAR = 'foreman.id'
|
SATELLITE6_INSTANCE_ID_VAR = 'foreman.id'
|
||||||
|
|
||||||
|
# ---------------------
|
||||||
|
# ----- CloudForms -----
|
||||||
|
# ---------------------
|
||||||
|
CLOUDFORMS_ENABLED_VAR = 'power_state'
|
||||||
|
CLOUDFORMS_ENABLED_VALUE = 'on'
|
||||||
|
CLOUDFORMS_GROUP_FILTER = r'^.+$'
|
||||||
|
CLOUDFORMS_HOST_FILTER = r'^.+$'
|
||||||
|
CLOUDFORMS_EXCLUDE_EMPTY_GROUPS = True
|
||||||
|
CLOUDFORMS_INSTANCE_ID_VAR = 'id'
|
||||||
|
|
||||||
# ---------------------
|
# ---------------------
|
||||||
# -- Activity Stream --
|
# -- Activity Stream --
|
||||||
# ---------------------
|
# ---------------------
|
||||||
|
|||||||
@@ -46,13 +46,13 @@ export function CredentialsList($scope, $rootScope, $location, $log,
|
|||||||
Wait('stop');
|
Wait('stop');
|
||||||
$('#prompt-modal').modal('hide');
|
$('#prompt-modal').modal('hide');
|
||||||
|
|
||||||
list.fields.kind.searchOptions = $scope.credential_kind_options;
|
list.fields.kind.searchOptions = $scope.credential_kind_options_list;
|
||||||
|
|
||||||
// Translate the kind value
|
// Translate the kind value
|
||||||
for (i = 0; i < $scope.credentials.length; i++) {
|
for (i = 0; i < $scope.credentials.length; i++) {
|
||||||
for (j = 0; j < $scope.credential_kind_options.length; j++) {
|
for (j = 0; j < $scope.credential_kind_options_list.length; j++) {
|
||||||
if ($scope.credential_kind_options[j].value === $scope.credentials[i].kind) {
|
if ($scope.credential_kind_options_list[j].value === $scope.credentials[i].kind) {
|
||||||
$scope.credentials[i].kind = $scope.credential_kind_options[j].label;
|
$scope.credentials[i].kind = $scope.credential_kind_options_list[j].label;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -77,6 +77,15 @@ export function CredentialsList($scope, $rootScope, $location, $log,
|
|||||||
$scope.search(list.iterator);
|
$scope.search(list.iterator);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Load the list of options for Kind
|
||||||
|
GetChoices({
|
||||||
|
scope: $scope,
|
||||||
|
url: defaultUrl,
|
||||||
|
field: 'kind',
|
||||||
|
variable: 'credential_kind_options_list',
|
||||||
|
callback: 'choicesReadyCredential'
|
||||||
|
});
|
||||||
|
|
||||||
$scope.addCredential = function () {
|
$scope.addCredential = function () {
|
||||||
$state.transitionTo('credentials.add');
|
$state.transitionTo('credentials.add');
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -100,6 +100,7 @@
|
|||||||
// equal to case 'ec2' || 'rax' || 'azure' || 'azure_rm' || 'vmware' || 'satellite6' || 'cloudforms' || 'openstack'
|
// equal to case 'ec2' || 'rax' || 'azure' || 'azure_rm' || 'vmware' || 'satellite6' || 'cloudforms' || 'openstack'
|
||||||
else{
|
else{
|
||||||
var credentialBasePath = (source === 'ec2') ? GetBasePath('credentials') + '?kind=aws' : GetBasePath('credentials') + (source === '' ? '' : '?kind=' + (source));
|
var credentialBasePath = (source === 'ec2') ? GetBasePath('credentials') + '?kind=aws' : GetBasePath('credentials') + (source === '' ? '' : '?kind=' + (source));
|
||||||
|
$scope.cloudCredentialRequired = source !== '' && source !== 'custom' && source !== 'ec2' ? true : false;
|
||||||
CredentialList.basePath = credentialBasePath;
|
CredentialList.basePath = credentialBasePath;
|
||||||
LookUpInit({
|
LookUpInit({
|
||||||
scope: $scope,
|
scope: $scope,
|
||||||
@@ -122,7 +123,7 @@
|
|||||||
$scope.group_by_choices = source === 'ec2' ? $scope.ec2_group_by : null;
|
$scope.group_by_choices = source === 'ec2' ? $scope.ec2_group_by : null;
|
||||||
// azure_rm regions choices are keyed as "azure" in an OPTIONS request to the inventory_sources endpoint
|
// azure_rm regions choices are keyed as "azure" in an OPTIONS request to the inventory_sources endpoint
|
||||||
$scope.source_region_choices = source === 'azure_rm' ? $scope.azure_regions : $scope[source + '_regions'];
|
$scope.source_region_choices = source === 'azure_rm' ? $scope.azure_regions : $scope[source + '_regions'];
|
||||||
$scope.cloudCredentialRequired = source !== '' && source !== 'custom' ? true : false;
|
$scope.cloudCredentialRequired = source !== '' && source !== 'custom' && source !== 'ec2' ? true : false;
|
||||||
$scope.group_by = null;
|
$scope.group_by = null;
|
||||||
$scope.source_regions = null;
|
$scope.source_regions = null;
|
||||||
$scope.credential = null;
|
$scope.credential = null;
|
||||||
|
|||||||
@@ -100,6 +100,7 @@
|
|||||||
else{
|
else{
|
||||||
var credentialBasePath = (source.value === 'ec2') ? GetBasePath('credentials') + '?kind=aws' : GetBasePath('credentials') + (source.value === '' ? '' : '?kind=' + (source.value));
|
var credentialBasePath = (source.value === 'ec2') ? GetBasePath('credentials') + '?kind=aws' : GetBasePath('credentials') + (source.value === '' ? '' : '?kind=' + (source.value));
|
||||||
CredentialList.basePath = credentialBasePath;
|
CredentialList.basePath = credentialBasePath;
|
||||||
|
$scope.cloudCredentialRequired = source.value !== '' && source.value !== 'custom' && source.value !== 'ec2' ? true : false;
|
||||||
LookUpInit({
|
LookUpInit({
|
||||||
scope: $scope,
|
scope: $scope,
|
||||||
url: credentialBasePath,
|
url: credentialBasePath,
|
||||||
@@ -122,7 +123,7 @@
|
|||||||
// reset fields
|
// reset fields
|
||||||
// azure_rm regions choices are keyed as "azure" in an OPTIONS request to the inventory_sources endpoint
|
// azure_rm regions choices are keyed as "azure" in an OPTIONS request to the inventory_sources endpoint
|
||||||
$scope.source_region_choices = source.value === 'azure_rm' ? $scope.azure_regions : $scope[source.value + '_regions'];
|
$scope.source_region_choices = source.value === 'azure_rm' ? $scope.azure_regions : $scope[source.value + '_regions'];
|
||||||
$scope.cloudCredentialRequired = source.value !== '' && source.value !== 'custom' ? true : false;
|
$scope.cloudCredentialRequired = source.value !== '' && source.value !== 'custom' && source.value !== 'ec2' ? true : false;
|
||||||
$scope.group_by = null;
|
$scope.group_by = null;
|
||||||
$scope.source_regions = null;
|
$scope.source_regions = null;
|
||||||
$scope.credential = null;
|
$scope.credential = null;
|
||||||
|
|||||||
@@ -149,6 +149,12 @@ export default
|
|||||||
if(field.type === 'number'){
|
if(field.type === 'number'){
|
||||||
$scope[i] = Number($scope[i]);
|
$scope[i] = Number($scope[i]);
|
||||||
}
|
}
|
||||||
|
if(field.name === "username" && $scope.notification_type.value === "email" && value === null){
|
||||||
|
$scope[i] = "";
|
||||||
|
}
|
||||||
|
if(field.type === 'sensitive' && value === null){
|
||||||
|
$scope[i] = "";
|
||||||
|
}
|
||||||
return $scope[i];
|
return $scope[i];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -223,6 +223,12 @@ export default
|
|||||||
if(field.type === 'number'){
|
if(field.type === 'number'){
|
||||||
$scope[i] = Number($scope[i]);
|
$scope[i] = Number($scope[i]);
|
||||||
}
|
}
|
||||||
|
if(field.name === "username" && $scope.notification_type.value === "email" && value === null){
|
||||||
|
$scope[i] = "";
|
||||||
|
}
|
||||||
|
if(field.type === 'sensitive' && value === null){
|
||||||
|
$scope[i] = "";
|
||||||
|
}
|
||||||
return $scope[i];
|
return $scope[i];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -59,10 +59,6 @@ export default function() {
|
|||||||
username: {
|
username: {
|
||||||
label: 'Username',
|
label: 'Username',
|
||||||
type: 'text',
|
type: 'text',
|
||||||
awRequiredWhen: {
|
|
||||||
reqExpression: "email_required",
|
|
||||||
init: "false"
|
|
||||||
},
|
|
||||||
ngShow: "notification_type.value == 'email' ",
|
ngShow: "notification_type.value == 'email' ",
|
||||||
subForm: 'typeSubForm'
|
subForm: 'typeSubForm'
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ function () {
|
|||||||
obj.passwordLabel = ' Password';
|
obj.passwordLabel = ' Password';
|
||||||
obj.email_required = true;
|
obj.email_required = true;
|
||||||
obj.port_required = true;
|
obj.port_required = true;
|
||||||
obj.password_required = true;
|
obj.password_required = false;
|
||||||
break;
|
break;
|
||||||
case 'slack':
|
case 'slack':
|
||||||
obj.tokenLabel =' Token';
|
obj.tokenLabel =' Token';
|
||||||
|
|||||||
@@ -14,8 +14,11 @@ attempt=0
|
|||||||
while [[ $attempt -lt $retry_attempts ]]
|
while [[ $attempt -lt $retry_attempts ]]
|
||||||
do
|
do
|
||||||
status_code=`curl -s -i --data "host_config_key=$2" http://$1/api/v1/job_templates/$3/callback/ | head -n 1 | awk '{print $2}'`
|
status_code=`curl -s -i --data "host_config_key=$2" http://$1/api/v1/job_templates/$3/callback/ | head -n 1 | awk '{print $2}'`
|
||||||
if [[ $status_code == 202 ]]
|
if [[ $status_code -ge 300 ]]
|
||||||
then
|
then
|
||||||
|
echo "${status_code} received, encountered problem, halting."
|
||||||
|
exit 1
|
||||||
|
else
|
||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
attempt=$(( attempt + 1 ))
|
attempt=$(( attempt + 1 ))
|
||||||
|
|||||||
Reference in New Issue
Block a user