Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
38 changes: 33 additions & 5 deletions awx/main/models/inventory.py
Original file line number Diff line number Diff line change
Expand Up @@ -1471,6 +1471,8 @@ class PluginFileInjector(object):
collection = None
collection_migration = '2.9' # Starting with this version, we use collections
use_fqcn = False # plugin: name versus plugin: namespace.collection.name
# other plugin FQCNs the user may select via `plugin:` in source_vars in place of the default
alternate_plugins = frozenset()

# TODO: delete this method and update unit tests
@classmethod
Expand All @@ -1486,6 +1488,27 @@ def filename(self):
"""
return '{0}.yml'.format(self.plugin_name)

def get_alternate_plugin(self, source_vars):
"""The user-selected alternate plugin from the `plugin:` key of
source_vars, or None to use the default. Values of any other type or
FQCN are ignored (and overridden), same as before alternates existed.
"""
plugin = source_vars.get('plugin')
if isinstance(plugin, str) and plugin in self.alternate_plugins:
return plugin
return None

def get_filename(self, inventory_update):
"""Inventory filename for the plugin that will actually parse it.
The auto plugin loads whatever the file's `plugin:` key names, and that
plugin's verify_file() generally demands this exact file naming, so a
user-selected alternate plugin must also change the filename.
"""
plugin = self.get_alternate_plugin(inventory_update.source_vars_dict)
if plugin is not None:
return '{0}.yml'.format(plugin.rsplit('.', 1)[-1])
return self.filename

def inventory_contents(self, inventory_update, private_data_dir):
"""Returns a string that is the content for the inventory file for the inventory plugin"""
return yaml.safe_dump(self.inventory_as_dict(inventory_update, private_data_dir), default_flow_style=False, width=1000)
Expand All @@ -1497,7 +1520,9 @@ def inventory_as_dict(self, inventory_update, private_data_dir):
Note that a plugin value of '' should still be overridden.
'''
if self.plugin_name is not None:
if hasattr(self, 'downstream_namespace') and server_product_name() != 'AWX':
if self.get_alternate_plugin(source_vars) is not None:
pass # user selected an alternate supported plugin, keep it
elif hasattr(self, 'downstream_namespace') and server_product_name() != 'AWX':
source_vars['plugin'] = f'{self.downstream_namespace}.{self.downstream_collection}.{self.plugin_name}'
elif self.use_fqcn:
source_vars['plugin'] = f'{self.namespace}.{self.collection}.{self.plugin_name}'
Expand Down Expand Up @@ -1599,6 +1624,9 @@ class vmware(PluginFileInjector):
base_injector = 'managed'
namespace = 'community'
collection = 'vmware'
use_fqcn = True
# community.vmware is deprecated; users may opt into its replacement
alternate_plugins = frozenset({'vmware.vmware.vms'})


class openstack(PluginFileInjector):
Expand Down Expand Up @@ -1679,10 +1707,10 @@ def inventory_as_dict(self, inventory_update, private_data_dir):
class ascender(PluginFileInjector):
plugin_name = 'controller' # TODO: relying on routing for now, update after EEs pick up revised collection
base_injector = 'template'
namespace = 'awx'
collection = 'awx'
downstream_namespace = 'ansible'
downstream_collection = 'controller'
namespace = 'ctrliq'
collection = 'ascender'
# downstream_namespace = 'ansible'
# downstream_collection = 'controller'
use_fqcn = True


Expand Down
7 changes: 4 additions & 3 deletions awx/main/tasks/jobs.py
Original file line number Diff line number Diff line change
Expand Up @@ -1632,9 +1632,10 @@ def pseudo_build_inventory(self, inventory_update, private_data_dir):

if injector is not None:
content = injector.inventory_contents(inventory_update, private_data_dir)
# must be a statically named file
self.write_private_data_file(private_data_dir, injector.filename, content, sub_dir='inventory', file_permissions=0o700)
rel_path = os.path.join('inventory', injector.filename)
# the file must bear the exact name the selected plugin's verify_file() demands
inventory_filename = injector.get_filename(inventory_update)
self.write_private_data_file(private_data_dir, inventory_filename, content, sub_dir='inventory', file_permissions=0o700)
rel_path = os.path.join('inventory', inventory_filename)
elif src == 'scm':
rel_path = os.path.join('project', inventory_update.source_path)

Expand Down
27 changes: 26 additions & 1 deletion awx/main/tests/functional/models/test_inventory.py
Original file line number Diff line number Diff line change
Expand Up @@ -241,14 +241,39 @@ def test_plugin_filenames(self, source, filename):
# ('rhv', 'ovirt.ovirt.ovirt'),
('satellite6', 'theforeman.foreman.foreman'),
# ('insights', 'redhatinsights.insights.insights'),
('ascender', 'awx.awx.controller'),
('ascender', 'ctrliq.ascender.controller'),
('terraform', 'cloud.terraform.terraform_state'),
],
)
def test_plugin_proper_names(self, source, proper_name):
injector = InventorySource.injectors[source]()
assert injector.get_proper_name() == proper_name

@pytest.mark.parametrize(
'user_plugin,expected',
[
(None, 'community.vmware.vmware_vm_inventory'),
('community.vmware.vmware_vm_inventory', 'community.vmware.vmware_vm_inventory'),
('vmware.vmware.vms', 'vmware.vmware.vms'),
('evil.hacker.plugin', 'community.vmware.vmware_vm_inventory'),
('', 'community.vmware.vmware_vm_inventory'),
(['vmware.vmware.vms'], 'community.vmware.vmware_vm_inventory'), # unhashable values must not crash
({'name': 'vmware.vmware.vms'}, 'community.vmware.vmware_vm_inventory'),
],
)
def test_vmware_alternate_plugin_selection(self, user_plugin, expected):
"""The vmware source lets the user pick the plugin from the deprecated
community.vmware collection or its vmware.vmware replacement via the
`plugin` key of source_vars; anything else is overridden with the default.
"""
injector = InventorySource.injectors['vmware']()
source_vars = {} if user_plugin is None else {'plugin': user_plugin}
inventory_update = mock.Mock(source_vars_dict=source_vars)
assert injector.inventory_as_dict(inventory_update, '/tmp/private_data')['plugin'] == expected
# the auto plugin hands the file to the plugin named inside it, whose
# verify_file() only accepts files named after that plugin
assert injector.get_filename(inventory_update) == '{0}.yml'.format(expected.rsplit('.', 1)[-1])


@pytest.mark.django_db
def test_custom_source_custom_credential(organization):
Expand Down
2 changes: 1 addition & 1 deletion awx/ui/src/locales/ar/messages.js

Large diffs are not rendered by default.

Loading
Loading