Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP]Make cloud providers dynamic #15537

Draft
wants to merge 70 commits into
base: devel
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
70 commits
Select commit Hold shift + click to select a range
a9303ba
Add dynamic plugin_names pull for CLOUUD_PROVIDERS
djyasin Sep 17, 2024
4129087
Revise to return a tuple instead of a list of key value pairs
djyasin Sep 18, 2024
ea6f9a2
Update awx/main/constants.py
djyasin Sep 19, 2024
6199bd2
Attempt to add bootstrapping for imports
djyasin Sep 19, 2024
4330b29
Revise bootstrap attempt
djyasin Sep 20, 2024
ce9c764
Attempt to avoid importing InventorySourceOptions directly in constan…
djyasin Sep 20, 2024
7f1fd91
Attempt to avoid importing InventorySourceOptions directly in constan…
djyasin Sep 23, 2024
854d6f6
Resolve function object is not iterable error
djyasin Sep 23, 2024
4ff9d21
Add classmethod to load_plugin_names function
djyasin Sep 24, 2024
010944d
Refine call and adjust how I am using AppConfig
djyasin Sep 24, 2024
ab09c9a
Remove additional hardcoded CLOUD_PROVIDERS and got code working
djyasin Sep 24, 2024
8ca5f40
Remove unused imports
djyasin Sep 24, 2024
cf38e53
Move import to the top of the file to avoid in-function import
djyasin Sep 24, 2024
74027bd
Remove SOURCE_CHOICES = [] and ran isort on inventory.py
djyasin Sep 25, 2024
8c190a5
Rename load_plugin_names to discover_available_plugin_names() to be m…
djyasin Sep 25, 2024
e6cab0a
Revert isort changes in inventory.py
djyasin Sep 25, 2024
62c0615
Revert remainder of isort changes in invenory.py
djyasin Sep 25, 2024
743f7b9
Revert last isort changes in invenory.py
djyasin Sep 25, 2024
b3174bb
Rename function to be more descriptive that it is discovering cloud p…
djyasin Sep 25, 2024
c099267
Remove import copy that isort had added
djyasin Sep 25, 2024
039355d
Move plugin functionality over to a new file that makes more sense si…
djyasin Sep 26, 2024
36dc7f7
Move _all_functionality for CLOUD_PROVIDERS from constants to plugins
djyasin Sep 26, 2024
a6907c6
Begin implementation of Making Choices Lazy
djyasin Sep 26, 2024
aeb24a3
Add copy import back to avoid mixing linting fixes with functional ch…
djyasin Sep 30, 2024
648e161
Update awx/main/utils/plugins.py
djyasin Sep 30, 2024
164bc61
Revise plugin.py module description
djyasin Sep 30, 2024
549ce23
Remove __all__ from plugins.py as it is not needed
djyasin Sep 30, 2024
6c889b1
Add import to serializers and correct import for tests
djyasin Sep 30, 2024
63bcfd4
Correct import for CLOUD_PROVIDERS
djyasin Sep 30, 2024
f356cca
Remove redundant import of discover_available_cloud_provider_plugin_n…
djyasin Sep 30, 2024
1f293b7
Update awx/main/utils/plugins.py
djyasin Oct 1, 2024
950bde1
Update awx/api/serializers.py
djyasin Oct 1, 2024
bb4ffc0
Revert import linting fix so as not to mix functional fixes with lint…
djyasin Oct 1, 2024
41e224b
Update awx/main/tests/functional/models/test_inventory.py
djyasin Oct 1, 2024
ff91727
Update awx/main/utils/plugins.py
djyasin Oct 1, 2024
6d5fce1
Update awx/main/models/base.py
djyasin Oct 1, 2024
1c59e4b
Update awx/main/tests/functional/test_inventory_source_injectors.py
djyasin Oct 1, 2024
ae7c83b
Revise references to CLOUD_PROVIDERS to discover_available_cloud_prov…
djyasin Oct 1, 2024
37a9dea
Remove references to CLOUD_INVENTORY_SOURCES and replace with functio…
djyasin Oct 1, 2024
96009a7
Replace references to CLOUD_PROVIDERS
djyasin Oct 2, 2024
3a5d452
Ran Black on plugins.py
djyasin Oct 2, 2024
788630f
Remove reference to CLOUD_RPOVIDERS and rework docstring
djyasin Oct 2, 2024
4a0db49
Remove @cache to resolve test failures TypeError: argument of type 'f…
djyasin Oct 2, 2024
7f88fa4
Remove unused commented out code
djyasin Oct 2, 2024
66cf64c
Reinstating @cache because the removal did not resolve the errors
djyasin Oct 2, 2024
9f8c06e
Add iter to function and remove () from function call in tests to get…
djyasin Oct 2, 2024
c5b96c9
Remove iter from serializers
djyasin Oct 3, 2024
5e2a236
Update awx/main/models/base.py
djyasin Oct 3, 2024
5a87544
Update awx/main/models/inventory.py
djyasin Oct 3, 2024
3e07edd
Update awx/main/models/inventory.py
djyasin Oct 3, 2024
7787f68
Update awx/main/models/inventory.py
djyasin Oct 3, 2024
eaaafd5
Update awx/main/tests/functional/models/test_inventory.py
djyasin Oct 3, 2024
a068839
Update awx/main/tests/functional/models/test_inventory.py
djyasin Oct 3, 2024
72dd3dc
Update awx/main/tests/functional/test_inventory_source_injectors.py
djyasin Oct 3, 2024
f8bb1b6
Replace discover_cloud_inventory_sources with compute_cloud_inventory…
djyasin Oct 3, 2024
c8d9b8d
Add doctsring and type to compute_cloud_inventory_sources
djyasin Oct 3, 2024
bdc7d22
Update awx/main/models/base.py
djyasin Oct 4, 2024
c44bd64
Remove unused import of 'awx.main.utils.execution_environments.to_con…
djyasin Oct 4, 2024
22e9c3c
Update awx/main/models/base.py
djyasin Oct 4, 2024
4c14b00
Update awx/main/models/__init__.py
djyasin Oct 4, 2024
16a41d2
Move compute_cloud_inventory_sources to plugins.py and change related…
djyasin Oct 4, 2024
6cb7ebd
Update awx/main/models/base.py
djyasin Oct 4, 2024
8addff4
Update awx/api/views/__init__.py
djyasin Oct 4, 2024
5d56d2f
Update awx/api/views/__init__.py
djyasin Oct 4, 2024
911c8f8
Update awx/main/utils/plugins.py
djyasin Oct 4, 2024
3670317
Ran make black on __init__.py to fix linting errors
djyasin Oct 4, 2024
44c20d7
Fix missing import of compute_cloud_inventory_sources
djyasin Oct 4, 2024
355f091
correct import path error in __init__.py
djyasin Oct 4, 2024
c4c23f9
Update awx/api/serializers.py
djyasin Oct 4, 2024
89117bc
Update awx/api/serializers.py
djyasin Oct 4, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 8 additions & 2 deletions awx/api/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,6 @@
WorkflowJobTemplate,
WorkflowJobTemplateNode,
StdoutMaxBytesExceeded,
CLOUD_INVENTORY_SOURCES,
)
from awx.main.models.base import VERBOSITY_CHOICES, NEW_JOB_TYPE_CHOICES
from awx.main.models.rbac import role_summary_fields_generator, give_creator_permissions, get_role_codenames, to_permissions, get_role_from_object_role
Expand All @@ -119,7 +118,9 @@
truncate_stdout,
get_licenser,
)

from awx.main.utils.filters import SmartFilter
from awx.main.utils.plugins import compute_cloud_inventory_sources, discover_available_cloud_provider_plugin_names
from awx.main.utils.named_url_graph import reset_counters
from awx.main.scheduler.task_manager_models import TaskManagerModels
from awx.main.redact import UriCleaner, REPLACE_STR
Expand Down Expand Up @@ -2350,6 +2351,7 @@ class Meta:

class InventorySourceOptionsSerializer(BaseSerializer):
credential = DeprecatedCredentialField(help_text=_('Cloud credential to use for inventory updates.'))
source = serializers.ChoiceField(choices=[])

class Meta:
fields = (
Expand All @@ -2371,6 +2373,10 @@ class Meta:
)
read_only_fields = ('*', 'custom_virtualenv')

def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields['source'].choices = discover_available_cloud_provider_plugin_names()

def get_related(self, obj):
res = super(InventorySourceOptionsSerializer, self).get_related(obj)
if obj.credential: # TODO: remove when 'credential' field is removed
Expand Down Expand Up @@ -5550,7 +5556,7 @@ def get_summary_fields(self, obj):
return summary_fields

def validate_unified_job_template(self, value):
if type(value) == InventorySource and value.source not in CLOUD_INVENTORY_SOURCES:
if type(value) == InventorySource and value.source not in compute_cloud_inventory_sources():
raise serializers.ValidationError(_('Inventory Source must be a cloud resource.'))
elif type(value) == Project and value.scm_type == '':
raise serializers.ValidationError(_('Manual Project cannot have a schedule set.'))
Expand Down
5 changes: 3 additions & 2 deletions awx/api/views/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@
)
from awx.main.utils.encryption import encrypt_value
from awx.main.utils.filters import SmartFilter
from awx.main.utils.plugins import compute_cloud_inventory_sources
from awx.main.redact import UriCleaner
from awx.api.permissions import (
JobTemplateCallbackPermission,
Expand Down Expand Up @@ -2234,9 +2235,9 @@ class InventorySourceNotificationTemplatesAnyList(SubListCreateAttachDetachAPIVi

def post(self, request, *args, **kwargs):
parent = self.get_parent_object()
if parent.source not in models.CLOUD_INVENTORY_SOURCES:
if parent.source not in compute_cloud_inventory_sources():
return Response(
dict(msg=_("Notification Templates can only be assigned when source is one of {}.").format(models.CLOUD_INVENTORY_SOURCES, parent.source)),
dict(msg=_("Notification Templates can only be assigned when source is one of {}.").format(compute_cloud_inventory_sources(), parent.source)),
status=status.HTTP_400_BAD_REQUEST,
)
return super(InventorySourceNotificationTemplatesAnyList, self).post(request, *args, **kwargs)
Expand Down
2 changes: 0 additions & 2 deletions awx/main/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,13 @@
from django.utils.translation import gettext_lazy as _
djyasin marked this conversation as resolved.
Show resolved Hide resolved

__all__ = [
'CLOUD_PROVIDERS',
'PRIVILEGE_ESCALATION_METHODS',
'ANSI_SGR_PATTERN',
'CAN_CANCEL',
'ACTIVE_STATES',
'STANDARD_INVENTORY_UPDATE_ENV',
]

CLOUD_PROVIDERS = ('azure_rm', 'ec2', 'gce', 'vmware', 'openstack', 'rhv', 'satellite6', 'controller', 'insights', 'terraform', 'openshift_virtualization')
PRIVILEGE_ESCALATION_METHODS = [
('sudo', _('Sudo')),
('su', _('Su')),
Expand Down
2 changes: 1 addition & 1 deletion awx/main/models/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
from ansible_base.lib.utils.models import user_summary_fields

# AWX
from awx.main.models.base import BaseModel, PrimordialModel, accepts_json, CLOUD_INVENTORY_SOURCES, VERBOSITY_CHOICES # noqa
from awx.main.models.base import BaseModel, PrimordialModel, accepts_json, VERBOSITY_CHOICES # noqa
from awx.main.models.unified_jobs import UnifiedJob, UnifiedJobTemplate, StdoutMaxBytesExceeded # noqa
from awx.main.models.organization import Organization, Profile, Team, UserSessionMembership # noqa
from awx.main.models.credential import Credential, CredentialType, CredentialInputSource, ManagedCredentialType, build_safe_env # noqa
Expand Down
3 changes: 0 additions & 3 deletions awx/main/models/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@

# AWX
from awx.main.utils import encrypt_field, parse_yaml_or_json
from awx.main.constants import CLOUD_PROVIDERS

__all__ = [
'VarsDictProperty',
Expand All @@ -32,7 +31,6 @@
'JOB_TYPE_CHOICES',
'AD_HOC_JOB_TYPE_CHOICES',
'PROJECT_UPDATE_JOB_TYPE_CHOICES',
'CLOUD_INVENTORY_SOURCES',
'VERBOSITY_CHOICES',
]

Expand Down Expand Up @@ -61,7 +59,6 @@
(PERM_INVENTORY_CHECK, _('Check')),
]

CLOUD_INVENTORY_SOURCES = list(CLOUD_PROVIDERS) + ['scm']

VERBOSITY_CHOICES = [
(0, '0 (Normal)'),
Expand Down
30 changes: 6 additions & 24 deletions awx/main/models/inventory.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,15 +28,15 @@

# AWX
from awx.api.versioning import reverse
from awx.main.constants import CLOUD_PROVIDERS
from awx.main.utils.plugins import discover_available_cloud_provider_plugin_names, compute_cloud_inventory_sources
from awx.main.consumers import emit_channel_notification
from awx.main.fields import (
ImplicitRoleField,
SmartFilterField,
OrderedManyToManyField,
)
from awx.main.managers import HostManager, HostMetricActiveManager
from awx.main.models.base import BaseModel, CommonModelNameNotUnique, VarsDictProperty, CLOUD_INVENTORY_SOURCES, accepts_json
from awx.main.models.base import BaseModel, CommonModelNameNotUnique, VarsDictProperty, accepts_json
from awx.main.models.events import InventoryUpdateEvent, UnpartitionedInventoryUpdateEvent
from awx.main.models.unified_jobs import UnifiedJob, UnifiedJobTemplate
from awx.main.models.mixins import (
Expand Down Expand Up @@ -394,7 +394,7 @@ def update_computed_fields(self):
if self.kind == 'smart':
active_inventory_sources = self.inventory_sources.none()
else:
active_inventory_sources = self.inventory_sources.filter(source__in=CLOUD_INVENTORY_SOURCES)
active_inventory_sources = self.inventory_sources.filter(source__in=compute_cloud_inventory_sources())
failed_inventory_sources = active_inventory_sources.filter(last_job_failed=True)
total_hosts = active_hosts.count()
# if total_hosts has changed, set update_task_impact to True
Expand Down Expand Up @@ -914,23 +914,6 @@ class InventorySourceOptions(BaseModel):

injectors = dict()

SOURCE_CHOICES = [
('file', _('File, Directory or Script')),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we have these text explanations anywhere? Or do we need to have them in the plugins interfaces?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have been unable to locate the explanations in the repo, I think we may need them in the plugins interface. I will triple-check, though.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I haven't found these in the moved-out plugins either. So yes, we should consider including this information in the plugins interface. We could temporarily put this mapping into the interfaces repo for now, or have it somewhere in serializers. I'm not sure where it's displayed.

('constructed', _('Template additional groups and hostvars at runtime')),
('scm', _('Sourced from a Project')),
('ec2', _('Amazon EC2')),
('gce', _('Google Compute Engine')),
('azure_rm', _('Microsoft Azure Resource Manager')),
('vmware', _('VMware vCenter')),
('satellite6', _('Red Hat Satellite 6')),
('openstack', _('OpenStack')),
('rhv', _('Red Hat Virtualization')),
('controller', _('Red Hat Ansible Automation Platform')),
('insights', _('Red Hat Insights')),
('terraform', _('Terraform State')),
('openshift_virtualization', _('OpenShift Virtualization')),
]

# From the options of the Django management base command
INVENTORY_UPDATE_VERBOSITY_CHOICES = [
(0, '0 (WARNING)'),
Expand All @@ -943,7 +926,6 @@ class Meta:

source = models.CharField(
max_length=32,
choices=SOURCE_CHOICES,
blank=False,
default=None,
)
Expand Down Expand Up @@ -1047,7 +1029,7 @@ def cloud_credential_validation(source, cred):
# Allow an EC2 source to omit the credential. If Tower is running on
# an EC2 instance with an IAM Role assigned, boto will use credentials
# from the instance metadata instead of those explicitly provided.
elif source in CLOUD_PROVIDERS and source not in ['ec2', 'openshift_virtualization']:
elif source in discover_available_cloud_provider_plugin_names() and source not in ['ec2', 'openshift_virtualization']:
return _('Credential is required for a cloud source.')
elif source == 'custom' and cred and cred.credential_type.kind in ('scm', 'ssh', 'insights', 'vault'):
return _('Credentials of type machine, source control, insights and vault are disallowed for custom inventory sources.')
Expand All @@ -1061,7 +1043,7 @@ def get_cloud_credential(self):
"""Return the credential which is directly tied to the inventory source type."""
credential = None
for cred in self.credentials.all():
if self.source in CLOUD_PROVIDERS:
if self.source in discover_available_cloud_provider_plugin_names():
if cred.kind == self.source.replace('ec2', 'aws'):
credential = cred
break
Expand All @@ -1077,7 +1059,7 @@ def get_extra_credentials(self):
These are all credentials that should run their own inject_credential logic.
"""
special_cred = None
if self.source in CLOUD_PROVIDERS:
if self.source in discover_available_cloud_provider_plugin_names():
# these have special injection logic associated with them
special_cred = self.get_cloud_credential()
extra_creds = []
Expand Down
6 changes: 3 additions & 3 deletions awx/main/tests/functional/models/test_inventory.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@

# AWX
from awx.main.models import Host, Inventory, InventorySource, InventoryUpdate, CredentialType, Credential, Job
from awx.main.constants import CLOUD_PROVIDERS
from awx.main.utils.filters import SmartFilter
from awx.main.utils.plugins import discover_available_cloud_provider_plugin_names


@pytest.mark.django_db
Expand Down Expand Up @@ -166,11 +166,11 @@ def test_extra_credentials(self, project, credential):

def test_all_cloud_sources_covered(self):
"""Code in several places relies on the fact that the older
CLOUD_PROVIDERS constant contains the same names as what are
discover_cloud_provider_plugin_names returns the same names as what are
defined within the injectors
"""
# slight exception case for constructed, because it has a FQCN but is not a cloud source
assert set(CLOUD_PROVIDERS) | set(['constructed']) == set(InventorySource.injectors.keys())
assert set(discover_available_cloud_provider_plugin_names()) | set(['constructed']) == set(InventorySource.injectors.keys())

@pytest.mark.parametrize('source,filename', [('ec2', 'aws_ec2.yml'), ('openstack', 'openstack.yml'), ('gce', 'gcp_compute.yml')])
def test_plugin_filenames(self, source, filename):
Expand Down
5 changes: 3 additions & 2 deletions awx/main/tests/functional/test_inventory_source_injectors.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,9 @@

from awx.main.tasks.jobs import RunInventoryUpdate
from awx.main.models import InventorySource, Credential, CredentialType, UnifiedJob, ExecutionEnvironment
from awx.main.constants import CLOUD_PROVIDERS, STANDARD_INVENTORY_UPDATE_ENV
from awx.main.constants import STANDARD_INVENTORY_UPDATE_ENV
from awx.main.tests import data
from awx.main.utils.plugins import discover_available_cloud_provider_plugin_names

from django.conf import settings

Expand Down Expand Up @@ -192,7 +193,7 @@ def create_reference_data(source_dir, env, content):


@pytest.mark.django_db
@pytest.mark.parametrize('this_kind', CLOUD_PROVIDERS)
@pytest.mark.parametrize('this_kind', discover_available_cloud_provider_plugin_names())
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking into the test failures, it seems to me that my suspicion was likely right and this might need to be moved away from import-time invocation. And I'd try converting this to use pytest-subtests.

def test_inventory_update_injected_content(this_kind, inventory, fake_credential_factory, mock_me):
ExecutionEnvironment.objects.create(name='Control Plane EE', managed=True)
ExecutionEnvironment.objects.create(name='Default Job EE', managed=False)
Expand Down
33 changes: 33 additions & 0 deletions awx/main/utils/plugins.py
djyasin marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# Copyright (c) 2024 Ansible, Inc.
# All Rights Reserved.

"""
This module contains the code responsible for extracting the lists of dynamically discovered plugins.
"""

from functools import cache


@cache
def discover_available_cloud_provider_plugin_names() -> list[str]:
"""Return a list of cloud plugin names available in runtime.

The discovery result is cached since it does not change throughout
the life cycle of the server run.

:returns: List of plugin cloud names.
:rtype: list[str]
"""
from awx.main.models.inventory import InventorySourceOptions

return list(InventorySourceOptions.injectors.keys())


def compute_cloud_inventory_sources() -> list[str]:
"""Return a list of cloud provider plugin names
available plus source control management.

:returns: List of plugin cloud names plus source control.
:rtype: list[str]
"""
return discover_available_cloud_provider_plugin_names() + ['scm']
Loading