From 043aff6a8c93d425e84bfadba3aa7e4e2b62fadf Mon Sep 17 00:00:00 2001 From: Elijah DeLee Date: Tue, 1 Jun 2021 10:56:45 -0400 Subject: [PATCH 1/5] Create default EE in seperate awx-managment cmd Create EE at a seperate time and also attach a registry credential if auth information provided This command can be run multiple times on the same instance and should be idempotent. --- .../commands/create_preload_data.py | 10 +-- ...register_default_execution_environments.py | 89 +++++++++++++++++++ 2 files changed, 90 insertions(+), 9 deletions(-) create mode 100644 awx/main/management/commands/register_default_execution_environments.py diff --git a/awx/main/management/commands/create_preload_data.py b/awx/main/management/commands/create_preload_data.py index 83cce691de..41fa665abf 100644 --- a/awx/main/management/commands/create_preload_data.py +++ b/awx/main/management/commands/create_preload_data.py @@ -2,9 +2,8 @@ # All Rights Reserved from django.core.management.base import BaseCommand -from django.conf import settings from crum import impersonate -from awx.main.models import User, Organization, Project, Inventory, CredentialType, Credential, Host, JobTemplate, ExecutionEnvironment +from awx.main.models import User, Organization, Project, Inventory, CredentialType, Credential, Host, JobTemplate from awx.main.signals import disable_computed_fields @@ -68,13 +67,6 @@ class Command(BaseCommand): print('Demo Credential, Inventory, and Job Template added.') changed = True - for ee in reversed(settings.DEFAULT_EXECUTION_ENVIRONMENTS): - _, created = ExecutionEnvironment.objects.update_or_create(name=ee['name'], defaults={'image': ee['image'], 'managed_by_tower': True}) - - if created: - changed = True - print('Default Execution Environment(s) registered.') - if changed: print('(changed: True)') else: diff --git a/awx/main/management/commands/register_default_execution_environments.py b/awx/main/management/commands/register_default_execution_environments.py new file mode 100644 index 0000000000..ed1a5f074c --- /dev/null +++ b/awx/main/management/commands/register_default_execution_environments.py @@ -0,0 +1,89 @@ +# Copyright (c) 2015 Ansible, Inc. +# All Rights Reserved +import sys + +from django.core.management.base import BaseCommand +from django.conf import settings +from awx.main.models import CredentialType, Credential, ExecutionEnvironment + + +class Command(BaseCommand): + """Create default execution environments, intended for new installs""" + + help = """ + Creates or updates the execution environments set in settings.DEFAULT_EXECUTION_ENVIRONMENTS if they are not yet created. + Optionally provide authentication details to create or update a container registry credential that will be set on all of these default execution environments. + Note that settings.DEFAULT_EXECUTION_ENVIRONMENTS is and ordered list, the first in the list will be used for project updates and system jobs. + """ + + def add_arguments(self, parser): + parser.add_argument( + "--registry-url", + type=str, + default="", + help="URL for the container registry", + ) + parser.add_argument( + "--registry-username", + type=str, + default="", + help="username for the container registry", + ) + parser.add_argument( + "--registry-password", + type=str, + default="", + help="Password or token for CLI authentication with the container registry", + ) + parser.add_argument( + "--verify-ssl", + type=bool, + default=True, + help="Verify SSL when authenticating with the container registry", + ) + + def handle(self, *args, **options): + changed = False + registry_cred = None + + if options.get("registry_username"): + if not options.get("registry_password"): + sys.stderr.write("Registry password must be provided when providing registry username\n") + sys.exit(1) + if not options.get("registry_url"): + sys.stderr.write("Registry url must be provided when providing registry username\n") + sys.exit(1) + registry_cred_type = CredentialType.objects.filter(kind="registry") + if not registry_cred_type.exists(): + sys.stderr.write("No registry credential type found") + sys.exit(1) + registry_cred, created = Credential.objects.update_or_create( + name="Default Execution Environment Registry Credential", + managed_by_tower=True, + credential_type=registry_cred_type[0], + defaults={ + "inputs": { + "host": options.get("registry_url"), + "password": options.get("registry_password"), + "username": options.get("registry_username"), + "verify_ssl": options.get("verify_ssl"), + }, + }, + ) + if created: + changed = True + print("Default Execution Environment Credential registered.") + + for ee in reversed(settings.DEFAULT_EXECUTION_ENVIRONMENTS): + _, created = ExecutionEnvironment.objects.update_or_create( + name=ee["name"], defaults={"image": ee["image"], "managed_by_tower": True, "credential": registry_cred} + ) + + if created: + changed = True + print("Default Execution Environment(s) registered.") + + if changed: + print("(changed: True)") + else: + print("(changed: False)") From 7818b2008fac44c36ef1d2e14992f9b23ceb5027 Mon Sep 17 00:00:00 2001 From: Elijah DeLee Date: Wed, 2 Jun 2021 13:05:02 -0400 Subject: [PATCH 2/5] Allow modifications of managed_by_tower creds Sysadmins may need to modify managed_by_tower credentials, the only known case of which is a default container registry credential for use by the default execution environments, which are also managed_by_tower and allow modifications. --- awx/api/serializers.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/awx/api/serializers.py b/awx/api/serializers.py index 3ba2488a5b..b94631f46c 100644 --- a/awx/api/serializers.py +++ b/awx/api/serializers.py @@ -2603,8 +2603,6 @@ class CredentialSerializer(BaseSerializer): return summary_dict def validate(self, attrs): - if self.instance and self.instance.managed_by_tower: - raise PermissionDenied(detail=_("Modifications not allowed for managed credentials")) return super(CredentialSerializer, self).validate(attrs) def get_validation_exclusions(self, obj=None): From e740cfcb52cf3275c44bd680a84cd4f8cf7dcf49 Mon Sep 17 00:00:00 2001 From: Elijah DeLee Date: Wed, 2 Jun 2021 16:16:36 -0400 Subject: [PATCH 3/5] revert change to serializer and use get_or_create still need to fix up the handling of the verify-ssl bool, since it is not being respected --- awx/api/serializers.py | 2 ++ ...register_default_execution_environments.py | 22 +++++++++++-------- 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/awx/api/serializers.py b/awx/api/serializers.py index b94631f46c..3ba2488a5b 100644 --- a/awx/api/serializers.py +++ b/awx/api/serializers.py @@ -2603,6 +2603,8 @@ class CredentialSerializer(BaseSerializer): return summary_dict def validate(self, attrs): + if self.instance and self.instance.managed_by_tower: + raise PermissionDenied(detail=_("Modifications not allowed for managed credentials")) return super(CredentialSerializer, self).validate(attrs) def get_validation_exclusions(self, obj=None): diff --git a/awx/main/management/commands/register_default_execution_environments.py b/awx/main/management/commands/register_default_execution_environments.py index ed1a5f074c..faaa91ae29 100644 --- a/awx/main/management/commands/register_default_execution_environments.py +++ b/awx/main/management/commands/register_default_execution_environments.py @@ -53,26 +53,30 @@ class Command(BaseCommand): if not options.get("registry_url"): sys.stderr.write("Registry url must be provided when providing registry username\n") sys.exit(1) + registry_cred_inputs = { + "host": options.get("registry_url"), + "password": options.get("registry_password"), + "username": options.get("registry_username"), + "verify_ssl": options.get("verify_ssl"), + } registry_cred_type = CredentialType.objects.filter(kind="registry") if not registry_cred_type.exists(): sys.stderr.write("No registry credential type found") sys.exit(1) - registry_cred, created = Credential.objects.update_or_create( + registry_cred, created = Credential.objects.get_or_create( name="Default Execution Environment Registry Credential", managed_by_tower=True, credential_type=registry_cred_type[0], - defaults={ - "inputs": { - "host": options.get("registry_url"), - "password": options.get("registry_password"), - "username": options.get("registry_username"), - "verify_ssl": options.get("verify_ssl"), - }, - }, + defaults={"inputs": registry_cred_inputs}, ) if created: changed = True print("Default Execution Environment Credential registered.") + elif registry_cred.inputs != registry_cred_inputs: + registry_cred.inputs = registry_cred_inputs + registry_cred.save() + changed = True + print("Default Execution Environment Credential updated.") for ee in reversed(settings.DEFAULT_EXECUTION_ENVIRONMENTS): _, created = ExecutionEnvironment.objects.update_or_create( From 223c5bdaf6f3b97c50bcbe03cacace454bc39880 Mon Sep 17 00:00:00 2001 From: Shane McDonald Date: Wed, 2 Jun 2021 16:26:58 -0400 Subject: [PATCH 4/5] Tweaks to register_default_execution_environments command - The credential will always be updated. I dont think we want to decrypt the password here for comparison - Preserve newlines in help text --- ...register_default_execution_environments.py | 51 +++++++++++-------- 1 file changed, 31 insertions(+), 20 deletions(-) diff --git a/awx/main/management/commands/register_default_execution_environments.py b/awx/main/management/commands/register_default_execution_environments.py index faaa91ae29..67d101bba3 100644 --- a/awx/main/management/commands/register_default_execution_environments.py +++ b/awx/main/management/commands/register_default_execution_environments.py @@ -1,6 +1,8 @@ # Copyright (c) 2015 Ansible, Inc. # All Rights Reserved import sys +from distutils.util import strtobool +from argparse import RawTextHelpFormatter from django.core.management.base import BaseCommand from django.conf import settings @@ -16,6 +18,12 @@ class Command(BaseCommand): Note that settings.DEFAULT_EXECUTION_ENVIRONMENTS is and ordered list, the first in the list will be used for project updates and system jobs. """ + # Preserves newlines in the help text + def create_parser(self, *args, **kwargs): + parser = super(Command, self).create_parser(*args, **kwargs) + parser.formatter_class = RawTextHelpFormatter + return parser + def add_arguments(self, parser): parser.add_argument( "--registry-url", @@ -37,7 +45,7 @@ class Command(BaseCommand): ) parser.add_argument( "--verify-ssl", - type=bool, + type=lambda x: bool(strtobool(str(x))), default=True, help="Verify SSL when authenticating with the container registry", ) @@ -50,33 +58,36 @@ class Command(BaseCommand): if not options.get("registry_password"): sys.stderr.write("Registry password must be provided when providing registry username\n") sys.exit(1) + if not options.get("registry_url"): sys.stderr.write("Registry url must be provided when providing registry username\n") sys.exit(1) - registry_cred_inputs = { + + registry_cred_type = CredentialType.objects.filter(kind="registry") + if not registry_cred_type.exists(): + sys.stderr.write("No registry credential type found") + sys.exit(1) + + registry_cred, created = Credential.objects.get_or_create( + name="Default Execution Environment Registry Credential", managed_by_tower=True, credential_type=registry_cred_type[0] + ) + + if created: + print("Default Execution Environment Credential registered.") + + inputs = { "host": options.get("registry_url"), "password": options.get("registry_password"), "username": options.get("registry_username"), "verify_ssl": options.get("verify_ssl"), } - registry_cred_type = CredentialType.objects.filter(kind="registry") - if not registry_cred_type.exists(): - sys.stderr.write("No registry credential type found") - sys.exit(1) - registry_cred, created = Credential.objects.get_or_create( - name="Default Execution Environment Registry Credential", - managed_by_tower=True, - credential_type=registry_cred_type[0], - defaults={"inputs": registry_cred_inputs}, - ) - if created: - changed = True - print("Default Execution Environment Credential registered.") - elif registry_cred.inputs != registry_cred_inputs: - registry_cred.inputs = registry_cred_inputs - registry_cred.save() - changed = True - print("Default Execution Environment Credential updated.") + + registry_cred.inputs = inputs + registry_cred.save() + changed = True + + if not created: + print('Updated Default Execution Environment Credential') for ee in reversed(settings.DEFAULT_EXECUTION_ENVIRONMENTS): _, created = ExecutionEnvironment.objects.update_or_create( From 52eeace20f02ff33c14a33bae72ca9a63fcb255f Mon Sep 17 00:00:00 2001 From: Shane McDonald Date: Wed, 2 Jun 2021 18:19:41 -0400 Subject: [PATCH 5/5] Register default EEs when booting dev env --- tools/docker-compose/bootstrap_development.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/tools/docker-compose/bootstrap_development.sh b/tools/docker-compose/bootstrap_development.sh index 3f3cb586a3..1b2b1a29e9 100755 --- a/tools/docker-compose/bootstrap_development.sh +++ b/tools/docker-compose/bootstrap_development.sh @@ -29,6 +29,7 @@ if output=$(awx-manage createsuperuser --noinput --username=admin --email=admin@ awx-manage update_password --username=admin --password=${admin_password} fi awx-manage create_preload_data +awx-manage register_default_execution_environments mkdir -p /awx_devel/awx/public/static mkdir -p /awx_devel/awx/ui/static