Compare commits

..

42 Commits

Author SHA1 Message Date
David O Neill
1a8c96a5e6 Fix formsubmiterror not showing 2024-02-28 14:56:25 +00:00
Cesar Francisco San Nicolas Martinez
a5d17539c6 Removing Podman to use Docker again in the collection ci (#14938)
Removing Podman var to use Docker again in the collection ci
2024-02-28 14:56:49 +01:00
Thanhnguyet Vo
a49d894cf1 Added missing AWS secret management lookup creds. 2024-02-28 04:16:59 +00:00
Chris Meyers
b3466d4449 Make JWT the first auth class and default
* No harm in adding it to the list. If a JWT auth header is provided,
  then process it (valid or not). If a JWT is not provided, move on to
  the next auth.
2024-02-27 15:09:16 -05:00
Hao Liu
237adc6150 Publish multi-arch versioned awx-ee
dependent on https://github.com/ansible/awx-ee/pull/235
2024-02-27 08:21:51 +00:00
Hao Liu
09b028ee3c Publish multi-arch manifest of awx (#14929)
Promote multi-arch awx manifest
2024-02-26 17:05:57 -05:00
Hao Liu
fb83bfbc31 Fix ui_next banner (#14928) 2024-02-26 14:20:42 -05:00
Hao Liu
88e406e121 Fix CVEs and bump receptorctl (#14925)
CVE-2023-47627
CVE-2023-49083
CVE-2023-41040
CVE-2024-22195
CVE-2023-46137
2024-02-26 15:48:38 +00:00
Thanhnguyet Vo
59d0bcc63f Fixed some misc errors in illustrations and header formatting 2024-02-22 13:04:37 +00:00
Hao Liu
3fb3125bc3 Send QUIT to worker before dying (#14913)
Fix deadlock scenario where dispatcher child process stuck in reading from queue loop after dispatcher parent process decided to quit

Co-authored-by: Alan Rominger <arominge@redhat.com>
2024-02-21 16:08:43 -05:00
Alan Rominger
d70c6b9474 Reset another to test-playbooks 2024-02-21 15:11:12 +00:00
Alan Rominger
5549516a37 Reset these tests back to test-playbooks 2024-02-21 15:11:12 +00:00
Alan Rominger
14ac91a8a2 Swap repos test fix 2024-02-21 15:11:12 +00:00
Alan Rominger
d5753818a0 Stop using the bulky test-playbooks in tests where possible 2024-02-21 15:11:12 +00:00
Chad Ferman
33010a2e02 added # -*-coding:utf-8-*- to test if this fixes issues with users being unable to have Japanese, Chinese and Korean Characters in email messages 2024-02-21 14:50:46 +00:00
Alan Rominger
14454cc670 Fix playbook errors found by debugging 2024-02-21 14:36:41 +00:00
Alan Rominger
7ab2bca16e Fix missing var name change 2024-02-21 14:36:41 +00:00
Alan Rominger
f0f655f2c3 Style consistency for task 'when' 2024-02-21 14:36:41 +00:00
Brian Coca
4286d411a7 fix project_update role/collection install
- avoid looping
  - avoid using multiple files, only one should be provided and processed per type
  - use first_found and variables to locate existing file
  - skip if no file exists
2024-02-21 14:36:41 +00:00
James Talton
06ad32ed8e Enhance the dashboard job summary endpoint to contain canceled and error job counts
Signed-off-by: James Talton <jtalton@redhat.com>
2024-02-21 14:12:59 +00:00
Alan Rominger
1ebff23232 Do not rely on unreliable dir output and use exists query 2024-02-21 13:43:54 +00:00
Alan Rominger
700de14c76 Make the migration middleware faster, second attempt 2024-02-21 13:43:54 +00:00
Thanhnguyet Vo
8605e339df Deleted duplicate graphics that were converted to drawio. 2024-02-21 13:10:41 +00:00
Thanhnguyet Vo
e50954ce40 Fixed graphics, illustrations, tables, examples, sizing 2024-02-21 13:10:41 +00:00
Hao Liu
7caca60308 Multi-arch build for AWX images in ghcr.io (#14899)
build amd64 and ARM image for
- awx
- awx_devel
- awx_kube_devel
2024-02-20 17:17:31 -05:00
Cesar Francisco San Nicolas Martinez
f4e13af056 Add support for terraform credentials in awxkit (#14902) 2024-02-20 13:42:25 +00:00
Jérémy Lal
decdb56288 Fix mistake in french message 2024-02-20 12:33:59 +00:00
Fdubois97
bcd4c2e8ef Fix: Ajout de traduction sur la langue FR 2024-02-20 12:32:18 +00:00
Thaïs DOUCET
d663066ac5 Fix typo in french message
Typo in "Launch Template" french message

Signed-off-by: Thaïs DOUCET <tdoucet@amiltone.com>
2024-02-20 12:21:31 +00:00
Sasa Jovicic
1ceebb275c Fix: login rerouting only works on the user's current tab (PR:#14399)
Signed-off-by: Sasa Jovicic <sjovicic@anexia-it.com>
2024-02-20 12:16:13 +00:00
César Francisco San Nicolás Martínez
f78ba282a6 Ability to user awxkit with websocket custom urls 2024-02-20 11:58:18 +00:00
Mikhail Yohman
81d88df757 Add YAML tab for Job Output event modal. 2024-02-19 16:45:46 +00:00
Hao Liu
0bdb01a9e9 Allow dev image to build on fork (#14898)
* Allow dev image to build on fork

Fix uppercase repo owner error in CI
example
```
Run docker pull ghcr.io/TheRealHaoLiu/awx_devel:devel
  docker pull ghcr.io/TheRealHaoLiu/awx_devel:devel
  shell: /usr/bin/bash --noprofile --norc -e -o pipefail {0}
  env:
    LC_ALL: C.UTF-8
    CI_GITHUB_TOKEN: ***
    DEV_DOCKER_OWNER: TheRealHaoLiu
    COMPOSE_TAG: devel
    py_version: 3
invalid reference format: repository name must be lowercase
```

---------

Co-authored-by: Rick Elrod <rick@elrod.me>
2024-02-19 16:19:59 +00:00
Alan Rominger
cd91fbf59f Label any changes to requirements folder with dependencies label 2024-02-19 15:56:53 +00:00
Michael Abashian
f240e640e5 Run prettier 2024-02-19 14:48:36 +00:00
ivarmu
46f489185e fixes problems with workflow nodes information section 2024-02-19 14:48:36 +00:00
Thanhnguyet Vo
dbb80fb7e3 Updated release notes so they don't need to be revised so often. 2024-02-19 12:18:57 +00:00
Seth Foster
cb3d357ce1 Disable install_bundle endpoint for ingress node
As we do for control nodes, disable the
install_bundle endpoint for ingress nodes.

This can be done by checking if instance managed
is True.

Signed-off-by: Seth Foster <fosterbseth@gmail.com>
2024-02-19 11:00:06 +00:00
Chris Meyers
dfa4db9266 Add tests for websocket endpoints
* authorized/not authorized tests for wsrelay endpoint
* not authorized test for web browser websockets
* skeleton of a test for authorized web browser websockets
2024-02-17 18:37:53 -05:00
Chris Meyers
6906a88dc9 Add pytest-asyncio to test channels websockets 2024-02-17 18:37:53 -05:00
Alan Rominger
1f7be9258c Remove tower_legacy module_utils that appears unused (#14421)
* Remove tower_legacy module that appears unused

* Update license details
2024-02-16 16:02:09 -05:00
Jake Jackson
dcce024424 Update release doc to check for awxkit tar files. (#14892)
add a step to release process to confirm tar file is published
2024-02-16 18:51:58 +00:00
68 changed files with 701 additions and 495 deletions

View File

@@ -11,6 +11,12 @@ runs:
shell: bash
run: echo py_version=`make PYTHON_VERSION` >> $GITHUB_ENV
- name: Set lower case owner name
shell: bash
run: echo "OWNER_LC=${OWNER,,}" >> $GITHUB_ENV
env:
OWNER: '${{ github.repository_owner }}'
- name: Log in to registry
shell: bash
run: |
@@ -18,11 +24,11 @@ runs:
- name: Pre-pull latest devel image to warm cache
shell: bash
run: docker pull ghcr.io/${{ github.repository_owner }}/awx_devel:${{ github.base_ref }}
run: docker pull ghcr.io/${OWNER_LC}/awx_devel:${{ github.base_ref }}
- name: Build image for current source checkout
shell: bash
run: |
DEV_DOCKER_TAG_BASE=ghcr.io/${{ github.repository_owner }} \
DEV_DOCKER_TAG_BASE=ghcr.io/${OWNER_LC} \
COMPOSE_TAG=${{ github.base_ref }} \
make docker-compose-build

View File

@@ -35,7 +35,7 @@ runs:
- name: Start AWX
shell: bash
run: |
DEV_DOCKER_TAG_BASE=ghcr.io/${{ github.repository_owner }} \
DEV_DOCKER_OWNER=${{ github.repository_owner }} \
COMPOSE_TAG=${{ github.base_ref }} \
COMPOSE_UP_OPTS="-d" \
make docker-compose

View File

@@ -15,5 +15,4 @@
"dependencies":
- any: ["awx/ui/package.json"]
- any: ["requirements/*.txt"]
- any: ["requirements/requirements.in"]
- any: ["requirements/*"]

View File

@@ -127,10 +127,6 @@ jobs:
- name: Run sanity tests
run: make test_collection_sanity
env:
# needed due to cgroupsv2. This is fixed, but a stable release
# with the fix has not been made yet.
ANSIBLE_TEST_PREFER_PODMAN: 1
collection-integration:
name: awx_collection integration

View File

@@ -9,22 +9,43 @@ on:
- release_*
- feature_*
jobs:
push:
if: endsWith(github.repository, '/awx') || startsWith(github.ref, 'refs/heads/release_')
push-development-images:
runs-on: ubuntu-latest
timeout-minutes: 60
timeout-minutes: 120
permissions:
packages: write
contents: read
strategy:
fail-fast: false
matrix:
build-targets:
- image-name: awx_devel
make-target: docker-compose-buildx
- image-name: awx_kube_devel
make-target: awx-kube-dev-buildx
- image-name: awx
make-target: awx-kube-buildx
steps:
- name: Skipping build of awx image for non-awx repository
run: |
echo "Skipping build of awx image for non-awx repository"
exit 0
if: matrix.build-targets.image-name == 'awx' && !endsWith(github.repository, '/awx')
- uses: actions/checkout@v3
- name: Get python version from Makefile
run: echo py_version=`make PYTHON_VERSION` >> $GITHUB_ENV
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
- name: Set lower case owner name
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Set GITHUB_ENV variables
run: |
echo "OWNER_LC=${OWNER,,}" >>${GITHUB_ENV}
echo "DEV_DOCKER_TAG_BASE=ghcr.io/${OWNER,,}" >> $GITHUB_ENV
echo "COMPOSE_TAG=${GITHUB_REF##*/}" >> $GITHUB_ENV
echo py_version=`make PYTHON_VERSION` >> $GITHUB_ENV
env:
OWNER: '${{ github.repository_owner }}'
@@ -37,23 +58,19 @@ jobs:
run: |
echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u ${{ github.actor }} --password-stdin
- name: Pre-pull image to warm build cache
run: |
docker pull ghcr.io/${OWNER_LC}/awx_devel:${GITHUB_REF##*/} || :
docker pull ghcr.io/${OWNER_LC}/awx_kube_devel:${GITHUB_REF##*/} || :
docker pull ghcr.io/${OWNER_LC}/awx:${GITHUB_REF##*/} || :
- name: Setup node and npm
uses: actions/setup-node@v2
with:
node-version: '16.13.1'
if: matrix.build-targets.image-name == 'awx'
- name: Build images
- name: Prebuild UI for awx image (to speed up build process)
run: |
DEV_DOCKER_TAG_BASE=ghcr.io/${OWNER_LC} COMPOSE_TAG=${GITHUB_REF##*/} make docker-compose-build
DEV_DOCKER_TAG_BASE=ghcr.io/${OWNER_LC} COMPOSE_TAG=${GITHUB_REF##*/} make awx-kube-dev-build
DEV_DOCKER_TAG_BASE=ghcr.io/${OWNER_LC} COMPOSE_TAG=${GITHUB_REF##*/} make awx-kube-build
sudo apt-get install gettext
make ui-release
make ui-next
if: matrix.build-targets.image-name == 'awx'
- name: Push development images
- name: Build and push AWX devel images
run: |
docker push ghcr.io/${OWNER_LC}/awx_devel:${GITHUB_REF##*/}
docker push ghcr.io/${OWNER_LC}/awx_kube_devel:${GITHUB_REF##*/}
- name: Push AWX k8s image, only for upstream and feature branches
run: docker push ghcr.io/${OWNER_LC}/awx:${GITHUB_REF##*/}
if: endsWith(github.repository, '/awx')
make ${{ matrix.build-targets.make-target }}

View File

@@ -83,11 +83,15 @@ jobs:
- name: Re-tag and promote awx image
run: |
docker pull ghcr.io/${{ github.repository }}:${{ github.event.release.tag_name }}
docker tag ghcr.io/${{ github.repository }}:${{ github.event.release.tag_name }} quay.io/${{ github.repository }}:${{ github.event.release.tag_name }}
docker tag ghcr.io/${{ github.repository }}:${{ github.event.release.tag_name }} quay.io/${{ github.repository }}:latest
docker push quay.io/${{ github.repository }}:${{ github.event.release.tag_name }}
docker push quay.io/${{ github.repository }}:latest
docker pull ghcr.io/${{ github.repository_owner }}/awx-ee:${{ github.event.release.tag_name }}
docker tag ghcr.io/${{ github.repository_owner }}/awx-ee:${{ github.event.release.tag_name }} quay.io/${{ github.repository_owner }}/awx-ee:${{ github.event.release.tag_name }}
docker push quay.io/${{ github.repository_owner }}/awx-ee:${{ github.event.release.tag_name }}
docker buildx imagetools create \
ghcr.io/${{ github.repository }}:${{ github.event.release.tag_name }} \
--tag quay.io/${{ github.repository }}:${{ github.event.release.tag_name }}
docker buildx imagetools create \
ghcr.io/${{ github.repository }}:${{ github.event.release.tag_name }} \
--tag quay.io/${{ github.repository }}:latest
- name: Re-tag and promote awx-ee image
run: |
docker buildx imagetools create \
ghcr.io/${{ github.repository_owner }}/awx-ee:${{ github.event.release.tag_name }} \
--tag quay.io/${{ github.repository_owner }}/awx-ee:${{ github.event.release.tag_name }}

View File

@@ -102,9 +102,9 @@ jobs:
- name: tag awx-ee:latest with version input
run: |
docker pull quay.io/ansible/awx-ee:latest
docker tag quay.io/ansible/awx-ee:latest ghcr.io/${{ github.repository_owner }}/awx-ee:${{ github.event.inputs.version }}
docker push ghcr.io/${{ github.repository_owner }}/awx-ee:${{ github.event.inputs.version }}
docker buildx imagetools create \
quay.io/ansible/awx-ee:latest \
--tag ghcr.io/${{ github.repository_owner }}/awx-ee:${{ github.event.inputs.version }}
- name: Stage awx-operator image
working-directory: awx-operator

View File

@@ -75,6 +75,9 @@ SDIST_TAR_FILE ?= $(SDIST_TAR_NAME).tar.gz
I18N_FLAG_FILE = .i18n_built
## PLATFORMS defines the target platforms for the manager image be build to provide support to multiple
PLATFORMS ?= linux/amd64,linux/arm64 # linux/ppc64le,linux/s390x
.PHONY: awx-link clean clean-tmp clean-venv requirements requirements_dev \
develop refresh adduser migrate dbchange \
receiver test test_unit test_coverage coverage_html \
@@ -532,7 +535,7 @@ docker-compose-sources: .git/hooks/pre-commit
-e enable_vault=$(VAULT) \
-e vault_tls=$(VAULT_TLS) \
-e enable_tacacs=$(TACACS) \
$(EXTRA_SOURCES_ANSIBLE_OPTS)
$(EXTRA_SOURCES_ANSIBLE_OPTS)
docker-compose: awx/projects docker-compose-sources
ansible-galaxy install --ignore-certs -r tools/docker-compose/ansible/requirements.yml;
@@ -586,28 +589,20 @@ docker-compose-build: Dockerfile.dev
--build-arg BUILDKIT_INLINE_CACHE=1 \
--cache-from=$(DEV_DOCKER_TAG_BASE)/awx_devel:$(COMPOSE_TAG) .
# ## Build awx_devel image for docker compose development environment for multiple architectures
# docker-compose-buildx: Dockerfile.dev
# DOCKER_BUILDKIT=1 docker build \
# -f Dockerfile.dev \
# -t $(DEVEL_IMAGE_NAME) \
# --build-arg BUILDKIT_INLINE_CACHE=1 \
# --cache-from=$(DEV_DOCKER_TAG_BASE)/awx_devel:$(COMPOSE_TAG) .
## Build awx_devel image for docker compose development environment for multiple architectures
# PLATFORMS defines the target platforms for the manager image be build to provide support to multiple
# architectures. (i.e. make docker-buildx IMG=myregistry/mypoperator:0.0.1). To use this option you need to:
# - able to use docker buildx . More info: https://docs.docker.com/build/buildx/
# - have enable BuildKit, More info: https://docs.docker.com/develop/develop-images/build_enhancements/
# - be able to push the image for your registry (i.e. if you do not inform a valid value via IMG=<myregistry/image:<tag>> than the export will fail)
# To properly provided solutions that supports more than one platform you should use this option.
PLATFORMS ?= linux/amd64,linux/arm64 # linux/ppc64le,linux/s390x
.PHONY: docker-compose-buildx
docker-compose-buildx: Dockerfile.dev ## Build and push docker image for the manager for cross-platform support
- docker buildx create --name project-v3-builder
docker buildx use project-v3-builder
- docker buildx build --push $(BUILD_ARGS) --platform=$(PLATFORMS) --tag $(DEVEL_IMAGE_NAME) -f Dockerfile.dev .
- docker buildx rm project-v3-builder
## Build awx_devel image for docker compose development environment for multiple architectures
docker-compose-buildx: Dockerfile.dev
- docker buildx create --name docker-compose-buildx
docker buildx use docker-compose-buildx
- docker buildx build \
--push \
--build-arg BUILDKIT_INLINE_CACHE=1 \
--cache-from=$(DEV_DOCKER_TAG_BASE)/awx_devel:$(COMPOSE_TAG) \
--platform=$(PLATFORMS) \
--tag $(DEVEL_IMAGE_NAME) \
-f Dockerfile.dev .
- docker buildx rm docker-compose-buildx
docker-clean:
-$(foreach container_id,$(shell docker ps -f name=tools_awx -aq && docker ps -f name=tools_receptor -aq),docker stop $(container_id); docker rm -f $(container_id);)
@@ -671,6 +666,21 @@ awx-kube-build: Dockerfile
--build-arg HEADLESS=$(HEADLESS) \
-t $(DEV_DOCKER_TAG_BASE)/awx:$(COMPOSE_TAG) .
## Build multi-arch awx image for deployment on Kubernetes environment.
awx-kube-buildx: Dockerfile
- docker buildx create --name awx-kube-buildx
docker buildx use awx-kube-buildx
- docker buildx build \
--push \
--build-arg VERSION=$(VERSION) \
--build-arg SETUPTOOLS_SCM_PRETEND_VERSION=$(VERSION) \
--build-arg HEADLESS=$(HEADLESS) \
--platform=$(PLATFORMS) \
--tag $(DEV_DOCKER_TAG_BASE)/awx:$(COMPOSE_TAG) \
-f Dockerfile .
- docker buildx rm awx-kube-buildx
.PHONY: Dockerfile.kube-dev
## Generate Docker.kube-dev for awx_kube_devel image
Dockerfile.kube-dev: tools/ansible/roles/dockerfile/templates/Dockerfile.j2
@@ -687,6 +697,18 @@ awx-kube-dev-build: Dockerfile.kube-dev
--cache-from=$(DEV_DOCKER_TAG_BASE)/awx_kube_devel:$(COMPOSE_TAG) \
-t $(DEV_DOCKER_TAG_BASE)/awx_kube_devel:$(COMPOSE_TAG) .
## Build and push multi-arch awx_kube_devel image for development on local Kubernetes environment.
awx-kube-dev-buildx: Dockerfile.kube-dev
- docker buildx create --name awx-kube-dev-buildx
docker buildx use awx-kube-dev-buildx
- docker buildx build \
--push \
--build-arg BUILDKIT_INLINE_CACHE=1 \
--cache-from=$(DEV_DOCKER_TAG_BASE)/awx_kube_devel:$(COMPOSE_TAG) \
--platform=$(PLATFORMS) \
--tag $(DEV_DOCKER_TAG_BASE)/awx_kube_devel:$(COMPOSE_TAG) \
-f Dockerfile.kube-dev .
- docker buildx rm awx-kube-dev-buildx
kind-dev-load: awx-kube-dev-build
$(KIND_BIN) load docker-image $(DEV_DOCKER_TAG_BASE)/awx_kube_devel:$(COMPOSE_TAG)

View File

@@ -5594,7 +5594,7 @@ class InstanceSerializer(BaseSerializer):
res['jobs'] = self.reverse('api:instance_unified_jobs_list', kwargs={'pk': obj.pk})
res['peers'] = self.reverse('api:instance_peers_list', kwargs={"pk": obj.pk})
res['instance_groups'] = self.reverse('api:instance_instance_groups_list', kwargs={'pk': obj.pk})
if obj.node_type in [Instance.Types.EXECUTION, Instance.Types.HOP]:
if obj.node_type in [Instance.Types.EXECUTION, Instance.Types.HOP] and not obj.managed:
res['install_bundle'] = self.reverse('api:instance_install_bundle', kwargs={'pk': obj.pk})
if self.context['request'].user.is_superuser or self.context['request'].user.is_system_auditor:
if obj.node_type == 'execution':

View File

@@ -272,16 +272,24 @@ class DashboardJobsGraphView(APIView):
success_query = user_unified_jobs.filter(status='successful')
failed_query = user_unified_jobs.filter(status='failed')
canceled_query = user_unified_jobs.filter(status='canceled')
error_query = user_unified_jobs.filter(status='error')
if job_type == 'inv_sync':
success_query = success_query.filter(instance_of=models.InventoryUpdate)
failed_query = failed_query.filter(instance_of=models.InventoryUpdate)
canceled_query = canceled_query.filter(instance_of=models.InventoryUpdate)
error_query = error_query.filter(instance_of=models.InventoryUpdate)
elif job_type == 'playbook_run':
success_query = success_query.filter(instance_of=models.Job)
failed_query = failed_query.filter(instance_of=models.Job)
canceled_query = canceled_query.filter(instance_of=models.Job)
error_query = error_query.filter(instance_of=models.Job)
elif job_type == 'scm_update':
success_query = success_query.filter(instance_of=models.ProjectUpdate)
failed_query = failed_query.filter(instance_of=models.ProjectUpdate)
canceled_query = canceled_query.filter(instance_of=models.ProjectUpdate)
error_query = error_query.filter(instance_of=models.ProjectUpdate)
end = now()
interval = 'day'
@@ -297,10 +305,12 @@ class DashboardJobsGraphView(APIView):
else:
return Response({'error': _('Unknown period "%s"') % str(period)}, status=status.HTTP_400_BAD_REQUEST)
dashboard_data = {"jobs": {"successful": [], "failed": []}}
dashboard_data = {"jobs": {"successful": [], "failed": [], "canceled": [], "error": []}}
succ_list = dashboard_data['jobs']['successful']
fail_list = dashboard_data['jobs']['failed']
canceled_list = dashboard_data['jobs']['canceled']
error_list = dashboard_data['jobs']['error']
qs_s = (
success_query.filter(finished__range=(start, end))
@@ -318,6 +328,22 @@ class DashboardJobsGraphView(APIView):
.annotate(agg=Count('id', distinct=True))
)
data_f = {item['d']: item['agg'] for item in qs_f}
qs_c = (
canceled_query.filter(finished__range=(start, end))
.annotate(d=Trunc('finished', interval, tzinfo=end.tzinfo))
.order_by()
.values('d')
.annotate(agg=Count('id', distinct=True))
)
data_c = {item['d']: item['agg'] for item in qs_c}
qs_e = (
error_query.filter(finished__range=(start, end))
.annotate(d=Trunc('finished', interval, tzinfo=end.tzinfo))
.order_by()
.values('d')
.annotate(agg=Count('id', distinct=True))
)
data_e = {item['d']: item['agg'] for item in qs_e}
start_date = start.replace(hour=0, minute=0, second=0, microsecond=0)
for d in itertools.count():
@@ -326,6 +352,8 @@ class DashboardJobsGraphView(APIView):
break
succ_list.append([time.mktime(date.timetuple()), data_s.get(date, 0)])
fail_list.append([time.mktime(date.timetuple()), data_f.get(date, 0)])
canceled_list.append([time.mktime(date.timetuple()), data_c.get(date, 0)])
error_list.append([time.mktime(date.timetuple()), data_e.get(date, 0)])
return Response(dashboard_data)

View File

@@ -259,6 +259,12 @@ class AWXConsumerPG(AWXConsumerBase):
current_downtime = time.time() - self.pg_down_time
if current_downtime > self.pg_max_wait:
logger.exception(f"Postgres event consumer has not recovered in {current_downtime} s, exiting")
# Sending QUIT to multiprocess queue to signal workers to exit
for worker in self.pool.workers:
try:
worker.quit()
except Exception:
logger.exception(f"Error sending QUIT to worker {worker}")
raise
# Wait for a second before next attempt, but still listen for any shutdown signals
for i in range(10):
@@ -270,6 +276,12 @@ class AWXConsumerPG(AWXConsumerBase):
except Exception:
# Log unanticipated exception in addition to writing to stderr to get timestamps and other metadata
logger.exception('Encountered unhandled error in dispatcher main loop')
# Sending QUIT to multiprocess queue to signal workers to exit
for worker in self.pool.workers:
try:
worker.quit()
except Exception:
logger.exception(f"Error sending QUIT to worker {worker}")
raise

View File

@@ -5,11 +5,12 @@ import logging
import threading
import time
import urllib.parse
from pathlib import Path
from django.conf import settings
from django.contrib.auth import logout
from django.contrib.auth.models import User
from django.db.migrations.executor import MigrationExecutor
from django.db.migrations.recorder import MigrationRecorder
from django.db import connection
from django.shortcuts import redirect
from django.apps import apps
@@ -17,9 +18,11 @@ from django.utils.deprecation import MiddlewareMixin
from django.utils.translation import gettext_lazy as _
from django.urls import reverse, resolve
from awx.main import migrations
from awx.main.utils.named_url_graph import generate_graph, GraphNode
from awx.conf import fields, register
from awx.main.utils.profiling import AWXProfiler
from awx.main.utils.common import memoize
logger = logging.getLogger('awx.main.middleware')
@@ -198,9 +201,22 @@ class URLModificationMiddleware(MiddlewareMixin):
request.path_info = new_path
@memoize(ttl=20)
def is_migrating():
latest_number = 0
latest_name = ''
for migration_path in Path(migrations.__path__[0]).glob('[0-9]*.py'):
try:
migration_number = int(migration_path.name.split('_', 1)[0])
except ValueError:
continue
if migration_number > latest_number:
latest_number = migration_number
latest_name = migration_path.name[: -len('.py')]
return not MigrationRecorder(connection).migration_qs.filter(app='main', name=latest_name).exists()
class MigrationRanCheckMiddleware(MiddlewareMixin):
def process_request(self, request):
executor = MigrationExecutor(connection)
plan = executor.migration_plan(executor.loader.graph.leaf_nodes())
if bool(plan) and getattr(resolve(request.path), 'url_name', '') != 'migrations_notran':
if is_migrating() and getattr(resolve(request.path), 'url_name', '') != 'migrations_notran':
return redirect(reverse("ui:migrations_notran"))

View File

@@ -1,5 +1,6 @@
# Copyright (c) 2019 Ansible, Inc.
# All Rights Reserved.
# -*-coding:utf-8-*-
class CustomNotificationBase(object):

View File

@@ -12,6 +12,7 @@ from . import consumers
logger = logging.getLogger('awx.main.routing')
_application = None
class AWXProtocolTypeRouter(ProtocolTypeRouter):
@@ -66,11 +67,52 @@ websocket_relay_urlpatterns = [
re_path(r'websocket/relay/$', consumers.RelayConsumer.as_asgi()),
]
application = AWXProtocolTypeRouter(
{
'websocket': MultipleURLRouterAdapter(
URLRouter(websocket_relay_urlpatterns),
DrfAuthMiddlewareStack(URLRouter(websocket_urlpatterns)),
)
}
)
def application_func(cls=AWXProtocolTypeRouter) -> ProtocolTypeRouter:
return cls(
{
'websocket': MultipleURLRouterAdapter(
URLRouter(websocket_relay_urlpatterns),
DrfAuthMiddlewareStack(URLRouter(websocket_urlpatterns)),
)
}
)
def __getattr__(name: str) -> ProtocolTypeRouter:
"""
Defer instantiating application.
For testing, we just need it to NOT run on import.
https://peps.python.org/pep-0562/#specification
Normally, someone would get application from this module via:
from awx.main.routing import application
and do something with the application:
application.do_something()
What does the callstack look like when the import runs?
...
awx.main.routing.__getattribute__(...) # <-- we don't define this so NOOP as far as we are concerned
if '__getattr__' in awx.main.routing.__dict__: # <-- this triggers the function we are in
return awx.main.routing.__dict__.__getattr__("application")
Why isn't this function simply implemented as:
def __getattr__(name):
if not _application:
_application = application_func()
return _application
It could. I manually tested it and it passes test_routing.py.
But my understanding after reading the PEP-0562 specification link above is that
performance would be a bit worse due to the extra __getattribute__ calls when
we reference non-global variables.
"""
if name == "application":
globs = globals()
if not globs['_application']:
globs['_application'] = application_func()
return globs['_application']
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")

View File

@@ -0,0 +1,90 @@
import pytest
from django.contrib.auth.models import AnonymousUser
from channels.routing import ProtocolTypeRouter
from channels.testing.websocket import WebsocketCommunicator
from awx.main.consumers import WebsocketSecretAuthHelper
@pytest.fixture
def application():
# code in routing hits the db on import because .. settings cache
from awx.main.routing import application_func
yield application_func(ProtocolTypeRouter)
@pytest.fixture
def websocket_server_generator(application):
def fn(endpoint):
return WebsocketCommunicator(application, endpoint)
return fn
@pytest.mark.asyncio
@pytest.mark.django_db
class TestWebsocketRelay:
@pytest.fixture
def websocket_relay_secret_generator(self, settings):
def fn(secret, set_broadcast_websocket_secret=False):
secret_backup = settings.BROADCAST_WEBSOCKET_SECRET
settings.BROADCAST_WEBSOCKET_SECRET = 'foobar'
res = ('secret'.encode('utf-8'), WebsocketSecretAuthHelper.construct_secret().encode('utf-8'))
if set_broadcast_websocket_secret is False:
settings.BROADCAST_WEBSOCKET_SECRET = secret_backup
return res
return fn
@pytest.fixture
def websocket_relay_secret(self, settings, websocket_relay_secret_generator):
return websocket_relay_secret_generator('foobar', set_broadcast_websocket_secret=True)
async def test_authorized(self, websocket_server_generator, websocket_relay_secret):
server = websocket_server_generator('/websocket/relay/')
server.scope['headers'] = (websocket_relay_secret,)
connected, _ = await server.connect()
assert connected is True
async def test_not_authorized(self, websocket_server_generator):
server = websocket_server_generator('/websocket/relay/')
connected, _ = await server.connect()
assert connected is False, "Connection to the relay websocket without auth. We expected the client to be denied."
async def test_wrong_secret(self, websocket_server_generator, websocket_relay_secret_generator):
server = websocket_server_generator('/websocket/relay/')
server.scope['headers'] = (websocket_relay_secret_generator('foobar', set_broadcast_websocket_secret=False),)
connected, _ = await server.connect()
assert connected is False
@pytest.mark.asyncio
@pytest.mark.django_db
class TestWebsocketEventConsumer:
async def test_unauthorized_anonymous(self, websocket_server_generator):
server = websocket_server_generator('/websocket/')
server.scope['user'] = AnonymousUser()
connected, _ = await server.connect()
assert connected is False, "Anonymous user should NOT be allowed to login."
@pytest.mark.skip(reason="Ran out of coding time.")
async def test_authorized(self, websocket_server_generator, application, admin):
server = websocket_server_generator('/websocket/')
"""
I ran out of time. Here is what I was thinking ...
Inject a valid session into the cookies in the header
server.scope['headers'] = (
(b'cookie', ...),
)
"""
connected, _ = await server.connect()
assert connected is True, "User should be allowed in via cookies auth via a session key in the cookies"

View File

@@ -216,42 +216,54 @@
- block:
- name: Fetch galaxy roles from roles/requirements.(yml/yaml)
ansible.builtin.command:
cmd: "ansible-galaxy role install -r {{ item }} {{ verbosity }}"
cmd: "ansible-galaxy role install -r {{ req_file }} {{ verbosity }}"
register: galaxy_result
with_fileglob:
- "{{ project_path | quote }}/roles/requirements.yaml"
- "{{ project_path | quote }}/roles/requirements.yml"
vars:
req_file: "{{ lookup('ansible.builtin.first_found', req_candidates, skip=True) }}"
req_candidates:
- "{{ project_path | quote }}/roles/requirements.yml"
- "{{ project_path | quote }}/roles/requirements.yaml"
changed_when: "'was installed successfully' in galaxy_result.stdout"
when: roles_enabled | bool
when:
- roles_enabled | bool
- req_file
tags:
- install_roles
- name: Fetch galaxy collections from collections/requirements.(yml/yaml)
ansible.builtin.command:
cmd: "ansible-galaxy collection install -r {{ item }} {{ verbosity }}"
cmd: "ansible-galaxy collection install -r {{ req_file }} {{ verbosity }}"
register: galaxy_collection_result
with_fileglob:
- "{{ project_path | quote }}/collections/requirements.yaml"
- "{{ project_path | quote }}/collections/requirements.yml"
vars:
req_file: "{{ lookup('ansible.builtin.first_found', req_candidates, skip=True) }}"
req_candidates:
- "{{ project_path | quote }}/collections/requirements.yml"
- "{{ project_path | quote }}/collections/requirements.yaml"
- "{{ project_path | quote }}/requirements.yml"
- "{{ project_path | quote }}/requirements.yaml"
changed_when: "'Nothing to do.' not in galaxy_collection_result.stdout"
when:
- "ansible_version.full is version_compare('2.9', '>=')"
- collections_enabled | bool
- req_file
tags:
- install_collections
- name: Fetch galaxy roles and collections from requirements.(yml/yaml)
ansible.builtin.command:
cmd: "ansible-galaxy install -r {{ item }} {{ verbosity }}"
cmd: "ansible-galaxy install -r {{ req_file }} {{ verbosity }}"
register: galaxy_combined_result
with_fileglob:
- "{{ project_path | quote }}/requirements.yaml"
- "{{ project_path | quote }}/requirements.yml"
vars:
req_file: "{{ lookup('ansible.builtin.first_found', req_candidates, skip=True) }}"
req_candidates:
- "{{ project_path | quote }}/requirements.yaml"
- "{{ project_path | quote }}/requirements.yml"
changed_when: "'Nothing to do.' not in galaxy_combined_result.stdout"
when:
- "ansible_version.full is version_compare('2.10', '>=')"
- collections_enabled | bool
- roles_enabled | bool
- req_file
tags:
- install_collections
- install_roles

View File

@@ -353,6 +353,7 @@ INSTALLED_APPS = [
'awx.sso',
'solo',
'ansible_base.rest_filters',
'ansible_base.jwt_consumer',
]
INTERNAL_IPS = ('127.0.0.1',)
@@ -362,6 +363,7 @@ REST_FRAMEWORK = {
'DEFAULT_PAGINATION_CLASS': 'awx.api.pagination.Pagination',
'PAGE_SIZE': 25,
'DEFAULT_AUTHENTICATION_CLASSES': (
'ansible_base.jwt_consumer.awx.auth.AwxJWTAuthentication',
'awx.api.authentication.LoggedOAuth2Authentication',
'awx.api.authentication.SessionAuthentication',
'awx.api.authentication.LoggedBasicAuthentication',

View File

@@ -6,6 +6,7 @@ import sortErrorMessages from './sortErrorMessages';
function FormSubmitError({ error }) {
const [errorMessage, setErrorMessage] = useState(null);
const [fieldError, setFieldsMessage] = useState(null);
const { values, setErrors } = useFormikContext();
useEffect(() => {
@@ -15,6 +16,7 @@ function FormSubmitError({ error }) {
}
if (fieldErrors) {
setErrors(fieldErrors);
setFieldsMessage(fieldErrors);
}
}, [error, setErrors, values]);
@@ -30,10 +32,34 @@ function FormSubmitError({ error }) {
ouiaId="form-submit-error-alert"
title={
Array.isArray(errorMessage)
? errorMessage.map((msg) => <div key={msg}>{msg}</div>)
: errorMessage
? errorMessage.map((msg) => (
<div key={msg}>
{msg.messages ? msg.messages : JSON.stringify(msg)}
</div>
))
: errorMessage && (
<div>
{errorMessage.messages
? errorMessage.messages
: JSON.stringify(errorMessage)}
</div>
)
}
/>
>
{Array.isArray(fieldError)
? fieldError.map((msg) => (
<div key={msg}>
{msg.messages ? msg.messages : JSON.stringify(msg)}
</div>
))
: fieldError && (
<div>
{fieldError.messages
? fieldError.messages
: JSON.stringify(fieldError)}
</div>
)}
</Alert>
</FormFullWidthLayout>
);
}

View File

@@ -5,14 +5,15 @@ import { mountWithContexts } from '../../../testUtils/enzymeHelpers';
import FormSubmitError from './FormSubmitError';
describe('<FormSubmitError>', () => {
test('should render null when no error present', () => {
test('should render null when no error present', async () => {
const wrapper = mountWithContexts(
<Formik>{() => <FormSubmitError error={null} />}</Formik>
);
expect(wrapper.find('FormSubmitError').text()).toEqual('');
const ele = await wrapper.find('FormSubmitError').text();
expect(ele).toEqual('');
});
test('should pass field errors to Formik', () => {
test('should pass field errors to Formik', async () => {
const error = {
response: {
data: {
@@ -30,26 +31,7 @@ describe('<FormSubmitError>', () => {
)}
</Formik>
);
expect(wrapper.find('p').text()).toEqual('invalid');
});
test('should display error message if field errors not provided', async () => {
const realConsole = global.console;
global.console = {
error: jest.fn(),
};
const error = {
message: 'There was an error',
};
let wrapper;
await act(async () => {
wrapper = mountWithContexts(
<Formik>{() => <FormSubmitError error={error} />}</Formik>
);
});
wrapper.update();
expect(wrapper.find('Alert').prop('title')).toEqual('There was an error');
expect(global.console.error).toHaveBeenCalledWith(error);
global.console = realConsole;
const pp = await wrapper.find('p').text();
expect(pp).toEqual('invalid');
});
});

View File

@@ -9,15 +9,18 @@ export default function sortErrorMessages(error, formValues = {}) {
Object.keys(error.response.data).length > 0
) {
const parsed = parseFieldErrors(error.response.data, formValues);
return {
formError: parsed.formErrors.join('; '),
formError:
parsed.formErrors.indexOf(';') > -1
? parsed.formErrors.join('; ')
: 'Error in fields',
fieldErrors: Object.keys(parsed.fieldErrors).length
? parsed.fieldErrors
: null,
};
}
/* eslint-disable-next-line no-console */
console.error(error);
return {
formError: error.message,
fieldErrors: null,

View File

@@ -35,7 +35,7 @@ describe('sortErrorMessages', () => {
};
const parsed = sortErrorMessages(error, { foo: '', baz: '' });
expect(parsed).toEqual({
formError: '',
formError: 'Error in fields',
fieldErrors: {
foo: 'bar',
baz: 'bam',
@@ -54,7 +54,7 @@ describe('sortErrorMessages', () => {
};
const parsed = sortErrorMessages(error, { foo: '', baz: '' });
expect(parsed).toEqual({
formError: 'oopsie',
formError: 'Error in fields',
fieldErrors: {
baz: 'bam',
},
@@ -72,7 +72,7 @@ describe('sortErrorMessages', () => {
};
const parsed = sortErrorMessages(error, { foo: '', baz: '' });
expect(parsed).toEqual({
formError: '',
formError: 'Error in fields',
fieldErrors: {
foo: 'bar; bar2',
baz: 'bam',
@@ -103,7 +103,7 @@ describe('sortErrorMessages', () => {
};
const parsed = sortErrorMessages(error, formValues);
expect(parsed).toEqual({
formError: '',
formError: 'Error in fields',
fieldErrors: {
inputs: {
url: 'URL Error',
@@ -135,7 +135,7 @@ describe('sortErrorMessages', () => {
};
const parsed = sortErrorMessages(error, formValues);
expect(parsed).toEqual({
formError: 'Other stuff error',
formError: 'Error in fields',
fieldErrors: {
inputs: {
url: 'URL Error',

View File

@@ -257,12 +257,17 @@ function PromptDetail({
numChips={5}
ouiaId="prompt-job-tag-chips"
totalChips={
!overrides.job_tags || overrides.job_tags === ''
overrides.job_tags === undefined ||
overrides.job_tags === null ||
overrides.job_tags === ''
? 0
: overrides.job_tags.split(',').length
}
>
{overrides.job_tags.length > 0 &&
{overrides.job_tags !== undefined &&
overrides.job_tags !== null &&
overrides.job_tags !== '' &&
overrides.job_tags.length > 0 &&
overrides.job_tags.split(',').map((jobTag) => (
<Chip
key={jobTag}
@@ -284,13 +289,18 @@ function PromptDetail({
<ChipGroup
numChips={5}
totalChips={
!overrides.skip_tags || overrides.skip_tags === ''
overrides.skip_tags === undefined ||
overrides.skip_tags === null ||
overrides.skip_tags === ''
? 0
: overrides.skip_tags.split(',').length
}
ouiaId="prompt-skip-tag-chips"
>
{overrides.skip_tags.length > 0 &&
{overrides.skip_tags !== undefined &&
overrides.skip_tags !== null &&
overrides.skip_tags !== '' &&
overrides.skip_tags.length > 0 &&
overrides.skip_tags.split(',').map((skipTag) => (
<Chip
key={skipTag}

View File

@@ -115,8 +115,11 @@ function SessionProvider({ children }) {
}, [setSessionTimeout, setSessionCountdown]);
useEffect(() => {
const isRedirectCondition = (location, histLength) =>
location.pathname === '/login' && histLength === 2;
const unlisten = history.listen((location, action) => {
if (action === 'POP') {
if (action === 'POP' || isRedirectCondition(location, history.length)) {
setIsRedirectLinkReceived(true);
}
});

View File

@@ -784,7 +784,7 @@ msgstr "Branche à utiliser dans lexécution de la tâche. Projet par défaut
#: screens/Inventory/shared/Inventory.helptext.js:155
msgid "Branch to use on inventory sync. Project default used if blank. Only allowed if project allow_override field is set to true."
msgstr ""
msgstr "Branche à utiliser pour la synchronisation de l'inventaire. La valeur par défaut du projet est utilisée si elle est vide. Cette option n'est autorisée que si le champ allow_override du projet est défini sur vrai."
#: components/About/About.js:45
msgid "Brand Image"
@@ -2832,7 +2832,7 @@ msgstr "Entrez les variables avec la syntaxe JSON ou YAML. Consultez la documen
#: screens/Inventory/shared/SmartInventoryForm.js:94
msgid "Enter inventory variables using either JSON or YAML syntax. Use the radio button to toggle between the two. Refer to the Ansible Controller documentation for example syntax."
msgstr ""
msgstr "Entrez les variables d'inventaire en utilisant la syntaxe JSON ou YAML. Utilisez le bouton d'option pour basculer entre les deux. Référez-vous à la documentation du contrôleur Ansible pour les exemples de syntaxe."
#: screens/CredentialType/CredentialTypeDetails/CredentialTypeDetails.js:87
msgid "Environment variables or extra variables that specify the values a credential type can inject."
@@ -3015,7 +3015,7 @@ msgstr "Recherche exacte sur le champ d'identification."
#: components/Search/RelatedLookupTypeInput.js:38
msgid "Exact search on name field."
msgstr ""
msgstr "Recherche exacte sur le champ nom."
#: screens/Project/shared/Project.helptext.js:23
msgid "Example URLs for GIT Source Control include:"
@@ -3242,7 +3242,7 @@ msgstr "Jobs ayant échoué"
#: screens/WorkflowApproval/WorkflowApprovalList/WorkflowApprovalList.js:262
msgid "Failed to approve one or more workflow approval."
msgstr ""
msgstr "Échec de l'approbation d'une ou plusieurs validations de flux de travail."
#: screens/WorkflowApproval/shared/WorkflowApprovalButton.js:56
msgid "Failed to approve {0}."
@@ -3474,7 +3474,7 @@ msgstr "N'a pas réussi à supprimer {name}."
#: screens/WorkflowApproval/WorkflowApprovalList/WorkflowApprovalList.js:263
msgid "Failed to deny one or more workflow approval."
msgstr ""
msgstr "Échec du refus d'une ou plusieurs validations de flux de travail."
#: screens/WorkflowApproval/shared/WorkflowDenyButton.js:51
msgid "Failed to deny {0}."
@@ -3520,7 +3520,7 @@ msgstr "Echec du lancement du Job."
#: screens/Inventory/InventoryHosts/InventoryHostItem.js:121
msgid "Failed to load related groups."
msgstr ""
msgstr "Impossible de charger les groupes associés."
#: screens/Instances/InstanceDetail/InstanceDetail.js:388
#: screens/Instances/InstanceList/InstanceList.js:266
@@ -3972,12 +3972,12 @@ msgstr "Demande(s) de bilan de santé soumise(s). Veuillez patienter et recharge
#: screens/Instances/InstanceDetail/InstanceDetail.js:234
#: screens/Instances/InstanceList/InstanceListItem.js:242
msgid "Health checks are asynchronous tasks. See the"
msgstr ""
msgstr "Les bilans de santé sont des tâches asynchrones. Veuillez consulter la documentation pour plus d'informations."
#: screens/InstanceGroup/Instances/InstanceList.js:286
#: screens/Instances/InstanceList/InstanceList.js:219
msgid "Health checks can only be run on execution nodes."
msgstr ""
msgstr "Les bilans de santé ne peuvent être exécutées que sur les nœuds d'exécution."
#: components/StatusLabel/StatusLabel.js:42
msgid "Healthy"
@@ -5048,7 +5048,7 @@ msgstr "Lancer"
#: components/TemplateList/TemplateListItem.js:214
msgid "Launch Template"
msgstr "Lacer le modèle."
msgstr "Lancer le modèle."
#: screens/ManagementJob/ManagementJobList/LaunchManagementPrompt.js:32
#: screens/ManagementJob/ManagementJobList/LaunchManagementPrompt.js:34
@@ -9637,7 +9637,7 @@ msgstr "Utilisateur"
#: components/AppContainer/PageHeaderToolbar.js:160
msgid "User Details"
msgstr "Détails de l'erreur"
msgstr "Détails de l'utilisateur"
#: screens/Setting/SettingList.js:121
#: screens/Setting/Settings.js:118

View File

@@ -80,7 +80,7 @@ function Dashboard() {
<Trans>
<p>
<InfoCircleIcon /> A tech preview of the new {brandName} user
interface can be found <a href="/ui_next/dashboard">here</a>.
interface can be found <a href="/ui_next">here</a>.
</p>
</Trans>
</Banner>

View File

@@ -3,6 +3,7 @@ import { Modal, Tab, Tabs, TabTitleText } from '@patternfly/react-core';
import PropTypes from 'prop-types';
import { t } from '@lingui/macro';
import { encode } from 'html-entities';
import { jsonToYaml } from 'util/yaml';
import StatusLabel from '../../../components/StatusLabel';
import { DetailList, Detail } from '../../../components/DetailList';
import ContentEmpty from '../../../components/ContentEmpty';
@@ -144,9 +145,28 @@ function HostEventModal({ onClose, hostEvent = {}, isOpen = false }) {
<ContentEmpty title={t`No JSON Available`} />
)}
</Tab>
<Tab
eventKey={2}
title={<TabTitleText>{t`YAML`}</TabTitleText>}
aria-label={t`YAML tab`}
ouiaId="yaml-tab"
>
{activeTabKey === 2 && jsonObj ? (
<CodeEditor
mode="javascript"
readOnly
value={jsonToYaml(JSON.stringify(jsonObj))}
onChange={() => {}}
rows={20}
hasErrors={false}
/>
) : (
<ContentEmpty title={t`No YAML Available`} />
)}
</Tab>
{stdOut?.length ? (
<Tab
eventKey={2}
eventKey={3}
title={<TabTitleText>{t`Output`}</TabTitleText>}
aria-label={t`Output tab`}
ouiaId="standard-out-tab"
@@ -163,7 +183,7 @@ function HostEventModal({ onClose, hostEvent = {}, isOpen = false }) {
) : null}
{stdErr?.length ? (
<Tab
eventKey={3}
eventKey={4}
title={<TabTitleText>{t`Standard Error`}</TabTitleText>}
aria-label={t`Standard error tab`}
ouiaId="standard-error-tab"

View File

@@ -2,6 +2,7 @@ import React from 'react';
import { shallow } from 'enzyme';
import { mountWithContexts } from '../../../../testUtils/enzymeHelpers';
import HostEventModal from './HostEventModal';
import { jsonToYaml } from 'util/yaml';
const hostEvent = {
changed: true,
@@ -167,6 +168,8 @@ const jsonValue = `{
]
}`;
const yamlValue = jsonToYaml(jsonValue);
describe('HostEventModal', () => {
test('initially renders successfully', () => {
const wrapper = shallow(
@@ -187,7 +190,7 @@ describe('HostEventModal', () => {
<HostEventModal hostEvent={hostEvent} onClose={() => {}} isOpen />
);
expect(wrapper.find('Tabs Tab').length).toEqual(4);
expect(wrapper.find('Tabs Tab').length).toEqual(5);
});
test('should initially show details tab', () => {
@@ -287,7 +290,7 @@ describe('HostEventModal', () => {
expect(codeEditor.prop('value')).toEqual(jsonValue);
});
test('should display Standard Out tab content on tab click', () => {
test('should display YAML tab content on tab click', () => {
const wrapper = shallow(
<HostEventModal hostEvent={hostEvent} onClose={() => {}} isOpen />
);
@@ -299,6 +302,21 @@ describe('HostEventModal', () => {
const codeEditor = wrapper.find('Tab[eventKey=2] CodeEditor');
expect(codeEditor.prop('mode')).toBe('javascript');
expect(codeEditor.prop('readOnly')).toBe(true);
expect(codeEditor.prop('value')).toEqual(yamlValue);
});
test('should display Standard Out tab content on tab click', () => {
const wrapper = shallow(
<HostEventModal hostEvent={hostEvent} onClose={() => {}} isOpen />
);
const handleTabClick = wrapper.find('Tabs').prop('onSelect');
handleTabClick(null, 3);
wrapper.update();
const codeEditor = wrapper.find('Tab[eventKey=3] CodeEditor');
expect(codeEditor.prop('mode')).toBe('javascript');
expect(codeEditor.prop('readOnly')).toBe(true);
expect(codeEditor.prop('value')).toEqual(hostEvent.event_data.res.stdout);
});
@@ -316,10 +334,10 @@ describe('HostEventModal', () => {
);
const handleTabClick = wrapper.find('Tabs').prop('onSelect');
handleTabClick(null, 3);
handleTabClick(null, 4);
wrapper.update();
const codeEditor = wrapper.find('Tab[eventKey=3] CodeEditor');
const codeEditor = wrapper.find('Tab[eventKey=4] CodeEditor');
expect(codeEditor.prop('mode')).toBe('javascript');
expect(codeEditor.prop('readOnly')).toBe(true);
expect(codeEditor.prop('value')).toEqual('error content');
@@ -351,10 +369,10 @@ describe('HostEventModal', () => {
);
const handleTabClick = wrapper.find('Tabs').prop('onSelect');
handleTabClick(null, 2);
handleTabClick(null, 3);
wrapper.update();
const codeEditor = wrapper.find('Tab[eventKey=2] CodeEditor');
const codeEditor = wrapper.find('Tab[eventKey=3] CodeEditor');
expect(codeEditor.prop('mode')).toBe('javascript');
expect(codeEditor.prop('readOnly')).toBe(true);
expect(codeEditor.prop('value')).toEqual('foo bar');
@@ -375,10 +393,10 @@ describe('HostEventModal', () => {
);
const handleTabClick = wrapper.find('Tabs').prop('onSelect');
handleTabClick(null, 2);
handleTabClick(null, 3);
wrapper.update();
const codeEditor = wrapper.find('Tab[eventKey=2] CodeEditor');
const codeEditor = wrapper.find('Tab[eventKey=3] CodeEditor');
expect(codeEditor.prop('mode')).toBe('javascript');
expect(codeEditor.prop('readOnly')).toBe(true);
expect(codeEditor.prop('value')).toEqual('baz\nbar');
@@ -394,10 +412,10 @@ describe('HostEventModal', () => {
);
const handleTabClick = wrapper.find('Tabs').prop('onSelect');
handleTabClick(null, 2);
handleTabClick(null, 3);
wrapper.update();
const codeEditor = wrapper.find('Tab[eventKey=2] CodeEditor');
const codeEditor = wrapper.find('Tab[eventKey=3] CodeEditor');
expect(codeEditor.prop('mode')).toBe('javascript');
expect(codeEditor.prop('readOnly')).toBe(true);
expect(codeEditor.prop('value')).toEqual(

View File

@@ -30,7 +30,7 @@ function SubscriptionUsage() {
<Trans>
<p>
<InfoCircleIcon /> A tech preview of the new {brandName} user
interface can be found <a href="/ui_next/dashboard">here</a>.
interface can be found <a href="/ui_next">here</a>.
</p>
</Trans>
</Banner>

View File

@@ -201,7 +201,11 @@ function NodeViewModal({ readOnly }) {
overrides.limit = originalNodeObject.limit;
}
if (launchConfig.ask_verbosity_on_launch) {
overrides.verbosity = originalNodeObject.verbosity.toString();
overrides.verbosity =
originalNodeObject.verbosity !== undefined &&
originalNodeObject.verbosity !== null
? originalNodeObject.verbosity.toString()
: '0';
}
if (launchConfig.ask_credential_on_launch) {
overrides.credentials = originalNodeCredentials || [];

View File

@@ -35,7 +35,7 @@ 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 && PRODUCT="$(PRODUCT)" PUBLIC_PATH=/static/awx/ npm run build:awx
@cd $(UI_NEXT_DIR)/src && PRODUCT="$(PRODUCT)" PUBLIC_PATH=/static/awx/ ROUTE_PREFIX=/ui_next 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

View File

@@ -18,7 +18,7 @@ documentation: https://github.com/ansible/awx/blob/devel/awx_collection/README.m
homepage: https://www.ansible.com/
issues: https://github.com/ansible/awx/issues?q=is%3Aissue+label%3Acomponent%3Aawx_collection
license:
- GPL-3.0-only
- GPL-3.0-or-later
name: awx
namespace: awx
readme: README.md

View File

@@ -1,119 +0,0 @@
# This code is part of Ansible, but is an independent component.
# This particular file snippet, and this file snippet only, is BSD licensed.
# Modules you write using this snippet, which is embedded dynamically by Ansible
# still belong to the author of the module, and may assign their own license
# to the complete work.
#
# Copyright (c), Wayne Witzel III <wayne@riotousliving.com>
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without modification,
# are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
# IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
# USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
from __future__ import absolute_import, division, print_function
__metaclass__ = type
import os
import traceback
TOWER_CLI_IMP_ERR = None
try:
import tower_cli.utils.exceptions as exc
from tower_cli.utils import parser
from tower_cli.api import client
HAS_TOWER_CLI = True
except ImportError:
TOWER_CLI_IMP_ERR = traceback.format_exc()
HAS_TOWER_CLI = False
from ansible.module_utils.basic import AnsibleModule, missing_required_lib
def tower_auth_config(module):
"""
`tower_auth_config` attempts to load the tower-cli.cfg file
specified from the `tower_config_file` parameter. If found,
if returns the contents of the file as a dictionary, else
it will attempt to fetch values from the module params and
only pass those values that have been set.
"""
config_file = module.params.pop('tower_config_file', None)
if config_file:
if not os.path.exists(config_file):
module.fail_json(msg='file not found: %s' % config_file)
if os.path.isdir(config_file):
module.fail_json(msg='directory can not be used as config file: %s' % config_file)
with open(config_file, 'r') as f:
return parser.string_to_dict(f.read())
else:
auth_config = {}
host = module.params.pop('tower_host', None)
if host:
auth_config['host'] = host
username = module.params.pop('tower_username', None)
if username:
auth_config['username'] = username
password = module.params.pop('tower_password', None)
if password:
auth_config['password'] = password
module.params.pop('tower_verify_ssl', None) # pop alias if used
verify_ssl = module.params.pop('validate_certs', None)
if verify_ssl is not None:
auth_config['verify_ssl'] = verify_ssl
return auth_config
def tower_check_mode(module):
'''Execute check mode logic for Ansible Tower modules'''
if module.check_mode:
try:
result = client.get('/ping').json()
module.exit_json(changed=True, tower_version='{0}'.format(result['version']))
except (exc.ServerError, exc.ConnectionError, exc.BadRequest) as excinfo:
module.fail_json(changed=False, msg='Failed check mode: {0}'.format(excinfo))
class TowerLegacyModule(AnsibleModule):
def __init__(self, argument_spec, **kwargs):
args = dict(
tower_host=dict(),
tower_username=dict(),
tower_password=dict(no_log=True),
validate_certs=dict(type='bool', aliases=['tower_verify_ssl']),
tower_config_file=dict(type='path'),
)
args.update(argument_spec)
kwargs.setdefault('mutually_exclusive', [])
kwargs['mutually_exclusive'].extend(
(
('tower_config_file', 'tower_host'),
('tower_config_file', 'tower_username'),
('tower_config_file', 'tower_password'),
('tower_config_file', 'validate_certs'),
)
)
super().__init__(argument_spec=args, **kwargs)
if not HAS_TOWER_CLI:
self.fail_json(msg=missing_required_lib('ansible-tower-cli'), exception=TOWER_CLI_IMP_ERR)

View File

@@ -181,10 +181,8 @@ def run_module(request, collection_import):
resource_class = resource_module.ControllerAWXKitModule
elif getattr(resource_module, 'ControllerAPIModule', None):
resource_class = resource_module.ControllerAPIModule
elif getattr(resource_module, 'TowerLegacyModule', None):
resource_class = resource_module.TowerLegacyModule
else:
raise RuntimeError("The module has neither a TowerLegacyModule, ControllerAWXKitModule or a ControllerAPIModule")
raise RuntimeError("The module has neither a ControllerAWXKitModule or a ControllerAPIModule")
with mock.patch.object(resource_class, '_load_params', new=mock_load_params):
# Call the test utility (like a mock server) instead of issuing HTTP requests

View File

@@ -26,7 +26,7 @@
name: "{{ project_name }}"
organization: "{{ org_name }}"
scm_type: git
scm_url: https://github.com/ansible/test-playbooks
scm_url: https://github.com/ansible/ansible-tower-samples
wait: true
- name: Create a git project with same name, different org

View File

@@ -31,7 +31,7 @@
name: "{{ project_name1 }}"
organization: Default
scm_type: git
scm_url: https://github.com/ansible/test-playbooks
scm_url: https://github.com/ansible/ansible-tower-samples
wait: true
register: result
@@ -44,7 +44,7 @@
name: "{{ project_name1 }}"
organization: Default
scm_type: git
scm_url: https://github.com/ansible/test-playbooks
scm_url: https://github.com/ansible/ansible-tower-samples
wait: true
state: exists
register: result
@@ -58,7 +58,7 @@
name: "{{ project_name1 }}"
organization: Default
scm_type: git
scm_url: https://github.com/ansible/test-playbooks
scm_url: https://github.com/ansible/ansible-tower-samples
wait: true
state: exists
request_timeout: .001
@@ -75,7 +75,7 @@
name: "{{ project_name1 }}"
organization: Default
scm_type: git
scm_url: https://github.com/ansible/test-playbooks
scm_url: https://github.com/ansible/ansible-tower-samples
wait: true
state: absent
register: result
@@ -89,7 +89,7 @@
name: "{{ project_name1 }}"
organization: Default
scm_type: git
scm_url: https://github.com/ansible/test-playbooks
scm_url: https://github.com/ansible/ansible-tower-samples
wait: true
state: exists
register: result
@@ -103,7 +103,7 @@
name: "{{ project_name1 }}"
organization: Default
scm_type: git
scm_url: https://github.com/ansible/test-playbooks
scm_url: https://github.com/ansible/ansible-tower-samples
wait: false
register: result
ignore_errors: true
@@ -137,7 +137,7 @@
name: "{{ project_name2 }}"
organization: "{{ org_name }}"
scm_type: git
scm_url: https://github.com/ansible/test-playbooks
scm_url: https://github.com/ansible/ansible-tower-samples
scm_credential: "{{ cred_name }}"
check_mode: true
@@ -162,7 +162,7 @@
name: "{{ project_name2 }}"
organization: Non_Existing_Org
scm_type: git
scm_url: https://github.com/ansible/test-playbooks
scm_url: https://github.com/ansible/ansible-tower-samples
scm_credential: "{{ cred_name }}"
register: result
ignore_errors: true
@@ -179,7 +179,7 @@
name: "{{ project_name2 }}"
organization: "{{ org_name }}"
scm_type: git
scm_url: https://github.com/ansible/test-playbooks
scm_url: https://github.com/ansible/ansible-tower-samples
scm_credential: Non_Existing_Credential
register: result
ignore_errors: true
@@ -191,7 +191,7 @@
- "'Non_Existing_Credential' in result.msg"
- "result.total_results == 0"
- name: Create a git project without credentials without waiting
- name: Create a git project using a branch and allowing branch override
project:
name: "{{ project_name3 }}"
organization: Default

View File

@@ -13,7 +13,7 @@
name: "{{ project_name1 }}"
organization: Default
scm_type: git
scm_url: https://github.com/ansible/test-playbooks
scm_url: https://github.com/ansible/ansible-tower-samples
wait: false
register: project_create_result

View File

@@ -19,8 +19,6 @@ homepage: https://www.ansible.com/
issues: https://github.com/ansible/awx/issues?q=is%3Aissue+label%3Acomponent%3Aawx_collection
license:
- GPL-3.0-or-later
# plugins/module_utils/tower_legacy.py
- BSD-2-Clause
name: {{ collection_package }}
namespace: {{ collection_namespace }}
readme: README.md

View File

@@ -96,6 +96,7 @@ credential_type_name_to_config_kind_map = {
'vault': 'vault',
'vmware vcenter': 'vmware',
'gpg public key': 'gpg_public_key',
'terraform backend configuration': 'terraform',
}
config_kind_to_credential_type_name_map = {kind: name for name, kind in credential_type_name_to_config_kind_map.items()}

View File

@@ -51,7 +51,16 @@ class WSClient(object):
# Subscription group types
def __init__(
self, token=None, hostname='', port=443, secure=True, session_id=None, csrftoken=None, add_received_time=False, session_cookie_name='awx_sessionid'
self,
token=None,
hostname='',
port=443,
secure=True,
ws_suffix='websocket/',
session_id=None,
csrftoken=None,
add_received_time=False,
session_cookie_name='awx_sessionid',
):
# delay this import, because this is an optional dependency
import websocket
@@ -68,6 +77,7 @@ class WSClient(object):
hostname = result.hostname
self.port = port
self.suffix = ws_suffix
self._use_ssl = secure
self.hostname = hostname
self.token = token
@@ -85,7 +95,7 @@ class WSClient(object):
else:
auth_cookie = ''
pref = 'wss://' if self._use_ssl else 'ws://'
url = '{0}{1.hostname}:{1.port}/websocket/'.format(pref, self)
url = '{0}{1.hostname}:{1.port}/{1.suffix}'.format(pref, self)
self.ws = websocket.WebSocketApp(
url, on_open=self._on_open, on_message=self._on_message, on_error=self._on_error, on_close=self._on_close, cookie=auth_cookie
)

View File

@@ -17,6 +17,11 @@ def test_explicit_hostname():
assert client.token == "token"
def test_websocket_suffix():
client = WSClient("token", "hostname", 566, ws_suffix='my-websocket/')
assert client.suffix == 'my-websocket/'
@pytest.mark.parametrize(
'url, result',
[

View File

@@ -13,7 +13,7 @@ Scaling your mesh is only available on Openshift and Kubernetes (K8S) deployment
Instances serve as nodes in your mesh topology. Automation mesh allows you to extend the footprint of your automation. Where you launch a job and where the ``ansible-playbook`` runs can be in different locations.
.. image:: ../common/images/instances_mesh_concept.png
.. image:: ../common/images/instances_mesh_concept.drawio.png
:alt: Site A pointing to Site B and dotted arrows to two hosts from Site B
Automation mesh is useful for:
@@ -23,7 +23,7 @@ Automation mesh is useful for:
The nodes (control, hop, and execution instances) are interconnected via receptor, forming a virtual mesh.
.. image:: ../common/images/instances_mesh_concept_with_nodes.png
.. image:: ../common/images/instances_mesh_concept_with_nodes.drawio.png
:alt: Control node pointing to hop node, which is pointing to two execution nodes.
@@ -78,11 +78,11 @@ Hop nodes can be added to sit between the control plane of AWX and standalone ex
Below is an example of an AWX task pod with two execution nodes. Traffic to execution node 2 flows through a hop node that is setup between it and the control plane.
.. image:: ../common/images/instances_awx_task_pods_hopnode.png
.. image:: ../common/images/instances_awx_task_pods_hopnode.drawio.png
:alt: AWX task pod with a hop node between the control plane of AWX and standalone execution nodes.
An example of a simple topology may look like the following:
Below are sample values used to configure each node in a simple topology:
.. list-table::
:widths: 20 30 10 20 15
@@ -95,9 +95,9 @@ An example of a simple topology may look like the following:
- Peers
* - Control plane
- awx-task-65d6d96987-mgn9j
- 27199
- True
- []
- n/a
- n/a
- [hop node]
* - Hop node
- awx-hop-node
- 27199
@@ -107,7 +107,7 @@ An example of a simple topology may look like the following:
- awx-example.com
- n/a
- False
- ["hop node"]
- [hop node]
@@ -117,11 +117,11 @@ Mesh topology
Mesh ingress is a feature that allows remote nodes to connect inbound to the control plane. This is especially useful when creating remote nodes in restricted networking environments that disallow inbound traffic.
.. image:: ../common/images/instances_mesh_ingress_topology.png
.. image:: ../common/images/instances_mesh_ingress_topology.drawio.png
:alt: Mesh ingress architecture showing the peering relationship between nodes.
An example of a topology that uses mesh ingress may look like the following:
Below are sample values used to configure each node in a mesh ingress topology:
.. list-table::
:widths: 20 30 10 20 15
@@ -133,12 +133,12 @@ An example of a topology that uses mesh ingress may look like the following:
- Peers from control nodes
- Peers
* - Control plane
- awx-task-xyz
- 27199
- True
- []
- awx-task-65d6d96987-mgn9j
- n/a
- n/a
- [hop node]
* - Hop node
- awx-hop-node
- awx-mesh-ingress-1
- 27199
- True
- []
@@ -146,13 +146,14 @@ An example of a topology that uses mesh ingress may look like the following:
- awx-example.com
- n/a
- False
- ["hop node"]
- [hop node]
In order to create a mesh ingress for AWX, see the `Mesh Ingress <https://ansible.readthedocs.io/projects/awx-operator/en/latest/user-guide/advanced-configuration/mesh-ingress.html>`_ chapter of the AWX Operator Documentation for information on setting up this type of topology. The last step is to create a remote execution node and add the execution node to an instance group in order for it to be used in your job execution. Whatever execution environment image used to run a playbook needs to be accessible for your remote execution node. Everything you are using in your playbook also needs to be accessible from this remote execution node.
.. image:: ../common/images/instances-job-template-using-remote-execution-ig.png
:alt: Job template using the instance group with the execution node to run jobs.
:alt: Job template using the instance group with the execution node to run jobs.
:width: 1400px
.. _ag_instances_add:
@@ -167,7 +168,8 @@ To create an instance in AWX:
2. In the Instances list view, click the **Add** button and the Create new Instance window opens.
.. image:: ../common/images/instances_create_new.png
:alt: Create a new instance form.
:alt: Create a new instance form.
:width: 1400px
An instance has several attributes that may be configured:
@@ -189,7 +191,8 @@ An instance has several attributes that may be configured:
Upon successful creation, the Details of the one of the created instances opens.
.. image:: ../common/images/instances_create_details.png
:alt: Details of the newly created instance.
:alt: Details of the newly created instance.
:width: 1400px
.. note::
@@ -198,7 +201,8 @@ Upon successful creation, the Details of the one of the created instances opens.
4. Click the download button next to the **Install Bundle** field to download the tarball that contain files to allow AWX to make proper TCP connections to the remote machine.
.. image:: ../common/images/instances_install_bundle.png
:alt: Instance details showing the Download button in the Install Bundle field of the Details tab.
:alt: Instance details showing the Download button in the Install Bundle field of the Details tab.
:width: 1400px
5. Extract the downloaded ``tar.gz`` file from the location you downloaded it. The install bundle contains TLS certificates and keys, a certificate authority, and a proper Receptor configuration file. To facilitate that these files will be in the right location on the remote machine, the install bundle includes an ``install_receptor.yml`` playbook. The playbook requires the Receptor collection which can be obtained via:
@@ -236,12 +240,14 @@ Wait a few minutes for the periodic AWX task to do a health check against the ne
9. To view other instances within the same topology or associate peers, click the **Peers** tab.
.. image:: ../common/images/instances_peers_tab.png
:alt: "Peers" tab showing two peers.
:alt: "Peers" tab showing two peers.
:width: 1400px
To associate peers with your node, click the **Associate** button to open a dialog box of instances eligible for peering.
.. image:: ../common/images/instances_associate_peer.png
:alt: Instances available to peer with the example hop node.
:alt: Instances available to peer with the example hop node.
:width: 1400px
Execution nodes can peer with either hop nodes or other execution nodes. Hop nodes can only peer with execution nodes unless you check the **Peers from control nodes** check box from the **Options** field.
@@ -249,8 +255,8 @@ Execution nodes can peer with either hop nodes or other execution nodes. Hop nod
If you associate or disassociate a peer, a notification will inform you to re-run the install bundle from the Peer Detail view (the :ref:`ag_topology_viewer` has the download link).
.. image:: ../common/images/instances_associate_peer_reinstallmsg.png
:alt: Notification to re-run the installation bundle due to change in the peering.
.. image:: ../common/images/instances_associate_peer_reinstallmsg.png
:alt: Notification to re-run the installation bundle due to change in the peering.
You can remove an instance by clicking **Remove** in the Instances page, or by setting the instance ``node_state = deprovisioning`` via the API. Upon deleting, a pop-up message will appear to notify that you may need to re-run the install bundle to make sure things that were removed are no longer connected.
@@ -264,7 +270,8 @@ Manage instances
Click **Instances** from the left side navigation menu to access the Instances list.
.. image:: ../common/images/instances_list_view.png
:alt: List view of instances in AWX
:alt: List view of instances in AWX
:width: 1400px
The Instances list displays all the current nodes in your topology, along with relevant details:
@@ -290,7 +297,9 @@ The Instances list displays all the current nodes in your topology, along with r
From this page, you can add, remove or run health checks on your nodes. Use the check boxes next to an instance to select it to remove or run a health check against. When a button is grayed-out, you do not have permission for that particular action. Contact your Administrator to grant you the required level of access. If you are able to remove an instance, you will receive a prompt for confirmation, like the one below:
.. image:: ../common/images/instances_delete_prompt.png
:alt: Prompt for deleting instances in AWX.
:alt: Prompt for deleting instances in AWX
:width: 1400px
.. note::
@@ -303,7 +312,8 @@ Click **Remove** to confirm.
If running a health check on an instance, at the top of the Details page, a message displays that the health check is in progress.
.. image:: ../common/images/instances_health_check.png
:alt: Health check for instances in AWX
:alt: Health check for instances in AWX
:width: 1400px
Click **Reload** to refresh the instance status.
@@ -311,13 +321,14 @@ Click **Reload** to refresh the instance status.
Health checks are ran asynchronously, and may take up to a minute for the instance status to update, even with a refresh. The status may or may not change after the health check. At the bottom of the Details page, a timer/clock icon displays next to the last known health check date and time stamp if the health check task is currently running.
.. image:: ../common/images/instances_health_check_pending.png
:alt: Health check for instance still in pending state.
.. image:: ../common/images/instances_health_check_pending.png
:alt: Health check for instance still in pending state.
The example health check shows the status updates with an error on node 'one':
.. image:: ../common/images/topology-viewer-instance-with-errors.png
:alt: Health check showing an error in one of the instances.
:alt: Health check showing an error in one of the instances.
:width: 1400px
Using a custom Receptor CA

View File

@@ -7,6 +7,7 @@ Setting up LDAP Authentication
single: LDAP
pair: authentication; LDAP
This chapter describes how to integrate LDAP authentication with AWX.
.. note::

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

View File

Before

Width:  |  Height:  |  Size: 40 KiB

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 70 KiB

After

Width:  |  Height:  |  Size: 123 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 132 KiB

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 55 KiB

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 49 KiB

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 41 KiB

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 53 KiB

After

Width:  |  Height:  |  Size: 172 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 60 KiB

View File

@@ -11,19 +11,13 @@ Release Notes
pair: release notes; v23.3.0
pair: release notes; v23.3.1
pair: release notes; v23.4.0
pair: release notes; v23.5.0
pair: release notes; v23.5.1
pair: release notes; v23.6.0
pair: release notes; v23.7.0
pair: release notes; v23.8.0
pair: release notes; v23.8.1
For versions older than 23.0.0, refer to `AWX Release Notes <https://github.com/ansible/awx/releases>`_.
Refer to `AWX Release Notes <https://github.com/ansible/awx/releases>`_.
- See `What's Changed for 23.4.0 <https://github.com/ansible/awx/releases/tag/23.4.0>`_.
- See `What's Changed for 23.3.1 <https://github.com/ansible/awx/releases/tag/23.3.1>`_.
- See `What's Changed for 23.3.0 <https://github.com/ansible/awx/releases/tag/23.3.0>`_.
- See `What's Changed for 23.2.0 <https://github.com/ansible/awx/releases/tag/23.2.0>`_.
- See `What's Changed for 23.1.0 <https://github.com/ansible/awx/releases/tag/23.1.0>`_.
- See `What's Changed for 23.0.0 <https://github.com/ansible/awx/releases/tag/23.0.0>`_.

View File

@@ -10,14 +10,15 @@ Secret Management System
Users and admins upload machine and cloud credentials so that automation can access machines and external services on their behalf. By default, sensitive credential values (such as SSH passwords, SSH private keys, API tokens for cloud services) are stored in the database after being encrypted. With external credentials backed by credential plugins, you can map credential fields (like a password or an SSH Private key) to values stored in a :term:`secret management system` instead of providing them to AWX directly. AWX provides a secret management system that include integrations for:
- Centrify Vault Credential Provider Lookup
- CyberArk Central Credential Provider Lookup (CCP)
- CyberArk Conjur Secrets Manager Lookup
- HashiCorp Vault Key-Value Store (KV)
- HashiCorp Vault SSH Secrets Engine
- Microsoft Azure Key Management System (KMS)
- Thycotic DevOps Secrets Vault
- Thycotic Secret Server
- :ref:`ug_credentials_aws_lookup`
- :ref:`ug_credentials_centrify`
- :ref:`ug_credentials_cyberarkccp`
- :ref:`ug_credentials_cyberarkconjur`
- :ref:`ug_credentials_hashivault` (KV)
- :ref:`ug_credentials_hashivaultssh`
- :ref:`ug_credentials_azurekeyvault` (KMS)
- :ref:`ug_credentials_thycoticvault`
- :ref:`ug_credentials_thycoticserver`
These external secret values will be fetched prior to running a playbook that needs them. For more information on specifying these credentials in the User Interface, see :ref:`ug_credentials`.
@@ -49,11 +50,92 @@ Use the AWX User Interface to configure and use each of the supported 3-party se
.. image:: ../common/images/credentials-link-credential-prompt.png
:alt: Credential section of the external secret management system dialog
4. Select the credential you want to link to, and click **Next**. This takes you to the **Metadata** tab of the input source. This example shows the Metadata prompt for HashiVault Secret Lookup. Metadata is specific to the input source you select. See the :ref:`ug_metadata_creds_inputs` table for details.
4. Select the credential you want to link to, and click **Next**. This takes you to the **Metadata** tab of the input source. Metadata is specific to the input source you select:
.. list-table::
:widths: 10 10 25
:width: 1400px
:header-rows: 1
* - Input Source
- Metadata
- Description
* - *AWS Secrets Manager*
- AWS Secrets Manager Region (required)
- The region where the secrets manager is located.
* -
- AWS Secret Name (Required)
- Specify the AWS secret name that was generated by the AWS access key.
* - *Centrify Vault Credential Provider Lookup*
- Account Name (Required)
- Name of the system account or domain associated with Centrify Vault.
* -
- System Name
- Specify the name used by the Centrify portal.
* - *CyberArk Central Credential Provider Lookup*
- Object Query (Required)
- Lookup query for the object.
* -
- Object Query Format
- Select ``Exact`` for a specific secret name, or ``Regexp`` for a secret that has a dynamically generated name.
* -
- Object Property
- Specifies the name of the property to return (e.g., ``UserName``, ``Address``, etc.) other than the default of ``Content``.
* -
- Reason
- If required per the object's policy, supply a reason for checking out the secret, as CyberArk logs those.
* - *CyberArk Conjur Secrets Lookup*
- Secret Identifier
- The identifier for the secret.
* -
- Secret Version
- Specify a version of the secret, if necessary, otherwise, leave it empty to use the latest version.
* - *HashiVault Secret Lookup*
- Name of Secret Backend
- Specify the name of the KV backend to use. Leave it blank to use the first path segment of the **Path to Secret** field instead.
* -
- Path to Secret (required)
- Specify the path to where the secret information is stored; for example, ``/path/username``.
* -
- Key Name (required)
- Specify the name of the key to look up the secret information.
* -
- Secret Version (V2 Only)
- Specify a version if necessary, otherwise, leave it empty to use the latest version.
* - *HashiCorp Signed SSH*
- Unsigned Public Key (required)
- Specify the public key of the cert you want to get signed. It needs to be present in the authorized keys file of the target host(s).
* -
- Path to Secret (required)
- Specify the path to where the secret information is stored; for example, ``/path/username``.
* -
- Role Name (required)
- A role is a collection of SSH settings and parameters that are stored in Hashi vault. Typically, you can specify a couple of them with different privileges, timeouts, etc. So you could have a role that is allowed to get a cert signed for root, and other less privileged ones, for example.
* -
- Valid Principals
- Specify a user (or users) other than the default, that you are requesting vault to authorize the cert for the stored key. Hashi vault has a default user for whom it signs (e.g., ec2-user).
* - *Azure KMS*
- Secret Name (required)
- The actual name of the secret as it is referenced in Azure's Key vault app.
* -
- Secret Version
- Specify a version of the secret, if necessary, otherwise, leave it empty to use the latest version.
* - *Thycotic DevOps Secrets Vault*
- Secret Path (required)
- Specify the path to where the secret information is stored (e.g., /path/username).
* - *Thycotic Secret Server*
- Secret ID (required)
- The identifier for the secret.
* -
- Secret Field
- Specify the field to be used from the secret.
This example shows the Metadata prompt for HashiVault Secret Lookup.
.. image:: ../common/images/credentials-link-metadata-prompt.png
:alt: Metadata section of the external secret management system dialog
5. Click **Test** to verify connection to the secret management system. If the lookup is unsuccessful, an error message like this one displays:
.. image:: ../common/images/credentials-link-metadata-test-error.png
@@ -65,133 +147,37 @@ Use the AWX User Interface to configure and use each of the supported 3-party se
8. Click **Save** when done.
.. _ug_metadata_creds_inputs:
Metadata for credential input sources
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
.. _ug_credentials_aws_lookup:
**Centrify Vault Credential Provider Lookup**
AWS Secrets Manager Lookup
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. index::
pair: credential types; AWS
.. list-table::
:widths: 25 50
:header-rows: 1
This plugin allows AWS to be used as a credential input source to pull secrets from AWS SecretsManager. `AWS Secrets Manager <https://aws.amazon.com/secrets-manager/>`_ provides similar service to :ref:`ug_credentials_azurekeyvault`, and the AWS collection provides a lookup plugin for it.
* - Metadata
- Description
* - Account Name (Required)
- Name of the system account or domain associated with Centrify Vault.
* - System Name
- Specify the name used by the Centrify portal.
When **AWS Secrets Manager lookup** is selected for **Credential Type**, provide the following attributes to properly configure your lookup:
**CyberArk Central Credential Provider Lookup**
- **AWS Access Key** (required): provide the access key used for communicating with AWS' key management system
- **AWS Secret Key** (required): provide the secret as obtained by the AWS IAM console
.. list-table::
:widths: 25 50
:header-rows: 1
* - Metadata
- Description
* - Object Query (Required)
- Lookup query for the object.
* - Object Query Format
- Select ``Exact`` for a specific secret name, or ``Regexp`` for a secret that has a dynamically generated name.
* - Object Property
- Specifies the name of the property to return (e.g., ``UserName``, ``Address``, etc.) other than the default of ``Content``.
* - Reason
- If required per the object's policy, supply a reason for checking out the secret, as CyberArk logs those.
Below shows an example of a configured AWS Secret Manager credential.
**CyberArk Conjur Secrets Lookup**
.. image:: ../common/images/credentials-create-aws-secret-credential.png
:width: 1400px
:alt: Example new AWS Secret Manager credential lookup dialog
.. list-table::
:widths: 25 50
:header-rows: 1
* - Metadata
- Description
* - Secret Identifier
- The identifier for the secret.
* - Secret Version
- Specify a version of the secret, if necessary, otherwise, leave it empty to use the latest version.
**HashiVault Secret Lookup**
.. list-table::
:widths: 25 50
:header-rows: 1
* - Metadata
- Description
* - Name of Secret Backend
- Specify the name of the KV backend to use. Leave it blank to use the first path segment of the **Path to Secret** field instead.
* - Path to Secret (required)
- Specify the path to where the secret information is stored; for example, ``/path/username``.
* - Key Name (required)
- Specify the name of the key to look up the secret information.
* - Secret Version (V2 Only)
- Specify a version if necessary, otherwise, leave it empty to use the latest version.
**HashiCorp Signed SSH**
.. list-table::
:widths: 25 50
:header-rows: 1
* - Metadata
- Description
* - Unsigned Public Key (required)
- Specify the public key of the cert you want to get signed. It needs to be present in the authorized keys file of the target host(s).
* - Path to Secret (required)
- Specify the path to where the secret information is stored; for example, ``/path/username``.
* - Role Name (required)
- A role is a collection of SSH settings and parameters that are stored in Hashi vault. Typically, you can specify a couple of them with different privileges, timeouts, etc. So you could have a role that is allowed to get a cert signed for root, and other less privileged ones, for example.
* - Valid Principals
- Specify a user (or users) other than the default, that you are requesting vault to authorize the cert for the stored key. Hashi vault has a default user for whom it signs (e.g., ec2-user).
**Azure KMS**
.. list-table::
:widths: 25 50
:header-rows: 1
* - Metadata
- Description
* - Secret Name (required)
- The actual name of the secret as it is referenced in Azure's Key vault app.
* - Secret Version
- Specify a version of the secret, if necessary, otherwise, leave it empty to use the latest version.
**Thycotic DevOps Secrets Vault**
.. list-table::
:widths: 25 50
:header-rows: 1
* - Metadata
- Description
* - Secret Path (required)
- Specify the path to where the secret information is stored (e.g., /path/username).
**Thycotic Secret Server**
.. list-table::
:widths: 25 50
:header-rows: 1
* - Metadata
- Description
* - Secret ID (required)
- The identifier for the secret.
* - Secret Field
- Specify the field to be used from the secret.
.. _ug_credentials_centrify:
Centrify Vault Credential Provider Lookup
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. index::
pair: credential types; Centrify
You need the Centrify Vault web service running to store secrets in order for this integration to work. When **Centrify Vault Credential Provider Lookup** is selected for **Credential Type**, provide the following metadata to properly configure your lookup:
You need the Centrify Vault web service running to store secrets in order for this integration to work. When **Centrify Vault Credential Provider Lookup** is selected for **Credential Type**, provide the following attributes to properly configure your lookup:
- **Centrify Tenant URL** (required): provide the URL used for communicating with Centrify's secret management system
- **Centrify API User** (required): provide the username
@@ -208,12 +194,12 @@ Below shows an example of a configured CyberArk AIM credential.
.. _ug_credentials_cyberarkccp:
CyberArk Central Credential Provider (CCP) Lookup
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. index::
single: CyberArk CCP
pair: credential; CyberArk CCP
You need the CyberArk Central Credential Provider web service running to store secrets in order for this integration to work. When **CyberArk Central Credential Provider Lookup** is selected for **Credential Type**, provide the following metadata to properly configure your lookup:
You need the CyberArk Central Credential Provider web service running to store secrets in order for this integration to work. When **CyberArk Central Credential Provider Lookup** is selected for **Credential Type**, provide the following attributes to properly configure your lookup:
- **CyberArk CCP URL** (required): provide the URL used for communicating with CyberArk CCP's secret management system; must include URL scheme (http, https, etc.)
- **Web Service ID**: optionally specify the identifier for the web service; leaving it blank defaults to AIMWebService
@@ -230,14 +216,14 @@ Below shows an example of a configured CyberArk CCP credential.
.. _ug_credentials_cyberarkconjur:
CyberArk Conjur Secrets Manager Lookup
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. index::
single: CyberArk Conjur
pair: credential; CyberArk Conjur
With a Conjur Cloud tenant available to target, configure the CyberArk Conjur Secrets Lookup external management system credential plugin as documented.
When **CyberArk Conjur Secrets Manager Lookup** is selected for **Credential Type**, provide the following metadata to properly configure your lookup:
When **CyberArk Conjur Secrets Manager Lookup** is selected for **Credential Type**, provide the following attributes to properly configure your lookup:
- **Conjur URL** (required): provide the URL used for communicating with CyberArk Conjur's secret management system; must include URL scheme (http, https, etc.)
- **API Key** (required): provide the key given by your Conjur admin
@@ -253,12 +239,12 @@ Below shows an example of a configured CyberArk Conjur credential.
.. _ug_credentials_hashivault:
HashiCorp Vault Secret Lookup
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. index::
single: HashiCorp Secret Lookup
pair: credential; HashiCorp KV
When **HashiCorp Vault Secret Lookup** is selected for **Credential Type**, provide the following metadata to properly configure your lookup:
When **HashiCorp Vault Secret Lookup** is selected for **Credential Type**, provide the following attributes to properly configure your lookup:
- **Server URL** (required): provide the URL used for communicating with HashiCorp Vault's secret management system
- **Token**: specify the access token used to authenticate HashiCorp's server
@@ -291,7 +277,7 @@ Below shows an example of a configured HashiCorp Vault Secret Lookup credential
.. image:: ../common/images/credentials-create-hashicorp-kv-credential.png
:alt: Example new HashiCorp Vault Secret lookup dialog
To test the lookup, create another credential that uses the HashiCorp Vault lookup. The example below shows the metadata for a machine credential configured to look up HashiCorp Vault secret credentials:
To test the lookup, create another credential that uses the HashiCorp Vault lookup. The example below shows the attributes for a machine credential configured to look up HashiCorp Vault secret credentials:
.. image:: ../common/images/credentials-machine-test-hashicorp-metadata.png
:alt: Example machine credential lookup metadata for HashiCorp Vault.
@@ -300,12 +286,12 @@ To test the lookup, create another credential that uses the HashiCorp Vault look
.. _ug_credentials_hashivaultssh:
HashiCorp Vault Signed SSH
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. index::
single: HashiCorp SSH Secrets Engine
pair: credential; HashiCorp SSH Secrets Engine
When **HashiCorp Vault Signed SSH** is selected for **Credential Type**, provide the following metadata to properly configure your lookup:
When **HashiCorp Vault Signed SSH** is selected for **Credential Type**, provide the following attributes to properly configure your lookup:
- **Server URL** (required): provide the URL used for communicating with HashiCorp Signed SSH's secret management system
- **Token**: specify the access token used to authenticate HashiCorp's server
@@ -335,13 +321,13 @@ Below shows an example of a configured HashiCorp SSH Secrets Engine credential.
.. _ug_credentials_azurekeyvault:
Microsoft Azure Key Vault
^^^^^^^^^^^^^^^^^^^^^^^^^^^
~~~~~~~~~~~~~~~~~~~~~~~~~~
.. index::
single: MS Azure KMS
pair: credential; MS Azure KMS
triple: credential; Azure; KMS
When **Microsoft Azure Key Vault** is selected for **Credential Type**, provide the following metadata to properly configure your lookup:
When **Microsoft Azure Key Vault** is selected for **Credential Type**, provide the following attributes to properly configure your lookup:
- **Vault URL (DNS Name)** (required): provide the URL used for communicating with MS Azure's key management system
- **Client ID** (required): provide the identifier as obtained by the Azure Active Directory
@@ -357,12 +343,12 @@ Below shows an example of a configured Microsoft Azure KMS credential.
.. _ug_credentials_thycoticvault:
Thycotic DevOps Secrets Vault
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. index::
single: Thycotic DevOps Secrets Vault
pair: credential; Thycotic DevOps Secrets Vault
When **Thycotic DevOps Secrets Vault** is selected for **Credential Type**, provide the following metadata to properly configure your lookup:
When **Thycotic DevOps Secrets Vault** is selected for **Credential Type**, provide the following attributes to properly configure your lookup:
- **Tenant** (required): provide the URL used for communicating with Thycotic's secret management system
- **Top-level Domain (TLD)** : provide the top-level domain designation (e.g., com, edu, org) associated with the secret vault you want to integrate
@@ -379,12 +365,12 @@ Below shows an example of a configured Thycotic DevOps Secrets Vault credential.
.. _ug_credentials_thycoticserver:
Thycotic Secret Server
^^^^^^^^^^^^^^^^^^^^^^^^
~~~~~~~~~~~~~~~~~~~~~~~
.. index::
single: Thycotic Secret Server
pair: credential; Thycotic Secret Server
When **Thycotic Secrets Server** is selected for **Credential Type**, provide the following metadata to properly configure your lookup:
When **Thycotic Secrets Server** is selected for **Credential Type**, provide the following attributes to properly configure your lookup:
- **Secret Server URL** (required): provide the URL used for communicating with the Thycotic Secrets Server management system
- **Username** (required): specify the authenticated user for this service

View File

@@ -128,7 +128,7 @@ The following credential types are supported with AWX:
.. contents::
:local:
The credential types associated with Centrify, CyberArk, HashiCorp Vault, Microsoft Azure Key Management System (KMS), and Thycotic are part of the credential plugins capability that allows an external system to lookup your secrets information. See the :ref:`ug_credential_plugins` section for further detail.
The credential types associated with AWS Secrets Manager, Centrify, CyberArk, HashiCorp Vault, Microsoft Azure Key Management System (KMS), and Thycotic are part of the credential plugins capability that allows an external system to lookup your secrets information. See the :ref:`ug_credential_plugins` section for further detail.
.. _ug_credentials_aws:
@@ -166,6 +166,10 @@ AWX provides support for EC2 STS tokens (sometimes referred to as IAM STS creden
To use implicit IAM role credentials, do not attach AWS cloud credentials in AWX when relying on IAM roles to access the AWS API. While it may seem to make sense to attach your AWS cloud credential to your job template, doing so will force the use of your AWS credentials and will not "fall through" to use your IAM role credentials (this is due to the use of the boto library.)
AWS Secrets Manager
^^^^^^^^^^^^^^^^^^^^^
This is considered part of the secret management capability. See :ref:`ug_credentials_aws_lookup` for more detail.
Ansible Galaxy/Automation Hub API Token
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

BIN
docs/img/pypi_files.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

View File

@@ -124,10 +124,14 @@ This workflow will take the generated images and promote them to quay.io in addi
![Verify release awx.awx collection](img/galaxy.png)
8. Go to awxkit's page on [PiPy](https://pypi.org/project/awxkit/#history) and validate the latest release is there:
8. Go to awxkit's page on [PyPi](https://pypi.org/project/awxkit/#history) and validate the latest release is there:
![Verify awxkit](img/pypi.png)
9. While verifying that awxkit was published on Pypi, also validate that the latest version of the [tar](https://pypi.org/project/awxkit/#files) file is there as well.
![Verify awxkit files](img/pypi_files.png)
### Releasing the AWX operator
Once the AWX image is live, we can now release the AWX operator.

View File

@@ -1,4 +1,4 @@
aiohttp
aiohttp>=3.8.6 # CVE-2023-47627
ansiconv==1.0.0 # UPGRADE BLOCKER: from 2013, consider replacing instead of upgrading
asciichartpy
asn1
@@ -8,7 +8,7 @@ boto3
botocore
channels
channels-redis==3.4.1 # see UPGRADE BLOCKERs
cryptography>=41.0.2 # CVE-2023-38325
cryptography>=41.0.6 # CVE-2023-49083
Cython<3 # this is needed as a build dependency, one day we may have separated build deps
daphne
distro
@@ -26,15 +26,15 @@ django-split-settings==1.0.0 # We hit a strange issue where the release proce
djangorestframework
djangorestframework-yaml
filelock
GitPython>=3.1.32 # CVE-2023-40267
GitPython>=3.1.37 # CVE-2023-41040
hiredis==2.0.0 # see UPGRADE BLOCKERs
irc
jinja2
jinja2>=3.1.3 # CVE-2024-22195
JSON-log-formatter
jsonschema
Markdown # used for formatting API help
openshift
pexpect==4.7.0 # see library notes
pexpect==4.7.0 # see library notes
prometheus_client
psycopg
psutil
@@ -49,20 +49,20 @@ pyyaml>=6.0.1
receptorctl
social-auth-core[openidconnect]==4.4.2 # see UPGRADE BLOCKERs
social-auth-app-django==5.4.0 # see UPGRADE BLOCKERs
sqlparse >= 0.4.4 # Required by django https://github.com/ansible/awx/security/dependabot/96
sqlparse>=0.4.4 # Required by django https://github.com/ansible/awx/security/dependabot/96
redis
requests
slack-sdk
tacacs_plus==1.0 # UPGRADE BLOCKER: auth does not work with later versions
twilio
twisted[tls]
twisted[tls]>=23.10.0 # CVE-2023-46137
uWSGI
uwsgitop
wheel>=0.38.1 # CVE-2022-40898
wheel>=0.38.1 # CVE-2022-40898
pip==21.2.4 # see UPGRADE BLOCKERs
setuptools # see UPGRADE BLOCKERs
setuptools_scm[toml] # see UPGRADE BLOCKERs, xmlsec build dep
setuptools-rust >= 0.11.4 # cryptography build dep
setuptools-rust>=0.11.4 # cryptography build dep
pkgconfig>=1.5.1 # xmlsec build dep - needed for offline build
# Temporarily added to use ansible-runner from git branch, to be removed

View File

@@ -1,6 +1,6 @@
adal==1.2.7
# via msrestazure
aiohttp==3.8.3
aiohttp==3.9.3
# via -r /awx_devel/requirements/requirements.in
aioredis==1.3.1
# via channels-redis
@@ -70,14 +70,12 @@ channels==3.0.5
channels-redis==3.4.1
# via -r /awx_devel/requirements/requirements.in
charset-normalizer==2.1.1
# via
# aiohttp
# requests
# via requests
click==8.1.3
# via receptorctl
constantly==15.1.0
# via twisted
cryptography==41.0.3
cryptography==41.0.7
# via
# -r /awx_devel/requirements/requirements.in
# adal
@@ -163,7 +161,7 @@ frozenlist==1.3.3
# aiosignal
gitdb==4.0.10
# via gitpython
gitpython==3.1.32
gitpython==3.1.42
# via -r /awx_devel/requirements/requirements.in
google-auth==2.14.1
# via kubernetes
@@ -216,7 +214,7 @@ jaraco-text==3.11.0
# via
# irc
# jaraco-collections
jinja2==3.1.2
jinja2==3.1.3
# via -r /awx_devel/requirements/requirements.in
jmespath==1.0.1
# via
@@ -362,7 +360,7 @@ pyyaml==6.0.1
# djangorestframework-yaml
# kubernetes
# receptorctl
receptorctl==1.4.2
receptorctl==1.4.4
# via -r /awx_devel/requirements/requirements.in
redis==4.3.5
# via -r /awx_devel/requirements/requirements.in
@@ -440,7 +438,7 @@ tomli==2.0.1
# via setuptools-scm
twilio==7.15.3
# via -r /awx_devel/requirements/requirements.in
twisted[tls]==22.10.0
twisted[tls]==23.10.0
# via
# -r /awx_devel/requirements/requirements.in
# daphne

View File

@@ -8,6 +8,7 @@ ipython>=7.31.1 # https://github.com/ansible/awx/security/dependabot/30
unittest2
black
pytest!=7.0.0
pytest-asyncio
pytest-cov
pytest-django
pytest-mock==1.11.1

View File

@@ -96,6 +96,7 @@ stdout_logfile=/dev/stdout
stdout_logfile_maxbytes=0
stderr_logfile=/dev/stderr
stderr_logfile_maxbytes=0
[program:awx-rsyslogd]
command = rsyslogd -n -i /var/run/awx-rsyslog/rsyslog.pid -f /var/lib/awx/rsyslog/rsyslog.conf
autorestart = true