mirror of
https://github.com/ansible/awx.git
synced 2026-03-03 09:48:51 -03:30
Merge pull request #13644 from fosterseth/inv_source_scm_branch
Add scm_branch to inventory source and inventory update
This commit is contained in:
@@ -160,7 +160,7 @@ SUMMARIZABLE_FK_FIELDS = {
|
|||||||
'default_environment': DEFAULT_SUMMARY_FIELDS + ('image',),
|
'default_environment': DEFAULT_SUMMARY_FIELDS + ('image',),
|
||||||
'execution_environment': DEFAULT_SUMMARY_FIELDS + ('image',),
|
'execution_environment': DEFAULT_SUMMARY_FIELDS + ('image',),
|
||||||
'project': DEFAULT_SUMMARY_FIELDS + ('status', 'scm_type', 'allow_override'),
|
'project': DEFAULT_SUMMARY_FIELDS + ('status', 'scm_type', 'allow_override'),
|
||||||
'source_project': DEFAULT_SUMMARY_FIELDS + ('status', 'scm_type'),
|
'source_project': DEFAULT_SUMMARY_FIELDS + ('status', 'scm_type', 'allow_override'),
|
||||||
'project_update': DEFAULT_SUMMARY_FIELDS + ('status', 'failed'),
|
'project_update': DEFAULT_SUMMARY_FIELDS + ('status', 'failed'),
|
||||||
'credential': DEFAULT_SUMMARY_FIELDS + ('kind', 'cloud', 'kubernetes', 'credential_type_id'),
|
'credential': DEFAULT_SUMMARY_FIELDS + ('kind', 'cloud', 'kubernetes', 'credential_type_id'),
|
||||||
'signature_validation_credential': DEFAULT_SUMMARY_FIELDS + ('kind', 'credential_type_id'),
|
'signature_validation_credential': DEFAULT_SUMMARY_FIELDS + ('kind', 'credential_type_id'),
|
||||||
@@ -2128,6 +2128,7 @@ class InventorySourceOptionsSerializer(BaseSerializer):
|
|||||||
'source',
|
'source',
|
||||||
'source_path',
|
'source_path',
|
||||||
'source_vars',
|
'source_vars',
|
||||||
|
'scm_branch',
|
||||||
'credential',
|
'credential',
|
||||||
'enabled_var',
|
'enabled_var',
|
||||||
'enabled_value',
|
'enabled_value',
|
||||||
@@ -2292,10 +2293,14 @@ class InventorySourceSerializer(UnifiedJobTemplateSerializer, InventorySourceOpt
|
|||||||
if ('source' in attrs or 'source_project' in attrs) and get_field_from_model_or_attrs('source_project') is None:
|
if ('source' in attrs or 'source_project' in attrs) and get_field_from_model_or_attrs('source_project') is None:
|
||||||
raise serializers.ValidationError({"source_project": _("Project required for scm type sources.")})
|
raise serializers.ValidationError({"source_project": _("Project required for scm type sources.")})
|
||||||
else:
|
else:
|
||||||
redundant_scm_fields = list(filter(lambda x: attrs.get(x, None), ['source_project', 'source_path']))
|
redundant_scm_fields = list(filter(lambda x: attrs.get(x, None), ['source_project', 'source_path', 'scm_branch']))
|
||||||
if redundant_scm_fields:
|
if redundant_scm_fields:
|
||||||
raise serializers.ValidationError({"detail": _("Cannot set %s if not SCM type." % ' '.join(redundant_scm_fields))})
|
raise serializers.ValidationError({"detail": _("Cannot set %s if not SCM type." % ' '.join(redundant_scm_fields))})
|
||||||
|
|
||||||
|
project = get_field_from_model_or_attrs('source_project')
|
||||||
|
if get_field_from_model_or_attrs('scm_branch') and not project.allow_override:
|
||||||
|
raise serializers.ValidationError({'scm_branch': _('Project does not allow overriding branch.')})
|
||||||
|
|
||||||
attrs = super(InventorySourceSerializer, self).validate(attrs)
|
attrs = super(InventorySourceSerializer, self).validate(attrs)
|
||||||
|
|
||||||
# Check type consistency of source and cloud credential, if provided
|
# Check type consistency of source and cloud credential, if provided
|
||||||
|
|||||||
32
awx/main/migrations/0175_inventorysource_scm_branch.py
Normal file
32
awx/main/migrations/0175_inventorysource_scm_branch.py
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
# Generated by Django 3.2.16 on 2023-03-03 20:44
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
dependencies = [
|
||||||
|
('main', '0174_ensure_org_ee_admin_roles'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='inventorysource',
|
||||||
|
name='scm_branch',
|
||||||
|
field=models.CharField(
|
||||||
|
blank=True,
|
||||||
|
default='',
|
||||||
|
help_text='Inventory source SCM branch. Project default used if blank. Only allowed if project allow_override field is set to true.',
|
||||||
|
max_length=1024,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='inventoryupdate',
|
||||||
|
name='scm_branch',
|
||||||
|
field=models.CharField(
|
||||||
|
blank=True,
|
||||||
|
default='',
|
||||||
|
help_text='Inventory source SCM branch. Project default used if blank. Only allowed if project allow_override field is set to true.',
|
||||||
|
max_length=1024,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -872,6 +872,12 @@ class InventorySourceOptions(BaseModel):
|
|||||||
default='',
|
default='',
|
||||||
help_text=_('Inventory source variables in YAML or JSON format.'),
|
help_text=_('Inventory source variables in YAML or JSON format.'),
|
||||||
)
|
)
|
||||||
|
scm_branch = models.CharField(
|
||||||
|
max_length=1024,
|
||||||
|
default='',
|
||||||
|
blank=True,
|
||||||
|
help_text=_('Inventory source SCM branch. Project default used if blank. Only allowed if project allow_override field is set to true.'),
|
||||||
|
)
|
||||||
enabled_var = models.TextField(
|
enabled_var = models.TextField(
|
||||||
blank=True,
|
blank=True,
|
||||||
default='',
|
default='',
|
||||||
|
|||||||
@@ -759,7 +759,7 @@ class SourceControlMixin(BaseTask):
|
|||||||
|
|
||||||
def sync_and_copy(self, project, private_data_dir, scm_branch=None):
|
def sync_and_copy(self, project, private_data_dir, scm_branch=None):
|
||||||
self.acquire_lock(project, self.instance.id)
|
self.acquire_lock(project, self.instance.id)
|
||||||
|
is_commit = False
|
||||||
try:
|
try:
|
||||||
original_branch = None
|
original_branch = None
|
||||||
failed_reason = project.get_reason_if_failed()
|
failed_reason = project.get_reason_if_failed()
|
||||||
@@ -771,6 +771,7 @@ class SourceControlMixin(BaseTask):
|
|||||||
if os.path.exists(project_path):
|
if os.path.exists(project_path):
|
||||||
git_repo = git.Repo(project_path)
|
git_repo = git.Repo(project_path)
|
||||||
if git_repo.head.is_detached:
|
if git_repo.head.is_detached:
|
||||||
|
is_commit = True
|
||||||
original_branch = git_repo.head.commit
|
original_branch = git_repo.head.commit
|
||||||
else:
|
else:
|
||||||
original_branch = git_repo.active_branch
|
original_branch = git_repo.active_branch
|
||||||
@@ -782,7 +783,11 @@ class SourceControlMixin(BaseTask):
|
|||||||
# for git project syncs, non-default branches can be problems
|
# for git project syncs, non-default branches can be problems
|
||||||
# restore to branch the repo was on before this run
|
# restore to branch the repo was on before this run
|
||||||
try:
|
try:
|
||||||
original_branch.checkout()
|
if is_commit:
|
||||||
|
git_repo.head.set_commit(original_branch)
|
||||||
|
git_repo.head.reset(index=True, working_tree=True)
|
||||||
|
else:
|
||||||
|
original_branch.checkout()
|
||||||
except Exception:
|
except Exception:
|
||||||
# this could have failed due to dirty tree, but difficult to predict all cases
|
# this could have failed due to dirty tree, but difficult to predict all cases
|
||||||
logger.exception(f'Failed to restore project repo to prior state after {self.instance.id}')
|
logger.exception(f'Failed to restore project repo to prior state after {self.instance.id}')
|
||||||
@@ -1581,7 +1586,7 @@ class RunInventoryUpdate(SourceControlMixin, BaseTask):
|
|||||||
if inventory_update.source == 'scm':
|
if inventory_update.source == 'scm':
|
||||||
if not source_project:
|
if not source_project:
|
||||||
raise RuntimeError('Could not find project to run SCM inventory update from.')
|
raise RuntimeError('Could not find project to run SCM inventory update from.')
|
||||||
self.sync_and_copy(source_project, private_data_dir)
|
self.sync_and_copy(source_project, private_data_dir, scm_branch=inventory_update.inventory_source.scm_branch)
|
||||||
else:
|
else:
|
||||||
# If source is not SCM make an empty project directory, content is built inside inventory folder
|
# If source is not SCM make an empty project directory, content is built inside inventory folder
|
||||||
super(RunInventoryUpdate, self).build_project_dir(inventory_update, private_data_dir)
|
super(RunInventoryUpdate, self).build_project_dir(inventory_update, private_data_dir)
|
||||||
|
|||||||
@@ -48,6 +48,7 @@ function InventorySourceDetail({ inventorySource }) {
|
|||||||
source,
|
source,
|
||||||
source_path,
|
source_path,
|
||||||
source_vars,
|
source_vars,
|
||||||
|
scm_branch,
|
||||||
update_cache_timeout,
|
update_cache_timeout,
|
||||||
update_on_launch,
|
update_on_launch,
|
||||||
verbosity,
|
verbosity,
|
||||||
@@ -233,6 +234,11 @@ function InventorySourceDetail({ inventorySource }) {
|
|||||||
helpText={helpText.subFormVerbosityFields}
|
helpText={helpText.subFormVerbosityFields}
|
||||||
value={VERBOSITY()[verbosity]}
|
value={VERBOSITY()[verbosity]}
|
||||||
/>
|
/>
|
||||||
|
<Detail
|
||||||
|
label={t`Source Control Branch`}
|
||||||
|
helpText={helpText.sourceControlBranch}
|
||||||
|
value={scm_branch}
|
||||||
|
/>
|
||||||
<Detail
|
<Detail
|
||||||
label={t`Cache timeout`}
|
label={t`Cache timeout`}
|
||||||
value={`${update_cache_timeout} ${t`seconds`}`}
|
value={`${update_cache_timeout} ${t`seconds`}`}
|
||||||
|
|||||||
@@ -152,6 +152,7 @@ const getInventoryHelpTextStrings = () => ({
|
|||||||
},
|
},
|
||||||
enabledVariableField: t`Retrieve the enabled state from the given dict of host variables.
|
enabledVariableField: t`Retrieve the enabled state from the given dict of host variables.
|
||||||
The enabled variable may be specified using dot notation, e.g: 'foo.bar'`,
|
The enabled variable may be specified using dot notation, e.g: 'foo.bar'`,
|
||||||
|
sourceControlBranch: t`Branch to use on inventory sync. Project default used if blank. Only allowed if project allow_override field is set to true.`,
|
||||||
enabledValue: t`This field is ignored unless an Enabled Variable is set. If the enabled variable matches this value, the host will be enabled on import.`,
|
enabledValue: t`This field is ignored unless an Enabled Variable is set. If the enabled variable matches this value, the host will be enabled on import.`,
|
||||||
hostFilter: t`Regular expression where only matching host names will be imported. The filter is applied as a post-processing step after any inventory plugin filters are applied.`,
|
hostFilter: t`Regular expression where only matching host names will be imported. The filter is applied as a post-processing step after any inventory plugin filters are applied.`,
|
||||||
sourceVars: (docsBaseUrl, source) => {
|
sourceVars: (docsBaseUrl, source) => {
|
||||||
|
|||||||
@@ -71,6 +71,7 @@ const InventorySourceFormFields = ({
|
|||||||
source_project: null,
|
source_project: null,
|
||||||
source_script: null,
|
source_script: null,
|
||||||
source_vars: '---\n',
|
source_vars: '---\n',
|
||||||
|
scm_branch: null,
|
||||||
update_cache_timeout: 0,
|
update_cache_timeout: 0,
|
||||||
update_on_launch: false,
|
update_on_launch: false,
|
||||||
verbosity: 1,
|
verbosity: 1,
|
||||||
@@ -248,6 +249,7 @@ const InventorySourceForm = ({
|
|||||||
source_project: source?.summary_fields?.source_project || null,
|
source_project: source?.summary_fields?.source_project || null,
|
||||||
source_script: source?.summary_fields?.source_script || null,
|
source_script: source?.summary_fields?.source_script || null,
|
||||||
source_vars: source?.source_vars || '---\n',
|
source_vars: source?.source_vars || '---\n',
|
||||||
|
scm_branch: source?.scm_branch || '',
|
||||||
update_cache_timeout: source?.update_cache_timeout || 0,
|
update_cache_timeout: source?.update_cache_timeout || 0,
|
||||||
update_on_launch: source?.update_on_launch || false,
|
update_on_launch: source?.update_on_launch || false,
|
||||||
verbosity: source?.verbosity || 1,
|
verbosity: source?.verbosity || 1,
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import { required } from 'util/validators';
|
|||||||
import CredentialLookup from 'components/Lookup/CredentialLookup';
|
import CredentialLookup from 'components/Lookup/CredentialLookup';
|
||||||
import ProjectLookup from 'components/Lookup/ProjectLookup';
|
import ProjectLookup from 'components/Lookup/ProjectLookup';
|
||||||
import Popover from 'components/Popover';
|
import Popover from 'components/Popover';
|
||||||
|
import FormField from 'components/FormField';
|
||||||
import {
|
import {
|
||||||
OptionsField,
|
OptionsField,
|
||||||
SourceVarsField,
|
SourceVarsField,
|
||||||
@@ -36,7 +37,6 @@ const SCMSubForm = ({ autoPopulateProject }) => {
|
|||||||
name: 'source_path',
|
name: 'source_path',
|
||||||
validate: required(t`Select a value for this field`),
|
validate: required(t`Select a value for this field`),
|
||||||
});
|
});
|
||||||
|
|
||||||
const { error: sourcePathError, request: fetchSourcePath } = useRequest(
|
const { error: sourcePathError, request: fetchSourcePath } = useRequest(
|
||||||
useCallback(async (projectId) => {
|
useCallback(async (projectId) => {
|
||||||
const { data } = await ProjectsAPI.readInventories(projectId);
|
const { data } = await ProjectsAPI.readInventories(projectId);
|
||||||
@@ -44,7 +44,6 @@ const SCMSubForm = ({ autoPopulateProject }) => {
|
|||||||
}, []),
|
}, []),
|
||||||
[]
|
[]
|
||||||
);
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (projectMeta.initialValue) {
|
if (projectMeta.initialValue) {
|
||||||
fetchSourcePath(projectMeta.initialValue.id);
|
fetchSourcePath(projectMeta.initialValue.id);
|
||||||
@@ -58,6 +57,7 @@ const SCMSubForm = ({ autoPopulateProject }) => {
|
|||||||
(value) => {
|
(value) => {
|
||||||
setFieldValue('source_project', value);
|
setFieldValue('source_project', value);
|
||||||
setFieldTouched('source_project', true, false);
|
setFieldTouched('source_project', true, false);
|
||||||
|
setFieldValue('scm_branch', '', false);
|
||||||
if (sourcePathField.value) {
|
if (sourcePathField.value) {
|
||||||
setFieldValue('source_path', '');
|
setFieldValue('source_path', '');
|
||||||
setFieldTouched('source_path', false);
|
setFieldTouched('source_path', false);
|
||||||
@@ -68,7 +68,6 @@ const SCMSubForm = ({ autoPopulateProject }) => {
|
|||||||
},
|
},
|
||||||
[fetchSourcePath, setFieldValue, setFieldTouched, sourcePathField.value]
|
[fetchSourcePath, setFieldValue, setFieldTouched, sourcePathField.value]
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleCredentialUpdate = useCallback(
|
const handleCredentialUpdate = useCallback(
|
||||||
(value) => {
|
(value) => {
|
||||||
setFieldValue('credential', value);
|
setFieldValue('credential', value);
|
||||||
@@ -76,9 +75,17 @@ const SCMSubForm = ({ autoPopulateProject }) => {
|
|||||||
},
|
},
|
||||||
[setFieldValue, setFieldTouched]
|
[setFieldValue, setFieldTouched]
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
{projectField.value?.allow_override && (
|
||||||
|
<FormField
|
||||||
|
id="project-scm-branch"
|
||||||
|
name="scm_branch"
|
||||||
|
type="text"
|
||||||
|
label={t`Source Control Branch/Tag/Commit`}
|
||||||
|
tooltip={helpText.sourceControlBranch}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
<CredentialLookup
|
<CredentialLookup
|
||||||
credentialTypeKind="cloud"
|
credentialTypeKind="cloud"
|
||||||
label={t`Credential`}
|
label={t`Credential`}
|
||||||
|
|||||||
@@ -105,6 +105,11 @@ options:
|
|||||||
description:
|
description:
|
||||||
- Project to use as source with scm option
|
- Project to use as source with scm option
|
||||||
type: str
|
type: str
|
||||||
|
scm_branch:
|
||||||
|
description:
|
||||||
|
- Inventory source SCM branch.
|
||||||
|
- Project must have branch override enabled.
|
||||||
|
type: str
|
||||||
state:
|
state:
|
||||||
description:
|
description:
|
||||||
- Desired state of the resource.
|
- Desired state of the resource.
|
||||||
@@ -178,6 +183,7 @@ def main():
|
|||||||
update_on_launch=dict(type='bool'),
|
update_on_launch=dict(type='bool'),
|
||||||
update_cache_timeout=dict(type='int'),
|
update_cache_timeout=dict(type='int'),
|
||||||
source_project=dict(),
|
source_project=dict(),
|
||||||
|
scm_branch=dict(type='str'),
|
||||||
notification_templates_started=dict(type="list", elements='str'),
|
notification_templates_started=dict(type="list", elements='str'),
|
||||||
notification_templates_success=dict(type="list", elements='str'),
|
notification_templates_success=dict(type="list", elements='str'),
|
||||||
notification_templates_error=dict(type="list", elements='str'),
|
notification_templates_error=dict(type="list", elements='str'),
|
||||||
@@ -272,6 +278,7 @@ def main():
|
|||||||
'enabled_var',
|
'enabled_var',
|
||||||
'enabled_value',
|
'enabled_value',
|
||||||
'host_filter',
|
'host_filter',
|
||||||
|
'scm_branch',
|
||||||
)
|
)
|
||||||
|
|
||||||
# Layer in all remaining optional information
|
# Layer in all remaining optional information
|
||||||
|
|||||||
@@ -112,6 +112,7 @@ def test_falsy_value(run_module, admin_user, base_inventory):
|
|||||||
# credential ? ? o o r r r r r r r o
|
# credential ? ? o o r r r r r r r o
|
||||||
# source_project ? ? r - - - - - - - - -
|
# source_project ? ? r - - - - - - - - -
|
||||||
# source_path ? ? r - - - - - - - - -
|
# source_path ? ? r - - - - - - - - -
|
||||||
|
# scm_branch ? ? r - - - - - - - - -
|
||||||
# verbosity ? ? o o o o o o o o o o
|
# verbosity ? ? o o o o o o o o o o
|
||||||
# overwrite ? ? o o o o o o o o o o
|
# overwrite ? ? o o o o o o o o o o
|
||||||
# overwrite_vars ? ? o o o o o o o o o o
|
# overwrite_vars ? ? o o o o o o o o o o
|
||||||
|
|||||||
@@ -319,6 +319,7 @@ class InventorySource(HasCreate, HasNotifications, UnifiedJobTemplate):
|
|||||||
optional_fields = (
|
optional_fields = (
|
||||||
'source_path',
|
'source_path',
|
||||||
'source_vars',
|
'source_vars',
|
||||||
|
'scm_branch',
|
||||||
'timeout',
|
'timeout',
|
||||||
'overwrite',
|
'overwrite',
|
||||||
'overwrite_vars',
|
'overwrite_vars',
|
||||||
|
|||||||
Reference in New Issue
Block a user