Skip to content

Commit

Permalink
Merge pull request #12728 from john-westcott-iv/ig_fallback
Browse files Browse the repository at this point in the history
Adding prevent_instance_group_fallback
  • Loading branch information
john-westcott-iv authored Oct 3, 2022
2 parents 8333b0c + d1588b9 commit 5347637
Show file tree
Hide file tree
Showing 21 changed files with 456 additions and 291 deletions.
2 changes: 2 additions & 0 deletions awx/api/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -1680,6 +1680,7 @@ class Meta:
'total_inventory_sources',
'inventory_sources_with_failures',
'pending_deletion',
'prevent_instance_group_fallback',
)

def get_related(self, obj):
Expand Down Expand Up @@ -2937,6 +2938,7 @@ class Meta:
'job_slice_count',
'webhook_service',
'webhook_credential',
'prevent_instance_group_fallback',
)
read_only_fields = ('*', 'custom_virtualenv')

Expand Down
29 changes: 29 additions & 0 deletions awx/main/migrations/0172_prevent_instance_fallback.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# Generated by Django 3.2.13 on 2022-09-29 18:10

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('main', '0171_add_health_check_started'),
]

operations = [
migrations.AddField(
model_name='inventory',
name='prevent_instance_group_fallback',
field=models.BooleanField(
default=False,
help_text='If enabled, the inventory will prevent adding any organization instance groups to the list of preferred instances groups to run associated job templates on.If this setting is enabled and you provided an empty list, the global instance groups will be applied.',
),
),
migrations.AddField(
model_name='jobtemplate',
name='prevent_instance_group_fallback',
field=models.BooleanField(
default=False,
help_text='If enabled, the job template will prevent adding any inventory or organization instance groups to the list of preferred instances groups to run on.If this setting is enabled and you provided an empty list, the global instance groups will be applied.',
),
),
]
15 changes: 7 additions & 8 deletions awx/main/models/ad_hoc_commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -228,15 +228,14 @@ def add_to_update_fields(name):

@property
def preferred_instance_groups(self):
if self.inventory is not None and self.inventory.organization is not None:
organization_groups = [x for x in self.inventory.organization.instance_groups.all()]
else:
organization_groups = []
selected_groups = []
if self.inventory is not None:
inventory_groups = [x for x in self.inventory.instance_groups.all()]
else:
inventory_groups = []
selected_groups = inventory_groups + organization_groups
for instance_group in self.inventory.instance_groups.all():
selected_groups.append(instance_group)
if not self.inventory.prevent_instance_group_fallback and self.inventory.organization is not None:
for instance_group in self.inventory.organization.instance_groups.all():
selected_groups.append(instance_group)

if not selected_groups:
return self.global_instance_groups
return selected_groups
Expand Down
32 changes: 23 additions & 9 deletions awx/main/models/inventory.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ class Inventory(CommonModelNameNotUnique, ResourceMixin, RelatedJobsMixin):
an inventory source contains lists and hosts.
"""

FIELDS_TO_PRESERVE_AT_COPY = ['hosts', 'groups', 'instance_groups']
FIELDS_TO_PRESERVE_AT_COPY = ['hosts', 'groups', 'instance_groups', 'prevent_instance_group_fallback']
KIND_CHOICES = [
('', _('Hosts have a direct link to this inventory.')),
('smart', _('Hosts for inventory generated using the host_filter property.')),
Expand Down Expand Up @@ -175,6 +175,16 @@ class Meta:
related_name='inventory_labels',
help_text=_('Labels associated with this inventory.'),
)
prevent_instance_group_fallback = models.BooleanField(
default=False,
help_text=(
"If enabled, the inventory will prevent adding any organization "
"instance groups to the list of preferred instances groups to run "
"associated job templates on."
"If this setting is enabled and you provided an empty list, the global instance "
"groups will be applied."
),
)

def get_absolute_url(self, request=None):
return reverse('api:inventory_detail', kwargs={'pk': self.pk}, request=request)
Expand Down Expand Up @@ -1268,15 +1278,19 @@ def get_notification_friendly_name(self):

@property
def preferred_instance_groups(self):
if self.inventory_source.inventory is not None and self.inventory_source.inventory.organization is not None:
organization_groups = [x for x in self.inventory_source.inventory.organization.instance_groups.all()]
else:
organization_groups = []
selected_groups = []
if self.inventory_source.inventory is not None:
inventory_groups = [x for x in self.inventory_source.inventory.instance_groups.all()]
else:
inventory_groups = []
selected_groups = inventory_groups + organization_groups
# Add the inventory sources IG to the selected IGs first
for instance_group in self.inventory_source.inventory.instance_groups.all():
selected_groups.append(instance_group)
# If the inventory allows for fallback and we have an organization then also append the orgs IGs to the end of the list
if (
not getattr(self.inventory_source.inventory, 'prevent_instance_group_fallback', False)
and self.inventory_source.inventory.organization is not None
):
for instance_group in self.inventory_source.inventory.organization.instance_groups.all():
selected_groups.append(instance_group)

if not selected_groups:
return self.global_instance_groups
return selected_groups
Expand Down
31 changes: 17 additions & 14 deletions awx/main/models/jobs.py
Original file line number Diff line number Diff line change
Expand Up @@ -203,7 +203,7 @@ class JobTemplate(UnifiedJobTemplate, JobOptions, SurveyJobTemplateMixin, Resour
playbook) to an inventory source with a given credential.
"""

FIELDS_TO_PRESERVE_AT_COPY = ['labels', 'instance_groups', 'credentials', 'survey_spec']
FIELDS_TO_PRESERVE_AT_COPY = ['labels', 'instance_groups', 'credentials', 'survey_spec', 'prevent_instance_group_fallback']
FIELDS_TO_DISCARD_AT_COPY = ['vault_credential', 'credential']
SOFT_UNIQUE_TOGETHER = [('polymorphic_ctype', 'name', 'organization')]

Expand Down Expand Up @@ -274,6 +274,15 @@ class Meta:
'admin_role',
],
)
prevent_instance_group_fallback = models.BooleanField(
default=False,
help_text=(
"If enabled, the job template will prevent adding any inventory or organization "
"instance groups to the list of preferred instances groups to run on."
"If this setting is enabled and you provided an empty list, the global instance "
"groups will be applied."
),
)

@classmethod
def _get_unified_job_class(cls):
Expand Down Expand Up @@ -797,19 +806,13 @@ def is_container_group_task(self):
def preferred_instance_groups(self):
# If the user specified instance groups those will be handled by the unified_job.create_unified_job
# This function handles only the defaults for a template w/o user specification
if self.organization is not None:
organization_groups = [x for x in self.organization.instance_groups.all()]
else:
organization_groups = []
if self.inventory is not None:
inventory_groups = [x for x in self.inventory.instance_groups.all()]
else:
inventory_groups = []
if self.job_template is not None:
template_groups = [x for x in self.job_template.instance_groups.all()]
else:
template_groups = []
selected_groups = template_groups + inventory_groups + organization_groups
selected_groups = []
for obj_type in ['job_template', 'inventory', 'organization']:
if getattr(self, obj_type) is not None:
for instance_group in getattr(self, obj_type).instance_groups.all():
selected_groups.append(instance_group)
if getattr(getattr(self, obj_type), 'prevent_instance_group_fallback', False):
break
if not selected_groups:
return self.global_instance_groups
return selected_groups
Expand Down
5 changes: 5 additions & 0 deletions awx/main/tests/functional/test_copy.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ def test_job_template_copy(
job_template_with_survey_passwords.inventory = inventory
job_template_with_survey_passwords.labels.add(label)
job_template_with_survey_passwords.instance_groups.add(ig)
job_template_with_survey_passwords.prevent_instance_group_fallback = True
job_template_with_survey_passwords.save()
job_template_with_survey_passwords.credentials.add(credential)
job_template_with_survey_passwords.credentials.add(machine_credential)
Expand Down Expand Up @@ -65,6 +66,7 @@ def test_job_template_copy(
assert jt_copy.labels.get(pk=label.pk) == label
assert jt_copy.instance_groups.count() != 0
assert jt_copy.instance_groups.get(pk=ig.pk) == ig
assert jt_copy.prevent_instance_group_fallback == True


@pytest.mark.django_db
Expand Down Expand Up @@ -95,6 +97,8 @@ def test_inventory_copy(inventory, group_factory, post, get, alice, organization
host = group_1_1.hosts.create(name='host', inventory=inventory)
group_2_1.hosts.add(host)
inventory.admin_role.members.add(alice)
inventory.prevent_instance_group_fallback = True
inventory.save()
assert get(reverse('api:inventory_copy', kwargs={'pk': inventory.pk}), alice, expect=200).data['can_copy'] is False
inventory.organization.admin_role.members.add(alice)
assert get(reverse('api:inventory_copy', kwargs={'pk': inventory.pk}), alice, expect=200).data['can_copy'] is True
Expand All @@ -110,6 +114,7 @@ def test_inventory_copy(inventory, group_factory, post, get, alice, organization
assert inventory_copy.organization == organization
assert inventory_copy.created_by == alice
assert inventory_copy.name == 'new inv name'
assert inventory_copy.prevent_instance_group_fallback == True
assert set(group_1_1_copy.parents.all()) == set()
assert set(group_2_1_copy.parents.all()) == set([group_1_1_copy])
assert set(group_2_2_copy.parents.all()) == set([group_1_1_copy, group_2_1_copy])
Expand Down
4 changes: 4 additions & 0 deletions awx/main/tests/functional/test_instances.py
Original file line number Diff line number Diff line change
Expand Up @@ -391,6 +391,8 @@ def test_ad_hoc_instance_groups(self, instance_group_factory, inventory, default
assert ad_hoc.preferred_instance_groups == [ig_org]
inventory.instance_groups.add(ig_inv)
assert ad_hoc.preferred_instance_groups == [ig_inv, ig_org]
inventory.prevent_instance_group_fallback = True
assert ad_hoc.preferred_instance_groups == [ig_inv]

def test_inventory_update_instance_groups(self, instance_group_factory, inventory_source, default_instance_group):
iu = InventoryUpdate.objects.create(inventory_source=inventory_source, source=inventory_source.source)
Expand All @@ -404,6 +406,8 @@ def test_inventory_update_instance_groups(self, instance_group_factory, inventor
inventory_source.instance_groups.add(ig_tmp)
# API does not allow setting IGs on inventory source, so ignore those
assert iu.preferred_instance_groups == [ig_inv, ig_org]
inventory_source.inventory.prevent_instance_group_fallback = True
assert iu.preferred_instance_groups == [ig_inv]

def test_job_instance_groups(self, instance_group_factory, inventory, project, default_instance_group):
jt = JobTemplate.objects.create(inventory=inventory, project=project)
Expand Down
39 changes: 38 additions & 1 deletion awx/ui/src/screens/Inventory/InventoryDetail/InventoryDetail.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,14 @@ import React, { useCallback, useEffect } from 'react';
import { Link, useHistory } from 'react-router-dom';

import { t } from '@lingui/macro';
import { Button, Chip } from '@patternfly/react-core';
import {
Button,
Chip,
TextList,
TextListItem,
TextListItemVariants,
TextListVariants,
} from '@patternfly/react-core';
import AlertModal from 'components/AlertModal';
import { CardBody, CardActionsRow } from 'components/Card';
import { DetailList, Detail, UserDateDetail } from 'components/DetailList';
Expand Down Expand Up @@ -50,9 +57,23 @@ function InventoryDetail({ inventory }) {
const { organization, user_capabilities: userCapabilities } =
inventory.summary_fields;

const { prevent_instance_group_fallback } = inventory;

const deleteDetailsRequests =
relatedResourceDeleteRequests.inventory(inventory);

const renderOptionsField = prevent_instance_group_fallback;

const renderOptions = (
<TextList component={TextListVariants.ul}>
{prevent_instance_group_fallback && (
<TextListItem component={TextListItemVariants.li}>
{t`Prevent Instance Group Fallback`}
</TextListItem>
)}
</TextList>
);

if (isLoading) {
return <ContentLoading />;
}
Expand Down Expand Up @@ -104,6 +125,22 @@ function InventoryDetail({ inventory }) {
isEmpty={instanceGroups.length === 0}
/>
)}
{prevent_instance_group_fallback && (
<Detail
label={t`Prevent Instance Group Fallback`}
dataCy="inv-detail-prevent-instnace-group-fallback"
helpText={helpText.preventInstanceGroupFallback}
/>
)}
{renderOptionsField && (
<Detail
fullWidth
label={t`Enabled Options`}
value={renderOptions}
dataCy="jt-detail-enabled-options"
helpText={helpText.enabledOptions}
/>
)}
{inventory.summary_fields.labels && (
<Detail
fullWidth
Expand Down
5 changes: 5 additions & 0 deletions awx/ui/src/screens/Inventory/shared/Inventory.helptext.js
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,11 @@ const getInventoryHelpTextStrings = () => ({
sourcePath: t`The inventory file
to be synced by this source. You can select from
the dropdown or enter a file within the input.`,
preventInstanceGroupFallback: t`If enabled, the inventory will prevent adding any organization instance groups to the list of preferred instances groups to run associated job templates on.
Note: If this setting is enabled and you provided an empty list, the global instance groups will be applied.`,
enabledOptions: (
<p>{t`Prevent Instance Group Fallback: If enabled, the inventory will prevent adding any organization instance groups to the list of preferred instances groups to run associated job templates on.`}</p>
),
});

export default getInventoryHelpTextStrings;
23 changes: 21 additions & 2 deletions awx/ui/src/screens/Inventory/shared/InventoryForm.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,21 @@ import { func, shape } from 'prop-types';
import { Form, FormGroup } from '@patternfly/react-core';
import { VariablesField } from 'components/CodeEditor';
import Popover from 'components/Popover';
import FormField, { FormSubmitError } from 'components/FormField';
import FormField, {
CheckboxField,
FormSubmitError,
} from 'components/FormField';
import FormActionGroup from 'components/FormActionGroup';
import { required } from 'util/validators';
import LabelSelect from 'components/LabelSelect';
import InstanceGroupsLookup from 'components/Lookup/InstanceGroupsLookup';
import OrganizationLookup from 'components/Lookup/OrganizationLookup';
import ContentError from 'components/ContentError';
import { FormColumnLayout, FormFullWidthLayout } from 'components/FormLayout';
import {
FormColumnLayout,
FormFullWidthLayout,
FormCheckboxLayout,
} from 'components/FormLayout';
import getHelpText from './Inventory.helptext';

function InventoryFormFields({ inventory }) {
Expand Down Expand Up @@ -84,6 +91,16 @@ function InventoryFormFields({ inventory }) {
createText={t`Create`}
/>
</FormGroup>
<FormGroup fieldId="inventory-option-checkboxes" label={t`Options`}>
<FormCheckboxLayout>
<CheckboxField
id="option-prevent-instance-group-fallback"
name="prevent_instance_group_fallback"
label={t`Prevent Instance Group Fallback`}
tooltip={helpText.preventInstanceGroupFallback}
/>
</FormCheckboxLayout>
</FormGroup>
<VariablesField
tooltip={helpText.variables()}
id="inventory-variables"
Expand Down Expand Up @@ -112,6 +129,8 @@ function InventoryForm({
null,
instanceGroups: instanceGroups || [],
labels: inventory?.summary_fields?.labels?.results || [],
prevent_instance_group_fallback:
inventory.prevent_instance_group_fallback || false,
};
return (
<Formik
Expand Down
Loading

0 comments on commit 5347637

Please sign in to comment.