mirror of
https://github.com/ansible/awx.git
synced 2026-02-10 22:24:45 -03:30
Compare commits
11 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2a1dffd363 | ||
|
|
8c7ab8fcf2 | ||
|
|
3de8455960 | ||
|
|
d832e75e99 | ||
|
|
a89e266feb | ||
|
|
8e1516eeb7 | ||
|
|
c7f2fdbe57 | ||
|
|
c75757bf22 | ||
|
|
b8ec7c4072 | ||
|
|
bb1c155bc9 | ||
|
|
4822dd79fc |
@@ -1,7 +1,10 @@
|
||||
from .plugin import CredentialPlugin
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from thycotic.secrets.server import DomainPasswordGrantAuthorizer, PasswordGrantAuthorizer, SecretServer, ServerSecret
|
||||
try:
|
||||
from delinea.secrets.server import DomainPasswordGrantAuthorizer, PasswordGrantAuthorizer, SecretServer, ServerSecret
|
||||
except ImportError:
|
||||
from thycotic.secrets.server import DomainPasswordGrantAuthorizer, PasswordGrantAuthorizer, SecretServer, ServerSecret
|
||||
|
||||
tss_inputs = {
|
||||
'fields': [
|
||||
|
||||
@@ -161,7 +161,7 @@ class AWXConsumerRedis(AWXConsumerBase):
|
||||
class AWXConsumerPG(AWXConsumerBase):
|
||||
def __init__(self, *args, schedule=None, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.pg_max_wait = settings.DISPATCHER_DB_DOWNTOWN_TOLLERANCE
|
||||
self.pg_max_wait = settings.DISPATCHER_DB_DOWNTIME_TOLERANCE
|
||||
# if no successful loops have ran since startup, then we should fail right away
|
||||
self.pg_is_down = True # set so that we fail if we get database errors on startup
|
||||
init_time = time.time()
|
||||
|
||||
@@ -29,7 +29,7 @@ class RunnerCallback:
|
||||
self.safe_env = {}
|
||||
self.event_ct = 0
|
||||
self.model = model
|
||||
self.update_attempts = int(settings.DISPATCHER_DB_DOWNTOWN_TOLLERANCE / 5)
|
||||
self.update_attempts = int(settings.DISPATCHER_DB_DOWNTIME_TOLERANCE / 5)
|
||||
self.wrapup_event_dispatched = False
|
||||
self.artifacts_processed = False
|
||||
self.extra_update_fields = {}
|
||||
|
||||
@@ -112,7 +112,7 @@ class BaseTask(object):
|
||||
|
||||
def __init__(self):
|
||||
self.cleanup_paths = []
|
||||
self.update_attempts = int(settings.DISPATCHER_DB_DOWNTOWN_TOLLERANCE / 5)
|
||||
self.update_attempts = int(settings.DISPATCHER_DB_DOWNTIME_TOLERANCE / 5)
|
||||
self.runner_callback = self.callback_class(model=self.model)
|
||||
|
||||
def update_model(self, pk, _attempt=0, **updates):
|
||||
|
||||
@@ -283,6 +283,7 @@ class LogstashFormatter(LogstashFormatterBase):
|
||||
message.update(self.get_debug_fields(record))
|
||||
|
||||
if settings.LOG_AGGREGATOR_TYPE == 'splunk':
|
||||
# splunk messages must have a top level "event" key
|
||||
message = {'event': message}
|
||||
# splunk messages must have a top level "event" key when using the /services/collector/event receiver.
|
||||
# The event receiver wont scan an event for a timestamp field therefore a time field must also be supplied containing epoch timestamp
|
||||
message = {'time': record.created, 'event': message}
|
||||
return self.serialize(message)
|
||||
|
||||
@@ -453,7 +453,7 @@ RECEPTOR_SERVICE_ADVERTISEMENT_PERIOD = 60 # https://github.com/ansible/recepto
|
||||
EXECUTION_NODE_REMEDIATION_CHECKS = 60 * 30 # once every 30 minutes check if an execution node errors have been resolved
|
||||
|
||||
# Amount of time dispatcher will try to reconnect to database for jobs and consuming new work
|
||||
DISPATCHER_DB_DOWNTOWN_TOLLERANCE = 40
|
||||
DISPATCHER_DB_DOWNTIME_TOLERANCE = 40
|
||||
|
||||
BROKER_URL = 'unix:///var/run/redis/redis.sock'
|
||||
CELERYBEAT_SCHEDULE = {
|
||||
|
||||
@@ -6,5 +6,20 @@ class ConstructedInventories extends InstanceGroupsMixin(Base) {
|
||||
super(http);
|
||||
this.baseUrl = 'api/v2/constructed_inventories/';
|
||||
}
|
||||
|
||||
async readConstructedInventoryOptions(id, method) {
|
||||
const {
|
||||
data: { actions },
|
||||
} = await this.http.options(`${this.baseUrl}${id}/`);
|
||||
|
||||
if (actions[method]) {
|
||||
return actions[method];
|
||||
}
|
||||
|
||||
throw new Error(
|
||||
`You have insufficient access to this Constructed Inventory.
|
||||
Please contact your system administrator if there is an issue with your access.`
|
||||
);
|
||||
}
|
||||
}
|
||||
export default ConstructedInventories;
|
||||
|
||||
51
awx/ui/src/api/models/ConstructedInventories.test.js
Normal file
51
awx/ui/src/api/models/ConstructedInventories.test.js
Normal file
@@ -0,0 +1,51 @@
|
||||
import ConstructedInventories from './ConstructedInventories';
|
||||
|
||||
describe('ConstructedInventoriesAPI', () => {
|
||||
const constructedInventoryId = 1;
|
||||
const constructedInventoryMethod = 'PUT';
|
||||
let ConstructedInventoriesAPI;
|
||||
let mockHttp;
|
||||
|
||||
beforeEach(() => {
|
||||
const optionsPromise = () =>
|
||||
Promise.resolve({
|
||||
data: {
|
||||
actions: {
|
||||
PUT: {},
|
||||
},
|
||||
},
|
||||
});
|
||||
mockHttp = {
|
||||
options: jest.fn(optionsPromise),
|
||||
};
|
||||
ConstructedInventoriesAPI = new ConstructedInventories(mockHttp);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.resetAllMocks();
|
||||
});
|
||||
|
||||
test('readConstructedInventoryOptions calls options with the expected params', async () => {
|
||||
await ConstructedInventoriesAPI.readConstructedInventoryOptions(
|
||||
constructedInventoryId,
|
||||
constructedInventoryMethod
|
||||
);
|
||||
expect(mockHttp.options).toHaveBeenCalledTimes(1);
|
||||
expect(mockHttp.options).toHaveBeenCalledWith(
|
||||
`api/v2/constructed_inventories/${constructedInventoryId}/`
|
||||
);
|
||||
});
|
||||
|
||||
test('readConstructedInventory should throw an error if action method is missing', async () => {
|
||||
try {
|
||||
await ConstructedInventoriesAPI.readConstructedInventoryOptions(
|
||||
constructedInventoryId,
|
||||
'POST'
|
||||
);
|
||||
} catch (error) {
|
||||
expect(error.message).toContain(
|
||||
'You have insufficient access to this Constructed Inventory.'
|
||||
);
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -1,14 +1,43 @@
|
||||
import React, { useState } from 'react';
|
||||
import React, { useCallback, useEffect, useState } from 'react';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
import { Card, PageSection } from '@patternfly/react-core';
|
||||
import { ConstructedInventoriesAPI, InventoriesAPI } from 'api';
|
||||
import useRequest from 'hooks/useRequest';
|
||||
import { CardBody } from 'components/Card';
|
||||
import ContentError from 'components/ContentError';
|
||||
import ContentLoading from 'components/ContentLoading';
|
||||
import ConstructedInventoryForm from '../shared/ConstructedInventoryForm';
|
||||
|
||||
function ConstructedInventoryAdd() {
|
||||
const history = useHistory();
|
||||
const [submitError, setSubmitError] = useState(null);
|
||||
|
||||
const {
|
||||
isLoading: isLoadingOptions,
|
||||
error: optionsError,
|
||||
request: fetchOptions,
|
||||
result: options,
|
||||
} = useRequest(
|
||||
useCallback(async () => {
|
||||
const res = await ConstructedInventoriesAPI.readOptions();
|
||||
const { data } = res;
|
||||
return data.actions.POST;
|
||||
}, []),
|
||||
null
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
fetchOptions();
|
||||
}, [fetchOptions]);
|
||||
|
||||
if (isLoadingOptions || (!options && !optionsError)) {
|
||||
return <ContentLoading />;
|
||||
}
|
||||
|
||||
if (optionsError) {
|
||||
return <ContentError error={optionsError} />;
|
||||
}
|
||||
|
||||
const handleCancel = () => {
|
||||
history.push('/inventories');
|
||||
};
|
||||
@@ -48,6 +77,7 @@ function ConstructedInventoryAdd() {
|
||||
onCancel={handleCancel}
|
||||
onSubmit={handleSubmit}
|
||||
submitError={submitError}
|
||||
options={options}
|
||||
/>
|
||||
</CardBody>
|
||||
</Card>
|
||||
|
||||
@@ -55,6 +55,7 @@ describe('<ConstructedInventoryAdd />', () => {
|
||||
context: { router: { history } },
|
||||
});
|
||||
});
|
||||
await waitForElement(wrapper, 'ContentLoading', (el) => el.length === 0);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
|
||||
@@ -20,6 +20,27 @@ function ConstructedInventoryEdit({ inventory }) {
|
||||
const detailsUrl = `/inventories/constructed_inventory/${inventory.id}/details`;
|
||||
const constructedInventoryId = inventory.id;
|
||||
|
||||
const {
|
||||
isLoading: isLoadingOptions,
|
||||
error: optionsError,
|
||||
request: fetchOptions,
|
||||
result: options,
|
||||
} = useRequest(
|
||||
useCallback(
|
||||
() =>
|
||||
ConstructedInventoriesAPI.readConstructedInventoryOptions(
|
||||
constructedInventoryId,
|
||||
'PUT'
|
||||
),
|
||||
[constructedInventoryId]
|
||||
),
|
||||
null
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
fetchOptions();
|
||||
}, [fetchOptions]);
|
||||
|
||||
const {
|
||||
result: { initialInstanceGroups, initialInputInventories },
|
||||
request: fetchedRelatedData,
|
||||
@@ -44,6 +65,7 @@ function ConstructedInventoryEdit({ inventory }) {
|
||||
isLoading: true,
|
||||
}
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
fetchedRelatedData();
|
||||
}, [fetchedRelatedData]);
|
||||
@@ -99,12 +121,12 @@ function ConstructedInventoryEdit({ inventory }) {
|
||||
|
||||
const handleCancel = () => history.push(detailsUrl);
|
||||
|
||||
if (isLoading) {
|
||||
return <ContentLoading />;
|
||||
if (contentError || optionsError) {
|
||||
return <ContentError error={contentError || optionsError} />;
|
||||
}
|
||||
|
||||
if (contentError) {
|
||||
return <ContentError error={contentError} />;
|
||||
if (isLoading || isLoadingOptions || (!options && !optionsError)) {
|
||||
return <ContentLoading />;
|
||||
}
|
||||
|
||||
return (
|
||||
@@ -116,6 +138,7 @@ function ConstructedInventoryEdit({ inventory }) {
|
||||
constructedInventory={inventory}
|
||||
instanceGroups={initialInstanceGroups}
|
||||
inputInventories={initialInputInventories}
|
||||
options={options}
|
||||
/>
|
||||
</CardBody>
|
||||
);
|
||||
|
||||
@@ -51,27 +51,22 @@ describe('<ConstructedInventoryEdit />', () => {
|
||||
};
|
||||
|
||||
beforeEach(async () => {
|
||||
ConstructedInventoriesAPI.readOptions.mockResolvedValue({
|
||||
data: {
|
||||
related: {},
|
||||
actions: {
|
||||
POST: {
|
||||
limit: {
|
||||
label: 'Limit',
|
||||
help_text: '',
|
||||
},
|
||||
update_cache_timeout: {
|
||||
label: 'Update cache timeout',
|
||||
help_text: 'help',
|
||||
},
|
||||
verbosity: {
|
||||
label: 'Verbosity',
|
||||
help_text: '',
|
||||
},
|
||||
},
|
||||
ConstructedInventoriesAPI.readConstructedInventoryOptions.mockResolvedValue(
|
||||
{
|
||||
limit: {
|
||||
label: 'Limit',
|
||||
help_text: '',
|
||||
},
|
||||
},
|
||||
});
|
||||
update_cache_timeout: {
|
||||
label: 'Update cache timeout',
|
||||
help_text: 'help',
|
||||
},
|
||||
verbosity: {
|
||||
label: 'Verbosity',
|
||||
help_text: '',
|
||||
},
|
||||
}
|
||||
);
|
||||
InventoriesAPI.readInstanceGroups.mockResolvedValue({
|
||||
data: {
|
||||
results: associatedInstanceGroups,
|
||||
@@ -169,6 +164,21 @@ describe('<ConstructedInventoryEdit />', () => {
|
||||
expect(wrapper.find('ContentError').length).toBe(1);
|
||||
});
|
||||
|
||||
test('should throw content error if user has insufficient options permissions', async () => {
|
||||
expect(wrapper.find('ContentError').length).toBe(0);
|
||||
ConstructedInventoriesAPI.readConstructedInventoryOptions.mockImplementationOnce(
|
||||
() => Promise.reject(new Error())
|
||||
);
|
||||
await act(async () => {
|
||||
wrapper = mountWithContexts(
|
||||
<ConstructedInventoryEdit inventory={mockInv} />
|
||||
);
|
||||
});
|
||||
|
||||
await waitForElement(wrapper, 'ContentLoading', (el) => el.length === 0);
|
||||
expect(wrapper.find('ContentError').length).toBe(1);
|
||||
});
|
||||
|
||||
test('unsuccessful form submission should show an error message', async () => {
|
||||
const error = {
|
||||
response: {
|
||||
|
||||
@@ -1,14 +1,10 @@
|
||||
import React, { useCallback, useEffect } from 'react';
|
||||
import React, { useCallback } from 'react';
|
||||
import { Formik, useField, useFormikContext } from 'formik';
|
||||
import { func, shape } from 'prop-types';
|
||||
import { t } from '@lingui/macro';
|
||||
import { ConstructedInventoriesAPI } from 'api';
|
||||
import { minMaxValue, required } from 'util/validators';
|
||||
import useRequest from 'hooks/useRequest';
|
||||
import { Form, FormGroup } from '@patternfly/react-core';
|
||||
import { VariablesField } from 'components/CodeEditor';
|
||||
import ContentError from 'components/ContentError';
|
||||
import ContentLoading from 'components/ContentLoading';
|
||||
import FormActionGroup from 'components/FormActionGroup/FormActionGroup';
|
||||
import FormField, { FormSubmitError } from 'components/FormField';
|
||||
import { FormFullWidthLayout, FormColumnLayout } from 'components/FormLayout';
|
||||
@@ -165,6 +161,7 @@ function ConstructedInventoryForm({
|
||||
onCancel,
|
||||
onSubmit,
|
||||
submitError,
|
||||
options,
|
||||
}) {
|
||||
const initialValues = {
|
||||
kind: 'constructed',
|
||||
@@ -179,32 +176,6 @@ function ConstructedInventoryForm({
|
||||
source_vars: constructedInventory?.source_vars || '---',
|
||||
};
|
||||
|
||||
const {
|
||||
isLoading,
|
||||
error,
|
||||
request: fetchOptions,
|
||||
result: options,
|
||||
} = useRequest(
|
||||
useCallback(async () => {
|
||||
const res = await ConstructedInventoriesAPI.readOptions();
|
||||
const { data } = res;
|
||||
return data.actions.POST;
|
||||
}, []),
|
||||
null
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
fetchOptions();
|
||||
}, [fetchOptions]);
|
||||
|
||||
if (isLoading || (!options && !error)) {
|
||||
return <ContentLoading />;
|
||||
}
|
||||
|
||||
if (error) {
|
||||
return <ContentError error={error} />;
|
||||
}
|
||||
|
||||
return (
|
||||
<Formik initialValues={initialValues} onSubmit={onSubmit}>
|
||||
{(formik) => (
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import React from 'react';
|
||||
import { act } from 'react-dom/test-utils';
|
||||
import { ConstructedInventoriesAPI } from 'api';
|
||||
import {
|
||||
mountWithContexts,
|
||||
waitForElement,
|
||||
@@ -19,38 +18,35 @@ const mockFormValues = {
|
||||
inputInventories: [{ id: 100, name: 'East' }],
|
||||
};
|
||||
|
||||
const options = {
|
||||
limit: {
|
||||
label: 'Limit',
|
||||
help_text: '',
|
||||
},
|
||||
update_cache_timeout: {
|
||||
label: 'Update cache timeout',
|
||||
help_text: 'help',
|
||||
},
|
||||
verbosity: {
|
||||
label: 'Verbosity',
|
||||
help_text: '',
|
||||
},
|
||||
};
|
||||
|
||||
describe('<ConstructedInventoryForm />', () => {
|
||||
let wrapper;
|
||||
const onSubmit = jest.fn();
|
||||
|
||||
beforeEach(async () => {
|
||||
ConstructedInventoriesAPI.readOptions.mockResolvedValue({
|
||||
data: {
|
||||
related: {},
|
||||
actions: {
|
||||
POST: {
|
||||
limit: {
|
||||
label: 'Limit',
|
||||
help_text: '',
|
||||
},
|
||||
update_cache_timeout: {
|
||||
label: 'Update cache timeout',
|
||||
help_text: 'help',
|
||||
},
|
||||
verbosity: {
|
||||
label: 'Verbosity',
|
||||
help_text: '',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
await act(async () => {
|
||||
wrapper = mountWithContexts(
|
||||
<ConstructedInventoryForm onCancel={() => {}} onSubmit={onSubmit} />
|
||||
<ConstructedInventoryForm
|
||||
onCancel={() => {}}
|
||||
onSubmit={onSubmit}
|
||||
options={options}
|
||||
/>
|
||||
);
|
||||
});
|
||||
await waitForElement(wrapper, 'ContentLoading', (el) => el.length === 0);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
@@ -104,20 +100,4 @@ describe('<ConstructedInventoryForm />', () => {
|
||||
'The plugin parameter is required.'
|
||||
);
|
||||
});
|
||||
|
||||
test('should throw content error when option request fails', async () => {
|
||||
let newWrapper;
|
||||
ConstructedInventoriesAPI.readOptions.mockImplementationOnce(() =>
|
||||
Promise.reject(new Error())
|
||||
);
|
||||
await act(async () => {
|
||||
newWrapper = mountWithContexts(
|
||||
<ConstructedInventoryForm onCancel={() => {}} onSubmit={() => {}} />
|
||||
);
|
||||
});
|
||||
expect(newWrapper.find('ContentError').length).toBe(0);
|
||||
newWrapper.update();
|
||||
expect(newWrapper.find('ContentError').length).toBe(1);
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -5,10 +5,13 @@ UI_NEXT_DIR := $(patsubst %/,%,$(dir $(lastword $(MAKEFILE_LIST))))
|
||||
# NOTE: you will not be able to build within the docker-compose development environment if you use this option
|
||||
UI_NEXT_LOCAL ?=
|
||||
|
||||
# Git repo and branch to the UI_NEXT repo
|
||||
## Git repo and branch to the UI_NEXT repo
|
||||
UI_NEXT_GIT_REPO ?= https://github.com/ansible/ansible-ui.git
|
||||
UI_NEXT_GIT_BRANCH ?= main
|
||||
|
||||
## Product name to display on the UI used in UI_NEXT build process
|
||||
PRODUCT ?= AWX
|
||||
|
||||
.PHONY: ui-next
|
||||
## Default build target of ui-next Makefile, builds ui-next/build
|
||||
ui-next: ui-next/build
|
||||
@@ -32,7 +35,8 @@ ui-next/src/build: $(UI_NEXT_DIR)/src/build/awx
|
||||
## True target for ui-next/src/build. Build ui_next from source.
|
||||
$(UI_NEXT_DIR)/src/build/awx: $(UI_NEXT_DIR)/src $(UI_NEXT_DIR)/src/node_modules/webpack
|
||||
@echo "=== Building ui_next ==="
|
||||
@cd $(UI_NEXT_DIR)/src && npm run build:awx
|
||||
@cd $(UI_NEXT_DIR)/src && PRODUCT="$(PRODUCT)" PUBLIC_PATH=/static/awx/ npm run build:awx
|
||||
@mv $(UI_NEXT_DIR)/src/build/awx/index.html $(UI_NEXT_DIR)/src/build/awx/index_awx.html
|
||||
|
||||
.PHONY: ui-next/src
|
||||
## Clone or link src of UI_NEXT to ui-next/src, will re-clone/link/update if necessary.
|
||||
|
||||
@@ -214,7 +214,7 @@ class LookupModule(LookupBase):
|
||||
if not isinstance(rule[field_name], list):
|
||||
rule[field_name] = rule[field_name].split(',')
|
||||
for value in rule[field_name]:
|
||||
value = value.strip()
|
||||
value = value.strip().lower()
|
||||
if value not in valid_list:
|
||||
raise AnsibleError('In rule {0} {1} must only contain values in {2}'.format(rule_number, field_name, ', '.join(valid_list.keys())))
|
||||
return_values.append(valid_list[value])
|
||||
|
||||
@@ -356,3 +356,19 @@
|
||||
that:
|
||||
- results is success
|
||||
- "'DTSTART;TZID=UTC:20220430T103045 RRULE:FREQ=MONTHLY;BYMONTHDAY=12,13,14,15,16,17,18;BYDAY=SA;INTERVAL=1' == complex_rule"
|
||||
|
||||
- name: mondays, Tuesdays, and WEDNESDAY with case-insensitivity
|
||||
set_fact:
|
||||
complex_rule: "{{ query(ruleset_plugin_name, '2022-04-30 10:30:45', rules=rrules, timezone='UTC' ) }}"
|
||||
ignore_errors: True
|
||||
register: results
|
||||
vars:
|
||||
rrules:
|
||||
- frequency: 'day'
|
||||
interval: 1
|
||||
byweekday: monday, Tuesday, WEDNESDAY
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- results is success
|
||||
- "'DTSTART;TZID=UTC:20220430T103045 RRULE:FREQ=DAILY;BYDAY=MO,TU,WE;INTERVAL=1' == complex_rule"
|
||||
|
||||
@@ -162,7 +162,7 @@ class ApiV2(base.Base):
|
||||
export_key = 'create_approval_template'
|
||||
rel_option_endpoint = _page.related.get('create_approval_template')
|
||||
|
||||
rel_post_fields = self._cache.get_post_fields(rel_option_endpoint)
|
||||
rel_post_fields = utils.get_post_fields(rel_option_endpoint, self._cache)
|
||||
if rel_post_fields is None:
|
||||
log.debug("%s is a read-only endpoint.", rel_endpoint)
|
||||
continue
|
||||
@@ -202,7 +202,7 @@ class ApiV2(base.Base):
|
||||
return utils.remove_encrypted(fields)
|
||||
|
||||
def _export_list(self, endpoint):
|
||||
post_fields = self._cache.get_post_fields(endpoint)
|
||||
post_fields = utils.get_post_fields(endpoint, self._cache)
|
||||
if post_fields is None:
|
||||
return None
|
||||
|
||||
@@ -267,7 +267,7 @@ class ApiV2(base.Base):
|
||||
|
||||
def _import_list(self, endpoint, assets):
|
||||
log.debug("_import_list -- endpoint: %s, assets: %s", endpoint.endpoint, repr(assets))
|
||||
post_fields = self._cache.get_post_fields(endpoint)
|
||||
post_fields = utils.get_post_fields(endpoint, self._cache)
|
||||
|
||||
changed = False
|
||||
|
||||
|
||||
@@ -495,7 +495,6 @@ class TentativePage(str):
|
||||
class PageCache(object):
|
||||
def __init__(self):
|
||||
self.options = {}
|
||||
self.post_fields = {}
|
||||
self.pages_by_url = {}
|
||||
self.pages_by_natural_key = {}
|
||||
|
||||
@@ -517,29 +516,6 @@ class PageCache(object):
|
||||
|
||||
return self.options.setdefault(url, options)
|
||||
|
||||
def get_post_fields(self, page):
|
||||
url = page.endpoint if isinstance(page, Page) else str(page)
|
||||
key = get_registered_page(url)
|
||||
if key in self.post_fields:
|
||||
return self.post_fields[key]
|
||||
|
||||
options_page = self.get_options(page)
|
||||
|
||||
if options_page is None:
|
||||
return None
|
||||
|
||||
if 'POST' not in options_page.r.headers.get('Allow', ''):
|
||||
return None
|
||||
|
||||
if 'POST' in options_page.json['actions']:
|
||||
post_fields = options_page.json['actions']['POST']
|
||||
else:
|
||||
log.warning("Insufficient privileges on %s, inferring POST fields from description.", options_page.endpoint)
|
||||
post_fields = utils.parse_description(options_page.json['description'])
|
||||
self.post_fields[key] = post_fields
|
||||
|
||||
return post_fields
|
||||
|
||||
def set_page(self, page):
|
||||
log.debug("set_page: %s %s", type(page), page.endpoint)
|
||||
self.pages_by_url[page.endpoint] = page
|
||||
|
||||
@@ -31,3 +31,18 @@ def remove_encrypted(value):
|
||||
if isinstance(value, dict):
|
||||
return {k: remove_encrypted(v) for k, v in value.items()}
|
||||
return value
|
||||
|
||||
|
||||
def get_post_fields(page, cache):
|
||||
options_page = cache.get_options(page)
|
||||
if options_page is None:
|
||||
return None
|
||||
|
||||
if 'POST' not in options_page.r.headers.get('Allow', ''):
|
||||
return None
|
||||
|
||||
if 'POST' in options_page.json['actions']:
|
||||
return options_page.json['actions']['POST']
|
||||
else:
|
||||
log.warning("Insufficient privileges on %s, inferring POST fields from description.", options_page.endpoint)
|
||||
return parse_description(options_page.json['description'])
|
||||
|
||||
@@ -137,12 +137,12 @@ To retrieve your admin password
|
||||
|
||||
To tail logs from the task containers
|
||||
```bash
|
||||
kubectl logs -f deployment/awx -n awx -c awx-web
|
||||
kubectl logs -f deployment/awx-task -n awx -c awx-task
|
||||
```
|
||||
|
||||
To tail logs from the web containers
|
||||
```bash
|
||||
kubectl logs -f deployment/awx -n awx -c awx-web
|
||||
kubectl logs -f deployment/awx-web -n awx -c awx-web
|
||||
```
|
||||
|
||||
NOTE: If there's multiple replica of the awx deployment you can use `stern` to tail logs from all replicas. For more information about `stern` check out https://github.com/wercker/stern.
|
||||
|
||||
@@ -42,7 +42,7 @@ pygerduty
|
||||
pyparsing==2.4.6 # Upgrading to v3 of pyparsing introduce errors on smart host filtering: Expected 'or' term, found 'or' (at char 15), (line:1, col:16)
|
||||
python-daemon>3.0.0
|
||||
python-dsv-sdk
|
||||
python-tss-sdk==1.0.0
|
||||
python-tss-sdk==1.2.1
|
||||
python-ldap
|
||||
pyyaml>=6.0.1
|
||||
receptorctl==1.3.0
|
||||
|
||||
@@ -333,7 +333,7 @@ python-ldap==3.4.3
|
||||
# django-auth-ldap
|
||||
python-string-utils==1.0.0
|
||||
# via openshift
|
||||
python-tss-sdk==1.0.0
|
||||
python-tss-sdk==1.2.1
|
||||
# via -r /awx_devel/requirements/requirements.in
|
||||
python3-openid==3.2.0
|
||||
# via social-auth-core
|
||||
|
||||
Reference in New Issue
Block a user