Add dynamically configurable debug settings (#14008)

Co-authored-by: Michael Abashian <mabashia@redhat.com>
This commit is contained in:
Gabriel Muniz 2023-06-15 09:31:54 -04:00 committed by GitHub
parent db71b63829
commit 875f1a82e4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 8488 additions and 6903 deletions

View File

@ -848,6 +848,46 @@ register(
category_slug='system',
)
register(
'AWX_CLEANUP_PATHS',
field_class=fields.BooleanField,
label=_('Enable or Disable tmp dir cleanup'),
default=True,
help_text=_('Enable or Disable TMP Dir cleanup'),
category=('Debug'),
category_slug='debug',
)
register(
'AWX_REQUEST_PROFILE',
field_class=fields.BooleanField,
label=_('Debug Web Requests'),
default=False,
help_text=_('Debug web request python timing'),
category=('Debug'),
category_slug='debug',
)
register(
'DEFAULT_CONTAINER_RUN_OPTIONS',
field_class=fields.StringListField,
label=_('Container Run Options'),
default=['--network', 'slirp4netns:enable_ipv6=true'],
help_text=_("List of options to pass to podman run example: ['--network', 'slirp4netns:enable_ipv6=true', '--log-level', 'debug']"),
category=('Jobs'),
category_slug='jobs',
)
register(
'RECEPTOR_RELEASE_WORK',
field_class=fields.BooleanField,
label=_('Release Receptor Work'),
default=True,
help_text=_('Release receptor work'),
category=('Debug'),
category_slug='debug',
)
def logging_validate(serializer, attrs):
if not serializer.instance or not hasattr(serializer.instance, 'LOG_AGGREGATOR_HOST') or not hasattr(serializer.instance, 'LOG_AGGREGATOR_TYPE'):

View File

@ -86,6 +86,9 @@ function JobsEdit() {
),
AWX_TASK_ENV: formatJson(form.AWX_TASK_ENV),
GALAXY_TASK_ENV: formatJson(form.GALAXY_TASK_ENV),
DEFAULT_CONTAINER_RUN_OPTIONS: formatJson(
form.DEFAULT_CONTAINER_RUN_OPTIONS
),
});
};
@ -214,6 +217,10 @@ function JobsEdit() {
name="AD_HOC_COMMANDS"
config={jobs.AD_HOC_COMMANDS}
/>
<ObjectField
name="DEFAULT_CONTAINER_RUN_OPTIONS"
config={jobs.DEFAULT_CONTAINER_RUN_OPTIONS}
/>
<ObjectField
name="AWX_ANSIBLE_CALLBACK_PLUGINS"
config={jobs.AWX_ANSIBLE_CALLBACK_PLUGINS}

View File

@ -139,6 +139,17 @@ function SettingList() {
},
],
},
{
header: t`Troubleshooting`,
description: t`View and edit debug options`,
id: 'troubleshooting',
routes: [
{
title: t`Troubleshooting settings`,
path: '/settings/troubleshooting',
},
],
},
];
if (Object.keys(config).length === 0) {

View File

@ -24,6 +24,7 @@ import SAML from './SAML';
import SettingList from './SettingList';
import TACACS from './TACACS';
import UI from './UI';
import Troubleshooting from './Troubleshooting';
function Settings() {
const { license_info = {}, me } = useConfig();
@ -118,6 +119,9 @@ function Settings() {
'/settings/ui': t`User Interface`,
'/settings/ui/details': t`Details`,
'/settings/ui/edit': t`Edit Details`,
'/settings/troubleshooting': t`Troubleshooting`,
'/settings/troubleshooting/details': t`Details`,
'/settings/troubleshooting/edit': t`Edit Details`,
};
if (error) {
@ -191,6 +195,9 @@ function Settings() {
<Route path="/settings/tacacs">
<TACACS />
</Route>
<Route path="/settings/troubleshooting">
<Troubleshooting />
</Route>
<Route path="/settings/ui">
<UI />
</Route>

View File

@ -0,0 +1,36 @@
import React from 'react';
import { Link, Redirect, Route, Switch } from 'react-router-dom';
import { t } from '@lingui/macro';
import { PageSection, Card } from '@patternfly/react-core';
import ContentError from 'components/ContentError';
import TroubleshootingDetail from './TroubleshootingDetail';
import TroubleshootingEdit from './TroubleshootingEdit';
function Troubleshooting() {
const baseURL = '/settings/troubleshooting';
return (
<PageSection>
<Card>
<Switch>
<Redirect from={baseURL} to={`${baseURL}/details`} exact />
<Route path={`${baseURL}/details`}>
<TroubleshootingDetail />
</Route>
<Route path={`${baseURL}/edit`}>
<TroubleshootingEdit />
</Route>
<Route key="not-found" path={`${baseURL}/*`}>
<ContentError isNotFound>
<Link
to={`${baseURL}/details`}
>{t`View Troubleshooting settings`}</Link>
</ContentError>
</Route>
</Switch>
</Card>
</PageSection>
);
}
export default Troubleshooting;

View File

@ -0,0 +1,60 @@
import React from 'react';
import { act } from 'react-dom/test-utils';
import { createMemoryHistory } from 'history';
import { SettingsAPI } from 'api';
import { mountWithContexts } from '../../../../testUtils/enzymeHelpers';
import mockJobSettings from '../shared/data.jobSettings.json';
import Jobs from './Troubleshooting';
import Troubleshooting from './Troubleshooting';
jest.mock('../../../api');
describe('<Troubleshooting />', () => {
let wrapper;
beforeEach(() => {
SettingsAPI.readCategory.mockResolvedValue({
data: mockJobSettings,
});
});
afterEach(() => {
jest.clearAllMocks();
});
test('should render troubleshooting details', async () => {
const history = createMemoryHistory({
initialEntries: ['/settings/troubleshooting/details'],
});
await act(async () => {
wrapper = mountWithContexts(<Jobs />, {
context: { router: { history } },
});
});
expect(wrapper.find('TroubleshootingDetail').length).toBe(1);
});
test('should render troubleshooting edit', async () => {
const history = createMemoryHistory({
initialEntries: ['/settings/troubleshooting/edit'],
});
await act(async () => {
wrapper = mountWithContexts(<Jobs />, {
context: { router: { history } },
});
});
expect(wrapper.find('TroubleshootingEdit').length).toBe(1);
});
test('should show content error when user navigates to erroneous route', async () => {
const history = createMemoryHistory({
initialEntries: ['/settings/troubleshooting/foo'],
});
await act(async () => {
wrapper = mountWithContexts(<Troubleshooting />, {
context: { router: { history } },
});
});
expect(wrapper.find('ContentError').length).toBe(1);
});
});

View File

@ -0,0 +1,105 @@
import React, { useEffect, useCallback } from 'react';
import { Link } from 'react-router-dom';
import { t } from '@lingui/macro';
import { Button } from '@patternfly/react-core';
import { CaretLeftIcon } from '@patternfly/react-icons';
import { CardBody, CardActionsRow } from 'components/Card';
import ContentError from 'components/ContentError';
import ContentLoading from 'components/ContentLoading';
import { DetailList } from 'components/DetailList';
import RoutedTabs from 'components/RoutedTabs';
import useRequest from 'hooks/useRequest';
import { useConfig } from 'contexts/Config';
import { useSettings } from 'contexts/Settings';
import { SettingsAPI } from 'api';
import { sortNestedDetails } from '../../shared/settingUtils';
import { SettingDetail } from '../../shared';
function TroubleshootingDetail() {
const { me } = useConfig();
const { GET: options } = useSettings();
const {
isLoading,
error,
request,
result: debug,
} = useRequest(
useCallback(async () => {
const { data } = await SettingsAPI.readCategory('debug');
const { ...debugData } = data;
const mergedData = {};
Object.keys(debugData).forEach((key) => {
mergedData[key] = options[key];
mergedData[key].value = debugData[key];
});
return sortNestedDetails(mergedData);
}, [options]),
null
);
useEffect(() => {
request();
}, [request]);
const tabsArray = [
{
name: (
<>
<CaretLeftIcon />
{t`Back to Settings`}
</>
),
link: `/settings`,
id: 99,
},
{
name: t`Details`,
link: `/settings/troubleshooting/details`,
id: 0,
},
];
return (
<>
<RoutedTabs tabsArray={tabsArray} />
<CardBody>
{isLoading && <ContentLoading />}
{!isLoading && error && <ContentError error={error} />}
{!isLoading && debug && (
<DetailList>
{debug.map(([key, detail]) => (
<SettingDetail
key={key}
id={key}
helpText={detail?.help_text}
label={detail?.label}
type={detail?.type}
unit={detail?.unit}
value={detail?.value}
/>
))}
</DetailList>
)}
{me?.is_superuser && (
<CardActionsRow>
<Button
ouiaId="troubleshooting-detail-edit-button"
aria-label={t`Edit`}
component={Link}
to="/settings/troubleshooting/edit"
>
{t`Edit`}
</Button>
</CardActionsRow>
)}
</CardBody>
</>
);
}
export default TroubleshootingDetail;

View File

@ -0,0 +1,115 @@
import React from 'react';
import { act } from 'react-dom/test-utils';
import { SettingsProvider } from 'contexts/Settings';
import { SettingsAPI } from 'api';
import {
mountWithContexts,
waitForElement,
} from '../../../../../testUtils/enzymeHelpers';
import {
assertDetail,
assertVariableDetail,
} from '../../shared/settingTestUtils';
import mockAllOptions from '../../shared/data.allSettingOptions.json';
import mockJobSettings from '../../shared/data.jobSettings.json';
import TroubleshootingDetail from './TroubleshootingDetail';
jest.mock('../../../../api');
describe('<TroubleshootingDetail />', () => {
let wrapper;
beforeEach(() => {
SettingsAPI.readCategory.mockResolvedValue({
data: mockJobSettings,
});
});
beforeEach(async () => {
await act(async () => {
wrapper = mountWithContexts(
<SettingsProvider value={mockAllOptions.actions}>
<TroubleshootingDetail />
</SettingsProvider>
);
});
await waitForElement(wrapper, 'ContentLoading', (el) => el.length === 0);
});
afterAll(() => {
jest.clearAllMocks();
});
test('initially renders without crashing', () => {
expect(wrapper.find('TroubleshootingDetail').length).toBe(1);
});
test('should render expected tabs', () => {
const expectedTabs = ['Back to Settings', 'Details'];
wrapper.find('RoutedTabs li').forEach((tab, index) => {
expect(tab.text()).toEqual(expectedTabs[index]);
});
});
test('should render expected details', () => {
assertDetail(wrapper, 'Job execution path', '/tmp');
assertDetail(wrapper, 'Run Project Updates With Higher Verbosity', 'Off');
assertDetail(wrapper, 'Enable Role Download', 'On');
assertDetail(wrapper, 'Enable Collection(s) Download', 'On');
assertDetail(wrapper, 'Follow symlinks', 'Off');
assertDetail(
wrapper,
'Ignore Ansible Galaxy SSL Certificate Verification',
'Off'
);
assertDetail(wrapper, 'Maximum Scheduled Jobs', '10');
assertDetail(wrapper, 'Default Job Timeout', '0 seconds');
assertDetail(wrapper, 'Default Job Idle Timeout', '0 seconds');
assertDetail(wrapper, 'Default Inventory Update Timeout', '0 seconds');
assertDetail(wrapper, 'Default Project Update Timeout', '0 seconds');
assertDetail(wrapper, 'Per-Host Ansible Fact Cache Timeout', '0 seconds');
assertDetail(wrapper, 'Maximum number of forks per job', '200');
assertDetail(wrapper, 'Expose host paths for Container Groups', 'Off');
assertVariableDetail(
wrapper,
'Ansible Modules Allowed for Ad Hoc Jobs',
'[\n "command"\n]'
);
assertVariableDetail(wrapper, 'Paths to expose to isolated jobs', '[]');
assertVariableDetail(wrapper, 'Extra Environment Variables', '{}');
assertVariableDetail(wrapper, 'Ansible Callback Plugins', '[]');
});
test('should hide edit button from non-superusers', async () => {
const config = {
me: {
is_superuser: false,
},
};
await act(async () => {
wrapper = mountWithContexts(
<SettingsProvider value={mockAllOptions.actions}>
<TroubleshootingDetail />
</SettingsProvider>,
{
context: { config },
}
);
});
await waitForElement(wrapper, 'ContentLoading', (el) => el.length === 0);
expect(wrapper.find('Button[aria-label="Edit"]').exists()).toBeFalsy();
});
test('should display content error when api throws error on initial render', async () => {
SettingsAPI.readCategory.mockRejectedValue(new Error());
await act(async () => {
wrapper = mountWithContexts(
<SettingsProvider value={mockAllOptions.actions}>
<TroubleshootingDetail />
</SettingsProvider>
);
});
await waitForElement(wrapper, 'ContentLoading', (el) => el.length === 0);
expect(wrapper.find('ContentError').length).toBe(1);
});
});

View File

@ -0,0 +1 @@
export { default } from './TroubleshootingDetail';

View File

@ -0,0 +1,142 @@
import React, { useCallback, useEffect } from 'react';
import { useHistory } from 'react-router-dom';
import { Formik } from 'formik';
import { Form } from '@patternfly/react-core';
import { CardBody } from 'components/Card';
import ContentError from 'components/ContentError';
import ContentLoading from 'components/ContentLoading';
import { FormSubmitError } from 'components/FormField';
import { FormColumnLayout } from 'components/FormLayout';
import { useSettings } from 'contexts/Settings';
import useModal from 'hooks/useModal';
import useRequest from 'hooks/useRequest';
import { SettingsAPI } from 'api';
import {
BooleanField,
RevertAllAlert,
RevertFormActionGroup,
} from '../../shared';
function TroubleshootingEdit() {
const history = useHistory();
const { isModalOpen, toggleModal, closeModal } = useModal();
const { PUT: options } = useSettings();
const {
isLoading,
error,
request: fetchJobs,
result: debug,
} = useRequest(
useCallback(async () => {
const { data } = await SettingsAPI.readCategory('debug');
const { ...debugData } = data;
const mergedData = {};
Object.keys(debugData).forEach((key) => {
if (!options[key]) {
return;
}
mergedData[key] = options[key];
mergedData[key].value = debugData[key];
});
return mergedData;
}, [options]),
null
);
useEffect(() => {
fetchJobs();
}, [fetchJobs]);
const { error: submitError, request: submitForm } = useRequest(
useCallback(
async (values) => {
await SettingsAPI.updateAll(values);
history.push('/settings/troubleshooting/details');
},
[history]
),
null
);
const { error: revertError, request: revertAll } = useRequest(
useCallback(async () => {
await SettingsAPI.revertCategory('debug');
}, []),
null
);
const handleSubmit = async (form) => {
await submitForm({
...form,
});
};
const handleRevertAll = async () => {
await revertAll();
closeModal();
history.push('/settings/troubleshooting/details');
};
const handleCancel = () => {
history.push('/settings/troubleshooting/details');
};
const initialValues = (fields) =>
Object.keys(fields).reduce((acc, key) => {
if (fields[key].type === 'list' || fields[key].type === 'nested object') {
acc[key] = fields[key].value
? JSON.stringify(fields[key].value, null, 2)
: null;
} else {
acc[key] = fields[key].value ?? '';
}
return acc;
}, {});
return (
<CardBody>
{isLoading && <ContentLoading />}
{!isLoading && error && <ContentError error={error} />}
{!isLoading && debug && (
<Formik initialValues={initialValues(debug)} onSubmit={handleSubmit}>
{(formik) => (
<Form autoComplete="off" onSubmit={formik.handleSubmit}>
<FormColumnLayout>
<BooleanField
name="AWX_CLEANUP_PATHS"
config={debug.AWX_CLEANUP_PATHS}
/>
<BooleanField
name="AWX_REQUEST_PROFILE"
config={debug.AWX_REQUEST_PROFILE}
/>
<BooleanField
name="RECEPTOR_RELEASE_WORK"
config={debug.RECEPTOR_RELEASE_WORK}
/>
{submitError && <FormSubmitError error={submitError} />}
{revertError && <FormSubmitError error={revertError} />}
</FormColumnLayout>
<RevertFormActionGroup
onCancel={handleCancel}
onSubmit={formik.handleSubmit}
onRevert={toggleModal}
/>
{isModalOpen && (
<RevertAllAlert
onClose={closeModal}
onRevertAll={handleRevertAll}
/>
)}
</Form>
)}
</Formik>
)}
</CardBody>
);
}
export default TroubleshootingEdit;

View File

@ -0,0 +1,123 @@
import React from 'react';
import { act } from 'react-dom/test-utils';
import { createMemoryHistory } from 'history';
import { SettingsProvider } from 'contexts/Settings';
import { SettingsAPI } from 'api';
import {
mountWithContexts,
waitForElement,
} from '../../../../../testUtils/enzymeHelpers';
import mockAllOptions from '../../shared/data.allSettingOptions.json';
import mockTroubleshootingSettings from './data.defaultTroubleshootingSettings.json';
import TroubleshootingEdit from './TroubleshootingEdit';
jest.mock('../../../../api');
describe('<TroubleshootingEdit />', () => {
let wrapper;
let history;
beforeEach(() => {
SettingsAPI.revertCategory.mockResolvedValue({});
SettingsAPI.updateAll.mockResolvedValue({});
SettingsAPI.readCategory.mockResolvedValue({
data: mockTroubleshootingSettings,
});
});
afterEach(() => {
jest.clearAllMocks();
});
beforeEach(async () => {
history = createMemoryHistory({
initialEntries: ['/settings/troubleshooting/edit'],
});
await act(async () => {
wrapper = mountWithContexts(
<SettingsProvider value={mockAllOptions.actions}>
<TroubleshootingEdit />
</SettingsProvider>,
{
context: { router: { history } },
}
);
});
await waitForElement(wrapper, 'ContentLoading', (el) => el.length === 0);
});
test('initially renders without crashing', () => {
expect(wrapper.find('TroubleshootingEdit').length).toBe(1);
});
test('should successfully send default values to api on form revert all', async () => {
expect(SettingsAPI.revertCategory).toHaveBeenCalledTimes(0);
expect(wrapper.find('RevertAllAlert')).toHaveLength(0);
await act(async () => {
wrapper
.find('button[aria-label="Revert all to default"]')
.invoke('onClick')();
});
wrapper.update();
expect(wrapper.find('RevertAllAlert')).toHaveLength(1);
await act(async () => {
wrapper
.find('RevertAllAlert button[aria-label="Confirm revert all"]')
.invoke('onClick')();
});
wrapper.update();
expect(SettingsAPI.revertCategory).toHaveBeenCalledTimes(1);
expect(SettingsAPI.revertCategory).toHaveBeenCalledWith('debug');
});
test('should successfully send request to api on form submission', async () => {
expect(SettingsAPI.updateAll).toHaveBeenCalledTimes(0);
await act(async () => {
wrapper.find('Form').invoke('onSubmit')();
});
expect(SettingsAPI.updateAll).toHaveBeenCalledTimes(1);
const { ...troubleshootingRequest } = mockTroubleshootingSettings;
expect(SettingsAPI.updateAll).toHaveBeenCalledWith(troubleshootingRequest);
});
test('should display error message on unsuccessful submission', async () => {
const error = {
response: {
data: { detail: 'An error occurred' },
},
};
SettingsAPI.updateAll.mockImplementation(() => Promise.reject(error));
expect(wrapper.find('FormSubmitError').length).toBe(0);
expect(SettingsAPI.updateAll).toHaveBeenCalledTimes(0);
await act(async () => {
wrapper.find('Form').invoke('onSubmit')();
});
wrapper.update();
expect(wrapper.find('FormSubmitError').length).toBe(1);
expect(SettingsAPI.updateAll).toHaveBeenCalledTimes(1);
});
test('should navigate to troubleshooting settings detail when cancel is clicked', async () => {
await act(async () => {
wrapper.find('button[aria-label="Cancel"]').invoke('onClick')();
});
expect(history.location.pathname).toEqual(
'/settings/troubleshooting/details'
);
});
test('should display ContentError on throw', async () => {
SettingsAPI.readCategory.mockImplementationOnce(() =>
Promise.reject(new Error())
);
await act(async () => {
wrapper = mountWithContexts(
<SettingsProvider value={mockAllOptions.actions}>
<TroubleshootingEdit />
</SettingsProvider>
);
});
await waitForElement(wrapper, 'ContentLoading', (el) => el.length === 0);
expect(wrapper.find('ContentError').length).toBe(1);
});
});

View File

@ -0,0 +1,5 @@
{
"AWX_CLEANUP_PATHS": false,
"AWX_REQUEST_PROFILE": false,
"RECEPTOR_RELEASE_WORK": false
}

View File

@ -0,0 +1 @@
export { default } from './TroubleshootingEdit';

View File

@ -0,0 +1 @@
export { default } from './Troubleshooting';

File diff suppressed because it is too large Load Diff

View File

@ -3,55 +3,46 @@
"ACTIVITY_STREAM_ENABLED_FOR_INVENTORY_SYNC": false,
"ORG_ADMINS_CAN_SEE_ALL_USERS": true,
"MANAGE_ORGANIZATION_AUTH": true,
"DISABLE_LOCAL_AUTH": false,
"TOWER_URL_BASE": "https://localhost:3000",
"REMOTE_HOST_HEADERS": ["REMOTE_ADDR", "REMOTE_HOST"],
"REMOTE_HOST_HEADERS": [
"REMOTE_ADDR",
"REMOTE_HOST"
],
"PROXY_IP_ALLOWED_LIST": [],
"LICENSE": {},
"REDHAT_USERNAME": "",
"REDHAT_PASSWORD": "",
"SUBSCRIPTIONS_USERNAME": "",
"SUBSCRIPTIONS_PASSWORD": "",
"AUTOMATION_ANALYTICS_URL": "https://example.com",
"INSTALL_UUID": "3f5a4d68-3a94-474c-a3c0-f23a33122ce6",
"DEFAULT_CONTROL_PLANE_QUEUE_NAME": "controlplane",
"DEFAULT_EXECUTION_QUEUE_NAME": "default",
"DEFAULT_EXECUTION_ENVIRONMENT": null,
"CUSTOM_VENV_PATHS": [],
"AD_HOC_COMMANDS": [
"command",
"shell",
"yum",
"apt",
"apt_key",
"apt_repository",
"apt_rpm",
"service",
"group",
"user",
"mount",
"ping",
"selinux",
"setup",
"win_ping",
"win_service",
"win_updates",
"win_group",
"win_user"
"command"
],
"ALLOW_JINJA_IN_EXTRA_VARS": "template",
"AWX_ISOLATION_BASE_PATH": "/tmp",
"AWX_ISOLATION_SHOW_PATHS": [],
"AWX_TASK_ENV": {},
"AWX_RUNNER_KEEPALIVE_SECONDS": 0,
"GALAXY_TASK_ENV": {
"ANSIBLE_FORCE_COLOR": "false",
"GIT_SSH_COMMAND": "ssh -o StrictHostKeyChecking=no"
"ANSIBLE_FORCE_COLOR": "false",
"GIT_SSH_COMMAND": "ssh -o StrictHostKeyChecking=no"
},
"INSIGHTS_TRACKING_STATE": false,
"PROJECT_UPDATE_VVV": false,
"AWX_ROLES_ENABLED": true,
"AWX_COLLECTIONS_ENABLED": true,
"AWX_SHOW_PLAYBOOK_LINKS": false,
"AWX_MOUNT_ISOLATED_PATHS_ON_K8S": false,
"GALAXY_IGNORE_CERTS": false,
"STDOUT_MAX_BYTES_DISPLAY": 1048576,
"EVENT_STDOUT_MAX_BYTES_DISPLAY": 1024,
"MAX_WEBSOCKET_EVENT_RATE": 30,
"SCHEDULE_MAX_JOBS": 10,
"AWX_RUNNER_KEEPALIVE_SECONDS": 0,
"AWX_ANSIBLE_CALLBACK_PLUGINS": [],
"DEFAULT_JOB_TIMEOUT": 0,
"DEFAULT_JOB_IDLE_TIMEOUT": 0,
@ -65,10 +56,11 @@
"LOG_AGGREGATOR_USERNAME": "",
"LOG_AGGREGATOR_PASSWORD": "",
"LOG_AGGREGATOR_LOGGERS": [
"awx",
"activity_stream",
"job_events",
"system_tracking"
"awx",
"activity_stream",
"job_events",
"system_tracking",
"broadcast_websocket"
],
"LOG_AGGREGATOR_INDIVIDUAL_FACTS": false,
"LOG_AGGREGATOR_ENABLED": true,
@ -83,28 +75,41 @@
"LOG_AGGREGATOR_RSYSLOGD_DEBUG": false,
"API_400_ERROR_LOG_FORMAT": "status {status_code} received by user {user_name} attempting to access {url_path} from {remote_addr}",
"AUTOMATION_ANALYTICS_LAST_GATHER": null,
"AUTOMATION_ANALYTICS_LAST_ENTRIES": "",
"AUTOMATION_ANALYTICS_GATHER_INTERVAL": 14400,
"IS_K8S": false,
"BULK_JOB_MAX_LAUNCH": 100,
"BULK_HOST_MAX_CREATE": 100,
"UI_NEXT": false,
"SUBSCRIPTION_USAGE_MODEL": "",
"CLEANUP_HOST_METRICS_LAST_TS": null,
"AWX_CLEANUP_PATHS": true,
"AWX_REQUEST_PROFILE": false,
"DEFAULT_CONTAINER_RUN_OPTIONS": [
"--network",
"slirp4netns:enable_ipv6=true"
],
"RECEPTOR_RELEASE_WORK": true,
"SESSION_COOKIE_AGE": 1800,
"SESSIONS_PER_USER": -1,
"DISABLE_LOCAL_AUTH": false,
"AUTH_BASIC_ENABLED": true,
"OAUTH2_PROVIDER": {
"ACCESS_TOKEN_EXPIRE_SECONDS": 31536000000,
"REFRESH_TOKEN_EXPIRE_SECONDS": 2628000,
"AUTHORIZATION_CODE_EXPIRE_SECONDS": 600
"ACCESS_TOKEN_EXPIRE_SECONDS": 31536000000,
"AUTHORIZATION_CODE_EXPIRE_SECONDS": 600,
"REFRESH_TOKEN_EXPIRE_SECONDS": 2628000
},
"ALLOW_OAUTH2_FOR_EXTERNAL_USERS": false,
"LOGIN_REDIRECT_OVERRIDE": "",
"ALLOW_METRICS_FOR_ANONYMOUS_USERS": false,
"PENDO_TRACKING_STATE": "off",
"CUSTOM_LOGIN_INFO": "",
"CUSTOM_LOGO": "",
"MAX_UI_JOB_EVENTS": 4000,
"UI_LIVE_UPDATES_ENABLED": true,
"AUTHENTICATION_BACKENDS": [
"awx.sso.backends.LDAPBackend",
"awx.sso.backends.RADIUSBackend",
"awx.sso.backends.TACACSPlusBackend",
"social_core.backends.github.GithubTeamOAuth2",
"django.contrib.auth.backends.ModelBackend"
"awx.sso.backends.TACACSPlusBackend",
"awx.main.backends.AWXModelBackend"
],
"SOCIAL_AUTH_ORGANIZATION_MAP": null,
"SOCIAL_AUTH_TEAM_MAP": null,
@ -115,8 +120,8 @@
"AUTH_LDAP_BIND_PASSWORD": "$encrypted$",
"AUTH_LDAP_START_TLS": false,
"AUTH_LDAP_CONNECTION_OPTIONS": {
"OPT_REFERRALS": 0,
"OPT_NETWORK_TIMEOUT": 30
"OPT_REFERRALS": 0,
"OPT_NETWORK_TIMEOUT": 30
},
"AUTH_LDAP_USER_SEARCH": [],
"AUTH_LDAP_USER_DN_TEMPLATE": "uid=%(user)s,OU=Users,DC=example,DC=com",
@ -127,7 +132,10 @@
"(objectClass=group)"
],
"AUTH_LDAP_GROUP_TYPE": "MemberDNGroupType",
"AUTH_LDAP_GROUP_TYPE_PARAMS": { "name_attr": "cn", "member_attr": "member" },
"AUTH_LDAP_GROUP_TYPE_PARAMS": {
"member_attr": "member",
"name_attr": "cn"
},
"AUTH_LDAP_REQUIRE_GROUP": "CN=Service Users,OU=Users,DC=example,DC=com",
"AUTH_LDAP_DENY_GROUP": null,
"AUTH_LDAP_USER_FLAGS_BY_GROUP": { "is_superuser": ["cn=superusers"] },
@ -138,8 +146,8 @@
"AUTH_LDAP_1_BIND_PASSWORD": "",
"AUTH_LDAP_1_START_TLS": true,
"AUTH_LDAP_1_CONNECTION_OPTIONS": {
"OPT_REFERRALS": 0,
"OPT_NETWORK_TIMEOUT": 30
"OPT_REFERRALS": 0,
"OPT_NETWORK_TIMEOUT": 30
},
"AUTH_LDAP_1_USER_SEARCH": [],
"AUTH_LDAP_1_USER_DN_TEMPLATE": null,
@ -147,11 +155,11 @@
"AUTH_LDAP_1_GROUP_SEARCH": [],
"AUTH_LDAP_1_GROUP_TYPE": "MemberDNGroupType",
"AUTH_LDAP_1_GROUP_TYPE_PARAMS": {
"member_attr": "member",
"name_attr": "cn"
"member_attr": "member",
"name_attr": "cn"
},
"AUTH_LDAP_1_REQUIRE_GROUP": null,
"AUTH_LDAP_1_DENY_GROUP": "CN=Disabled1",
"AUTH_LDAP_1_DENY_GROUP": null,
"AUTH_LDAP_1_USER_FLAGS_BY_GROUP": {},
"AUTH_LDAP_1_ORGANIZATION_MAP": {},
"AUTH_LDAP_1_TEAM_MAP": {},
@ -160,8 +168,8 @@
"AUTH_LDAP_2_BIND_PASSWORD": "",
"AUTH_LDAP_2_START_TLS": false,
"AUTH_LDAP_2_CONNECTION_OPTIONS": {
"OPT_REFERRALS": 0,
"OPT_NETWORK_TIMEOUT": 30
"OPT_REFERRALS": 0,
"OPT_NETWORK_TIMEOUT": 30
},
"AUTH_LDAP_2_USER_SEARCH": [],
"AUTH_LDAP_2_USER_DN_TEMPLATE": null,
@ -169,8 +177,8 @@
"AUTH_LDAP_2_GROUP_SEARCH": [],
"AUTH_LDAP_2_GROUP_TYPE": "MemberDNGroupType",
"AUTH_LDAP_2_GROUP_TYPE_PARAMS": {
"member_attr": "member",
"name_attr": "cn"
"member_attr": "member",
"name_attr": "cn"
},
"AUTH_LDAP_2_REQUIRE_GROUP": null,
"AUTH_LDAP_2_DENY_GROUP": "CN=Disabled2",
@ -182,8 +190,8 @@
"AUTH_LDAP_3_BIND_PASSWORD": "",
"AUTH_LDAP_3_START_TLS": false,
"AUTH_LDAP_3_CONNECTION_OPTIONS": {
"OPT_REFERRALS": 0,
"OPT_NETWORK_TIMEOUT": 30
"OPT_REFERRALS": 0,
"OPT_NETWORK_TIMEOUT": 30
},
"AUTH_LDAP_3_USER_SEARCH": [],
"AUTH_LDAP_3_USER_DN_TEMPLATE": null,
@ -191,8 +199,8 @@
"AUTH_LDAP_3_GROUP_SEARCH": [],
"AUTH_LDAP_3_GROUP_TYPE": "MemberDNGroupType",
"AUTH_LDAP_3_GROUP_TYPE_PARAMS": {
"member_attr": "member",
"name_attr": "cn"
"member_attr": "member",
"name_attr": "cn"
},
"AUTH_LDAP_3_REQUIRE_GROUP": null,
"AUTH_LDAP_3_DENY_GROUP": null,
@ -204,8 +212,8 @@
"AUTH_LDAP_4_BIND_PASSWORD": "",
"AUTH_LDAP_4_START_TLS": false,
"AUTH_LDAP_4_CONNECTION_OPTIONS": {
"OPT_REFERRALS": 0,
"OPT_NETWORK_TIMEOUT": 30
"OPT_REFERRALS": 0,
"OPT_NETWORK_TIMEOUT": 30
},
"AUTH_LDAP_4_USER_SEARCH": [],
"AUTH_LDAP_4_USER_DN_TEMPLATE": null,
@ -213,8 +221,8 @@
"AUTH_LDAP_4_GROUP_SEARCH": [],
"AUTH_LDAP_4_GROUP_TYPE": "MemberDNGroupType",
"AUTH_LDAP_4_GROUP_TYPE_PARAMS": {
"member_attr": "member",
"name_attr": "cn"
"member_attr": "member",
"name_attr": "cn"
},
"AUTH_LDAP_4_REQUIRE_GROUP": null,
"AUTH_LDAP_4_DENY_GROUP": null,
@ -226,8 +234,8 @@
"AUTH_LDAP_5_BIND_PASSWORD": "",
"AUTH_LDAP_5_START_TLS": false,
"AUTH_LDAP_5_CONNECTION_OPTIONS": {
"OPT_REFERRALS": 0,
"OPT_NETWORK_TIMEOUT": 30
"OPT_REFERRALS": 0,
"OPT_NETWORK_TIMEOUT": 30
},
"AUTH_LDAP_5_USER_SEARCH": [],
"AUTH_LDAP_5_USER_DN_TEMPLATE": null,
@ -235,8 +243,8 @@
"AUTH_LDAP_5_GROUP_SEARCH": [],
"AUTH_LDAP_5_GROUP_TYPE": "MemberDNGroupType",
"AUTH_LDAP_5_GROUP_TYPE_PARAMS": {
"member_attr": "member",
"name_attr": "cn"
"member_attr": "member",
"name_attr": "cn"
},
"AUTH_LDAP_5_REQUIRE_GROUP": null,
"AUTH_LDAP_5_DENY_GROUP": null,
@ -276,11 +284,38 @@
"SOCIAL_AUTH_GITHUB_TEAM_ID": "team_id",
"SOCIAL_AUTH_GITHUB_TEAM_ORGANIZATION_MAP": {},
"SOCIAL_AUTH_GITHUB_TEAM_TEAM_MAP": {},
"SOCIAL_AUTH_GITHUB_ENTERPRISE_CALLBACK_URL": "https://localhost:3000/sso/complete/github-enterprise/",
"SOCIAL_AUTH_GITHUB_ENTERPRISE_URL": "",
"SOCIAL_AUTH_GITHUB_ENTERPRISE_API_URL": "",
"SOCIAL_AUTH_GITHUB_ENTERPRISE_KEY": "",
"SOCIAL_AUTH_GITHUB_ENTERPRISE_SECRET": "",
"SOCIAL_AUTH_GITHUB_ENTERPRISE_ORGANIZATION_MAP": null,
"SOCIAL_AUTH_GITHUB_ENTERPRISE_TEAM_MAP": null,
"SOCIAL_AUTH_GITHUB_ENTERPRISE_ORG_CALLBACK_URL": "https://localhost:3000/sso/complete/github-enterprise-org/",
"SOCIAL_AUTH_GITHUB_ENTERPRISE_ORG_URL": "",
"SOCIAL_AUTH_GITHUB_ENTERPRISE_ORG_API_URL": "",
"SOCIAL_AUTH_GITHUB_ENTERPRISE_ORG_KEY": "",
"SOCIAL_AUTH_GITHUB_ENTERPRISE_ORG_SECRET": "",
"SOCIAL_AUTH_GITHUB_ENTERPRISE_ORG_NAME": "",
"SOCIAL_AUTH_GITHUB_ENTERPRISE_ORG_ORGANIZATION_MAP": null,
"SOCIAL_AUTH_GITHUB_ENTERPRISE_ORG_TEAM_MAP": null,
"SOCIAL_AUTH_GITHUB_ENTERPRISE_TEAM_CALLBACK_URL": "https://localhost:3000/sso/complete/github-enterprise-team/",
"SOCIAL_AUTH_GITHUB_ENTERPRISE_TEAM_URL": "",
"SOCIAL_AUTH_GITHUB_ENTERPRISE_TEAM_API_URL": "",
"SOCIAL_AUTH_GITHUB_ENTERPRISE_TEAM_KEY": "",
"SOCIAL_AUTH_GITHUB_ENTERPRISE_TEAM_SECRET": "",
"SOCIAL_AUTH_GITHUB_ENTERPRISE_TEAM_ID": "",
"SOCIAL_AUTH_GITHUB_ENTERPRISE_TEAM_ORGANIZATION_MAP": null,
"SOCIAL_AUTH_GITHUB_ENTERPRISE_TEAM_TEAM_MAP": null,
"SOCIAL_AUTH_AZUREAD_OAUTH2_CALLBACK_URL": "https://localhost:3000/sso/complete/azuread-oauth2/",
"SOCIAL_AUTH_AZUREAD_OAUTH2_KEY": "",
"SOCIAL_AUTH_AZUREAD_OAUTH2_SECRET": "",
"SOCIAL_AUTH_AZUREAD_OAUTH2_ORGANIZATION_MAP": null,
"SOCIAL_AUTH_AZUREAD_OAUTH2_TEAM_MAP": null,
"SOCIAL_AUTH_OIDC_KEY": null,
"SOCIAL_AUTH_OIDC_SECRET": "",
"SOCIAL_AUTH_OIDC_OIDC_ENDPOINT": "",
"SOCIAL_AUTH_OIDC_VERIFY_SSL": true,
"SAML_AUTO_CREATE_OBJECTS": true,
"SOCIAL_AUTH_SAML_CALLBACK_URL": "https://localhost:3000/sso/complete/saml/",
"SOCIAL_AUTH_SAML_METADATA_URL": "https://localhost:3000/sso/metadata/saml/",
@ -291,7 +326,9 @@
"SOCIAL_AUTH_SAML_TECHNICAL_CONTACT": {},
"SOCIAL_AUTH_SAML_SUPPORT_CONTACT": {},
"SOCIAL_AUTH_SAML_ENABLED_IDPS": {},
"SOCIAL_AUTH_SAML_SECURITY_CONFIG": { "requestedAuthnContext": false },
"SOCIAL_AUTH_SAML_SECURITY_CONFIG": {
"requestedAuthnContext": false
},
"SOCIAL_AUTH_SAML_SP_EXTRA": null,
"SOCIAL_AUTH_SAML_EXTRA_DATA": null,
"SOCIAL_AUTH_SAML_ORGANIZATION_MAP": null,
@ -299,99 +336,215 @@
"SOCIAL_AUTH_SAML_ORGANIZATION_ATTR": {},
"SOCIAL_AUTH_SAML_TEAM_ATTR": {},
"SOCIAL_AUTH_SAML_USER_FLAGS_BY_ATTR": {},
"SOCIAL_AUTH_OIDC_KEY": "",
"SOCIAL_AUTH_OIDC_SECRET": "",
"SOCIAL_AUTH_OIDC_OIDC_ENDPOINT": "",
"SOCIAL_AUTH_OIDC_VERIFY_SSL": true,
"NAMED_URL_FORMATS": {
"organizations": "<name>",
"teams": "<name>++<organization.name>",
"credential_types": "<name>+<kind>",
"credentials": "<name>++<credential_type.name>+<credential_type.kind>++<organization.name>",
"notification_templates": "<name>++<organization.name>",
"job_templates": "<name>++<organization.name>",
"projects": "<name>++<organization.name>",
"inventories": "<name>++<organization.name>",
"hosts": "<name>++<inventory.name>++<organization.name>",
"groups": "<name>++<inventory.name>++<organization.name>",
"inventory_sources": "<name>++<inventory.name>++<organization.name>",
"inventory_scripts": "<name>++<organization.name>",
"instance_groups": "<name>",
"labels": "<name>++<organization.name>",
"workflow_job_templates": "<name>++<organization.name>",
"workflow_job_template_nodes": "<identifier>++<workflow_job_template.name>++<organization.name>",
"applications": "<name>++<organization.name>",
"users": "<username>",
"instances": "<hostname>"
},
"LOCAL_PASSWORD_MIN_LENGTH": 0,
"LOCAL_PASSWORD_MIN_DIGITS": 0,
"LOCAL_PASSWORD_MIN_UPPER": 0,
"LOCAL_PASSWORD_MIN_SPECIAL": 0,
"NAMED_URL_GRAPH_NODES": {
"organizations": { "fields": ["name"], "adj_list": [] },
"teams": {
"fields": ["name"],
"adj_list": [["organization", "organizations"]]
},
"credential_types": { "fields": ["name", "kind"], "adj_list": [] },
"credentials": {
"fields": ["name"],
"adj_list": [
["credential_type", "credential_types"],
["organization", "organizations"]
]
},
"notification_templates": {
"fields": ["name"],
"adj_list": [["organization", "organizations"]]
},
"job_templates": {
"fields": ["name"],
"adj_list": [["organization", "organizations"]]
},
"projects": {
"fields": ["name"],
"adj_list": [["organization", "organizations"]]
},
"inventories": {
"fields": ["name"],
"adj_list": [["organization", "organizations"]]
},
"hosts": { "fields": ["name"], "adj_list": [["inventory", "inventories"]] },
"groups": {
"fields": ["name"],
"adj_list": [["inventory", "inventories"]]
},
"inventory_sources": {
"fields": ["name"],
"adj_list": [["inventory", "inventories"]]
},
"inventory_scripts": {
"fields": ["name"],
"adj_list": [["organization", "organizations"]]
},
"instance_groups": { "fields": ["name"], "adj_list": [] },
"labels": {
"fields": ["name"],
"adj_list": [["organization", "organizations"]]
},
"workflow_job_templates": {
"fields": ["name"],
"adj_list": [["organization", "organizations"]]
},
"workflow_job_template_nodes": {
"fields": ["identifier"],
"adj_list": [["workflow_job_template", "workflow_job_templates"]]
},
"applications": {
"fields": ["name"],
"adj_list": [["organization", "organizations"]]
},
"users": { "fields": ["username"], "adj_list": [] },
"instances": { "fields": ["hostname"], "adj_list": [] }
"NAMED_URL_FORMATS": {
"execution_environments": "<name>",
"organizations": "<name>",
"teams": "<name>++<organization.name>",
"credential_types": "<name>+<kind>",
"credentials": "<name>++<credential_type.name>+<credential_type.kind>++<organization.name>",
"notification_templates": "<name>++<organization.name>",
"job_templates": "<name>++<organization.name>",
"projects": "<name>++<organization.name>",
"inventories": "<name>++<organization.name>",
"hosts": "<name>++<inventory.name>++<organization.name>",
"groups": "<name>++<inventory.name>++<organization.name>",
"inventory_sources": "<name>++<inventory.name>++<organization.name>",
"instance_groups": "<name>",
"workflow_job_templates": "<name>++<organization.name>",
"workflow_job_template_nodes": "<identifier>++<workflow_job_template.name>++<organization.name>",
"labels": "<name>++<organization.name>",
"applications": "<name>++<organization.name>",
"users": "<username>",
"instances": "<hostname>"
},
"DEFAULT_EXECUTION_ENVIRONMENT": 1,
"AWX_MOUNT_ISOLATED_PATHS_ON_K8S": false,
"UI_NEXT": false
}
"NAMED_URL_GRAPH_NODES": {
"execution_environments": {
"fields": [
"name"
],
"adj_list": []
},
"organizations": {
"fields": [
"name"
],
"adj_list": []
},
"teams": {
"fields": [
"name"
],
"adj_list": [
[
"organization",
"organizations"
]
]
},
"credential_types": {
"fields": [
"name",
"kind"
],
"adj_list": []
},
"credentials": {
"fields": [
"name"
],
"adj_list": [
[
"credential_type",
"credential_types"
],
[
"organization",
"organizations"
]
]
},
"notification_templates": {
"fields": [
"name"
],
"adj_list": [
[
"organization",
"organizations"
]
]
},
"job_templates": {
"fields": [
"name"
],
"adj_list": [
[
"organization",
"organizations"
]
]
},
"projects": {
"fields": [
"name"
],
"adj_list": [
[
"organization",
"organizations"
]
]
},
"inventories": {
"fields": [
"name"
],
"adj_list": [
[
"organization",
"organizations"
]
]
},
"hosts": {
"fields": [
"name"
],
"adj_list": [
[
"inventory",
"inventories"
]
]
},
"groups": {
"fields": [
"name"
],
"adj_list": [
[
"inventory",
"inventories"
]
]
},
"inventory_sources": {
"fields": [
"name"
],
"adj_list": [
[
"inventory",
"inventories"
]
]
},
"instance_groups": {
"fields": [
"name"
],
"adj_list": []
},
"workflow_job_templates": {
"fields": [
"name"
],
"adj_list": [
[
"organization",
"organizations"
]
]
},
"workflow_job_template_nodes": {
"fields": [
"identifier"
],
"adj_list": [
[
"workflow_job_template",
"workflow_job_templates"
]
]
},
"labels": {
"fields": [
"name"
],
"adj_list": [
[
"organization",
"organizations"
]
]
},
"applications": {
"fields": [
"name"
],
"adj_list": [
[
"organization",
"organizations"
]
]
},
"users": {
"fields": [
"username"
],
"adj_list": []
},
"instances": {
"fields": [
"hostname"
],
"adj_list": []
}
}
}

View File

@ -1,4 +1,3 @@
{
"AD_HOC_COMMANDS": [
"command"
@ -7,6 +6,7 @@
"AWX_ISOLATION_BASE_PATH": "/tmp",
"AWX_ISOLATION_SHOW_PATHS": [],
"AWX_TASK_ENV": {},
"AWX_RUNNER_KEEPALIVE_SECONDS": 0,
"GALAXY_TASK_ENV": {
"ANSIBLE_FORCE_COLOR": "false",
"GIT_SSH_COMMAND": "ssh -o StrictHostKeyChecking=no"
@ -15,11 +15,12 @@
"AWX_ROLES_ENABLED": true,
"AWX_COLLECTIONS_ENABLED": true,
"AWX_SHOW_PLAYBOOK_LINKS": false,
"AWX_MOUNT_ISOLATED_PATHS_ON_K8S": false,
"GALAXY_IGNORE_CERTS": false,
"STDOUT_MAX_BYTES_DISPLAY": 1048576,
"EVENT_STDOUT_MAX_BYTES_DISPLAY": 1024,
"MAX_WEBSOCKET_EVENT_RATE": 30,
"SCHEDULE_MAX_JOBS": 10,
"AWX_RUNNER_KEEPALIVE_SECONDS": 0,
"AWX_ANSIBLE_CALLBACK_PLUGINS": [],
"DEFAULT_JOB_TIMEOUT": 0,
"DEFAULT_JOB_IDLE_TIMEOUT": 0,
@ -27,5 +28,8 @@
"DEFAULT_PROJECT_UPDATE_TIMEOUT": 0,
"ANSIBLE_FACT_CACHE_TIMEOUT": 0,
"MAX_FORKS": 200,
"AWX_MOUNT_ISOLATED_PATHS_ON_K8S": false
}
"DEFAULT_CONTAINER_RUN_OPTIONS": [
"--network",
"slirp4netns:enable_ipv6=true"
]
}