mirror of
https://github.com/ansible/awx.git
synced 2026-04-01 08:15:09 -02:30
Compare commits
47 Commits
24.6.1-doc
...
poc-inv-cr
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
21d7ccc3ea | ||
|
|
75b9df3f33 | ||
|
|
6d0c47fdd0 | ||
|
|
54b4acbdfc | ||
|
|
a41766090e | ||
|
|
34fa897dda | ||
|
|
32df114e41 | ||
|
|
018f235a64 | ||
|
|
7e77235d5e | ||
|
|
139d8f0ae2 | ||
|
|
7691365aea | ||
|
|
59f61517d4 | ||
|
|
fa670e2d7f | ||
|
|
a87a044d64 | ||
|
|
381ade1148 | ||
|
|
864a30e3d4 | ||
|
|
5f42db67e6 | ||
|
|
ddf4f288d4 | ||
|
|
e75bc8bc1e | ||
|
|
bb533287b8 | ||
|
|
9979fc659e | ||
|
|
9e5babc093 | ||
|
|
c71e2524ed | ||
|
|
48b4c62186 | ||
|
|
853730acb9 | ||
|
|
f1448fced1 | ||
|
|
7697b6a69b | ||
|
|
22a491c32c | ||
|
|
cbd9dce940 | ||
|
|
a4fdcc1cca | ||
|
|
df95439008 | ||
|
|
acd834df8b | ||
|
|
587f0ecf98 | ||
|
|
5a2091f7bf | ||
|
|
fa7423819a | ||
|
|
fde8af9f11 | ||
|
|
209e7e27b1 | ||
|
|
6c7d29a982 | ||
|
|
282ba36839 | ||
|
|
b727d2c3b3 | ||
|
|
7fc3d5c7c7 | ||
|
|
4e055f46c4 | ||
|
|
f595985b7c | ||
|
|
ea232315bf | ||
|
|
ee251812b5 | ||
|
|
00ba1ea569 | ||
|
|
d91af132c1 |
2
.github/actions/awx_devel_image/action.yml
vendored
2
.github/actions/awx_devel_image/action.yml
vendored
@@ -24,7 +24,7 @@ runs:
|
|||||||
|
|
||||||
- name: Pre-pull latest devel image to warm cache
|
- name: Pre-pull latest devel image to warm cache
|
||||||
shell: bash
|
shell: bash
|
||||||
run: docker pull ghcr.io/${OWNER_LC}/awx_devel:${{ github.base_ref }}
|
run: docker pull -q ghcr.io/${OWNER_LC}/awx_devel:${{ github.base_ref }}
|
||||||
|
|
||||||
- name: Build image for current source checkout
|
- name: Build image for current source checkout
|
||||||
shell: bash
|
shell: bash
|
||||||
|
|||||||
10
.github/actions/run_awx_devel/action.yml
vendored
10
.github/actions/run_awx_devel/action.yml
vendored
@@ -57,16 +57,6 @@ runs:
|
|||||||
awx-manage update_password --username=admin --password=password
|
awx-manage update_password --username=admin --password=password
|
||||||
EOSH
|
EOSH
|
||||||
|
|
||||||
- name: Build UI
|
|
||||||
# This must be a string comparison in composite actions:
|
|
||||||
# https://github.com/actions/runner/issues/2238
|
|
||||||
if: ${{ inputs.build-ui == 'true' }}
|
|
||||||
shell: bash
|
|
||||||
run: |
|
|
||||||
docker exec -i tools_awx_1 sh <<-EOSH
|
|
||||||
make ui-devel
|
|
||||||
EOSH
|
|
||||||
|
|
||||||
- name: Get instance data
|
- name: Get instance data
|
||||||
id: data
|
id: data
|
||||||
shell: bash
|
shell: bash
|
||||||
|
|||||||
26
.github/workflows/ci.yml
vendored
26
.github/workflows/ci.yml
vendored
@@ -38,7 +38,9 @@ jobs:
|
|||||||
- name: ui-test-general
|
- name: ui-test-general
|
||||||
command: make ui-test-general
|
command: make ui-test-general
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
show-progress: false
|
||||||
|
|
||||||
- name: Build awx_devel image for running checks
|
- name: Build awx_devel image for running checks
|
||||||
uses: ./.github/actions/awx_devel_image
|
uses: ./.github/actions/awx_devel_image
|
||||||
@@ -52,7 +54,9 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
timeout-minutes: 60
|
timeout-minutes: 60
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
show-progress: false
|
||||||
|
|
||||||
- uses: ./.github/actions/run_awx_devel
|
- uses: ./.github/actions/run_awx_devel
|
||||||
id: awx
|
id: awx
|
||||||
@@ -70,13 +74,15 @@ jobs:
|
|||||||
DEBUG_OUTPUT_DIR: /tmp/awx_operator_molecule_test
|
DEBUG_OUTPUT_DIR: /tmp/awx_operator_molecule_test
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout awx
|
- name: Checkout awx
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
|
show-progress: false
|
||||||
path: awx
|
path: awx
|
||||||
|
|
||||||
- name: Checkout awx-operator
|
- name: Checkout awx-operator
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
|
show-progress: false\
|
||||||
repository: ansible/awx-operator
|
repository: ansible/awx-operator
|
||||||
path: awx-operator
|
path: awx-operator
|
||||||
|
|
||||||
@@ -130,7 +136,9 @@ jobs:
|
|||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
show-progress: false
|
||||||
|
|
||||||
# The containers that GitHub Actions use have Ansible installed, so upgrade to make sure we have the latest version.
|
# The containers that GitHub Actions use have Ansible installed, so upgrade to make sure we have the latest version.
|
||||||
- name: Upgrade ansible-core
|
- name: Upgrade ansible-core
|
||||||
@@ -154,7 +162,9 @@ jobs:
|
|||||||
- name: r-z0-9
|
- name: r-z0-9
|
||||||
regex: ^[r-z0-9]
|
regex: ^[r-z0-9]
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
show-progress: false
|
||||||
|
|
||||||
- uses: ./.github/actions/run_awx_devel
|
- uses: ./.github/actions/run_awx_devel
|
||||||
id: awx
|
id: awx
|
||||||
@@ -200,7 +210,9 @@ jobs:
|
|||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
show-progress: false
|
||||||
|
|
||||||
- name: Upgrade ansible-core
|
- name: Upgrade ansible-core
|
||||||
run: python3 -m pip install --upgrade ansible-core
|
run: python3 -m pip install --upgrade ansible-core
|
||||||
|
|||||||
57
.github/workflows/dab-release.yml
vendored
Normal file
57
.github/workflows/dab-release.yml
vendored
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
---
|
||||||
|
name: django-ansible-base requirements update
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
schedule:
|
||||||
|
- cron: '0 6 * * *' # once an day @ 6 AM
|
||||||
|
permissions:
|
||||||
|
pull-requests: write
|
||||||
|
contents: write
|
||||||
|
jobs:
|
||||||
|
dab-pin-newest:
|
||||||
|
if: (github.repository_owner == 'ansible' && endsWith(github.repository, 'awx')) || github.event_name != 'schedule'
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- id: dab-release
|
||||||
|
name: Get current django-ansible-base release version
|
||||||
|
uses: pozetroninc/github-action-get-latest-release@2a61c339ea7ef0a336d1daa35ef0cb1418e7676c # v0.8.0
|
||||||
|
with:
|
||||||
|
owner: ansible
|
||||||
|
repo: django-ansible-base
|
||||||
|
excludes: prerelease, draft
|
||||||
|
|
||||||
|
- name: Check out respository code
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- id: dab-pinned
|
||||||
|
name: Get current django-ansible-base pinned version
|
||||||
|
run:
|
||||||
|
echo "version=$(requirements/django-ansible-base-pinned-version.sh)" >> "$GITHUB_OUTPUT"
|
||||||
|
|
||||||
|
- name: Update django-ansible-base pinned version to upstream release
|
||||||
|
run:
|
||||||
|
requirements/django-ansible-base-pinned-version.sh -s ${{ steps.dab-release.outputs.release }}
|
||||||
|
|
||||||
|
- name: Create Pull Request
|
||||||
|
uses: peter-evans/create-pull-request@c5a7806660adbe173f04e3e038b0ccdcd758773c # v6
|
||||||
|
with:
|
||||||
|
base: devel
|
||||||
|
branch: bump-django-ansible-base
|
||||||
|
title: Bump django-ansible-base to ${{ steps.dab-release.outputs.release }}
|
||||||
|
body: |
|
||||||
|
##### SUMMARY
|
||||||
|
Automated .github/workflows/dab-release.yml
|
||||||
|
|
||||||
|
django-ansible-base upstream released version == ${{ steps.dab-release.outputs.release }}
|
||||||
|
requirements_git.txt django-ansible-base pinned version == ${{ steps.dab-pinned.outputs.version }}
|
||||||
|
|
||||||
|
##### ISSUE TYPE
|
||||||
|
- Bug, Docs Fix or other nominal change
|
||||||
|
|
||||||
|
##### COMPONENT NAME
|
||||||
|
- API
|
||||||
|
|
||||||
|
commit-message: |
|
||||||
|
Update django-ansible-base version to ${{ steps.dab-pinned.outputs.version }}
|
||||||
|
add-paths:
|
||||||
|
requirements/requirements_git.txt
|
||||||
12
.github/workflows/devel_images.yml
vendored
12
.github/workflows/devel_images.yml
vendored
@@ -35,7 +35,9 @@ jobs:
|
|||||||
exit 0
|
exit 0
|
||||||
if: matrix.build-targets.image-name == 'awx' && !endsWith(github.repository, '/awx')
|
if: matrix.build-targets.image-name == 'awx' && !endsWith(github.repository, '/awx')
|
||||||
|
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
show-progress: false
|
||||||
|
|
||||||
- name: Set up QEMU
|
- name: Set up QEMU
|
||||||
uses: docker/setup-qemu-action@v3
|
uses: docker/setup-qemu-action@v3
|
||||||
@@ -60,16 +62,14 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u ${{ github.actor }} --password-stdin
|
echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u ${{ github.actor }} --password-stdin
|
||||||
|
|
||||||
- name: Setup node and npm
|
- name: Setup node and npm for the new UI build
|
||||||
uses: actions/setup-node@v2
|
uses: actions/setup-node@v2
|
||||||
with:
|
with:
|
||||||
node-version: '16.13.1'
|
node-version: '18'
|
||||||
if: matrix.build-targets.image-name == 'awx'
|
if: matrix.build-targets.image-name == 'awx'
|
||||||
|
|
||||||
- name: Prebuild UI for awx image (to speed up build process)
|
- name: Prebuild new UI for awx image (to speed up build process)
|
||||||
run: |
|
run: |
|
||||||
sudo apt-get install gettext
|
|
||||||
make ui-release
|
|
||||||
make ui-next
|
make ui-next
|
||||||
if: matrix.build-targets.image-name == 'awx'
|
if: matrix.build-targets.image-name == 'awx'
|
||||||
|
|
||||||
|
|||||||
4
.github/workflows/docs.yml
vendored
4
.github/workflows/docs.yml
vendored
@@ -8,7 +8,9 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
timeout-minutes: 30
|
timeout-minutes: 30
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
show-progress: false
|
||||||
|
|
||||||
- name: install tox
|
- name: install tox
|
||||||
run: pip install tox
|
run: pip install tox
|
||||||
|
|||||||
5
.github/workflows/label_issue.yml
vendored
5
.github/workflows/label_issue.yml
vendored
@@ -30,7 +30,10 @@ jobs:
|
|||||||
timeout-minutes: 20
|
timeout-minutes: 20
|
||||||
name: Label Issue - Community
|
name: Label Issue - Community
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
show-progress: false
|
||||||
|
|
||||||
- uses: actions/setup-python@v4
|
- uses: actions/setup-python@v4
|
||||||
- name: Install python requests
|
- name: Install python requests
|
||||||
run: pip install requests
|
run: pip install requests
|
||||||
|
|||||||
5
.github/workflows/label_pr.yml
vendored
5
.github/workflows/label_pr.yml
vendored
@@ -29,7 +29,10 @@ jobs:
|
|||||||
timeout-minutes: 20
|
timeout-minutes: 20
|
||||||
name: Label PR - Community
|
name: Label PR - Community
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
show-progress: false
|
||||||
|
|
||||||
- uses: actions/setup-python@v4
|
- uses: actions/setup-python@v4
|
||||||
- name: Install python requests
|
- name: Install python requests
|
||||||
run: pip install requests
|
run: pip install requests
|
||||||
|
|||||||
4
.github/workflows/promote.yml
vendored
4
.github/workflows/promote.yml
vendored
@@ -32,7 +32,9 @@ jobs:
|
|||||||
echo "TAG_NAME=${{ github.event.release.tag_name }}" >> $GITHUB_ENV
|
echo "TAG_NAME=${{ github.event.release.tag_name }}" >> $GITHUB_ENV
|
||||||
|
|
||||||
- name: Checkout awx
|
- name: Checkout awx
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
show-progress: false
|
||||||
|
|
||||||
- name: Get python version from Makefile
|
- name: Get python version from Makefile
|
||||||
run: echo py_version=`make PYTHON_VERSION` >> $GITHUB_ENV
|
run: echo py_version=`make PYTHON_VERSION` >> $GITHUB_ENV
|
||||||
|
|||||||
26
.github/workflows/stage.yml
vendored
26
.github/workflows/stage.yml
vendored
@@ -45,19 +45,22 @@ jobs:
|
|||||||
exit 0
|
exit 0
|
||||||
|
|
||||||
- name: Checkout awx
|
- name: Checkout awx
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
|
show-progress: false
|
||||||
path: awx
|
path: awx
|
||||||
|
|
||||||
- name: Checkout awx-operator
|
- name: Checkout awx-operator
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
|
show-progress: false
|
||||||
repository: ${{ github.repository_owner }}/awx-operator
|
repository: ${{ github.repository_owner }}/awx-operator
|
||||||
path: awx-operator
|
path: awx-operator
|
||||||
|
|
||||||
- name: Checkout awx-logos
|
- name: Checkout awx-logos
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
|
show-progress: false
|
||||||
repository: ansible/awx-logos
|
repository: ansible/awx-logos
|
||||||
path: awx-logos
|
path: awx-logos
|
||||||
|
|
||||||
@@ -86,17 +89,14 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
cp ../awx-logos/awx/ui/client/assets/* awx/ui/public/static/media/
|
cp ../awx-logos/awx/ui/client/assets/* awx/ui/public/static/media/
|
||||||
|
|
||||||
- name: Setup node and npm
|
- name: Setup node and npm for new UI build
|
||||||
uses: actions/setup-node@v2
|
uses: actions/setup-node@v2
|
||||||
with:
|
with:
|
||||||
node-version: '16.13.1'
|
node-version: '18'
|
||||||
|
|
||||||
- name: Prebuild UI for awx image (to speed up build process)
|
- name: Prebuild new UI for awx image (to speed up build process)
|
||||||
working-directory: awx
|
working-directory: awx
|
||||||
run: |
|
run: make ui-next
|
||||||
sudo apt-get install gettext
|
|
||||||
make ui-release
|
|
||||||
make ui-next
|
|
||||||
|
|
||||||
- name: Set build env variables
|
- name: Set build env variables
|
||||||
run: |
|
run: |
|
||||||
@@ -136,9 +136,9 @@ jobs:
|
|||||||
- name: Pulling images for test deployment with awx-operator
|
- name: Pulling images for test deployment with awx-operator
|
||||||
# awx operator molecue test expect to kind load image and buildx exports image to registry and not local
|
# awx operator molecue test expect to kind load image and buildx exports image to registry and not local
|
||||||
run: |
|
run: |
|
||||||
docker pull ${AWX_OPERATOR_TEST_IMAGE}
|
docker pull -q ${AWX_OPERATOR_TEST_IMAGE}
|
||||||
docker pull ${AWX_EE_TEST_IMAGE}
|
docker pull -q ${AWX_EE_TEST_IMAGE}
|
||||||
docker pull ${AWX_TEST_IMAGE}:${AWX_TEST_VERSION}
|
docker pull -q ${AWX_TEST_IMAGE}:${AWX_TEST_VERSION}
|
||||||
|
|
||||||
- name: Run test deployment with awx-operator
|
- name: Run test deployment with awx-operator
|
||||||
working-directory: awx-operator
|
working-directory: awx-operator
|
||||||
|
|||||||
4
.github/workflows/update_dependabot_prs.yml
vendored
4
.github/workflows/update_dependabot_prs.yml
vendored
@@ -13,7 +13,9 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout branch
|
- name: Checkout branch
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
show-progress: false
|
||||||
|
|
||||||
- name: Update PR Body
|
- name: Update PR Body
|
||||||
env:
|
env:
|
||||||
|
|||||||
6
.github/workflows/upload_schema.yml
vendored
6
.github/workflows/upload_schema.yml
vendored
@@ -18,7 +18,9 @@ jobs:
|
|||||||
packages: write
|
packages: write
|
||||||
contents: read
|
contents: read
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
show-progress: false
|
||||||
|
|
||||||
- name: Get python version from Makefile
|
- name: Get python version from Makefile
|
||||||
run: echo py_version=`make PYTHON_VERSION` >> $GITHUB_ENV
|
run: echo py_version=`make PYTHON_VERSION` >> $GITHUB_ENV
|
||||||
@@ -34,7 +36,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Pre-pull image to warm build cache
|
- name: Pre-pull image to warm build cache
|
||||||
run: |
|
run: |
|
||||||
docker pull ghcr.io/${{ github.repository_owner }}/awx_devel:${GITHUB_REF##*/} || :
|
docker pull -q ghcr.io/${{ github.repository_owner }}/awx_devel:${GITHUB_REF##*/} || :
|
||||||
|
|
||||||
- name: Build image
|
- name: Build image
|
||||||
run: |
|
run: |
|
||||||
|
|||||||
@@ -67,7 +67,7 @@ If you're not using Docker for Mac, or Docker for Windows, you may need, or choo
|
|||||||
|
|
||||||
#### Frontend Development
|
#### Frontend Development
|
||||||
|
|
||||||
See [the ui development documentation](awx/ui/CONTRIBUTING.md).
|
See [the ansible-ui development documentation](https://github.com/ansible/ansible-ui/blob/main/CONTRIBUTING.md).
|
||||||
|
|
||||||
#### Fork and clone the AWX repo
|
#### Fork and clone the AWX repo
|
||||||
|
|
||||||
@@ -121,7 +121,7 @@ If it has someone assigned to it then that person is the person responsible for
|
|||||||
|
|
||||||
**NOTES**
|
**NOTES**
|
||||||
|
|
||||||
> Issue assignment will only be done for maintainers of the project. If you decide to work on an issue, please feel free to add a comment in the issue to let others know that you are working on it; but know that we will accept the first pull request from whomever is able to fix an issue. Once your PR is accepted we can add you as an assignee to an issue upon request.
|
> Issue assignment will only be done for maintainers of the project. If you decide to work on an issue, please feel free to add a comment in the issue to let others know that you are working on it; but know that we will accept the first pull request from whomever is able to fix an issue. Once your PR is accepted we can add you as an assignee to an issue upon request.
|
||||||
|
|
||||||
|
|
||||||
> If you work in a part of the codebase that is going through active development, your changes may be rejected, or you may be asked to `rebase`. A good idea before starting work is to have a discussion with us in the `#ansible-awx` channel on irc.libera.chat, or on the [mailing list](https://groups.google.com/forum/#!forum/awx-project).
|
> If you work in a part of the codebase that is going through active development, your changes may be rejected, or you may be asked to `rebase`. A good idea before starting work is to have a discussion with us in the `#ansible-awx` channel on irc.libera.chat, or on the [mailing list](https://groups.google.com/forum/#!forum/awx-project).
|
||||||
@@ -132,7 +132,7 @@ If it has someone assigned to it then that person is the person responsible for
|
|||||||
|
|
||||||
At this time we do not accept PRs for adding additional language translations as we have an automated process for generating our translations. This is because translations require constant care as new strings are added and changed in the code base. Because of this the .po files are overwritten during every translation release cycle. We also can't support a lot of translations on AWX as its an open source project and each language adds time and cost to maintain. If you would like to see AWX translated into a new language please create an issue and ask others you know to upvote the issue. Our translation team will review the needs of the community and see what they can do around supporting additional language.
|
At this time we do not accept PRs for adding additional language translations as we have an automated process for generating our translations. This is because translations require constant care as new strings are added and changed in the code base. Because of this the .po files are overwritten during every translation release cycle. We also can't support a lot of translations on AWX as its an open source project and each language adds time and cost to maintain. If you would like to see AWX translated into a new language please create an issue and ask others you know to upvote the issue. Our translation team will review the needs of the community and see what they can do around supporting additional language.
|
||||||
|
|
||||||
If you find an issue with an existing translation, please see the [Reporting Issues](#reporting-issues) section to open an issue and our translation team will work with you on a resolution.
|
If you find an issue with an existing translation, please see the [Reporting Issues](#reporting-issues) section to open an issue and our translation team will work with you on a resolution.
|
||||||
|
|
||||||
|
|
||||||
## Submitting Pull Requests
|
## Submitting Pull Requests
|
||||||
@@ -161,7 +161,7 @@ Sometimes it might take us a while to fully review your PR. We try to keep the `
|
|||||||
When your PR is initially submitted the checks will not be run until a maintainer allows them to be. Once a maintainer has done a quick review of your work the PR will have the linter and unit tests run against them via GitHub Actions, and the status reported in the PR.
|
When your PR is initially submitted the checks will not be run until a maintainer allows them to be. Once a maintainer has done a quick review of your work the PR will have the linter and unit tests run against them via GitHub Actions, and the status reported in the PR.
|
||||||
|
|
||||||
## Reporting Issues
|
## Reporting Issues
|
||||||
|
|
||||||
We welcome your feedback, and encourage you to file an issue when you run into a problem. But before opening a new issues, we ask that you please view our [Issues guide](./ISSUES.md).
|
We welcome your feedback, and encourage you to file an issue when you run into a problem. But before opening a new issues, we ask that you please view our [Issues guide](./ISSUES.md).
|
||||||
|
|
||||||
## Getting Help
|
## Getting Help
|
||||||
|
|||||||
6
Makefile
6
Makefile
@@ -502,13 +502,7 @@ ui-test-general:
|
|||||||
$(NPM_BIN) run --prefix awx/ui pretest
|
$(NPM_BIN) run --prefix awx/ui pretest
|
||||||
$(NPM_BIN) run --prefix awx/ui/ test-general --runInBand
|
$(NPM_BIN) run --prefix awx/ui/ test-general --runInBand
|
||||||
|
|
||||||
# NOTE: The make target ui-next is imported from awx/ui_next/Makefile
|
|
||||||
HEADLESS ?= no
|
|
||||||
ifeq ($(HEADLESS), yes)
|
|
||||||
dist/$(SDIST_TAR_FILE):
|
dist/$(SDIST_TAR_FILE):
|
||||||
else
|
|
||||||
dist/$(SDIST_TAR_FILE): $(UI_BUILD_FLAG_FILE) ui-next
|
|
||||||
endif
|
|
||||||
$(PYTHON) -m build -s
|
$(PYTHON) -m build -s
|
||||||
ln -sf $(SDIST_TAR_FILE) dist/awx.tar.gz
|
ln -sf $(SDIST_TAR_FILE) dist/awx.tar.gz
|
||||||
|
|
||||||
|
|||||||
@@ -35,4 +35,7 @@ We ask all of our community members and contributors to adhere to the [Ansible c
|
|||||||
Get Involved
|
Get Involved
|
||||||
------------
|
------------
|
||||||
|
|
||||||
We welcome your feedback and ideas. See the [AWX Communication guide](https://ansible.readthedocs.io/projects/awx/en/latest/contributor/communication.html) to learn how to join the conversation.
|
We welcome your feedback and ideas. Here's how to reach us with feedback and questions:
|
||||||
|
|
||||||
|
- Join the [Ansible AWX channel on Matrix](https://matrix.to/#/#awx:ansible.com)
|
||||||
|
- Join the [Ansible Community Forum](https://forum.ansible.com)
|
||||||
|
|||||||
@@ -103,7 +103,7 @@ class Metadata(metadata.SimpleMetadata):
|
|||||||
default = field.get_default()
|
default = field.get_default()
|
||||||
if type(default) is UUID:
|
if type(default) is UUID:
|
||||||
default = 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx'
|
default = 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx'
|
||||||
if field.field_name == 'TOWER_URL_BASE' and default == 'https://towerhost':
|
if field.field_name == 'TOWER_URL_BASE' and default == 'https://platformhost':
|
||||||
default = '{}://{}'.format(self.request.scheme, self.request.get_host())
|
default = '{}://{}'.format(self.request.scheme, self.request.get_host())
|
||||||
field_info['default'] = default
|
field_info['default'] = default
|
||||||
except serializers.SkipField:
|
except serializers.SkipField:
|
||||||
|
|||||||
@@ -2,6 +2,12 @@
|
|||||||
- hosts: all
|
- hosts: all
|
||||||
become: yes
|
become: yes
|
||||||
tasks:
|
tasks:
|
||||||
|
- name: Create the receptor group
|
||||||
|
group:
|
||||||
|
{% verbatim %}
|
||||||
|
name: "{{ receptor_group }}"
|
||||||
|
{% endverbatim %}
|
||||||
|
state: present
|
||||||
- name: Create the receptor user
|
- name: Create the receptor user
|
||||||
user:
|
user:
|
||||||
{% verbatim %}
|
{% verbatim %}
|
||||||
|
|||||||
@@ -1400,7 +1400,9 @@ class ExecutionEnvironmentAccess(BaseAccess):
|
|||||||
|
|
||||||
def filtered_queryset(self):
|
def filtered_queryset(self):
|
||||||
return ExecutionEnvironment.objects.filter(
|
return ExecutionEnvironment.objects.filter(
|
||||||
Q(organization__in=Organization.accessible_pk_qs(self.user, 'read_role')) | Q(organization__isnull=True)
|
Q(organization__in=Organization.accessible_pk_qs(self.user, 'read_role'))
|
||||||
|
| Q(organization__isnull=True)
|
||||||
|
| Q(id__in=ExecutionEnvironment.access_ids_qs(self.user, 'change'))
|
||||||
).distinct()
|
).distinct()
|
||||||
|
|
||||||
@check_superuser
|
@check_superuser
|
||||||
@@ -1419,11 +1421,13 @@ class ExecutionEnvironmentAccess(BaseAccess):
|
|||||||
else:
|
else:
|
||||||
if self.user not in obj.organization.execution_environment_admin_role:
|
if self.user not in obj.organization.execution_environment_admin_role:
|
||||||
raise PermissionDenied
|
raise PermissionDenied
|
||||||
if data and 'organization' in data:
|
if not self.check_related('organization', Organization, data, obj=obj, role_field='execution_environment_admin_role'):
|
||||||
new_org = get_object_from_data('organization', Organization, data, obj=obj)
|
return False
|
||||||
if not new_org or self.user not in new_org.execution_environment_admin_role:
|
# Special case that check_related does not catch, org users can not remove the organization from the EE
|
||||||
|
if data and ('organization' in data or 'organization_id' in data):
|
||||||
|
if (not data.get('organization')) and (not data.get('organization_id')):
|
||||||
return False
|
return False
|
||||||
return self.check_related('organization', Organization, data, obj=obj, role_field='execution_environment_admin_role')
|
return True
|
||||||
|
|
||||||
def can_delete(self, obj):
|
def can_delete(self, obj):
|
||||||
if obj.managed:
|
if obj.managed:
|
||||||
@@ -2100,15 +2104,18 @@ class WorkflowJobTemplateAccess(NotificationAttachMixin, BaseAccess):
|
|||||||
|
|
||||||
if not self.check_related('organization', Organization, data, role_field='workflow_admin_role', mandatory=True):
|
if not self.check_related('organization', Organization, data, role_field='workflow_admin_role', mandatory=True):
|
||||||
if data.get('organization', None) is None:
|
if data.get('organization', None) is None:
|
||||||
self.messages['organization'] = [_('An organization is required to create a workflow job template for normal user')]
|
if self.save_messages:
|
||||||
|
self.messages['organization'] = [_('An organization is required to create a workflow job template for normal user')]
|
||||||
return False
|
return False
|
||||||
|
|
||||||
if not self.check_related('inventory', Inventory, data, role_field='use_role'):
|
if not self.check_related('inventory', Inventory, data, role_field='use_role'):
|
||||||
self.messages['inventory'] = [_('You do not have use_role to the inventory')]
|
if self.save_messages:
|
||||||
|
self.messages['inventory'] = [_('You do not have use_role to the inventory')]
|
||||||
return False
|
return False
|
||||||
|
|
||||||
if not self.check_related('execution_environment', ExecutionEnvironment, data, role_field='read_role'):
|
if not self.check_related('execution_environment', ExecutionEnvironment, data, role_field='read_role'):
|
||||||
self.messages['execution_environment'] = [_('You do not have read_role to the execution environment')]
|
if self.save_messages:
|
||||||
|
self.messages['execution_environment'] = [_('You do not have read_role to the execution environment')]
|
||||||
return False
|
return False
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|||||||
@@ -66,10 +66,8 @@ class FixedSlidingWindow:
|
|||||||
|
|
||||||
|
|
||||||
class RelayWebsocketStatsManager:
|
class RelayWebsocketStatsManager:
|
||||||
def __init__(self, event_loop, local_hostname):
|
def __init__(self, local_hostname):
|
||||||
self._local_hostname = local_hostname
|
self._local_hostname = local_hostname
|
||||||
|
|
||||||
self._event_loop = event_loop
|
|
||||||
self._stats = dict()
|
self._stats = dict()
|
||||||
self._redis_key = BROADCAST_WEBSOCKET_REDIS_KEY_NAME
|
self._redis_key = BROADCAST_WEBSOCKET_REDIS_KEY_NAME
|
||||||
|
|
||||||
@@ -94,7 +92,10 @@ class RelayWebsocketStatsManager:
|
|||||||
self.start()
|
self.start()
|
||||||
|
|
||||||
def start(self):
|
def start(self):
|
||||||
self.async_task = self._event_loop.create_task(self.run_loop())
|
self.async_task = asyncio.get_running_loop().create_task(
|
||||||
|
self.run_loop(),
|
||||||
|
name='RelayWebsocketStatsManager.run_loop',
|
||||||
|
)
|
||||||
return self.async_task
|
return self.async_task
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
|||||||
@@ -929,6 +929,16 @@ register(
|
|||||||
category_slug='debug',
|
category_slug='debug',
|
||||||
)
|
)
|
||||||
|
|
||||||
|
register(
|
||||||
|
'RECEPTOR_KEEP_WORK_ON_ERROR',
|
||||||
|
field_class=fields.BooleanField,
|
||||||
|
label=_('Keep receptor work on error'),
|
||||||
|
default=False,
|
||||||
|
help_text=_('Prevent receptor work from being released on when error is detected'),
|
||||||
|
category=('Debug'),
|
||||||
|
category_slug='debug',
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def logging_validate(serializer, attrs):
|
def logging_validate(serializer, attrs):
|
||||||
if not serializer.instance or not hasattr(serializer.instance, 'LOG_AGGREGATOR_HOST') or not hasattr(serializer.instance, 'LOG_AGGREGATOR_TYPE'):
|
if not serializer.instance or not hasattr(serializer.instance, 'LOG_AGGREGATOR_HOST') or not hasattr(serializer.instance, 'LOG_AGGREGATOR_TYPE'):
|
||||||
|
|||||||
@@ -43,6 +43,7 @@ STANDARD_INVENTORY_UPDATE_ENV = {
|
|||||||
}
|
}
|
||||||
CAN_CANCEL = ('new', 'pending', 'waiting', 'running')
|
CAN_CANCEL = ('new', 'pending', 'waiting', 'running')
|
||||||
ACTIVE_STATES = CAN_CANCEL
|
ACTIVE_STATES = CAN_CANCEL
|
||||||
|
ERROR_STATES = ('error',)
|
||||||
MINIMAL_EVENTS = set(['playbook_on_play_start', 'playbook_on_task_start', 'playbook_on_stats', 'EOF'])
|
MINIMAL_EVENTS = set(['playbook_on_play_start', 'playbook_on_task_start', 'playbook_on_stats', 'EOF'])
|
||||||
CENSOR_VALUE = '************'
|
CENSOR_VALUE = '************'
|
||||||
ENV_BLOCKLIST = frozenset(
|
ENV_BLOCKLIST = frozenset(
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
# All Rights Reserved
|
# All Rights Reserved
|
||||||
|
|
||||||
from django.core.management.base import BaseCommand
|
from django.core.management.base import BaseCommand
|
||||||
|
from django.db import transaction
|
||||||
from crum import impersonate
|
from crum import impersonate
|
||||||
from awx.main.models import User, Organization, Project, Inventory, CredentialType, Credential, Host, JobTemplate
|
from awx.main.models import User, Organization, Project, Inventory, CredentialType, Credential, Host, JobTemplate
|
||||||
from awx.main.signals import disable_computed_fields
|
from awx.main.signals import disable_computed_fields
|
||||||
@@ -13,6 +14,12 @@ class Command(BaseCommand):
|
|||||||
help = 'Creates a preload tower data if there is none.'
|
help = 'Creates a preload tower data if there is none.'
|
||||||
|
|
||||||
def handle(self, *args, **kwargs):
|
def handle(self, *args, **kwargs):
|
||||||
|
# Wrap the operation in an atomic block, so we do not on accident
|
||||||
|
# create the organization but not create the project, etc.
|
||||||
|
with transaction.atomic():
|
||||||
|
self._handle()
|
||||||
|
|
||||||
|
def _handle(self):
|
||||||
changed = False
|
changed = False
|
||||||
|
|
||||||
# Create a default organization as the first superuser found.
|
# Create a default organization as the first superuser found.
|
||||||
@@ -43,10 +50,11 @@ class Command(BaseCommand):
|
|||||||
|
|
||||||
ssh_type = CredentialType.objects.filter(namespace='ssh').first()
|
ssh_type = CredentialType.objects.filter(namespace='ssh').first()
|
||||||
c, _ = Credential.objects.get_or_create(
|
c, _ = Credential.objects.get_or_create(
|
||||||
credential_type=ssh_type, name='Demo Credential', inputs={'username': superuser.username}, created_by=superuser
|
credential_type=ssh_type, name='Demo Credential', inputs={'username': getattr(superuser, 'username', 'null')}, created_by=superuser
|
||||||
)
|
)
|
||||||
|
|
||||||
c.admin_role.members.add(superuser)
|
if superuser:
|
||||||
|
c.admin_role.members.add(superuser)
|
||||||
|
|
||||||
public_galaxy_credential, _ = Credential.objects.get_or_create(
|
public_galaxy_credential, _ = Credential.objects.get_or_create(
|
||||||
name='Ansible Galaxy',
|
name='Ansible Galaxy',
|
||||||
|
|||||||
@@ -277,7 +277,6 @@ def setup_managed_role_definitions(apps, schema_editor):
|
|||||||
to_create = {
|
to_create = {
|
||||||
'object_admin': '{cls.__name__} Admin',
|
'object_admin': '{cls.__name__} Admin',
|
||||||
'org_admin': 'Organization Admin',
|
'org_admin': 'Organization Admin',
|
||||||
'org_audit': 'Organization Audit',
|
|
||||||
'org_children': 'Organization {cls.__name__} Admin',
|
'org_children': 'Organization {cls.__name__} Admin',
|
||||||
'special': '{cls.__name__} {action}',
|
'special': '{cls.__name__} {action}',
|
||||||
}
|
}
|
||||||
@@ -334,12 +333,19 @@ def setup_managed_role_definitions(apps, schema_editor):
|
|||||||
for perm in special_perms:
|
for perm in special_perms:
|
||||||
action = perm.codename.split('_')[0]
|
action = perm.codename.split('_')[0]
|
||||||
view_perm = Permission.objects.get(content_type=ct, codename__startswith='view_')
|
view_perm = Permission.objects.get(content_type=ct, codename__startswith='view_')
|
||||||
|
perm_list = [perm, view_perm]
|
||||||
|
# Handle special-case where adhoc role also listed use permission
|
||||||
|
if action == 'adhoc':
|
||||||
|
for other_perm in object_perms:
|
||||||
|
if other_perm.codename == 'use_inventory':
|
||||||
|
perm_list.append(other_perm)
|
||||||
|
break
|
||||||
managed_role_definitions.append(
|
managed_role_definitions.append(
|
||||||
get_or_create_managed(
|
get_or_create_managed(
|
||||||
to_create['special'].format(cls=cls, action=action.title()),
|
to_create['special'].format(cls=cls, action=action.title()),
|
||||||
f'Has {action} permissions to a single {cls._meta.verbose_name}',
|
f'Has {action} permissions to a single {cls._meta.verbose_name}',
|
||||||
ct,
|
ct,
|
||||||
[perm, view_perm],
|
perm_list,
|
||||||
RoleDefinition,
|
RoleDefinition,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@@ -355,18 +361,40 @@ def setup_managed_role_definitions(apps, schema_editor):
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
if 'org_audit' in to_create:
|
# Special "organization action" roles
|
||||||
audit_permissions = [perm for perm in org_perms if perm.codename.startswith('view_')]
|
audit_permissions = [perm for perm in org_perms if perm.codename.startswith('view_')]
|
||||||
audit_permissions.append(Permission.objects.get(codename='audit_organization'))
|
audit_permissions.append(Permission.objects.get(codename='audit_organization'))
|
||||||
managed_role_definitions.append(
|
managed_role_definitions.append(
|
||||||
get_or_create_managed(
|
get_or_create_managed(
|
||||||
to_create['org_audit'].format(cls=Organization),
|
'Organization Audit',
|
||||||
'Has permission to view all objects inside of a single organization',
|
'Has permission to view all objects inside of a single organization',
|
||||||
org_ct,
|
org_ct,
|
||||||
audit_permissions,
|
audit_permissions,
|
||||||
RoleDefinition,
|
RoleDefinition,
|
||||||
)
|
|
||||||
)
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
org_execute_permissions = {'view_jobtemplate', 'execute_jobtemplate', 'view_workflowjobtemplate', 'execute_workflowjobtemplate', 'view_organization'}
|
||||||
|
managed_role_definitions.append(
|
||||||
|
get_or_create_managed(
|
||||||
|
'Organization Execute',
|
||||||
|
'Has permission to execute all runnable objects in the organization',
|
||||||
|
org_ct,
|
||||||
|
[perm for perm in org_perms if perm.codename in org_execute_permissions],
|
||||||
|
RoleDefinition,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
org_approval_permissions = {'view_organization', 'view_workflowjobtemplate', 'approve_workflowjobtemplate'}
|
||||||
|
managed_role_definitions.append(
|
||||||
|
get_or_create_managed(
|
||||||
|
'Organization Approval',
|
||||||
|
'Has permission to approve any workflow steps within a single organization',
|
||||||
|
org_ct,
|
||||||
|
[perm for perm in org_perms if perm.codename in org_approval_permissions],
|
||||||
|
RoleDefinition,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
unexpected_role_definitions = RoleDefinition.objects.filter(managed=True).exclude(pk__in=[rd.pk for rd in managed_role_definitions])
|
unexpected_role_definitions = RoleDefinition.objects.filter(managed=True).exclude(pk__in=[rd.pk for rd in managed_role_definitions])
|
||||||
for role_definition in unexpected_role_definitions:
|
for role_definition in unexpected_role_definitions:
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ from jinja2 import sandbox
|
|||||||
|
|
||||||
# Django
|
# Django
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.utils.translation import gettext_lazy as _, gettext_noop
|
from django.utils.translation import gettext_lazy as _
|
||||||
from django.core.exceptions import ValidationError
|
from django.core.exceptions import ValidationError
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.utils.encoding import force_str
|
from django.utils.encoding import force_str
|
||||||
@@ -47,7 +47,7 @@ from awx.main.models.rbac import (
|
|||||||
)
|
)
|
||||||
from awx.main.models import Team, Organization
|
from awx.main.models import Team, Organization
|
||||||
from awx.main.utils import encrypt_field
|
from awx.main.utils import encrypt_field
|
||||||
from . import injectors as builtin_injectors
|
from awx_plugins.credentials import injectors as builtin_injectors
|
||||||
|
|
||||||
__all__ = ['Credential', 'CredentialType', 'CredentialInputSource', 'build_safe_env']
|
__all__ = ['Credential', 'CredentialType', 'CredentialInputSource', 'build_safe_env']
|
||||||
|
|
||||||
@@ -321,13 +321,14 @@ class Credential(PasswordFieldsModel, CommonModelNameNotUnique, ResourceMixin):
|
|||||||
raise ValueError('{} is not a dynamic input field'.format(field_name))
|
raise ValueError('{} is not a dynamic input field'.format(field_name))
|
||||||
|
|
||||||
def validate_role_assignment(self, actor, role_definition):
|
def validate_role_assignment(self, actor, role_definition):
|
||||||
if isinstance(actor, User):
|
if self.organization:
|
||||||
if actor.is_superuser or Organization.access_qs(actor, 'change').filter(id=self.organization.id).exists():
|
if isinstance(actor, User):
|
||||||
return
|
if actor.is_superuser or Organization.access_qs(actor, 'member').filter(id=self.organization.id).exists():
|
||||||
if isinstance(actor, Team):
|
return
|
||||||
if actor.organization == self.organization:
|
if isinstance(actor, Team):
|
||||||
return
|
if actor.organization == self.organization:
|
||||||
raise DRFValidationError({'detail': _(f"You cannot grant credential access to a {actor._meta.object_name} not in the credentials' organization")})
|
return
|
||||||
|
raise DRFValidationError({'detail': _(f"You cannot grant credential access to a {actor._meta.object_name} not in the credentials' organization")})
|
||||||
|
|
||||||
|
|
||||||
class CredentialType(CommonModelNameNotUnique):
|
class CredentialType(CommonModelNameNotUnique):
|
||||||
@@ -600,666 +601,6 @@ class ManagedCredentialType(SimpleNamespace):
|
|||||||
return CredentialType(**self.get_creation_params())
|
return CredentialType(**self.get_creation_params())
|
||||||
|
|
||||||
|
|
||||||
ManagedCredentialType(
|
|
||||||
namespace='ssh',
|
|
||||||
kind='ssh',
|
|
||||||
name=gettext_noop('Machine'),
|
|
||||||
inputs={
|
|
||||||
'fields': [
|
|
||||||
{'id': 'username', 'label': gettext_noop('Username'), 'type': 'string'},
|
|
||||||
{'id': 'password', 'label': gettext_noop('Password'), 'type': 'string', 'secret': True, 'ask_at_runtime': True},
|
|
||||||
{'id': 'ssh_key_data', 'label': gettext_noop('SSH Private Key'), 'type': 'string', 'format': 'ssh_private_key', 'secret': True, 'multiline': True},
|
|
||||||
{
|
|
||||||
'id': 'ssh_public_key_data',
|
|
||||||
'label': gettext_noop('Signed SSH Certificate'),
|
|
||||||
'type': 'string',
|
|
||||||
'multiline': True,
|
|
||||||
'secret': True,
|
|
||||||
},
|
|
||||||
{'id': 'ssh_key_unlock', 'label': gettext_noop('Private Key Passphrase'), 'type': 'string', 'secret': True, 'ask_at_runtime': True},
|
|
||||||
{
|
|
||||||
'id': 'become_method',
|
|
||||||
'label': gettext_noop('Privilege Escalation Method'),
|
|
||||||
'type': 'string',
|
|
||||||
'help_text': gettext_noop('Specify a method for "become" operations. This is equivalent to specifying the --become-method Ansible parameter.'),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'id': 'become_username',
|
|
||||||
'label': gettext_noop('Privilege Escalation Username'),
|
|
||||||
'type': 'string',
|
|
||||||
},
|
|
||||||
{'id': 'become_password', 'label': gettext_noop('Privilege Escalation Password'), 'type': 'string', 'secret': True, 'ask_at_runtime': True},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
ManagedCredentialType(
|
|
||||||
namespace='scm',
|
|
||||||
kind='scm',
|
|
||||||
name=gettext_noop('Source Control'),
|
|
||||||
managed=True,
|
|
||||||
inputs={
|
|
||||||
'fields': [
|
|
||||||
{'id': 'username', 'label': gettext_noop('Username'), 'type': 'string'},
|
|
||||||
{'id': 'password', 'label': gettext_noop('Password'), 'type': 'string', 'secret': True},
|
|
||||||
{'id': 'ssh_key_data', 'label': gettext_noop('SCM Private Key'), 'type': 'string', 'format': 'ssh_private_key', 'secret': True, 'multiline': True},
|
|
||||||
{'id': 'ssh_key_unlock', 'label': gettext_noop('Private Key Passphrase'), 'type': 'string', 'secret': True},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
ManagedCredentialType(
|
|
||||||
namespace='vault',
|
|
||||||
kind='vault',
|
|
||||||
name=gettext_noop('Vault'),
|
|
||||||
managed=True,
|
|
||||||
inputs={
|
|
||||||
'fields': [
|
|
||||||
{'id': 'vault_password', 'label': gettext_noop('Vault Password'), 'type': 'string', 'secret': True, 'ask_at_runtime': True},
|
|
||||||
{
|
|
||||||
'id': 'vault_id',
|
|
||||||
'label': gettext_noop('Vault Identifier'),
|
|
||||||
'type': 'string',
|
|
||||||
'format': 'vault_id',
|
|
||||||
'help_text': gettext_noop(
|
|
||||||
'Specify an (optional) Vault ID. This is '
|
|
||||||
'equivalent to specifying the --vault-id '
|
|
||||||
'Ansible parameter for providing multiple Vault '
|
|
||||||
'passwords. Note: this feature only works in '
|
|
||||||
'Ansible 2.4+.'
|
|
||||||
),
|
|
||||||
},
|
|
||||||
],
|
|
||||||
'required': ['vault_password'],
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
ManagedCredentialType(
|
|
||||||
namespace='net',
|
|
||||||
kind='net',
|
|
||||||
name=gettext_noop('Network'),
|
|
||||||
managed=True,
|
|
||||||
inputs={
|
|
||||||
'fields': [
|
|
||||||
{'id': 'username', 'label': gettext_noop('Username'), 'type': 'string'},
|
|
||||||
{
|
|
||||||
'id': 'password',
|
|
||||||
'label': gettext_noop('Password'),
|
|
||||||
'type': 'string',
|
|
||||||
'secret': True,
|
|
||||||
},
|
|
||||||
{'id': 'ssh_key_data', 'label': gettext_noop('SSH Private Key'), 'type': 'string', 'format': 'ssh_private_key', 'secret': True, 'multiline': True},
|
|
||||||
{
|
|
||||||
'id': 'ssh_key_unlock',
|
|
||||||
'label': gettext_noop('Private Key Passphrase'),
|
|
||||||
'type': 'string',
|
|
||||||
'secret': True,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'id': 'authorize',
|
|
||||||
'label': gettext_noop('Authorize'),
|
|
||||||
'type': 'boolean',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'id': 'authorize_password',
|
|
||||||
'label': gettext_noop('Authorize Password'),
|
|
||||||
'type': 'string',
|
|
||||||
'secret': True,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
'dependencies': {
|
|
||||||
'authorize_password': ['authorize'],
|
|
||||||
},
|
|
||||||
'required': ['username'],
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
ManagedCredentialType(
|
|
||||||
namespace='aws',
|
|
||||||
kind='cloud',
|
|
||||||
name=gettext_noop('Amazon Web Services'),
|
|
||||||
managed=True,
|
|
||||||
inputs={
|
|
||||||
'fields': [
|
|
||||||
{'id': 'username', 'label': gettext_noop('Access Key'), 'type': 'string'},
|
|
||||||
{
|
|
||||||
'id': 'password',
|
|
||||||
'label': gettext_noop('Secret Key'),
|
|
||||||
'type': 'string',
|
|
||||||
'secret': True,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'id': 'security_token',
|
|
||||||
'label': gettext_noop('STS Token'),
|
|
||||||
'type': 'string',
|
|
||||||
'secret': True,
|
|
||||||
'help_text': gettext_noop(
|
|
||||||
'Security Token Service (STS) is a web service '
|
|
||||||
'that enables you to request temporary, '
|
|
||||||
'limited-privilege credentials for AWS Identity '
|
|
||||||
'and Access Management (IAM) users.'
|
|
||||||
),
|
|
||||||
},
|
|
||||||
],
|
|
||||||
'required': ['username', 'password'],
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
ManagedCredentialType(
|
|
||||||
namespace='openstack',
|
|
||||||
kind='cloud',
|
|
||||||
name=gettext_noop('OpenStack'),
|
|
||||||
managed=True,
|
|
||||||
inputs={
|
|
||||||
'fields': [
|
|
||||||
{'id': 'username', 'label': gettext_noop('Username'), 'type': 'string'},
|
|
||||||
{
|
|
||||||
'id': 'password',
|
|
||||||
'label': gettext_noop('Password (API Key)'),
|
|
||||||
'type': 'string',
|
|
||||||
'secret': True,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'id': 'host',
|
|
||||||
'label': gettext_noop('Host (Authentication URL)'),
|
|
||||||
'type': 'string',
|
|
||||||
'help_text': gettext_noop('The host to authenticate with. For example, https://openstack.business.com/v2.0/'),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'id': 'project',
|
|
||||||
'label': gettext_noop('Project (Tenant Name)'),
|
|
||||||
'type': 'string',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'id': 'project_domain_name',
|
|
||||||
'label': gettext_noop('Project (Domain Name)'),
|
|
||||||
'type': 'string',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'id': 'domain',
|
|
||||||
'label': gettext_noop('Domain Name'),
|
|
||||||
'type': 'string',
|
|
||||||
'help_text': gettext_noop(
|
|
||||||
'OpenStack domains define administrative boundaries. '
|
|
||||||
'It is only needed for Keystone v3 authentication '
|
|
||||||
'URLs. Refer to the documentation for '
|
|
||||||
'common scenarios.'
|
|
||||||
),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'id': 'region',
|
|
||||||
'label': gettext_noop('Region Name'),
|
|
||||||
'type': 'string',
|
|
||||||
'help_text': gettext_noop('For some cloud providers, like OVH, region must be specified'),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'id': 'verify_ssl',
|
|
||||||
'label': gettext_noop('Verify SSL'),
|
|
||||||
'type': 'boolean',
|
|
||||||
'default': True,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
'required': ['username', 'password', 'host', 'project'],
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
ManagedCredentialType(
|
|
||||||
namespace='vmware',
|
|
||||||
kind='cloud',
|
|
||||||
name=gettext_noop('VMware vCenter'),
|
|
||||||
managed=True,
|
|
||||||
inputs={
|
|
||||||
'fields': [
|
|
||||||
{
|
|
||||||
'id': 'host',
|
|
||||||
'label': gettext_noop('VCenter Host'),
|
|
||||||
'type': 'string',
|
|
||||||
'help_text': gettext_noop('Enter the hostname or IP address that corresponds to your VMware vCenter.'),
|
|
||||||
},
|
|
||||||
{'id': 'username', 'label': gettext_noop('Username'), 'type': 'string'},
|
|
||||||
{
|
|
||||||
'id': 'password',
|
|
||||||
'label': gettext_noop('Password'),
|
|
||||||
'type': 'string',
|
|
||||||
'secret': True,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
'required': ['host', 'username', 'password'],
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
ManagedCredentialType(
|
|
||||||
namespace='satellite6',
|
|
||||||
kind='cloud',
|
|
||||||
name=gettext_noop('Red Hat Satellite 6'),
|
|
||||||
managed=True,
|
|
||||||
inputs={
|
|
||||||
'fields': [
|
|
||||||
{
|
|
||||||
'id': 'host',
|
|
||||||
'label': gettext_noop('Satellite 6 URL'),
|
|
||||||
'type': 'string',
|
|
||||||
'help_text': gettext_noop('Enter the URL that corresponds to your Red Hat Satellite 6 server. For example, https://satellite.example.org'),
|
|
||||||
},
|
|
||||||
{'id': 'username', 'label': gettext_noop('Username'), 'type': 'string'},
|
|
||||||
{
|
|
||||||
'id': 'password',
|
|
||||||
'label': gettext_noop('Password'),
|
|
||||||
'type': 'string',
|
|
||||||
'secret': True,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
'required': ['host', 'username', 'password'],
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
ManagedCredentialType(
|
|
||||||
namespace='gce',
|
|
||||||
kind='cloud',
|
|
||||||
name=gettext_noop('Google Compute Engine'),
|
|
||||||
managed=True,
|
|
||||||
inputs={
|
|
||||||
'fields': [
|
|
||||||
{
|
|
||||||
'id': 'username',
|
|
||||||
'label': gettext_noop('Service Account Email Address'),
|
|
||||||
'type': 'string',
|
|
||||||
'help_text': gettext_noop('The email address assigned to the Google Compute Engine service account.'),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'id': 'project',
|
|
||||||
'label': 'Project',
|
|
||||||
'type': 'string',
|
|
||||||
'help_text': gettext_noop(
|
|
||||||
'The Project ID is the GCE assigned identification. '
|
|
||||||
'It is often constructed as three words or two words '
|
|
||||||
'followed by a three-digit number. Examples: project-id-000 '
|
|
||||||
'and another-project-id'
|
|
||||||
),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'id': 'ssh_key_data',
|
|
||||||
'label': gettext_noop('RSA Private Key'),
|
|
||||||
'type': 'string',
|
|
||||||
'format': 'ssh_private_key',
|
|
||||||
'secret': True,
|
|
||||||
'multiline': True,
|
|
||||||
'help_text': gettext_noop('Paste the contents of the PEM file associated with the service account email.'),
|
|
||||||
},
|
|
||||||
],
|
|
||||||
'required': ['username', 'ssh_key_data'],
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
ManagedCredentialType(
|
|
||||||
namespace='azure_rm',
|
|
||||||
kind='cloud',
|
|
||||||
name=gettext_noop('Microsoft Azure Resource Manager'),
|
|
||||||
managed=True,
|
|
||||||
inputs={
|
|
||||||
'fields': [
|
|
||||||
{
|
|
||||||
'id': 'subscription',
|
|
||||||
'label': gettext_noop('Subscription ID'),
|
|
||||||
'type': 'string',
|
|
||||||
'help_text': gettext_noop('Subscription ID is an Azure construct, which is mapped to a username.'),
|
|
||||||
},
|
|
||||||
{'id': 'username', 'label': gettext_noop('Username'), 'type': 'string'},
|
|
||||||
{
|
|
||||||
'id': 'password',
|
|
||||||
'label': gettext_noop('Password'),
|
|
||||||
'type': 'string',
|
|
||||||
'secret': True,
|
|
||||||
},
|
|
||||||
{'id': 'client', 'label': gettext_noop('Client ID'), 'type': 'string'},
|
|
||||||
{
|
|
||||||
'id': 'secret',
|
|
||||||
'label': gettext_noop('Client Secret'),
|
|
||||||
'type': 'string',
|
|
||||||
'secret': True,
|
|
||||||
},
|
|
||||||
{'id': 'tenant', 'label': gettext_noop('Tenant ID'), 'type': 'string'},
|
|
||||||
{
|
|
||||||
'id': 'cloud_environment',
|
|
||||||
'label': gettext_noop('Azure Cloud Environment'),
|
|
||||||
'type': 'string',
|
|
||||||
'help_text': gettext_noop('Environment variable AZURE_CLOUD_ENVIRONMENT when using Azure GovCloud or Azure stack.'),
|
|
||||||
},
|
|
||||||
],
|
|
||||||
'required': ['subscription'],
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
ManagedCredentialType(
|
|
||||||
namespace='github_token',
|
|
||||||
kind='token',
|
|
||||||
name=gettext_noop('GitHub Personal Access Token'),
|
|
||||||
managed=True,
|
|
||||||
inputs={
|
|
||||||
'fields': [
|
|
||||||
{
|
|
||||||
'id': 'token',
|
|
||||||
'label': gettext_noop('Token'),
|
|
||||||
'type': 'string',
|
|
||||||
'secret': True,
|
|
||||||
'help_text': gettext_noop('This token needs to come from your profile settings in GitHub'),
|
|
||||||
}
|
|
||||||
],
|
|
||||||
'required': ['token'],
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
ManagedCredentialType(
|
|
||||||
namespace='gitlab_token',
|
|
||||||
kind='token',
|
|
||||||
name=gettext_noop('GitLab Personal Access Token'),
|
|
||||||
managed=True,
|
|
||||||
inputs={
|
|
||||||
'fields': [
|
|
||||||
{
|
|
||||||
'id': 'token',
|
|
||||||
'label': gettext_noop('Token'),
|
|
||||||
'type': 'string',
|
|
||||||
'secret': True,
|
|
||||||
'help_text': gettext_noop('This token needs to come from your profile settings in GitLab'),
|
|
||||||
}
|
|
||||||
],
|
|
||||||
'required': ['token'],
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
ManagedCredentialType(
|
|
||||||
namespace='bitbucket_dc_token',
|
|
||||||
kind='token',
|
|
||||||
name=gettext_noop('Bitbucket Data Center HTTP Access Token'),
|
|
||||||
managed=True,
|
|
||||||
inputs={
|
|
||||||
'fields': [
|
|
||||||
{
|
|
||||||
'id': 'token',
|
|
||||||
'label': gettext_noop('Token'),
|
|
||||||
'type': 'string',
|
|
||||||
'secret': True,
|
|
||||||
'help_text': gettext_noop('This token needs to come from your user settings in Bitbucket'),
|
|
||||||
}
|
|
||||||
],
|
|
||||||
'required': ['token'],
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
ManagedCredentialType(
|
|
||||||
namespace='insights',
|
|
||||||
kind='insights',
|
|
||||||
name=gettext_noop('Insights'),
|
|
||||||
managed=True,
|
|
||||||
inputs={
|
|
||||||
'fields': [
|
|
||||||
{'id': 'username', 'label': gettext_noop('Username'), 'type': 'string'},
|
|
||||||
{'id': 'password', 'label': gettext_noop('Password'), 'type': 'string', 'secret': True},
|
|
||||||
],
|
|
||||||
'required': ['username', 'password'],
|
|
||||||
},
|
|
||||||
injectors={
|
|
||||||
'extra_vars': {
|
|
||||||
"scm_username": "{{username}}",
|
|
||||||
"scm_password": "{{password}}",
|
|
||||||
},
|
|
||||||
'env': {
|
|
||||||
'INSIGHTS_USER': '{{username}}',
|
|
||||||
'INSIGHTS_PASSWORD': '{{password}}',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
ManagedCredentialType(
|
|
||||||
namespace='rhv',
|
|
||||||
kind='cloud',
|
|
||||||
name=gettext_noop('Red Hat Virtualization'),
|
|
||||||
managed=True,
|
|
||||||
inputs={
|
|
||||||
'fields': [
|
|
||||||
{'id': 'host', 'label': gettext_noop('Host (Authentication URL)'), 'type': 'string', 'help_text': gettext_noop('The host to authenticate with.')},
|
|
||||||
{'id': 'username', 'label': gettext_noop('Username'), 'type': 'string'},
|
|
||||||
{
|
|
||||||
'id': 'password',
|
|
||||||
'label': gettext_noop('Password'),
|
|
||||||
'type': 'string',
|
|
||||||
'secret': True,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'id': 'ca_file',
|
|
||||||
'label': gettext_noop('CA File'),
|
|
||||||
'type': 'string',
|
|
||||||
'help_text': gettext_noop('Absolute file path to the CA file to use (optional)'),
|
|
||||||
},
|
|
||||||
],
|
|
||||||
'required': ['host', 'username', 'password'],
|
|
||||||
},
|
|
||||||
injectors={
|
|
||||||
# The duplication here is intentional; the ovirt4 inventory plugin
|
|
||||||
# writes a .ini file for authentication, while the ansible modules for
|
|
||||||
# ovirt4 use a separate authentication process that support
|
|
||||||
# environment variables; by injecting both, we support both
|
|
||||||
'file': {
|
|
||||||
'template': '\n'.join(
|
|
||||||
[
|
|
||||||
'[ovirt]',
|
|
||||||
'ovirt_url={{host}}',
|
|
||||||
'ovirt_username={{username}}',
|
|
||||||
'ovirt_password={{password}}',
|
|
||||||
'{% if ca_file %}ovirt_ca_file={{ca_file}}{% endif %}',
|
|
||||||
]
|
|
||||||
)
|
|
||||||
},
|
|
||||||
'env': {'OVIRT_INI_PATH': '{{tower.filename}}', 'OVIRT_URL': '{{host}}', 'OVIRT_USERNAME': '{{username}}', 'OVIRT_PASSWORD': '{{password}}'},
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
ManagedCredentialType(
|
|
||||||
namespace='controller',
|
|
||||||
kind='cloud',
|
|
||||||
name=gettext_noop('Red Hat Ansible Automation Platform'),
|
|
||||||
managed=True,
|
|
||||||
inputs={
|
|
||||||
'fields': [
|
|
||||||
{
|
|
||||||
'id': 'host',
|
|
||||||
'label': gettext_noop('Red Hat Ansible Automation Platform'),
|
|
||||||
'type': 'string',
|
|
||||||
'help_text': gettext_noop('Red Hat Ansible Automation Platform base URL to authenticate with.'),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'id': 'username',
|
|
||||||
'label': gettext_noop('Username'),
|
|
||||||
'type': 'string',
|
|
||||||
'help_text': gettext_noop(
|
|
||||||
'Red Hat Ansible Automation Platform username id to authenticate as.This should not be set if an OAuth token is being used.'
|
|
||||||
),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'id': 'password',
|
|
||||||
'label': gettext_noop('Password'),
|
|
||||||
'type': 'string',
|
|
||||||
'secret': True,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'id': 'oauth_token',
|
|
||||||
'label': gettext_noop('OAuth Token'),
|
|
||||||
'type': 'string',
|
|
||||||
'secret': True,
|
|
||||||
'help_text': gettext_noop('An OAuth token to use to authenticate with.This should not be set if username/password are being used.'),
|
|
||||||
},
|
|
||||||
{'id': 'verify_ssl', 'label': gettext_noop('Verify SSL'), 'type': 'boolean', 'secret': False},
|
|
||||||
],
|
|
||||||
'required': ['host'],
|
|
||||||
},
|
|
||||||
injectors={
|
|
||||||
'env': {
|
|
||||||
'TOWER_HOST': '{{host}}',
|
|
||||||
'TOWER_USERNAME': '{{username}}',
|
|
||||||
'TOWER_PASSWORD': '{{password}}',
|
|
||||||
'TOWER_VERIFY_SSL': '{{verify_ssl}}',
|
|
||||||
'TOWER_OAUTH_TOKEN': '{{oauth_token}}',
|
|
||||||
'CONTROLLER_HOST': '{{host}}',
|
|
||||||
'CONTROLLER_USERNAME': '{{username}}',
|
|
||||||
'CONTROLLER_PASSWORD': '{{password}}',
|
|
||||||
'CONTROLLER_VERIFY_SSL': '{{verify_ssl}}',
|
|
||||||
'CONTROLLER_OAUTH_TOKEN': '{{oauth_token}}',
|
|
||||||
}
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
ManagedCredentialType(
|
|
||||||
namespace='kubernetes_bearer_token',
|
|
||||||
kind='kubernetes',
|
|
||||||
name=gettext_noop('OpenShift or Kubernetes API Bearer Token'),
|
|
||||||
inputs={
|
|
||||||
'fields': [
|
|
||||||
{
|
|
||||||
'id': 'host',
|
|
||||||
'label': gettext_noop('OpenShift or Kubernetes API Endpoint'),
|
|
||||||
'type': 'string',
|
|
||||||
'help_text': gettext_noop('The OpenShift or Kubernetes API Endpoint to authenticate with.'),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'id': 'bearer_token',
|
|
||||||
'label': gettext_noop('API authentication bearer token'),
|
|
||||||
'type': 'string',
|
|
||||||
'secret': True,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'id': 'verify_ssl',
|
|
||||||
'label': gettext_noop('Verify SSL'),
|
|
||||||
'type': 'boolean',
|
|
||||||
'default': True,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'id': 'ssl_ca_cert',
|
|
||||||
'label': gettext_noop('Certificate Authority data'),
|
|
||||||
'type': 'string',
|
|
||||||
'secret': True,
|
|
||||||
'multiline': True,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
'required': ['host', 'bearer_token'],
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
ManagedCredentialType(
|
|
||||||
namespace='registry',
|
|
||||||
kind='registry',
|
|
||||||
name=gettext_noop('Container Registry'),
|
|
||||||
inputs={
|
|
||||||
'fields': [
|
|
||||||
{
|
|
||||||
'id': 'host',
|
|
||||||
'label': gettext_noop('Authentication URL'),
|
|
||||||
'type': 'string',
|
|
||||||
'help_text': gettext_noop('Authentication endpoint for the container registry.'),
|
|
||||||
'default': 'quay.io',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'id': 'username',
|
|
||||||
'label': gettext_noop('Username'),
|
|
||||||
'type': 'string',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'id': 'password',
|
|
||||||
'label': gettext_noop('Password or Token'),
|
|
||||||
'type': 'string',
|
|
||||||
'secret': True,
|
|
||||||
'help_text': gettext_noop('A password or token used to authenticate with'),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'id': 'verify_ssl',
|
|
||||||
'label': gettext_noop('Verify SSL'),
|
|
||||||
'type': 'boolean',
|
|
||||||
'default': True,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
'required': ['host'],
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
ManagedCredentialType(
|
|
||||||
namespace='galaxy_api_token',
|
|
||||||
kind='galaxy',
|
|
||||||
name=gettext_noop('Ansible Galaxy/Automation Hub API Token'),
|
|
||||||
inputs={
|
|
||||||
'fields': [
|
|
||||||
{
|
|
||||||
'id': 'url',
|
|
||||||
'label': gettext_noop('Galaxy Server URL'),
|
|
||||||
'type': 'string',
|
|
||||||
'help_text': gettext_noop('The URL of the Galaxy instance to connect to.'),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'id': 'auth_url',
|
|
||||||
'label': gettext_noop('Auth Server URL'),
|
|
||||||
'type': 'string',
|
|
||||||
'help_text': gettext_noop('The URL of a Keycloak server token_endpoint, if using SSO auth.'),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'id': 'token',
|
|
||||||
'label': gettext_noop('API Token'),
|
|
||||||
'type': 'string',
|
|
||||||
'secret': True,
|
|
||||||
'help_text': gettext_noop('A token to use for authentication against the Galaxy instance.'),
|
|
||||||
},
|
|
||||||
],
|
|
||||||
'required': ['url'],
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
ManagedCredentialType(
|
|
||||||
namespace='gpg_public_key',
|
|
||||||
kind='cryptography',
|
|
||||||
name=gettext_noop('GPG Public Key'),
|
|
||||||
inputs={
|
|
||||||
'fields': [
|
|
||||||
{
|
|
||||||
'id': 'gpg_public_key',
|
|
||||||
'label': gettext_noop('GPG Public Key'),
|
|
||||||
'type': 'string',
|
|
||||||
'secret': True,
|
|
||||||
'multiline': True,
|
|
||||||
'help_text': gettext_noop('GPG Public Key used to validate content signatures.'),
|
|
||||||
},
|
|
||||||
],
|
|
||||||
'required': ['gpg_public_key'],
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
ManagedCredentialType(
|
|
||||||
namespace='terraform',
|
|
||||||
kind='cloud',
|
|
||||||
name=gettext_noop('Terraform backend configuration'),
|
|
||||||
managed=True,
|
|
||||||
inputs={
|
|
||||||
'fields': [
|
|
||||||
{
|
|
||||||
'id': 'configuration',
|
|
||||||
'label': gettext_noop('Backend configuration'),
|
|
||||||
'type': 'string',
|
|
||||||
'secret': True,
|
|
||||||
'multiline': True,
|
|
||||||
'help_text': gettext_noop('Terraform backend config as Hashicorp configuration language.'),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'id': 'gce_credentials',
|
|
||||||
'label': gettext_noop('Google Cloud Platform account credentials'),
|
|
||||||
'type': 'string',
|
|
||||||
'secret': True,
|
|
||||||
'multiline': True,
|
|
||||||
'help_text': gettext_noop('Google Cloud Platform account credentials in JSON format.'),
|
|
||||||
},
|
|
||||||
],
|
|
||||||
'required': ['configuration'],
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class CredentialInputSource(PrimordialModel):
|
class CredentialInputSource(PrimordialModel):
|
||||||
class Meta:
|
class Meta:
|
||||||
app_label = 'main'
|
app_label = 'main'
|
||||||
@@ -1323,6 +664,7 @@ class CredentialInputSource(PrimordialModel):
|
|||||||
view_name = 'api:credential_input_source_detail'
|
view_name = 'api:credential_input_source_detail'
|
||||||
return reverse(view_name, kwargs={'pk': self.pk}, request=request)
|
return reverse(view_name, kwargs={'pk': self.pk}, request=request)
|
||||||
|
|
||||||
|
from awx_plugins.credentials.plugins import *
|
||||||
|
|
||||||
for ns, plugin in credential_plugins.items():
|
for ns, plugin in credential_plugins.items():
|
||||||
CredentialType.load_plugin(ns, plugin)
|
CredentialType.load_plugin(ns, plugin)
|
||||||
|
|||||||
@@ -66,7 +66,3 @@ class ExecutionEnvironment(CommonModel):
|
|||||||
|
|
||||||
if actor._meta.model_name == 'user' and (not actor.has_obj_perm(self.organization, 'view')):
|
if actor._meta.model_name == 'user' and (not actor.has_obj_perm(self.organization, 'view')):
|
||||||
raise ValidationError({'user': _('User must have view permission to Execution Environment organization')})
|
raise ValidationError({'user': _('User must have view permission to Execution Environment organization')})
|
||||||
if actor._meta.model_name == 'team':
|
|
||||||
organization_cls = self._meta.get_field('organization').related_model
|
|
||||||
if self.orgaanization not in organization_cls.access_qs(actor, 'view'):
|
|
||||||
raise ValidationError({'team': _('Team must have view permission to Execution Environment organization')})
|
|
||||||
|
|||||||
@@ -10,10 +10,6 @@ import copy
|
|||||||
import os.path
|
import os.path
|
||||||
from urllib.parse import urljoin
|
from urllib.parse import urljoin
|
||||||
|
|
||||||
import yaml
|
|
||||||
import tempfile
|
|
||||||
import stat
|
|
||||||
|
|
||||||
# Django
|
# Django
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.db import models, connection
|
from django.db import models, connection
|
||||||
@@ -28,6 +24,7 @@ from django.db.models import Q
|
|||||||
from rest_framework.exceptions import ParseError
|
from rest_framework.exceptions import ParseError
|
||||||
|
|
||||||
from ansible_base.lib.utils.models import prevent_search
|
from ansible_base.lib.utils.models import prevent_search
|
||||||
|
from awx_plugins.inventory.plugins import PluginFileInjector
|
||||||
|
|
||||||
# AWX
|
# AWX
|
||||||
from awx.api.versioning import reverse
|
from awx.api.versioning import reverse
|
||||||
@@ -52,11 +49,9 @@ from awx.main.models.notifications import (
|
|||||||
NotificationTemplate,
|
NotificationTemplate,
|
||||||
JobNotificationMixin,
|
JobNotificationMixin,
|
||||||
)
|
)
|
||||||
from awx.main.models.credential.injectors import _openstack_data
|
|
||||||
from awx.main.utils import _inventory_updates
|
from awx.main.utils import _inventory_updates
|
||||||
from awx.main.utils.safe_yaml import sanitize_jinja
|
from awx.main.utils.safe_yaml import sanitize_jinja
|
||||||
from awx.main.utils.execution_environments import to_container_path, get_control_plane_execution_environment
|
from awx.main.utils.execution_environments import get_control_plane_execution_environment
|
||||||
from awx.main.utils.licensing import server_product_name
|
|
||||||
|
|
||||||
|
|
||||||
__all__ = ['Inventory', 'Host', 'Group', 'InventorySource', 'InventoryUpdate', 'SmartInventoryMembership', 'HostMetric', 'HostMetricSummaryMonthly']
|
__all__ = ['Inventory', 'Host', 'Group', 'InventorySource', 'InventoryUpdate', 'SmartInventoryMembership', 'HostMetric', 'HostMetricSummaryMonthly']
|
||||||
@@ -1427,297 +1422,5 @@ class CustomInventoryScript(CommonModelNameNotUnique):
|
|||||||
return reverse('api:inventory_script_detail', kwargs={'pk': self.pk}, request=request)
|
return reverse('api:inventory_script_detail', kwargs={'pk': self.pk}, request=request)
|
||||||
|
|
||||||
|
|
||||||
class PluginFileInjector(object):
|
|
||||||
plugin_name = None # Ansible core name used to reference plugin
|
|
||||||
# base injector should be one of None, "managed", or "template"
|
|
||||||
# this dictates which logic to borrow from playbook injectors
|
|
||||||
base_injector = None
|
|
||||||
# every source should have collection, these are for the collection name
|
|
||||||
namespace = None
|
|
||||||
collection = None
|
|
||||||
collection_migration = '2.9' # Starting with this version, we use collections
|
|
||||||
use_fqcn = False # plugin: name versus plugin: namespace.collection.name
|
|
||||||
|
|
||||||
# TODO: delete this method and update unit tests
|
|
||||||
@classmethod
|
|
||||||
def get_proper_name(cls):
|
|
||||||
if cls.plugin_name is None:
|
|
||||||
return None
|
|
||||||
return f'{cls.namespace}.{cls.collection}.{cls.plugin_name}'
|
|
||||||
|
|
||||||
@property
|
|
||||||
def filename(self):
|
|
||||||
"""Inventory filename for using the inventory plugin
|
|
||||||
This is created dynamically, but the auto plugin requires this exact naming
|
|
||||||
"""
|
|
||||||
return '{0}.yml'.format(self.plugin_name)
|
|
||||||
|
|
||||||
def inventory_contents(self, inventory_update, private_data_dir):
|
|
||||||
"""Returns a string that is the content for the inventory file for the inventory plugin"""
|
|
||||||
return yaml.safe_dump(self.inventory_as_dict(inventory_update, private_data_dir), default_flow_style=False, width=1000)
|
|
||||||
|
|
||||||
def inventory_as_dict(self, inventory_update, private_data_dir):
|
|
||||||
source_vars = dict(inventory_update.source_vars_dict) # make a copy
|
|
||||||
'''
|
|
||||||
None conveys that we should use the user-provided plugin.
|
|
||||||
Note that a plugin value of '' should still be overridden.
|
|
||||||
'''
|
|
||||||
if self.plugin_name is not None:
|
|
||||||
if hasattr(self, 'downstream_namespace') and server_product_name() != 'AWX':
|
|
||||||
source_vars['plugin'] = f'{self.downstream_namespace}.{self.downstream_collection}.{self.plugin_name}'
|
|
||||||
elif self.use_fqcn:
|
|
||||||
source_vars['plugin'] = f'{self.namespace}.{self.collection}.{self.plugin_name}'
|
|
||||||
else:
|
|
||||||
source_vars['plugin'] = self.plugin_name
|
|
||||||
return source_vars
|
|
||||||
|
|
||||||
def build_env(self, inventory_update, env, private_data_dir, private_data_files):
|
|
||||||
injector_env = self.get_plugin_env(inventory_update, private_data_dir, private_data_files)
|
|
||||||
env.update(injector_env)
|
|
||||||
# All CLOUD_PROVIDERS sources implement as inventory plugin from collection
|
|
||||||
env['ANSIBLE_INVENTORY_ENABLED'] = 'auto'
|
|
||||||
return env
|
|
||||||
|
|
||||||
def _get_shared_env(self, inventory_update, private_data_dir, private_data_files):
|
|
||||||
"""By default, we will apply the standard managed injectors"""
|
|
||||||
injected_env = {}
|
|
||||||
credential = inventory_update.get_cloud_credential()
|
|
||||||
# some sources may have no credential, specifically ec2
|
|
||||||
if credential is None:
|
|
||||||
return injected_env
|
|
||||||
if self.base_injector in ('managed', 'template'):
|
|
||||||
injected_env['INVENTORY_UPDATE_ID'] = str(inventory_update.pk) # so injector knows this is inventory
|
|
||||||
if self.base_injector == 'managed':
|
|
||||||
from awx.main.models.credential import injectors as builtin_injectors
|
|
||||||
|
|
||||||
cred_kind = inventory_update.source.replace('ec2', 'aws')
|
|
||||||
if cred_kind in dir(builtin_injectors):
|
|
||||||
getattr(builtin_injectors, cred_kind)(credential, injected_env, private_data_dir)
|
|
||||||
elif self.base_injector == 'template':
|
|
||||||
safe_env = injected_env.copy()
|
|
||||||
args = []
|
|
||||||
credential.credential_type.inject_credential(credential, injected_env, safe_env, args, private_data_dir)
|
|
||||||
# NOTE: safe_env is handled externally to injector class by build_safe_env static method
|
|
||||||
# that means that managed injectors must only inject detectable env keys
|
|
||||||
# enforcement of this is accomplished by tests
|
|
||||||
return injected_env
|
|
||||||
|
|
||||||
def get_plugin_env(self, inventory_update, private_data_dir, private_data_files):
|
|
||||||
env = self._get_shared_env(inventory_update, private_data_dir, private_data_files)
|
|
||||||
return env
|
|
||||||
|
|
||||||
def build_private_data(self, inventory_update, private_data_dir):
|
|
||||||
return self.build_plugin_private_data(inventory_update, private_data_dir)
|
|
||||||
|
|
||||||
def build_plugin_private_data(self, inventory_update, private_data_dir):
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
class azure_rm(PluginFileInjector):
|
|
||||||
plugin_name = 'azure_rm'
|
|
||||||
base_injector = 'managed'
|
|
||||||
namespace = 'azure'
|
|
||||||
collection = 'azcollection'
|
|
||||||
|
|
||||||
def get_plugin_env(self, *args, **kwargs):
|
|
||||||
ret = super(azure_rm, self).get_plugin_env(*args, **kwargs)
|
|
||||||
# We need native jinja2 types so that tags can give JSON null value
|
|
||||||
ret['ANSIBLE_JINJA2_NATIVE'] = str(True)
|
|
||||||
return ret
|
|
||||||
|
|
||||||
|
|
||||||
class ec2(PluginFileInjector):
|
|
||||||
plugin_name = 'aws_ec2'
|
|
||||||
base_injector = 'managed'
|
|
||||||
namespace = 'amazon'
|
|
||||||
collection = 'aws'
|
|
||||||
|
|
||||||
def get_plugin_env(self, *args, **kwargs):
|
|
||||||
ret = super(ec2, self).get_plugin_env(*args, **kwargs)
|
|
||||||
# We need native jinja2 types so that ec2_state_code will give integer
|
|
||||||
ret['ANSIBLE_JINJA2_NATIVE'] = str(True)
|
|
||||||
return ret
|
|
||||||
|
|
||||||
|
|
||||||
class gce(PluginFileInjector):
|
|
||||||
plugin_name = 'gcp_compute'
|
|
||||||
base_injector = 'managed'
|
|
||||||
namespace = 'google'
|
|
||||||
collection = 'cloud'
|
|
||||||
|
|
||||||
def get_plugin_env(self, *args, **kwargs):
|
|
||||||
ret = super(gce, self).get_plugin_env(*args, **kwargs)
|
|
||||||
# We need native jinja2 types so that ip addresses can give JSON null value
|
|
||||||
ret['ANSIBLE_JINJA2_NATIVE'] = str(True)
|
|
||||||
return ret
|
|
||||||
|
|
||||||
def inventory_as_dict(self, inventory_update, private_data_dir):
|
|
||||||
ret = super().inventory_as_dict(inventory_update, private_data_dir)
|
|
||||||
credential = inventory_update.get_cloud_credential()
|
|
||||||
# InventorySource.source_vars take precedence over ENV vars
|
|
||||||
if 'projects' not in ret:
|
|
||||||
ret['projects'] = [credential.get_input('project', default='')]
|
|
||||||
return ret
|
|
||||||
|
|
||||||
|
|
||||||
class vmware(PluginFileInjector):
|
|
||||||
plugin_name = 'vmware_vm_inventory'
|
|
||||||
base_injector = 'managed'
|
|
||||||
namespace = 'community'
|
|
||||||
collection = 'vmware'
|
|
||||||
|
|
||||||
|
|
||||||
class openstack(PluginFileInjector):
|
|
||||||
plugin_name = 'openstack'
|
|
||||||
namespace = 'openstack'
|
|
||||||
collection = 'cloud'
|
|
||||||
|
|
||||||
def _get_clouds_dict(self, inventory_update, cred, private_data_dir):
|
|
||||||
openstack_data = _openstack_data(cred)
|
|
||||||
|
|
||||||
openstack_data['clouds']['devstack']['private'] = inventory_update.source_vars_dict.get('private', True)
|
|
||||||
ansible_variables = {
|
|
||||||
'use_hostnames': True,
|
|
||||||
'expand_hostvars': False,
|
|
||||||
'fail_on_errors': True,
|
|
||||||
}
|
|
||||||
provided_count = 0
|
|
||||||
for var_name in ansible_variables:
|
|
||||||
if var_name in inventory_update.source_vars_dict:
|
|
||||||
ansible_variables[var_name] = inventory_update.source_vars_dict[var_name]
|
|
||||||
provided_count += 1
|
|
||||||
if provided_count:
|
|
||||||
# Must we provide all 3 because the user provides any 1 of these??
|
|
||||||
# this probably results in some incorrect mangling of the defaults
|
|
||||||
openstack_data['ansible'] = ansible_variables
|
|
||||||
return openstack_data
|
|
||||||
|
|
||||||
def build_plugin_private_data(self, inventory_update, private_data_dir):
|
|
||||||
credential = inventory_update.get_cloud_credential()
|
|
||||||
private_data = {'credentials': {}}
|
|
||||||
|
|
||||||
openstack_data = self._get_clouds_dict(inventory_update, credential, private_data_dir)
|
|
||||||
private_data['credentials'][credential] = yaml.safe_dump(openstack_data, default_flow_style=False, allow_unicode=True)
|
|
||||||
return private_data
|
|
||||||
|
|
||||||
def get_plugin_env(self, inventory_update, private_data_dir, private_data_files):
|
|
||||||
env = super(openstack, self).get_plugin_env(inventory_update, private_data_dir, private_data_files)
|
|
||||||
credential = inventory_update.get_cloud_credential()
|
|
||||||
cred_data = private_data_files['credentials']
|
|
||||||
env['OS_CLIENT_CONFIG_FILE'] = to_container_path(cred_data[credential], private_data_dir)
|
|
||||||
return env
|
|
||||||
|
|
||||||
|
|
||||||
class rhv(PluginFileInjector):
|
|
||||||
"""ovirt uses the custom credential templating, and that is all"""
|
|
||||||
|
|
||||||
plugin_name = 'ovirt'
|
|
||||||
base_injector = 'template'
|
|
||||||
initial_version = '2.9'
|
|
||||||
namespace = 'ovirt'
|
|
||||||
collection = 'ovirt'
|
|
||||||
downstream_namespace = 'redhat'
|
|
||||||
downstream_collection = 'rhv'
|
|
||||||
use_fqcn = True
|
|
||||||
|
|
||||||
|
|
||||||
class satellite6(PluginFileInjector):
|
|
||||||
plugin_name = 'foreman'
|
|
||||||
namespace = 'theforeman'
|
|
||||||
collection = 'foreman'
|
|
||||||
downstream_namespace = 'redhat'
|
|
||||||
downstream_collection = 'satellite'
|
|
||||||
use_fqcn = True
|
|
||||||
|
|
||||||
def get_plugin_env(self, inventory_update, private_data_dir, private_data_files):
|
|
||||||
# this assumes that this is merged
|
|
||||||
# https://github.com/ansible/ansible/pull/52693
|
|
||||||
credential = inventory_update.get_cloud_credential()
|
|
||||||
ret = super(satellite6, self).get_plugin_env(inventory_update, private_data_dir, private_data_files)
|
|
||||||
if credential:
|
|
||||||
ret['FOREMAN_SERVER'] = credential.get_input('host', default='')
|
|
||||||
ret['FOREMAN_USER'] = credential.get_input('username', default='')
|
|
||||||
ret['FOREMAN_PASSWORD'] = credential.get_input('password', default='')
|
|
||||||
return ret
|
|
||||||
|
|
||||||
|
|
||||||
class terraform(PluginFileInjector):
|
|
||||||
plugin_name = 'terraform_state'
|
|
||||||
namespace = 'cloud'
|
|
||||||
collection = 'terraform'
|
|
||||||
use_fqcn = True
|
|
||||||
|
|
||||||
def inventory_as_dict(self, inventory_update, private_data_dir):
|
|
||||||
ret = super().inventory_as_dict(inventory_update, private_data_dir)
|
|
||||||
credential = inventory_update.get_cloud_credential()
|
|
||||||
config_cred = credential.get_input('configuration')
|
|
||||||
if config_cred:
|
|
||||||
handle, path = tempfile.mkstemp(dir=os.path.join(private_data_dir, 'env'))
|
|
||||||
with os.fdopen(handle, 'w') as f:
|
|
||||||
os.chmod(path, stat.S_IRUSR | stat.S_IWUSR)
|
|
||||||
f.write(config_cred)
|
|
||||||
ret['backend_config_files'] = to_container_path(path, private_data_dir)
|
|
||||||
return ret
|
|
||||||
|
|
||||||
def build_plugin_private_data(self, inventory_update, private_data_dir):
|
|
||||||
credential = inventory_update.get_cloud_credential()
|
|
||||||
|
|
||||||
private_data = {'credentials': {}}
|
|
||||||
gce_cred = credential.get_input('gce_credentials', default=None)
|
|
||||||
if gce_cred:
|
|
||||||
private_data['credentials'][credential] = gce_cred
|
|
||||||
return private_data
|
|
||||||
|
|
||||||
def get_plugin_env(self, inventory_update, private_data_dir, private_data_files):
|
|
||||||
env = super(terraform, self).get_plugin_env(inventory_update, private_data_dir, private_data_files)
|
|
||||||
credential = inventory_update.get_cloud_credential()
|
|
||||||
cred_data = private_data_files['credentials']
|
|
||||||
if credential in cred_data:
|
|
||||||
env['GOOGLE_BACKEND_CREDENTIALS'] = to_container_path(cred_data[credential], private_data_dir)
|
|
||||||
return env
|
|
||||||
|
|
||||||
|
|
||||||
class controller(PluginFileInjector):
|
|
||||||
plugin_name = 'tower' # TODO: relying on routing for now, update after EEs pick up revised collection
|
|
||||||
base_injector = 'template'
|
|
||||||
namespace = 'awx'
|
|
||||||
collection = 'awx'
|
|
||||||
downstream_namespace = 'ansible'
|
|
||||||
downstream_collection = 'controller'
|
|
||||||
|
|
||||||
|
|
||||||
class insights(PluginFileInjector):
|
|
||||||
plugin_name = 'insights'
|
|
||||||
base_injector = 'template'
|
|
||||||
namespace = 'redhatinsights'
|
|
||||||
collection = 'insights'
|
|
||||||
downstream_namespace = 'redhat'
|
|
||||||
downstream_collection = 'insights'
|
|
||||||
use_fqcn = True
|
|
||||||
|
|
||||||
|
|
||||||
class openshift_virtualization(PluginFileInjector):
|
|
||||||
plugin_name = 'kubevirt'
|
|
||||||
base_injector = 'template'
|
|
||||||
namespace = 'kubevirt'
|
|
||||||
collection = 'core'
|
|
||||||
downstream_namespace = 'redhat'
|
|
||||||
downstream_collection = 'openshift_virtualization'
|
|
||||||
use_fqcn = True
|
|
||||||
|
|
||||||
|
|
||||||
class constructed(PluginFileInjector):
|
|
||||||
plugin_name = 'constructed'
|
|
||||||
namespace = 'ansible'
|
|
||||||
collection = 'builtin'
|
|
||||||
|
|
||||||
def build_env(self, *args, **kwargs):
|
|
||||||
env = super().build_env(*args, **kwargs)
|
|
||||||
# Enable script inventory plugin so we pick up the script files from source inventories
|
|
||||||
env['ANSIBLE_INVENTORY_ENABLED'] += ',script'
|
|
||||||
env['ANSIBLE_INVENTORY_ANY_UNPARSED_IS_FAILED'] = 'True'
|
|
||||||
return env
|
|
||||||
|
|
||||||
|
|
||||||
for cls in PluginFileInjector.__subclasses__():
|
for cls in PluginFileInjector.__subclasses__():
|
||||||
InventorySourceOptions.injectors[cls.__name__] = cls
|
InventorySourceOptions.injectors[cls.__name__] = cls
|
||||||
|
|||||||
@@ -396,11 +396,11 @@ class JobNotificationMixin(object):
|
|||||||
'verbosity': 0,
|
'verbosity': 0,
|
||||||
},
|
},
|
||||||
'job_friendly_name': 'Job',
|
'job_friendly_name': 'Job',
|
||||||
'url': 'https://towerhost/#/jobs/playbook/1010',
|
'url': 'https://platformhost/#/jobs/playbook/1010',
|
||||||
'approval_status': 'approved',
|
'approval_status': 'approved',
|
||||||
'approval_node_name': 'Approve Me',
|
'approval_node_name': 'Approve Me',
|
||||||
'workflow_url': 'https://towerhost/#/jobs/workflow/1010',
|
'workflow_url': 'https://platformhost/#/jobs/workflow/1010',
|
||||||
'job_metadata': """{'url': 'https://towerhost/$/jobs/playbook/13',
|
'job_metadata': """{'url': 'https://platformhost/$/jobs/playbook/13',
|
||||||
'traceback': '',
|
'traceback': '',
|
||||||
'status': 'running',
|
'status': 'running',
|
||||||
'started': '2019-08-07T21:46:38.362630+00:00',
|
'started': '2019-08-07T21:46:38.362630+00:00',
|
||||||
|
|||||||
@@ -689,9 +689,15 @@ def sync_parents_to_new_rbac(instance, action, model, pk_set, reverse, **kwargs)
|
|||||||
|
|
||||||
for role_id in pk_set:
|
for role_id in pk_set:
|
||||||
if reverse:
|
if reverse:
|
||||||
child_role = Role.objects.get(id=role_id)
|
try:
|
||||||
|
child_role = Role.objects.get(id=role_id)
|
||||||
|
except Role.DoesNotExist:
|
||||||
|
continue
|
||||||
else:
|
else:
|
||||||
parent_role = Role.objects.get(id=role_id)
|
try:
|
||||||
|
parent_role = Role.objects.get(id=role_id)
|
||||||
|
except Role.DoesNotExist:
|
||||||
|
continue
|
||||||
|
|
||||||
# To a fault, we want to avoid running this if triggered from implicit_parents management
|
# To a fault, we want to avoid running this if triggered from implicit_parents management
|
||||||
# we only want to do anything if we know for sure this is a non-implicit team role
|
# we only want to do anything if we know for sure this is a non-implicit team role
|
||||||
|
|||||||
@@ -405,10 +405,11 @@ class AWXReceptorJob:
|
|||||||
finally:
|
finally:
|
||||||
# Make sure to always release the work unit if we established it
|
# Make sure to always release the work unit if we established it
|
||||||
if self.unit_id is not None and settings.RECEPTOR_RELEASE_WORK:
|
if self.unit_id is not None and settings.RECEPTOR_RELEASE_WORK:
|
||||||
try:
|
if settings.RECPETOR_KEEP_WORK_ON_ERROR and getattr(res, 'status', 'error') == 'error':
|
||||||
receptor_ctl.simple_command(f"work release {self.unit_id}")
|
try:
|
||||||
except Exception:
|
receptor_ctl.simple_command(f"work release {self.unit_id}")
|
||||||
logger.exception(f"Error releasing work unit {self.unit_id}.")
|
except Exception:
|
||||||
|
logger.exception(f"Error releasing work unit {self.unit_id}.")
|
||||||
|
|
||||||
def _run_internal(self, receptor_ctl):
|
def _run_internal(self, receptor_ctl):
|
||||||
# Create a socketpair. Where the left side will be used for writing our payload
|
# Create a socketpair. Where the left side will be used for writing our payload
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ from awx.main.models import (
|
|||||||
Job,
|
Job,
|
||||||
convert_jsonfields,
|
convert_jsonfields,
|
||||||
)
|
)
|
||||||
from awx.main.constants import ACTIVE_STATES
|
from awx.main.constants import ACTIVE_STATES, ERROR_STATES
|
||||||
from awx.main.dispatch.publish import task
|
from awx.main.dispatch.publish import task
|
||||||
from awx.main.dispatch import get_task_queuename, reaper
|
from awx.main.dispatch import get_task_queuename, reaper
|
||||||
from awx.main.utils.common import ignore_inventory_computed_fields, ignore_inventory_group_removal
|
from awx.main.utils.common import ignore_inventory_computed_fields, ignore_inventory_group_removal
|
||||||
@@ -685,6 +685,8 @@ def awx_receptor_workunit_reaper():
|
|||||||
|
|
||||||
unit_ids = [id for id in receptor_work_list]
|
unit_ids = [id for id in receptor_work_list]
|
||||||
jobs_with_unreleased_receptor_units = UnifiedJob.objects.filter(work_unit_id__in=unit_ids).exclude(status__in=ACTIVE_STATES)
|
jobs_with_unreleased_receptor_units = UnifiedJob.objects.filter(work_unit_id__in=unit_ids).exclude(status__in=ACTIVE_STATES)
|
||||||
|
if settings.RECEPTOR_KEEP_WORK_ON_ERROR:
|
||||||
|
jobs_with_unreleased_receptor_units = jobs_with_unreleased_receptor_units.exclude(status__in=ERROR_STATES)
|
||||||
for job in jobs_with_unreleased_receptor_units:
|
for job in jobs_with_unreleased_receptor_units:
|
||||||
logger.debug(f"{job.log_format} is not active, reaping receptor work unit {job.work_unit_id}")
|
logger.debug(f"{job.log_format} is not active, reaping receptor work unit {job.work_unit_id}")
|
||||||
receptor_ctl.simple_command(f"work cancel {job.work_unit_id}")
|
receptor_ctl.simple_command(f"work cancel {job.work_unit_id}")
|
||||||
@@ -704,7 +706,10 @@ def awx_k8s_reaper():
|
|||||||
logger.debug("Checking for orphaned k8s pods for {}.".format(group))
|
logger.debug("Checking for orphaned k8s pods for {}.".format(group))
|
||||||
pods = PodManager.list_active_jobs(group)
|
pods = PodManager.list_active_jobs(group)
|
||||||
time_cutoff = now() - timedelta(seconds=settings.K8S_POD_REAPER_GRACE_PERIOD)
|
time_cutoff = now() - timedelta(seconds=settings.K8S_POD_REAPER_GRACE_PERIOD)
|
||||||
for job in UnifiedJob.objects.filter(pk__in=pods.keys(), finished__lte=time_cutoff).exclude(status__in=ACTIVE_STATES):
|
reap_job_candidates = UnifiedJob.objects.filter(pk__in=pods.keys(), finished__lte=time_cutoff).exclude(status__in=ACTIVE_STATES)
|
||||||
|
if settings.RECEPTOR_KEEP_WORK_ON_ERROR:
|
||||||
|
reap_job_candidates = reap_job_candidates.exclude(status__in=ERROR_STATES)
|
||||||
|
for job in reap_job_candidates:
|
||||||
logger.debug('{} is no longer active, reaping orphaned k8s pod'.format(job.log_format))
|
logger.debug('{} is no longer active, reaping orphaned k8s pod'.format(job.log_format))
|
||||||
try:
|
try:
|
||||||
pm = PodManager(job)
|
pm = PodManager(job)
|
||||||
@@ -980,5 +985,15 @@ def periodic_resource_sync():
|
|||||||
if acquired is False:
|
if acquired is False:
|
||||||
logger.debug("Not running periodic_resource_sync, another task holds lock")
|
logger.debug("Not running periodic_resource_sync, another task holds lock")
|
||||||
return
|
return
|
||||||
|
logger.debug("Running periodic resource sync")
|
||||||
|
|
||||||
SyncExecutor().run()
|
executor = SyncExecutor()
|
||||||
|
executor.run()
|
||||||
|
for key, item_list in executor.results.items():
|
||||||
|
if not item_list or key == 'noop':
|
||||||
|
continue
|
||||||
|
# Log creations and conflicts
|
||||||
|
if len(item_list) > 10 and settings.LOG_AGGREGATOR_LEVEL != 'DEBUG':
|
||||||
|
logger.info(f'Periodic resource sync {key}, first 10 items:\n{item_list[:10]}')
|
||||||
|
else:
|
||||||
|
logger.info(f'Periodic resource sync {key}:\n{item_list}')
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -68,13 +68,17 @@ def test_assign_managed_role(admin_user, alice, rando, inventory, post, setup_ma
|
|||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_assign_custom_delete_role(admin_user, rando, inventory, delete, patch):
|
def test_assign_custom_delete_role(admin_user, rando, inventory, delete, patch):
|
||||||
|
# TODO: just a delete_inventory, without change_inventory
|
||||||
rd, _ = RoleDefinition.objects.get_or_create(
|
rd, _ = RoleDefinition.objects.get_or_create(
|
||||||
name='inventory-delete', permissions=['delete_inventory', 'view_inventory'], content_type=ContentType.objects.get_for_model(Inventory)
|
name='inventory-delete',
|
||||||
|
permissions=['delete_inventory', 'view_inventory', 'change_inventory'],
|
||||||
|
content_type=ContentType.objects.get_for_model(Inventory),
|
||||||
)
|
)
|
||||||
rd.give_permission(rando, inventory)
|
rd.give_permission(rando, inventory)
|
||||||
inv_id = inventory.pk
|
inv_id = inventory.pk
|
||||||
inv_url = reverse('api:inventory_detail', kwargs={'pk': inv_id})
|
inv_url = reverse('api:inventory_detail', kwargs={'pk': inv_id})
|
||||||
patch(url=inv_url, data={"description": "new"}, user=rando, expect=403)
|
# TODO: eventually this will be valid test, for now ignore
|
||||||
|
# patch(url=inv_url, data={"description": "new"}, user=rando, expect=403)
|
||||||
delete(url=inv_url, user=rando, expect=202)
|
delete(url=inv_url, user=rando, expect=202)
|
||||||
assert Inventory.objects.get(id=inv_id).pending_deletion
|
assert Inventory.objects.get(id=inv_id).pending_deletion
|
||||||
|
|
||||||
@@ -128,7 +132,7 @@ def test_assign_credential_to_user_of_another_org(setup_managed_roles, credentia
|
|||||||
rd = RoleDefinition.objects.get(name="Credential Admin")
|
rd = RoleDefinition.objects.get(name="Credential Admin")
|
||||||
credential.organization = organization
|
credential.organization = organization
|
||||||
credential.save(update_fields=['organization'])
|
credential.save(update_fields=['organization'])
|
||||||
assert credential.organization not in Organization.access_qs(rando, 'change')
|
assert credential.organization not in Organization.access_qs(rando, 'member')
|
||||||
url = django_reverse('roleuserassignment-list')
|
url = django_reverse('roleuserassignment-list')
|
||||||
resp = post(url=url, data={"user": rando.id, "role_definition": rd.id, "object_id": credential.id}, user=admin_user, expect=400)
|
resp = post(url=url, data={"user": rando.id, "role_definition": rd.id, "object_id": credential.id}, user=admin_user, expect=400)
|
||||||
assert "You cannot grant credential access to a User not in the credentials' organization" in str(resp.data)
|
assert "You cannot grant credential access to a User not in the credentials' organization" in str(resp.data)
|
||||||
@@ -139,7 +143,7 @@ def test_assign_credential_to_user_of_another_org(setup_managed_roles, credentia
|
|||||||
post(url=url, data={"user": rando.id, "role_definition": rd.id, "object_id": credential.id}, user=admin_user, expect=201)
|
post(url=url, data={"user": rando.id, "role_definition": rd.id, "object_id": credential.id}, user=admin_user, expect=201)
|
||||||
|
|
||||||
# can assign credential to org_admin
|
# can assign credential to org_admin
|
||||||
assert credential.organization in Organization.access_qs(org_admin, 'change')
|
assert credential.organization in Organization.access_qs(org_admin, 'member')
|
||||||
post(url=url, data={"user": org_admin.id, "role_definition": rd.id, "object_id": credential.id}, user=admin_user, expect=201)
|
post(url=url, data={"user": org_admin.id, "role_definition": rd.id, "object_id": credential.id}, user=admin_user, expect=201)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
from unittest import mock
|
from unittest import mock
|
||||||
|
import json
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
@@ -6,11 +7,13 @@ from django.contrib.contenttypes.models import ContentType
|
|||||||
|
|
||||||
from crum import impersonate
|
from crum import impersonate
|
||||||
|
|
||||||
from awx.main.models.rbac import get_role_from_object_role, give_creator_permissions
|
from awx.main.fields import ImplicitRoleField
|
||||||
|
from awx.main.models.rbac import get_role_from_object_role, give_creator_permissions, get_role_codenames, get_role_definition
|
||||||
from awx.main.models import User, Organization, WorkflowJobTemplate, WorkflowJobTemplateNode, Team
|
from awx.main.models import User, Organization, WorkflowJobTemplate, WorkflowJobTemplateNode, Team
|
||||||
from awx.api.versioning import reverse
|
from awx.api.versioning import reverse
|
||||||
|
|
||||||
from ansible_base.rbac.models import RoleUserAssignment, RoleDefinition
|
from ansible_base.rbac.models import RoleUserAssignment, RoleDefinition
|
||||||
|
from ansible_base.rbac import permission_registry
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
@@ -24,6 +27,7 @@ from ansible_base.rbac.models import RoleUserAssignment, RoleDefinition
|
|||||||
'auditor_role',
|
'auditor_role',
|
||||||
'read_role',
|
'read_role',
|
||||||
'execute_role',
|
'execute_role',
|
||||||
|
'approval_role',
|
||||||
'notification_admin_role',
|
'notification_admin_role',
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
@@ -39,6 +43,37 @@ def test_round_trip_roles(organization, rando, role_name, setup_managed_roles):
|
|||||||
assert old_role.id == getattr(organization, role_name).id
|
assert old_role.id == getattr(organization, role_name).id
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.django_db
|
||||||
|
@pytest.mark.parametrize('model', sorted(permission_registry.all_registered_models, key=lambda cls: cls._meta.model_name))
|
||||||
|
def test_role_migration_matches(request, model, setup_managed_roles):
|
||||||
|
fixture_name = model._meta.verbose_name.replace(' ', '_')
|
||||||
|
obj = request.getfixturevalue(fixture_name)
|
||||||
|
role_ct = 0
|
||||||
|
for field in obj._meta.get_fields():
|
||||||
|
if isinstance(field, ImplicitRoleField):
|
||||||
|
if field.name == 'read_role':
|
||||||
|
continue # intentionally left as "Compat" roles
|
||||||
|
role_ct += 1
|
||||||
|
old_role = getattr(obj, field.name)
|
||||||
|
old_codenames = set(get_role_codenames(old_role))
|
||||||
|
rd = get_role_definition(old_role)
|
||||||
|
new_codenames = set(rd.permissions.values_list('codename', flat=True))
|
||||||
|
# all the old roles should map to a non-Compat role definition
|
||||||
|
if 'Compat' not in rd.name:
|
||||||
|
model_rds = RoleDefinition.objects.filter(content_type=ContentType.objects.get_for_model(obj))
|
||||||
|
rd_data = {}
|
||||||
|
for rd in model_rds:
|
||||||
|
rd_data[rd.name] = list(rd.permissions.values_list('codename', flat=True))
|
||||||
|
assert (
|
||||||
|
'Compat' not in rd.name
|
||||||
|
), f'Permissions for old vs new roles did not match.\nold {field.name}: {old_codenames}\nnew:\n{json.dumps(rd_data, indent=2)}'
|
||||||
|
assert new_codenames == set(old_codenames)
|
||||||
|
|
||||||
|
# In the old system these models did not have object-level roles, all others expect some model roles
|
||||||
|
if model._meta.model_name not in ('notificationtemplate', 'executionenvironment'):
|
||||||
|
assert role_ct > 0
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_role_naming(setup_managed_roles):
|
def test_role_naming(setup_managed_roles):
|
||||||
qs = RoleDefinition.objects.filter(content_type=ContentType.objects.get(model='jobtemplate'), name__endswith='dmin')
|
qs = RoleDefinition.objects.filter(content_type=ContentType.objects.get(model='jobtemplate'), name__endswith='dmin')
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import pytest
|
|||||||
from django.contrib.contenttypes.models import ContentType
|
from django.contrib.contenttypes.models import ContentType
|
||||||
|
|
||||||
from awx.main.access import ExecutionEnvironmentAccess
|
from awx.main.access import ExecutionEnvironmentAccess
|
||||||
from awx.main.models import ExecutionEnvironment, Organization
|
from awx.main.models import ExecutionEnvironment, Organization, Team
|
||||||
from awx.main.models.rbac import get_role_codenames
|
from awx.main.models.rbac import get_role_codenames
|
||||||
|
|
||||||
from awx.api.versioning import reverse
|
from awx.api.versioning import reverse
|
||||||
@@ -63,6 +63,11 @@ def check_user_capabilities(get, setup_managed_roles):
|
|||||||
# ___ begin tests ___
|
# ___ begin tests ___
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.django_db
|
||||||
|
def test_any_user_can_view_global_ee(control_plane_execution_environment, rando):
|
||||||
|
assert ExecutionEnvironmentAccess(rando).can_read(control_plane_execution_environment)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_managed_ee_not_assignable(control_plane_execution_environment, ee_rd, rando, admin_user, post):
|
def test_managed_ee_not_assignable(control_plane_execution_environment, ee_rd, rando, admin_user, post):
|
||||||
url = django_reverse('roleuserassignment-list')
|
url = django_reverse('roleuserassignment-list')
|
||||||
@@ -77,6 +82,21 @@ def test_org_member_required_for_assignment(org_ee, ee_rd, rando, admin_user, po
|
|||||||
assert 'User must have view permission to Execution Environment organization' in str(r.data)
|
assert 'User must have view permission to Execution Environment organization' in str(r.data)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.django_db
|
||||||
|
def test_team_can_have_permission(org_ee, ee_rd, rando, admin_user, post):
|
||||||
|
org2 = Organization.objects.create(name='a different team')
|
||||||
|
team = Team.objects.create(name='a team', organization=org2)
|
||||||
|
team.member_role.members.add(rando)
|
||||||
|
assert org_ee not in ExecutionEnvironmentAccess(rando).get_queryset() # user can not view the EE
|
||||||
|
|
||||||
|
url = django_reverse('roleteamassignment-list')
|
||||||
|
|
||||||
|
# can give object roles to the team now
|
||||||
|
post(url, {'role_definition': ee_rd.pk, 'team': team.id, 'object_id': org_ee.pk}, user=admin_user, expect=201)
|
||||||
|
assert rando.has_obj_perm(org_ee, 'change')
|
||||||
|
assert org_ee in ExecutionEnvironmentAccess(rando).get_queryset() # user can view the EE now
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_give_object_permission_to_ee(org_ee, ee_rd, org_member, check_user_capabilities):
|
def test_give_object_permission_to_ee(org_ee, ee_rd, org_member, check_user_capabilities):
|
||||||
access = ExecutionEnvironmentAccess(org_member)
|
access = ExecutionEnvironmentAccess(org_member)
|
||||||
@@ -85,11 +105,29 @@ def test_give_object_permission_to_ee(org_ee, ee_rd, org_member, check_user_capa
|
|||||||
check_user_capabilities(org_member, org_ee, {'edit': False, 'delete': False, 'copy': False})
|
check_user_capabilities(org_member, org_ee, {'edit': False, 'delete': False, 'copy': False})
|
||||||
|
|
||||||
ee_rd.give_permission(org_member, org_ee)
|
ee_rd.give_permission(org_member, org_ee)
|
||||||
assert access.can_change(org_ee, {'name': 'new'})
|
assert access.can_change(org_ee, {'name': 'new', 'organization': org_ee.organization.id})
|
||||||
|
|
||||||
check_user_capabilities(org_member, org_ee, {'edit': True, 'delete': True, 'copy': False})
|
check_user_capabilities(org_member, org_ee, {'edit': True, 'delete': True, 'copy': False})
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.django_db
|
||||||
|
def test_need_related_organization_access(org_ee, ee_rd, org_member):
|
||||||
|
org2 = Organization.objects.create(name='another organization')
|
||||||
|
ee_rd.give_permission(org_member, org_ee)
|
||||||
|
org2.member_role.members.add(org_member)
|
||||||
|
access = ExecutionEnvironmentAccess(org_member)
|
||||||
|
assert access.can_change(org_ee, {'name': 'new', 'organization': org_ee.organization})
|
||||||
|
assert access.can_change(org_ee, {'name': 'new', 'organization': org_ee.organization.id})
|
||||||
|
assert not access.can_change(org_ee, {'name': 'new', 'organization': org2.id})
|
||||||
|
assert not access.can_change(org_ee, {'name': 'new', 'organization': org2})
|
||||||
|
|
||||||
|
# User can make the change if they have relevant permission to the new organization
|
||||||
|
org_ee.organization.execution_environment_admin_role.members.add(org_member)
|
||||||
|
org2.execution_environment_admin_role.members.add(org_member)
|
||||||
|
assert access.can_change(org_ee, {'name': 'new', 'organization': org2.id})
|
||||||
|
assert access.can_change(org_ee, {'name': 'new', 'organization': org2})
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
@pytest.mark.parametrize('style', ['new', 'old'])
|
@pytest.mark.parametrize('style', ['new', 'old'])
|
||||||
def test_give_org_permission_to_ee(org_ee, organization, org_member, check_user_capabilities, style, org_ee_rd):
|
def test_give_org_permission_to_ee(org_ee, organization, org_member, check_user_capabilities, style, org_ee_rd):
|
||||||
@@ -103,5 +141,8 @@ def test_give_org_permission_to_ee(org_ee, organization, org_member, check_user_
|
|||||||
else:
|
else:
|
||||||
organization.execution_environment_admin_role.members.add(org_member)
|
organization.execution_environment_admin_role.members.add(org_member)
|
||||||
|
|
||||||
assert access.can_change(org_ee, {'name': 'new'})
|
assert access.can_change(org_ee, {'name': 'new', 'organization': organization.id})
|
||||||
check_user_capabilities(org_member, org_ee, {'edit': True, 'delete': True, 'copy': True})
|
check_user_capabilities(org_member, org_ee, {'edit': True, 'delete': True, 'copy': True})
|
||||||
|
|
||||||
|
# Extra check, user can not remove the EE from the organization
|
||||||
|
assert not access.can_change(org_ee, {'name': 'new', 'organization': None})
|
||||||
|
|||||||
@@ -48,3 +48,17 @@ def test_org_resource_role(ext_auth, organization, rando, org_admin):
|
|||||||
assert access.can_attach(organization, rando, 'member_role.members') == ext_auth
|
assert access.can_attach(organization, rando, 'member_role.members') == ext_auth
|
||||||
organization.member_role.members.add(rando)
|
organization.member_role.members.add(rando)
|
||||||
assert access.can_unattach(organization, rando, 'member_role.members') == ext_auth
|
assert access.can_unattach(organization, rando, 'member_role.members') == ext_auth
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.django_db
|
||||||
|
def test_delete_org_while_workflow_active(workflow_job_template):
|
||||||
|
'''
|
||||||
|
Delete org while workflow job is active (i.e. changing status)
|
||||||
|
'''
|
||||||
|
assert workflow_job_template.organization # sanity check
|
||||||
|
wj = workflow_job_template.create_unified_job() # status should be new
|
||||||
|
workflow_job_template.organization.delete()
|
||||||
|
wj.refresh_from_db()
|
||||||
|
assert wj.status != 'pending' # sanity check
|
||||||
|
wj.status = 'pending' # status needs to change in order to trigger workflow_job_template.save()
|
||||||
|
wj.save(update_fields=['status'])
|
||||||
|
|||||||
@@ -17,13 +17,13 @@ def advisory_lock(*args, lock_session_timeout_milliseconds=0, **kwargs):
|
|||||||
with connection.cursor() as cur:
|
with connection.cursor() as cur:
|
||||||
idle_in_transaction_session_timeout = cur.execute('SHOW idle_in_transaction_session_timeout').fetchone()[0]
|
idle_in_transaction_session_timeout = cur.execute('SHOW idle_in_transaction_session_timeout').fetchone()[0]
|
||||||
idle_session_timeout = cur.execute('SHOW idle_session_timeout').fetchone()[0]
|
idle_session_timeout = cur.execute('SHOW idle_session_timeout').fetchone()[0]
|
||||||
cur.execute(f"SET idle_in_transaction_session_timeout = {lock_session_timeout_milliseconds}")
|
cur.execute(f"SET idle_in_transaction_session_timeout = '{lock_session_timeout_milliseconds}'")
|
||||||
cur.execute(f"SET idle_session_timeout = {lock_session_timeout_milliseconds}")
|
cur.execute(f"SET idle_session_timeout = '{lock_session_timeout_milliseconds}'")
|
||||||
with django_pglocks_advisory_lock(*args, **kwargs) as internal_lock:
|
with django_pglocks_advisory_lock(*args, **kwargs) as internal_lock:
|
||||||
yield internal_lock
|
yield internal_lock
|
||||||
if lock_session_timeout_milliseconds > 0:
|
if lock_session_timeout_milliseconds > 0:
|
||||||
with connection.cursor() as cur:
|
with connection.cursor() as cur:
|
||||||
cur.execute(f"SET idle_in_transaction_session_timeout = {idle_in_transaction_session_timeout}")
|
cur.execute(f"SET idle_in_transaction_session_timeout = '{idle_in_transaction_session_timeout}'")
|
||||||
cur.execute(f"SET idle_session_timeout = {idle_session_timeout}")
|
cur.execute(f"SET idle_session_timeout = '{idle_session_timeout}'")
|
||||||
else:
|
else:
|
||||||
yield True
|
yield True
|
||||||
|
|||||||
@@ -47,7 +47,6 @@ class WebsocketRelayConnection:
|
|||||||
verify_ssl: bool = settings.BROADCAST_WEBSOCKET_VERIFY_CERT,
|
verify_ssl: bool = settings.BROADCAST_WEBSOCKET_VERIFY_CERT,
|
||||||
):
|
):
|
||||||
self.name = name
|
self.name = name
|
||||||
self.event_loop = asyncio.get_event_loop()
|
|
||||||
self.stats = stats
|
self.stats = stats
|
||||||
self.remote_host = remote_host
|
self.remote_host = remote_host
|
||||||
self.remote_port = remote_port
|
self.remote_port = remote_port
|
||||||
@@ -110,7 +109,10 @@ class WebsocketRelayConnection:
|
|||||||
self.stats.record_connection_lost()
|
self.stats.record_connection_lost()
|
||||||
|
|
||||||
def start(self):
|
def start(self):
|
||||||
self.async_task = self.event_loop.create_task(self.connect())
|
self.async_task = asyncio.get_running_loop().create_task(
|
||||||
|
self.connect(),
|
||||||
|
name=f"WebsocketRelayConnection.connect.{self.name}",
|
||||||
|
)
|
||||||
return self.async_task
|
return self.async_task
|
||||||
|
|
||||||
def cancel(self):
|
def cancel(self):
|
||||||
@@ -121,7 +123,10 @@ class WebsocketRelayConnection:
|
|||||||
# metrics messages
|
# metrics messages
|
||||||
# the "metrics" group is not subscribed to in the typical fashion, so we
|
# the "metrics" group is not subscribed to in the typical fashion, so we
|
||||||
# just explicitly create it
|
# just explicitly create it
|
||||||
producer = self.event_loop.create_task(self.run_producer("metrics", websocket, "metrics"))
|
producer = asyncio.get_running_loop().create_task(
|
||||||
|
self.run_producer("metrics", websocket, "metrics"),
|
||||||
|
name="WebsocketRelayConnection.run_producer.metrics",
|
||||||
|
)
|
||||||
self.producers["metrics"] = {"task": producer, "subscriptions": {"metrics"}}
|
self.producers["metrics"] = {"task": producer, "subscriptions": {"metrics"}}
|
||||||
async for msg in websocket:
|
async for msg in websocket:
|
||||||
self.stats.record_message_received()
|
self.stats.record_message_received()
|
||||||
@@ -143,7 +148,10 @@ class WebsocketRelayConnection:
|
|||||||
name = f"{self.remote_host}-{group}"
|
name = f"{self.remote_host}-{group}"
|
||||||
origin_channel = payload['origin_channel']
|
origin_channel = payload['origin_channel']
|
||||||
if not self.producers.get(name):
|
if not self.producers.get(name):
|
||||||
producer = self.event_loop.create_task(self.run_producer(name, websocket, group))
|
producer = asyncio.get_running_loop().create_task(
|
||||||
|
self.run_producer(name, websocket, group),
|
||||||
|
name=f"WebsocketRelayConnection.run_producer.{name}",
|
||||||
|
)
|
||||||
self.producers[name] = {"task": producer, "subscriptions": {origin_channel}}
|
self.producers[name] = {"task": producer, "subscriptions": {origin_channel}}
|
||||||
logger.debug(f"Producer {name} started.")
|
logger.debug(f"Producer {name} started.")
|
||||||
else:
|
else:
|
||||||
@@ -297,9 +305,7 @@ class WebSocketRelayManager(object):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
async def run(self):
|
async def run(self):
|
||||||
event_loop = asyncio.get_running_loop()
|
self.stats_mgr = RelayWebsocketStatsManager(self.local_hostname)
|
||||||
|
|
||||||
self.stats_mgr = RelayWebsocketStatsManager(event_loop, self.local_hostname)
|
|
||||||
self.stats_mgr.start()
|
self.stats_mgr.start()
|
||||||
|
|
||||||
database_conf = deepcopy(settings.DATABASES['default'])
|
database_conf = deepcopy(settings.DATABASES['default'])
|
||||||
@@ -323,7 +329,10 @@ class WebSocketRelayManager(object):
|
|||||||
)
|
)
|
||||||
|
|
||||||
await async_conn.set_autocommit(True)
|
await async_conn.set_autocommit(True)
|
||||||
on_ws_heartbeat_task = event_loop.create_task(self.on_ws_heartbeat(async_conn))
|
on_ws_heartbeat_task = asyncio.get_running_loop().create_task(
|
||||||
|
self.on_ws_heartbeat(async_conn),
|
||||||
|
name="WebSocketRelayManager.on_ws_heartbeat",
|
||||||
|
)
|
||||||
|
|
||||||
# Establishes a websocket connection to /websocket/relay on all API servers
|
# Establishes a websocket connection to /websocket/relay on all API servers
|
||||||
while True:
|
while True:
|
||||||
|
|||||||
@@ -828,7 +828,7 @@ MANAGE_ORGANIZATION_AUTH = True
|
|||||||
DISABLE_LOCAL_AUTH = False
|
DISABLE_LOCAL_AUTH = False
|
||||||
|
|
||||||
# Note: This setting may be overridden by database settings.
|
# Note: This setting may be overridden by database settings.
|
||||||
TOWER_URL_BASE = "https://towerhost"
|
TOWER_URL_BASE = "https://platformhost"
|
||||||
|
|
||||||
INSIGHTS_URL_BASE = "https://example.org"
|
INSIGHTS_URL_BASE = "https://example.org"
|
||||||
INSIGHTS_AGENT_MIME = 'application/example'
|
INSIGHTS_AGENT_MIME = 'application/example'
|
||||||
@@ -1009,6 +1009,7 @@ AWX_RUNNER_KEEPALIVE_SECONDS = 0
|
|||||||
|
|
||||||
# Delete completed work units in receptor
|
# Delete completed work units in receptor
|
||||||
RECEPTOR_RELEASE_WORK = True
|
RECEPTOR_RELEASE_WORK = True
|
||||||
|
RECPETOR_KEEP_WORK_ON_ERROR = False
|
||||||
|
|
||||||
# K8S only. Use receptor_log_level on AWX spec to set this properly
|
# K8S only. Use receptor_log_level on AWX spec to set this properly
|
||||||
RECEPTOR_LOG_LEVEL = 'info'
|
RECEPTOR_LOG_LEVEL = 'info'
|
||||||
|
|||||||
@@ -64,7 +64,7 @@
|
|||||||
<div class="col-sm-6">
|
<div class="col-sm-6">
|
||||||
</div>
|
</div>
|
||||||
<div class="col-sm-6 footer-copyright">
|
<div class="col-sm-6 footer-copyright">
|
||||||
Copyright © 2021 <a href="http://www.redhat.com" target="_blank">Red Hat</a>, Inc. All Rights Reserved.
|
Copyright © 2024 <a href="http://www.redhat.com" target="_blank">Red Hat</a>, Inc. All Rights Reserved.
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -59,7 +59,7 @@ function ActivityStream() {
|
|||||||
{
|
{
|
||||||
page: 1,
|
page: 1,
|
||||||
page_size: 20,
|
page_size: 20,
|
||||||
order_by: '-timestamp',
|
order_by: '-id',
|
||||||
},
|
},
|
||||||
['id', 'page', 'page_size']
|
['id', 'page', 'page_size']
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -89,7 +89,7 @@
|
|||||||
"LC_ALL": "en_US.UTF-8",
|
"LC_ALL": "en_US.UTF-8",
|
||||||
"MFLAGS": "-w",
|
"MFLAGS": "-w",
|
||||||
"OLDPWD": "/awx_devel",
|
"OLDPWD": "/awx_devel",
|
||||||
"AWX_HOST": "https://towerhost",
|
"AWX_HOST": "https://platformhost",
|
||||||
"HOSTNAME": "awx",
|
"HOSTNAME": "awx",
|
||||||
"LANGUAGE": "en_US:en",
|
"LANGUAGE": "en_US:en",
|
||||||
"SDB_HOST": "0.0.0.0",
|
"SDB_HOST": "0.0.0.0",
|
||||||
|
|||||||
@@ -89,7 +89,7 @@
|
|||||||
"LC_ALL": "en_US.UTF-8",
|
"LC_ALL": "en_US.UTF-8",
|
||||||
"MFLAGS": "-w",
|
"MFLAGS": "-w",
|
||||||
"OLDPWD": "/awx_devel",
|
"OLDPWD": "/awx_devel",
|
||||||
"AWX_HOST": "https://towerhost",
|
"AWX_HOST": "https://platformhost",
|
||||||
"HOSTNAME": "awx",
|
"HOSTNAME": "awx",
|
||||||
"LANGUAGE": "en_US:en",
|
"LANGUAGE": "en_US:en",
|
||||||
"SDB_HOST": "0.0.0.0",
|
"SDB_HOST": "0.0.0.0",
|
||||||
|
|||||||
@@ -164,7 +164,7 @@
|
|||||||
"ANSIBLE_RETRY_FILES_ENABLED": "False",
|
"ANSIBLE_RETRY_FILES_ENABLED": "False",
|
||||||
"MAX_EVENT_RES": "700000",
|
"MAX_EVENT_RES": "700000",
|
||||||
"ANSIBLE_CALLBACK_PLUGINS": "/awx_devel/awx/plugins/callback",
|
"ANSIBLE_CALLBACK_PLUGINS": "/awx_devel/awx/plugins/callback",
|
||||||
"AWX_HOST": "https://towerhost",
|
"AWX_HOST": "https://platformhost",
|
||||||
"ANSIBLE_SSH_CONTROL_PATH_DIR": "/tmp/awx_2_a4b1afiw/cp",
|
"ANSIBLE_SSH_CONTROL_PATH_DIR": "/tmp/awx_2_a4b1afiw/cp",
|
||||||
"ANSIBLE_STDOUT_CALLBACK": "awx_display"
|
"ANSIBLE_STDOUT_CALLBACK": "awx_display"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ describe('<AzureAD />', () => {
|
|||||||
SettingsAPI.readCategory.mockResolvedValue({
|
SettingsAPI.readCategory.mockResolvedValue({
|
||||||
data: {
|
data: {
|
||||||
SOCIAL_AUTH_AZUREAD_OAUTH2_CALLBACK_URL:
|
SOCIAL_AUTH_AZUREAD_OAUTH2_CALLBACK_URL:
|
||||||
'https://towerhost/sso/complete/azuread-oauth2/',
|
'https://platformhost/sso/complete/azuread-oauth2/',
|
||||||
SOCIAL_AUTH_AZUREAD_OAUTH2_KEY: 'mock key',
|
SOCIAL_AUTH_AZUREAD_OAUTH2_KEY: 'mock key',
|
||||||
SOCIAL_AUTH_AZUREAD_OAUTH2_SECRET: '$encrypted$',
|
SOCIAL_AUTH_AZUREAD_OAUTH2_SECRET: '$encrypted$',
|
||||||
SOCIAL_AUTH_AZUREAD_OAUTH2_ORGANIZATION_MAP: {},
|
SOCIAL_AUTH_AZUREAD_OAUTH2_ORGANIZATION_MAP: {},
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ describe('<AzureADDetail />', () => {
|
|||||||
SettingsAPI.readCategory.mockResolvedValue({
|
SettingsAPI.readCategory.mockResolvedValue({
|
||||||
data: {
|
data: {
|
||||||
SOCIAL_AUTH_AZUREAD_OAUTH2_CALLBACK_URL:
|
SOCIAL_AUTH_AZUREAD_OAUTH2_CALLBACK_URL:
|
||||||
'https://towerhost/sso/complete/azuread-oauth2/',
|
'https://platformhost/sso/complete/azuread-oauth2/',
|
||||||
SOCIAL_AUTH_AZUREAD_OAUTH2_KEY: 'mock key',
|
SOCIAL_AUTH_AZUREAD_OAUTH2_KEY: 'mock key',
|
||||||
SOCIAL_AUTH_AZUREAD_OAUTH2_SECRET: '$encrypted$',
|
SOCIAL_AUTH_AZUREAD_OAUTH2_SECRET: '$encrypted$',
|
||||||
SOCIAL_AUTH_AZUREAD_OAUTH2_ORGANIZATION_MAP: {},
|
SOCIAL_AUTH_AZUREAD_OAUTH2_ORGANIZATION_MAP: {},
|
||||||
@@ -62,7 +62,7 @@ describe('<AzureADDetail />', () => {
|
|||||||
assertDetail(
|
assertDetail(
|
||||||
wrapper,
|
wrapper,
|
||||||
'Azure AD OAuth2 Callback URL',
|
'Azure AD OAuth2 Callback URL',
|
||||||
'https://towerhost/sso/complete/azuread-oauth2/'
|
'https://platformhost/sso/complete/azuread-oauth2/'
|
||||||
);
|
);
|
||||||
assertDetail(wrapper, 'Azure AD OAuth2 Key', 'mock key');
|
assertDetail(wrapper, 'Azure AD OAuth2 Key', 'mock key');
|
||||||
assertDetail(wrapper, 'Azure AD OAuth2 Secret', 'Encrypted');
|
assertDetail(wrapper, 'Azure AD OAuth2 Secret', 'Encrypted');
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ describe('<AzureADEdit />', () => {
|
|||||||
SettingsAPI.readCategory.mockResolvedValue({
|
SettingsAPI.readCategory.mockResolvedValue({
|
||||||
data: {
|
data: {
|
||||||
SOCIAL_AUTH_AZUREAD_OAUTH2_CALLBACK_URL:
|
SOCIAL_AUTH_AZUREAD_OAUTH2_CALLBACK_URL:
|
||||||
'https://towerhost/sso/complete/azuread-oauth2/',
|
'https://platformhost/sso/complete/azuread-oauth2/',
|
||||||
SOCIAL_AUTH_AZUREAD_OAUTH2_KEY: 'mock key',
|
SOCIAL_AUTH_AZUREAD_OAUTH2_KEY: 'mock key',
|
||||||
SOCIAL_AUTH_AZUREAD_OAUTH2_SECRET: '$encrypted$',
|
SOCIAL_AUTH_AZUREAD_OAUTH2_SECRET: '$encrypted$',
|
||||||
SOCIAL_AUTH_AZUREAD_OAUTH2_ORGANIZATION_MAP: {},
|
SOCIAL_AUTH_AZUREAD_OAUTH2_ORGANIZATION_MAP: {},
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ describe('<GitHub />', () => {
|
|||||||
SettingsAPI.readCategory.mockResolvedValueOnce({
|
SettingsAPI.readCategory.mockResolvedValueOnce({
|
||||||
data: {
|
data: {
|
||||||
SOCIAL_AUTH_GITHUB_CALLBACK_URL:
|
SOCIAL_AUTH_GITHUB_CALLBACK_URL:
|
||||||
'https://towerhost/sso/complete/github/',
|
'https://platformhost/sso/complete/github/',
|
||||||
SOCIAL_AUTH_GITHUB_KEY: 'mock github key',
|
SOCIAL_AUTH_GITHUB_KEY: 'mock github key',
|
||||||
SOCIAL_AUTH_GITHUB_SECRET: '$encrypted$',
|
SOCIAL_AUTH_GITHUB_SECRET: '$encrypted$',
|
||||||
SOCIAL_AUTH_GITHUB_ORGANIZATION_MAP: null,
|
SOCIAL_AUTH_GITHUB_ORGANIZATION_MAP: null,
|
||||||
@@ -29,7 +29,7 @@ describe('<GitHub />', () => {
|
|||||||
SettingsAPI.readCategory.mockResolvedValueOnce({
|
SettingsAPI.readCategory.mockResolvedValueOnce({
|
||||||
data: {
|
data: {
|
||||||
SOCIAL_AUTH_GITHUB_ORG_CALLBACK_URL:
|
SOCIAL_AUTH_GITHUB_ORG_CALLBACK_URL:
|
||||||
'https://towerhost/sso/complete/github-org/',
|
'https://platformhost/sso/complete/github-org/',
|
||||||
SOCIAL_AUTH_GITHUB_ORG_KEY: '',
|
SOCIAL_AUTH_GITHUB_ORG_KEY: '',
|
||||||
SOCIAL_AUTH_GITHUB_ORG_SECRET: '$encrypted$',
|
SOCIAL_AUTH_GITHUB_ORG_SECRET: '$encrypted$',
|
||||||
SOCIAL_AUTH_GITHUB_ORG_NAME: '',
|
SOCIAL_AUTH_GITHUB_ORG_NAME: '',
|
||||||
@@ -40,7 +40,7 @@ describe('<GitHub />', () => {
|
|||||||
SettingsAPI.readCategory.mockResolvedValueOnce({
|
SettingsAPI.readCategory.mockResolvedValueOnce({
|
||||||
data: {
|
data: {
|
||||||
SOCIAL_AUTH_GITHUB_TEAM_CALLBACK_URL:
|
SOCIAL_AUTH_GITHUB_TEAM_CALLBACK_URL:
|
||||||
'https://towerhost/sso/complete/github-team/',
|
'https://platformhost/sso/complete/github-team/',
|
||||||
SOCIAL_AUTH_GITHUB_TEAM_KEY: 'OAuth2 key (Client ID)',
|
SOCIAL_AUTH_GITHUB_TEAM_KEY: 'OAuth2 key (Client ID)',
|
||||||
SOCIAL_AUTH_GITHUB_TEAM_SECRET: '$encrypted$',
|
SOCIAL_AUTH_GITHUB_TEAM_SECRET: '$encrypted$',
|
||||||
SOCIAL_AUTH_GITHUB_TEAM_ID: 'team_id',
|
SOCIAL_AUTH_GITHUB_TEAM_ID: 'team_id',
|
||||||
@@ -51,7 +51,7 @@ describe('<GitHub />', () => {
|
|||||||
SettingsAPI.readCategory.mockResolvedValueOnce({
|
SettingsAPI.readCategory.mockResolvedValueOnce({
|
||||||
data: {
|
data: {
|
||||||
SOCIAL_AUTH_GITHUB_ENTERPRISE_CALLBACK_URL:
|
SOCIAL_AUTH_GITHUB_ENTERPRISE_CALLBACK_URL:
|
||||||
'https://towerhost/sso/complete/github-enterprise/',
|
'https://platformhost/sso/complete/github-enterprise/',
|
||||||
SOCIAL_AUTH_GITHUB_ENTERPRISE_URL: 'https://localhost/url',
|
SOCIAL_AUTH_GITHUB_ENTERPRISE_URL: 'https://localhost/url',
|
||||||
SOCIAL_AUTH_GITHUB_ENTERPRISE_API_URL: 'https://localhost/apiurl',
|
SOCIAL_AUTH_GITHUB_ENTERPRISE_API_URL: 'https://localhost/apiurl',
|
||||||
SOCIAL_AUTH_GITHUB_ENTERPRISE_KEY: 'ent_key',
|
SOCIAL_AUTH_GITHUB_ENTERPRISE_KEY: 'ent_key',
|
||||||
@@ -63,7 +63,7 @@ describe('<GitHub />', () => {
|
|||||||
SettingsAPI.readCategory.mockResolvedValueOnce({
|
SettingsAPI.readCategory.mockResolvedValueOnce({
|
||||||
data: {
|
data: {
|
||||||
SOCIAL_AUTH_GITHUB_ENTERPRISE_ORG_CALLBACK_URL:
|
SOCIAL_AUTH_GITHUB_ENTERPRISE_ORG_CALLBACK_URL:
|
||||||
'https://towerhost/sso/complete/github-enterprise-org/',
|
'https://platformhost/sso/complete/github-enterprise-org/',
|
||||||
SOCIAL_AUTH_GITHUB_ENTERPRISE_ORG_URL: 'https://localhost/url',
|
SOCIAL_AUTH_GITHUB_ENTERPRISE_ORG_URL: 'https://localhost/url',
|
||||||
SOCIAL_AUTH_GITHUB_ENTERPRISE_ORG_API_URL: 'https://localhost/apiurl',
|
SOCIAL_AUTH_GITHUB_ENTERPRISE_ORG_API_URL: 'https://localhost/apiurl',
|
||||||
SOCIAL_AUTH_GITHUB_ENTERPRISE_ORG_KEY: 'ent_org_key',
|
SOCIAL_AUTH_GITHUB_ENTERPRISE_ORG_KEY: 'ent_org_key',
|
||||||
@@ -76,7 +76,7 @@ describe('<GitHub />', () => {
|
|||||||
SettingsAPI.readCategory.mockResolvedValueOnce({
|
SettingsAPI.readCategory.mockResolvedValueOnce({
|
||||||
data: {
|
data: {
|
||||||
SOCIAL_AUTH_GITHUB_ENTERPRISE_TEAM_CALLBACK_URL:
|
SOCIAL_AUTH_GITHUB_ENTERPRISE_TEAM_CALLBACK_URL:
|
||||||
'https://towerhost/sso/complete/github-enterprise-team/',
|
'https://platformhost/sso/complete/github-enterprise-team/',
|
||||||
SOCIAL_AUTH_GITHUB_ENTERPRISE_TEAM_URL: 'https://localhost/url',
|
SOCIAL_AUTH_GITHUB_ENTERPRISE_TEAM_URL: 'https://localhost/url',
|
||||||
SOCIAL_AUTH_GITHUB_ENTERPRISE_TEAM_API_URL: 'https://localhost/apiurl',
|
SOCIAL_AUTH_GITHUB_ENTERPRISE_TEAM_API_URL: 'https://localhost/apiurl',
|
||||||
SOCIAL_AUTH_GITHUB_ENTERPRISE_TEAM_KEY: 'ent_team_key',
|
SOCIAL_AUTH_GITHUB_ENTERPRISE_TEAM_KEY: 'ent_team_key',
|
||||||
|
|||||||
@@ -22,7 +22,8 @@ jest.mock('../../../../api');
|
|||||||
|
|
||||||
const mockDefault = {
|
const mockDefault = {
|
||||||
data: {
|
data: {
|
||||||
SOCIAL_AUTH_GITHUB_CALLBACK_URL: 'https://towerhost/sso/complete/github/',
|
SOCIAL_AUTH_GITHUB_CALLBACK_URL:
|
||||||
|
'https://platformhost/sso/complete/github/',
|
||||||
SOCIAL_AUTH_GITHUB_KEY: 'mock github key',
|
SOCIAL_AUTH_GITHUB_KEY: 'mock github key',
|
||||||
SOCIAL_AUTH_GITHUB_SECRET: '$encrypted$',
|
SOCIAL_AUTH_GITHUB_SECRET: '$encrypted$',
|
||||||
SOCIAL_AUTH_GITHUB_ORGANIZATION_MAP: null,
|
SOCIAL_AUTH_GITHUB_ORGANIZATION_MAP: null,
|
||||||
@@ -32,7 +33,7 @@ const mockDefault = {
|
|||||||
const mockOrg = {
|
const mockOrg = {
|
||||||
data: {
|
data: {
|
||||||
SOCIAL_AUTH_GITHUB_ORG_CALLBACK_URL:
|
SOCIAL_AUTH_GITHUB_ORG_CALLBACK_URL:
|
||||||
'https://towerhost/sso/complete/github-org/',
|
'https://platformhost/sso/complete/github-org/',
|
||||||
SOCIAL_AUTH_GITHUB_ORG_KEY: '',
|
SOCIAL_AUTH_GITHUB_ORG_KEY: '',
|
||||||
SOCIAL_AUTH_GITHUB_ORG_SECRET: '$encrypted$',
|
SOCIAL_AUTH_GITHUB_ORG_SECRET: '$encrypted$',
|
||||||
SOCIAL_AUTH_GITHUB_ORG_NAME: '',
|
SOCIAL_AUTH_GITHUB_ORG_NAME: '',
|
||||||
@@ -43,7 +44,7 @@ const mockOrg = {
|
|||||||
const mockTeam = {
|
const mockTeam = {
|
||||||
data: {
|
data: {
|
||||||
SOCIAL_AUTH_GITHUB_TEAM_CALLBACK_URL:
|
SOCIAL_AUTH_GITHUB_TEAM_CALLBACK_URL:
|
||||||
'https://towerhost/sso/complete/github-team/',
|
'https://platformhost/sso/complete/github-team/',
|
||||||
SOCIAL_AUTH_GITHUB_TEAM_KEY: 'OAuth2 key (Client ID)',
|
SOCIAL_AUTH_GITHUB_TEAM_KEY: 'OAuth2 key (Client ID)',
|
||||||
SOCIAL_AUTH_GITHUB_TEAM_SECRET: '$encrypted$',
|
SOCIAL_AUTH_GITHUB_TEAM_SECRET: '$encrypted$',
|
||||||
SOCIAL_AUTH_GITHUB_TEAM_ID: 'team_id',
|
SOCIAL_AUTH_GITHUB_TEAM_ID: 'team_id',
|
||||||
@@ -54,7 +55,7 @@ const mockTeam = {
|
|||||||
const mockEnterprise = {
|
const mockEnterprise = {
|
||||||
data: {
|
data: {
|
||||||
SOCIAL_AUTH_GITHUB_ENTERPRISE_CALLBACK_URL:
|
SOCIAL_AUTH_GITHUB_ENTERPRISE_CALLBACK_URL:
|
||||||
'https://towerhost/sso/complete/github-enterprise/',
|
'https://platformhost/sso/complete/github-enterprise/',
|
||||||
SOCIAL_AUTH_GITHUB_ENTERPRISE_URL: 'https://localhost/enterpriseurl',
|
SOCIAL_AUTH_GITHUB_ENTERPRISE_URL: 'https://localhost/enterpriseurl',
|
||||||
SOCIAL_AUTH_GITHUB_ENTERPRISE_API_URL: 'https://localhost/enterpriseapi',
|
SOCIAL_AUTH_GITHUB_ENTERPRISE_API_URL: 'https://localhost/enterpriseapi',
|
||||||
SOCIAL_AUTH_GITHUB_ENTERPRISE_KEY: 'foobar',
|
SOCIAL_AUTH_GITHUB_ENTERPRISE_KEY: 'foobar',
|
||||||
@@ -66,7 +67,7 @@ const mockEnterprise = {
|
|||||||
const mockEnterpriseOrg = {
|
const mockEnterpriseOrg = {
|
||||||
data: {
|
data: {
|
||||||
SOCIAL_AUTH_GITHUB_ENTERPRISE_ORG_CALLBACK_URL:
|
SOCIAL_AUTH_GITHUB_ENTERPRISE_ORG_CALLBACK_URL:
|
||||||
'https://towerhost/sso/complete/github-enterprise-org/',
|
'https://platformhost/sso/complete/github-enterprise-org/',
|
||||||
SOCIAL_AUTH_GITHUB_ENTERPRISE_ORG_URL: 'https://localhost/orgurl',
|
SOCIAL_AUTH_GITHUB_ENTERPRISE_ORG_URL: 'https://localhost/orgurl',
|
||||||
SOCIAL_AUTH_GITHUB_ENTERPRISE_ORG_API_URL: 'https://localhost/orgapi',
|
SOCIAL_AUTH_GITHUB_ENTERPRISE_ORG_API_URL: 'https://localhost/orgapi',
|
||||||
SOCIAL_AUTH_GITHUB_ENTERPRISE_ORG_KEY: 'foobar',
|
SOCIAL_AUTH_GITHUB_ENTERPRISE_ORG_KEY: 'foobar',
|
||||||
@@ -79,7 +80,7 @@ const mockEnterpriseOrg = {
|
|||||||
const mockEnterpriseTeam = {
|
const mockEnterpriseTeam = {
|
||||||
data: {
|
data: {
|
||||||
SOCIAL_AUTH_GITHUB_ENTERPRISE_TEAM_CALLBACK_URL:
|
SOCIAL_AUTH_GITHUB_ENTERPRISE_TEAM_CALLBACK_URL:
|
||||||
'https://towerhost/sso/complete/github-enterprise-team/',
|
'https://platformhost/sso/complete/github-enterprise-team/',
|
||||||
SOCIAL_AUTH_GITHUB_ENTERPRISE_TEAM_URL: 'https://localhost/teamurl',
|
SOCIAL_AUTH_GITHUB_ENTERPRISE_TEAM_URL: 'https://localhost/teamurl',
|
||||||
SOCIAL_AUTH_GITHUB_ENTERPRISE_TEAM_API_URL: 'https://localhost/teamapi',
|
SOCIAL_AUTH_GITHUB_ENTERPRISE_TEAM_API_URL: 'https://localhost/teamapi',
|
||||||
SOCIAL_AUTH_GITHUB_ENTERPRISE_TEAM_KEY: 'foobar',
|
SOCIAL_AUTH_GITHUB_ENTERPRISE_TEAM_KEY: 'foobar',
|
||||||
@@ -143,7 +144,7 @@ describe('<GitHubDetail />', () => {
|
|||||||
assertDetail(
|
assertDetail(
|
||||||
wrapper,
|
wrapper,
|
||||||
'GitHub OAuth2 Callback URL',
|
'GitHub OAuth2 Callback URL',
|
||||||
'https://towerhost/sso/complete/github/'
|
'https://platformhost/sso/complete/github/'
|
||||||
);
|
);
|
||||||
assertDetail(wrapper, 'GitHub OAuth2 Key', 'mock github key');
|
assertDetail(wrapper, 'GitHub OAuth2 Key', 'mock github key');
|
||||||
assertDetail(wrapper, 'GitHub OAuth2 Secret', 'Encrypted');
|
assertDetail(wrapper, 'GitHub OAuth2 Secret', 'Encrypted');
|
||||||
@@ -218,7 +219,7 @@ describe('<GitHubDetail />', () => {
|
|||||||
assertDetail(
|
assertDetail(
|
||||||
wrapper,
|
wrapper,
|
||||||
'GitHub Organization OAuth2 Callback URL',
|
'GitHub Organization OAuth2 Callback URL',
|
||||||
'https://towerhost/sso/complete/github-org/'
|
'https://platformhost/sso/complete/github-org/'
|
||||||
);
|
);
|
||||||
assertDetail(wrapper, 'GitHub Organization OAuth2 Key', 'Not configured');
|
assertDetail(wrapper, 'GitHub Organization OAuth2 Key', 'Not configured');
|
||||||
assertDetail(wrapper, 'GitHub Organization OAuth2 Secret', 'Encrypted');
|
assertDetail(wrapper, 'GitHub Organization OAuth2 Secret', 'Encrypted');
|
||||||
@@ -269,7 +270,7 @@ describe('<GitHubDetail />', () => {
|
|||||||
assertDetail(
|
assertDetail(
|
||||||
wrapper,
|
wrapper,
|
||||||
'GitHub Team OAuth2 Callback URL',
|
'GitHub Team OAuth2 Callback URL',
|
||||||
'https://towerhost/sso/complete/github-team/'
|
'https://platformhost/sso/complete/github-team/'
|
||||||
);
|
);
|
||||||
assertDetail(wrapper, 'GitHub Team OAuth2 Key', 'OAuth2 key (Client ID)');
|
assertDetail(wrapper, 'GitHub Team OAuth2 Key', 'OAuth2 key (Client ID)');
|
||||||
assertDetail(wrapper, 'GitHub Team OAuth2 Secret', 'Encrypted');
|
assertDetail(wrapper, 'GitHub Team OAuth2 Secret', 'Encrypted');
|
||||||
@@ -316,7 +317,7 @@ describe('<GitHubDetail />', () => {
|
|||||||
assertDetail(
|
assertDetail(
|
||||||
wrapper,
|
wrapper,
|
||||||
'GitHub Enterprise OAuth2 Callback URL',
|
'GitHub Enterprise OAuth2 Callback URL',
|
||||||
'https://towerhost/sso/complete/github-enterprise/'
|
'https://platformhost/sso/complete/github-enterprise/'
|
||||||
);
|
);
|
||||||
assertDetail(
|
assertDetail(
|
||||||
wrapper,
|
wrapper,
|
||||||
@@ -343,7 +344,7 @@ describe('<GitHubDetail />', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('Enterprise Org', () => {
|
describe('Enterprise Organization', () => {
|
||||||
let wrapper;
|
let wrapper;
|
||||||
|
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
@@ -376,7 +377,7 @@ describe('<GitHubDetail />', () => {
|
|||||||
assertDetail(
|
assertDetail(
|
||||||
wrapper,
|
wrapper,
|
||||||
'GitHub Enterprise Organization OAuth2 Callback URL',
|
'GitHub Enterprise Organization OAuth2 Callback URL',
|
||||||
'https://towerhost/sso/complete/github-enterprise-org/'
|
'https://platformhost/sso/complete/github-enterprise-org/'
|
||||||
);
|
);
|
||||||
assertDetail(
|
assertDetail(
|
||||||
wrapper,
|
wrapper,
|
||||||
@@ -445,7 +446,7 @@ describe('<GitHubDetail />', () => {
|
|||||||
assertDetail(
|
assertDetail(
|
||||||
wrapper,
|
wrapper,
|
||||||
'GitHub Enterprise Team OAuth2 Callback URL',
|
'GitHub Enterprise Team OAuth2 Callback URL',
|
||||||
'https://towerhost/sso/complete/github-enterprise-team/'
|
'https://platformhost/sso/complete/github-enterprise-team/'
|
||||||
);
|
);
|
||||||
assertDetail(
|
assertDetail(
|
||||||
wrapper,
|
wrapper,
|
||||||
@@ -476,23 +477,4 @@ describe('<GitHubDetail />', () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('Redirect', () => {
|
|
||||||
test('should render redirect when user navigates to erroneous category', async () => {
|
|
||||||
let wrapper;
|
|
||||||
useRouteMatch.mockImplementation(() => ({
|
|
||||||
url: '/settings/github/foo/details',
|
|
||||||
path: '/settings/github/:category/details',
|
|
||||||
params: { category: 'foo' },
|
|
||||||
}));
|
|
||||||
await act(async () => {
|
|
||||||
wrapper = mountWithContexts(
|
|
||||||
<SettingsProvider value={mockAllOptions.actions}>
|
|
||||||
<GitHubDetail />
|
|
||||||
</SettingsProvider>
|
|
||||||
);
|
|
||||||
});
|
|
||||||
await waitForElement(wrapper, 'Redirect');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ describe('<GitHubEnterpriseEdit />', () => {
|
|||||||
SettingsAPI.readCategory.mockResolvedValue({
|
SettingsAPI.readCategory.mockResolvedValue({
|
||||||
data: {
|
data: {
|
||||||
SOCIAL_AUTH_GITHUB_ENTERPRISE_CALLBACK_URL:
|
SOCIAL_AUTH_GITHUB_ENTERPRISE_CALLBACK_URL:
|
||||||
'https://towerhost/sso/complete/github-enterprise/',
|
'https://platformhost/sso/complete/github-enterprise/',
|
||||||
SOCIAL_AUTH_GITHUB_ENTERPRISE_URL: '',
|
SOCIAL_AUTH_GITHUB_ENTERPRISE_URL: '',
|
||||||
SOCIAL_AUTH_GITHUB_ENTERPRISE_API_URL: '',
|
SOCIAL_AUTH_GITHUB_ENTERPRISE_API_URL: '',
|
||||||
SOCIAL_AUTH_GITHUB_ENTERPRISE_KEY: '',
|
SOCIAL_AUTH_GITHUB_ENTERPRISE_KEY: '',
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ describe('<GitHubEnterpriseOrgEdit />', () => {
|
|||||||
SettingsAPI.readCategory.mockResolvedValue({
|
SettingsAPI.readCategory.mockResolvedValue({
|
||||||
data: {
|
data: {
|
||||||
SOCIAL_AUTH_GITHUB_ENTERPRISE_ORG_CALLBACK_URL:
|
SOCIAL_AUTH_GITHUB_ENTERPRISE_ORG_CALLBACK_URL:
|
||||||
'https://towerhost/sso/complete/github-enterprise-org/',
|
'https://platformhost/sso/complete/github-enterprise-org/',
|
||||||
SOCIAL_AUTH_GITHUB_ENTERPRISE_ORG_URL: '',
|
SOCIAL_AUTH_GITHUB_ENTERPRISE_ORG_URL: '',
|
||||||
SOCIAL_AUTH_GITHUB_ENTERPRISE_ORG_API_URL: '',
|
SOCIAL_AUTH_GITHUB_ENTERPRISE_ORG_API_URL: '',
|
||||||
SOCIAL_AUTH_GITHUB_ENTERPRISE_ORG_KEY: '',
|
SOCIAL_AUTH_GITHUB_ENTERPRISE_ORG_KEY: '',
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ describe('<GitHubEnterpriseTeamEdit />', () => {
|
|||||||
SettingsAPI.readCategory.mockResolvedValue({
|
SettingsAPI.readCategory.mockResolvedValue({
|
||||||
data: {
|
data: {
|
||||||
SOCIAL_AUTH_GITHUB_ENTERPRISE_TEAM_CALLBACK_URL:
|
SOCIAL_AUTH_GITHUB_ENTERPRISE_TEAM_CALLBACK_URL:
|
||||||
'https://towerhost/sso/complete/github-enterprise-team/',
|
'https://platformhost/sso/complete/github-enterprise-team/',
|
||||||
SOCIAL_AUTH_GITHUB_ENTERPRISE_TEAM_URL: '',
|
SOCIAL_AUTH_GITHUB_ENTERPRISE_TEAM_URL: '',
|
||||||
SOCIAL_AUTH_GITHUB_ENTERPRISE_TEAM_API_URL: '',
|
SOCIAL_AUTH_GITHUB_ENTERPRISE_TEAM_API_URL: '',
|
||||||
SOCIAL_AUTH_GITHUB_ENTERPRISE_TEAM_KEY: '',
|
SOCIAL_AUTH_GITHUB_ENTERPRISE_TEAM_KEY: '',
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ describe('<GitHubOrgEdit />', () => {
|
|||||||
SettingsAPI.readCategory.mockResolvedValue({
|
SettingsAPI.readCategory.mockResolvedValue({
|
||||||
data: {
|
data: {
|
||||||
SOCIAL_AUTH_GITHUB_ORG_CALLBACK_URL:
|
SOCIAL_AUTH_GITHUB_ORG_CALLBACK_URL:
|
||||||
'https://towerhost/sso/complete/github-org/',
|
'https://platformhost/sso/complete/github-org/',
|
||||||
SOCIAL_AUTH_GITHUB_ORG_KEY: '',
|
SOCIAL_AUTH_GITHUB_ORG_KEY: '',
|
||||||
SOCIAL_AUTH_GITHUB_ORG_SECRET: '$encrypted$',
|
SOCIAL_AUTH_GITHUB_ORG_SECRET: '$encrypted$',
|
||||||
SOCIAL_AUTH_GITHUB_ORG_NAME: '',
|
SOCIAL_AUTH_GITHUB_ORG_NAME: '',
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ describe('<GitHubTeamEdit />', () => {
|
|||||||
SettingsAPI.readCategory.mockResolvedValue({
|
SettingsAPI.readCategory.mockResolvedValue({
|
||||||
data: {
|
data: {
|
||||||
SOCIAL_AUTH_GITHUB_TEAM_CALLBACK_URL:
|
SOCIAL_AUTH_GITHUB_TEAM_CALLBACK_URL:
|
||||||
'https://towerhost/sso/complete/github-team/',
|
'https://platformhost/sso/complete/github-team/',
|
||||||
SOCIAL_AUTH_GITHUB_TEAM_KEY: 'OAuth2 key (Client ID)',
|
SOCIAL_AUTH_GITHUB_TEAM_KEY: 'OAuth2 key (Client ID)',
|
||||||
SOCIAL_AUTH_GITHUB_TEAM_SECRET: '$encrypted$',
|
SOCIAL_AUTH_GITHUB_TEAM_SECRET: '$encrypted$',
|
||||||
SOCIAL_AUTH_GITHUB_TEAM_ID: 'team_id',
|
SOCIAL_AUTH_GITHUB_TEAM_ID: 'team_id',
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ describe('<GoogleOAuth2 />', () => {
|
|||||||
SettingsAPI.readCategory.mockResolvedValue({
|
SettingsAPI.readCategory.mockResolvedValue({
|
||||||
data: {
|
data: {
|
||||||
SOCIAL_AUTH_GOOGLE_OAUTH2_CALLBACK_URL:
|
SOCIAL_AUTH_GOOGLE_OAUTH2_CALLBACK_URL:
|
||||||
'https://towerhost/sso/complete/google-oauth2/',
|
'https://platformhost/sso/complete/google-oauth2/',
|
||||||
SOCIAL_AUTH_GOOGLE_OAUTH2_KEY: 'mock key',
|
SOCIAL_AUTH_GOOGLE_OAUTH2_KEY: 'mock key',
|
||||||
SOCIAL_AUTH_GOOGLE_OAUTH2_SECRET: '$encrypted$',
|
SOCIAL_AUTH_GOOGLE_OAUTH2_SECRET: '$encrypted$',
|
||||||
SOCIAL_AUTH_GOOGLE_OAUTH2_WHITELISTED_DOMAINS: [
|
SOCIAL_AUTH_GOOGLE_OAUTH2_WHITELISTED_DOMAINS: [
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ describe('<GoogleOAuth2Detail />', () => {
|
|||||||
SettingsAPI.readCategory.mockResolvedValue({
|
SettingsAPI.readCategory.mockResolvedValue({
|
||||||
data: {
|
data: {
|
||||||
SOCIAL_AUTH_GOOGLE_OAUTH2_CALLBACK_URL:
|
SOCIAL_AUTH_GOOGLE_OAUTH2_CALLBACK_URL:
|
||||||
'https://towerhost/sso/complete/google-oauth2/',
|
'https://platformhost/sso/complete/google-oauth2/',
|
||||||
SOCIAL_AUTH_GOOGLE_OAUTH2_KEY: 'mock key',
|
SOCIAL_AUTH_GOOGLE_OAUTH2_KEY: 'mock key',
|
||||||
SOCIAL_AUTH_GOOGLE_OAUTH2_SECRET: '$encrypted$',
|
SOCIAL_AUTH_GOOGLE_OAUTH2_SECRET: '$encrypted$',
|
||||||
SOCIAL_AUTH_GOOGLE_OAUTH2_WHITELISTED_DOMAINS: [
|
SOCIAL_AUTH_GOOGLE_OAUTH2_WHITELISTED_DOMAINS: [
|
||||||
@@ -68,7 +68,7 @@ describe('<GoogleOAuth2Detail />', () => {
|
|||||||
assertDetail(
|
assertDetail(
|
||||||
wrapper,
|
wrapper,
|
||||||
'Google OAuth2 Callback URL',
|
'Google OAuth2 Callback URL',
|
||||||
'https://towerhost/sso/complete/google-oauth2/'
|
'https://platformhost/sso/complete/google-oauth2/'
|
||||||
);
|
);
|
||||||
assertDetail(wrapper, 'Google OAuth2 Key', 'mock key');
|
assertDetail(wrapper, 'Google OAuth2 Key', 'mock key');
|
||||||
assertDetail(wrapper, 'Google OAuth2 Secret', 'Encrypted');
|
assertDetail(wrapper, 'Google OAuth2 Secret', 'Encrypted');
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ describe('<GoogleOAuth2Edit />', () => {
|
|||||||
SettingsAPI.readCategory.mockResolvedValue({
|
SettingsAPI.readCategory.mockResolvedValue({
|
||||||
data: {
|
data: {
|
||||||
SOCIAL_AUTH_GOOGLE_OAUTH2_CALLBACK_URL:
|
SOCIAL_AUTH_GOOGLE_OAUTH2_CALLBACK_URL:
|
||||||
'https://towerhost/sso/complete/google-oauth2/',
|
'https://platformhost/sso/complete/google-oauth2/',
|
||||||
SOCIAL_AUTH_GOOGLE_OAUTH2_KEY: 'mock key',
|
SOCIAL_AUTH_GOOGLE_OAUTH2_KEY: 'mock key',
|
||||||
SOCIAL_AUTH_GOOGLE_OAUTH2_SECRET: '$encrypted$',
|
SOCIAL_AUTH_GOOGLE_OAUTH2_SECRET: '$encrypted$',
|
||||||
SOCIAL_AUTH_GOOGLE_OAUTH2_WHITELISTED_DOMAINS: [
|
SOCIAL_AUTH_GOOGLE_OAUTH2_WHITELISTED_DOMAINS: [
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ describe('<MiscSystemDetail />', () => {
|
|||||||
ACTIVITY_STREAM_ENABLED_FOR_INVENTORY_SYNC: false,
|
ACTIVITY_STREAM_ENABLED_FOR_INVENTORY_SYNC: false,
|
||||||
ORG_ADMINS_CAN_SEE_ALL_USERS: true,
|
ORG_ADMINS_CAN_SEE_ALL_USERS: true,
|
||||||
MANAGE_ORGANIZATION_AUTH: true,
|
MANAGE_ORGANIZATION_AUTH: true,
|
||||||
TOWER_URL_BASE: 'https://towerhost',
|
TOWER_URL_BASE: 'https://platformhost',
|
||||||
REMOTE_HOST_HEADERS: [],
|
REMOTE_HOST_HEADERS: [],
|
||||||
PROXY_IP_ALLOWED_LIST: [],
|
PROXY_IP_ALLOWED_LIST: [],
|
||||||
CSRF_TRUSTED_ORIGINS: [],
|
CSRF_TRUSTED_ORIGINS: [],
|
||||||
@@ -94,7 +94,7 @@ describe('<MiscSystemDetail />', () => {
|
|||||||
'Automation Analytics upload URL',
|
'Automation Analytics upload URL',
|
||||||
'https://example.com'
|
'https://example.com'
|
||||||
);
|
);
|
||||||
assertDetail(wrapper, 'Base URL of the service', 'https://towerhost');
|
assertDetail(wrapper, 'Base URL of the service', 'https://platformhost');
|
||||||
assertDetail(wrapper, 'Gather data for Automation Analytics', 'Off');
|
assertDetail(wrapper, 'Gather data for Automation Analytics', 'Off');
|
||||||
assertDetail(
|
assertDetail(
|
||||||
wrapper,
|
wrapper,
|
||||||
|
|||||||
@@ -15,8 +15,10 @@ describe('<SAML />', () => {
|
|||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
SettingsAPI.readCategory.mockResolvedValue({
|
SettingsAPI.readCategory.mockResolvedValue({
|
||||||
data: {
|
data: {
|
||||||
SOCIAL_AUTH_SAML_CALLBACK_URL: 'https://towerhost/sso/complete/saml/',
|
SOCIAL_AUTH_SAML_CALLBACK_URL:
|
||||||
SOCIAL_AUTH_SAML_METADATA_URL: 'https://towerhost/sso/metadata/saml/',
|
'https://platformhost/sso/complete/saml/',
|
||||||
|
SOCIAL_AUTH_SAML_METADATA_URL:
|
||||||
|
'https://platformhost/sso/metadata/saml/',
|
||||||
SOCIAL_AUTH_SAML_SP_ENTITY_ID: '',
|
SOCIAL_AUTH_SAML_SP_ENTITY_ID: '',
|
||||||
SOCIAL_AUTH_SAML_SP_PUBLIC_CERT: '',
|
SOCIAL_AUTH_SAML_SP_PUBLIC_CERT: '',
|
||||||
SOCIAL_AUTH_SAML_SP_PRIVATE_KEY: '',
|
SOCIAL_AUTH_SAML_SP_PRIVATE_KEY: '',
|
||||||
|
|||||||
@@ -21,8 +21,10 @@ describe('<SAMLDetail />', () => {
|
|||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
SettingsAPI.readCategory.mockResolvedValue({
|
SettingsAPI.readCategory.mockResolvedValue({
|
||||||
data: {
|
data: {
|
||||||
SOCIAL_AUTH_SAML_CALLBACK_URL: 'https://towerhost/sso/complete/saml/',
|
SOCIAL_AUTH_SAML_CALLBACK_URL:
|
||||||
SOCIAL_AUTH_SAML_METADATA_URL: 'https://towerhost/sso/metadata/saml/',
|
'https://platformhost/sso/complete/saml/',
|
||||||
|
SOCIAL_AUTH_SAML_METADATA_URL:
|
||||||
|
'https://platformhost/sso/metadata/saml/',
|
||||||
SOCIAL_AUTH_SAML_SP_ENTITY_ID: 'mock_id',
|
SOCIAL_AUTH_SAML_SP_ENTITY_ID: 'mock_id',
|
||||||
SOCIAL_AUTH_SAML_SP_PUBLIC_CERT: 'mock_cert',
|
SOCIAL_AUTH_SAML_SP_PUBLIC_CERT: 'mock_cert',
|
||||||
SOCIAL_AUTH_SAML_SP_PRIVATE_KEY: '',
|
SOCIAL_AUTH_SAML_SP_PRIVATE_KEY: '',
|
||||||
@@ -71,12 +73,12 @@ describe('<SAMLDetail />', () => {
|
|||||||
assertDetail(
|
assertDetail(
|
||||||
wrapper,
|
wrapper,
|
||||||
'SAML Assertion Consumer Service (ACS) URL',
|
'SAML Assertion Consumer Service (ACS) URL',
|
||||||
'https://towerhost/sso/complete/saml/'
|
'https://platformhost/sso/complete/saml/'
|
||||||
);
|
);
|
||||||
assertDetail(
|
assertDetail(
|
||||||
wrapper,
|
wrapper,
|
||||||
'SAML Service Provider Metadata URL',
|
'SAML Service Provider Metadata URL',
|
||||||
'https://towerhost/sso/metadata/saml/'
|
'https://platformhost/sso/metadata/saml/'
|
||||||
);
|
);
|
||||||
assertDetail(wrapper, 'SAML Service Provider Entity ID', 'mock_id');
|
assertDetail(wrapper, 'SAML Service Provider Entity ID', 'mock_id');
|
||||||
assertVariableDetail(
|
assertVariableDetail(
|
||||||
|
|||||||
@@ -22,8 +22,10 @@ describe('<SAMLEdit />', () => {
|
|||||||
SettingsAPI.readCategory.mockResolvedValue({
|
SettingsAPI.readCategory.mockResolvedValue({
|
||||||
data: {
|
data: {
|
||||||
SAML_AUTO_CREATE_OBJECTS: true,
|
SAML_AUTO_CREATE_OBJECTS: true,
|
||||||
SOCIAL_AUTH_SAML_CALLBACK_URL: 'https://towerhost/sso/complete/saml/',
|
SOCIAL_AUTH_SAML_CALLBACK_URL:
|
||||||
SOCIAL_AUTH_SAML_METADATA_URL: 'https://towerhost/sso/metadata/saml/',
|
'https://platformhost/sso/complete/saml/',
|
||||||
|
SOCIAL_AUTH_SAML_METADATA_URL:
|
||||||
|
'https://platformhost/sso/metadata/saml/',
|
||||||
SOCIAL_AUTH_SAML_SP_ENTITY_ID: 'mock_id',
|
SOCIAL_AUTH_SAML_SP_ENTITY_ID: 'mock_id',
|
||||||
SOCIAL_AUTH_SAML_SP_PUBLIC_CERT: 'mock_cert',
|
SOCIAL_AUTH_SAML_SP_PUBLIC_CERT: 'mock_cert',
|
||||||
SOCIAL_AUTH_SAML_SP_PRIVATE_KEY: '$encrypted$',
|
SOCIAL_AUTH_SAML_SP_PRIVATE_KEY: '$encrypted$',
|
||||||
|
|||||||
@@ -117,6 +117,10 @@ function TroubleshootingEdit() {
|
|||||||
name="RECEPTOR_RELEASE_WORK"
|
name="RECEPTOR_RELEASE_WORK"
|
||||||
config={debug.RECEPTOR_RELEASE_WORK}
|
config={debug.RECEPTOR_RELEASE_WORK}
|
||||||
/>
|
/>
|
||||||
|
<BooleanField
|
||||||
|
name="RECEPTOR_KEEP_WORK_ON_ERROR"
|
||||||
|
config={debug.RECEPTOR_KEEP_WORK_ON_ERROR}
|
||||||
|
/>
|
||||||
{submitError && <FormSubmitError error={submitError} />}
|
{submitError && <FormSubmitError error={submitError} />}
|
||||||
{revertError && <FormSubmitError error={revertError} />}
|
{revertError && <FormSubmitError error={revertError} />}
|
||||||
</FormColumnLayout>
|
</FormColumnLayout>
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
{
|
{
|
||||||
"AWX_CLEANUP_PATHS": false,
|
"AWX_CLEANUP_PATHS": false,
|
||||||
"AWX_REQUEST_PROFILE": false,
|
"AWX_REQUEST_PROFILE": false,
|
||||||
"RECEPTOR_RELEASE_WORK": false
|
"RECEPTOR_RELEASE_WORK": false,
|
||||||
}
|
"RECEPTOR_KEEP_WORK_ON_ERROR": false
|
||||||
|
}
|
||||||
|
|||||||
@@ -830,6 +830,15 @@
|
|||||||
"category_slug": "debug",
|
"category_slug": "debug",
|
||||||
"default": true
|
"default": true
|
||||||
},
|
},
|
||||||
|
"RECEPTOR_KEEP_WORK_ON_ERROR": {
|
||||||
|
"type": "boolean",
|
||||||
|
"required": false,
|
||||||
|
"label": "Keep receptor work on error",
|
||||||
|
"help_text": "Prevent receptor work from being released on when error is detected",
|
||||||
|
"category": "Debug",
|
||||||
|
"category_slug": "debug",
|
||||||
|
"default": false
|
||||||
|
},
|
||||||
"SESSION_COOKIE_AGE": {
|
"SESSION_COOKIE_AGE": {
|
||||||
"type": "integer",
|
"type": "integer",
|
||||||
"required": true,
|
"required": true,
|
||||||
@@ -5173,6 +5182,14 @@
|
|||||||
"category_slug": "debug",
|
"category_slug": "debug",
|
||||||
"defined_in_file": false
|
"defined_in_file": false
|
||||||
},
|
},
|
||||||
|
"RECEPTOR_KEEP_WORK_ON_ERROR": {
|
||||||
|
"type": "boolean",
|
||||||
|
"label": "Keep receptor work on error",
|
||||||
|
"help_text": "Prevent receptor work from being released on when error is detected",
|
||||||
|
"category": "Debug",
|
||||||
|
"category_slug": "debug",
|
||||||
|
"defined_in_file": false
|
||||||
|
},
|
||||||
"SESSION_COOKIE_AGE": {
|
"SESSION_COOKIE_AGE": {
|
||||||
"type": "integer",
|
"type": "integer",
|
||||||
"label": "Idle Time Force Log Out",
|
"label": "Idle Time Force Log Out",
|
||||||
|
|||||||
@@ -91,6 +91,7 @@
|
|||||||
"slirp4netns:enable_ipv6=true"
|
"slirp4netns:enable_ipv6=true"
|
||||||
],
|
],
|
||||||
"RECEPTOR_RELEASE_WORK": true,
|
"RECEPTOR_RELEASE_WORK": true,
|
||||||
|
"RECEPTOR_KEEP_WORK_ON_ERROR": false,
|
||||||
"SESSION_COOKIE_AGE": 1800,
|
"SESSION_COOKIE_AGE": 1800,
|
||||||
"SESSIONS_PER_USER": -1,
|
"SESSIONS_PER_USER": -1,
|
||||||
"DISABLE_LOCAL_AUTH": false,
|
"DISABLE_LOCAL_AUTH": false,
|
||||||
|
|||||||
@@ -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.
|
## 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
|
$(UI_NEXT_DIR)/src/build/awx: $(UI_NEXT_DIR)/src $(UI_NEXT_DIR)/src/node_modules/webpack
|
||||||
@echo "=== Building ui_next ==="
|
@echo "=== Building ui_next ==="
|
||||||
@cd $(UI_NEXT_DIR)/src && PRODUCT="$(PRODUCT)" PUBLIC_PATH=/static/awx/ ROUTE_PREFIX=/ui_next npm run build:awx
|
@cd $(UI_NEXT_DIR)/src && PRODUCT="$(PRODUCT)" PUBLIC_PATH=/static/awx/ ROUTE_PREFIX=/ npm run build:awx
|
||||||
@mv $(UI_NEXT_DIR)/src/build/awx/index.html $(UI_NEXT_DIR)/src/build/awx/index_awx.html
|
@mv $(UI_NEXT_DIR)/src/build/awx/index.html $(UI_NEXT_DIR)/src/build/awx/index_awx.html
|
||||||
|
|
||||||
.PHONY: ui-next/src
|
.PHONY: ui-next/src
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
from django.conf import settings
|
|
||||||
from django.http import Http404
|
|
||||||
from django.urls import re_path
|
from django.urls import re_path
|
||||||
from django.views.generic.base import TemplateView
|
from django.views.generic.base import TemplateView
|
||||||
|
|
||||||
@@ -7,12 +5,6 @@ from django.views.generic.base import TemplateView
|
|||||||
class IndexView(TemplateView):
|
class IndexView(TemplateView):
|
||||||
template_name = 'index_awx.html'
|
template_name = 'index_awx.html'
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
|
||||||
if settings.UI_NEXT is False:
|
|
||||||
raise Http404()
|
|
||||||
|
|
||||||
return super().get_context_data(**kwargs)
|
|
||||||
|
|
||||||
|
|
||||||
app_name = 'ui_next'
|
app_name = 'ui_next'
|
||||||
|
|
||||||
|
|||||||
@@ -18,8 +18,6 @@ def get_urlpatterns(prefix=None):
|
|||||||
prefix = f'/{prefix}/'
|
prefix = f'/{prefix}/'
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
re_path(r'', include('awx.ui.urls', namespace='ui')),
|
|
||||||
re_path(r'^ui_next/.*', include('awx.ui_next.urls', namespace='ui_next')),
|
|
||||||
path(f'api{prefix}', include('awx.api.urls', namespace='api')),
|
path(f'api{prefix}', include('awx.api.urls', namespace='api')),
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -36,6 +34,9 @@ def get_urlpatterns(prefix=None):
|
|||||||
re_path(r'^(?:api/)?500.html$', handle_500),
|
re_path(r'^(?:api/)?500.html$', handle_500),
|
||||||
re_path(r'^csp-violation/', handle_csp_violation),
|
re_path(r'^csp-violation/', handle_csp_violation),
|
||||||
re_path(r'^login/', handle_login_redirect),
|
re_path(r'^login/', handle_login_redirect),
|
||||||
|
# want api/v2/doesnotexist to return a 404, not match the ui_next urls,
|
||||||
|
# so use a negative lookahead assertion here
|
||||||
|
re_path(r'^(?!api/|sso/).*', include('awx.ui_next.urls', namespace='ui_next')),
|
||||||
]
|
]
|
||||||
|
|
||||||
if settings.SETTINGS_MODULE == 'awx.settings.development':
|
if settings.SETTINGS_MODULE == 'awx.settings.development':
|
||||||
|
|||||||
0
awx_plugins/__init__.py
Normal file
0
awx_plugins/__init__.py
Normal file
0
awx_plugins/credentials/__init__.py
Normal file
0
awx_plugins/credentials/__init__.py
Normal file
666
awx_plugins/credentials/plugins.py
Normal file
666
awx_plugins/credentials/plugins.py
Normal file
@@ -0,0 +1,666 @@
|
|||||||
|
|
||||||
|
# Django
|
||||||
|
from django.utils.translation import gettext_noop
|
||||||
|
|
||||||
|
# AWX
|
||||||
|
from awx.main.models.credential import ManagedCredentialType
|
||||||
|
|
||||||
|
|
||||||
|
ManagedCredentialType(
|
||||||
|
namespace='ssh',
|
||||||
|
kind='ssh',
|
||||||
|
name=gettext_noop('Machine'),
|
||||||
|
inputs={
|
||||||
|
'fields': [
|
||||||
|
{'id': 'username', 'label': gettext_noop('Username'), 'type': 'string'},
|
||||||
|
{'id': 'password', 'label': gettext_noop('Password'), 'type': 'string', 'secret': True, 'ask_at_runtime': True},
|
||||||
|
{'id': 'ssh_key_data', 'label': gettext_noop('SSH Private Key'), 'type': 'string', 'format': 'ssh_private_key', 'secret': True, 'multiline': True},
|
||||||
|
{
|
||||||
|
'id': 'ssh_public_key_data',
|
||||||
|
'label': gettext_noop('Signed SSH Certificate'),
|
||||||
|
'type': 'string',
|
||||||
|
'multiline': True,
|
||||||
|
'secret': True,
|
||||||
|
},
|
||||||
|
{'id': 'ssh_key_unlock', 'label': gettext_noop('Private Key Passphrase'), 'type': 'string', 'secret': True, 'ask_at_runtime': True},
|
||||||
|
{
|
||||||
|
'id': 'become_method',
|
||||||
|
'label': gettext_noop('Privilege Escalation Method'),
|
||||||
|
'type': 'string',
|
||||||
|
'help_text': gettext_noop('Specify a method for "become" operations. This is equivalent to specifying the --become-method Ansible parameter.'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'id': 'become_username',
|
||||||
|
'label': gettext_noop('Privilege Escalation Username'),
|
||||||
|
'type': 'string',
|
||||||
|
},
|
||||||
|
{'id': 'become_password', 'label': gettext_noop('Privilege Escalation Password'), 'type': 'string', 'secret': True, 'ask_at_runtime': True},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
ManagedCredentialType(
|
||||||
|
namespace='scm',
|
||||||
|
kind='scm',
|
||||||
|
name=gettext_noop('Source Control'),
|
||||||
|
managed=True,
|
||||||
|
inputs={
|
||||||
|
'fields': [
|
||||||
|
{'id': 'username', 'label': gettext_noop('Username'), 'type': 'string'},
|
||||||
|
{'id': 'password', 'label': gettext_noop('Password'), 'type': 'string', 'secret': True},
|
||||||
|
{'id': 'ssh_key_data', 'label': gettext_noop('SCM Private Key'), 'type': 'string', 'format': 'ssh_private_key', 'secret': True, 'multiline': True},
|
||||||
|
{'id': 'ssh_key_unlock', 'label': gettext_noop('Private Key Passphrase'), 'type': 'string', 'secret': True},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
ManagedCredentialType(
|
||||||
|
namespace='vault',
|
||||||
|
kind='vault',
|
||||||
|
name=gettext_noop('Vault'),
|
||||||
|
managed=True,
|
||||||
|
inputs={
|
||||||
|
'fields': [
|
||||||
|
{'id': 'vault_password', 'label': gettext_noop('Vault Password'), 'type': 'string', 'secret': True, 'ask_at_runtime': True},
|
||||||
|
{
|
||||||
|
'id': 'vault_id',
|
||||||
|
'label': gettext_noop('Vault Identifier'),
|
||||||
|
'type': 'string',
|
||||||
|
'format': 'vault_id',
|
||||||
|
'help_text': gettext_noop(
|
||||||
|
'Specify an (optional) Vault ID. This is '
|
||||||
|
'equivalent to specifying the --vault-id '
|
||||||
|
'Ansible parameter for providing multiple Vault '
|
||||||
|
'passwords. Note: this feature only works in '
|
||||||
|
'Ansible 2.4+.'
|
||||||
|
),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
'required': ['vault_password'],
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
ManagedCredentialType(
|
||||||
|
namespace='net',
|
||||||
|
kind='net',
|
||||||
|
name=gettext_noop('Network'),
|
||||||
|
managed=True,
|
||||||
|
inputs={
|
||||||
|
'fields': [
|
||||||
|
{'id': 'username', 'label': gettext_noop('Username'), 'type': 'string'},
|
||||||
|
{
|
||||||
|
'id': 'password',
|
||||||
|
'label': gettext_noop('Password'),
|
||||||
|
'type': 'string',
|
||||||
|
'secret': True,
|
||||||
|
},
|
||||||
|
{'id': 'ssh_key_data', 'label': gettext_noop('SSH Private Key'), 'type': 'string', 'format': 'ssh_private_key', 'secret': True, 'multiline': True},
|
||||||
|
{
|
||||||
|
'id': 'ssh_key_unlock',
|
||||||
|
'label': gettext_noop('Private Key Passphrase'),
|
||||||
|
'type': 'string',
|
||||||
|
'secret': True,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'id': 'authorize',
|
||||||
|
'label': gettext_noop('Authorize'),
|
||||||
|
'type': 'boolean',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'id': 'authorize_password',
|
||||||
|
'label': gettext_noop('Authorize Password'),
|
||||||
|
'type': 'string',
|
||||||
|
'secret': True,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
'dependencies': {
|
||||||
|
'authorize_password': ['authorize'],
|
||||||
|
},
|
||||||
|
'required': ['username'],
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
ManagedCredentialType(
|
||||||
|
namespace='aws',
|
||||||
|
kind='cloud',
|
||||||
|
name=gettext_noop('Amazon Web Services'),
|
||||||
|
managed=True,
|
||||||
|
inputs={
|
||||||
|
'fields': [
|
||||||
|
{'id': 'username', 'label': gettext_noop('Access Key'), 'type': 'string'},
|
||||||
|
{
|
||||||
|
'id': 'password',
|
||||||
|
'label': gettext_noop('Secret Key'),
|
||||||
|
'type': 'string',
|
||||||
|
'secret': True,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'id': 'security_token',
|
||||||
|
'label': gettext_noop('STS Token'),
|
||||||
|
'type': 'string',
|
||||||
|
'secret': True,
|
||||||
|
'help_text': gettext_noop(
|
||||||
|
'Security Token Service (STS) is a web service '
|
||||||
|
'that enables you to request temporary, '
|
||||||
|
'limited-privilege credentials for AWS Identity '
|
||||||
|
'and Access Management (IAM) users.'
|
||||||
|
),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
'required': ['username', 'password'],
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
ManagedCredentialType(
|
||||||
|
namespace='openstack',
|
||||||
|
kind='cloud',
|
||||||
|
name=gettext_noop('OpenStack'),
|
||||||
|
managed=True,
|
||||||
|
inputs={
|
||||||
|
'fields': [
|
||||||
|
{'id': 'username', 'label': gettext_noop('Username'), 'type': 'string'},
|
||||||
|
{
|
||||||
|
'id': 'password',
|
||||||
|
'label': gettext_noop('Password (API Key)'),
|
||||||
|
'type': 'string',
|
||||||
|
'secret': True,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'id': 'host',
|
||||||
|
'label': gettext_noop('Host (Authentication URL)'),
|
||||||
|
'type': 'string',
|
||||||
|
'help_text': gettext_noop('The host to authenticate with. For example, https://openstack.business.com/v2.0/'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'id': 'project',
|
||||||
|
'label': gettext_noop('Project (Tenant Name)'),
|
||||||
|
'type': 'string',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'id': 'project_domain_name',
|
||||||
|
'label': gettext_noop('Project (Domain Name)'),
|
||||||
|
'type': 'string',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'id': 'domain',
|
||||||
|
'label': gettext_noop('Domain Name'),
|
||||||
|
'type': 'string',
|
||||||
|
'help_text': gettext_noop(
|
||||||
|
'OpenStack domains define administrative boundaries. '
|
||||||
|
'It is only needed for Keystone v3 authentication '
|
||||||
|
'URLs. Refer to the documentation for '
|
||||||
|
'common scenarios.'
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'id': 'region',
|
||||||
|
'label': gettext_noop('Region Name'),
|
||||||
|
'type': 'string',
|
||||||
|
'help_text': gettext_noop('For some cloud providers, like OVH, region must be specified'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'id': 'verify_ssl',
|
||||||
|
'label': gettext_noop('Verify SSL'),
|
||||||
|
'type': 'boolean',
|
||||||
|
'default': True,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
'required': ['username', 'password', 'host', 'project'],
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
ManagedCredentialType(
|
||||||
|
namespace='vmware',
|
||||||
|
kind='cloud',
|
||||||
|
name=gettext_noop('VMware vCenter'),
|
||||||
|
managed=True,
|
||||||
|
inputs={
|
||||||
|
'fields': [
|
||||||
|
{
|
||||||
|
'id': 'host',
|
||||||
|
'label': gettext_noop('VCenter Host'),
|
||||||
|
'type': 'string',
|
||||||
|
'help_text': gettext_noop('Enter the hostname or IP address that corresponds to your VMware vCenter.'),
|
||||||
|
},
|
||||||
|
{'id': 'username', 'label': gettext_noop('Username'), 'type': 'string'},
|
||||||
|
{
|
||||||
|
'id': 'password',
|
||||||
|
'label': gettext_noop('Password'),
|
||||||
|
'type': 'string',
|
||||||
|
'secret': True,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
'required': ['host', 'username', 'password'],
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
ManagedCredentialType(
|
||||||
|
namespace='satellite6',
|
||||||
|
kind='cloud',
|
||||||
|
name=gettext_noop('Red Hat Satellite 6'),
|
||||||
|
managed=True,
|
||||||
|
inputs={
|
||||||
|
'fields': [
|
||||||
|
{
|
||||||
|
'id': 'host',
|
||||||
|
'label': gettext_noop('Satellite 6 URL'),
|
||||||
|
'type': 'string',
|
||||||
|
'help_text': gettext_noop('Enter the URL that corresponds to your Red Hat Satellite 6 server. For example, https://satellite.example.org'),
|
||||||
|
},
|
||||||
|
{'id': 'username', 'label': gettext_noop('Username'), 'type': 'string'},
|
||||||
|
{
|
||||||
|
'id': 'password',
|
||||||
|
'label': gettext_noop('Password'),
|
||||||
|
'type': 'string',
|
||||||
|
'secret': True,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
'required': ['host', 'username', 'password'],
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
ManagedCredentialType(
|
||||||
|
namespace='gce',
|
||||||
|
kind='cloud',
|
||||||
|
name=gettext_noop('Google Compute Engine'),
|
||||||
|
managed=True,
|
||||||
|
inputs={
|
||||||
|
'fields': [
|
||||||
|
{
|
||||||
|
'id': 'username',
|
||||||
|
'label': gettext_noop('Service Account Email Address'),
|
||||||
|
'type': 'string',
|
||||||
|
'help_text': gettext_noop('The email address assigned to the Google Compute Engine service account.'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'id': 'project',
|
||||||
|
'label': 'Project',
|
||||||
|
'type': 'string',
|
||||||
|
'help_text': gettext_noop(
|
||||||
|
'The Project ID is the GCE assigned identification. '
|
||||||
|
'It is often constructed as three words or two words '
|
||||||
|
'followed by a three-digit number. Examples: project-id-000 '
|
||||||
|
'and another-project-id'
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'id': 'ssh_key_data',
|
||||||
|
'label': gettext_noop('RSA Private Key'),
|
||||||
|
'type': 'string',
|
||||||
|
'format': 'ssh_private_key',
|
||||||
|
'secret': True,
|
||||||
|
'multiline': True,
|
||||||
|
'help_text': gettext_noop('Paste the contents of the PEM file associated with the service account email.'),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
'required': ['username', 'ssh_key_data'],
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
ManagedCredentialType(
|
||||||
|
namespace='azure_rm',
|
||||||
|
kind='cloud',
|
||||||
|
name=gettext_noop('Microsoft Azure Resource Manager'),
|
||||||
|
managed=True,
|
||||||
|
inputs={
|
||||||
|
'fields': [
|
||||||
|
{
|
||||||
|
'id': 'subscription',
|
||||||
|
'label': gettext_noop('Subscription ID'),
|
||||||
|
'type': 'string',
|
||||||
|
'help_text': gettext_noop('Subscription ID is an Azure construct, which is mapped to a username.'),
|
||||||
|
},
|
||||||
|
{'id': 'username', 'label': gettext_noop('Username'), 'type': 'string'},
|
||||||
|
{
|
||||||
|
'id': 'password',
|
||||||
|
'label': gettext_noop('Password'),
|
||||||
|
'type': 'string',
|
||||||
|
'secret': True,
|
||||||
|
},
|
||||||
|
{'id': 'client', 'label': gettext_noop('Client ID'), 'type': 'string'},
|
||||||
|
{
|
||||||
|
'id': 'secret',
|
||||||
|
'label': gettext_noop('Client Secret'),
|
||||||
|
'type': 'string',
|
||||||
|
'secret': True,
|
||||||
|
},
|
||||||
|
{'id': 'tenant', 'label': gettext_noop('Tenant ID'), 'type': 'string'},
|
||||||
|
{
|
||||||
|
'id': 'cloud_environment',
|
||||||
|
'label': gettext_noop('Azure Cloud Environment'),
|
||||||
|
'type': 'string',
|
||||||
|
'help_text': gettext_noop('Environment variable AZURE_CLOUD_ENVIRONMENT when using Azure GovCloud or Azure stack.'),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
'required': ['subscription'],
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
ManagedCredentialType(
|
||||||
|
namespace='github_token',
|
||||||
|
kind='token',
|
||||||
|
name=gettext_noop('GitHub Personal Access Token'),
|
||||||
|
managed=True,
|
||||||
|
inputs={
|
||||||
|
'fields': [
|
||||||
|
{
|
||||||
|
'id': 'token',
|
||||||
|
'label': gettext_noop('Token'),
|
||||||
|
'type': 'string',
|
||||||
|
'secret': True,
|
||||||
|
'help_text': gettext_noop('This token needs to come from your profile settings in GitHub'),
|
||||||
|
}
|
||||||
|
],
|
||||||
|
'required': ['token'],
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
ManagedCredentialType(
|
||||||
|
namespace='gitlab_token',
|
||||||
|
kind='token',
|
||||||
|
name=gettext_noop('GitLab Personal Access Token'),
|
||||||
|
managed=True,
|
||||||
|
inputs={
|
||||||
|
'fields': [
|
||||||
|
{
|
||||||
|
'id': 'token',
|
||||||
|
'label': gettext_noop('Token'),
|
||||||
|
'type': 'string',
|
||||||
|
'secret': True,
|
||||||
|
'help_text': gettext_noop('This token needs to come from your profile settings in GitLab'),
|
||||||
|
}
|
||||||
|
],
|
||||||
|
'required': ['token'],
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
ManagedCredentialType(
|
||||||
|
namespace='bitbucket_dc_token',
|
||||||
|
kind='token',
|
||||||
|
name=gettext_noop('Bitbucket Data Center HTTP Access Token'),
|
||||||
|
managed=True,
|
||||||
|
inputs={
|
||||||
|
'fields': [
|
||||||
|
{
|
||||||
|
'id': 'token',
|
||||||
|
'label': gettext_noop('Token'),
|
||||||
|
'type': 'string',
|
||||||
|
'secret': True,
|
||||||
|
'help_text': gettext_noop('This token needs to come from your user settings in Bitbucket'),
|
||||||
|
}
|
||||||
|
],
|
||||||
|
'required': ['token'],
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
ManagedCredentialType(
|
||||||
|
namespace='insights',
|
||||||
|
kind='insights',
|
||||||
|
name=gettext_noop('Insights'),
|
||||||
|
managed=True,
|
||||||
|
inputs={
|
||||||
|
'fields': [
|
||||||
|
{'id': 'username', 'label': gettext_noop('Username'), 'type': 'string'},
|
||||||
|
{'id': 'password', 'label': gettext_noop('Password'), 'type': 'string', 'secret': True},
|
||||||
|
],
|
||||||
|
'required': ['username', 'password'],
|
||||||
|
},
|
||||||
|
injectors={
|
||||||
|
'extra_vars': {
|
||||||
|
"scm_username": "{{username}}",
|
||||||
|
"scm_password": "{{password}}",
|
||||||
|
},
|
||||||
|
'env': {
|
||||||
|
'INSIGHTS_USER': '{{username}}',
|
||||||
|
'INSIGHTS_PASSWORD': '{{password}}',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
ManagedCredentialType(
|
||||||
|
namespace='rhv',
|
||||||
|
kind='cloud',
|
||||||
|
name=gettext_noop('Red Hat Virtualization'),
|
||||||
|
managed=True,
|
||||||
|
inputs={
|
||||||
|
'fields': [
|
||||||
|
{'id': 'host', 'label': gettext_noop('Host (Authentication URL)'), 'type': 'string', 'help_text': gettext_noop('The host to authenticate with.')},
|
||||||
|
{'id': 'username', 'label': gettext_noop('Username'), 'type': 'string'},
|
||||||
|
{
|
||||||
|
'id': 'password',
|
||||||
|
'label': gettext_noop('Password'),
|
||||||
|
'type': 'string',
|
||||||
|
'secret': True,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'id': 'ca_file',
|
||||||
|
'label': gettext_noop('CA File'),
|
||||||
|
'type': 'string',
|
||||||
|
'help_text': gettext_noop('Absolute file path to the CA file to use (optional)'),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
'required': ['host', 'username', 'password'],
|
||||||
|
},
|
||||||
|
injectors={
|
||||||
|
# The duplication here is intentional; the ovirt4 inventory plugin
|
||||||
|
# writes a .ini file for authentication, while the ansible modules for
|
||||||
|
# ovirt4 use a separate authentication process that support
|
||||||
|
# environment variables; by injecting both, we support both
|
||||||
|
'file': {
|
||||||
|
'template': '\n'.join(
|
||||||
|
[
|
||||||
|
'[ovirt]',
|
||||||
|
'ovirt_url={{host}}',
|
||||||
|
'ovirt_username={{username}}',
|
||||||
|
'ovirt_password={{password}}',
|
||||||
|
'{% if ca_file %}ovirt_ca_file={{ca_file}}{% endif %}',
|
||||||
|
]
|
||||||
|
)
|
||||||
|
},
|
||||||
|
'env': {'OVIRT_INI_PATH': '{{tower.filename}}', 'OVIRT_URL': '{{host}}', 'OVIRT_USERNAME': '{{username}}', 'OVIRT_PASSWORD': '{{password}}'},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
ManagedCredentialType(
|
||||||
|
namespace='controller',
|
||||||
|
kind='cloud',
|
||||||
|
name=gettext_noop('Red Hat Ansible Automation Platform'),
|
||||||
|
managed=True,
|
||||||
|
inputs={
|
||||||
|
'fields': [
|
||||||
|
{
|
||||||
|
'id': 'host',
|
||||||
|
'label': gettext_noop('Red Hat Ansible Automation Platform'),
|
||||||
|
'type': 'string',
|
||||||
|
'help_text': gettext_noop('Red Hat Ansible Automation Platform base URL to authenticate with.'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'id': 'username',
|
||||||
|
'label': gettext_noop('Username'),
|
||||||
|
'type': 'string',
|
||||||
|
'help_text': gettext_noop(
|
||||||
|
'Red Hat Ansible Automation Platform username id to authenticate as.This should not be set if an OAuth token is being used.'
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'id': 'password',
|
||||||
|
'label': gettext_noop('Password'),
|
||||||
|
'type': 'string',
|
||||||
|
'secret': True,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'id': 'oauth_token',
|
||||||
|
'label': gettext_noop('OAuth Token'),
|
||||||
|
'type': 'string',
|
||||||
|
'secret': True,
|
||||||
|
'help_text': gettext_noop('An OAuth token to use to authenticate with.This should not be set if username/password are being used.'),
|
||||||
|
},
|
||||||
|
{'id': 'verify_ssl', 'label': gettext_noop('Verify SSL'), 'type': 'boolean', 'secret': False},
|
||||||
|
],
|
||||||
|
'required': ['host'],
|
||||||
|
},
|
||||||
|
injectors={
|
||||||
|
'env': {
|
||||||
|
'TOWER_HOST': '{{host}}',
|
||||||
|
'TOWER_USERNAME': '{{username}}',
|
||||||
|
'TOWER_PASSWORD': '{{password}}',
|
||||||
|
'TOWER_VERIFY_SSL': '{{verify_ssl}}',
|
||||||
|
'TOWER_OAUTH_TOKEN': '{{oauth_token}}',
|
||||||
|
'CONTROLLER_HOST': '{{host}}',
|
||||||
|
'CONTROLLER_USERNAME': '{{username}}',
|
||||||
|
'CONTROLLER_PASSWORD': '{{password}}',
|
||||||
|
'CONTROLLER_VERIFY_SSL': '{{verify_ssl}}',
|
||||||
|
'CONTROLLER_OAUTH_TOKEN': '{{oauth_token}}',
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
ManagedCredentialType(
|
||||||
|
namespace='kubernetes_bearer_token',
|
||||||
|
kind='kubernetes',
|
||||||
|
name=gettext_noop('OpenShift or Kubernetes API Bearer Token'),
|
||||||
|
inputs={
|
||||||
|
'fields': [
|
||||||
|
{
|
||||||
|
'id': 'host',
|
||||||
|
'label': gettext_noop('OpenShift or Kubernetes API Endpoint'),
|
||||||
|
'type': 'string',
|
||||||
|
'help_text': gettext_noop('The OpenShift or Kubernetes API Endpoint to authenticate with.'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'id': 'bearer_token',
|
||||||
|
'label': gettext_noop('API authentication bearer token'),
|
||||||
|
'type': 'string',
|
||||||
|
'secret': True,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'id': 'verify_ssl',
|
||||||
|
'label': gettext_noop('Verify SSL'),
|
||||||
|
'type': 'boolean',
|
||||||
|
'default': True,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'id': 'ssl_ca_cert',
|
||||||
|
'label': gettext_noop('Certificate Authority data'),
|
||||||
|
'type': 'string',
|
||||||
|
'secret': True,
|
||||||
|
'multiline': True,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
'required': ['host', 'bearer_token'],
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
ManagedCredentialType(
|
||||||
|
namespace='registry',
|
||||||
|
kind='registry',
|
||||||
|
name=gettext_noop('Container Registry'),
|
||||||
|
inputs={
|
||||||
|
'fields': [
|
||||||
|
{
|
||||||
|
'id': 'host',
|
||||||
|
'label': gettext_noop('Authentication URL'),
|
||||||
|
'type': 'string',
|
||||||
|
'help_text': gettext_noop('Authentication endpoint for the container registry.'),
|
||||||
|
'default': 'quay.io',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'id': 'username',
|
||||||
|
'label': gettext_noop('Username'),
|
||||||
|
'type': 'string',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'id': 'password',
|
||||||
|
'label': gettext_noop('Password or Token'),
|
||||||
|
'type': 'string',
|
||||||
|
'secret': True,
|
||||||
|
'help_text': gettext_noop('A password or token used to authenticate with'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'id': 'verify_ssl',
|
||||||
|
'label': gettext_noop('Verify SSL'),
|
||||||
|
'type': 'boolean',
|
||||||
|
'default': True,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
'required': ['host'],
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
ManagedCredentialType(
|
||||||
|
namespace='galaxy_api_token',
|
||||||
|
kind='galaxy',
|
||||||
|
name=gettext_noop('Ansible Galaxy/Automation Hub API Token'),
|
||||||
|
inputs={
|
||||||
|
'fields': [
|
||||||
|
{
|
||||||
|
'id': 'url',
|
||||||
|
'label': gettext_noop('Galaxy Server URL'),
|
||||||
|
'type': 'string',
|
||||||
|
'help_text': gettext_noop('The URL of the Galaxy instance to connect to.'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'id': 'auth_url',
|
||||||
|
'label': gettext_noop('Auth Server URL'),
|
||||||
|
'type': 'string',
|
||||||
|
'help_text': gettext_noop('The URL of a Keycloak server token_endpoint, if using SSO auth.'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'id': 'token',
|
||||||
|
'label': gettext_noop('API Token'),
|
||||||
|
'type': 'string',
|
||||||
|
'secret': True,
|
||||||
|
'help_text': gettext_noop('A token to use for authentication against the Galaxy instance.'),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
'required': ['url'],
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
ManagedCredentialType(
|
||||||
|
namespace='gpg_public_key',
|
||||||
|
kind='cryptography',
|
||||||
|
name=gettext_noop('GPG Public Key'),
|
||||||
|
inputs={
|
||||||
|
'fields': [
|
||||||
|
{
|
||||||
|
'id': 'gpg_public_key',
|
||||||
|
'label': gettext_noop('GPG Public Key'),
|
||||||
|
'type': 'string',
|
||||||
|
'secret': True,
|
||||||
|
'multiline': True,
|
||||||
|
'help_text': gettext_noop('GPG Public Key used to validate content signatures.'),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
'required': ['gpg_public_key'],
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
ManagedCredentialType(
|
||||||
|
namespace='terraform',
|
||||||
|
kind='cloud',
|
||||||
|
name=gettext_noop('Terraform backend configuration'),
|
||||||
|
managed=True,
|
||||||
|
inputs={
|
||||||
|
'fields': [
|
||||||
|
{
|
||||||
|
'id': 'configuration',
|
||||||
|
'label': gettext_noop('Backend configuration'),
|
||||||
|
'type': 'string',
|
||||||
|
'secret': True,
|
||||||
|
'multiline': True,
|
||||||
|
'help_text': gettext_noop('Terraform backend config as Hashicorp configuration language.'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'id': 'gce_credentials',
|
||||||
|
'label': gettext_noop('Google Cloud Platform account credentials'),
|
||||||
|
'type': 'string',
|
||||||
|
'secret': True,
|
||||||
|
'multiline': True,
|
||||||
|
'help_text': gettext_noop('Google Cloud Platform account credentials in JSON format.'),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
'required': ['configuration'],
|
||||||
|
},
|
||||||
|
)
|
||||||
0
awx_plugins/inventory/__init__.py
Normal file
0
awx_plugins/inventory/__init__.py
Normal file
303
awx_plugins/inventory/plugins.py
Normal file
303
awx_plugins/inventory/plugins.py
Normal file
@@ -0,0 +1,303 @@
|
|||||||
|
import yaml
|
||||||
|
import stat
|
||||||
|
import tempfile
|
||||||
|
|
||||||
|
import os.path
|
||||||
|
|
||||||
|
from awx_plugins.credentials.injectors import _openstack_data
|
||||||
|
from awx.main.utils.execution_environments import to_container_path
|
||||||
|
|
||||||
|
from awx.main.utils.licensing import server_product_name
|
||||||
|
|
||||||
|
|
||||||
|
class PluginFileInjector(object):
|
||||||
|
plugin_name = None # Ansible core name used to reference plugin
|
||||||
|
# base injector should be one of None, "managed", or "template"
|
||||||
|
# this dictates which logic to borrow from playbook injectors
|
||||||
|
base_injector = None
|
||||||
|
# every source should have collection, these are for the collection name
|
||||||
|
namespace = None
|
||||||
|
collection = None
|
||||||
|
collection_migration = '2.9' # Starting with this version, we use collections
|
||||||
|
use_fqcn = False # plugin: name versus plugin: namespace.collection.name
|
||||||
|
|
||||||
|
# TODO: delete this method and update unit tests
|
||||||
|
@classmethod
|
||||||
|
def get_proper_name(cls):
|
||||||
|
if cls.plugin_name is None:
|
||||||
|
return None
|
||||||
|
return f'{cls.namespace}.{cls.collection}.{cls.plugin_name}'
|
||||||
|
|
||||||
|
@property
|
||||||
|
def filename(self):
|
||||||
|
"""Inventory filename for using the inventory plugin
|
||||||
|
This is created dynamically, but the auto plugin requires this exact naming
|
||||||
|
"""
|
||||||
|
return '{0}.yml'.format(self.plugin_name)
|
||||||
|
|
||||||
|
def inventory_contents(self, inventory_update, private_data_dir):
|
||||||
|
"""Returns a string that is the content for the inventory file for the inventory plugin"""
|
||||||
|
return yaml.safe_dump(self.inventory_as_dict(inventory_update, private_data_dir), default_flow_style=False, width=1000)
|
||||||
|
|
||||||
|
def inventory_as_dict(self, inventory_update, private_data_dir):
|
||||||
|
source_vars = dict(inventory_update.source_vars_dict) # make a copy
|
||||||
|
'''
|
||||||
|
None conveys that we should use the user-provided plugin.
|
||||||
|
Note that a plugin value of '' should still be overridden.
|
||||||
|
'''
|
||||||
|
if self.plugin_name is not None:
|
||||||
|
if hasattr(self, 'downstream_namespace') and server_product_name() != 'AWX':
|
||||||
|
source_vars['plugin'] = f'{self.downstream_namespace}.{self.downstream_collection}.{self.plugin_name}'
|
||||||
|
elif self.use_fqcn:
|
||||||
|
source_vars['plugin'] = f'{self.namespace}.{self.collection}.{self.plugin_name}'
|
||||||
|
else:
|
||||||
|
source_vars['plugin'] = self.plugin_name
|
||||||
|
return source_vars
|
||||||
|
|
||||||
|
def build_env(self, inventory_update, env, private_data_dir, private_data_files):
|
||||||
|
injector_env = self.get_plugin_env(inventory_update, private_data_dir, private_data_files)
|
||||||
|
env.update(injector_env)
|
||||||
|
# All CLOUD_PROVIDERS sources implement as inventory plugin from collection
|
||||||
|
env['ANSIBLE_INVENTORY_ENABLED'] = 'auto'
|
||||||
|
return env
|
||||||
|
|
||||||
|
def _get_shared_env(self, inventory_update, private_data_dir, private_data_files):
|
||||||
|
"""By default, we will apply the standard managed injectors"""
|
||||||
|
injected_env = {}
|
||||||
|
credential = inventory_update.get_cloud_credential()
|
||||||
|
# some sources may have no credential, specifically ec2
|
||||||
|
if credential is None:
|
||||||
|
return injected_env
|
||||||
|
if self.base_injector in ('managed', 'template'):
|
||||||
|
injected_env['INVENTORY_UPDATE_ID'] = str(inventory_update.pk) # so injector knows this is inventory
|
||||||
|
if self.base_injector == 'managed':
|
||||||
|
from awx_plugins.credentials import injectors as builtin_injectors
|
||||||
|
|
||||||
|
cred_kind = inventory_update.source.replace('ec2', 'aws')
|
||||||
|
if cred_kind in dir(builtin_injectors):
|
||||||
|
getattr(builtin_injectors, cred_kind)(credential, injected_env, private_data_dir)
|
||||||
|
elif self.base_injector == 'template':
|
||||||
|
safe_env = injected_env.copy()
|
||||||
|
args = []
|
||||||
|
credential.credential_type.inject_credential(credential, injected_env, safe_env, args, private_data_dir)
|
||||||
|
# NOTE: safe_env is handled externally to injector class by build_safe_env static method
|
||||||
|
# that means that managed injectors must only inject detectable env keys
|
||||||
|
# enforcement of this is accomplished by tests
|
||||||
|
return injected_env
|
||||||
|
|
||||||
|
def get_plugin_env(self, inventory_update, private_data_dir, private_data_files):
|
||||||
|
env = self._get_shared_env(inventory_update, private_data_dir, private_data_files)
|
||||||
|
return env
|
||||||
|
|
||||||
|
def build_private_data(self, inventory_update, private_data_dir):
|
||||||
|
return self.build_plugin_private_data(inventory_update, private_data_dir)
|
||||||
|
|
||||||
|
def build_plugin_private_data(self, inventory_update, private_data_dir):
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
class azure_rm(PluginFileInjector):
|
||||||
|
plugin_name = 'azure_rm'
|
||||||
|
base_injector = 'managed'
|
||||||
|
namespace = 'azure'
|
||||||
|
collection = 'azcollection'
|
||||||
|
|
||||||
|
def get_plugin_env(self, *args, **kwargs):
|
||||||
|
ret = super(azure_rm, self).get_plugin_env(*args, **kwargs)
|
||||||
|
# We need native jinja2 types so that tags can give JSON null value
|
||||||
|
ret['ANSIBLE_JINJA2_NATIVE'] = str(True)
|
||||||
|
return ret
|
||||||
|
|
||||||
|
|
||||||
|
class ec2(PluginFileInjector):
|
||||||
|
plugin_name = 'aws_ec2'
|
||||||
|
base_injector = 'managed'
|
||||||
|
namespace = 'amazon'
|
||||||
|
collection = 'aws'
|
||||||
|
|
||||||
|
def get_plugin_env(self, *args, **kwargs):
|
||||||
|
ret = super(ec2, self).get_plugin_env(*args, **kwargs)
|
||||||
|
# We need native jinja2 types so that ec2_state_code will give integer
|
||||||
|
ret['ANSIBLE_JINJA2_NATIVE'] = str(True)
|
||||||
|
return ret
|
||||||
|
|
||||||
|
|
||||||
|
class gce(PluginFileInjector):
|
||||||
|
plugin_name = 'gcp_compute'
|
||||||
|
base_injector = 'managed'
|
||||||
|
namespace = 'google'
|
||||||
|
collection = 'cloud'
|
||||||
|
|
||||||
|
def get_plugin_env(self, *args, **kwargs):
|
||||||
|
ret = super(gce, self).get_plugin_env(*args, **kwargs)
|
||||||
|
# We need native jinja2 types so that ip addresses can give JSON null value
|
||||||
|
ret['ANSIBLE_JINJA2_NATIVE'] = str(True)
|
||||||
|
return ret
|
||||||
|
|
||||||
|
def inventory_as_dict(self, inventory_update, private_data_dir):
|
||||||
|
ret = super().inventory_as_dict(inventory_update, private_data_dir)
|
||||||
|
credential = inventory_update.get_cloud_credential()
|
||||||
|
# InventorySource.source_vars take precedence over ENV vars
|
||||||
|
if 'projects' not in ret:
|
||||||
|
ret['projects'] = [credential.get_input('project', default='')]
|
||||||
|
return ret
|
||||||
|
|
||||||
|
|
||||||
|
class vmware(PluginFileInjector):
|
||||||
|
plugin_name = 'vmware_vm_inventory'
|
||||||
|
base_injector = 'managed'
|
||||||
|
namespace = 'community'
|
||||||
|
collection = 'vmware'
|
||||||
|
|
||||||
|
|
||||||
|
class openstack(PluginFileInjector):
|
||||||
|
plugin_name = 'openstack'
|
||||||
|
namespace = 'openstack'
|
||||||
|
collection = 'cloud'
|
||||||
|
|
||||||
|
def _get_clouds_dict(self, inventory_update, cred, private_data_dir):
|
||||||
|
openstack_data = _openstack_data(cred)
|
||||||
|
|
||||||
|
openstack_data['clouds']['devstack']['private'] = inventory_update.source_vars_dict.get('private', True)
|
||||||
|
ansible_variables = {
|
||||||
|
'use_hostnames': True,
|
||||||
|
'expand_hostvars': False,
|
||||||
|
'fail_on_errors': True,
|
||||||
|
}
|
||||||
|
provided_count = 0
|
||||||
|
for var_name in ansible_variables:
|
||||||
|
if var_name in inventory_update.source_vars_dict:
|
||||||
|
ansible_variables[var_name] = inventory_update.source_vars_dict[var_name]
|
||||||
|
provided_count += 1
|
||||||
|
if provided_count:
|
||||||
|
# Must we provide all 3 because the user provides any 1 of these??
|
||||||
|
# this probably results in some incorrect mangling of the defaults
|
||||||
|
openstack_data['ansible'] = ansible_variables
|
||||||
|
return openstack_data
|
||||||
|
|
||||||
|
def build_plugin_private_data(self, inventory_update, private_data_dir):
|
||||||
|
credential = inventory_update.get_cloud_credential()
|
||||||
|
private_data = {'credentials': {}}
|
||||||
|
|
||||||
|
openstack_data = self._get_clouds_dict(inventory_update, credential, private_data_dir)
|
||||||
|
private_data['credentials'][credential] = yaml.safe_dump(openstack_data, default_flow_style=False, allow_unicode=True)
|
||||||
|
return private_data
|
||||||
|
|
||||||
|
def get_plugin_env(self, inventory_update, private_data_dir, private_data_files):
|
||||||
|
env = super(openstack, self).get_plugin_env(inventory_update, private_data_dir, private_data_files)
|
||||||
|
credential = inventory_update.get_cloud_credential()
|
||||||
|
cred_data = private_data_files['credentials']
|
||||||
|
env['OS_CLIENT_CONFIG_FILE'] = to_container_path(cred_data[credential], private_data_dir)
|
||||||
|
return env
|
||||||
|
|
||||||
|
|
||||||
|
class rhv(PluginFileInjector):
|
||||||
|
"""ovirt uses the custom credential templating, and that is all"""
|
||||||
|
|
||||||
|
plugin_name = 'ovirt'
|
||||||
|
base_injector = 'template'
|
||||||
|
initial_version = '2.9'
|
||||||
|
namespace = 'ovirt'
|
||||||
|
collection = 'ovirt'
|
||||||
|
downstream_namespace = 'redhat'
|
||||||
|
downstream_collection = 'rhv'
|
||||||
|
use_fqcn = True
|
||||||
|
|
||||||
|
|
||||||
|
class satellite6(PluginFileInjector):
|
||||||
|
plugin_name = 'foreman'
|
||||||
|
namespace = 'theforeman'
|
||||||
|
collection = 'foreman'
|
||||||
|
downstream_namespace = 'redhat'
|
||||||
|
downstream_collection = 'satellite'
|
||||||
|
use_fqcn = True
|
||||||
|
|
||||||
|
def get_plugin_env(self, inventory_update, private_data_dir, private_data_files):
|
||||||
|
# this assumes that this is merged
|
||||||
|
# https://github.com/ansible/ansible/pull/52693
|
||||||
|
credential = inventory_update.get_cloud_credential()
|
||||||
|
ret = super(satellite6, self).get_plugin_env(inventory_update, private_data_dir, private_data_files)
|
||||||
|
if credential:
|
||||||
|
ret['FOREMAN_SERVER'] = credential.get_input('host', default='')
|
||||||
|
ret['FOREMAN_USER'] = credential.get_input('username', default='')
|
||||||
|
ret['FOREMAN_PASSWORD'] = credential.get_input('password', default='')
|
||||||
|
return ret
|
||||||
|
|
||||||
|
|
||||||
|
class terraform(PluginFileInjector):
|
||||||
|
plugin_name = 'terraform_state'
|
||||||
|
namespace = 'cloud'
|
||||||
|
collection = 'terraform'
|
||||||
|
use_fqcn = True
|
||||||
|
|
||||||
|
def inventory_as_dict(self, inventory_update, private_data_dir):
|
||||||
|
ret = super().inventory_as_dict(inventory_update, private_data_dir)
|
||||||
|
credential = inventory_update.get_cloud_credential()
|
||||||
|
config_cred = credential.get_input('configuration')
|
||||||
|
if config_cred:
|
||||||
|
handle, path = tempfile.mkstemp(dir=os.path.join(private_data_dir, 'env'))
|
||||||
|
with os.fdopen(handle, 'w') as f:
|
||||||
|
os.chmod(path, stat.S_IRUSR | stat.S_IWUSR)
|
||||||
|
f.write(config_cred)
|
||||||
|
ret['backend_config_files'] = to_container_path(path, private_data_dir)
|
||||||
|
return ret
|
||||||
|
|
||||||
|
def build_plugin_private_data(self, inventory_update, private_data_dir):
|
||||||
|
credential = inventory_update.get_cloud_credential()
|
||||||
|
|
||||||
|
private_data = {'credentials': {}}
|
||||||
|
gce_cred = credential.get_input('gce_credentials', default=None)
|
||||||
|
if gce_cred:
|
||||||
|
private_data['credentials'][credential] = gce_cred
|
||||||
|
return private_data
|
||||||
|
|
||||||
|
def get_plugin_env(self, inventory_update, private_data_dir, private_data_files):
|
||||||
|
env = super(terraform, self).get_plugin_env(inventory_update, private_data_dir, private_data_files)
|
||||||
|
credential = inventory_update.get_cloud_credential()
|
||||||
|
cred_data = private_data_files['credentials']
|
||||||
|
if credential in cred_data:
|
||||||
|
env['GOOGLE_BACKEND_CREDENTIALS'] = to_container_path(cred_data[credential], private_data_dir)
|
||||||
|
return env
|
||||||
|
|
||||||
|
|
||||||
|
class controller(PluginFileInjector):
|
||||||
|
plugin_name = 'tower' # TODO: relying on routing for now, update after EEs pick up revised collection
|
||||||
|
base_injector = 'template'
|
||||||
|
namespace = 'awx'
|
||||||
|
collection = 'awx'
|
||||||
|
downstream_namespace = 'ansible'
|
||||||
|
downstream_collection = 'controller'
|
||||||
|
|
||||||
|
|
||||||
|
class insights(PluginFileInjector):
|
||||||
|
plugin_name = 'insights'
|
||||||
|
base_injector = 'template'
|
||||||
|
namespace = 'redhatinsights'
|
||||||
|
collection = 'insights'
|
||||||
|
downstream_namespace = 'redhat'
|
||||||
|
downstream_collection = 'insights'
|
||||||
|
use_fqcn = True
|
||||||
|
|
||||||
|
|
||||||
|
class openshift_virtualization(PluginFileInjector):
|
||||||
|
plugin_name = 'kubevirt'
|
||||||
|
base_injector = 'template'
|
||||||
|
namespace = 'kubevirt'
|
||||||
|
collection = 'core'
|
||||||
|
downstream_namespace = 'redhat'
|
||||||
|
downstream_collection = 'openshift_virtualization'
|
||||||
|
use_fqcn = True
|
||||||
|
|
||||||
|
|
||||||
|
class constructed(PluginFileInjector):
|
||||||
|
plugin_name = 'constructed'
|
||||||
|
namespace = 'ansible'
|
||||||
|
collection = 'builtin'
|
||||||
|
|
||||||
|
def build_env(self, *args, **kwargs):
|
||||||
|
env = super().build_env(*args, **kwargs)
|
||||||
|
# Enable script inventory plugin so we pick up the script files from source inventories
|
||||||
|
env['ANSIBLE_INVENTORY_ENABLED'] += ',script'
|
||||||
|
env['ANSIBLE_INVENTORY_ANY_UNPARSED_IS_FAILED'] = 'True'
|
||||||
|
return env
|
||||||
|
|
||||||
@@ -13,7 +13,10 @@ Likewise content in this guide can be removed or replaced if it applies to funct
|
|||||||
|
|
||||||
**Join us online**
|
**Join us online**
|
||||||
|
|
||||||
Need help or want to discuss AWX including the documentation? See the :ref:`Communication guide<communication>` to learn how to join the conversation!
|
We talk about AWX documentation on Matrix at `#docs:ansible.im <https://matrix.to/#/#docs:ansible.im>`_ and on libera IRC at ``#ansible-docs`` if you ever want to join us and chat about the docs!
|
||||||
|
|
||||||
|
You can also find lots of AWX discussion and get answers to questions at `forum.ansible.com <https://forum.ansible.com/>`_.
|
||||||
|
|
||||||
|
|
||||||
.. toctree::
|
.. toctree::
|
||||||
:maxdepth: 2
|
:maxdepth: 2
|
||||||
|
|||||||
@@ -320,6 +320,43 @@ Items surrounded by ``{}`` will be substituted when the log error is generated.
|
|||||||
- **error**: The error message returned by the API or, if no error is specified, the HTTP status as text
|
- **error**: The error message returned by the API or, if no error is specified, the HTTP status as text
|
||||||
|
|
||||||
|
|
||||||
|
.. _logging-api-otel:
|
||||||
|
|
||||||
|
OTel configuration with AWX
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
You can integrate OTel with AWX by configuring logging manually to point to your OTel collector. To do this, add the following codeblock in your `settings file <https://github.com/ansible/awx/blob/devel/tools/docker-compose/ansible/roles/sources/templates/local_settings.py.j2#L50>`_ (``local_settings.py.j2``):
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
LOGGING['handlers']['otel'] |= {
|
||||||
|
'class': 'awx.main.utils.handlers.OTLPHandler',
|
||||||
|
'endpoint': 'http://otel:4317',
|
||||||
|
}
|
||||||
|
# Add otel log handler to all log handlers where propagate is False
|
||||||
|
for name in LOGGING['loggers'].keys():
|
||||||
|
if not LOGGING['loggers'][name].get('propagate', True):
|
||||||
|
handler = LOGGING['loggers'][name].get('handlers', [])
|
||||||
|
if 'otel' not in handler:
|
||||||
|
LOGGING['loggers'][name].get('handlers', []).append('otel')
|
||||||
|
|
||||||
|
# Everything without explicit propagate=False ends up logging to 'awx' so add it
|
||||||
|
handler = LOGGING['loggers']['awx'].get('handlers', [])
|
||||||
|
if 'otel' not in handler:
|
||||||
|
LOGGING['loggers']['awx'].get('handlers', []).append('otel')
|
||||||
|
|
||||||
|
Edit ``'endpoint': 'http://otel:4317',`` to point to your OTel collector.
|
||||||
|
|
||||||
|
To see it working in the dev environment, set the following:
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
|
OTEL=true GRAFANA=true LOKI=true PROMETHEUS=true make docker-compose
|
||||||
|
|
||||||
|
Then go to `http://localhost:3001 <http://localhost:3001>`_ to access Grafana and see the logs.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Troubleshoot Logging
|
Troubleshoot Logging
|
||||||
---------------------
|
---------------------
|
||||||
|
|
||||||
|
|||||||
@@ -1,39 +0,0 @@
|
|||||||
.. _communication:
|
|
||||||
|
|
||||||
Communication
|
|
||||||
=============
|
|
||||||
|
|
||||||
We welcome your feedback, questions and ideas. Here's how to reach the community.
|
|
||||||
|
|
||||||
.. _code_of_conduct:
|
|
||||||
|
|
||||||
Code of Conduct
|
|
||||||
---------------
|
|
||||||
|
|
||||||
All communication and interactions in the Ansible Community are governed by our :ref:`code_of_conduct`. Please read and abide by it!
|
|
||||||
Reach out to our community team at `codeofconduct@ansible.com <mailto:codeofconduct@ansible.com>`_ if you have any questions or need assistance.
|
|
||||||
|
|
||||||
.. _forum:
|
|
||||||
|
|
||||||
Forum
|
|
||||||
-----
|
|
||||||
|
|
||||||
Join the `Ansible Forum <https://forum.ansible.com>`_ as a single starting point and our default communication platform for questions and help, development discussions, events, and much more. `Register <https://forum.ansible.com/signup?>`_ to join the community. Search by categories and tags to find interesting topics or start a new one; subscribe only to topics you need!
|
|
||||||
|
|
||||||
* `Get Help <https://forum.ansible.com/c/help/6>`_: get help or help others. Please add appropriate tags if you start new discussions, for example `awx`, `ee`, and `documentation`.
|
|
||||||
* `Posts tagged with 'awx' <https://forum.ansible.com/tag/awx>`_: subscribe to participate in project/technology-related conversations. There are other related tags in the forum you can use.
|
|
||||||
* `Bullhorn newsletter <https://docs.ansible.com/ansible/devel/community/communication.html#the-bullhorn>`_: used to announce releases and important changes.
|
|
||||||
* `Social Spaces <https://forum.ansible.com/c/chat/4>`_: gather and interact with fellow enthusiasts.
|
|
||||||
* `News & Announcements <https://forum.ansible.com/c/news/5>`_: track project-wide announcements including social events.
|
|
||||||
|
|
||||||
For more information on the forum navigation, see `Navigating the Ansible forum <https://forum.ansible.com/t/navigating-the-ansible-forum-tags-categories-and-concepts/39>`_ post.
|
|
||||||
|
|
||||||
Matrix
|
|
||||||
------
|
|
||||||
|
|
||||||
For real-time interactions, conversations in the AWX community happen over the Matrix protocol in the following channels:
|
|
||||||
|
|
||||||
* `#awx:ansible.com <https://matrix.to/#/#awx:ansible.com>`_: AWX project-related discussions.
|
|
||||||
* `#docs:ansible.im <https://matrix.to/#/#docs:ansible.im>`_: Ansible and AWX documentation-related discussions.
|
|
||||||
|
|
||||||
For more information, see the community-hosted `Matrix FAQ <https://hackmd.io/@ansible-community/community-matrix-faq>`_.
|
|
||||||
@@ -10,14 +10,13 @@ There are so many ways you can contribute to AWX.
|
|||||||
|
|
||||||
**Join us online**
|
**Join us online**
|
||||||
|
|
||||||
Need help or want to discuss AWX including the documentation? See the :ref:`Communication guide<communication>` to learn how to join the conversation!
|
You can chat with us and ask questions on Matrix at `#awx:ansible.com <https://matrix.to/#/#awx:ansible.com>`_ or visit the `Ansible Community Forum <https://forum.ansible.com/c/project/7/>`_ to find contributor resources.
|
||||||
|
|
||||||
.. toctree::
|
.. toctree::
|
||||||
:maxdepth: 2
|
:maxdepth: 2
|
||||||
:numbered:
|
:numbered:
|
||||||
|
|
||||||
intro
|
intro
|
||||||
communication
|
|
||||||
setting_up
|
setting_up
|
||||||
work_items
|
work_items
|
||||||
report_issues
|
report_issues
|
||||||
@@ -4,4 +4,6 @@ Introduction
|
|||||||
|
|
||||||
Hi there! We're excited to have you as a contributor.
|
Hi there! We're excited to have you as a contributor.
|
||||||
|
|
||||||
Get started with joining the community! See the :ref:`Communication guide<communication>` to learn how.
|
Have questions about this document or anything not covered here? Come chat with us and ask questions on Matrix at `#awx:ansible.com <https://matrix.to/#/#awx:ansible.com>`_.
|
||||||
|
|
||||||
|
Also visit the `Ansible Community Forum <https://forum.ansible.com/c/project/7/>`_ to find contributor resources where you can also submit your questions or concerns.
|
||||||
@@ -9,13 +9,14 @@ as possible. Version information, and an accurate reproducing scenario are criti
|
|||||||
|
|
||||||
Be sure to attach the ``component:docs`` label to your issue. These labels are determined by the template data. Please use the template and fill it out as accurately as possible.
|
Be sure to attach the ``component:docs`` label to your issue. These labels are determined by the template data. Please use the template and fill it out as accurately as possible.
|
||||||
|
|
||||||
Please don't use the issue tracker as a way to ask how to do something. Instead, discuss it on on the Ansible Forum, or you can chat with us and ask questions on Matrix. See the :ref:`Communication guide<communication>` for details.
|
Please don't use the issue tracker as a way to ask how to do something. Instead, discuss it on on the `Ansible Community Forum <https://forum.ansible.com/c/project/7/>`_, or you can chat with us and ask questions on Matrix at `#awx:ansible.com <https://matrix.to/#/#awx:ansible.com>`_.
|
||||||
|
|
||||||
Before opening a new issue, please use the issue search feature to see if what you're experiencing has already been reported. If you have any extra detail to provide, please comment. Otherwise, rather than posting a "me too" comment, please consider giving it a `"thumbs up" <https://github.com/blog/2119-add-reactions-to-pull-requests-issues-and-comment>`_ to give us an indication of the severity of the problem.
|
Before opening a new issue, please use the issue search feature to see if what you're experiencing has already been reported. If you have any extra detail to provide, please comment. Otherwise, rather than posting a "me too" comment, please consider giving it a `"thumbs up" <https://github.com/blog/2119-add-reactions-to-pull-requests-issues-and-comment>`_ to give us an indication of the severity of the problem.
|
||||||
|
|
||||||
See `How issues are resolved <https://github.com/ansible/awx/blob/devel/ISSUES.md#how-issues-are-resolved>`_ for more information about the triaging and resolution process.
|
See `How issues are resolved <https://github.com/ansible/awx/blob/devel/ISSUES.md#how-issues-are-resolved>`_ for more information about the triaging and resolution process.
|
||||||
|
|
||||||
|
|
||||||
Getting help
|
Getting help
|
||||||
-------------
|
-------------
|
||||||
|
|
||||||
See the :ref:`Communication guide<communication>` to learn how to get help.
|
If you require additional assistance, join the discussions on the `Ansible Community Forum <https://forum.ansible.com/c/project/7/>`_. Specify with tags ``#documentation`` and ``#awx`` to narrow down the area(s) of interest. For more information on tags, see `Navigating the Ansible forum — Tags, Categories, and Concepts <https://forum.ansible.com/t/navigating-the-ansible-forum-tags-categories-and-concepts/39>`_. You may also reach out to us and ask questions on Matrix at `#awx:ansible.com <https://matrix.to/#/#awx:ansible.com>`_.
|
||||||
|
|||||||
@@ -18,18 +18,19 @@ Fixing and updating the documentation are always appreciated, so reviewing the b
|
|||||||
Things to know prior to submitting revisions
|
Things to know prior to submitting revisions
|
||||||
----------------------------------------------
|
----------------------------------------------
|
||||||
|
|
||||||
- Please follow the `Ansible code of conduct <http://docs.ansible.com/ansible/latest/community/code_of_conduct.html>`_ in all your interactions with the community.
|
|
||||||
- All doc revisions or additions are done through pull requests against the ``devel`` branch.
|
- All doc revisions or additions are done through pull requests against the ``devel`` branch.
|
||||||
- You must use ``git commit --signoff`` for any commit to be merged, and agree that usage of ``--signoff`` constitutes agreement with the terms of `DCO 1.1 <https://github.com/ansible/awx/blob/devel/DCO_1_1.md>`_.
|
- You must use ``git commit --signoff`` for any commit to be merged, and agree that usage of ``--signoff`` constitutes agreement with the terms of `DCO 1.1 <https://github.com/ansible/awx/blob/devel/DCO_1_1.md>`_.
|
||||||
- Take care to make sure no merge commits are in the submission, and use ``git rebase`` vs ``git merge`` for this reason.
|
- Take care to make sure no merge commits are in the submission, and use ``git rebase`` vs ``git merge`` for this reason.
|
||||||
- If collaborating with someone else on the same branch, consider using ``--force-with-lease`` instead of ``--force``. This will prevent you from accidentally overwriting commits pushed by someone else. For more information, see `git push docs <https://git-scm.com/docs/git-push#git-push---force-with-leaseltrefnamegt>`_.
|
- If collaborating with someone else on the same branch, consider using ``--force-with-lease`` instead of ``--force``. This will prevent you from accidentally overwriting commits pushed by someone else. For more information, see `git push docs <https://git-scm.com/docs/git-push#git-push---force-with-leaseltrefnamegt>`_.
|
||||||
- If submitting a large doc change, it's a good idea to join the :ref:`Ansible Forum<forum>`, and talk about what you would like to do or add first. Use the ``#documentation`` and ``#awx`` tags to help notify relevant people of the topic. This not only helps everyone know what's going on, it also helps save time and effort, if the community decides some changes are needed.
|
- If submitting a large doc change, it's a good idea to join the `Ansible Community Forum <https://forum.ansible.com/c/project/7/>`_, and talk about what you would like to do or add first. Use the ``#documentation`` and ``#awx`` tags to help notify relevant people of the topic. This not only helps everyone know what's going on, it also helps save time and effort, if the community decides some changes are needed. For more information on tags, see `Navigating the Ansible forum — Tags, Categories, and Concepts <https://forum.ansible.com/t/navigating-the-ansible-forum-tags-categories-and-concepts/39>`_.
|
||||||
|
- We ask all of our community members and contributors to adhere to the `Ansible code of conduct <http://docs.ansible.com/ansible/latest/community/code_of_conduct.html>`_. If you have questions, or need assistance, please reach out to our community team at `codeofconduct@ansible.com <mailto:codeofconduct@ansible.com>`_.
|
||||||
|
|
||||||
|
|
||||||
.. Note::
|
.. Note::
|
||||||
|
|
||||||
- Issue assignment will only be done for maintainers of the project. If you decide to work on an issue, please feel free to add a comment in the issue to let others know that you are working on it; but know that we will accept the first pull request from whomever is able to fix an issue. Once your PR is accepted we can add you as an assignee to an issue upon request.
|
- Issue assignment will only be done for maintainers of the project. If you decide to work on an issue, please feel free to add a comment in the issue to let others know that you are working on it; but know that we will accept the first pull request from whomever is able to fix an issue. Once your PR is accepted we can add you as an assignee to an issue upon request.
|
||||||
|
|
||||||
- If you work in a part of the docs that is going through active development, your changes may be rejected, or you may be asked to `rebase`. A good idea before starting work is to have a :ref:`discussion with the community<communication>`.
|
- If you work in a part of the docs that is going through active development, your changes may be rejected, or you may be asked to `rebase`. A good idea before starting work is to have a discussion with us and ask questions on Matrix at `#awx:ansible.com <https://matrix.to/#/#awx:ansible.com>`_ or discuss your ideas on the `Ansible Community Forum <https://forum.ansible.com/c/project/7/>`_.
|
||||||
|
|
||||||
- If you find an issue with the functions of the UI or API, please see the `Reporting Issues <https://github.com/ansible/awx/blob/devel/CONTRIBUTING.md#reporting-issues>`_ section to open an issue.
|
- If you find an issue with the functions of the UI or API, please see the `Reporting Issues <https://github.com/ansible/awx/blob/devel/CONTRIBUTING.md#reporting-issues>`_ section to open an issue.
|
||||||
|
|
||||||
|
|||||||
@@ -12,7 +12,9 @@ Likewise content in this guide can be removed or replaced if it applies to funct
|
|||||||
|
|
||||||
**Join us online**
|
**Join us online**
|
||||||
|
|
||||||
Need help or want to discuss AWX including the documentation? See the :ref:`Communication guide<communication>` to learn how to join the conversation!
|
We talk about AWX documentation on Matrix at `#docs:ansible.im <https://matrix.to/#/#docs:ansible.im>`_ and on libera IRC at ``#ansible-docs`` if you ever want to join us and chat about the docs!
|
||||||
|
|
||||||
|
You can also find lots of AWX discussion and get answers to questions at `forum.ansible.com <https://forum.ansible.com/>`_.
|
||||||
|
|
||||||
.. toctree::
|
.. toctree::
|
||||||
:maxdepth: 2
|
:maxdepth: 2
|
||||||
|
|||||||
@@ -12,7 +12,9 @@ Likewise content in this guide can be removed or replaced if it applies to funct
|
|||||||
|
|
||||||
**Join us online**
|
**Join us online**
|
||||||
|
|
||||||
Need help or want to discuss AWX including the documentation? See the :ref:`Communication guide<communication>` to learn how to join the conversation!
|
We talk about AWX documentation on Matrix at `#docs:ansible.im <https://matrix.to/#/#docs:ansible.im>`_ if you ever want to join us and chat about the docs!
|
||||||
|
|
||||||
|
You can also find lots of AWX discussion and get answers to questions on the `Ansible Community Forum <https://forum.ansible.com/c/project/7/>`_.
|
||||||
|
|
||||||
.. toctree::
|
.. toctree::
|
||||||
:maxdepth: 2
|
:maxdepth: 2
|
||||||
@@ -23,4 +25,4 @@ Need help or want to discuss AWX including the documentation? See the :ref:`Comm
|
|||||||
known_issues
|
known_issues
|
||||||
supported_locales
|
supported_locales
|
||||||
|
|
||||||
.. include:: ../common/copyright.rst
|
.. include:: ../common/copyright.rst
|
||||||
@@ -94,7 +94,7 @@ Field lookups may also be used for more advanced queries, by appending the looku
|
|||||||
|
|
||||||
The following field lookups are supported:
|
The following field lookups are supported:
|
||||||
|
|
||||||
- ``exact``: Exact match (default lookup if not specified).
|
- ``exact``: Exact match (default lookup if not specified, refer to the following note for more information).
|
||||||
- ``iexact``: Case-insensitive version of exact.
|
- ``iexact``: Case-insensitive version of exact.
|
||||||
- ``contains``: Field contains value.
|
- ``contains``: Field contains value.
|
||||||
- ``icontains``: Case-insensitive version of contains.
|
- ``icontains``: Case-insensitive version of contains.
|
||||||
@@ -122,3 +122,18 @@ Filtering based on the requesting user's level of access by query string paramet
|
|||||||
|
|
||||||
- ``role_level``: Level of role to filter on, such as ``admin_role``
|
- ``role_level``: Level of role to filter on, such as ``admin_role``
|
||||||
|
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
Previous releases of AWX returned queries with **__exact** results by default, but you may find that the latest versions are returning a larger subset instead. As a workaround, set the ``limit`` to ``?limit__exact`` for the default filter. For example, ``/api/v2/jobs/?limit__exact=example.domain.com`` results in:
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
|
{
|
||||||
|
"count": 1,
|
||||||
|
"next": null,
|
||||||
|
"previous": null,
|
||||||
|
"results": [
|
||||||
|
...
|
||||||
|
|
||||||
|
.. this note is generically written for AWX. For downstream, the change started in AAP 2.0 so we can be more specific if necessary.
|
||||||
|
|||||||
@@ -12,7 +12,9 @@ Likewise content in this guide can be removed or replaced if it applies to funct
|
|||||||
|
|
||||||
**Join us online**
|
**Join us online**
|
||||||
|
|
||||||
Need help or want to discuss AWX including the documentation? See the :ref:`Communication guide<communication>` to learn how to join the conversation!
|
We talk about AWX documentation on Matrix at `#docs:ansible.im <https://matrix.to/#/#docs:ansible.im>`_ and on libera IRC at ``#ansible-docs`` if you ever want to join us and chat about the docs!
|
||||||
|
|
||||||
|
You can also find lots of AWX discussion and get answers to questions at `forum.ansible.com <https://forum.ansible.com/>`_.
|
||||||
|
|
||||||
.. toctree::
|
.. toctree::
|
||||||
:maxdepth: 2
|
:maxdepth: 2
|
||||||
|
|||||||
@@ -12,7 +12,9 @@ Likewise content in this guide can be removed or replaced if it applies to funct
|
|||||||
|
|
||||||
**Join us online**
|
**Join us online**
|
||||||
|
|
||||||
Need help or want to discuss AWX including the documentation? See the :ref:`Communication guide<communication>` to learn how to join the conversation!
|
We talk about AWX documentation on Matrix at `#docs:ansible.im <https://matrix.to/#/#docs:ansible.im>`_ and on libera IRC at ``#ansible-docs`` if you ever want to join us and chat about the docs!
|
||||||
|
|
||||||
|
You can also find lots of AWX discussion and get answers to questions at `forum.ansible.com <https://forum.ansible.com/>`_.
|
||||||
|
|
||||||
.. toctree::
|
.. toctree::
|
||||||
:maxdepth: 2
|
:maxdepth: 2
|
||||||
|
|||||||
@@ -13,7 +13,9 @@ Likewise content in this guide can be removed or replaced if it applies to funct
|
|||||||
|
|
||||||
**Join us online**
|
**Join us online**
|
||||||
|
|
||||||
Need help or want to discuss AWX including the documentation? See the :ref:`Communication guide<communication>` to learn how to join the conversation!
|
We talk about AWX documentation on Matrix at `#docs:ansible.im <https://matrix.to/#/#docs:ansible.im>`_ and on libera IRC at ``#ansible-docs`` if you ever want to join us and chat about the docs!
|
||||||
|
|
||||||
|
You can also find lots of AWX discussion and get answers to questions at `forum.ansible.com <https://forum.ansible.com/>`_.
|
||||||
|
|
||||||
.. toctree::
|
.. toctree::
|
||||||
:maxdepth: 2
|
:maxdepth: 2
|
||||||
|
|||||||
@@ -264,7 +264,7 @@ This will create an `httpbin` service reachable from the AWX container at `http:
|
|||||||
The Grafana notification type allows you to create Grafana annotations. Details about this feature of Grafana are available at http://docs.grafana.org/reference/annotations/. In order to allow AWX to add annotations, an API Key needs to be created in Grafana. Note that the created annotations are region events with start and endtime of the associated AWX Job. The annotation description is also provided by the subject of the associated AWX Job, for example:
|
The Grafana notification type allows you to create Grafana annotations. Details about this feature of Grafana are available at http://docs.grafana.org/reference/annotations/. In order to allow AWX to add annotations, an API Key needs to be created in Grafana. Note that the created annotations are region events with start and endtime of the associated AWX Job. The annotation description is also provided by the subject of the associated AWX Job, for example:
|
||||||
|
|
||||||
```
|
```
|
||||||
Job #1 'Ping Macbook' succeeded: https://towerhost/#/jobs/playbook/1
|
Job #1 'Ping Macbook' succeeded: https://platformhost/#/jobs/playbook/1
|
||||||
```
|
```
|
||||||
|
|
||||||
The configurable options of the Grafana notification type are:
|
The configurable options of the Grafana notification type are:
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ X-API-Query-Time: 0.004s
|
|||||||
X-API-Time: 0.026s
|
X-API-Time: 0.026s
|
||||||
|
|
||||||
{
|
{
|
||||||
"SOCIAL_AUTH_GITHUB_TEAM_CALLBACK_URL": "https://towerhost/sso/complete/github-team/",
|
"SOCIAL_AUTH_GITHUB_TEAM_CALLBACK_URL": "https://platformhost/sso/complete/github-team/",
|
||||||
"SOCIAL_AUTH_GITHUB_TEAM_KEY": "",
|
"SOCIAL_AUTH_GITHUB_TEAM_KEY": "",
|
||||||
"SOCIAL_AUTH_GITHUB_TEAM_SECRET": "",
|
"SOCIAL_AUTH_GITHUB_TEAM_SECRET": "",
|
||||||
"SOCIAL_AUTH_GITHUB_TEAM_ID": "",
|
"SOCIAL_AUTH_GITHUB_TEAM_ID": "",
|
||||||
|
|||||||
77
requirements/django-ansible-base-pinned-version.sh
Executable file
77
requirements/django-ansible-base-pinned-version.sh
Executable file
@@ -0,0 +1,77 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
set +x
|
||||||
|
|
||||||
|
# CONSTANTS
|
||||||
|
export REGEX_LEFT='https://github.com/ansible/django-ansible-base@'
|
||||||
|
export REGEX_RIGHT='#egg=django-ansible-base'
|
||||||
|
|
||||||
|
# GLOBALS
|
||||||
|
SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
|
||||||
|
REQ_FILE=$SCRIPT_DIR/requirements_git.txt
|
||||||
|
|
||||||
|
# Pin Function
|
||||||
|
DESIRED_VERSION=''
|
||||||
|
Pin()
|
||||||
|
{
|
||||||
|
export DESIRED_VERSION
|
||||||
|
perl -p -i -e 's/\Q$ENV{REGEX_LEFT}\E(.*?)\Q$ENV{REGEX_RIGHT}\E/$ENV{REGEX_LEFT}$ENV{DESIRED_VERSION}$ENV{REGEX_RIGHT}/g' $REQ_FILE
|
||||||
|
}
|
||||||
|
|
||||||
|
# Current Function
|
||||||
|
Current()
|
||||||
|
{
|
||||||
|
REQUIREMENTS_LINE=$(grep django-ansible-base $REQ_FILE)
|
||||||
|
|
||||||
|
echo "$REQUIREMENTS_LINE" | perl -nE 'say $1 if /\Q$ENV{REGEX_LEFT}\E(.*?)\Q$ENV{REGEX_RIGHT}\E/'
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Help()
|
||||||
|
{
|
||||||
|
# Display Help
|
||||||
|
echo ""
|
||||||
|
echo "Help:"
|
||||||
|
echo ""
|
||||||
|
echo "Interact with django-ansible-base in $REQ_FILE."
|
||||||
|
echo "By default, output the current django-ansible-base pinned version."
|
||||||
|
echo
|
||||||
|
echo "Syntax: scriptTemplate [-s|h|v]"
|
||||||
|
echo "options:"
|
||||||
|
echo "s Set django-ansible-base version to pin to."
|
||||||
|
echo "h Print this Help."
|
||||||
|
echo "v Verbose mode."
|
||||||
|
echo
|
||||||
|
}
|
||||||
|
|
||||||
|
if [ $# -eq 0 ]; then
|
||||||
|
Current
|
||||||
|
exit
|
||||||
|
fi
|
||||||
|
|
||||||
|
|
||||||
|
while getopts ":hs:" option; do
|
||||||
|
case $option in
|
||||||
|
h) # display Help
|
||||||
|
Help
|
||||||
|
exit
|
||||||
|
;;
|
||||||
|
s)
|
||||||
|
DESIRED_VERSION=$OPTARG;;
|
||||||
|
:)
|
||||||
|
echo "Option -${OPTARG} requires an argument."
|
||||||
|
Help
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
\?) # Invalid option
|
||||||
|
echo "Error: Invalid option"
|
||||||
|
echo ""
|
||||||
|
Help
|
||||||
|
exit;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
if [ -n "$DESIRED_VERSION" ]; then
|
||||||
|
Pin
|
||||||
|
Current
|
||||||
|
fi
|
||||||
|
|
||||||
@@ -2,4 +2,4 @@ git+https://github.com/ansible/system-certifi.git@devel#egg=certifi
|
|||||||
# Remove pbr from requirements.in when moving ansible-runner to requirements.in
|
# Remove pbr from requirements.in when moving ansible-runner to requirements.in
|
||||||
git+https://github.com/ansible/ansible-runner.git@devel#egg=ansible-runner
|
git+https://github.com/ansible/ansible-runner.git@devel#egg=ansible-runner
|
||||||
git+https://github.com/ansible/python3-saml.git@devel#egg=python3-saml
|
git+https://github.com/ansible/python3-saml.git@devel#egg=python3-saml
|
||||||
django-ansible-base @ git+https://github.com/ansible/django-ansible-base@devel#egg=django-ansible-base[rest_filters,jwt_consumer,resource_registry,rbac]
|
django-ansible-base @ git+https://github.com/ansible/django-ansible-base@2024.7.17#egg=django-ansible-base[rest_filters,jwt_consumer,resource_registry,rbac]
|
||||||
|
|||||||
@@ -4,8 +4,20 @@
|
|||||||
### DO NOT EDIT
|
### DO NOT EDIT
|
||||||
###
|
###
|
||||||
|
|
||||||
|
{% if not headless|bool %}
|
||||||
|
# UI_next build contaienr
|
||||||
|
FROM quay.io/centos/centos:stream9 AS ui-next-builder
|
||||||
|
USER root
|
||||||
|
RUN dnf -y update && dnf install -y nodejs make git
|
||||||
|
RUN npm install -g n && n 18
|
||||||
|
|
||||||
|
COPY . /tmp/src/
|
||||||
|
WORKDIR /tmp/src/
|
||||||
|
RUN make ui-next
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
# Build container
|
# Build container
|
||||||
FROM quay.io/centos/centos:stream9 as builder
|
FROM quay.io/centos/centos:stream9 AS builder
|
||||||
|
|
||||||
ENV LANG en_US.UTF-8
|
ENV LANG en_US.UTF-8
|
||||||
ENV LANGUAGE en_US:en
|
ENV LANGUAGE en_US:en
|
||||||
@@ -31,9 +43,6 @@ RUN dnf -y update && dnf install -y 'dnf-command(config-manager)' && \
|
|||||||
libffi-devel \
|
libffi-devel \
|
||||||
libtool-ltdl-devel \
|
libtool-ltdl-devel \
|
||||||
make \
|
make \
|
||||||
{% if not headless|bool %}
|
|
||||||
nodejs \
|
|
||||||
{% endif %}
|
|
||||||
nss \
|
nss \
|
||||||
openldap-devel \
|
openldap-devel \
|
||||||
# pin to older openssl, see jira AAP-23449
|
# pin to older openssl, see jira AAP-23449
|
||||||
@@ -74,21 +83,20 @@ RUN cd /tmp && make requirements_awx
|
|||||||
|
|
||||||
ARG VERSION
|
ARG VERSION
|
||||||
ARG SETUPTOOLS_SCM_PRETEND_VERSION
|
ARG SETUPTOOLS_SCM_PRETEND_VERSION
|
||||||
ARG HEADLESS
|
|
||||||
|
|
||||||
{% if (build_dev|bool) or (kube_dev|bool) %}
|
{% if (build_dev|bool) or (kube_dev|bool) %}
|
||||||
ADD requirements/requirements_dev.txt /tmp/requirements
|
ADD requirements/requirements_dev.txt /tmp/requirements
|
||||||
RUN cd /tmp && make requirements_awx_dev
|
RUN cd /tmp && make requirements_awx_dev
|
||||||
{% else %}
|
{% else %}
|
||||||
# Use the distro provided npm to bootstrap our required version of node
|
|
||||||
|
|
||||||
{% if not headless|bool %}
|
|
||||||
RUN npm install -g n && n 16.13.1
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
# Copy source into builder, build sdist, install it into awx venv
|
# Copy source into builder, build sdist, install it into awx venv
|
||||||
COPY . /tmp/src/
|
COPY . /tmp/src/
|
||||||
WORKDIR /tmp/src/
|
WORKDIR /tmp/src/
|
||||||
|
|
||||||
|
{% if not headless|bool %}
|
||||||
|
COPY --from=ui-next-builder /tmp/src/awx/ui_next/build /tmp/src/awx/ui_next/build
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
RUN make sdist && /var/lib/awx/venv/awx/bin/pip install dist/awx.tar.gz
|
RUN make sdist && /var/lib/awx/venv/awx/bin/pip install dist/awx.tar.gz
|
||||||
|
|
||||||
{% if not headless|bool %}
|
{% if not headless|bool %}
|
||||||
@@ -189,7 +197,7 @@ COPY --from=builder /var/lib/awx /var/lib/awx
|
|||||||
|
|
||||||
RUN ln -s /var/lib/awx/venv/awx/bin/awx-manage /usr/bin/awx-manage
|
RUN ln -s /var/lib/awx/venv/awx/bin/awx-manage /usr/bin/awx-manage
|
||||||
|
|
||||||
{%if build_dev|bool %}
|
{% if build_dev|bool %}
|
||||||
COPY --from={{ receptor_image }} /usr/bin/receptor /usr/bin/receptor
|
COPY --from={{ receptor_image }} /usr/bin/receptor /usr/bin/receptor
|
||||||
|
|
||||||
RUN openssl req -nodes -newkey rsa:2048 -keyout /etc/nginx/nginx.key -out /etc/nginx/nginx.csr \
|
RUN openssl req -nodes -newkey rsa:2048 -keyout /etc/nginx/nginx.key -out /etc/nginx/nginx.csr \
|
||||||
|
|||||||
@@ -208,13 +208,25 @@ awx_1 | Applying auth.0001_initial... OK
|
|||||||
|
|
||||||
##### Clean and build the UI
|
##### Clean and build the UI
|
||||||
|
|
||||||
|
Prerequisites (on your local machine)
|
||||||
|
- npm
|
||||||
|
- nodejs
|
||||||
|
|
||||||
|
Required versions listed here https://github.com/ansible/ansible-ui/blob/main/README.md
|
||||||
|
|
||||||
|
On your local machine (not in awx container)
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
$ docker exec tools_awx_1 make clean-ui ui-devel
|
make clean/ui-next ui-next
|
||||||
```
|
```
|
||||||
|
|
||||||
See [the ui development documentation](../../awx/ui/README.md) for more information on using the frontend development, build, and test tooling.
|
This will clone the ansible-ui into the `awx/ui_next/src` directory and build the static files. Then when the containers come up, awx-manage collectstatic will copy those files into the proper place.
|
||||||
|
|
||||||
Once migrations are completed and the UI is built, you can begin using AWX. The UI can be reached in your browser at `https://localhost:8043/#/home`, and the API can be found at `https://localhost:8043/api/v2`.
|
You can also use `UI_NEXT_LOCAL` to build from a locally cloned ansible-ui repo.
|
||||||
|
|
||||||
|
See [the ui development documentation](https://github.com/ansible/ansible-ui/blob/main/CONTRIBUTING.md) for more information on using the frontend development, build, and test tooling.
|
||||||
|
|
||||||
|
Once migrations are completed and the UI is built, you can begin using AWX. The UI can be reached in your browser at `https://localhost:8043/`, and the API can be found at `https://localhost:8043/api/v2`.
|
||||||
|
|
||||||
##### Create an admin user
|
##### Create an admin user
|
||||||
|
|
||||||
@@ -561,11 +573,11 @@ If you have a playbook like:
|
|||||||
var: the_secret_from_vault
|
var: the_secret_from_vault
|
||||||
```
|
```
|
||||||
|
|
||||||
And run it through AWX with the credential `Credential From Vault via Token Auth` tied to it, the debug should result in `this_is_the_secret_value`. If you run it through AWX with the credential `Credential From Vault via Userpass Auth`, the debug should result in `this_is_the_userpass_secret_value`.
|
And run it through AWX with the credential `Credential From Vault via Token Auth` tied to it, the debug should result in `this_is_the_secret_value`. If you run it through AWX with the credential `Credential From Vault via Userpass Auth`, the debug should result in `this_is_the_userpass_secret_value`.
|
||||||
|
|
||||||
### HashiVault with LDAP
|
### HashiVault with LDAP
|
||||||
|
|
||||||
If you wish to have your OpenLDAP container connected to the Vault container, you will first need to have the OpenLDAP container running alongside AWX and Vault.
|
If you wish to have your OpenLDAP container connected to the Vault container, you will first need to have the OpenLDAP container running alongside AWX and Vault.
|
||||||
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
@@ -574,7 +586,7 @@ VAULT=true LDAP=true make docker-compose
|
|||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Similar to the above, you will need to unseal the vault before we can run the other needed playbooks.
|
Similar to the above, you will need to unseal the vault before we can run the other needed playbooks.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
|
||||||
@@ -582,7 +594,7 @@ ansible-playbook tools/docker-compose/ansible/unseal_vault.yml
|
|||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Now that the vault is unsealed, we can plumb the vault container now while passing true to enable_ldap extra var.
|
Now that the vault is unsealed, we can plumb the vault container now while passing true to enable_ldap extra var.
|
||||||
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
@@ -595,7 +607,7 @@ ansible-playbook tools/docker-compose/ansible/plumb_vault.yml -e enable_ldap=tru
|
|||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
This will populate your AWX instance with LDAP specific items.
|
This will populate your AWX instance with LDAP specific items.
|
||||||
|
|
||||||
- A vault LDAP Lookup Cred tied to the LDAP `awx_ldap_vault` user called `Vault LDAP Lookup Cred`
|
- A vault LDAP Lookup Cred tied to the LDAP `awx_ldap_vault` user called `Vault LDAP Lookup Cred`
|
||||||
- A credential called `Credential From HashiCorp Vault via LDAP Auth` which is of the created type using the `Vault LDAP Lookup Cred` to get the secret.
|
- A credential called `Credential From HashiCorp Vault via LDAP Auth` which is of the created type using the `Vault LDAP Lookup Cred` to get the secret.
|
||||||
|
|||||||
@@ -47,9 +47,6 @@ services:
|
|||||||
{% if minikube_container_group|bool %}
|
{% if minikube_container_group|bool %}
|
||||||
MINIKUBE_CONTAINER_GROUP: "true"
|
MINIKUBE_CONTAINER_GROUP: "true"
|
||||||
{% endif %}
|
{% endif %}
|
||||||
links:
|
|
||||||
- postgres
|
|
||||||
- redis_{{ container_postfix }}
|
|
||||||
networks:
|
networks:
|
||||||
- awx
|
- awx
|
||||||
- service-mesh
|
- service-mesh
|
||||||
@@ -80,8 +77,12 @@ services:
|
|||||||
- "~/.kube/config:/var/lib/awx/.kube/config"
|
- "~/.kube/config:/var/lib/awx/.kube/config"
|
||||||
- "redis_socket_{{ container_postfix }}:/var/run/redis/:rw"
|
- "redis_socket_{{ container_postfix }}:/var/run/redis/:rw"
|
||||||
privileged: true
|
privileged: true
|
||||||
{% if editable_dependencies | length > 0 %}
|
|
||||||
depends_on:
|
depends_on:
|
||||||
|
postgres:
|
||||||
|
condition: service_started
|
||||||
|
redis_{{ container_postfix }}:
|
||||||
|
condition: service_started
|
||||||
|
{% if editable_dependencies | length > 0 %}
|
||||||
init_awx:
|
init_awx:
|
||||||
condition: service_completed_successfully
|
condition: service_completed_successfully
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@@ -197,10 +198,6 @@ services:
|
|||||||
volumes:
|
volumes:
|
||||||
- "../../docker-compose/_sources/prometheus.yml:/etc/prometheus/prometheus.yml"
|
- "../../docker-compose/_sources/prometheus.yml:/etc/prometheus/prometheus.yml"
|
||||||
- "prometheus_storage:/prometheus:rw"
|
- "prometheus_storage:/prometheus:rw"
|
||||||
links:
|
|
||||||
{% for i in range(control_plane_node_count|int) %}
|
|
||||||
- awx_{{ loop.index }}:awx{{ loop.index }} # because underscores are not valid in hostnames
|
|
||||||
{% endfor %}
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if enable_grafana|bool %}
|
{% if enable_grafana|bool %}
|
||||||
grafana:
|
grafana:
|
||||||
@@ -214,8 +211,6 @@ services:
|
|||||||
volumes:
|
volumes:
|
||||||
- "../../grafana:/etc/grafana/provisioning"
|
- "../../grafana:/etc/grafana/provisioning"
|
||||||
- "grafana_storage:/var/lib/grafana:rw"
|
- "grafana_storage:/var/lib/grafana:rw"
|
||||||
links:
|
|
||||||
- prometheus
|
|
||||||
depends_on:
|
depends_on:
|
||||||
- prometheus
|
- prometheus
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@@ -318,8 +313,6 @@ services:
|
|||||||
container_name: tools_receptor_hop
|
container_name: tools_receptor_hop
|
||||||
hostname: receptor-hop
|
hostname: receptor-hop
|
||||||
command: 'receptor --config /etc/receptor/receptor.conf'
|
command: 'receptor --config /etc/receptor/receptor.conf'
|
||||||
links:
|
|
||||||
- awx_1
|
|
||||||
networks:
|
networks:
|
||||||
- awx
|
- awx
|
||||||
ports:
|
ports:
|
||||||
@@ -335,8 +328,6 @@ services:
|
|||||||
command: 'receptor --config /etc/receptor/receptor.conf'
|
command: 'receptor --config /etc/receptor/receptor.conf'
|
||||||
environment:
|
environment:
|
||||||
RECEPTORCTL_SOCKET: {{ receptor_socket_file }}
|
RECEPTORCTL_SOCKET: {{ receptor_socket_file }}
|
||||||
links:
|
|
||||||
- receptor-hop
|
|
||||||
networks:
|
networks:
|
||||||
- awx
|
- awx
|
||||||
volumes:
|
volumes:
|
||||||
|
|||||||
@@ -23,8 +23,6 @@ else
|
|||||||
wait-for-migrations
|
wait-for-migrations
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Make sure that the UI static file directory exists, Django complains otherwise.
|
|
||||||
mkdir -p /awx_devel/awx/ui/build/static
|
|
||||||
|
|
||||||
# Make sure that the UI_NEXT statifc file directory exists, if UI_NEXT is not built yet put a placeholder file in it.
|
# Make sure that the UI_NEXT statifc file directory exists, if UI_NEXT is not built yet put a placeholder file in it.
|
||||||
if [ ! -d "/awx_devel/awx/ui_next/build/awx" ]; then
|
if [ ! -d "/awx_devel/awx/ui_next/build/awx" ]; then
|
||||||
|
|||||||
@@ -30,13 +30,13 @@ Request server configuration from Ansible Tower
|
|||||||
|
|
||||||
Usage:
|
Usage:
|
||||||
Execution using positional parameters:
|
Execution using positional parameters:
|
||||||
$($MyInvocation.MyCommand.Name) https://example.towerhost.net 44d7507f2ead49af5fca80aa18fd24bc 38
|
$($MyInvocation.MyCommand.Name) https://example.platformhost.net 44d7507f2ead49af5fca80aa18fd24bc 38
|
||||||
|
|
||||||
Ignore self-signed certificates using named parameters:
|
Ignore self-signed certificates using named parameters:
|
||||||
$($MyInvocation.MyCommand.Name) -k -s https://example.towerhost.local -c 44d7507f2ead49af5fca80aa18fd24bc -t 38
|
$($MyInvocation.MyCommand.Name) -k -s https://example.platformhost.local -c 44d7507f2ead49af5fca80aa18fd24bc -t 38
|
||||||
|
|
||||||
Execution using optional extra_vars:
|
Execution using optional extra_vars:
|
||||||
$($MyInvocation.MyCommand.Name) https://example.towerhost.net 44d7507f2ead49af5fca80aa18fd24bc 38 '{ key: value, dict: { key: value }}'
|
$($MyInvocation.MyCommand.Name) https://example.platformhost.net 44d7507f2ead49af5fca80aa18fd24bc 38 '{ key: value, dict: { key: value }}'
|
||||||
|
|
||||||
Options:
|
Options:
|
||||||
-help, -h Show this message
|
-help, -h Show this message
|
||||||
|
|||||||
Reference in New Issue
Block a user