mirror of
https://github.com/ansible/awx.git
synced 2026-02-10 22:24:45 -03:30
Compare commits
21 Commits
12824-Inst
...
21.8.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1bedf32baf | ||
|
|
c5cf39abb7 | ||
|
|
6b315f39de | ||
|
|
529a936d0a | ||
|
|
e40824bded | ||
|
|
ed318ea784 | ||
|
|
d2b69e05f6 | ||
|
|
b57ae592ed | ||
|
|
e22f887765 | ||
|
|
fc838ba44b | ||
|
|
b19aa4a88d | ||
|
|
eba24db74c | ||
|
|
153a197fad | ||
|
|
8f4c329c2a | ||
|
|
368eb46f5b | ||
|
|
d6fea77082 | ||
|
|
aaf6f5f17e | ||
|
|
3303f7bfcf | ||
|
|
41fd6ea37f | ||
|
|
4808a0053f | ||
|
|
de41601f27 |
18
.github/workflows/pr_body_check.yml
vendored
18
.github/workflows/pr_body_check.yml
vendored
@@ -13,21 +13,13 @@ jobs:
|
|||||||
packages: write
|
packages: write
|
||||||
contents: read
|
contents: read
|
||||||
steps:
|
steps:
|
||||||
- name: Write PR body to a file
|
|
||||||
run: |
|
|
||||||
cat >> pr.body << __SOME_RANDOM_PR_EOF__
|
|
||||||
${{ github.event.pull_request.body }}
|
|
||||||
__SOME_RANDOM_PR_EOF__
|
|
||||||
|
|
||||||
- name: Display the received body for troubleshooting
|
|
||||||
run: cat pr.body
|
|
||||||
|
|
||||||
# We want to write these out individually just incase the options were joined on a single line
|
|
||||||
- name: Check for each of the lines
|
- name: Check for each of the lines
|
||||||
|
env:
|
||||||
|
PR_BODY: ${{ github.event.pull_request.body }}
|
||||||
run: |
|
run: |
|
||||||
grep "Bug, Docs Fix or other nominal change" pr.body > Z
|
echo $PR_BODY | grep "Bug, Docs Fix or other nominal change" > Z
|
||||||
grep "New or Enhanced Feature" pr.body > Y
|
echo $PR_BODY | grep "New or Enhanced Feature" > Y
|
||||||
grep "Breaking Change" pr.body > X
|
echo $PR_BODY | grep "Breaking Change" > X
|
||||||
exit 0
|
exit 0
|
||||||
# We exit 0 and set the shell to prevent the returns from the greps from failing this step
|
# We exit 0 and set the shell to prevent the returns from the greps from failing this step
|
||||||
# See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#exit-codes-and-error-action-preference
|
# See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#exit-codes-and-error-action-preference
|
||||||
|
|||||||
24
Makefile
24
Makefile
@@ -85,6 +85,7 @@ clean: clean-ui clean-api clean-awxkit clean-dist
|
|||||||
|
|
||||||
clean-api:
|
clean-api:
|
||||||
rm -rf build $(NAME)-$(VERSION) *.egg-info
|
rm -rf build $(NAME)-$(VERSION) *.egg-info
|
||||||
|
rm -rf .tox
|
||||||
find . -type f -regex ".*\.py[co]$$" -delete
|
find . -type f -regex ".*\.py[co]$$" -delete
|
||||||
find . -type d -name "__pycache__" -delete
|
find . -type d -name "__pycache__" -delete
|
||||||
rm -f awx/awx_test.sqlite3*
|
rm -f awx/awx_test.sqlite3*
|
||||||
@@ -181,7 +182,7 @@ collectstatic:
|
|||||||
@if [ "$(VENV_BASE)" ]; then \
|
@if [ "$(VENV_BASE)" ]; then \
|
||||||
. $(VENV_BASE)/awx/bin/activate; \
|
. $(VENV_BASE)/awx/bin/activate; \
|
||||||
fi; \
|
fi; \
|
||||||
mkdir -p awx/public/static && $(PYTHON) manage.py collectstatic --clear --noinput > /dev/null 2>&1
|
$(PYTHON) manage.py collectstatic --clear --noinput > /dev/null 2>&1
|
||||||
|
|
||||||
DEV_RELOAD_COMMAND ?= supervisorctl restart tower-processes:*
|
DEV_RELOAD_COMMAND ?= supervisorctl restart tower-processes:*
|
||||||
|
|
||||||
@@ -377,6 +378,8 @@ clean-ui:
|
|||||||
rm -rf awx/ui/build
|
rm -rf awx/ui/build
|
||||||
rm -rf awx/ui/src/locales/_build
|
rm -rf awx/ui/src/locales/_build
|
||||||
rm -rf $(UI_BUILD_FLAG_FILE)
|
rm -rf $(UI_BUILD_FLAG_FILE)
|
||||||
|
# the collectstatic command doesn't like it if this dir doesn't exist.
|
||||||
|
mkdir -p awx/ui/build/static
|
||||||
|
|
||||||
awx/ui/node_modules:
|
awx/ui/node_modules:
|
||||||
NODE_OPTIONS=--max-old-space-size=6144 $(NPM_BIN) --prefix awx/ui --loglevel warn --force ci
|
NODE_OPTIONS=--max-old-space-size=6144 $(NPM_BIN) --prefix awx/ui --loglevel warn --force ci
|
||||||
@@ -386,16 +389,14 @@ $(UI_BUILD_FLAG_FILE):
|
|||||||
$(PYTHON) tools/scripts/compilemessages.py
|
$(PYTHON) tools/scripts/compilemessages.py
|
||||||
$(NPM_BIN) --prefix awx/ui --loglevel warn run compile-strings
|
$(NPM_BIN) --prefix awx/ui --loglevel warn run compile-strings
|
||||||
$(NPM_BIN) --prefix awx/ui --loglevel warn run build
|
$(NPM_BIN) --prefix awx/ui --loglevel warn run build
|
||||||
mkdir -p awx/public/static/css
|
mkdir -p /var/lib/awx/public/static/css
|
||||||
mkdir -p awx/public/static/js
|
mkdir -p /var/lib/awx/public/static/js
|
||||||
mkdir -p awx/public/static/media
|
mkdir -p /var/lib/awx/public/static/media
|
||||||
cp -r awx/ui/build/static/css/* awx/public/static/css
|
cp -r awx/ui/build/static/css/* /var/lib/awx/public/static/css
|
||||||
cp -r awx/ui/build/static/js/* awx/public/static/js
|
cp -r awx/ui/build/static/js/* /var/lib/awx/public/static/js
|
||||||
cp -r awx/ui/build/static/media/* awx/public/static/media
|
cp -r awx/ui/build/static/media/* /var/lib/awx/public/static/media
|
||||||
touch $@
|
touch $@
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
ui-release: $(UI_BUILD_FLAG_FILE)
|
ui-release: $(UI_BUILD_FLAG_FILE)
|
||||||
|
|
||||||
ui-devel: awx/ui/node_modules
|
ui-devel: awx/ui/node_modules
|
||||||
@@ -453,6 +454,7 @@ COMPOSE_OPTS ?=
|
|||||||
CONTROL_PLANE_NODE_COUNT ?= 1
|
CONTROL_PLANE_NODE_COUNT ?= 1
|
||||||
EXECUTION_NODE_COUNT ?= 2
|
EXECUTION_NODE_COUNT ?= 2
|
||||||
MINIKUBE_CONTAINER_GROUP ?= false
|
MINIKUBE_CONTAINER_GROUP ?= false
|
||||||
|
MINIKUBE_SETUP ?= false # if false, run minikube separately
|
||||||
EXTRA_SOURCES_ANSIBLE_OPTS ?=
|
EXTRA_SOURCES_ANSIBLE_OPTS ?=
|
||||||
|
|
||||||
ifneq ($(ADMIN_PASSWORD),)
|
ifneq ($(ADMIN_PASSWORD),)
|
||||||
@@ -461,7 +463,7 @@ endif
|
|||||||
|
|
||||||
docker-compose-sources: .git/hooks/pre-commit
|
docker-compose-sources: .git/hooks/pre-commit
|
||||||
@if [ $(MINIKUBE_CONTAINER_GROUP) = true ]; then\
|
@if [ $(MINIKUBE_CONTAINER_GROUP) = true ]; then\
|
||||||
ansible-playbook -i tools/docker-compose/inventory tools/docker-compose-minikube/deploy.yml; \
|
ansible-playbook -i tools/docker-compose/inventory -e minikube_setup=$(MINIKUBE_SETUP) tools/docker-compose-minikube/deploy.yml; \
|
||||||
fi;
|
fi;
|
||||||
|
|
||||||
ansible-playbook -i tools/docker-compose/inventory tools/docker-compose/ansible/sources.yml \
|
ansible-playbook -i tools/docker-compose/inventory tools/docker-compose/ansible/sources.yml \
|
||||||
@@ -635,4 +637,4 @@ help/generate:
|
|||||||
} \
|
} \
|
||||||
} \
|
} \
|
||||||
{ lastLine = $$0 }' $(MAKEFILE_LIST) | sort -u
|
{ lastLine = $$0 }' $(MAKEFILE_LIST) | sort -u
|
||||||
@printf "\n"
|
@printf "\n"
|
||||||
|
|||||||
@@ -4952,7 +4952,7 @@ class InstanceSerializer(BaseSerializer):
|
|||||||
res['install_bundle'] = self.reverse('api:instance_install_bundle', kwargs={'pk': obj.pk})
|
res['install_bundle'] = self.reverse('api:instance_install_bundle', kwargs={'pk': obj.pk})
|
||||||
res['peers'] = self.reverse('api:instance_peers_list', kwargs={"pk": obj.pk})
|
res['peers'] = self.reverse('api:instance_peers_list', kwargs={"pk": obj.pk})
|
||||||
if self.context['request'].user.is_superuser or self.context['request'].user.is_system_auditor:
|
if self.context['request'].user.is_superuser or self.context['request'].user.is_system_auditor:
|
||||||
if obj.node_type != 'hop':
|
if obj.node_type == 'execution':
|
||||||
res['health_check'] = self.reverse('api:instance_health_check', kwargs={'pk': obj.pk})
|
res['health_check'] = self.reverse('api:instance_health_check', kwargs={'pk': obj.pk})
|
||||||
return res
|
return res
|
||||||
|
|
||||||
|
|||||||
@@ -392,8 +392,8 @@ class InstanceHealthCheck(GenericAPIView):
|
|||||||
permission_classes = (IsSystemAdminOrAuditor,)
|
permission_classes = (IsSystemAdminOrAuditor,)
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
|
return super().get_queryset().filter(node_type='execution')
|
||||||
# FIXME: For now, we don't have a good way of checking the health of a hop node.
|
# FIXME: For now, we don't have a good way of checking the health of a hop node.
|
||||||
return super().get_queryset().exclude(node_type='hop')
|
|
||||||
|
|
||||||
def get(self, request, *args, **kwargs):
|
def get(self, request, *args, **kwargs):
|
||||||
obj = self.get_object()
|
obj = self.get_object()
|
||||||
@@ -413,9 +413,10 @@ class InstanceHealthCheck(GenericAPIView):
|
|||||||
|
|
||||||
execution_node_health_check.apply_async([obj.hostname])
|
execution_node_health_check.apply_async([obj.hostname])
|
||||||
else:
|
else:
|
||||||
from awx.main.tasks.system import cluster_node_health_check
|
return Response(
|
||||||
|
{"error": f"Cannot run a health check on instances of type {obj.node_type}. Health checks can only be run on execution nodes."},
|
||||||
cluster_node_health_check.apply_async([obj.hostname], queue=obj.hostname)
|
status=status.HTTP_400_BAD_REQUEST,
|
||||||
|
)
|
||||||
return Response({'msg': f"Health check is running for {obj.hostname}."}, status=status.HTTP_200_OK)
|
return Response({'msg': f"Health check is running for {obj.hostname}."}, status=status.HTTP_200_OK)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
6241
awx/locale/translations/es/django.po
Normal file
6241
awx/locale/translations/es/django.po
Normal file
File diff suppressed because it is too large
Load Diff
10833
awx/locale/translations/es/messages.po
Normal file
10833
awx/locale/translations/es/messages.po
Normal file
File diff suppressed because it is too large
Load Diff
6243
awx/locale/translations/fr/django.po
Normal file
6243
awx/locale/translations/fr/django.po
Normal file
File diff suppressed because it is too large
Load Diff
10713
awx/locale/translations/fr/messages.po
Normal file
10713
awx/locale/translations/fr/messages.po
Normal file
File diff suppressed because it is too large
Load Diff
6240
awx/locale/translations/ja/django.po
Normal file
6240
awx/locale/translations/ja/django.po
Normal file
File diff suppressed because it is too large
Load Diff
10739
awx/locale/translations/ja/messages.po
Normal file
10739
awx/locale/translations/ja/messages.po
Normal file
File diff suppressed because it is too large
Load Diff
6240
awx/locale/translations/ko/django.po
Normal file
6240
awx/locale/translations/ko/django.po
Normal file
File diff suppressed because it is too large
Load Diff
10700
awx/locale/translations/ko/messages.po
Normal file
10700
awx/locale/translations/ko/messages.po
Normal file
File diff suppressed because it is too large
Load Diff
6241
awx/locale/translations/nl/django.po
Normal file
6241
awx/locale/translations/nl/django.po
Normal file
File diff suppressed because it is too large
Load Diff
10725
awx/locale/translations/nl/messages.po
Normal file
10725
awx/locale/translations/nl/messages.po
Normal file
File diff suppressed because it is too large
Load Diff
6242
awx/locale/translations/zh/django.po
Normal file
6242
awx/locale/translations/zh/django.po
Normal file
File diff suppressed because it is too large
Load Diff
10698
awx/locale/translations/zh/messages.po
Normal file
10698
awx/locale/translations/zh/messages.po
Normal file
File diff suppressed because it is too large
Load Diff
@@ -247,6 +247,19 @@ class Inventory(CommonModelNameNotUnique, ResourceMixin, RelatedJobsMixin):
|
|||||||
return (number, step)
|
return (number, step)
|
||||||
|
|
||||||
def get_sliced_hosts(self, host_queryset, slice_number, slice_count):
|
def get_sliced_hosts(self, host_queryset, slice_number, slice_count):
|
||||||
|
"""
|
||||||
|
Returns a slice of Hosts given a slice number and total slice count, or
|
||||||
|
the original queryset if slicing is not requested.
|
||||||
|
|
||||||
|
NOTE: If slicing is performed, this will return a List[Host] with the
|
||||||
|
resulting slice. If slicing is not performed it will return the
|
||||||
|
original queryset (not evaluating it or forcing it to a list). This
|
||||||
|
puts the burden on the caller to check the resulting type. This is
|
||||||
|
non-ideal because it's easy to get wrong, but I think the only way
|
||||||
|
around it is to force the queryset which has memory implications for
|
||||||
|
large inventories.
|
||||||
|
"""
|
||||||
|
|
||||||
if slice_count > 1 and slice_number > 0:
|
if slice_count > 1 and slice_number > 0:
|
||||||
offset = slice_number - 1
|
offset = slice_number - 1
|
||||||
host_queryset = host_queryset[offset::slice_count]
|
host_queryset = host_queryset[offset::slice_count]
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ from urllib.parse import urljoin
|
|||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.core.exceptions import ValidationError
|
from django.core.exceptions import ValidationError
|
||||||
from django.db import models
|
from django.db import models
|
||||||
|
from django.db.models.query import QuerySet
|
||||||
|
|
||||||
# from django.core.cache import cache
|
# from django.core.cache import cache
|
||||||
from django.utils.encoding import smart_str
|
from django.utils.encoding import smart_str
|
||||||
@@ -844,22 +845,30 @@ class Job(UnifiedJob, JobOptions, SurveyJobMixin, JobNotificationMixin, TaskMana
|
|||||||
def get_notification_friendly_name(self):
|
def get_notification_friendly_name(self):
|
||||||
return "Job"
|
return "Job"
|
||||||
|
|
||||||
def _get_inventory_hosts(self, only=['name', 'ansible_facts', 'ansible_facts_modified', 'modified', 'inventory_id']):
|
def _get_inventory_hosts(self, only=('name', 'ansible_facts', 'ansible_facts_modified', 'modified', 'inventory_id'), **filters):
|
||||||
|
"""Return value is an iterable for the relevant hosts for this job"""
|
||||||
if not self.inventory:
|
if not self.inventory:
|
||||||
return []
|
return []
|
||||||
host_queryset = self.inventory.hosts.only(*only)
|
host_queryset = self.inventory.hosts.only(*only)
|
||||||
return self.inventory.get_sliced_hosts(host_queryset, self.job_slice_number, self.job_slice_count)
|
if filters:
|
||||||
|
host_queryset = host_queryset.filter(**filters)
|
||||||
|
host_queryset = self.inventory.get_sliced_hosts(host_queryset, self.job_slice_number, self.job_slice_count)
|
||||||
|
if isinstance(host_queryset, QuerySet):
|
||||||
|
return host_queryset.iterator()
|
||||||
|
return host_queryset
|
||||||
|
|
||||||
def start_job_fact_cache(self, destination, modification_times, timeout=None):
|
def start_job_fact_cache(self, destination, modification_times, timeout=None):
|
||||||
self.log_lifecycle("start_job_fact_cache")
|
self.log_lifecycle("start_job_fact_cache")
|
||||||
os.makedirs(destination, mode=0o700)
|
os.makedirs(destination, mode=0o700)
|
||||||
hosts = self._get_inventory_hosts()
|
|
||||||
if timeout is None:
|
if timeout is None:
|
||||||
timeout = settings.ANSIBLE_FACT_CACHE_TIMEOUT
|
timeout = settings.ANSIBLE_FACT_CACHE_TIMEOUT
|
||||||
if timeout > 0:
|
if timeout > 0:
|
||||||
# exclude hosts with fact data older than `settings.ANSIBLE_FACT_CACHE_TIMEOUT seconds`
|
# exclude hosts with fact data older than `settings.ANSIBLE_FACT_CACHE_TIMEOUT seconds`
|
||||||
timeout = now() - datetime.timedelta(seconds=timeout)
|
timeout = now() - datetime.timedelta(seconds=timeout)
|
||||||
hosts = hosts.filter(ansible_facts_modified__gte=timeout)
|
hosts = self._get_inventory_hosts(ansible_facts_modified__gte=timeout)
|
||||||
|
else:
|
||||||
|
hosts = self._get_inventory_hosts()
|
||||||
for host in hosts:
|
for host in hosts:
|
||||||
filepath = os.sep.join(map(str, [destination, host.name]))
|
filepath = os.sep.join(map(str, [destination, host.name]))
|
||||||
if not os.path.realpath(filepath).startswith(destination):
|
if not os.path.realpath(filepath).startswith(destination):
|
||||||
|
|||||||
@@ -208,7 +208,10 @@ def run_until_complete(node, timing_data=None, **kwargs):
|
|||||||
if state_name.lower() == 'failed':
|
if state_name.lower() == 'failed':
|
||||||
work_detail = status.get('Detail', '')
|
work_detail = status.get('Detail', '')
|
||||||
if work_detail:
|
if work_detail:
|
||||||
raise RemoteJobError(f'Receptor error from {node}, detail:\n{work_detail}')
|
if stdout:
|
||||||
|
raise RemoteJobError(f'Receptor error from {node}, detail:\n{work_detail}\nstdout:\n{stdout}')
|
||||||
|
else:
|
||||||
|
raise RemoteJobError(f'Receptor error from {node}, detail:\n{work_detail}')
|
||||||
else:
|
else:
|
||||||
raise RemoteJobError(f'Unknown ansible-runner error on node {node}, stdout:\n{stdout}')
|
raise RemoteJobError(f'Unknown ansible-runner error on node {node}, stdout:\n{stdout}')
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ from awx.main.models.ha import Instance
|
|||||||
from django.test.utils import override_settings
|
from django.test.utils import override_settings
|
||||||
|
|
||||||
|
|
||||||
INSTANCE_KWARGS = dict(hostname='example-host', cpu=6, memory=36000000000, cpu_capacity=6, mem_capacity=42)
|
INSTANCE_KWARGS = dict(hostname='example-host', cpu=6, node_type='execution', memory=36000000000, cpu_capacity=6, mem_capacity=42)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
|
|||||||
@@ -110,7 +110,7 @@ if settings.COLOR_LOGS is True:
|
|||||||
# logs rendered with cyan text
|
# logs rendered with cyan text
|
||||||
previous_level_map = self.level_map.copy()
|
previous_level_map = self.level_map.copy()
|
||||||
if record.name == "awx.analytics.job_lifecycle":
|
if record.name == "awx.analytics.job_lifecycle":
|
||||||
self.level_map[logging.DEBUG] = (None, 'cyan', True)
|
self.level_map[logging.INFO] = (None, 'cyan', True)
|
||||||
msg = super(ColorHandler, self).colorize(line, record)
|
msg = super(ColorHandler, self).colorize(line, record)
|
||||||
self.level_map = previous_level_map
|
self.level_map = previous_level_map
|
||||||
return msg
|
return msg
|
||||||
|
|||||||
@@ -101,7 +101,7 @@ USE_L10N = True
|
|||||||
|
|
||||||
USE_TZ = True
|
USE_TZ = True
|
||||||
|
|
||||||
STATICFILES_DIRS = (os.path.join(BASE_DIR, 'ui', 'build', 'static'), os.path.join(BASE_DIR, 'static'))
|
STATICFILES_DIRS = [os.path.join(BASE_DIR, 'ui', 'build', 'static'), os.path.join(BASE_DIR, 'static')]
|
||||||
|
|
||||||
# Absolute filesystem path to the directory where static file are collected via
|
# Absolute filesystem path to the directory where static file are collected via
|
||||||
# the collectstatic command.
|
# the collectstatic command.
|
||||||
|
|||||||
@@ -416,8 +416,14 @@ function ScheduleForm({
|
|||||||
|
|
||||||
if (options.end === 'onDate') {
|
if (options.end === 'onDate') {
|
||||||
if (
|
if (
|
||||||
DateTime.fromISO(values.startDate) >=
|
DateTime.fromFormat(
|
||||||
DateTime.fromISO(options.endDate)
|
`${values.startDate} ${values.startTime}`,
|
||||||
|
'yyyy-LL-dd h:mm a'
|
||||||
|
).toMillis() >=
|
||||||
|
DateTime.fromFormat(
|
||||||
|
`${options.endDate} ${options.endTime}`,
|
||||||
|
'yyyy-LL-dd h:mm a'
|
||||||
|
).toMillis()
|
||||||
) {
|
) {
|
||||||
freqErrors.endDate = t`Please select an end date/time that comes after the start date/time.`;
|
freqErrors.endDate = t`Please select an end date/time that comes after the start date/time.`;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -900,6 +900,36 @@ describe('<ScheduleForm />', () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('should create schedule with the same start and end date provided that the end date is at a later time', async () => {
|
||||||
|
const today = DateTime.now().toFormat('yyyy-LL-dd');
|
||||||
|
const laterTime = DateTime.now().plus({ hours: 1 }).toFormat('h:mm a');
|
||||||
|
await act(async () => {
|
||||||
|
wrapper.find('DatePicker[aria-label="End date"]').prop('onChange')(
|
||||||
|
today,
|
||||||
|
new Date(today)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
wrapper.update();
|
||||||
|
expect(
|
||||||
|
wrapper
|
||||||
|
.find('FormGroup[data-cy="schedule-End date/time"]')
|
||||||
|
.prop('helperTextInvalid')
|
||||||
|
).toBe(
|
||||||
|
'Please select an end date/time that comes after the start date/time.'
|
||||||
|
);
|
||||||
|
await act(async () => {
|
||||||
|
wrapper.find('TimePicker[aria-label="End time"]').prop('onChange')(
|
||||||
|
laterTime
|
||||||
|
);
|
||||||
|
});
|
||||||
|
wrapper.update();
|
||||||
|
expect(
|
||||||
|
wrapper
|
||||||
|
.find('FormGroup[data-cy="schedule-End date/time"]')
|
||||||
|
.prop('helperTextInvalid')
|
||||||
|
).toBe(undefined);
|
||||||
|
});
|
||||||
|
|
||||||
test('error shown when on day number is not between 1 and 31', async () => {
|
test('error shown when on day number is not between 1 and 31', async () => {
|
||||||
await act(async () => {
|
await act(async () => {
|
||||||
wrapper.find('FrequencySelect#schedule-frequency').invoke('onChange')([
|
wrapper.find('FrequencySelect#schedule-frequency').invoke('onChange')([
|
||||||
|
|||||||
6241
awx/ui/src/locales/translations/es/django.po
Normal file
6241
awx/ui/src/locales/translations/es/django.po
Normal file
File diff suppressed because it is too large
Load Diff
10833
awx/ui/src/locales/translations/es/messages.po
Normal file
10833
awx/ui/src/locales/translations/es/messages.po
Normal file
File diff suppressed because it is too large
Load Diff
6243
awx/ui/src/locales/translations/fr/django.po
Normal file
6243
awx/ui/src/locales/translations/fr/django.po
Normal file
File diff suppressed because it is too large
Load Diff
10713
awx/ui/src/locales/translations/fr/messages.po
Normal file
10713
awx/ui/src/locales/translations/fr/messages.po
Normal file
File diff suppressed because it is too large
Load Diff
6240
awx/ui/src/locales/translations/ja/django.po
Normal file
6240
awx/ui/src/locales/translations/ja/django.po
Normal file
File diff suppressed because it is too large
Load Diff
10739
awx/ui/src/locales/translations/ja/messages.po
Normal file
10739
awx/ui/src/locales/translations/ja/messages.po
Normal file
File diff suppressed because it is too large
Load Diff
6240
awx/ui/src/locales/translations/ko/django.po
Normal file
6240
awx/ui/src/locales/translations/ko/django.po
Normal file
File diff suppressed because it is too large
Load Diff
10700
awx/ui/src/locales/translations/ko/messages.po
Normal file
10700
awx/ui/src/locales/translations/ko/messages.po
Normal file
File diff suppressed because it is too large
Load Diff
6241
awx/ui/src/locales/translations/nl/django.po
Normal file
6241
awx/ui/src/locales/translations/nl/django.po
Normal file
File diff suppressed because it is too large
Load Diff
10725
awx/ui/src/locales/translations/nl/messages.po
Normal file
10725
awx/ui/src/locales/translations/nl/messages.po
Normal file
File diff suppressed because it is too large
Load Diff
6242
awx/ui/src/locales/translations/zh/django.po
Normal file
6242
awx/ui/src/locales/translations/zh/django.po
Normal file
File diff suppressed because it is too large
Load Diff
10698
awx/ui/src/locales/translations/zh/messages.po
Normal file
10698
awx/ui/src/locales/translations/zh/messages.po
Normal file
File diff suppressed because it is too large
Load Diff
@@ -114,7 +114,12 @@ def main():
|
|||||||
# Update the project
|
# Update the project
|
||||||
result = module.post_endpoint(project['related']['update'])
|
result = module.post_endpoint(project['related']['update'])
|
||||||
|
|
||||||
if result['status_code'] != 202:
|
if result['status_code'] == 405:
|
||||||
|
module.fail_json(
|
||||||
|
msg="Unable to trigger a project update because the project scm_type ({0}) does not support it.".format(project['scm_type']),
|
||||||
|
response=result
|
||||||
|
)
|
||||||
|
elif result['status_code'] != 202:
|
||||||
module.fail_json(msg="Failed to update project, see response for details", response=result)
|
module.fail_json(msg="Failed to update project, see response for details", response=result)
|
||||||
|
|
||||||
module.json_output['changed'] = True
|
module.json_output['changed'] = True
|
||||||
|
|||||||
@@ -275,7 +275,13 @@ class ApiV2(base.Base):
|
|||||||
# When creating a project, we need to wait for its
|
# When creating a project, we need to wait for its
|
||||||
# first project update to finish so that associated
|
# first project update to finish so that associated
|
||||||
# JTs have valid options for playbook names
|
# JTs have valid options for playbook names
|
||||||
_page.wait_until_completed()
|
try:
|
||||||
|
_page.wait_until_completed(timeout=300)
|
||||||
|
except AssertionError:
|
||||||
|
# If the project update times out, try to
|
||||||
|
# carry on in the hopes that it will
|
||||||
|
# finish before it is needed.
|
||||||
|
pass
|
||||||
else:
|
else:
|
||||||
# If we are an existing project and our scm_tpye is not changing don't try and import the local_path setting
|
# If we are an existing project and our scm_tpye is not changing don't try and import the local_path setting
|
||||||
if asset['natural_key']['type'] == 'project' and 'local_path' in post_data and _page['scm_type'] == post_data['scm_type']:
|
if asset['natural_key']['type'] == 'project' and 'local_path' in post_data and _page['scm_type'] == post_data['scm_type']:
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
from contextlib import suppress
|
from contextlib import suppress
|
||||||
|
|
||||||
from awxkit.api.pages import SystemJobTemplate
|
from awxkit.api.pages import JobTemplate, SystemJobTemplate, Project, InventorySource
|
||||||
|
from awxkit.api.pages.workflow_job_templates import WorkflowJobTemplate
|
||||||
from awxkit.api.mixins import HasCreate
|
from awxkit.api.mixins import HasCreate
|
||||||
from awxkit.api.resources import resources
|
from awxkit.api.resources import resources
|
||||||
from awxkit.config import config
|
from awxkit.config import config
|
||||||
@@ -11,7 +12,7 @@ from . import base
|
|||||||
|
|
||||||
|
|
||||||
class Schedule(HasCreate, base.Base):
|
class Schedule(HasCreate, base.Base):
|
||||||
dependencies = [SystemJobTemplate]
|
dependencies = [JobTemplate, SystemJobTemplate, Project, InventorySource, WorkflowJobTemplate]
|
||||||
NATURAL_KEY = ('unified_job_template', 'name')
|
NATURAL_KEY = ('unified_job_template', 'name')
|
||||||
|
|
||||||
def silent_delete(self):
|
def silent_delete(self):
|
||||||
|
|||||||
@@ -9,4 +9,4 @@ template_dest: '_build'
|
|||||||
receptor_image: quay.io/ansible/receptor:devel
|
receptor_image: quay.io/ansible/receptor:devel
|
||||||
|
|
||||||
# Helper vars to construct the proper download URL for the current architecture
|
# Helper vars to construct the proper download URL for the current architecture
|
||||||
image_architecture: '{{ { "x86_64": "amd64", "aarch64": "arm64", "armv7": "arm", "ppc64le": "ppc64le" }[ansible_facts.architecture] }}'
|
image_architecture: '{{ { "x86_64": "amd64", "aarch64": "arm64", "armv7": "arm", "arm64": "arm64", "ppc64le": "ppc64le" }[ansible_facts.architecture] }}'
|
||||||
|
|||||||
@@ -9,8 +9,8 @@ addons:
|
|||||||
minikube_url_linux: 'https://storage.googleapis.com/minikube/releases/latest/minikube-linux-amd64'
|
minikube_url_linux: 'https://storage.googleapis.com/minikube/releases/latest/minikube-linux-amd64'
|
||||||
minikube_url_macos: 'https://storage.googleapis.com/minikube/releases/latest/minikube-darwin-amd64'
|
minikube_url_macos: 'https://storage.googleapis.com/minikube/releases/latest/minikube-darwin-amd64'
|
||||||
|
|
||||||
kubectl_url_linux: 'https://dl.k8s.io/release/v1.21.0/bin/linux/amd64/kubectl'
|
kubectl_url_linux: 'https://dl.k8s.io/release/v1.25.0/bin/linux/amd64/kubectl'
|
||||||
kubectl_url_macos: 'https://dl.k8s.io/release/v1.21.0/bin/darwin/amd64/kubectl'
|
kubectl_url_macos: 'https://dl.k8s.io/release/v1.25.0/bin/darwin/amd64/kubectl'
|
||||||
|
|
||||||
# Service Account Name
|
# Service Account Name
|
||||||
minikube_service_account_name: 'awx-devel'
|
minikube_service_account_name: 'awx-devel'
|
||||||
|
|||||||
@@ -8,6 +8,10 @@
|
|||||||
state: 'directory'
|
state: 'directory'
|
||||||
mode: '0700'
|
mode: '0700'
|
||||||
|
|
||||||
|
- name: debug minikube_setup
|
||||||
|
debug:
|
||||||
|
var: minikube_setup
|
||||||
|
|
||||||
# Linux block
|
# Linux block
|
||||||
- block:
|
- block:
|
||||||
- name: Download Minikube
|
- name: Download Minikube
|
||||||
@@ -24,6 +28,7 @@
|
|||||||
when:
|
when:
|
||||||
- ansible_architecture == "x86_64"
|
- ansible_architecture == "x86_64"
|
||||||
- ansible_system == "Linux"
|
- ansible_system == "Linux"
|
||||||
|
- minikube_setup | default(False) | bool
|
||||||
|
|
||||||
# MacOS block
|
# MacOS block
|
||||||
- block:
|
- block:
|
||||||
@@ -41,25 +46,29 @@
|
|||||||
when:
|
when:
|
||||||
- ansible_architecture == "x86_64"
|
- ansible_architecture == "x86_64"
|
||||||
- ansible_system == "Darwin"
|
- ansible_system == "Darwin"
|
||||||
|
- minikube_setup | default(False) | bool
|
||||||
|
|
||||||
- name: Starting Minikube
|
- block:
|
||||||
shell: "{{ sources_dest }}/minikube start --driver={{ driver }} --install-addons=true --addons={{ addons | join(',') }}"
|
- name: Starting Minikube
|
||||||
register: minikube_stdout
|
shell: "{{ sources_dest }}/minikube start --driver={{ driver }} --install-addons=true --addons={{ addons | join(',') }}"
|
||||||
|
register: minikube_stdout
|
||||||
|
|
||||||
- name: Enable Ingress Controller on Minikube
|
- name: Enable Ingress Controller on Minikube
|
||||||
shell: "{{ sources_dest }}/minikube addons enable ingress"
|
shell: "{{ sources_dest }}/minikube addons enable ingress"
|
||||||
|
when:
|
||||||
|
- minikube_stdout.rc == 0
|
||||||
|
register: _minikube_ingress
|
||||||
|
ignore_errors: true
|
||||||
|
|
||||||
|
- name: Show Minikube Ingress known-issue 7332 warning
|
||||||
|
pause:
|
||||||
|
seconds: 5
|
||||||
|
prompt: "The Minikube Ingress addon has been disabled since it looks like you are hitting https://github.com/kubernetes/minikube/issues/7332"
|
||||||
|
when:
|
||||||
|
- '"minikube/issues/7332" in _minikube_ingress.stderr'
|
||||||
|
- ansible_system == "Darwin"
|
||||||
when:
|
when:
|
||||||
- minikube_stdout.rc == 0
|
- minikube_setup | default(False) | bool
|
||||||
register: _minikube_ingress
|
|
||||||
ignore_errors: true
|
|
||||||
|
|
||||||
- name: Show Minikube Ingress known-issue 7332 warning
|
|
||||||
pause:
|
|
||||||
seconds: 5
|
|
||||||
prompt: "The Minikube Ingress addon has been disabled since it looks like you are hitting https://github.com/kubernetes/minikube/issues/7332"
|
|
||||||
when:
|
|
||||||
- '"minikube/issues/7332" in _minikube_ingress.stderr'
|
|
||||||
- ansible_system == "Darwin"
|
|
||||||
|
|
||||||
- name: Create ServiceAccount and clusterRoleBinding
|
- name: Create ServiceAccount and clusterRoleBinding
|
||||||
k8s:
|
k8s:
|
||||||
|
|||||||
@@ -301,11 +301,19 @@ Note that you may see multiple messages of the form `2021-03-04 20:11:47,666 WAR
|
|||||||
|
|
||||||
To bring up a 1 node AWX + minikube that is accessible from AWX run the following.
|
To bring up a 1 node AWX + minikube that is accessible from AWX run the following.
|
||||||
|
|
||||||
|
Start minikube
|
||||||
|
|
||||||
|
```bash
|
||||||
|
(host)$minikube start --cpus=4 --memory=8g --addons=ingress`
|
||||||
|
```
|
||||||
|
|
||||||
|
Start AWX
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
(host)$ make docker-compose-container-group
|
(host)$ make docker-compose-container-group
|
||||||
```
|
```
|
||||||
|
|
||||||
Alternatively, you can set the env var `MINIKUBE_CONTAINER_GROUP=true` to use the default dev env bring up. his way you can use other env flags like the cluster node count.
|
Alternatively, you can set the env var `MINIKUBE_CONTAINER_GROUP=true` to use the default dev env bring up. his way you can use other env flags like the cluster node count. Set `MINIKUBE_SETUP=true` to make the roles download, install and run minikube for you, but if you run into issues with this just start minikube yourself.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
(host)$ MINIKUBE_CONTAINER_GROUP=true make docker-compose
|
(host)$ MINIKUBE_CONTAINER_GROUP=true make docker-compose
|
||||||
|
|||||||
@@ -19,6 +19,9 @@ else
|
|||||||
wait-for-migrations
|
wait-for-migrations
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# Make sure that the UI static file directory exists, Django complains otherwise.
|
||||||
|
mkdir -p /awx_devel/awx/ui/build/static
|
||||||
|
|
||||||
if output=$(awx-manage createsuperuser --noinput --username=admin --email=admin@localhost 2> /dev/null); then
|
if output=$(awx-manage createsuperuser --noinput --username=admin --email=admin@localhost 2> /dev/null); then
|
||||||
echo $output
|
echo $output
|
||||||
fi
|
fi
|
||||||
@@ -27,10 +30,6 @@ echo "Admin password: ${DJANGO_SUPERUSER_PASSWORD}"
|
|||||||
awx-manage create_preload_data
|
awx-manage create_preload_data
|
||||||
awx-manage register_default_execution_environments
|
awx-manage register_default_execution_environments
|
||||||
|
|
||||||
mkdir -p /awx_devel/awx/public/static
|
|
||||||
mkdir -p /awx_devel/awx/ui/static
|
|
||||||
mkdir -p /awx_devel/awx/ui/build/static
|
|
||||||
|
|
||||||
awx-manage provision_instance --hostname="$(hostname)" --node_type="$MAIN_NODE_TYPE"
|
awx-manage provision_instance --hostname="$(hostname)" --node_type="$MAIN_NODE_TYPE"
|
||||||
awx-manage register_queue --queuename=controlplane --instance_percent=100
|
awx-manage register_queue --queuename=controlplane --instance_percent=100
|
||||||
awx-manage register_queue --queuename=default --instance_percent=100
|
awx-manage register_queue --queuename=default --instance_percent=100
|
||||||
|
|||||||
Reference in New Issue
Block a user