mirror of
https://github.com/ansible/awx.git
synced 2026-05-07 01:17:37 -02:30
remove a number of unnecessary 3.2 migrations
This commit is contained in:
@@ -2,7 +2,6 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
from django.db import migrations
|
from django.db import migrations
|
||||||
from awx.conf.migrations import _reencrypt
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
@@ -12,5 +11,8 @@ class Migration(migrations.Migration):
|
|||||||
]
|
]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
migrations.RunPython(_reencrypt.replace_aesecb_fernet),
|
# This list is intentionally empty.
|
||||||
|
# Tower 3.2 included several data migrations that are no longer
|
||||||
|
# necessary (this list is now empty because Tower 3.2 is past EOL and
|
||||||
|
# cannot be directly upgraded to modern versions of Tower)
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -1,30 +1,13 @@
|
|||||||
import base64
|
import base64
|
||||||
import hashlib
|
import hashlib
|
||||||
|
|
||||||
from django.utils.encoding import smart_str
|
|
||||||
|
|
||||||
from cryptography.hazmat.backends import default_backend
|
from cryptography.hazmat.backends import default_backend
|
||||||
from cryptography.hazmat.primitives.ciphers import Cipher
|
from cryptography.hazmat.primitives.ciphers import Cipher
|
||||||
from cryptography.hazmat.primitives.ciphers.algorithms import AES
|
from cryptography.hazmat.primitives.ciphers.algorithms import AES
|
||||||
from cryptography.hazmat.primitives.ciphers.modes import ECB
|
from cryptography.hazmat.primitives.ciphers.modes import ECB
|
||||||
|
|
||||||
from awx.conf import settings_registry
|
|
||||||
|
|
||||||
|
__all__ = ['get_encryption_key', 'decrypt_field']
|
||||||
__all__ = ['replace_aesecb_fernet', 'get_encryption_key', 'encrypt_field',
|
|
||||||
'decrypt_value', 'decrypt_value', 'should_decrypt_field']
|
|
||||||
|
|
||||||
|
|
||||||
def replace_aesecb_fernet(apps, schema_editor):
|
|
||||||
from awx.main.utils.encryption import encrypt_field
|
|
||||||
Setting = apps.get_model('conf', 'Setting')
|
|
||||||
|
|
||||||
for setting in Setting.objects.filter().order_by('pk'):
|
|
||||||
if settings_registry.is_setting_encrypted(setting.key):
|
|
||||||
if should_decrypt_field(setting.value):
|
|
||||||
setting.value = decrypt_field(setting, 'value')
|
|
||||||
setting.value = encrypt_field(setting, 'value')
|
|
||||||
setting.save()
|
|
||||||
|
|
||||||
|
|
||||||
def get_encryption_key(field_name, pk=None):
|
def get_encryption_key(field_name, pk=None):
|
||||||
@@ -76,38 +59,3 @@ def decrypt_field(instance, field_name, subfield=None):
|
|||||||
key = get_encryption_key(field_name, getattr(instance, 'pk', None))
|
key = get_encryption_key(field_name, getattr(instance, 'pk', None))
|
||||||
|
|
||||||
return decrypt_value(key, value)
|
return decrypt_value(key, value)
|
||||||
|
|
||||||
|
|
||||||
def encrypt_field(instance, field_name, ask=False, subfield=None, skip_utf8=False):
|
|
||||||
'''
|
|
||||||
Return content of the given instance and field name encrypted.
|
|
||||||
'''
|
|
||||||
value = getattr(instance, field_name)
|
|
||||||
if isinstance(value, dict) and subfield is not None:
|
|
||||||
value = value[subfield]
|
|
||||||
if not value or value.startswith('$encrypted$') or (ask and value == 'ASK'):
|
|
||||||
return value
|
|
||||||
if skip_utf8:
|
|
||||||
utf8 = False
|
|
||||||
else:
|
|
||||||
utf8 = type(value) == str
|
|
||||||
value = smart_str(value)
|
|
||||||
key = get_encryption_key(field_name, getattr(instance, 'pk', None))
|
|
||||||
encryptor = Cipher(AES(key), ECB(), default_backend()).encryptor()
|
|
||||||
block_size = 16
|
|
||||||
while len(value) % block_size != 0:
|
|
||||||
value += '\x00'
|
|
||||||
encrypted = encryptor.update(value) + encryptor.finalize()
|
|
||||||
b64data = base64.b64encode(encrypted)
|
|
||||||
tokens = ['$encrypted', 'AES', b64data]
|
|
||||||
if utf8:
|
|
||||||
# If the value to encrypt is utf-8, we need to add a marker so we
|
|
||||||
# know to decode the data when it's decrypted later
|
|
||||||
tokens.insert(1, 'UTF8')
|
|
||||||
return '$'.join(tokens)
|
|
||||||
|
|
||||||
|
|
||||||
def should_decrypt_field(value):
|
|
||||||
if hasattr(value, 'startswith'):
|
|
||||||
return value.startswith('$encrypted$') and '$AESCBC$' not in value
|
|
||||||
return False
|
|
||||||
|
|||||||
@@ -7,12 +7,6 @@ from django.db import migrations, models
|
|||||||
|
|
||||||
# AWX
|
# AWX
|
||||||
from awx.main.migrations import ActivityStreamDisabledMigration
|
from awx.main.migrations import ActivityStreamDisabledMigration
|
||||||
from awx.main.migrations import _inventory_source as invsrc
|
|
||||||
from awx.main.migrations import _migration_utils as migration_utils
|
|
||||||
from awx.main.migrations import _reencrypt as reencrypt
|
|
||||||
from awx.main.migrations import _scan_jobs as scan_jobs
|
|
||||||
from awx.main.migrations import _credentialtypes as credentialtypes
|
|
||||||
from awx.main.migrations import _azure_credentials as azurecreds
|
|
||||||
import awx.main.fields
|
import awx.main.fields
|
||||||
|
|
||||||
|
|
||||||
@@ -23,16 +17,8 @@ class Migration(ActivityStreamDisabledMigration):
|
|||||||
]
|
]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
# Inventory Refresh
|
# This list is intentionally empty.
|
||||||
migrations.RunPython(migration_utils.set_current_apps_for_migrations),
|
# Tower 3.2 included several data migrations that are no longer
|
||||||
migrations.RunPython(invsrc.remove_rax_inventory_sources),
|
# necessary (this list is now empty because Tower 3.2 is past EOL and
|
||||||
migrations.RunPython(azurecreds.remove_azure_credentials),
|
# cannot be directly upgraded to modern versions of Tower)
|
||||||
migrations.RunPython(invsrc.remove_azure_inventory_sources),
|
|
||||||
migrations.RunPython(invsrc.remove_inventory_source_with_no_inventory_link),
|
|
||||||
migrations.RunPython(invsrc.rename_inventory_sources),
|
|
||||||
migrations.RunPython(reencrypt.replace_aesecb_fernet),
|
|
||||||
migrations.RunPython(scan_jobs.migrate_scan_job_templates),
|
|
||||||
|
|
||||||
migrations.RunPython(credentialtypes.migrate_to_v2_credentials),
|
|
||||||
migrations.RunPython(credentialtypes.migrate_job_credentials),
|
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -15,8 +15,6 @@ class Migration(migrations.Migration):
|
|||||||
]
|
]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
migrations.RunPython(migration_utils.set_current_apps_for_migrations),
|
|
||||||
migrations.RunPython(credentialtypes.create_rhv_tower_credtype),
|
|
||||||
migrations.AlterField(
|
migrations.AlterField(
|
||||||
model_name='inventorysource',
|
model_name='inventorysource',
|
||||||
name='source',
|
name='source',
|
||||||
|
|||||||
@@ -3,8 +3,6 @@ from __future__ import unicode_literals
|
|||||||
|
|
||||||
from django.db import migrations
|
from django.db import migrations
|
||||||
from awx.main.migrations import ActivityStreamDisabledMigration
|
from awx.main.migrations import ActivityStreamDisabledMigration
|
||||||
from awx.main.migrations import _reencrypt as reencrypt
|
|
||||||
from awx.main.migrations import _migration_utils as migration_utils
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(ActivityStreamDisabledMigration):
|
class Migration(ActivityStreamDisabledMigration):
|
||||||
@@ -14,6 +12,8 @@ class Migration(ActivityStreamDisabledMigration):
|
|||||||
]
|
]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
migrations.RunPython(migration_utils.set_current_apps_for_migrations),
|
# This list is intentionally empty.
|
||||||
migrations.RunPython(reencrypt.encrypt_survey_passwords),
|
# Tower 3.2 included several data migrations that are no longer
|
||||||
|
# necessary (this list is now empty because Tower 3.2 is past EOL and
|
||||||
|
# cannot be directly upgraded to modern versions of Tower)
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -1,10 +1,6 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
# AWX
|
|
||||||
from awx.main.migrations import _migration_utils as migration_utils
|
|
||||||
from awx.main.migrations import _credentialtypes as credentialtypes
|
|
||||||
|
|
||||||
from django.db import migrations
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
@@ -15,6 +11,8 @@ class Migration(migrations.Migration):
|
|||||||
]
|
]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
migrations.RunPython(migration_utils.set_current_apps_for_migrations),
|
# This list is intentionally empty.
|
||||||
migrations.RunPython(credentialtypes.add_azure_cloud_environment_field),
|
# Tower 3.2 included several data migrations that are no longer
|
||||||
|
# necessary (this list is now empty because Tower 3.2 is past EOL and
|
||||||
|
# cannot be directly upgraded to modern versions of Tower)
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -1,15 +0,0 @@
|
|||||||
import logging
|
|
||||||
|
|
||||||
from django.db.models import Q
|
|
||||||
|
|
||||||
logger = logging.getLogger('awx.main.migrations')
|
|
||||||
|
|
||||||
|
|
||||||
def remove_azure_credentials(apps, schema_editor):
|
|
||||||
'''Azure is not supported as of 3.2 and greater. Instead, azure_rm is
|
|
||||||
supported.
|
|
||||||
'''
|
|
||||||
Credential = apps.get_model('main', 'Credential')
|
|
||||||
logger.debug("Removing all Azure Credentials from database.")
|
|
||||||
Credential.objects.filter(kind='azure').delete()
|
|
||||||
|
|
||||||
@@ -1,6 +1,4 @@
|
|||||||
from awx.main import utils
|
|
||||||
from awx.main.models import CredentialType
|
from awx.main.models import CredentialType
|
||||||
from awx.main.utils.encryption import encrypt_field, decrypt_field
|
|
||||||
from django.db.models import Q
|
from django.db.models import Q
|
||||||
|
|
||||||
|
|
||||||
@@ -61,16 +59,6 @@ def _disassociate_non_insights_projects(apps, cred):
|
|||||||
apps.get_model('main', 'Project').objects.filter(~Q(scm_type='insights') & Q(credential=cred)).update(credential=None)
|
apps.get_model('main', 'Project').objects.filter(~Q(scm_type='insights') & Q(credential=cred)).update(credential=None)
|
||||||
|
|
||||||
|
|
||||||
def migrate_to_v2_credentials(apps, schema_editor):
|
|
||||||
# TODO: remove once legacy/EOL'd Towers no longer support this upgrade path
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
def migrate_job_credentials(apps, schema_editor):
|
|
||||||
# TODO: remove once legacy/EOL'd Towers no longer support this upgrade path
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
def add_vault_id_field(apps, schema_editor):
|
def add_vault_id_field(apps, schema_editor):
|
||||||
# this is no longer necessary; schemas are defined in code
|
# this is no longer necessary; schemas are defined in code
|
||||||
pass
|
pass
|
||||||
@@ -81,21 +69,11 @@ def remove_vault_id_field(apps, schema_editor):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
def create_rhv_tower_credtype(apps, schema_editor):
|
|
||||||
# this is no longer necessary; schemas are defined in code
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
def add_tower_verify_field(apps, schema_editor):
|
def add_tower_verify_field(apps, schema_editor):
|
||||||
# this is no longer necessary; schemas are defined in code
|
# this is no longer necessary; schemas are defined in code
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
def add_azure_cloud_environment_field(apps, schema_editor):
|
|
||||||
# this is no longer necessary; schemas are defined in code
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
def remove_become_methods(apps, schema_editor):
|
def remove_become_methods(apps, schema_editor):
|
||||||
# this is no longer necessary; schemas are defined in code
|
# this is no longer necessary; schemas are defined in code
|
||||||
pass
|
pass
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import logging
|
import logging
|
||||||
|
|
||||||
from django.db.models import Q
|
|
||||||
from django.utils.encoding import smart_text
|
from django.utils.encoding import smart_text
|
||||||
|
|
||||||
from awx.main.utils.common import parse_yaml_or_json
|
from awx.main.utils.common import parse_yaml_or_json
|
||||||
@@ -8,64 +7,6 @@ from awx.main.utils.common import parse_yaml_or_json
|
|||||||
logger = logging.getLogger('awx.main.migrations')
|
logger = logging.getLogger('awx.main.migrations')
|
||||||
|
|
||||||
|
|
||||||
def remove_manual_inventory_sources(apps, schema_editor):
|
|
||||||
'''Previously we would automatically create inventory sources after
|
|
||||||
Group creation and we would use the parent Group as our interface for the user.
|
|
||||||
During that process we would create InventorySource that had a source of "manual".
|
|
||||||
'''
|
|
||||||
# TODO: use this in the 3.3 data migrations
|
|
||||||
InventorySource = apps.get_model('main', 'InventorySource')
|
|
||||||
# see models/inventory.py SOURCE_CHOICES - ('', _('Manual'))
|
|
||||||
logger.debug("Removing all Manual InventorySource from database.")
|
|
||||||
InventorySource.objects.filter(source='').delete()
|
|
||||||
|
|
||||||
|
|
||||||
def remove_rax_inventory_sources(apps, schema_editor):
|
|
||||||
'''Rackspace inventory sources are not supported since 3.2, remove them.
|
|
||||||
'''
|
|
||||||
InventorySource = apps.get_model('main', 'InventorySource')
|
|
||||||
logger.debug("Removing all Rackspace InventorySource from database.")
|
|
||||||
InventorySource.objects.filter(source='rax').delete()
|
|
||||||
|
|
||||||
|
|
||||||
def rename_inventory_sources(apps, schema_editor):
|
|
||||||
'''Rename existing InventorySource entries using the following format.
|
|
||||||
{{ inventory_source.name }} - {{ inventory.module }} - {{ number }}
|
|
||||||
The number will be incremented for each InventorySource for the organization.
|
|
||||||
'''
|
|
||||||
Organization = apps.get_model('main', 'Organization')
|
|
||||||
InventorySource = apps.get_model('main', 'InventorySource')
|
|
||||||
|
|
||||||
for org in Organization.objects.iterator():
|
|
||||||
for i, invsrc in enumerate(InventorySource.objects.filter(Q(inventory__organization=org) |
|
|
||||||
Q(deprecated_group__inventory__organization=org)).distinct().all()):
|
|
||||||
|
|
||||||
inventory = invsrc.deprecated_group.inventory if invsrc.deprecated_group else invsrc.inventory
|
|
||||||
name = '{0} - {1} - {2}'.format(invsrc.name, inventory.name, i)
|
|
||||||
logger.debug("Renaming InventorySource({0}) {1} -> {2}".format(
|
|
||||||
invsrc.pk, invsrc.name, name
|
|
||||||
))
|
|
||||||
invsrc.name = name
|
|
||||||
invsrc.save()
|
|
||||||
|
|
||||||
|
|
||||||
def remove_inventory_source_with_no_inventory_link(apps, schema_editor):
|
|
||||||
'''If we cannot determine the Inventory for which an InventorySource exists
|
|
||||||
we can safely remove it.
|
|
||||||
'''
|
|
||||||
InventorySource = apps.get_model('main', 'InventorySource')
|
|
||||||
logger.debug("Removing all InventorySource that have no link to an Inventory from database.")
|
|
||||||
InventorySource.objects.filter(Q(inventory__organization=None) & Q(deprecated_group__inventory=None)).delete()
|
|
||||||
|
|
||||||
|
|
||||||
def remove_azure_inventory_sources(apps, schema_editor):
|
|
||||||
'''Azure inventory sources are not supported since 3.2, remove them.
|
|
||||||
'''
|
|
||||||
InventorySource = apps.get_model('main', 'InventorySource')
|
|
||||||
logger.debug("Removing all Azure InventorySource from database.")
|
|
||||||
InventorySource.objects.filter(source='azure').delete()
|
|
||||||
|
|
||||||
|
|
||||||
def _get_instance_id(from_dict, new_id, default=''):
|
def _get_instance_id(from_dict, new_id, default=''):
|
||||||
'''logic mostly duplicated with inventory_import command Command._get_instance_id
|
'''logic mostly duplicated with inventory_import command Command._get_instance_id
|
||||||
frozen in time here, for purposes of migrations
|
frozen in time here, for purposes of migrations
|
||||||
|
|||||||
@@ -1,79 +1,12 @@
|
|||||||
import logging
|
import logging
|
||||||
import json
|
|
||||||
from django.utils.translation import ugettext_lazy as _
|
|
||||||
|
|
||||||
from awx.conf.migrations._reencrypt import (
|
from awx.conf.migrations._reencrypt import (
|
||||||
decrypt_field,
|
decrypt_field,
|
||||||
should_decrypt_field,
|
|
||||||
)
|
)
|
||||||
from awx.main.utils.encryption import encrypt_field
|
|
||||||
|
|
||||||
from awx.main.notifications.email_backend import CustomEmailBackend
|
|
||||||
from awx.main.notifications.slack_backend import SlackBackend
|
|
||||||
from awx.main.notifications.twilio_backend import TwilioBackend
|
|
||||||
from awx.main.notifications.pagerduty_backend import PagerDutyBackend
|
|
||||||
from awx.main.notifications.hipchat_backend import HipChatBackend
|
|
||||||
from awx.main.notifications.mattermost_backend import MattermostBackend
|
|
||||||
from awx.main.notifications.webhook_backend import WebhookBackend
|
|
||||||
from awx.main.notifications.irc_backend import IrcBackend
|
|
||||||
|
|
||||||
logger = logging.getLogger('awx.main.migrations')
|
logger = logging.getLogger('awx.main.migrations')
|
||||||
|
|
||||||
__all__ = ['replace_aesecb_fernet']
|
__all__ = []
|
||||||
|
|
||||||
|
|
||||||
NOTIFICATION_TYPES = [('email', _('Email'), CustomEmailBackend),
|
|
||||||
('slack', _('Slack'), SlackBackend),
|
|
||||||
('twilio', _('Twilio'), TwilioBackend),
|
|
||||||
('pagerduty', _('Pagerduty'), PagerDutyBackend),
|
|
||||||
('hipchat', _('HipChat'), HipChatBackend),
|
|
||||||
('mattermost', _('Mattermost'), MattermostBackend),
|
|
||||||
('webhook', _('Webhook'), WebhookBackend),
|
|
||||||
('irc', _('IRC'), IrcBackend)]
|
|
||||||
|
|
||||||
|
|
||||||
PASSWORD_FIELDS = ('password', 'security_token', 'ssh_key_data', 'ssh_key_unlock',
|
|
||||||
'become_password', 'vault_password', 'secret', 'authorize_password')
|
|
||||||
|
|
||||||
|
|
||||||
def replace_aesecb_fernet(apps, schema_editor):
|
|
||||||
_notification_templates(apps)
|
|
||||||
_credentials(apps)
|
|
||||||
_unified_jobs(apps)
|
|
||||||
|
|
||||||
|
|
||||||
def _notification_templates(apps):
|
|
||||||
NotificationTemplate = apps.get_model('main', 'NotificationTemplate')
|
|
||||||
for nt in NotificationTemplate.objects.all():
|
|
||||||
CLASS_FOR_NOTIFICATION_TYPE = dict([(x[0], x[2]) for x in NOTIFICATION_TYPES])
|
|
||||||
notification_class = CLASS_FOR_NOTIFICATION_TYPE[nt.notification_type]
|
|
||||||
for field in filter(lambda x: notification_class.init_parameters[x]['type'] == "password",
|
|
||||||
notification_class.init_parameters):
|
|
||||||
if should_decrypt_field(nt.notification_configuration[field]):
|
|
||||||
nt.notification_configuration[field] = decrypt_field(nt, 'notification_configuration', subfield=field)
|
|
||||||
nt.notification_configuration[field] = encrypt_field(nt, 'notification_configuration', subfield=field)
|
|
||||||
nt.save()
|
|
||||||
|
|
||||||
|
|
||||||
def _credentials(apps):
|
|
||||||
for credential in apps.get_model('main', 'Credential').objects.all():
|
|
||||||
for field_name in PASSWORD_FIELDS:
|
|
||||||
value = getattr(credential, field_name)
|
|
||||||
if should_decrypt_field(value):
|
|
||||||
value = decrypt_field(credential, field_name)
|
|
||||||
setattr(credential, field_name, value)
|
|
||||||
setattr(credential, field_name, encrypt_field(credential, field_name))
|
|
||||||
credential.save()
|
|
||||||
|
|
||||||
|
|
||||||
def _unified_jobs(apps):
|
|
||||||
UnifiedJob = apps.get_model('main', 'UnifiedJob')
|
|
||||||
for uj in UnifiedJob.objects.all():
|
|
||||||
if uj.start_args is not None:
|
|
||||||
if should_decrypt_field(uj.start_args):
|
|
||||||
uj.start_args = decrypt_field(uj, 'start_args')
|
|
||||||
uj.start_args = encrypt_field(uj, 'start_args')
|
|
||||||
uj.save()
|
|
||||||
|
|
||||||
|
|
||||||
def blank_old_start_args(apps, schema_editor):
|
def blank_old_start_args(apps, schema_editor):
|
||||||
@@ -91,53 +24,3 @@ def blank_old_start_args(apps, schema_editor):
|
|||||||
logger.debug('Blanking job args for %s', uj.pk)
|
logger.debug('Blanking job args for %s', uj.pk)
|
||||||
uj.start_args = ''
|
uj.start_args = ''
|
||||||
uj.save()
|
uj.save()
|
||||||
|
|
||||||
|
|
||||||
def encrypt_survey_passwords(apps, schema_editor):
|
|
||||||
_encrypt_survey_passwords(
|
|
||||||
apps.get_model('main', 'Job'),
|
|
||||||
apps.get_model('main', 'JobTemplate'),
|
|
||||||
apps.get_model('main', 'WorkflowJob'),
|
|
||||||
apps.get_model('main', 'WorkflowJobTemplate'),
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def _encrypt_survey_passwords(Job, JobTemplate, WorkflowJob, WorkflowJobTemplate):
|
|
||||||
from awx.main.utils.encryption import encrypt_value
|
|
||||||
for _type in (JobTemplate, WorkflowJobTemplate):
|
|
||||||
for jt in _type.objects.exclude(survey_spec={}):
|
|
||||||
changed = False
|
|
||||||
if jt.survey_spec.get('spec', []):
|
|
||||||
for field in jt.survey_spec['spec']:
|
|
||||||
if field.get('type') == 'password' and field.get('default', ''):
|
|
||||||
default = field['default']
|
|
||||||
if default.startswith('$encrypted$'):
|
|
||||||
if default == '$encrypted$':
|
|
||||||
# If you have a survey_spec with a literal
|
|
||||||
# '$encrypted$' as the default, you have
|
|
||||||
# encountered a known bug in awx/Tower
|
|
||||||
# https://github.com/ansible/ansible-tower/issues/7800
|
|
||||||
logger.error(
|
|
||||||
'{}.pk={} survey_spec has ambiguous $encrypted$ default for {}, needs attention...'.format(jt, jt.pk, field['variable'])
|
|
||||||
)
|
|
||||||
field['default'] = ''
|
|
||||||
changed = True
|
|
||||||
continue
|
|
||||||
field['default'] = encrypt_value(field['default'], pk=None)
|
|
||||||
changed = True
|
|
||||||
if changed:
|
|
||||||
jt.save()
|
|
||||||
|
|
||||||
for _type in (Job, WorkflowJob):
|
|
||||||
for job in _type.objects.defer('result_stdout_text').exclude(survey_passwords={}).iterator():
|
|
||||||
changed = False
|
|
||||||
for key in job.survey_passwords:
|
|
||||||
if key in job.extra_vars:
|
|
||||||
extra_vars = json.loads(job.extra_vars)
|
|
||||||
if not extra_vars.get(key, '') or extra_vars[key].startswith('$encrypted$'):
|
|
||||||
continue
|
|
||||||
extra_vars[key] = encrypt_value(extra_vars[key], pk=None)
|
|
||||||
job.extra_vars = json.dumps(extra_vars)
|
|
||||||
changed = True
|
|
||||||
if changed:
|
|
||||||
job.save()
|
|
||||||
|
|||||||
@@ -1,89 +1,9 @@
|
|||||||
import logging
|
import logging
|
||||||
|
|
||||||
from django.utils.timezone import now
|
|
||||||
from django.utils.text import slugify
|
|
||||||
|
|
||||||
from awx.main.models.base import PERM_INVENTORY_SCAN, PERM_INVENTORY_DEPLOY
|
|
||||||
from awx.main import utils
|
|
||||||
|
|
||||||
|
|
||||||
logger = logging.getLogger('awx.main.migrations')
|
logger = logging.getLogger('awx.main.migrations')
|
||||||
|
|
||||||
|
|
||||||
def _create_fact_scan_project(ContentType, Project, org):
|
|
||||||
ct = ContentType.objects.get_for_model(Project)
|
|
||||||
name = u"Tower Fact Scan - {}".format(org.name if org else "No Organization")
|
|
||||||
proj = Project(name=name,
|
|
||||||
scm_url='https://github.com/ansible/awx-facts-playbooks',
|
|
||||||
scm_type='git',
|
|
||||||
scm_update_on_launch=True,
|
|
||||||
scm_update_cache_timeout=86400,
|
|
||||||
organization=org,
|
|
||||||
created=now(),
|
|
||||||
modified=now(),
|
|
||||||
polymorphic_ctype=ct)
|
|
||||||
proj.save()
|
|
||||||
|
|
||||||
slug_name = slugify(str(name)).replace(u'-', u'_')
|
|
||||||
proj.local_path = u'_%d__%s' % (int(proj.pk), slug_name)
|
|
||||||
|
|
||||||
proj.save()
|
|
||||||
return proj
|
|
||||||
|
|
||||||
|
|
||||||
def _create_fact_scan_projects(ContentType, Project, orgs):
|
|
||||||
return {org.id : _create_fact_scan_project(ContentType, Project, org) for org in orgs}
|
|
||||||
|
|
||||||
|
|
||||||
def _get_tower_scan_job_templates(JobTemplate):
|
|
||||||
return JobTemplate.objects.filter(job_type=PERM_INVENTORY_SCAN, project__isnull=True) \
|
|
||||||
.prefetch_related('inventory__organization')
|
|
||||||
|
|
||||||
|
|
||||||
def _get_orgs(Organization, job_template_ids):
|
|
||||||
return Organization.objects.filter(inventories__jobtemplates__in=job_template_ids).distinct()
|
|
||||||
|
|
||||||
|
|
||||||
def _migrate_scan_job_templates(apps):
|
|
||||||
JobTemplate = apps.get_model('main', 'JobTemplate')
|
|
||||||
Organization = apps.get_model('main', 'Organization')
|
|
||||||
ContentType = apps.get_model('contenttypes', 'ContentType')
|
|
||||||
Project = apps.get_model('main', 'Project')
|
|
||||||
|
|
||||||
project_no_org = None
|
|
||||||
|
|
||||||
# A scan job template with a custom project will retain the custom project.
|
|
||||||
JobTemplate.objects.filter(job_type=PERM_INVENTORY_SCAN, project__isnull=False).update(use_fact_cache=True, job_type=PERM_INVENTORY_DEPLOY)
|
|
||||||
|
|
||||||
# Scan jobs templates using Tower's default scan playbook will now point at
|
|
||||||
# the same playbook but in a github repo.
|
|
||||||
jts = _get_tower_scan_job_templates(JobTemplate)
|
|
||||||
if jts.count() == 0:
|
|
||||||
return
|
|
||||||
|
|
||||||
orgs = _get_orgs(Organization, jts.values_list('id'))
|
|
||||||
if orgs.count() == 0:
|
|
||||||
return
|
|
||||||
|
|
||||||
org_proj_map = _create_fact_scan_projects(ContentType, Project, orgs)
|
|
||||||
for jt in jts:
|
|
||||||
if jt.inventory and jt.inventory.organization:
|
|
||||||
jt.project_id = org_proj_map[jt.inventory.organization.id].id
|
|
||||||
# Job Templates without an Organization; through related Inventory
|
|
||||||
else:
|
|
||||||
if not project_no_org:
|
|
||||||
project_no_org = _create_fact_scan_project(ContentType, Project, None)
|
|
||||||
jt.project_id = project_no_org.id
|
|
||||||
jt.job_type = PERM_INVENTORY_DEPLOY
|
|
||||||
jt.playbook = "scan_facts.yml"
|
|
||||||
jt.use_fact_cache = True
|
|
||||||
jt.save()
|
|
||||||
|
|
||||||
|
|
||||||
def migrate_scan_job_templates(apps, schema_editor):
|
|
||||||
_migrate_scan_job_templates(apps)
|
|
||||||
|
|
||||||
|
|
||||||
def remove_scan_type_nodes(apps, schema_editor):
|
def remove_scan_type_nodes(apps, schema_editor):
|
||||||
WorkflowJobTemplateNode = apps.get_model('main', 'WorkflowJobTemplateNode')
|
WorkflowJobTemplateNode = apps.get_model('main', 'WorkflowJobTemplateNode')
|
||||||
WorkflowJobNode = apps.get_model('main', 'WorkflowJobNode')
|
WorkflowJobNode = apps.get_model('main', 'WorkflowJobNode')
|
||||||
|
|||||||
@@ -2,52 +2,10 @@ import pytest
|
|||||||
from unittest import mock
|
from unittest import mock
|
||||||
|
|
||||||
from awx.main.migrations import _inventory_source as invsrc
|
from awx.main.migrations import _inventory_source as invsrc
|
||||||
from awx.main.models import InventorySource
|
|
||||||
|
|
||||||
from django.apps import apps
|
from django.apps import apps
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db
|
|
||||||
def test_inv_src_manual_removal(inventory_source):
|
|
||||||
inventory_source.source = ''
|
|
||||||
inventory_source.save()
|
|
||||||
|
|
||||||
assert InventorySource.objects.filter(pk=inventory_source.pk).exists()
|
|
||||||
invsrc.remove_manual_inventory_sources(apps, None)
|
|
||||||
assert not InventorySource.objects.filter(pk=inventory_source.pk).exists()
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db
|
|
||||||
def test_rax_inv_src_removal(inventory_source):
|
|
||||||
inventory_source.source = 'rax'
|
|
||||||
inventory_source.save()
|
|
||||||
|
|
||||||
assert InventorySource.objects.filter(pk=inventory_source.pk).exists()
|
|
||||||
invsrc.remove_rax_inventory_sources(apps, None)
|
|
||||||
assert not InventorySource.objects.filter(pk=inventory_source.pk).exists()
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db
|
|
||||||
def test_inv_src_rename(inventory_source_factory):
|
|
||||||
inv_src01 = inventory_source_factory('t1')
|
|
||||||
|
|
||||||
invsrc.rename_inventory_sources(apps, None)
|
|
||||||
|
|
||||||
inv_src01.refresh_from_db()
|
|
||||||
# inv-is-t1 is generated in the inventory_source_factory
|
|
||||||
assert inv_src01.name == 't1 - inv-is-t1 - 0'
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db
|
|
||||||
def test_azure_inv_src_removal(inventory_source):
|
|
||||||
inventory_source.source = 'azure'
|
|
||||||
inventory_source.save()
|
|
||||||
|
|
||||||
assert InventorySource.objects.filter(pk=inventory_source.pk).exists()
|
|
||||||
invsrc.remove_azure_inventory_sources(apps, None)
|
|
||||||
assert not InventorySource.objects.filter(pk=inventory_source.pk).exists()
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize('vars,id_var,result', [
|
@pytest.mark.parametrize('vars,id_var,result', [
|
||||||
({'foo': {'bar': '1234'}}, 'foo.bar', '1234'),
|
({'foo': {'bar': '1234'}}, 'foo.bar', '1234'),
|
||||||
({'cat': 'meow'}, 'cat', 'meow'),
|
({'cat': 'meow'}, 'cat', 'meow'),
|
||||||
|
|||||||
@@ -1,100 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
|
||||||
# Copyright (c) 2017 Ansible, Inc.
|
|
||||||
# All Rights Reserved.
|
|
||||||
import pytest
|
|
||||||
|
|
||||||
from django.apps import apps
|
|
||||||
|
|
||||||
from awx.main.models.base import PERM_INVENTORY_SCAN, PERM_INVENTORY_DEPLOY
|
|
||||||
from awx.main.models import (
|
|
||||||
JobTemplate,
|
|
||||||
Project,
|
|
||||||
Inventory,
|
|
||||||
Organization,
|
|
||||||
)
|
|
||||||
|
|
||||||
from awx.main.migrations._scan_jobs import _migrate_scan_job_templates
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
|
||||||
def organizations():
|
|
||||||
return [Organization.objects.create(name=u"org-\xe9-{}".format(x)) for x in range(3)]
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
|
||||||
def inventories(organizations):
|
|
||||||
return [Inventory.objects.create(name=u"inv-\xe9-{}".format(x),
|
|
||||||
organization=organizations[x]) for x in range(3)]
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
|
||||||
def job_templates_scan(inventories):
|
|
||||||
return [JobTemplate.objects.create(name=u"jt-\xe9-scan-{}".format(x),
|
|
||||||
job_type=PERM_INVENTORY_SCAN,
|
|
||||||
inventory=inventories[x]) for x in range(3)]
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
|
||||||
def job_templates_deploy(inventories):
|
|
||||||
return [JobTemplate.objects.create(name=u"jt-\xe9-deploy-{}".format(x),
|
|
||||||
job_type=PERM_INVENTORY_DEPLOY,
|
|
||||||
inventory=inventories[x]) for x in range(3)]
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
|
||||||
def project_custom(organizations):
|
|
||||||
return Project.objects.create(name=u"proj-\xe9-scan_custom",
|
|
||||||
scm_url='https://giggity.com',
|
|
||||||
organization=organizations[0])
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
|
||||||
def job_templates_custom_scan_project(project_custom):
|
|
||||||
return [JobTemplate.objects.create(name=u"jt-\xe9-scan-custom-{}".format(x),
|
|
||||||
project=project_custom,
|
|
||||||
job_type=PERM_INVENTORY_SCAN) for x in range(3)]
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
|
||||||
def job_template_scan_no_org():
|
|
||||||
return JobTemplate.objects.create(name=u"jt-\xe9-scan-no-org",
|
|
||||||
job_type=PERM_INVENTORY_SCAN)
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db
|
|
||||||
def test_scan_jobs_migration(job_templates_scan, job_templates_deploy, job_templates_custom_scan_project, project_custom, job_template_scan_no_org):
|
|
||||||
_migrate_scan_job_templates(apps)
|
|
||||||
|
|
||||||
# Ensure there are no scan job templates after the migration
|
|
||||||
assert 0 == JobTemplate.objects.filter(job_type=PERM_INVENTORY_SCAN).count()
|
|
||||||
|
|
||||||
# Ensure special No Organization proj created
|
|
||||||
# And No Organization project is associated with correct jt
|
|
||||||
proj = Project.objects.get(name="Tower Fact Scan - No Organization")
|
|
||||||
assert proj.id == JobTemplate.objects.get(id=job_template_scan_no_org.id).project.id
|
|
||||||
|
|
||||||
# Ensure per-org projects were created
|
|
||||||
projs = Project.objects.filter(name__startswith="Tower Fact Scan")
|
|
||||||
assert projs.count() == 4
|
|
||||||
|
|
||||||
# Ensure scan job templates with Tower project are migrated
|
|
||||||
for i, jt_old in enumerate(job_templates_scan):
|
|
||||||
jt = JobTemplate.objects.get(id=jt_old.id)
|
|
||||||
assert PERM_INVENTORY_DEPLOY == jt.job_type
|
|
||||||
assert jt.use_fact_cache is True
|
|
||||||
assert projs[i] == jt.project
|
|
||||||
|
|
||||||
# Ensure scan job templates with custom projects are migrated
|
|
||||||
for jt_old in job_templates_custom_scan_project:
|
|
||||||
jt = JobTemplate.objects.get(id=jt_old.id)
|
|
||||||
assert PERM_INVENTORY_DEPLOY == jt.job_type
|
|
||||||
assert jt.use_fact_cache is True
|
|
||||||
assert project_custom == jt.project
|
|
||||||
|
|
||||||
# Ensure other job template aren't touched
|
|
||||||
for jt_old in job_templates_deploy:
|
|
||||||
jt = JobTemplate.objects.get(id=jt_old.id)
|
|
||||||
assert PERM_INVENTORY_DEPLOY == jt.job_type
|
|
||||||
assert jt.project is None
|
|
||||||
|
|
||||||
Reference in New Issue
Block a user