mirror of
https://github.com/ansible/awx.git
synced 2026-02-08 13:04:43 -03:30
Compare commits
15 Commits
feature_us
...
feature-dj
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
af59abbbc4 | ||
|
|
8ab3514428 | ||
|
|
98781a82c7 | ||
|
|
d3fabe81d1 | ||
|
|
b274d0e5ef | ||
|
|
4494412f0c | ||
|
|
b82bec7d04 | ||
|
|
2cee1caad2 | ||
|
|
c3045b1169 | ||
|
|
27024378bc | ||
|
|
8eff90d4c0 | ||
|
|
9b633b6492 | ||
|
|
11dbc56ecb | ||
|
|
4c1bd1e88e | ||
|
|
865cb7518e |
2
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
2
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
@@ -19,8 +19,6 @@ body:
|
||||
required: true
|
||||
- label: I understand that AWX is open source software provided for free and that I might not receive a timely response.
|
||||
required: true
|
||||
- label: I am **NOT** reporting a (potential) security vulnerability. (These should be emailed to `security@ansible.com` instead.)
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: summary
|
||||
|
||||
2
.github/workflows/ci.yml
vendored
2
.github/workflows/ci.yml
vendored
@@ -3,7 +3,7 @@ name: CI
|
||||
env:
|
||||
LC_ALL: "C.UTF-8" # prevent ERROR: Ansible could not initialize the preferred locale: unsupported locale setting
|
||||
CI_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
DEV_DOCKER_OWNER: ${{ github.repository_owner }}
|
||||
DEV_DOCKER_TAG_BASE: ghcr.io/${{ github.repository_owner }}
|
||||
COMPOSE_TAG: ${{ github.base_ref || 'devel' }}
|
||||
on:
|
||||
pull_request:
|
||||
|
||||
5
Makefile
5
Makefile
@@ -42,10 +42,7 @@ TACACS ?= false
|
||||
|
||||
VENV_BASE ?= /var/lib/awx/venv
|
||||
|
||||
DEV_DOCKER_OWNER ?= ansible
|
||||
# Docker will only accept lowercase, so github names like Paul need to be paul
|
||||
DEV_DOCKER_OWNER_LOWER = $(shell echo $(DEV_DOCKER_OWNER) | tr A-Z a-z)
|
||||
DEV_DOCKER_TAG_BASE ?= ghcr.io/$(DEV_DOCKER_OWNER_LOWER)
|
||||
DEV_DOCKER_TAG_BASE ?= ghcr.io/ansible
|
||||
DEVEL_IMAGE_NAME ?= $(DEV_DOCKER_TAG_BASE)/awx_devel:$(COMPOSE_TAG)
|
||||
|
||||
RECEPTOR_IMAGE ?= quay.io/ansible/receptor:devel
|
||||
|
||||
@@ -522,16 +522,14 @@ class SubListAPIView(ParentMixin, ListAPIView):
|
||||
def get_queryset(self):
|
||||
parent = self.get_parent_object()
|
||||
self.check_parent_access(parent)
|
||||
sublist_qs = self.get_sublist_queryset(parent)
|
||||
if not self.filter_read_permission:
|
||||
return optimize_queryset(self.get_sublist_queryset(parent))
|
||||
qs = self.request.user.get_queryset(self.model)
|
||||
if hasattr(self, 'parent_key'):
|
||||
# This is vastly preferable for ReverseForeignKey relationships
|
||||
return qs.filter(**{self.parent_key: parent})
|
||||
return qs.distinct() & self.get_sublist_queryset(parent).distinct()
|
||||
return optimize_queryset(sublist_qs)
|
||||
qs = self.request.user.get_queryset(self.model).distinct()
|
||||
return qs & sublist_qs
|
||||
|
||||
def get_sublist_queryset(self, parent):
|
||||
return getattrd(parent, self.relationship)
|
||||
return getattrd(parent, self.relationship).distinct()
|
||||
|
||||
|
||||
class DestroyAPIView(generics.DestroyAPIView):
|
||||
@@ -580,6 +578,15 @@ class SubListCreateAPIView(SubListAPIView, ListCreateAPIView):
|
||||
d.update({'parent_key': getattr(self, 'parent_key', None)})
|
||||
return d
|
||||
|
||||
def get_queryset(self):
|
||||
if hasattr(self, 'parent_key'):
|
||||
# Prefer this filtering because ForeignKey allows us more assumptions
|
||||
parent = self.get_parent_object()
|
||||
self.check_parent_access(parent)
|
||||
qs = self.request.user.get_queryset(self.model)
|
||||
return qs.filter(**{self.parent_key: parent})
|
||||
return super(SubListCreateAPIView, self).get_queryset()
|
||||
|
||||
def create(self, request, *args, **kwargs):
|
||||
# If the object ID was not specified, it probably doesn't exist in the
|
||||
# DB yet. We want to see if we can create it. The URL may choose to
|
||||
|
||||
@@ -399,7 +399,10 @@ def _copy_table(table, query, path):
|
||||
file_path = os.path.join(path, table + '_table.csv')
|
||||
file = FileSplitter(filespec=file_path)
|
||||
with connection.cursor() as cursor:
|
||||
cursor.copy_expert(query, file)
|
||||
with cursor.copy(query) as copy:
|
||||
while data := copy.read():
|
||||
byte_data = bytes(data)
|
||||
file.write(byte_data.decode())
|
||||
return file.file_list()
|
||||
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ from django.conf import settings
|
||||
from django.utils.functional import cached_property
|
||||
from django.utils.timezone import now as tz_now
|
||||
from django.db import transaction, connection as django_connection
|
||||
from django.db.utils import DataError
|
||||
from django_guid import set_guid
|
||||
|
||||
import psutil
|
||||
@@ -191,10 +192,16 @@ class CallbackBrokerWorker(BaseWorker):
|
||||
e._retry_count = retry_count
|
||||
|
||||
# special sanitization logic for postgres treatment of NUL 0x00 char
|
||||
if (retry_count == 1) and isinstance(exc_indv, ValueError) and ("\x00" in e.stdout):
|
||||
e.stdout = e.stdout.replace("\x00", "")
|
||||
|
||||
if retry_count >= self.INDIVIDUAL_EVENT_RETRIES:
|
||||
if (retry_count == 1) and isinstance(exc_indv, DataError):
|
||||
# The easiest place is in stdout. This raises as an error stating that it can't save a NUL character
|
||||
if "\x00" in e.stdout:
|
||||
e.stdout = e.stdout.replace("\x00", "")
|
||||
# There is also a chance that NUL char is embedded in event data which is part of a JSON blob. In that case we, thankfully, get a different exception
|
||||
if 'unsupported Unicode escape sequence' in str(exc_indv):
|
||||
e.event_data = json.loads(
|
||||
json.dumps(e.event_data).replace("\x00", "").replace("\\x00", "").replace("\u0000", "").replace("\\u0000", "")
|
||||
)
|
||||
elif retry_count >= self.INDIVIDUAL_EVENT_RETRIES:
|
||||
logger.error(f'Hit max retries ({retry_count}) saving individual Event error: {str(exc_indv)}\ndata:\n{e.__dict__}')
|
||||
events.remove(e)
|
||||
else:
|
||||
|
||||
@@ -2,9 +2,6 @@
|
||||
# Python
|
||||
from __future__ import unicode_literals
|
||||
|
||||
# Psycopg2
|
||||
from psycopg2.extensions import AsIs
|
||||
|
||||
# Django
|
||||
from django.db import connection, migrations, models, OperationalError, ProgrammingError
|
||||
from django.conf import settings
|
||||
@@ -136,8 +133,8 @@ class Migration(migrations.Migration):
|
||||
),
|
||||
),
|
||||
migrations.RunSQL(
|
||||
[("CREATE INDEX host_ansible_facts_default_gin ON %s USING gin" "(ansible_facts jsonb_path_ops);", [AsIs(Host._meta.db_table)])],
|
||||
[('DROP INDEX host_ansible_facts_default_gin;', None)],
|
||||
sql="CREATE INDEX host_ansible_facts_default_gin ON {} USING gin(ansible_facts jsonb_path_ops);".format(Host._meta.db_table),
|
||||
reverse_sql='DROP INDEX host_ansible_facts_default_gin;',
|
||||
),
|
||||
# SCM file-based inventories
|
||||
migrations.AddField(
|
||||
|
||||
@@ -22,10 +22,8 @@ def migrate_event_data(apps, schema_editor):
|
||||
# recreate counter for the new table's primary key to
|
||||
# start where the *old* table left off (we have to do this because the
|
||||
# counter changed from an int to a bigint)
|
||||
cursor.execute(f'DROP SEQUENCE IF EXISTS "{tblname}_id_seq" CASCADE;')
|
||||
cursor.execute(f'CREATE SEQUENCE "{tblname}_id_seq";')
|
||||
cursor.execute(f'ALTER TABLE "{tblname}" ALTER COLUMN "id" ' f"SET DEFAULT nextval('{tblname}_id_seq');")
|
||||
cursor.execute(f"SELECT setval('{tblname}_id_seq', (SELECT MAX(id) FROM _old_{tblname}), true);")
|
||||
cursor.execute(f'CREATE SEQUENCE IF NOT EXISTS "{tblname}_id_seq";')
|
||||
cursor.execute(f"SELECT setval('{tblname}_id_seq', COALESCE((SELECT MAX(id)+1 FROM _old_{tblname}), 1), false);")
|
||||
cursor.execute(f'DROP TABLE _old_{tblname};')
|
||||
|
||||
|
||||
|
||||
30
awx/main/migrations/0183_pre_django_upgrade.py
Normal file
30
awx/main/migrations/0183_pre_django_upgrade.py
Normal file
@@ -0,0 +1,30 @@
|
||||
# Generated by Django 3.2.16 on 2023-04-21 14:15
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
migrations.swappable_dependency(settings.OAUTH2_PROVIDER_ID_TOKEN_MODEL),
|
||||
('main', '0182_constructed_inventory'),
|
||||
('oauth2_provider', '0005_auto_20211222_2352'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='oauth2accesstoken',
|
||||
name='id_token',
|
||||
field=models.OneToOneField(
|
||||
blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='access_token', to=settings.OAUTH2_PROVIDER_ID_TOKEN_MODEL
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='oauth2application',
|
||||
name='algorithm',
|
||||
field=models.CharField(
|
||||
blank=True, choices=[('', 'No OIDC support'), ('RS256', 'RSA with SHA-2 256'), ('HS256', 'HMAC with SHA-2 256')], default='', max_length=5
|
||||
),
|
||||
),
|
||||
]
|
||||
972
awx/main/migrations/0184_django_upgrade.py
Normal file
972
awx/main/migrations/0184_django_upgrade.py
Normal file
@@ -0,0 +1,972 @@
|
||||
# Generated by Django 4.2 on 2023-04-21 14:43
|
||||
|
||||
import awx.main.fields
|
||||
import awx.main.utils.polymorphic
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
('contenttypes', '0002_remove_content_type_name'),
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
('main', '0183_pre_django_upgrade'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='activitystream',
|
||||
name='unified_job',
|
||||
field=models.ManyToManyField(blank=True, related_name='activity_stream_as_unified_job+', to='main.unifiedjob'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='activitystream',
|
||||
name='unified_job_template',
|
||||
field=models.ManyToManyField(blank=True, related_name='activity_stream_as_unified_job_template+', to='main.unifiedjobtemplate'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='credential',
|
||||
name='created_by',
|
||||
field=models.ForeignKey(
|
||||
default=None,
|
||||
editable=False,
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.SET_NULL,
|
||||
related_name='%s(class)s_created+',
|
||||
to=settings.AUTH_USER_MODEL,
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='credential',
|
||||
name='modified_by',
|
||||
field=models.ForeignKey(
|
||||
default=None,
|
||||
editable=False,
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.SET_NULL,
|
||||
related_name='%s(class)s_modified+',
|
||||
to=settings.AUTH_USER_MODEL,
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='credentialinputsource',
|
||||
name='created_by',
|
||||
field=models.ForeignKey(
|
||||
default=None,
|
||||
editable=False,
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.SET_NULL,
|
||||
related_name='%s(class)s_created+',
|
||||
to=settings.AUTH_USER_MODEL,
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='credentialinputsource',
|
||||
name='modified_by',
|
||||
field=models.ForeignKey(
|
||||
default=None,
|
||||
editable=False,
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.SET_NULL,
|
||||
related_name='%s(class)s_modified+',
|
||||
to=settings.AUTH_USER_MODEL,
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='credentialtype',
|
||||
name='created_by',
|
||||
field=models.ForeignKey(
|
||||
default=None,
|
||||
editable=False,
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.SET_NULL,
|
||||
related_name='%s(class)s_created+',
|
||||
to=settings.AUTH_USER_MODEL,
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='credentialtype',
|
||||
name='modified_by',
|
||||
field=models.ForeignKey(
|
||||
default=None,
|
||||
editable=False,
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.SET_NULL,
|
||||
related_name='%s(class)s_modified+',
|
||||
to=settings.AUTH_USER_MODEL,
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='custominventoryscript',
|
||||
name='created_by',
|
||||
field=models.ForeignKey(
|
||||
default=None,
|
||||
editable=False,
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.SET_NULL,
|
||||
related_name='%s(class)s_created+',
|
||||
to=settings.AUTH_USER_MODEL,
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='custominventoryscript',
|
||||
name='modified_by',
|
||||
field=models.ForeignKey(
|
||||
default=None,
|
||||
editable=False,
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.SET_NULL,
|
||||
related_name='%s(class)s_modified+',
|
||||
to=settings.AUTH_USER_MODEL,
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='executionenvironment',
|
||||
name='created_by',
|
||||
field=models.ForeignKey(
|
||||
default=None,
|
||||
editable=False,
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.SET_NULL,
|
||||
related_name='%s(class)s_created+',
|
||||
to=settings.AUTH_USER_MODEL,
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='executionenvironment',
|
||||
name='credential',
|
||||
field=models.ForeignKey(
|
||||
blank=True, default=None, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='%(class)ss', to='main.credential'
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='executionenvironment',
|
||||
name='modified_by',
|
||||
field=models.ForeignKey(
|
||||
default=None,
|
||||
editable=False,
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.SET_NULL,
|
||||
related_name='%s(class)s_modified+',
|
||||
to=settings.AUTH_USER_MODEL,
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='executionenvironment',
|
||||
name='organization',
|
||||
field=models.ForeignKey(
|
||||
blank=True,
|
||||
default=None,
|
||||
help_text='The organization used to determine access to this execution environment.',
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
related_name='%(class)ss',
|
||||
to='main.organization',
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='group',
|
||||
name='created_by',
|
||||
field=models.ForeignKey(
|
||||
default=None,
|
||||
editable=False,
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.SET_NULL,
|
||||
related_name='%s(class)s_created+',
|
||||
to=settings.AUTH_USER_MODEL,
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='group',
|
||||
name='modified_by',
|
||||
field=models.ForeignKey(
|
||||
default=None,
|
||||
editable=False,
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.SET_NULL,
|
||||
related_name='%s(class)s_modified+',
|
||||
to=settings.AUTH_USER_MODEL,
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='host',
|
||||
name='created_by',
|
||||
field=models.ForeignKey(
|
||||
default=None,
|
||||
editable=False,
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.SET_NULL,
|
||||
related_name='%s(class)s_created+',
|
||||
to=settings.AUTH_USER_MODEL,
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='host',
|
||||
name='modified_by',
|
||||
field=models.ForeignKey(
|
||||
default=None,
|
||||
editable=False,
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.SET_NULL,
|
||||
related_name='%s(class)s_modified+',
|
||||
to=settings.AUTH_USER_MODEL,
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='host',
|
||||
name='smart_inventories',
|
||||
field=models.ManyToManyField(related_name='+', through='main.SmartInventoryMembership', to='main.inventory'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='instancegroup',
|
||||
name='credential',
|
||||
field=models.ForeignKey(
|
||||
blank=True, default=None, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='%(class)ss', to='main.credential'
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='inventory',
|
||||
name='created_by',
|
||||
field=models.ForeignKey(
|
||||
default=None,
|
||||
editable=False,
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.SET_NULL,
|
||||
related_name='%s(class)s_created+',
|
||||
to=settings.AUTH_USER_MODEL,
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='inventory',
|
||||
name='modified_by',
|
||||
field=models.ForeignKey(
|
||||
default=None,
|
||||
editable=False,
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.SET_NULL,
|
||||
related_name='%s(class)s_modified+',
|
||||
to=settings.AUTH_USER_MODEL,
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='job',
|
||||
name='inventory',
|
||||
field=models.ForeignKey(
|
||||
blank=True, default=None, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='%(class)ss', to='main.inventory'
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='job',
|
||||
name='project',
|
||||
field=models.ForeignKey(
|
||||
blank=True, default=None, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='%(class)ss', to='main.project'
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='job',
|
||||
name='webhook_credential',
|
||||
field=models.ForeignKey(
|
||||
blank=True,
|
||||
help_text='Personal Access Token for posting back the status to the service API',
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.SET_NULL,
|
||||
related_name='%(class)ss',
|
||||
to='main.credential',
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='joblaunchconfig',
|
||||
name='credentials',
|
||||
field=models.ManyToManyField(related_name='%(class)ss', to='main.credential'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='joblaunchconfig',
|
||||
name='execution_environment',
|
||||
field=models.ForeignKey(
|
||||
blank=True,
|
||||
default=None,
|
||||
help_text='The container image to be used for execution.',
|
||||
null=True,
|
||||
on_delete=awx.main.utils.polymorphic.SET_NULL,
|
||||
related_name='%(class)s_as_prompt',
|
||||
to='main.executionenvironment',
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='joblaunchconfig',
|
||||
name='instance_groups',
|
||||
field=awx.main.fields.OrderedManyToManyField(
|
||||
blank=True, editable=False, related_name='%(class)ss', through='main.JobLaunchConfigInstanceGroupMembership', to='main.instancegroup'
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='joblaunchconfig',
|
||||
name='inventory',
|
||||
field=models.ForeignKey(
|
||||
blank=True,
|
||||
default=None,
|
||||
help_text='Inventory applied as a prompt, assuming job template prompts for inventory',
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.SET_NULL,
|
||||
related_name='%(class)ss',
|
||||
to='main.inventory',
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='joblaunchconfig',
|
||||
name='labels',
|
||||
field=models.ManyToManyField(related_name='%(class)s_labels', to='main.label'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='jobtemplate',
|
||||
name='inventory',
|
||||
field=models.ForeignKey(
|
||||
blank=True, default=None, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='%(class)ss', to='main.inventory'
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='jobtemplate',
|
||||
name='project',
|
||||
field=models.ForeignKey(
|
||||
blank=True, default=None, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='%(class)ss', to='main.project'
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='jobtemplate',
|
||||
name='webhook_credential',
|
||||
field=models.ForeignKey(
|
||||
blank=True,
|
||||
help_text='Personal Access Token for posting back the status to the service API',
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.SET_NULL,
|
||||
related_name='%(class)ss',
|
||||
to='main.credential',
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='label',
|
||||
name='created_by',
|
||||
field=models.ForeignKey(
|
||||
default=None,
|
||||
editable=False,
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.SET_NULL,
|
||||
related_name='%s(class)s_created+',
|
||||
to=settings.AUTH_USER_MODEL,
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='label',
|
||||
name='modified_by',
|
||||
field=models.ForeignKey(
|
||||
default=None,
|
||||
editable=False,
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.SET_NULL,
|
||||
related_name='%s(class)s_modified+',
|
||||
to=settings.AUTH_USER_MODEL,
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='notificationtemplate',
|
||||
name='created_by',
|
||||
field=models.ForeignKey(
|
||||
default=None,
|
||||
editable=False,
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.SET_NULL,
|
||||
related_name='%s(class)s_created+',
|
||||
to=settings.AUTH_USER_MODEL,
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='notificationtemplate',
|
||||
name='modified_by',
|
||||
field=models.ForeignKey(
|
||||
default=None,
|
||||
editable=False,
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.SET_NULL,
|
||||
related_name='%s(class)s_modified+',
|
||||
to=settings.AUTH_USER_MODEL,
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='oauth2accesstoken',
|
||||
name='user',
|
||||
field=models.ForeignKey(
|
||||
blank=True,
|
||||
help_text='The user representing the token owner',
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
related_name='%(app_label)s_%(class)s',
|
||||
to=settings.AUTH_USER_MODEL,
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='oauth2application',
|
||||
name='user',
|
||||
field=models.ForeignKey(
|
||||
blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='%(app_label)s_%(class)s', to=settings.AUTH_USER_MODEL
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='organization',
|
||||
name='created_by',
|
||||
field=models.ForeignKey(
|
||||
default=None,
|
||||
editable=False,
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.SET_NULL,
|
||||
related_name='%s(class)s_created+',
|
||||
to=settings.AUTH_USER_MODEL,
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='organization',
|
||||
name='galaxy_credentials',
|
||||
field=awx.main.fields.OrderedManyToManyField(
|
||||
blank=True, related_name='%(class)s_galaxy_credentials', through='main.OrganizationGalaxyCredentialMembership', to='main.credential'
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='organization',
|
||||
name='modified_by',
|
||||
field=models.ForeignKey(
|
||||
default=None,
|
||||
editable=False,
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.SET_NULL,
|
||||
related_name='%s(class)s_modified+',
|
||||
to=settings.AUTH_USER_MODEL,
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='organization',
|
||||
name='notification_templates_approvals',
|
||||
field=models.ManyToManyField(blank=True, related_name='%(class)s_notification_templates_for_approvals', to='main.notificationtemplate'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='organization',
|
||||
name='notification_templates_error',
|
||||
field=models.ManyToManyField(blank=True, related_name='%(class)s_notification_templates_for_errors', to='main.notificationtemplate'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='organization',
|
||||
name='notification_templates_started',
|
||||
field=models.ManyToManyField(blank=True, related_name='%(class)s_notification_templates_for_started', to='main.notificationtemplate'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='organization',
|
||||
name='notification_templates_success',
|
||||
field=models.ManyToManyField(blank=True, related_name='%(class)s_notification_templates_for_success', to='main.notificationtemplate'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='project',
|
||||
name='credential',
|
||||
field=models.ForeignKey(
|
||||
blank=True, default=None, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='%(class)ss', to='main.credential'
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='project',
|
||||
name='signature_validation_credential',
|
||||
field=models.ForeignKey(
|
||||
blank=True,
|
||||
default=None,
|
||||
help_text='An optional credential used for validating files in the project against unexpected changes.',
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.SET_NULL,
|
||||
related_name='%(class)ss_signature_validation',
|
||||
to='main.credential',
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='projectupdate',
|
||||
name='credential',
|
||||
field=models.ForeignKey(
|
||||
blank=True, default=None, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='%(class)ss', to='main.credential'
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='schedule',
|
||||
name='created_by',
|
||||
field=models.ForeignKey(
|
||||
default=None,
|
||||
editable=False,
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.SET_NULL,
|
||||
related_name='%s(class)s_created+',
|
||||
to=settings.AUTH_USER_MODEL,
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='schedule',
|
||||
name='credentials',
|
||||
field=models.ManyToManyField(related_name='%(class)ss', to='main.credential'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='schedule',
|
||||
name='execution_environment',
|
||||
field=models.ForeignKey(
|
||||
blank=True,
|
||||
default=None,
|
||||
help_text='The container image to be used for execution.',
|
||||
null=True,
|
||||
on_delete=awx.main.utils.polymorphic.SET_NULL,
|
||||
related_name='%(class)s_as_prompt',
|
||||
to='main.executionenvironment',
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='schedule',
|
||||
name='inventory',
|
||||
field=models.ForeignKey(
|
||||
blank=True,
|
||||
default=None,
|
||||
help_text='Inventory applied as a prompt, assuming job template prompts for inventory',
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.SET_NULL,
|
||||
related_name='%(class)ss',
|
||||
to='main.inventory',
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='schedule',
|
||||
name='labels',
|
||||
field=models.ManyToManyField(related_name='%(class)s_labels', to='main.label'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='schedule',
|
||||
name='modified_by',
|
||||
field=models.ForeignKey(
|
||||
default=None,
|
||||
editable=False,
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.SET_NULL,
|
||||
related_name='%s(class)s_modified+',
|
||||
to=settings.AUTH_USER_MODEL,
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='team',
|
||||
name='created_by',
|
||||
field=models.ForeignKey(
|
||||
default=None,
|
||||
editable=False,
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.SET_NULL,
|
||||
related_name='%s(class)s_created+',
|
||||
to=settings.AUTH_USER_MODEL,
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='team',
|
||||
name='modified_by',
|
||||
field=models.ForeignKey(
|
||||
default=None,
|
||||
editable=False,
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.SET_NULL,
|
||||
related_name='%s(class)s_modified+',
|
||||
to=settings.AUTH_USER_MODEL,
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='unifiedjob',
|
||||
name='created_by',
|
||||
field=models.ForeignKey(
|
||||
default=None,
|
||||
editable=False,
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.SET_NULL,
|
||||
related_name='%s(class)s_created+',
|
||||
to=settings.AUTH_USER_MODEL,
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='unifiedjob',
|
||||
name='credentials',
|
||||
field=models.ManyToManyField(related_name='%(class)ss', to='main.credential'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='unifiedjob',
|
||||
name='dependent_jobs',
|
||||
field=models.ManyToManyField(editable=False, related_name='%(class)s_blocked_jobs', to='main.unifiedjob'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='unifiedjob',
|
||||
name='execution_environment',
|
||||
field=models.ForeignKey(
|
||||
blank=True,
|
||||
default=None,
|
||||
help_text='The container image to be used for execution.',
|
||||
null=True,
|
||||
on_delete=awx.main.utils.polymorphic.SET_NULL,
|
||||
related_name='%(class)ss',
|
||||
to='main.executionenvironment',
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='unifiedjob',
|
||||
name='labels',
|
||||
field=models.ManyToManyField(blank=True, related_name='%(class)s_labels', to='main.label'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='unifiedjob',
|
||||
name='modified_by',
|
||||
field=models.ForeignKey(
|
||||
default=None,
|
||||
editable=False,
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.SET_NULL,
|
||||
related_name='%s(class)s_modified+',
|
||||
to=settings.AUTH_USER_MODEL,
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='unifiedjob',
|
||||
name='notifications',
|
||||
field=models.ManyToManyField(editable=False, related_name='%(class)s_notifications', to='main.notification'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='unifiedjob',
|
||||
name='organization',
|
||||
field=models.ForeignKey(
|
||||
blank=True,
|
||||
help_text='The organization used to determine access to this unified job.',
|
||||
null=True,
|
||||
on_delete=awx.main.utils.polymorphic.SET_NULL,
|
||||
related_name='%(class)ss',
|
||||
to='main.organization',
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='unifiedjob',
|
||||
name='polymorphic_ctype',
|
||||
field=models.ForeignKey(
|
||||
editable=False,
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
related_name='polymorphic_%(app_label)s.%(class)s_set+',
|
||||
to='contenttypes.contenttype',
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='unifiedjob',
|
||||
name='unified_job_template',
|
||||
field=models.ForeignKey(
|
||||
default=None,
|
||||
editable=False,
|
||||
null=True,
|
||||
on_delete=awx.main.utils.polymorphic.SET_NULL,
|
||||
related_name='%(class)s_unified_jobs',
|
||||
to='main.unifiedjobtemplate',
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='unifiedjobtemplate',
|
||||
name='created_by',
|
||||
field=models.ForeignKey(
|
||||
default=None,
|
||||
editable=False,
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.SET_NULL,
|
||||
related_name='%s(class)s_created+',
|
||||
to=settings.AUTH_USER_MODEL,
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='unifiedjobtemplate',
|
||||
name='credentials',
|
||||
field=models.ManyToManyField(related_name='%(class)ss', to='main.credential'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='unifiedjobtemplate',
|
||||
name='current_job',
|
||||
field=models.ForeignKey(
|
||||
default=None,
|
||||
editable=False,
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.SET_NULL,
|
||||
related_name='%(class)s_as_current_job+',
|
||||
to='main.unifiedjob',
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='unifiedjobtemplate',
|
||||
name='execution_environment',
|
||||
field=models.ForeignKey(
|
||||
blank=True,
|
||||
default=None,
|
||||
help_text='The container image to be used for execution.',
|
||||
null=True,
|
||||
on_delete=awx.main.utils.polymorphic.SET_NULL,
|
||||
related_name='%(class)ss',
|
||||
to='main.executionenvironment',
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='unifiedjobtemplate',
|
||||
name='labels',
|
||||
field=models.ManyToManyField(blank=True, related_name='%(class)s_labels', to='main.label'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='unifiedjobtemplate',
|
||||
name='last_job',
|
||||
field=models.ForeignKey(
|
||||
default=None,
|
||||
editable=False,
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.SET_NULL,
|
||||
related_name='%(class)s_as_last_job+',
|
||||
to='main.unifiedjob',
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='unifiedjobtemplate',
|
||||
name='modified_by',
|
||||
field=models.ForeignKey(
|
||||
default=None,
|
||||
editable=False,
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.SET_NULL,
|
||||
related_name='%s(class)s_modified+',
|
||||
to=settings.AUTH_USER_MODEL,
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='unifiedjobtemplate',
|
||||
name='next_schedule',
|
||||
field=models.ForeignKey(
|
||||
default=None,
|
||||
editable=False,
|
||||
null=True,
|
||||
on_delete=awx.main.utils.polymorphic.SET_NULL,
|
||||
related_name='%(class)s_as_next_schedule+',
|
||||
to='main.schedule',
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='unifiedjobtemplate',
|
||||
name='notification_templates_error',
|
||||
field=models.ManyToManyField(blank=True, related_name='%(class)s_notification_templates_for_errors', to='main.notificationtemplate'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='unifiedjobtemplate',
|
||||
name='notification_templates_started',
|
||||
field=models.ManyToManyField(blank=True, related_name='%(class)s_notification_templates_for_started', to='main.notificationtemplate'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='unifiedjobtemplate',
|
||||
name='notification_templates_success',
|
||||
field=models.ManyToManyField(blank=True, related_name='%(class)s_notification_templates_for_success', to='main.notificationtemplate'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='unifiedjobtemplate',
|
||||
name='organization',
|
||||
field=models.ForeignKey(
|
||||
blank=True,
|
||||
help_text='The organization used to determine access to this template.',
|
||||
null=True,
|
||||
on_delete=awx.main.utils.polymorphic.SET_NULL,
|
||||
related_name='%(class)ss',
|
||||
to='main.organization',
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='unifiedjobtemplate',
|
||||
name='polymorphic_ctype',
|
||||
field=models.ForeignKey(
|
||||
editable=False,
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
related_name='polymorphic_%(app_label)s.%(class)s_set+',
|
||||
to='contenttypes.contenttype',
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='workflowapproval',
|
||||
name='approved_or_denied_by',
|
||||
field=models.ForeignKey(
|
||||
default=None,
|
||||
editable=False,
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.SET_NULL,
|
||||
related_name='%s(class)s_approved+',
|
||||
to=settings.AUTH_USER_MODEL,
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='workflowjob',
|
||||
name='inventory',
|
||||
field=models.ForeignKey(
|
||||
blank=True,
|
||||
default=None,
|
||||
help_text='Inventory applied as a prompt, assuming job template prompts for inventory',
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.SET_NULL,
|
||||
related_name='%(class)ss',
|
||||
to='main.inventory',
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='workflowjob',
|
||||
name='webhook_credential',
|
||||
field=models.ForeignKey(
|
||||
blank=True,
|
||||
help_text='Personal Access Token for posting back the status to the service API',
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.SET_NULL,
|
||||
related_name='%(class)ss',
|
||||
to='main.credential',
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='workflowjobnode',
|
||||
name='always_nodes',
|
||||
field=models.ManyToManyField(blank=True, related_name='%(class)ss_always', to='main.workflowjobnode'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='workflowjobnode',
|
||||
name='credentials',
|
||||
field=models.ManyToManyField(related_name='%(class)ss', to='main.credential'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='workflowjobnode',
|
||||
name='execution_environment',
|
||||
field=models.ForeignKey(
|
||||
blank=True,
|
||||
default=None,
|
||||
help_text='The container image to be used for execution.',
|
||||
null=True,
|
||||
on_delete=awx.main.utils.polymorphic.SET_NULL,
|
||||
related_name='%(class)s_as_prompt',
|
||||
to='main.executionenvironment',
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='workflowjobnode',
|
||||
name='failure_nodes',
|
||||
field=models.ManyToManyField(blank=True, related_name='%(class)ss_failure', to='main.workflowjobnode'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='workflowjobnode',
|
||||
name='inventory',
|
||||
field=models.ForeignKey(
|
||||
blank=True,
|
||||
default=None,
|
||||
help_text='Inventory applied as a prompt, assuming job template prompts for inventory',
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.SET_NULL,
|
||||
related_name='%(class)ss',
|
||||
to='main.inventory',
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='workflowjobnode',
|
||||
name='labels',
|
||||
field=models.ManyToManyField(related_name='%(class)s_labels', to='main.label'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='workflowjobnode',
|
||||
name='success_nodes',
|
||||
field=models.ManyToManyField(blank=True, related_name='%(class)ss_success', to='main.workflowjobnode'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='workflowjobnode',
|
||||
name='unified_job_template',
|
||||
field=models.ForeignKey(
|
||||
blank=True, default=None, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='%(class)ss', to='main.unifiedjobtemplate'
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='workflowjobtemplate',
|
||||
name='inventory',
|
||||
field=models.ForeignKey(
|
||||
blank=True,
|
||||
default=None,
|
||||
help_text='Inventory applied as a prompt, assuming job template prompts for inventory',
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.SET_NULL,
|
||||
related_name='%(class)ss',
|
||||
to='main.inventory',
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='workflowjobtemplate',
|
||||
name='notification_templates_approvals',
|
||||
field=models.ManyToManyField(blank=True, related_name='%(class)s_notification_templates_for_approvals', to='main.notificationtemplate'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='workflowjobtemplate',
|
||||
name='webhook_credential',
|
||||
field=models.ForeignKey(
|
||||
blank=True,
|
||||
help_text='Personal Access Token for posting back the status to the service API',
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.SET_NULL,
|
||||
related_name='%(class)ss',
|
||||
to='main.credential',
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='workflowjobtemplatenode',
|
||||
name='always_nodes',
|
||||
field=models.ManyToManyField(blank=True, related_name='%(class)ss_always', to='main.workflowjobtemplatenode'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='workflowjobtemplatenode',
|
||||
name='credentials',
|
||||
field=models.ManyToManyField(related_name='%(class)ss', to='main.credential'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='workflowjobtemplatenode',
|
||||
name='execution_environment',
|
||||
field=models.ForeignKey(
|
||||
blank=True,
|
||||
default=None,
|
||||
help_text='The container image to be used for execution.',
|
||||
null=True,
|
||||
on_delete=awx.main.utils.polymorphic.SET_NULL,
|
||||
related_name='%(class)s_as_prompt',
|
||||
to='main.executionenvironment',
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='workflowjobtemplatenode',
|
||||
name='failure_nodes',
|
||||
field=models.ManyToManyField(blank=True, related_name='%(class)ss_failure', to='main.workflowjobtemplatenode'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='workflowjobtemplatenode',
|
||||
name='inventory',
|
||||
field=models.ForeignKey(
|
||||
blank=True,
|
||||
default=None,
|
||||
help_text='Inventory applied as a prompt, assuming job template prompts for inventory',
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.SET_NULL,
|
||||
related_name='%(class)ss',
|
||||
to='main.inventory',
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='workflowjobtemplatenode',
|
||||
name='labels',
|
||||
field=models.ManyToManyField(related_name='%(class)s_labels', to='main.label'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='workflowjobtemplatenode',
|
||||
name='success_nodes',
|
||||
field=models.ManyToManyField(blank=True, related_name='%(class)ss_success', to='main.workflowjobtemplatenode'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='workflowjobtemplatenode',
|
||||
name='unified_job_template',
|
||||
field=models.ForeignKey(
|
||||
blank=True, default=None, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='%(class)ss', to='main.unifiedjobtemplate'
|
||||
),
|
||||
),
|
||||
]
|
||||
102
awx/main/migrations/0185_djanog_indexes.py
Normal file
102
awx/main/migrations/0185_djanog_indexes.py
Normal file
@@ -0,0 +1,102 @@
|
||||
# Generated by Django 4.2 on 2023-04-28 19:21
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
('main', '0184_django_upgrade'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RenameIndex(
|
||||
model_name='adhoccommandevent',
|
||||
new_name='main_adhocc_ad_hoc__a57777_idx',
|
||||
old_fields=('ad_hoc_command', 'job_created', 'counter'),
|
||||
),
|
||||
migrations.RenameIndex(
|
||||
model_name='adhoccommandevent',
|
||||
new_name='main_adhocc_ad_hoc__e72142_idx',
|
||||
old_fields=('ad_hoc_command', 'job_created', 'event'),
|
||||
),
|
||||
migrations.RenameIndex(
|
||||
model_name='adhoccommandevent',
|
||||
new_name='main_adhocc_ad_hoc__1e4d24_idx',
|
||||
old_fields=('ad_hoc_command', 'job_created', 'uuid'),
|
||||
),
|
||||
migrations.RenameIndex(
|
||||
model_name='inventoryupdateevent',
|
||||
new_name='main_invent_invento_f72b21_idx',
|
||||
old_fields=('inventory_update', 'job_created', 'uuid'),
|
||||
),
|
||||
migrations.RenameIndex(
|
||||
model_name='inventoryupdateevent',
|
||||
new_name='main_invent_invento_364dcb_idx',
|
||||
old_fields=('inventory_update', 'job_created', 'counter'),
|
||||
),
|
||||
migrations.RenameIndex(
|
||||
model_name='jobevent',
|
||||
new_name='main_jobeve_job_id_51c382_idx',
|
||||
old_fields=('job', 'job_created', 'counter'),
|
||||
),
|
||||
migrations.RenameIndex(
|
||||
model_name='jobevent',
|
||||
new_name='main_jobeve_job_id_0ddc6b_idx',
|
||||
old_fields=('job', 'job_created', 'event'),
|
||||
),
|
||||
migrations.RenameIndex(
|
||||
model_name='jobevent',
|
||||
new_name='main_jobeve_job_id_40a56d_idx',
|
||||
old_fields=('job', 'job_created', 'parent_uuid'),
|
||||
),
|
||||
migrations.RenameIndex(
|
||||
model_name='jobevent',
|
||||
new_name='main_jobeve_job_id_3c4a4a_idx',
|
||||
old_fields=('job', 'job_created', 'uuid'),
|
||||
),
|
||||
migrations.RenameIndex(
|
||||
model_name='projectupdateevent',
|
||||
new_name='main_projec_project_c44b7c_idx',
|
||||
old_fields=('project_update', 'job_created', 'event'),
|
||||
),
|
||||
migrations.RenameIndex(
|
||||
model_name='projectupdateevent',
|
||||
new_name='main_projec_project_449bbd_idx',
|
||||
old_fields=('project_update', 'job_created', 'uuid'),
|
||||
),
|
||||
migrations.RenameIndex(
|
||||
model_name='projectupdateevent',
|
||||
new_name='main_projec_project_69559a_idx',
|
||||
old_fields=('project_update', 'job_created', 'counter'),
|
||||
),
|
||||
migrations.RenameIndex(
|
||||
model_name='role',
|
||||
new_name='main_rbac_r_content_979bdd_idx',
|
||||
old_fields=('content_type', 'object_id'),
|
||||
),
|
||||
migrations.RenameIndex(
|
||||
model_name='roleancestorentry',
|
||||
new_name='main_rbac_r_ancesto_22b9f0_idx',
|
||||
old_fields=('ancestor', 'content_type_id', 'object_id'),
|
||||
),
|
||||
migrations.RenameIndex(
|
||||
model_name='roleancestorentry',
|
||||
new_name='main_rbac_r_ancesto_b44606_idx',
|
||||
old_fields=('ancestor', 'content_type_id', 'role_field'),
|
||||
),
|
||||
migrations.RenameIndex(
|
||||
model_name='roleancestorentry',
|
||||
new_name='main_rbac_r_ancesto_c87b87_idx',
|
||||
old_fields=('ancestor', 'descendent'),
|
||||
),
|
||||
migrations.RenameIndex(
|
||||
model_name='systemjobevent',
|
||||
new_name='main_system_system__e39825_idx',
|
||||
old_fields=('system_job', 'job_created', 'uuid'),
|
||||
),
|
||||
migrations.RenameIndex(
|
||||
model_name='systemjobevent',
|
||||
new_name='main_system_system__73537a_idx',
|
||||
old_fields=('system_job', 'job_created', 'counter'),
|
||||
),
|
||||
]
|
||||
@@ -8,7 +8,7 @@ logger = logging.getLogger('awx.main.migrations')
|
||||
def migrate_org_admin_to_use(apps, schema_editor):
|
||||
logger.info('Initiated migration from Org admin to use role')
|
||||
roles_added = 0
|
||||
for org in Organization.objects.prefetch_related('admin_role__members').iterator():
|
||||
for org in Organization.objects.prefetch_related('admin_role__members').iterator(chunk_size=1000):
|
||||
igs = list(org.instance_groups.all())
|
||||
if not igs:
|
||||
continue
|
||||
|
||||
@@ -195,6 +195,9 @@ class Credential(PasswordFieldsModel, CommonModelNameNotUnique, ResourceMixin):
|
||||
|
||||
@cached_property
|
||||
def dynamic_input_fields(self):
|
||||
# if the credential is not yet saved we can't access the input_sources
|
||||
if not self.id:
|
||||
return []
|
||||
return [obj.input_field_name for obj in self.input_sources.all()]
|
||||
|
||||
def _password_field_allows_ask(self, field):
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import datetime
|
||||
from datetime import timezone
|
||||
import logging
|
||||
from collections import defaultdict
|
||||
|
||||
@@ -10,7 +11,7 @@ from django.db import models, DatabaseError
|
||||
from django.db.models.functions import Cast
|
||||
from django.utils.dateparse import parse_datetime
|
||||
from django.utils.text import Truncator
|
||||
from django.utils.timezone import utc, now
|
||||
from django.utils.timezone import now
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from django.utils.encoding import force_str
|
||||
|
||||
@@ -422,7 +423,7 @@ class BasePlaybookEvent(CreatedModifiedModel):
|
||||
if not isinstance(kwargs['created'], datetime.datetime):
|
||||
kwargs['created'] = parse_datetime(kwargs['created'])
|
||||
if not kwargs['created'].tzinfo:
|
||||
kwargs['created'] = kwargs['created'].replace(tzinfo=utc)
|
||||
kwargs['created'] = kwargs['created'].replace(tzinfo=timezone.utc)
|
||||
except (KeyError, ValueError):
|
||||
kwargs.pop('created', None)
|
||||
|
||||
@@ -432,7 +433,7 @@ class BasePlaybookEvent(CreatedModifiedModel):
|
||||
if not isinstance(kwargs['job_created'], datetime.datetime):
|
||||
kwargs['job_created'] = parse_datetime(kwargs['job_created'])
|
||||
if not kwargs['job_created'].tzinfo:
|
||||
kwargs['job_created'] = kwargs['job_created'].replace(tzinfo=utc)
|
||||
kwargs['job_created'] = kwargs['job_created'].replace(tzinfo=timezone.utc)
|
||||
except (KeyError, ValueError):
|
||||
kwargs.pop('job_created', None)
|
||||
|
||||
@@ -470,11 +471,11 @@ class JobEvent(BasePlaybookEvent):
|
||||
class Meta:
|
||||
app_label = 'main'
|
||||
ordering = ('pk',)
|
||||
index_together = [
|
||||
('job', 'job_created', 'event'),
|
||||
('job', 'job_created', 'uuid'),
|
||||
('job', 'job_created', 'parent_uuid'),
|
||||
('job', 'job_created', 'counter'),
|
||||
indexes = [
|
||||
models.Index(fields=['job', 'job_created', 'event']),
|
||||
models.Index(fields=['job', 'job_created', 'uuid']),
|
||||
models.Index(fields=['job', 'job_created', 'parent_uuid']),
|
||||
models.Index(fields=['job', 'job_created', 'counter']),
|
||||
]
|
||||
|
||||
id = models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')
|
||||
@@ -632,10 +633,10 @@ class ProjectUpdateEvent(BasePlaybookEvent):
|
||||
class Meta:
|
||||
app_label = 'main'
|
||||
ordering = ('pk',)
|
||||
index_together = [
|
||||
('project_update', 'job_created', 'event'),
|
||||
('project_update', 'job_created', 'uuid'),
|
||||
('project_update', 'job_created', 'counter'),
|
||||
indexes = [
|
||||
models.Index(fields=['project_update', 'job_created', 'event']),
|
||||
models.Index(fields=['project_update', 'job_created', 'uuid']),
|
||||
models.Index(fields=['project_update', 'job_created', 'counter']),
|
||||
]
|
||||
|
||||
id = models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')
|
||||
@@ -734,7 +735,7 @@ class BaseCommandEvent(CreatedModifiedModel):
|
||||
if not isinstance(kwargs['created'], datetime.datetime):
|
||||
kwargs['created'] = parse_datetime(kwargs['created'])
|
||||
if not kwargs['created'].tzinfo:
|
||||
kwargs['created'] = kwargs['created'].replace(tzinfo=utc)
|
||||
kwargs['created'] = kwargs['created'].replace(tzinfo=timezone.utc)
|
||||
except (KeyError, ValueError):
|
||||
kwargs.pop('created', None)
|
||||
|
||||
@@ -770,10 +771,10 @@ class AdHocCommandEvent(BaseCommandEvent):
|
||||
class Meta:
|
||||
app_label = 'main'
|
||||
ordering = ('-pk',)
|
||||
index_together = [
|
||||
('ad_hoc_command', 'job_created', 'event'),
|
||||
('ad_hoc_command', 'job_created', 'uuid'),
|
||||
('ad_hoc_command', 'job_created', 'counter'),
|
||||
indexes = [
|
||||
models.Index(fields=['ad_hoc_command', 'job_created', 'event']),
|
||||
models.Index(fields=['ad_hoc_command', 'job_created', 'uuid']),
|
||||
models.Index(fields=['ad_hoc_command', 'job_created', 'counter']),
|
||||
]
|
||||
|
||||
EVENT_TYPES = [
|
||||
@@ -875,9 +876,9 @@ class InventoryUpdateEvent(BaseCommandEvent):
|
||||
class Meta:
|
||||
app_label = 'main'
|
||||
ordering = ('-pk',)
|
||||
index_together = [
|
||||
('inventory_update', 'job_created', 'uuid'),
|
||||
('inventory_update', 'job_created', 'counter'),
|
||||
indexes = [
|
||||
models.Index(fields=['inventory_update', 'job_created', 'uuid']),
|
||||
models.Index(fields=['inventory_update', 'job_created', 'counter']),
|
||||
]
|
||||
|
||||
id = models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')
|
||||
@@ -920,9 +921,9 @@ class SystemJobEvent(BaseCommandEvent):
|
||||
class Meta:
|
||||
app_label = 'main'
|
||||
ordering = ('-pk',)
|
||||
index_together = [
|
||||
('system_job', 'job_created', 'uuid'),
|
||||
('system_job', 'job_created', 'counter'),
|
||||
indexes = [
|
||||
models.Index(fields=['system_job', 'job_created', 'uuid']),
|
||||
models.Index(fields=['system_job', 'job_created', 'counter']),
|
||||
]
|
||||
|
||||
id = models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')
|
||||
|
||||
@@ -284,7 +284,7 @@ class JobNotificationMixin(object):
|
||||
'workflow_url',
|
||||
'scm_branch',
|
||||
'artifacts',
|
||||
{'host_status_counts': ['skipped', 'ok', 'changed', 'failed', 'failures', 'dark', 'processed', 'rescued', 'ignored']},
|
||||
{'host_status_counts': ['skipped', 'ok', 'changed', 'failed', 'failures', 'dark' 'processed', 'rescued', 'ignored']},
|
||||
{
|
||||
'summary_fields': [
|
||||
{
|
||||
|
||||
@@ -141,7 +141,7 @@ class Role(models.Model):
|
||||
app_label = 'main'
|
||||
verbose_name_plural = _('roles')
|
||||
db_table = 'main_rbac_roles'
|
||||
index_together = [("content_type", "object_id")]
|
||||
indexes = [models.Index(fields=["content_type", "object_id"])]
|
||||
ordering = ("content_type", "object_id")
|
||||
|
||||
role_field = models.TextField(null=False)
|
||||
@@ -447,10 +447,10 @@ class RoleAncestorEntry(models.Model):
|
||||
app_label = 'main'
|
||||
verbose_name_plural = _('role_ancestors')
|
||||
db_table = 'main_rbac_role_ancestors'
|
||||
index_together = [
|
||||
("ancestor", "content_type_id", "object_id"), # used by get_roles_on_resource
|
||||
("ancestor", "content_type_id", "role_field"), # used by accessible_objects
|
||||
("ancestor", "descendent"), # used by rebuild_role_ancestor_list in the NOT EXISTS clauses.
|
||||
indexes = [
|
||||
models.Index(fields=["ancestor", "content_type_id", "object_id"]), # used by get_roles_on_resource
|
||||
models.Index(fields=["ancestor", "content_type_id", "role_field"]), # used by accessible_objects
|
||||
models.Index(fields=["ancestor", "descendent"]), # used by rebuild_role_ancestor_list in the NOT EXISTS clauses.
|
||||
]
|
||||
|
||||
descendent = models.ForeignKey(Role, null=False, on_delete=models.CASCADE, related_name='+')
|
||||
|
||||
@@ -1137,11 +1137,9 @@ class UnifiedJob(
|
||||
if total > max_supported:
|
||||
raise StdoutMaxBytesExceeded(total, max_supported)
|
||||
|
||||
# psycopg2's copy_expert writes bytes, but callers of this
|
||||
# psycopg3's copy writes bytes, but callers of this
|
||||
# function assume a str-based fd will be returned; decode
|
||||
# .write() calls on the fly to maintain this interface
|
||||
_write = fd.write
|
||||
fd.write = lambda s: _write(smart_str(s))
|
||||
tbl = self._meta.db_table + 'event'
|
||||
created_by_cond = ''
|
||||
if self.has_unpartitioned_events:
|
||||
@@ -1150,7 +1148,9 @@ class UnifiedJob(
|
||||
created_by_cond = f"job_created='{self.created.isoformat()}' AND "
|
||||
|
||||
sql = f"copy (select stdout from {tbl} where {created_by_cond}{self.event_parent_key}={self.id} and stdout != '' order by start_line) to stdout" # nosql
|
||||
cursor.copy_expert(sql, fd)
|
||||
with cursor.copy(sql) as copy:
|
||||
while data := copy.read():
|
||||
fd.write(smart_str(bytes(data)))
|
||||
|
||||
if hasattr(fd, 'name'):
|
||||
# If we're dealing with a physical file, use `sed` to clean
|
||||
|
||||
@@ -639,7 +639,7 @@ class AWXReceptorJob:
|
||||
#
|
||||
RECEPTOR_CONFIG_STARTER = (
|
||||
{'local-only': None},
|
||||
{'log-level': 'info'},
|
||||
{'log-level': 'debug'},
|
||||
{'node': {'firewallrules': [{'action': 'reject', 'tonode': settings.CLUSTER_HOST_ID, 'toservice': 'control'}]}},
|
||||
{'control-service': {'service': 'control', 'filename': '/var/run/receptor/receptor.sock', 'permissions': '0660'}},
|
||||
{'work-command': {'worktype': 'local', 'command': 'ansible-runner', 'params': 'worker', 'allowruntimeparams': True}},
|
||||
|
||||
@@ -2,8 +2,8 @@ import pytest
|
||||
import tempfile
|
||||
import os
|
||||
import re
|
||||
import shutil
|
||||
import csv
|
||||
from io import StringIO
|
||||
|
||||
from django.utils.timezone import now
|
||||
from datetime import timedelta
|
||||
@@ -20,15 +20,16 @@ from awx.main.models import (
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def sqlite_copy_expert(request):
|
||||
# copy_expert is postgres-specific, and SQLite doesn't support it; mock its
|
||||
# behavior to test that it writes a file that contains stdout from events
|
||||
path = tempfile.mkdtemp(prefix="copied_tables")
|
||||
class MockCopy:
|
||||
headers = None
|
||||
results = None
|
||||
sent_data = False
|
||||
|
||||
def write_stdout(self, sql, fd):
|
||||
def __init__(self, sql, parent_connection):
|
||||
# Would be cool if we instead properly disected the SQL query and verified
|
||||
# it that way. But instead, we just take the naive approach here.
|
||||
self.results = None
|
||||
self.headers = None
|
||||
sql = sql.strip()
|
||||
assert sql.startswith("COPY (")
|
||||
assert sql.endswith(") TO STDOUT WITH CSV HEADER")
|
||||
@@ -51,29 +52,49 @@ def sqlite_copy_expert(request):
|
||||
elif not line.endswith(","):
|
||||
sql_new[-1] = sql_new[-1].rstrip(",")
|
||||
sql = "\n".join(sql_new)
|
||||
parent_connection.execute(sql)
|
||||
self.results = parent_connection.fetchall()
|
||||
self.headers = [i[0] for i in parent_connection.description]
|
||||
|
||||
self.execute(sql)
|
||||
results = self.fetchall()
|
||||
headers = [i[0] for i in self.description]
|
||||
def read(self):
|
||||
if not self.sent_data:
|
||||
mem_file = StringIO()
|
||||
csv_handle = csv.writer(
|
||||
mem_file,
|
||||
delimiter=",",
|
||||
quoting=csv.QUOTE_ALL,
|
||||
escapechar="\\",
|
||||
lineterminator="\n",
|
||||
)
|
||||
if self.headers:
|
||||
csv_handle.writerow(self.headers)
|
||||
if self.results:
|
||||
csv_handle.writerows(self.results)
|
||||
self.sent_data = True
|
||||
return memoryview((mem_file.getvalue()).encode())
|
||||
return None
|
||||
|
||||
csv_handle = csv.writer(
|
||||
fd,
|
||||
delimiter=",",
|
||||
quoting=csv.QUOTE_ALL,
|
||||
escapechar="\\",
|
||||
lineterminator="\n",
|
||||
)
|
||||
csv_handle.writerow(headers)
|
||||
csv_handle.writerows(results)
|
||||
def __enter__(self):
|
||||
return self
|
||||
|
||||
setattr(SQLiteCursorWrapper, "copy_expert", write_stdout)
|
||||
request.addfinalizer(lambda: shutil.rmtree(path))
|
||||
request.addfinalizer(lambda: delattr(SQLiteCursorWrapper, "copy_expert"))
|
||||
return path
|
||||
def __exit__(self, exc_type, exc_val, exc_tb):
|
||||
pass
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def sqlite_copy(request, mocker):
|
||||
# copy is postgres-specific, and SQLite doesn't support it; mock its
|
||||
# behavior to test that it writes a file that contains stdout from events
|
||||
|
||||
def write_stdout(self, sql):
|
||||
mock_copy = MockCopy(sql, self)
|
||||
return mock_copy
|
||||
|
||||
mocker.patch.object(SQLiteCursorWrapper, 'copy', write_stdout, create=True)
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_copy_tables_unified_job_query(sqlite_copy_expert, project, inventory, job_template):
|
||||
def test_copy_tables_unified_job_query(sqlite_copy, project, inventory, job_template):
|
||||
"""
|
||||
Ensure that various unified job types are in the output of the query.
|
||||
"""
|
||||
@@ -127,7 +148,7 @@ def workflow_job(states=["new", "new", "new", "new", "new"]):
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_copy_tables_workflow_job_node_query(sqlite_copy_expert, workflow_job):
|
||||
def test_copy_tables_workflow_job_node_query(sqlite_copy, workflow_job):
|
||||
time_start = now() - timedelta(hours=9)
|
||||
|
||||
with tempfile.TemporaryDirectory() as tmpdir:
|
||||
|
||||
@@ -214,7 +214,7 @@ class TestControllerNode:
|
||||
return AdHocCommand.objects.create(inventory=inventory)
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_field_controller_node_exists(self, sqlite_copy_expert, admin_user, job, project_update, inventory_update, adhoc, get, system_job_factory):
|
||||
def test_field_controller_node_exists(self, sqlite_copy, admin_user, job, project_update, inventory_update, adhoc, get, system_job_factory):
|
||||
system_job = system_job_factory()
|
||||
|
||||
r = get(reverse('api:unified_job_list') + '?id={}'.format(job.id), admin_user, expect=200)
|
||||
|
||||
@@ -57,7 +57,7 @@ def _mk_inventory_update(created=None):
|
||||
[_mk_inventory_update, InventoryUpdateEvent, 'inventory_update', 'api:inventory_update_stdout'],
|
||||
],
|
||||
)
|
||||
def test_text_stdout(sqlite_copy_expert, Parent, Child, relation, view, get, admin):
|
||||
def test_text_stdout(sqlite_copy, Parent, Child, relation, view, get, admin):
|
||||
job = Parent()
|
||||
job.save()
|
||||
for i in range(3):
|
||||
@@ -79,7 +79,7 @@ def test_text_stdout(sqlite_copy_expert, Parent, Child, relation, view, get, adm
|
||||
],
|
||||
)
|
||||
@pytest.mark.parametrize('download', [True, False])
|
||||
def test_ansi_stdout_filtering(sqlite_copy_expert, Parent, Child, relation, view, download, get, admin):
|
||||
def test_ansi_stdout_filtering(sqlite_copy, Parent, Child, relation, view, download, get, admin):
|
||||
job = Parent()
|
||||
job.save()
|
||||
for i in range(3):
|
||||
@@ -111,7 +111,7 @@ def test_ansi_stdout_filtering(sqlite_copy_expert, Parent, Child, relation, view
|
||||
[_mk_inventory_update, InventoryUpdateEvent, 'inventory_update', 'api:inventory_update_stdout'],
|
||||
],
|
||||
)
|
||||
def test_colorized_html_stdout(sqlite_copy_expert, Parent, Child, relation, view, get, admin):
|
||||
def test_colorized_html_stdout(sqlite_copy, Parent, Child, relation, view, get, admin):
|
||||
job = Parent()
|
||||
job.save()
|
||||
for i in range(3):
|
||||
@@ -134,7 +134,7 @@ def test_colorized_html_stdout(sqlite_copy_expert, Parent, Child, relation, view
|
||||
[_mk_inventory_update, InventoryUpdateEvent, 'inventory_update', 'api:inventory_update_stdout'],
|
||||
],
|
||||
)
|
||||
def test_stdout_line_range(sqlite_copy_expert, Parent, Child, relation, view, get, admin):
|
||||
def test_stdout_line_range(sqlite_copy, Parent, Child, relation, view, get, admin):
|
||||
job = Parent()
|
||||
job.save()
|
||||
for i in range(20):
|
||||
@@ -146,7 +146,7 @@ def test_stdout_line_range(sqlite_copy_expert, Parent, Child, relation, view, ge
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_text_stdout_from_system_job_events(sqlite_copy_expert, get, admin):
|
||||
def test_text_stdout_from_system_job_events(sqlite_copy, get, admin):
|
||||
created = tz_now()
|
||||
job = SystemJob(created=created)
|
||||
job.save()
|
||||
@@ -158,7 +158,7 @@ def test_text_stdout_from_system_job_events(sqlite_copy_expert, get, admin):
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_text_stdout_with_max_stdout(sqlite_copy_expert, get, admin):
|
||||
def test_text_stdout_with_max_stdout(sqlite_copy, get, admin):
|
||||
created = tz_now()
|
||||
job = SystemJob(created=created)
|
||||
job.save()
|
||||
@@ -185,7 +185,7 @@ def test_text_stdout_with_max_stdout(sqlite_copy_expert, get, admin):
|
||||
)
|
||||
@pytest.mark.parametrize('fmt', ['txt', 'ansi'])
|
||||
@mock.patch('awx.main.redact.UriCleaner.SENSITIVE_URI_PATTERN', mock.Mock(**{'search.return_value': None})) # really slow for large strings
|
||||
def test_max_bytes_display(sqlite_copy_expert, Parent, Child, relation, view, fmt, get, admin):
|
||||
def test_max_bytes_display(sqlite_copy, Parent, Child, relation, view, fmt, get, admin):
|
||||
created = tz_now()
|
||||
job = Parent(created=created)
|
||||
job.save()
|
||||
@@ -255,7 +255,7 @@ def test_legacy_result_stdout_with_max_bytes(Cls, view, fmt, get, admin):
|
||||
],
|
||||
)
|
||||
@pytest.mark.parametrize('fmt', ['txt', 'ansi', 'txt_download', 'ansi_download'])
|
||||
def test_text_with_unicode_stdout(sqlite_copy_expert, Parent, Child, relation, view, get, admin, fmt):
|
||||
def test_text_with_unicode_stdout(sqlite_copy, Parent, Child, relation, view, get, admin, fmt):
|
||||
job = Parent()
|
||||
job.save()
|
||||
for i in range(3):
|
||||
@@ -267,7 +267,7 @@ def test_text_with_unicode_stdout(sqlite_copy_expert, Parent, Child, relation, v
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_unicode_with_base64_ansi(sqlite_copy_expert, get, admin):
|
||||
def test_unicode_with_base64_ansi(sqlite_copy, get, admin):
|
||||
created = tz_now()
|
||||
job = Job(created=created)
|
||||
job.save()
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
# Python
|
||||
import pytest
|
||||
from unittest import mock
|
||||
import tempfile
|
||||
import shutil
|
||||
import urllib.parse
|
||||
from unittest.mock import PropertyMock
|
||||
|
||||
@@ -789,25 +787,43 @@ def oauth_application(admin):
|
||||
return Application.objects.create(name='test app', user=admin, client_type='confidential', authorization_grant_type='password')
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def sqlite_copy_expert(request):
|
||||
# copy_expert is postgres-specific, and SQLite doesn't support it; mock its
|
||||
# behavior to test that it writes a file that contains stdout from events
|
||||
path = tempfile.mkdtemp(prefix='job-event-stdout')
|
||||
class MockCopy:
|
||||
events = []
|
||||
index = -1
|
||||
|
||||
def write_stdout(self, sql, fd):
|
||||
# simulate postgres copy_expert support with ORM code
|
||||
def __init__(self, sql):
|
||||
self.events = []
|
||||
parts = sql.split(' ')
|
||||
tablename = parts[parts.index('from') + 1]
|
||||
for cls in (JobEvent, AdHocCommandEvent, ProjectUpdateEvent, InventoryUpdateEvent, SystemJobEvent):
|
||||
if cls._meta.db_table == tablename:
|
||||
for event in cls.objects.order_by('start_line').all():
|
||||
fd.write(event.stdout)
|
||||
self.events.append(event.stdout)
|
||||
|
||||
setattr(SQLiteCursorWrapper, 'copy_expert', write_stdout)
|
||||
request.addfinalizer(lambda: shutil.rmtree(path))
|
||||
request.addfinalizer(lambda: delattr(SQLiteCursorWrapper, 'copy_expert'))
|
||||
return path
|
||||
def read(self):
|
||||
self.index = self.index + 1
|
||||
if self.index < len(self.events):
|
||||
return memoryview(self.events[self.index].encode())
|
||||
|
||||
return None
|
||||
|
||||
def __enter__(self):
|
||||
return self
|
||||
|
||||
def __exit__(self, exc_type, exc_val, exc_tb):
|
||||
pass
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def sqlite_copy(request, mocker):
|
||||
# copy is postgres-specific, and SQLite doesn't support it; mock its
|
||||
# behavior to test that it writes a file that contains stdout from events
|
||||
|
||||
def write_stdout(self, sql):
|
||||
mock_copy = MockCopy(sql)
|
||||
return mock_copy
|
||||
|
||||
mocker.patch.object(SQLiteCursorWrapper, 'copy', write_stdout, create=True)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
|
||||
@@ -271,6 +271,7 @@ def test_inventory_update_excessively_long_name(inventory, inventory_source):
|
||||
class TestHostManager:
|
||||
def test_host_filter_not_smart(self, setup_ec2_gce, organization):
|
||||
smart_inventory = Inventory(name='smart', organization=organization, host_filter='inventory_sources__source=ec2')
|
||||
smart_inventory.save()
|
||||
assert len(smart_inventory.hosts.all()) == 0
|
||||
|
||||
def test_host_distinctness(self, setup_inventory_groups, organization):
|
||||
|
||||
@@ -98,7 +98,7 @@ class TestJobNotificationMixin(object):
|
||||
|
||||
@pytest.mark.django_db
|
||||
@pytest.mark.parametrize('JobClass', [AdHocCommand, InventoryUpdate, Job, ProjectUpdate, SystemJob, WorkflowJob])
|
||||
def test_context(self, JobClass, sqlite_copy_expert, project, inventory_source):
|
||||
def test_context(self, JobClass, sqlite_copy, project, inventory_source):
|
||||
"""The Jinja context defines all of the fields that can be used by a template. Ensure that the context generated
|
||||
for each job type has the expected structure."""
|
||||
kwargs = {}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
from datetime import datetime
|
||||
from django.utils.timezone import utc
|
||||
from datetime import timezone
|
||||
import pytest
|
||||
|
||||
from awx.main.models import JobEvent, ProjectUpdateEvent, AdHocCommandEvent, InventoryUpdateEvent, SystemJobEvent
|
||||
@@ -18,7 +18,7 @@ from awx.main.models import JobEvent, ProjectUpdateEvent, AdHocCommandEvent, Inv
|
||||
@pytest.mark.parametrize('created', [datetime(2018, 1, 1).isoformat(), datetime(2018, 1, 1)])
|
||||
def test_event_parse_created(job_identifier, cls, created):
|
||||
event = cls.create_from_data(**{job_identifier: 123, 'created': created})
|
||||
assert event.created == datetime(2018, 1, 1).replace(tzinfo=utc)
|
||||
assert event.created == datetime(2018, 1, 1).replace(tzinfo=timezone.utc)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
|
||||
@@ -395,6 +395,7 @@ AUTHENTICATION_BACKENDS = (
|
||||
OAUTH2_PROVIDER_APPLICATION_MODEL = 'main.OAuth2Application'
|
||||
OAUTH2_PROVIDER_ACCESS_TOKEN_MODEL = 'main.OAuth2AccessToken'
|
||||
OAUTH2_PROVIDER_REFRESH_TOKEN_MODEL = 'oauth2_provider.RefreshToken'
|
||||
OAUTH2_PROVIDER_ID_TOKEN_MODEL = "oauth2_provider.IDToken"
|
||||
|
||||
OAUTH2_PROVIDER = {'ACCESS_TOKEN_EXPIRE_SECONDS': 31536000000, 'AUTHORIZATION_CODE_EXPIRE_SECONDS': 600, 'REFRESH_TOKEN_EXPIRE_SECONDS': 2628000}
|
||||
ALLOW_OAUTH2_FOR_EXTERNAL_USERS = False
|
||||
|
||||
@@ -43,7 +43,6 @@ function LaunchButton({ resource, children }) {
|
||||
const [surveyConfig, setSurveyConfig] = useState(null);
|
||||
const [labels, setLabels] = useState([]);
|
||||
const [isLaunching, setIsLaunching] = useState(false);
|
||||
const [resourceCredentials, setResourceCredentials] = useState([]);
|
||||
const [error, setError] = useState(null);
|
||||
|
||||
const handleLaunch = async () => {
|
||||
@@ -84,13 +83,6 @@ function LaunchButton({ resource, children }) {
|
||||
setLabels(allLabels);
|
||||
}
|
||||
|
||||
if (launch.ask_credential_on_launch) {
|
||||
const {
|
||||
data: { results: templateCredentials },
|
||||
} = await JobTemplatesAPI.readCredentials(resource.id);
|
||||
setResourceCredentials(templateCredentials);
|
||||
}
|
||||
|
||||
if (canLaunchWithoutPrompt(launch)) {
|
||||
await launchWithParams({});
|
||||
} else {
|
||||
@@ -216,7 +208,6 @@ function LaunchButton({ resource, children }) {
|
||||
labels={labels}
|
||||
onLaunch={launchWithParams}
|
||||
onCancel={() => setShowLaunchPrompt(false)}
|
||||
resourceDefaultCredentials={resourceCredentials}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
|
||||
@@ -47,12 +47,6 @@ describe('LaunchButton', () => {
|
||||
variables_needed_to_start: [],
|
||||
},
|
||||
});
|
||||
JobTemplatesAPI.readCredentials.mockResolvedValue({
|
||||
data: {
|
||||
count: 0,
|
||||
results: [],
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => jest.clearAllMocks());
|
||||
|
||||
@@ -19,7 +19,6 @@ function PromptModalForm({
|
||||
labels,
|
||||
surveyConfig,
|
||||
instanceGroups,
|
||||
resourceDefaultCredentials,
|
||||
}) {
|
||||
const { setFieldTouched, values } = useFormikContext();
|
||||
const [showDescription, setShowDescription] = useState(false);
|
||||
@@ -36,9 +35,9 @@ function PromptModalForm({
|
||||
surveyConfig,
|
||||
resource,
|
||||
labels,
|
||||
instanceGroups,
|
||||
resourceDefaultCredentials
|
||||
instanceGroups
|
||||
);
|
||||
|
||||
const handleSubmit = async () => {
|
||||
const postValues = {};
|
||||
const setValue = (key, value) => {
|
||||
|
||||
@@ -69,20 +69,6 @@ describe('LaunchPrompt', () => {
|
||||
spec: [{ type: 'text', variable: 'foo' }],
|
||||
},
|
||||
});
|
||||
JobTemplatesAPI.readCredentials.mockResolvedValue({
|
||||
data: {
|
||||
results: [
|
||||
{
|
||||
id: 5,
|
||||
name: 'cred that prompts',
|
||||
credential_type: 1,
|
||||
inputs: {
|
||||
password: 'ASK',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
InstanceGroupsAPI.read.mockResolvedValue({
|
||||
data: {
|
||||
results: [
|
||||
@@ -226,16 +212,6 @@ describe('LaunchPrompt', () => {
|
||||
],
|
||||
},
|
||||
}}
|
||||
resourceDefaultCredentials={[
|
||||
{
|
||||
id: 5,
|
||||
name: 'cred that prompts',
|
||||
credential_type: 1,
|
||||
inputs: {
|
||||
password: 'ASK',
|
||||
},
|
||||
},
|
||||
]}
|
||||
onLaunch={noop}
|
||||
onCancel={noop}
|
||||
surveyConfig={{
|
||||
@@ -313,16 +289,6 @@ describe('LaunchPrompt', () => {
|
||||
resource={resource}
|
||||
onLaunch={noop}
|
||||
onCancel={noop}
|
||||
resourceDefaultCredentials={[
|
||||
{
|
||||
id: 5,
|
||||
name: 'cred that prompts',
|
||||
credential_type: 1,
|
||||
inputs: {
|
||||
password: 'ASK',
|
||||
},
|
||||
},
|
||||
]}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import 'styled-components/macro';
|
||||
import React, { useState, useCallback, useEffect } from 'react';
|
||||
import { useHistory, useLocation } from 'react-router-dom';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
|
||||
import { t } from '@lingui/macro';
|
||||
import { useField } from 'formik';
|
||||
@@ -8,7 +8,7 @@ import styled from 'styled-components';
|
||||
import { Alert, ToolbarItem } from '@patternfly/react-core';
|
||||
import { CredentialsAPI, CredentialTypesAPI } from 'api';
|
||||
import { getSearchableKeys } from 'components/PaginatedTable';
|
||||
import { getQSConfig, parseQueryString, updateQueryString } from 'util/qs';
|
||||
import { getQSConfig, parseQueryString } from 'util/qs';
|
||||
import useRequest from 'hooks/useRequest';
|
||||
import AnsibleSelect from '../../AnsibleSelect';
|
||||
import OptionsList from '../../OptionsList';
|
||||
@@ -31,18 +31,18 @@ function CredentialsStep({
|
||||
allowCredentialsWithPasswords,
|
||||
defaultCredentials = [],
|
||||
}) {
|
||||
const history = useHistory();
|
||||
const location = useLocation();
|
||||
const [field, meta, helpers] = useField({
|
||||
name: 'credentials',
|
||||
validate: (val) =>
|
||||
credentialsValidator(
|
||||
allowCredentialsWithPasswords,
|
||||
val,
|
||||
defaultCredentials ?? []
|
||||
defaultCredentials
|
||||
),
|
||||
});
|
||||
const [selectedType, setSelectedType] = useState(null);
|
||||
const history = useHistory();
|
||||
|
||||
const {
|
||||
result: types,
|
||||
error: typesError,
|
||||
@@ -104,32 +104,12 @@ function CredentialsStep({
|
||||
credentialsValidator(
|
||||
allowCredentialsWithPasswords,
|
||||
field.value,
|
||||
defaultCredentials ?? []
|
||||
defaultCredentials
|
||||
)
|
||||
);
|
||||
/* eslint-disable-next-line react-hooks/exhaustive-deps */
|
||||
}, []);
|
||||
|
||||
const removeAllSearchTerms = (qsConfig) => {
|
||||
const oldParams = parseQueryString(qsConfig, location.search);
|
||||
Object.keys(oldParams).forEach((key) => {
|
||||
oldParams[key] = null;
|
||||
});
|
||||
const defaultParams = {
|
||||
...oldParams,
|
||||
page: 1,
|
||||
page_size: 5,
|
||||
order_by: 'name',
|
||||
};
|
||||
const qs = updateQueryString(qsConfig, location.search, defaultParams);
|
||||
pushHistoryState(qs);
|
||||
};
|
||||
|
||||
const pushHistoryState = (qs) => {
|
||||
const { pathname } = history.location;
|
||||
history.push(qs ? `${pathname}?${qs}` : pathname);
|
||||
};
|
||||
|
||||
if (isTypesLoading) {
|
||||
return <ContentLoading />;
|
||||
}
|
||||
@@ -174,7 +154,9 @@ function CredentialsStep({
|
||||
value={selectedType && selectedType.id}
|
||||
onChange={(e, id) => {
|
||||
// Reset query params when the category of credentials is changed
|
||||
removeAllSearchTerms(QS_CONFIG);
|
||||
history.replace({
|
||||
search: '',
|
||||
});
|
||||
setSelectedType(types.find((o) => o.id === parseInt(id, 10)));
|
||||
}}
|
||||
/>
|
||||
|
||||
@@ -168,9 +168,7 @@ describe('CredentialsStep', () => {
|
||||
test('should reset query params (credential.page) when selected credential type is changed', async () => {
|
||||
let wrapper;
|
||||
const history = createMemoryHistory({
|
||||
initialEntries: [
|
||||
'?credential.page=2&credential.page_size=5&credential.order_by=name',
|
||||
],
|
||||
initialEntries: ['?credential.page=2'],
|
||||
});
|
||||
await act(async () => {
|
||||
wrapper = mountWithContexts(
|
||||
|
||||
@@ -46,8 +46,7 @@ export default function useLaunchSteps(
|
||||
surveyConfig,
|
||||
resource,
|
||||
labels,
|
||||
instanceGroups,
|
||||
resourceDefaultCredentials
|
||||
instanceGroups
|
||||
) {
|
||||
const [visited, setVisited] = useState({});
|
||||
const [isReady, setIsReady] = useState(false);
|
||||
@@ -57,7 +56,7 @@ export default function useLaunchSteps(
|
||||
useCredentialsStep(
|
||||
launchConfig,
|
||||
resource,
|
||||
resourceDefaultCredentials,
|
||||
resource.summary_fields.credentials || [],
|
||||
true
|
||||
),
|
||||
useCredentialPasswordsStep(
|
||||
|
||||
@@ -122,18 +122,6 @@ function sortWeekday(a, b) {
|
||||
}
|
||||
|
||||
function RunOnDetail({ type, options, prefix }) {
|
||||
const weekdays = {
|
||||
sunday: t`Sunday`,
|
||||
monday: t`Monday`,
|
||||
tuesday: t`Tuesday`,
|
||||
wednesday: t`Wednesday`,
|
||||
thursday: t`Thursday`,
|
||||
friday: t`Friday`,
|
||||
saturday: t`Saturday`,
|
||||
day: t`day`,
|
||||
weekday: t`weekday`,
|
||||
weekendDay: t`weekend day`,
|
||||
};
|
||||
if (type === 'month') {
|
||||
if (options.runOn === 'day') {
|
||||
return (
|
||||
@@ -144,16 +132,16 @@ function RunOnDetail({ type, options, prefix }) {
|
||||
/>
|
||||
);
|
||||
}
|
||||
const dayOfWeek = weekdays[options.runOnTheDay];
|
||||
const dayOfWeek = options.runOnTheDay;
|
||||
return (
|
||||
<Detail
|
||||
label={t`Run on`}
|
||||
value={
|
||||
options.runOnTheOccurrence === -1 ? (
|
||||
options.runOnDayNumber === -1 ? (
|
||||
t`The last ${dayOfWeek}`
|
||||
) : (
|
||||
<SelectOrdinal
|
||||
value={options.runOnTheOccurrence}
|
||||
value={options.runOnDayNumber}
|
||||
one={`The first ${dayOfWeek}`}
|
||||
two={`The second ${dayOfWeek}`}
|
||||
_3={`The third ${dayOfWeek}`}
|
||||
@@ -190,6 +178,18 @@ function RunOnDetail({ type, options, prefix }) {
|
||||
/>
|
||||
);
|
||||
}
|
||||
const weekdays = {
|
||||
sunday: t`Sunday`,
|
||||
monday: t`Monday`,
|
||||
tuesday: t`Tuesday`,
|
||||
wednesday: t`Wednesday`,
|
||||
thursday: t`Thursday`,
|
||||
friday: t`Friday`,
|
||||
saturday: t`Saturday`,
|
||||
day: t`day`,
|
||||
weekday: t`weekday`,
|
||||
weekendDay: t`weekend day`,
|
||||
};
|
||||
const weekday = weekdays[options.runOnTheDay];
|
||||
const month = months[options.runOnTheMonth];
|
||||
return (
|
||||
|
||||
@@ -11,8 +11,7 @@ import { JobTemplatesAPI, SchedulesAPI, WorkflowJobTemplatesAPI } from 'api';
|
||||
import { parseVariableField, jsonToYaml } from 'util/yaml';
|
||||
import { useConfig } from 'contexts/Config';
|
||||
import InstanceGroupLabels from 'components/InstanceGroupLabels';
|
||||
import parseRuleObj, { UnsupportedRRuleError } from '../shared/parseRuleObj';
|
||||
import UnsupportedRRuleAlert from '../shared/UnsupportedRRuleAlert';
|
||||
import parseRuleObj from '../shared/parseRuleObj';
|
||||
import FrequencyDetails from './FrequencyDetails';
|
||||
import AlertModal from '../../AlertModal';
|
||||
import { CardBody, CardActionsRow } from '../../Card';
|
||||
@@ -183,20 +182,8 @@ function ScheduleDetail({ hasDaysToKeepField, schedule, surveyConfig }) {
|
||||
month: t`Month`,
|
||||
year: t`Year`,
|
||||
};
|
||||
let rruleError;
|
||||
let frequency = [];
|
||||
let frequencyOptions = {};
|
||||
let exceptionFrequency = [];
|
||||
let exceptionOptions = {};
|
||||
try {
|
||||
({ frequency, frequencyOptions, exceptionFrequency, exceptionOptions } =
|
||||
parseRuleObj(schedule));
|
||||
} catch (parseRuleError) {
|
||||
if (parseRuleError instanceof UnsupportedRRuleError) {
|
||||
rruleError = parseRuleError;
|
||||
}
|
||||
}
|
||||
|
||||
const { frequency, frequencyOptions, exceptionFrequency, exceptionOptions } =
|
||||
parseRuleObj(schedule);
|
||||
const repeatFrequency = frequency.length
|
||||
? frequency.map((f) => frequencies[f]).join(', ')
|
||||
: t`None (Run Once)`;
|
||||
@@ -615,7 +602,6 @@ function ScheduleDetail({ hasDaysToKeepField, schedule, surveyConfig }) {
|
||||
</PromptDetailList>
|
||||
</>
|
||||
)}
|
||||
{rruleError && <UnsupportedRRuleAlert schedule={schedule} />}
|
||||
<CardActionsRow>
|
||||
{summary_fields?.user_capabilities?.edit && (
|
||||
<Button
|
||||
|
||||
@@ -587,31 +587,4 @@ describe('<ScheduleDetail />', () => {
|
||||
(el) => el.prop('isDisabled') === true
|
||||
);
|
||||
});
|
||||
test('should display warning for unsupported recurrence rules ', async () => {
|
||||
const unsupportedSchedule = {
|
||||
...schedule,
|
||||
rrule:
|
||||
'DTSTART:20221220T161500Z RRULE:FREQ=HOURLY;INTERVAL=1 EXRULE:FREQ=HOURLY;INTERVAL=1;BYDAY=TU;BYMONTHDAY=1,2,3,4,5,6,7 EXRULE:FREQ=HOURLY;INTERVAL=1;BYDAY=WE;BYMONTHDAY=2,3,4,5,6,7,8',
|
||||
};
|
||||
await act(async () => {
|
||||
wrapper = mountWithContexts(
|
||||
<Route
|
||||
path="/templates/job_template/:id/schedules/:scheduleId"
|
||||
component={() => <ScheduleDetail schedule={unsupportedSchedule} />}
|
||||
/>,
|
||||
{
|
||||
context: {
|
||||
router: {
|
||||
history,
|
||||
route: {
|
||||
location: history.location,
|
||||
match: { params: { id: 1 } },
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
);
|
||||
});
|
||||
expect(wrapper.find('UnsupportedRRuleAlert').length).toBe(1);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,32 +0,0 @@
|
||||
import React from 'react';
|
||||
import styled from 'styled-components';
|
||||
import { t } from '@lingui/macro';
|
||||
import { Alert } from '@patternfly/react-core';
|
||||
|
||||
const AlertWrapper = styled.div`
|
||||
margin-top: var(--pf-global--spacer--lg);
|
||||
margin-bottom: var(--pf-global--spacer--lg);
|
||||
`;
|
||||
const RulesTitle = styled.p`
|
||||
margin-top: var(--pf-global--spacer--lg);
|
||||
margin-bottom: var(--pf-global--spacer--lg);
|
||||
font-weight: var(--pf-global--FontWeight--bold);
|
||||
`;
|
||||
|
||||
export default function UnsupportedRRuleAlert({ schedule }) {
|
||||
return (
|
||||
<AlertWrapper>
|
||||
<Alert
|
||||
isInline
|
||||
variant="danger"
|
||||
ouiaId="schedule-warning"
|
||||
title={t`This schedule uses complex rules that are not supported in the
|
||||
UI. Please use the API to manage this schedule.`}
|
||||
/>
|
||||
<RulesTitle>{t`Schedule Rules`}:</RulesTitle>
|
||||
<pre css="white-space: pre; font-family: var(--pf-global--FontFamily--monospace)">
|
||||
{schedule.rrule.split(' ').join('\n')}
|
||||
</pre>
|
||||
</AlertWrapper>
|
||||
);
|
||||
}
|
||||
@@ -82,7 +82,11 @@ const frequencyTypes = {
|
||||
};
|
||||
|
||||
function parseRrule(rruleString, schedule, values) {
|
||||
const { frequency, options } = parseRule(rruleString, schedule);
|
||||
const { frequency, options } = parseRule(
|
||||
rruleString,
|
||||
schedule,
|
||||
values.exceptionFrequency
|
||||
);
|
||||
|
||||
if (values.frequencyOptions[frequency]) {
|
||||
throw new UnsupportedRRuleError(
|
||||
@@ -101,7 +105,11 @@ function parseRrule(rruleString, schedule, values) {
|
||||
}
|
||||
|
||||
function parseExRule(exruleString, schedule, values) {
|
||||
const { frequency, options } = parseRule(exruleString, schedule);
|
||||
const { frequency, options } = parseRule(
|
||||
exruleString,
|
||||
schedule,
|
||||
values.exceptionFrequency
|
||||
);
|
||||
|
||||
if (values.exceptionOptions[frequency]) {
|
||||
throw new UnsupportedRRuleError(
|
||||
@@ -121,7 +129,7 @@ function parseExRule(exruleString, schedule, values) {
|
||||
};
|
||||
}
|
||||
|
||||
function parseRule(ruleString, schedule) {
|
||||
function parseRule(ruleString, schedule, frequencies) {
|
||||
const {
|
||||
origOptions: {
|
||||
bymonth,
|
||||
@@ -170,6 +178,9 @@ function parseRule(ruleString, schedule) {
|
||||
throw new Error(`Unexpected rrule frequency: ${freq}`);
|
||||
}
|
||||
const frequency = frequencyTypes[freq];
|
||||
if (frequencies.includes(frequency)) {
|
||||
throw new Error(`Duplicate frequency types not supported (${frequency})`);
|
||||
}
|
||||
|
||||
if (freq === RRule.WEEKLY && byweekday) {
|
||||
options.daysOfWeek = byweekday;
|
||||
|
||||
@@ -91,11 +91,6 @@ function CredentialEdit({ credential }) {
|
||||
modifiedData.user = me.id;
|
||||
}
|
||||
}
|
||||
|
||||
if (credential.kind === 'vault' && !credential.inputs?.vault_id) {
|
||||
delete modifiedData.inputs.vault_id;
|
||||
}
|
||||
|
||||
const [{ data }] = await Promise.all([
|
||||
CredentialsAPI.update(credId, modifiedData),
|
||||
...destroyInputSources(),
|
||||
@@ -105,7 +100,7 @@ function CredentialEdit({ credential }) {
|
||||
|
||||
return data;
|
||||
},
|
||||
[me, credId, credential]
|
||||
[me, credId]
|
||||
)
|
||||
);
|
||||
|
||||
|
||||
@@ -47,14 +47,35 @@ class ItemNotDefined(Exception):
|
||||
class ControllerModule(AnsibleModule):
|
||||
url = None
|
||||
AUTH_ARGSPEC = dict(
|
||||
controller_host=dict(required=False, aliases=['tower_host'], fallback=(env_fallback, ['CONTROLLER_HOST', 'TOWER_HOST'])),
|
||||
controller_username=dict(required=False, aliases=['tower_username'], fallback=(env_fallback, ['CONTROLLER_USERNAME', 'TOWER_USERNAME'])),
|
||||
controller_password=dict(no_log=True, aliases=['tower_password'], required=False, fallback=(env_fallback, ['CONTROLLER_PASSWORD', 'TOWER_PASSWORD'])),
|
||||
validate_certs=dict(type='bool', aliases=['tower_verify_ssl'], required=False, fallback=(env_fallback, ['CONTROLLER_VERIFY_SSL', 'TOWER_VERIFY_SSL'])),
|
||||
controller_host=dict(
|
||||
required=False,
|
||||
aliases=['tower_host'],
|
||||
fallback=(env_fallback, ['CONTROLLER_HOST', 'TOWER_HOST'])),
|
||||
controller_username=dict(
|
||||
required=False,
|
||||
aliases=['tower_username'],
|
||||
fallback=(env_fallback, ['CONTROLLER_USERNAME', 'TOWER_USERNAME'])),
|
||||
controller_password=dict(
|
||||
no_log=True,
|
||||
aliases=['tower_password'],
|
||||
required=False,
|
||||
fallback=(env_fallback, ['CONTROLLER_PASSWORD', 'TOWER_PASSWORD'])),
|
||||
validate_certs=dict(
|
||||
type='bool',
|
||||
aliases=['tower_verify_ssl'],
|
||||
required=False,
|
||||
fallback=(env_fallback, ['CONTROLLER_VERIFY_SSL', 'TOWER_VERIFY_SSL'])),
|
||||
controller_oauthtoken=dict(
|
||||
type='raw', no_log=True, aliases=['tower_oauthtoken'], required=False, fallback=(env_fallback, ['CONTROLLER_OAUTH_TOKEN', 'TOWER_OAUTH_TOKEN'])
|
||||
),
|
||||
controller_config_file=dict(type='path', aliases=['tower_config_file'], required=False, default=None),
|
||||
type='raw',
|
||||
no_log=True,
|
||||
aliases=['tower_oauthtoken'],
|
||||
required=False,
|
||||
fallback=(env_fallback, ['CONTROLLER_OAUTH_TOKEN', 'TOWER_OAUTH_TOKEN'])),
|
||||
controller_config_file=dict(
|
||||
type='path',
|
||||
aliases=['tower_config_file'],
|
||||
required=False,
|
||||
default=None),
|
||||
)
|
||||
short_params = {
|
||||
'host': 'controller_host',
|
||||
@@ -299,7 +320,9 @@ class ControllerAPIModule(ControllerModule):
|
||||
def __init__(self, argument_spec, direct_params=None, error_callback=None, warn_callback=None, **kwargs):
|
||||
kwargs['supports_check_mode'] = True
|
||||
|
||||
super().__init__(argument_spec=argument_spec, direct_params=direct_params, error_callback=error_callback, warn_callback=warn_callback, **kwargs)
|
||||
super().__init__(
|
||||
argument_spec=argument_spec, direct_params=direct_params, error_callback=error_callback, warn_callback=warn_callback, **kwargs
|
||||
)
|
||||
self.session = Request(cookies=CookieJar(), validate_certs=self.verify_ssl)
|
||||
|
||||
if 'update_secrets' in self.params:
|
||||
@@ -307,6 +330,11 @@ class ControllerAPIModule(ControllerModule):
|
||||
else:
|
||||
self.update_secrets = True
|
||||
|
||||
@staticmethod
|
||||
def param_to_endpoint(name):
|
||||
exceptions = {'inventory': 'inventories', 'target_team': 'teams', 'workflow': 'workflow_job_templates'}
|
||||
return exceptions.get(name, '{0}s'.format(name))
|
||||
|
||||
@staticmethod
|
||||
def get_name_field_from_endpoint(endpoint):
|
||||
return ControllerAPIModule.IDENTITY_FIELDS.get(endpoint, 'name')
|
||||
@@ -377,7 +405,7 @@ class ControllerAPIModule(ControllerModule):
|
||||
response['json']['next'] = next_page
|
||||
return response
|
||||
|
||||
def get_one(self, endpoint, name_or_id=None, allow_none=True, check_exists=False, **kwargs):
|
||||
def get_one(self, endpoint, name_or_id=None, allow_none=True, **kwargs):
|
||||
new_kwargs = kwargs.copy()
|
||||
if name_or_id:
|
||||
name_field = self.get_name_field_from_endpoint(endpoint)
|
||||
@@ -418,11 +446,6 @@ class ControllerAPIModule(ControllerModule):
|
||||
# Or we weren't running with a or search and just got back too many to begin with.
|
||||
self.fail_wanted_one(response, endpoint, new_kwargs.get('data'))
|
||||
|
||||
if check_exists:
|
||||
name_field = self.get_name_field_from_endpoint(endpoint)
|
||||
self.json_output['id'] = response['json']['results'][0]['id']
|
||||
self.exit_json(**self.json_output)
|
||||
|
||||
return response['json']['results'][0]
|
||||
|
||||
def fail_wanted_one(self, response, endpoint, query_params):
|
||||
@@ -430,8 +453,7 @@ class ControllerAPIModule(ControllerModule):
|
||||
if len(sample['json']['results']) > 1:
|
||||
sample['json']['results'] = sample['json']['results'][:2] + ['...more results snipped...']
|
||||
url = self.build_url(endpoint, query_params)
|
||||
host_length = len(self.host)
|
||||
display_endpoint = url.geturl()[host_length:] # truncate to not include the base URL
|
||||
display_endpoint = url.geturl()[len(self.host):] # truncate to not include the base URL
|
||||
self.fail_json(
|
||||
msg="Request to {0} returned {1} items, expected 1".format(display_endpoint, response['json']['count']),
|
||||
query=query_params,
|
||||
@@ -953,7 +975,11 @@ class ControllerAPIModule(ControllerModule):
|
||||
# Attempt to delete our current token from /api/v2/tokens/
|
||||
# Post to the tokens endpoint with baisc auth to try and get a token
|
||||
endpoint = self.url_prefix.rstrip('/') + '/api/v2/tokens/{0}/'.format(self.oauth_token_id)
|
||||
api_token_url = (self.url._replace(path=endpoint, query=None)).geturl() # in error cases, fail_json exists before exception handling
|
||||
api_token_url = (
|
||||
self.url._replace(
|
||||
path=endpoint, query=None # in error cases, fail_json exists before exception handling
|
||||
)
|
||||
).geturl()
|
||||
|
||||
try:
|
||||
self.session.open(
|
||||
|
||||
@@ -60,7 +60,7 @@ options:
|
||||
description:
|
||||
- Desired state of the resource.
|
||||
default: "present"
|
||||
choices: ["present", "absent", "exists"]
|
||||
choices: ["present", "absent"]
|
||||
type: str
|
||||
skip_authorization:
|
||||
description:
|
||||
@@ -106,7 +106,7 @@ def main():
|
||||
client_type=dict(choices=['public', 'confidential']),
|
||||
organization=dict(required=True),
|
||||
redirect_uris=dict(type="list", elements='str'),
|
||||
state=dict(choices=['present', 'absent', 'exists'], default='present'),
|
||||
state=dict(choices=['present', 'absent'], default='present'),
|
||||
skip_authorization=dict(type='bool'),
|
||||
)
|
||||
|
||||
@@ -127,7 +127,7 @@ def main():
|
||||
org_id = module.resolve_name_to_id('organizations', organization)
|
||||
|
||||
# Attempt to look up application based on the provided name and org ID
|
||||
application = module.get_one('applications', name_or_id=name, check_exists=(state == 'exists'), **{'data': {'organization': org_id}})
|
||||
application = module.get_one('applications', name_or_id=name, **{'data': {'organization': org_id}})
|
||||
|
||||
if state == 'absent':
|
||||
# If the state was absent we can let the module delete it if needed, the module will handle exiting from this
|
||||
|
||||
@@ -247,7 +247,7 @@ def main():
|
||||
if organization:
|
||||
lookup_data['organization'] = org_id
|
||||
|
||||
credential = module.get_one('credentials', name_or_id=name, check_exists=(state == 'exists'), **{'data': lookup_data})
|
||||
credential = module.get_one('credentials', name_or_id=name, **{'data': lookup_data})
|
||||
|
||||
# Attempt to look up credential to copy based on the provided name
|
||||
if copy_from:
|
||||
@@ -265,6 +265,10 @@ def main():
|
||||
# If the state was absent we can let the module delete it if needed, the module will handle exiting from this
|
||||
module.delete_if_needed(credential)
|
||||
|
||||
if state == 'exists' and credential is not None:
|
||||
# If credential exists and state is exists, we're done here.
|
||||
module.exit_json(**module.json_output)
|
||||
|
||||
# Attempt to look up the related items the user specified (these will fail the module if not found)
|
||||
if user:
|
||||
user_id = module.resolve_name_to_id('users', user)
|
||||
|
||||
@@ -48,7 +48,7 @@ options:
|
||||
state:
|
||||
description:
|
||||
- Desired state of the resource.
|
||||
choices: ["present", "absent", "exists"]
|
||||
choices: ["present", "absent"]
|
||||
default: "present"
|
||||
type: str
|
||||
|
||||
@@ -80,7 +80,7 @@ def main():
|
||||
target_credential=dict(required=True),
|
||||
source_credential=dict(),
|
||||
metadata=dict(type="dict"),
|
||||
state=dict(choices=['present', 'absent', 'exists'], default='present'),
|
||||
state=dict(choices=['present', 'absent'], default='present'),
|
||||
)
|
||||
|
||||
# Create a module for ourselves
|
||||
@@ -101,7 +101,7 @@ def main():
|
||||
'target_credential': target_credential_id,
|
||||
'input_field_name': input_field_name,
|
||||
}
|
||||
credential_input_source = module.get_one('credential_input_sources', check_exists=(state == 'exists'), **{'data': lookup_data})
|
||||
credential_input_source = module.get_one('credential_input_sources', **{'data': lookup_data})
|
||||
|
||||
if state == 'absent':
|
||||
module.delete_if_needed(credential_input_source)
|
||||
|
||||
@@ -59,7 +59,7 @@ options:
|
||||
description:
|
||||
- Desired state of the resource.
|
||||
default: "present"
|
||||
choices: ["present", "absent", "exists"]
|
||||
choices: ["present", "absent"]
|
||||
type: str
|
||||
extends_documentation_fragment: awx.awx.auth
|
||||
'''
|
||||
@@ -98,7 +98,7 @@ def main():
|
||||
kind=dict(choices=list(KIND_CHOICES.keys())),
|
||||
inputs=dict(type='dict'),
|
||||
injectors=dict(type='dict'),
|
||||
state=dict(choices=['present', 'absent', 'exists'], default='present'),
|
||||
state=dict(choices=['present', 'absent'], default='present'),
|
||||
)
|
||||
|
||||
# Create a module for ourselves
|
||||
@@ -124,7 +124,7 @@ def main():
|
||||
credential_type_params['injectors'] = module.params.get('injectors')
|
||||
|
||||
# Attempt to look up credential_type based on the provided name
|
||||
credential_type = module.get_one('credential_types', name_or_id=name, check_exists=(state == 'exists'))
|
||||
credential_type = module.get_one('credential_types', name_or_id=name)
|
||||
|
||||
if state == 'absent':
|
||||
# If the state was absent we can let the module delete it if needed, the module will handle exiting from this
|
||||
|
||||
@@ -50,7 +50,7 @@ options:
|
||||
state:
|
||||
description:
|
||||
- Desired state of the resource.
|
||||
choices: ["present", "absent", "exists"]
|
||||
choices: ["present", "absent"]
|
||||
default: "present"
|
||||
type: str
|
||||
pull:
|
||||
@@ -83,7 +83,7 @@ def main():
|
||||
description=dict(),
|
||||
organization=dict(),
|
||||
credential=dict(),
|
||||
state=dict(choices=['present', 'absent', 'exists'], default='present'),
|
||||
state=dict(choices=['present', 'absent'], default='present'),
|
||||
# NOTE: Default for pull differs from API (which is blank by default)
|
||||
pull=dict(choices=['always', 'missing', 'never'], default='missing'),
|
||||
)
|
||||
@@ -99,7 +99,7 @@ def main():
|
||||
state = module.params.get('state')
|
||||
pull = module.params.get('pull')
|
||||
|
||||
existing_item = module.get_one('execution_environments', name_or_id=name, check_exists=(state == 'exists'))
|
||||
existing_item = module.get_one('execution_environments', name_or_id=name)
|
||||
|
||||
if state == 'absent':
|
||||
module.delete_if_needed(existing_item)
|
||||
|
||||
@@ -67,7 +67,7 @@ options:
|
||||
description:
|
||||
- Desired state of the resource.
|
||||
default: "present"
|
||||
choices: ["present", "absent", "exists"]
|
||||
choices: ["present", "absent"]
|
||||
type: str
|
||||
new_name:
|
||||
description:
|
||||
@@ -115,7 +115,7 @@ def main():
|
||||
children=dict(type='list', elements='str', aliases=['groups']),
|
||||
preserve_existing_hosts=dict(type='bool', default=False),
|
||||
preserve_existing_children=dict(type='bool', default=False, aliases=['preserve_existing_groups']),
|
||||
state=dict(choices=['present', 'absent', 'exists'], default='present'),
|
||||
state=dict(choices=['present', 'absent'], default='present'),
|
||||
)
|
||||
|
||||
# Create a module for ourselves
|
||||
@@ -135,7 +135,7 @@ def main():
|
||||
inventory_id = module.resolve_name_to_id('inventories', inventory)
|
||||
|
||||
# Attempt to look up the object based on the provided name and inventory ID
|
||||
group = module.get_one('groups', name_or_id=name, check_exists=(state == 'exists'), **{'data': {'inventory': inventory_id}})
|
||||
group = module.get_one('groups', name_or_id=name, **{'data': {'inventory': inventory_id}})
|
||||
|
||||
if state == 'absent':
|
||||
# If the state was absent we can let the module delete it if needed, the module will handle exiting from this
|
||||
|
||||
@@ -50,7 +50,7 @@ options:
|
||||
state:
|
||||
description:
|
||||
- Desired state of the resource.
|
||||
choices: ["present", "absent", "exists"]
|
||||
choices: ["present", "absent"]
|
||||
default: "present"
|
||||
type: str
|
||||
extends_documentation_fragment: awx.awx.auth
|
||||
@@ -83,7 +83,7 @@ def main():
|
||||
inventory=dict(required=True),
|
||||
enabled=dict(type='bool'),
|
||||
variables=dict(type='dict'),
|
||||
state=dict(choices=['present', 'absent', 'exists'], default='present'),
|
||||
state=dict(choices=['present', 'absent'], default='present'),
|
||||
)
|
||||
|
||||
# Create a module for ourselves
|
||||
@@ -102,7 +102,7 @@ def main():
|
||||
inventory_id = module.resolve_name_to_id('inventories', inventory)
|
||||
|
||||
# Attempt to look up host based on the provided name and inventory ID
|
||||
host = module.get_one('hosts', name_or_id=name, check_exists=(state == 'exists'), **{'data': {'inventory': inventory_id}})
|
||||
host = module.get_one('hosts', name_or_id=name, **{'data': {'inventory': inventory_id}})
|
||||
|
||||
if state == 'absent':
|
||||
# If the state was absent we can let the module delete it if needed, the module will handle exiting from this
|
||||
|
||||
@@ -81,7 +81,7 @@ options:
|
||||
state:
|
||||
description:
|
||||
- Desired state of the resource.
|
||||
choices: ["present", "absent", "exists"]
|
||||
choices: ["present", "absent"]
|
||||
default: "present"
|
||||
type: str
|
||||
extends_documentation_fragment: awx.awx.auth
|
||||
@@ -107,7 +107,7 @@ def main():
|
||||
policy_instance_list=dict(type='list', elements='str'),
|
||||
pod_spec_override=dict(),
|
||||
instances=dict(required=False, type="list", elements='str'),
|
||||
state=dict(choices=['present', 'absent', 'exists'], default='present'),
|
||||
state=dict(choices=['present', 'absent'], default='present'),
|
||||
)
|
||||
|
||||
# Create a module for ourselves
|
||||
@@ -128,7 +128,7 @@ def main():
|
||||
state = module.params.get('state')
|
||||
|
||||
# Attempt to look up an existing item based on the provided data
|
||||
existing_item = module.get_one('instance_groups', name_or_id=name, check_exists=(state == 'exists'))
|
||||
existing_item = module.get_one('instance_groups', name_or_id=name)
|
||||
|
||||
if state == 'absent':
|
||||
# If the state was absent we can let the module delete it if needed, the module will handle exiting from this
|
||||
|
||||
@@ -78,7 +78,7 @@ options:
|
||||
description:
|
||||
- Desired state of the resource.
|
||||
default: "present"
|
||||
choices: ["present", "absent", "exists"]
|
||||
choices: ["present", "absent"]
|
||||
type: str
|
||||
extends_documentation_fragment: awx.awx.auth
|
||||
'''
|
||||
@@ -149,7 +149,7 @@ def main():
|
||||
host_filter=dict(),
|
||||
instance_groups=dict(type="list", elements='str'),
|
||||
prevent_instance_group_fallback=dict(type='bool'),
|
||||
state=dict(choices=['present', 'absent', 'exists'], default='present'),
|
||||
state=dict(choices=['present', 'absent'], default='present'),
|
||||
input_inventories=dict(type='list', elements='str'),
|
||||
)
|
||||
|
||||
@@ -172,7 +172,7 @@ def main():
|
||||
org_id = module.resolve_name_to_id('organizations', organization)
|
||||
|
||||
# Attempt to look up inventory based on the provided name and org ID
|
||||
inventory = module.get_one('inventories', name_or_id=name, check_exists=(state == 'exists'), **{'data': {'organization': org_id}})
|
||||
inventory = module.get_one('inventories', name_or_id=name, **{'data': {'organization': org_id}})
|
||||
|
||||
# Attempt to look up credential to copy based on the provided name
|
||||
if copy_from:
|
||||
|
||||
@@ -118,7 +118,7 @@ options:
|
||||
description:
|
||||
- Desired state of the resource.
|
||||
default: "present"
|
||||
choices: ["present", "absent", "exists"]
|
||||
choices: ["present", "absent"]
|
||||
type: str
|
||||
notification_templates_started:
|
||||
description:
|
||||
@@ -192,7 +192,7 @@ def main():
|
||||
notification_templates_started=dict(type="list", elements='str'),
|
||||
notification_templates_success=dict(type="list", elements='str'),
|
||||
notification_templates_error=dict(type="list", elements='str'),
|
||||
state=dict(choices=['present', 'absent', 'exists'], default='present'),
|
||||
state=dict(choices=['present', 'absent'], default='present'),
|
||||
)
|
||||
|
||||
# Create a module for ourselves
|
||||
@@ -219,7 +219,6 @@ def main():
|
||||
inventory_source_object = module.get_one(
|
||||
'inventory_sources',
|
||||
name_or_id=name,
|
||||
check_exists=(state == 'exists'),
|
||||
**{
|
||||
'data': {
|
||||
'inventory': inventory_object['id'],
|
||||
|
||||
@@ -295,7 +295,7 @@ options:
|
||||
description:
|
||||
- Desired state of the resource.
|
||||
default: "present"
|
||||
choices: ["present", "absent", "exists"]
|
||||
choices: ["present", "absent"]
|
||||
type: str
|
||||
notification_templates_started:
|
||||
description:
|
||||
@@ -444,7 +444,7 @@ def main():
|
||||
notification_templates_success=dict(type="list", elements='str'),
|
||||
notification_templates_error=dict(type="list", elements='str'),
|
||||
prevent_instance_group_fallback=dict(type="bool"),
|
||||
state=dict(choices=['present', 'absent', 'exists'], default='present'),
|
||||
state=dict(choices=['present', 'absent'], default='present'),
|
||||
)
|
||||
|
||||
# Create a module for ourselves
|
||||
@@ -484,7 +484,7 @@ def main():
|
||||
new_fields['execution_environment'] = module.resolve_name_to_id('execution_environments', ee)
|
||||
|
||||
# Attempt to look up an existing item based on the provided data
|
||||
existing_item = module.get_one('job_templates', name_or_id=name, check_exists=(state == 'exists'), **{'data': search_fields})
|
||||
existing_item = module.get_one('job_templates', name_or_id=name, **{'data': search_fields})
|
||||
|
||||
# Attempt to look up credential to copy based on the provided name
|
||||
if copy_from:
|
||||
|
||||
@@ -41,7 +41,7 @@ options:
|
||||
description:
|
||||
- Desired state of the resource.
|
||||
default: "present"
|
||||
choices: ["present", "exists"]
|
||||
choices: ["present"]
|
||||
type: str
|
||||
extends_documentation_fragment: awx.awx.auth
|
||||
'''
|
||||
@@ -62,7 +62,7 @@ def main():
|
||||
name=dict(required=True),
|
||||
new_name=dict(),
|
||||
organization=dict(required=True),
|
||||
state=dict(choices=['present', 'exists'], default='present'),
|
||||
state=dict(choices=['present'], default='present'),
|
||||
)
|
||||
|
||||
# Create a module for ourselves
|
||||
@@ -72,7 +72,6 @@ def main():
|
||||
name = module.params.get('name')
|
||||
new_name = module.params.get("new_name")
|
||||
organization = module.params.get('organization')
|
||||
state = module.params.get("state")
|
||||
|
||||
# Attempt to look up the related items the user specified (these will fail the module if not found)
|
||||
organization_id = None
|
||||
@@ -83,7 +82,6 @@ def main():
|
||||
existing_item = module.get_one(
|
||||
'labels',
|
||||
name_or_id=name,
|
||||
check_exists=(state == 'exists'),
|
||||
**{
|
||||
'data': {
|
||||
'organization': organization_id,
|
||||
|
||||
@@ -97,7 +97,7 @@ options:
|
||||
description:
|
||||
- Desired state of the resource.
|
||||
default: "present"
|
||||
choices: ["present", "absent", "exists"]
|
||||
choices: ["present", "absent"]
|
||||
type: str
|
||||
extends_documentation_fragment: awx.awx.auth
|
||||
'''
|
||||
@@ -222,7 +222,7 @@ def main():
|
||||
notification_type=dict(choices=['email', 'grafana', 'irc', 'mattermost', 'pagerduty', 'rocketchat', 'slack', 'twilio', 'webhook']),
|
||||
notification_configuration=dict(type='dict'),
|
||||
messages=dict(type='dict'),
|
||||
state=dict(choices=['present', 'absent', 'exists'], default='present'),
|
||||
state=dict(choices=['present', 'absent'], default='present'),
|
||||
)
|
||||
|
||||
# Create a module for ourselves
|
||||
@@ -248,7 +248,6 @@ def main():
|
||||
existing_item = module.get_one(
|
||||
'notification_templates',
|
||||
name_or_id=name,
|
||||
check_exists=(state == 'exists'),
|
||||
**{
|
||||
'data': {
|
||||
'organization': organization_id,
|
||||
|
||||
@@ -52,7 +52,7 @@ options:
|
||||
description:
|
||||
- Desired state of the resource.
|
||||
default: "present"
|
||||
choices: ["present", "absent", "exists"]
|
||||
choices: ["present", "absent"]
|
||||
type: str
|
||||
instance_groups:
|
||||
description:
|
||||
@@ -130,7 +130,7 @@ def main():
|
||||
notification_templates_error=dict(type="list", elements='str'),
|
||||
notification_templates_approvals=dict(type="list", elements='str'),
|
||||
galaxy_credentials=dict(type="list", elements='str'),
|
||||
state=dict(choices=['present', 'absent', 'exists'], default='present'),
|
||||
state=dict(choices=['present', 'absent'], default='present'),
|
||||
)
|
||||
|
||||
# Create a module for ourselves
|
||||
@@ -146,7 +146,7 @@ def main():
|
||||
state = module.params.get('state')
|
||||
|
||||
# Attempt to look up organization based on the provided name
|
||||
organization = module.get_one('organizations', name_or_id=name, check_exists=(state == 'exists'))
|
||||
organization = module.get_one('organizations', name_or_id=name)
|
||||
|
||||
if state == 'absent':
|
||||
# If the state was absent we can let the module delete it if needed, the module will handle exiting from this
|
||||
|
||||
@@ -122,7 +122,7 @@ options:
|
||||
description:
|
||||
- Desired state of the resource.
|
||||
default: "present"
|
||||
choices: ["present", "absent", "exists"]
|
||||
choices: ["present", "absent"]
|
||||
type: str
|
||||
wait:
|
||||
description:
|
||||
@@ -272,7 +272,7 @@ def main():
|
||||
notification_templates_started=dict(type="list", elements='str'),
|
||||
notification_templates_success=dict(type="list", elements='str'),
|
||||
notification_templates_error=dict(type="list", elements='str'),
|
||||
state=dict(choices=['present', 'absent', 'exists'], default='present'),
|
||||
state=dict(choices=['present', 'absent'], default='present'),
|
||||
wait=dict(type='bool', default=True),
|
||||
update_project=dict(default=False, type='bool'),
|
||||
interval=dict(default=2.0, type='float'),
|
||||
@@ -313,7 +313,7 @@ def main():
|
||||
lookup_data['organization'] = org_id
|
||||
|
||||
# Attempt to look up project based on the provided name and org ID
|
||||
project = module.get_one('projects', name_or_id=name, check_exists=(state == 'exists'), data=lookup_data)
|
||||
project = module.get_one('projects', name_or_id=name, data=lookup_data)
|
||||
|
||||
# Attempt to look up credential to copy based on the provided name
|
||||
if copy_from:
|
||||
|
||||
@@ -24,23 +24,11 @@ options:
|
||||
user:
|
||||
description:
|
||||
- User that receives the permissions specified by the role.
|
||||
- Deprecated, use 'users'.
|
||||
type: str
|
||||
users:
|
||||
description:
|
||||
- Users that receive the permissions specified by the role.
|
||||
type: list
|
||||
elements: str
|
||||
team:
|
||||
description:
|
||||
- Team that receives the permissions specified by the role.
|
||||
- Deprecated, use 'teams'.
|
||||
type: str
|
||||
teams:
|
||||
description:
|
||||
- Teams that receive the permissions specified by the role.
|
||||
type: list
|
||||
elements: str
|
||||
role:
|
||||
description:
|
||||
- The role type to grant/revoke.
|
||||
@@ -173,9 +161,7 @@ def main():
|
||||
|
||||
argument_spec = dict(
|
||||
user=dict(),
|
||||
users=dict(type='list', elements='str'),
|
||||
team=dict(),
|
||||
teams=dict(type='list', elements='str'),
|
||||
role=dict(
|
||||
choices=[
|
||||
"admin",
|
||||
@@ -233,9 +219,9 @@ def main():
|
||||
'projects': 'project',
|
||||
'target_teams': 'target_team',
|
||||
'workflows': 'workflow',
|
||||
'users': 'user',
|
||||
'teams': 'team',
|
||||
}
|
||||
# Singular parameters
|
||||
resource_param_keys = ('user', 'team', 'lookup_organization')
|
||||
|
||||
resources = {}
|
||||
for resource_group, old_name in resource_list_param_keys.items():
|
||||
@@ -243,9 +229,9 @@ def main():
|
||||
resources.setdefault(resource_group, []).extend(module.params.get(resource_group))
|
||||
if module.params.get(old_name) is not None:
|
||||
resources.setdefault(resource_group, []).append(module.params.get(old_name))
|
||||
if module.params.get('lookup_organization') is not None:
|
||||
resources['lookup_organization'] = module.params.get('lookup_organization')
|
||||
|
||||
for resource_group in resource_param_keys:
|
||||
if module.params.get(resource_group) is not None:
|
||||
resources[resource_group] = module.params.get(resource_group)
|
||||
# Change workflows and target_teams key to its endpoint name.
|
||||
if 'workflows' in resources:
|
||||
resources['workflow_job_templates'] = resources.pop('workflows')
|
||||
@@ -262,13 +248,28 @@ def main():
|
||||
# separate actors from resources
|
||||
actor_data = {}
|
||||
missing_items = []
|
||||
for key in ('user', 'team'):
|
||||
if key in resources:
|
||||
if key == 'user':
|
||||
lookup_data_populated = {}
|
||||
else:
|
||||
lookup_data_populated = lookup_data
|
||||
# Attempt to look up project based on the provided name or ID and lookup data
|
||||
data = module.get_one('{0}s'.format(key), name_or_id=resources[key], data=lookup_data_populated)
|
||||
if data is None:
|
||||
module.fail_json(
|
||||
msg='Unable to find {0} with name: {1}'.format(key, resources[key]), changed=False
|
||||
)
|
||||
else:
|
||||
actor_data[key] = module.get_one('{0}s'.format(key), name_or_id=resources[key], data=lookup_data_populated)
|
||||
resources.pop(key)
|
||||
# Lookup Resources
|
||||
resource_data = {}
|
||||
for key, value in resources.items():
|
||||
for resource in value:
|
||||
# Attempt to look up project based on the provided name or ID and lookup data
|
||||
if key in resources:
|
||||
if key == 'organizations' or key == 'users':
|
||||
if key == 'organizations':
|
||||
lookup_data_populated = {}
|
||||
else:
|
||||
lookup_data_populated = lookup_data
|
||||
@@ -276,18 +277,14 @@ def main():
|
||||
if data is None:
|
||||
missing_items.append(resource)
|
||||
else:
|
||||
if key == 'users' or key == 'teams':
|
||||
actor_data.setdefault(key, []).append(data)
|
||||
else:
|
||||
resource_data.setdefault(key, []).append(data)
|
||||
resource_data.setdefault(key, []).append(data)
|
||||
if len(missing_items) > 0:
|
||||
module.fail_json(
|
||||
msg='There were {0} missing items, missing items: {1}'.format(len(missing_items), missing_items), changed=False
|
||||
)
|
||||
|
||||
# build association agenda
|
||||
associations = {}
|
||||
for actor_type, actors in actor_data.items():
|
||||
for actor_type, actor in actor_data.items():
|
||||
for key, value in resource_data.items():
|
||||
for resource in value:
|
||||
resource_roles = resource['summary_fields']['object_roles']
|
||||
@@ -297,10 +294,9 @@ def main():
|
||||
msg='Resource {0} has no role {1}, available roles: {2}'.format(resource['url'], role_field, available_roles), changed=False
|
||||
)
|
||||
role_data = resource_roles[role_field]
|
||||
endpoint = '/roles/{0}/{1}/'.format(role_data['id'], actor_type)
|
||||
endpoint = '/roles/{0}/{1}/'.format(role_data['id'], module.param_to_endpoint(actor_type))
|
||||
associations.setdefault(endpoint, [])
|
||||
for actor in actors:
|
||||
associations[endpoint].append(actor['id'])
|
||||
associations[endpoint].append(actor['id'])
|
||||
|
||||
# perform associations
|
||||
for association_endpoint, new_association_list in associations.items():
|
||||
|
||||
@@ -146,7 +146,7 @@ options:
|
||||
state:
|
||||
description:
|
||||
- Desired state of the resource.
|
||||
choices: ["present", "absent", "exists"]
|
||||
choices: ["present", "absent"]
|
||||
default: "present"
|
||||
type: str
|
||||
extends_documentation_fragment: awx.awx.auth
|
||||
@@ -220,7 +220,7 @@ def main():
|
||||
unified_job_template=dict(),
|
||||
organization=dict(),
|
||||
enabled=dict(type='bool'),
|
||||
state=dict(choices=['present', 'absent', 'exists'], default='present'),
|
||||
state=dict(choices=['present', 'absent'], default='present'),
|
||||
)
|
||||
|
||||
# Create a module for ourselves
|
||||
@@ -265,13 +265,8 @@ def main():
|
||||
search_fields['name'] = unified_job_template
|
||||
unified_job_template_id = module.get_one('unified_job_templates', **{'data': search_fields})['id']
|
||||
sched_search_fields['unified_job_template'] = unified_job_template_id
|
||||
|
||||
# Attempt to look up an existing item based on the provided data
|
||||
existing_item = module.get_one('schedules', name_or_id=name, check_exists=(state == 'exists'), **{'data': sched_search_fields})
|
||||
|
||||
if state == 'absent':
|
||||
# If the state was absent we can let the module delete it if needed, the module will handle exiting from this
|
||||
module.delete_if_needed(existing_item)
|
||||
existing_item = module.get_one('schedules', name_or_id=name, **{'data': sched_search_fields})
|
||||
|
||||
association_fields = {}
|
||||
|
||||
@@ -348,14 +343,18 @@ def main():
|
||||
else:
|
||||
new_fields['execution_environment'] = ee['id']
|
||||
|
||||
# If the state was present and we can let the module build or update the existing item, this will return on its own
|
||||
module.create_or_update_if_needed(
|
||||
existing_item,
|
||||
new_fields,
|
||||
endpoint='schedules',
|
||||
item_type='schedule',
|
||||
associations=association_fields,
|
||||
)
|
||||
if state == 'absent':
|
||||
# If the state was absent we can let the module delete it if needed, the module will handle exiting from this
|
||||
module.delete_if_needed(existing_item)
|
||||
elif state == 'present':
|
||||
# If the state was present and we can let the module build or update the existing item, this will return on its own
|
||||
module.create_or_update_if_needed(
|
||||
existing_item,
|
||||
new_fields,
|
||||
endpoint='schedules',
|
||||
item_type='schedule',
|
||||
associations=association_fields,
|
||||
)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
@@ -42,7 +42,7 @@ options:
|
||||
state:
|
||||
description:
|
||||
- Desired state of the resource.
|
||||
choices: ["present", "absent", "exists"]
|
||||
choices: ["present", "absent"]
|
||||
default: "present"
|
||||
type: str
|
||||
extends_documentation_fragment: awx.awx.auth
|
||||
@@ -69,7 +69,7 @@ def main():
|
||||
new_name=dict(),
|
||||
description=dict(),
|
||||
organization=dict(required=True),
|
||||
state=dict(choices=['present', 'absent', 'exists'], default='present'),
|
||||
state=dict(choices=['present', 'absent'], default='present'),
|
||||
)
|
||||
|
||||
# Create a module for ourselves
|
||||
@@ -86,7 +86,7 @@ def main():
|
||||
org_id = module.resolve_name_to_id('organizations', organization)
|
||||
|
||||
# Attempt to look up team based on the provided name and org ID
|
||||
team = module.get_one('teams', name_or_id=name, check_exists=(state == 'exists'), **{'data': {'organization': org_id}})
|
||||
team = module.get_one('teams', name_or_id=name, **{'data': {'organization': org_id}})
|
||||
|
||||
if state == 'absent':
|
||||
# If the state was absent we can let the module delete it if needed, the module will handle exiting from this
|
||||
|
||||
@@ -69,7 +69,7 @@ options:
|
||||
state:
|
||||
description:
|
||||
- Desired state of the resource.
|
||||
choices: ["present", "absent", "exists"]
|
||||
choices: ["present", "absent"]
|
||||
default: "present"
|
||||
type: str
|
||||
extends_documentation_fragment: awx.awx.auth
|
||||
@@ -137,7 +137,7 @@ def main():
|
||||
password=dict(no_log=True),
|
||||
update_secrets=dict(type='bool', default=True, no_log=False),
|
||||
organization=dict(),
|
||||
state=dict(choices=['present', 'absent', 'exists'], default='present'),
|
||||
state=dict(choices=['present', 'absent'], default='present'),
|
||||
)
|
||||
|
||||
# Create a module for ourselves
|
||||
@@ -158,7 +158,7 @@ def main():
|
||||
# Attempt to look up the related items the user specified (these will fail the module if not found)
|
||||
|
||||
# Attempt to look up an existing item based on the provided data
|
||||
existing_item = module.get_one('users', name_or_id=username, check_exists=(state == 'exists'))
|
||||
existing_item = module.get_one('users', name_or_id=username)
|
||||
|
||||
if state == 'absent':
|
||||
# If the state was absent we can let the module delete it if needed, the module will handle exiting from this
|
||||
|
||||
@@ -144,7 +144,6 @@ options:
|
||||
choices:
|
||||
- present
|
||||
- absent
|
||||
- exists
|
||||
default: "present"
|
||||
type: str
|
||||
notification_templates_started:
|
||||
@@ -668,7 +667,8 @@ def create_workflow_nodes(module, response, workflow_nodes, workflow_id):
|
||||
inv_lookup_data = {}
|
||||
if 'organization' in workflow_node['inventory']:
|
||||
inv_lookup_data['organization'] = module.resolve_name_to_id('organizations', workflow_node['inventory']['organization']['name'])
|
||||
workflow_node_fields['inventory'] = module.get_one('inventories', name_or_id=workflow_node['inventory']['name'], data=inv_lookup_data)['id']
|
||||
workflow_node_fields['inventory'] = module.get_one(
|
||||
'inventories', name_or_id=workflow_node['inventory']['name'], data=inv_lookup_data)['id']
|
||||
else:
|
||||
workflow_node_fields['inventory'] = module.get_one('inventories', name_or_id=workflow_node['inventory'])['id']
|
||||
|
||||
@@ -843,7 +843,7 @@ def main():
|
||||
notification_templates_approvals=dict(type="list", elements='str'),
|
||||
workflow_nodes=dict(type='list', elements='dict', aliases=['schema']),
|
||||
destroy_current_nodes=dict(type='bool', default=False, aliases=['destroy_current_schema']),
|
||||
state=dict(choices=['present', 'absent', 'exists'], default='present'),
|
||||
state=dict(choices=['present', 'absent'], default='present'),
|
||||
)
|
||||
|
||||
# Create a module for ourselves
|
||||
@@ -871,7 +871,7 @@ def main():
|
||||
search_fields['organization'] = new_fields['organization'] = organization_id
|
||||
|
||||
# Attempt to look up an existing item based on the provided data
|
||||
existing_item = module.get_one('workflow_job_templates', name_or_id=name, check_exists=(state == 'exists'), **{'data': search_fields})
|
||||
existing_item = module.get_one('workflow_job_templates', name_or_id=name, **{'data': search_fields})
|
||||
|
||||
# Attempt to look up credential to copy based on the provided name
|
||||
if copy_from:
|
||||
|
||||
@@ -179,7 +179,7 @@ options:
|
||||
state:
|
||||
description:
|
||||
- Desired state of the resource.
|
||||
choices: ["present", "absent", "exists"]
|
||||
choices: ["present", "absent"]
|
||||
default: "present"
|
||||
type: str
|
||||
extends_documentation_fragment: awx.awx.auth
|
||||
@@ -285,7 +285,7 @@ def main():
|
||||
job_slice_count=dict(type='int'),
|
||||
labels=dict(type='list', elements='str'),
|
||||
timeout=dict(type='int'),
|
||||
state=dict(choices=['present', 'absent', 'exists'], default='present'),
|
||||
state=dict(choices=['present', 'absent'], default='present'),
|
||||
)
|
||||
mutually_exclusive = [("unified_job_template", "approval_node")]
|
||||
required_if = [
|
||||
@@ -327,7 +327,7 @@ def main():
|
||||
search_fields['workflow_job_template'] = new_fields['workflow_job_template'] = workflow_job_template_id
|
||||
|
||||
# Attempt to look up an existing item based on the provided data
|
||||
existing_item = module.get_one('workflow_job_template_nodes', check_exists=(state == 'exists'), **{'data': search_fields})
|
||||
existing_item = module.get_one('workflow_job_template_nodes', **{'data': search_fields})
|
||||
|
||||
if state == 'absent':
|
||||
# If the state was absent we can let the module delete it if needed, the module will handle exiting from this
|
||||
|
||||
@@ -24,43 +24,6 @@
|
||||
that:
|
||||
- "result is changed"
|
||||
|
||||
- name: Run an application with exists
|
||||
application:
|
||||
name: "{{ app1_name }}"
|
||||
authorization_grant_type: "password"
|
||||
client_type: "public"
|
||||
organization: "Default"
|
||||
state: exists
|
||||
register: result
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- "result is not changed"
|
||||
|
||||
- name: Delete our application
|
||||
application:
|
||||
name: "{{ app1_name }}"
|
||||
organization: "Default"
|
||||
state: absent
|
||||
register: result
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- "result is changed"
|
||||
|
||||
- name: Run an application with exists
|
||||
application:
|
||||
name: "{{ app1_name }}"
|
||||
authorization_grant_type: "password"
|
||||
client_type: "public"
|
||||
organization: "Default"
|
||||
state: exists
|
||||
register: result
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- "result is changed"
|
||||
|
||||
- name: Delete our application
|
||||
application:
|
||||
name: "{{ app1_name }}"
|
||||
|
||||
@@ -47,42 +47,6 @@
|
||||
that:
|
||||
- "result is changed"
|
||||
|
||||
- name: Create an Org-specific credential with an ID with exists
|
||||
credential:
|
||||
name: "{{ ssh_cred_name1 }}"
|
||||
organization: Default
|
||||
credential_type: Machine
|
||||
state: exists
|
||||
register: result
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- "result is not changed"
|
||||
|
||||
- name: Delete an Org-specific credential with an ID
|
||||
credential:
|
||||
name: "{{ ssh_cred_name1 }}"
|
||||
organization: Default
|
||||
credential_type: Machine
|
||||
state: absent
|
||||
register: result
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- "result is changed"
|
||||
|
||||
- name: Create an Org-specific credential with an ID with exists
|
||||
credential:
|
||||
name: "{{ ssh_cred_name1 }}"
|
||||
organization: Default
|
||||
credential_type: Machine
|
||||
state: exists
|
||||
register: result
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- "result is changed"
|
||||
|
||||
- name: Delete a Org-specific credential
|
||||
credential:
|
||||
name: "{{ ssh_cred_name1 }}"
|
||||
|
||||
@@ -54,51 +54,6 @@
|
||||
that:
|
||||
- "result is changed"
|
||||
|
||||
- name: Add credential Input Source with exists
|
||||
credential_input_source:
|
||||
input_field_name: password
|
||||
target_credential: "{{ target_cred_result.id }}"
|
||||
source_credential: "{{ src_cred_result.id }}"
|
||||
metadata:
|
||||
object_query: "Safe=MY_SAFE;Object=AWX-user"
|
||||
object_query_format: "Exact"
|
||||
state: exists
|
||||
register: result
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- "result is not changed"
|
||||
|
||||
- name: Delete credential Input Source
|
||||
credential_input_source:
|
||||
input_field_name: password
|
||||
target_credential: "{{ target_cred_result.id }}"
|
||||
source_credential: "{{ src_cred_result.id }}"
|
||||
metadata:
|
||||
object_query: "Safe=MY_SAFE;Object=AWX-user"
|
||||
object_query_format: "Exact"
|
||||
state: absent
|
||||
register: result
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- "result is changed"
|
||||
|
||||
- name: Add credential Input Source with exists
|
||||
credential_input_source:
|
||||
input_field_name: password
|
||||
target_credential: "{{ target_cred_result.id }}"
|
||||
source_credential: "{{ src_cred_result.id }}"
|
||||
metadata:
|
||||
object_query: "Safe=MY_SAFE;Object=AWX-user"
|
||||
object_query_format: "Exact"
|
||||
state: exists
|
||||
register: result
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- "result is changed"
|
||||
|
||||
- name: Add Second credential Lookup
|
||||
credential:
|
||||
description: Credential for Testing Source Change
|
||||
|
||||
@@ -22,48 +22,6 @@
|
||||
that:
|
||||
- "result is changed"
|
||||
|
||||
- name: Add Tower credential type with exists
|
||||
credential_type:
|
||||
description: Credential type for Test
|
||||
name: "{{ cred_type_name }}"
|
||||
kind: cloud
|
||||
inputs: {"fields": [{"type": "string", "id": "username", "label": "Username"}, {"secret": true, "type": "string", "id": "password", "label": "Password"}], "required": ["username", "password"]}
|
||||
injectors: {"extra_vars": {"test": "foo"}}
|
||||
state: exists
|
||||
register: result
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- "result is not changed"
|
||||
|
||||
- name: Delete the credential type
|
||||
credential_type:
|
||||
description: Credential type for Test
|
||||
name: "{{ cred_type_name }}"
|
||||
kind: cloud
|
||||
inputs: {"fields": [{"type": "string", "id": "username", "label": "Username"}, {"secret": true, "type": "string", "id": "password", "label": "Password"}], "required": ["username", "password"]}
|
||||
injectors: {"extra_vars": {"test": "foo"}}
|
||||
state: absent
|
||||
register: result
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- "result is changed"
|
||||
|
||||
- name: Add Tower credential type with exists
|
||||
credential_type:
|
||||
description: Credential type for Test
|
||||
name: "{{ cred_type_name }}"
|
||||
kind: cloud
|
||||
inputs: {"fields": [{"type": "string", "id": "username", "label": "Username"}, {"secret": true, "type": "string", "id": "password", "label": "Password"}], "required": ["username", "password"]}
|
||||
injectors: {"extra_vars": {"test": "foo"}}
|
||||
state: exists
|
||||
register: result
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- "result is changed"
|
||||
|
||||
- name: Rename Tower credential type
|
||||
credential_type:
|
||||
name: "{{ cred_type_name }}"
|
||||
|
||||
@@ -22,48 +22,6 @@
|
||||
that:
|
||||
- "result is changed"
|
||||
|
||||
- name: Add an EE with exists
|
||||
execution_environment:
|
||||
name: "{{ ee_name }}"
|
||||
description: "EE for Testing"
|
||||
image: quay.io/ansible/awx-ee
|
||||
pull: always
|
||||
organization: Default
|
||||
state: exists
|
||||
register: result
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- "result is not changed"
|
||||
|
||||
- name: Delete an EE
|
||||
execution_environment:
|
||||
name: "{{ ee_name }}"
|
||||
description: "EE for Testing"
|
||||
image: quay.io/ansible/awx-ee
|
||||
pull: always
|
||||
organization: Default
|
||||
state: absent
|
||||
register: result
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- "result is changed"
|
||||
|
||||
- name: Add an EE with exists
|
||||
execution_environment:
|
||||
name: "{{ ee_name }}"
|
||||
description: "EE for Testing"
|
||||
image: quay.io/ansible/awx-ee
|
||||
pull: always
|
||||
organization: Default
|
||||
state: exists
|
||||
register: result
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- "result is changed"
|
||||
|
||||
- name: Associate the Test EE with Default Org (this should fail)
|
||||
execution_environment:
|
||||
name: "{{ ee_name }}"
|
||||
|
||||
@@ -19,9 +19,9 @@
|
||||
name: "{{ inv_name }}"
|
||||
organization: Default
|
||||
state: present
|
||||
registuer: result
|
||||
register: result
|
||||
|
||||
- name: Create Group 1
|
||||
- name: Create a Group
|
||||
group:
|
||||
name: "{{ group_name1 }}"
|
||||
inventory: "{{ result.id }}"
|
||||
@@ -34,46 +34,7 @@
|
||||
that:
|
||||
- "result is changed"
|
||||
|
||||
- name: Create Group 1 with exists
|
||||
group:
|
||||
name: "{{ group_name1 }}"
|
||||
inventory: "{{ inv_name }}"
|
||||
state: exists
|
||||
variables:
|
||||
foo: bar
|
||||
register: result
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- "result is not changed"
|
||||
|
||||
- name: Delete Group 1
|
||||
group:
|
||||
name: "{{ group_name1 }}"
|
||||
inventory: "{{ inv_name }}"
|
||||
state: absent
|
||||
variables:
|
||||
foo: bar
|
||||
register: result
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- "result is changed"
|
||||
|
||||
- name: Create Group 1 with exists
|
||||
group:
|
||||
name: "{{ group_name1 }}"
|
||||
inventory: "{{ inv_name }}"
|
||||
state: exists
|
||||
variables:
|
||||
foo: bar
|
||||
register: result
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- "result is changed"
|
||||
|
||||
- name: Create Group 2
|
||||
- name: Create a Group
|
||||
group:
|
||||
name: "{{ group_name2 }}"
|
||||
inventory: "{{ inv_name }}"
|
||||
@@ -86,7 +47,7 @@
|
||||
that:
|
||||
- "result is changed"
|
||||
|
||||
- name: Create Group 3
|
||||
- name: Create a Group
|
||||
group:
|
||||
name: "{{ group_name3 }}"
|
||||
inventory: "{{ inv_name }}"
|
||||
@@ -108,7 +69,7 @@
|
||||
- "{{ host_name2 }}"
|
||||
- "{{ host_name3 }}"
|
||||
|
||||
- name: Create Group 1 with hosts and sub group of Group 2
|
||||
- name: Create a Group with hosts and sub group
|
||||
group:
|
||||
name: "{{ group_name1 }}"
|
||||
inventory: "{{ inv_name }}"
|
||||
@@ -122,7 +83,7 @@
|
||||
foo: bar
|
||||
register: result
|
||||
|
||||
- name: Create Group 1 with hosts and sub group
|
||||
- name: Create a Group with hosts and sub group
|
||||
group:
|
||||
name: "{{ group_name1 }}"
|
||||
inventory: "{{ inv_name }}"
|
||||
@@ -143,7 +104,18 @@
|
||||
that:
|
||||
- group1_host_count == "3"
|
||||
|
||||
- name: Delete Group 2
|
||||
- name: Delete a Group
|
||||
group:
|
||||
name: "{{ group_name1 }}"
|
||||
inventory: "{{ inv_name }}"
|
||||
state: absent
|
||||
register: result
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- "result is changed"
|
||||
|
||||
- name: Delete a Group
|
||||
group:
|
||||
name: "{{ group_name2 }}"
|
||||
inventory: "{{ inv_name }}"
|
||||
@@ -155,7 +127,7 @@
|
||||
that:
|
||||
- "result is not changed"
|
||||
|
||||
- name: Delete Group 3
|
||||
- name: Delete a Group
|
||||
group:
|
||||
name: "{{ group_name3 }}"
|
||||
inventory: "{{ inv_name }}"
|
||||
@@ -164,19 +136,7 @@
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- "result is changed"
|
||||
|
||||
# If we delete group 1 first it will delete group 2 and 3
|
||||
- name: Delete Group 1
|
||||
group:
|
||||
name: "{{ group_name1 }}"
|
||||
inventory: "{{ inv_name }}"
|
||||
state: absent
|
||||
register: result
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- "result is changed"
|
||||
- "result is not changed"
|
||||
|
||||
- name: Check module fails with correct msg
|
||||
group:
|
||||
|
||||
@@ -29,45 +29,6 @@
|
||||
that:
|
||||
- "result is changed"
|
||||
|
||||
- name: Create a Host with exists
|
||||
host:
|
||||
name: "{{ host_name }}"
|
||||
inventory: "{{ inv_name }}"
|
||||
state: exists
|
||||
variables:
|
||||
foo: bar
|
||||
register: result
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- "result is not changed"
|
||||
|
||||
- name: Delete a Host
|
||||
host:
|
||||
name: "{{ host_name }}"
|
||||
inventory: "{{ inv_name }}"
|
||||
state: absent
|
||||
variables:
|
||||
foo: bar
|
||||
register: result
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- "result is changed"
|
||||
|
||||
- name: Create a Host with exists
|
||||
host:
|
||||
name: "{{ host_name }}"
|
||||
inventory: "{{ inv_name }}"
|
||||
state: exists
|
||||
variables:
|
||||
foo: bar
|
||||
register: result
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- "result is changed"
|
||||
|
||||
- name: Delete a Host
|
||||
host:
|
||||
name: "{{ result.id }}"
|
||||
|
||||
@@ -38,42 +38,6 @@
|
||||
that:
|
||||
- "result is changed"
|
||||
|
||||
- name: Create an Instance Group with exists
|
||||
instance_group:
|
||||
name: "{{ group_name1 }}"
|
||||
policy_instance_percentage: 34
|
||||
policy_instance_minimum: 12
|
||||
state: exists
|
||||
register: result
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- "result is not changed"
|
||||
|
||||
- name: Delete an Instance Group
|
||||
instance_group:
|
||||
name: "{{ group_name1 }}"
|
||||
policy_instance_percentage: 34
|
||||
policy_instance_minimum: 12
|
||||
state: absent
|
||||
register: result
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- "result is changed"
|
||||
|
||||
- name: Create an Instance Group with exists
|
||||
instance_group:
|
||||
name: "{{ group_name1 }}"
|
||||
policy_instance_percentage: 34
|
||||
policy_instance_minimum: 12
|
||||
state: exists
|
||||
register: result
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- "result is changed"
|
||||
|
||||
- name: Update an Instance Group
|
||||
instance_group:
|
||||
name: "{{ result.id }}"
|
||||
|
||||
@@ -51,45 +51,6 @@
|
||||
that:
|
||||
- "result is changed"
|
||||
|
||||
- name: Create an Inventory with exists
|
||||
inventory:
|
||||
name: "{{ inv_name1 }}"
|
||||
organization: Default
|
||||
instance_groups:
|
||||
- "{{ group_name1 }}"
|
||||
state: exists
|
||||
register: result
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- "result is not changed"
|
||||
|
||||
- name: Delete an Inventory
|
||||
inventory:
|
||||
name: "{{ inv_name1 }}"
|
||||
organization: Default
|
||||
instance_groups:
|
||||
- "{{ group_name1 }}"
|
||||
state: absent
|
||||
register: result
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- "result is changed"
|
||||
|
||||
- name: Create an Inventory with exists
|
||||
inventory:
|
||||
name: "{{ inv_name1 }}"
|
||||
organization: Default
|
||||
instance_groups:
|
||||
- "{{ group_name1 }}"
|
||||
state: exists
|
||||
register: result
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- "result is changed"
|
||||
|
||||
- name: Test Inventory module idempotency
|
||||
inventory:
|
||||
name: "{{ result.id }}"
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
organization: Default
|
||||
name: "{{ openstack_inv }}"
|
||||
|
||||
- name: Create an source inventory
|
||||
- name: Create a source inventory
|
||||
inventory_source:
|
||||
name: "{{ openstack_inv_source }}"
|
||||
description: Source for Test inventory
|
||||
@@ -47,60 +47,6 @@
|
||||
that:
|
||||
- "result is changed"
|
||||
|
||||
- name: Create an source inventory with exists
|
||||
inventory_source:
|
||||
name: "{{ openstack_inv_source }}"
|
||||
description: Source for Test inventory
|
||||
inventory: "{{ openstack_inv }}"
|
||||
credential: "{{ credential_result.id }}"
|
||||
overwrite: true
|
||||
update_on_launch: true
|
||||
source_vars:
|
||||
private: false
|
||||
source: openstack
|
||||
state: exists
|
||||
register: result
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- "result is not changed"
|
||||
|
||||
- name: Delete an source inventory
|
||||
inventory_source:
|
||||
name: "{{ openstack_inv_source }}"
|
||||
description: Source for Test inventory
|
||||
inventory: "{{ openstack_inv }}"
|
||||
credential: "{{ credential_result.id }}"
|
||||
overwrite: true
|
||||
update_on_launch: true
|
||||
source_vars:
|
||||
private: false
|
||||
source: openstack
|
||||
state: absent
|
||||
register: result
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- "result is changed"
|
||||
|
||||
- name: Create an source inventory with exists
|
||||
inventory_source:
|
||||
name: "{{ openstack_inv_source }}"
|
||||
description: Source for Test inventory
|
||||
inventory: "{{ openstack_inv }}"
|
||||
credential: "{{ credential_result.id }}"
|
||||
overwrite: true
|
||||
update_on_launch: true
|
||||
source_vars:
|
||||
private: false
|
||||
source: openstack
|
||||
state: exists
|
||||
register: result
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- "result is changed"
|
||||
|
||||
- name: Delete the inventory source with an invalid cred and source_project specified
|
||||
inventory_source:
|
||||
name: "{{ result.id }}"
|
||||
|
||||
@@ -10,7 +10,6 @@
|
||||
cred1: "AWX-Collection-tests-job_template-cred1-{{ test_id }}"
|
||||
cred2: "AWX-Collection-tests-job_template-cred2-{{ test_id }}"
|
||||
cred3: "AWX-Collection-tests-job_template-cred3-{{ test_id }}"
|
||||
inv1: "AWX-Collection-tests-job_template-inv-{{ test_id }}"
|
||||
proj1: "AWX-Collection-tests-job_template-proj-{{ test_id }}"
|
||||
jt1: "AWX-Collection-tests-job_template-jt1-{{ test_id }}"
|
||||
jt2: "AWX-Collection-tests-job_template-jt2-{{ test_id }}"
|
||||
@@ -26,11 +25,6 @@
|
||||
- Ansible Galaxy
|
||||
register: result
|
||||
|
||||
- name: Create an inventory
|
||||
inventory:
|
||||
name: "{{ inv1 }}"
|
||||
organization: "{{ org_name }}"
|
||||
|
||||
- name: Create a Demo Project
|
||||
project:
|
||||
name: "{{ proj1 }}"
|
||||
@@ -110,7 +104,7 @@
|
||||
job_template:
|
||||
name: "{{ jt1 }}"
|
||||
project: "{{ proj1 }}"
|
||||
inventory: "{{ inv1 }}"
|
||||
inventory: Demo Inventory
|
||||
playbook: hello_world.yml
|
||||
credentials:
|
||||
- "{{ cred1 }}"
|
||||
@@ -125,63 +119,6 @@
|
||||
that:
|
||||
- "jt1_result is changed"
|
||||
|
||||
- name: Create Job Template 1 with exists
|
||||
job_template:
|
||||
name: "{{ jt1 }}"
|
||||
project: "{{ proj1 }}"
|
||||
inventory: "{{ inv1 }}"
|
||||
playbook: hello_world.yml
|
||||
credentials:
|
||||
- "{{ cred1 }}"
|
||||
- "{{ cred2 }}"
|
||||
instance_groups:
|
||||
- "{{ group_name1 }}"
|
||||
job_type: run
|
||||
state: exists
|
||||
register: jt1_result
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- "jt1_result is not changed"
|
||||
|
||||
- name: Delete Job Template 1
|
||||
job_template:
|
||||
name: "{{ jt1 }}"
|
||||
project: "{{ proj1 }}"
|
||||
inventory: "{{ inv1 }}"
|
||||
playbook: hello_world.yml
|
||||
credentials:
|
||||
- "{{ cred1 }}"
|
||||
- "{{ cred2 }}"
|
||||
instance_groups:
|
||||
- "{{ group_name1 }}"
|
||||
job_type: run
|
||||
state: absent
|
||||
register: jt1_result
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- "jt1_result is changed"
|
||||
|
||||
- name: Create Job Template 1 with exists
|
||||
job_template:
|
||||
name: "{{ jt1 }}"
|
||||
project: "{{ proj1 }}"
|
||||
inventory: "{{ inv1 }}"
|
||||
playbook: hello_world.yml
|
||||
credentials:
|
||||
- "{{ cred1 }}"
|
||||
- "{{ cred2 }}"
|
||||
instance_groups:
|
||||
- "{{ group_name1 }}"
|
||||
job_type: run
|
||||
state: exists
|
||||
register: jt1_result
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- "jt1_result is changed"
|
||||
|
||||
- name: Add a credential to this JT
|
||||
job_template:
|
||||
name: "{{ jt1 }}"
|
||||
@@ -281,7 +218,7 @@
|
||||
name: "{{ jt2 }}"
|
||||
organization: Default
|
||||
project: "{{ proj1 }}"
|
||||
inventory: "{{ inv1 }}"
|
||||
inventory: Demo Inventory
|
||||
playbook: hello_world.yml
|
||||
credential: "{{ cred3 }}"
|
||||
job_type: run
|
||||
@@ -299,7 +236,7 @@
|
||||
name: "{{ jt2 }}"
|
||||
organization: Default
|
||||
project: "{{ proj1 }}"
|
||||
inventory: "{{ inv1 }}"
|
||||
inventory: Demo Inventory
|
||||
playbook: hello_world.yml
|
||||
credential: "{{ cred3 }}"
|
||||
job_type: run
|
||||
@@ -447,7 +384,7 @@
|
||||
job_template:
|
||||
name: "{{ jt2 }}"
|
||||
project: "{{ proj1 }}"
|
||||
inventory: "{{ inv1 }}"
|
||||
inventory: Demo Inventory
|
||||
playbook: hello_world.yml
|
||||
credential: "{{ cred3 }}"
|
||||
job_type: run
|
||||
@@ -507,12 +444,6 @@
|
||||
organization: Default
|
||||
state: absent
|
||||
|
||||
- name: Delete an inventory
|
||||
inventory:
|
||||
name: "{{ inv1 }}"
|
||||
organization: "{{ org_name }}"
|
||||
state: absent
|
||||
|
||||
- name: "Remove the organization"
|
||||
organization:
|
||||
name: "{{ org_name }}"
|
||||
|
||||
@@ -13,22 +13,6 @@
|
||||
name: "{{ label_name }}"
|
||||
organization: Default
|
||||
state: present
|
||||
register: results
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- "results is changed"
|
||||
|
||||
- name: Create a Label with exists
|
||||
label:
|
||||
name: "{{ label_name }}"
|
||||
organization: Default
|
||||
state: exists
|
||||
register: results
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- "results is not changed"
|
||||
|
||||
- name: Check module fails with correct msg
|
||||
label:
|
||||
|
||||
@@ -36,75 +36,6 @@
|
||||
that:
|
||||
- result is changed
|
||||
|
||||
- name: Create Slack notification with custom messages with exists
|
||||
notification_template:
|
||||
name: "{{ slack_not }}"
|
||||
organization: Default
|
||||
notification_type: slack
|
||||
notification_configuration:
|
||||
token: a_token
|
||||
channels:
|
||||
- general
|
||||
messages:
|
||||
started:
|
||||
message: "{{ '{{' }} job_friendly_name {{' }}' }} {{ '{{' }} job.id {{' }}' }} started"
|
||||
success:
|
||||
message: "{{ '{{' }} job_friendly_name {{ '}}' }} completed in {{ '{{' }} job.elapsed {{ '}}' }} seconds"
|
||||
error:
|
||||
message: "{{ '{{' }} job_friendly_name {{ '}}' }} FAILED! Please look at {{ '{{' }} job.url {{ '}}' }}"
|
||||
state: exists
|
||||
register: result
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- result is not changed
|
||||
|
||||
- name: Delete Slack notification with custom messages
|
||||
notification_template:
|
||||
name: "{{ slack_not }}"
|
||||
organization: Default
|
||||
notification_type: slack
|
||||
notification_configuration:
|
||||
token: a_token
|
||||
channels:
|
||||
- general
|
||||
messages:
|
||||
started:
|
||||
message: "{{ '{{' }} job_friendly_name {{' }}' }} {{ '{{' }} job.id {{' }}' }} started"
|
||||
success:
|
||||
message: "{{ '{{' }} job_friendly_name {{ '}}' }} completed in {{ '{{' }} job.elapsed {{ '}}' }} seconds"
|
||||
error:
|
||||
message: "{{ '{{' }} job_friendly_name {{ '}}' }} FAILED! Please look at {{ '{{' }} job.url {{ '}}' }}"
|
||||
state: absent
|
||||
register: result
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- result is changed
|
||||
|
||||
- name: Create Slack notification with custom messages with exists
|
||||
notification_template:
|
||||
name: "{{ slack_not }}"
|
||||
organization: Default
|
||||
notification_type: slack
|
||||
notification_configuration:
|
||||
token: a_token
|
||||
channels:
|
||||
- general
|
||||
messages:
|
||||
started:
|
||||
message: "{{ '{{' }} job_friendly_name {{' }}' }} {{ '{{' }} job.id {{' }}' }} started"
|
||||
success:
|
||||
message: "{{ '{{' }} job_friendly_name {{ '}}' }} completed in {{ '{{' }} job.elapsed {{ '}}' }} seconds"
|
||||
error:
|
||||
message: "{{ '{{' }} job_friendly_name {{ '}}' }} FAILED! Please look at {{ '{{' }} job.url {{ '}}' }}"
|
||||
state: exists
|
||||
register: result
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- result is changed
|
||||
|
||||
- name: Delete Slack notification
|
||||
notification_template:
|
||||
name: "{{ slack_not }}"
|
||||
|
||||
@@ -25,39 +25,6 @@
|
||||
- assert:
|
||||
that: "result is changed"
|
||||
|
||||
- name: "Create a new organization with exists"
|
||||
organization:
|
||||
name: "{{ org_name }}"
|
||||
galaxy_credentials:
|
||||
- Ansible Galaxy
|
||||
state: exists
|
||||
register: result
|
||||
|
||||
- assert:
|
||||
that: "result is not changed"
|
||||
|
||||
- name: "Delete a new organization"
|
||||
organization:
|
||||
name: "{{ org_name }}"
|
||||
galaxy_credentials:
|
||||
- Ansible Galaxy
|
||||
state: absent
|
||||
register: result
|
||||
|
||||
- assert:
|
||||
that: "result is changed"
|
||||
|
||||
- name: "Create a new organization with exists"
|
||||
organization:
|
||||
name: "{{ org_name }}"
|
||||
galaxy_credentials:
|
||||
- Ansible Galaxy
|
||||
state: exists
|
||||
register: result
|
||||
|
||||
- assert:
|
||||
that: "result is changed"
|
||||
|
||||
- name: "Make sure making the same org is not a change"
|
||||
organization:
|
||||
name: "{{ org_name }}"
|
||||
|
||||
@@ -39,48 +39,6 @@
|
||||
that:
|
||||
- result is changed
|
||||
|
||||
- name: Create a git project without credentials and wait with exists
|
||||
project:
|
||||
name: "{{ project_name1 }}"
|
||||
organization: Default
|
||||
scm_type: git
|
||||
scm_url: https://github.com/ansible/test-playbooks
|
||||
wait: true
|
||||
state: exists
|
||||
register: result
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- result is not changed
|
||||
|
||||
- name: Delete a git project without credentials and wait
|
||||
project:
|
||||
name: "{{ project_name1 }}"
|
||||
organization: Default
|
||||
scm_type: git
|
||||
scm_url: https://github.com/ansible/test-playbooks
|
||||
wait: true
|
||||
state: absent
|
||||
register: result
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- result is changed
|
||||
|
||||
- name: Create a git project without credentials and wait with exists
|
||||
project:
|
||||
name: "{{ project_name1 }}"
|
||||
organization: Default
|
||||
scm_type: git
|
||||
scm_url: https://github.com/ansible/test-playbooks
|
||||
wait: true
|
||||
state: exists
|
||||
register: result
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- result is changed
|
||||
|
||||
- name: Recreate the project to validate not changed
|
||||
project:
|
||||
name: "{{ project_name1 }}"
|
||||
|
||||
@@ -11,8 +11,6 @@
|
||||
jt1: "AWX-Collection-tests-role-jt1-{{ test_id }}"
|
||||
jt2: "AWX-Collection-tests-role-jt2-{{ test_id }}"
|
||||
wfjt_name: "AWX-Collection-tests-role-project-wfjt-{{ test_id }}"
|
||||
team_name: "AWX-Collection-tests-team-team-{{ test_id }}"
|
||||
team2_name: "AWX-Collection-tests-team-team-{{ test_id }}2"
|
||||
|
||||
- block:
|
||||
- name: Create a User
|
||||
@@ -29,32 +27,6 @@
|
||||
that:
|
||||
- "result is changed"
|
||||
|
||||
- name: Create a 2nd User
|
||||
user:
|
||||
first_name: Joe
|
||||
last_name: User
|
||||
username: "{{ username }}2"
|
||||
password: "{{ 65535 | random | to_uuid }}"
|
||||
email: joe@example.org
|
||||
state: present
|
||||
register: result
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- "result is changed"
|
||||
|
||||
- name: Create teams
|
||||
team:
|
||||
name: "{{ item }}"
|
||||
organization: Default
|
||||
register: result
|
||||
loop:
|
||||
- "{{ team_name }}"
|
||||
- "{{ team2_name }}"
|
||||
- assert:
|
||||
that:
|
||||
- "result is changed"
|
||||
|
||||
- name: Create a project
|
||||
project:
|
||||
name: "{{ project_name }}"
|
||||
@@ -83,14 +55,9 @@
|
||||
that:
|
||||
- "result is changed"
|
||||
|
||||
- name: Add Joe and teams to the update role of the default Project with lookup Organization
|
||||
- name: Add Joe to the update role of the default Project with lookup Organization
|
||||
role:
|
||||
user: "{{ username }}"
|
||||
users:
|
||||
- "{{ username }}2"
|
||||
teams:
|
||||
- "{{ team_name }}"
|
||||
- "{{ team2_name }}"
|
||||
role: update
|
||||
lookup_organization: Default
|
||||
project: "Demo Project"
|
||||
@@ -107,11 +74,6 @@
|
||||
- name: Add Joe to the new project by ID
|
||||
role:
|
||||
user: "{{ username }}"
|
||||
users:
|
||||
- "{{ username }}2"
|
||||
teams:
|
||||
- "{{ team_name }}"
|
||||
- "{{ team2_name }}"
|
||||
role: update
|
||||
project: "{{ project_info['id'] }}"
|
||||
state: "{{ item }}"
|
||||
@@ -127,8 +89,6 @@
|
||||
- name: Add Joe as execution admin to Default Org.
|
||||
role:
|
||||
user: "{{ username }}"
|
||||
users:
|
||||
- "{{ username }}2"
|
||||
role: execution_environment_admin
|
||||
organizations: Default
|
||||
state: "{{ item }}"
|
||||
@@ -150,8 +110,6 @@
|
||||
- name: Add Joe to workflow execute role
|
||||
role:
|
||||
user: "{{ username }}"
|
||||
users:
|
||||
- "{{ username }}2"
|
||||
role: execute
|
||||
workflow: test-role-workflow
|
||||
job_templates:
|
||||
@@ -167,8 +125,6 @@
|
||||
- name: Add Joe to nonexistant job template execute role
|
||||
role:
|
||||
user: "{{ username }}"
|
||||
users:
|
||||
- "{{ username }}2"
|
||||
role: execute
|
||||
workflow: test-role-workflow
|
||||
job_templates:
|
||||
@@ -185,8 +141,6 @@
|
||||
- name: Add Joe to workflow execute role, no-op
|
||||
role:
|
||||
user: "{{ username }}"
|
||||
users:
|
||||
- "{{ username }}2"
|
||||
role: execute
|
||||
workflow: test-role-workflow
|
||||
state: present
|
||||
@@ -199,8 +153,6 @@
|
||||
- name: Add Joe to workflow approve role
|
||||
role:
|
||||
user: "{{ username }}"
|
||||
users:
|
||||
- "{{ username }}2"
|
||||
role: approval
|
||||
workflow: test-role-workflow
|
||||
state: present
|
||||
@@ -218,23 +170,6 @@
|
||||
state: absent
|
||||
register: result
|
||||
|
||||
- name: Delete a 2nd User
|
||||
user:
|
||||
username: "{{ username }}2"
|
||||
email: joe@example.org
|
||||
state: absent
|
||||
register: result
|
||||
|
||||
- name: Delete teams
|
||||
team:
|
||||
name: "{{ item }}"
|
||||
organization: Default
|
||||
state: absent
|
||||
register: result
|
||||
loop:
|
||||
- "{{ team_name }}"
|
||||
- "{{ team2_name }}"
|
||||
|
||||
- name: Delete job templates
|
||||
job_template:
|
||||
name: "{{ item }}"
|
||||
|
||||
@@ -76,42 +76,6 @@
|
||||
that:
|
||||
- result is changed
|
||||
|
||||
- name: Build a real schedule with exists
|
||||
schedule:
|
||||
name: "{{ sched1 }}"
|
||||
state: exists
|
||||
unified_job_template: "Demo Job Template"
|
||||
rrule: "DTSTART:20191219T130551Z RRULE:FREQ=WEEKLY;INTERVAL=1;COUNT=1"
|
||||
register: result
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- result is not changed
|
||||
|
||||
- name: Delete a real schedule
|
||||
schedule:
|
||||
name: "{{ sched1 }}"
|
||||
state: absent
|
||||
unified_job_template: "Demo Job Template"
|
||||
rrule: "DTSTART:20191219T130551Z RRULE:FREQ=WEEKLY;INTERVAL=1;COUNT=1"
|
||||
register: result
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- result is changed
|
||||
|
||||
- name: Build a real schedule with exists
|
||||
schedule:
|
||||
name: "{{ sched1 }}"
|
||||
state: exists
|
||||
unified_job_template: "Demo Job Template"
|
||||
rrule: "DTSTART:20191219T130551Z RRULE:FREQ=WEEKLY;INTERVAL=1;COUNT=1"
|
||||
register: result
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- result is changed
|
||||
|
||||
- name: Rebuild the same schedule
|
||||
schedule:
|
||||
name: "{{ sched1 }}"
|
||||
|
||||
@@ -34,39 +34,6 @@
|
||||
that:
|
||||
- "result is changed"
|
||||
|
||||
- name: Create a team with exists
|
||||
team:
|
||||
name: "{{ team_name }}"
|
||||
organization: Default
|
||||
state: exists
|
||||
register: result
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- "result is not changed"
|
||||
|
||||
- name: Delete a team
|
||||
team:
|
||||
name: "{{ team_name }}"
|
||||
organization: Default
|
||||
state: absent
|
||||
register: result
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- "result is changed"
|
||||
|
||||
- name: Create a team with exists
|
||||
team:
|
||||
name: "{{ team_name }}"
|
||||
organization: Default
|
||||
state: exists
|
||||
register: result
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- "result is changed"
|
||||
|
||||
- name: Delete a team
|
||||
team:
|
||||
name: "{{ team_name }}"
|
||||
|
||||
@@ -20,42 +20,6 @@
|
||||
that:
|
||||
- "result is changed"
|
||||
|
||||
- name: Create a User with exists
|
||||
user:
|
||||
username: "{{ username }}"
|
||||
first_name: Joe
|
||||
password: "{{ 65535 | random | to_uuid }}"
|
||||
state: exists
|
||||
register: result
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- "result is not changed"
|
||||
|
||||
- name: Delete a User
|
||||
user:
|
||||
username: "{{ username }}"
|
||||
first_name: Joe
|
||||
password: "{{ 65535 | random | to_uuid }}"
|
||||
state: absent
|
||||
register: result
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- "result is changed"
|
||||
|
||||
- name: Create a User with exists
|
||||
user:
|
||||
username: "{{ username }}"
|
||||
first_name: Joe
|
||||
password: "{{ 65535 | random | to_uuid }}"
|
||||
state: exists
|
||||
register: result
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- "result is changed"
|
||||
|
||||
- name: Change a User by ID
|
||||
user:
|
||||
username: "{{ result.id }}"
|
||||
|
||||
@@ -254,65 +254,6 @@
|
||||
that:
|
||||
- "result is changed"
|
||||
|
||||
- name: Create a workflow job template with exists
|
||||
workflow_job_template:
|
||||
name: "{{ wfjt_name }}"
|
||||
organization: Default
|
||||
inventory: Demo Inventory
|
||||
extra_vars: {'foo': 'bar', 'another-foo': {'barz': 'bar2'}}
|
||||
labels:
|
||||
- "{{ lab1 }}"
|
||||
ask_inventory_on_launch: true
|
||||
ask_scm_branch_on_launch: true
|
||||
ask_limit_on_launch: true
|
||||
ask_tags_on_launch: true
|
||||
ask_variables_on_launch: true
|
||||
state: exists
|
||||
register: result
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- "result is not changed"
|
||||
|
||||
- name: Delete a workflow job template
|
||||
workflow_job_template:
|
||||
name: "{{ wfjt_name }}"
|
||||
organization: Default
|
||||
inventory: Demo Inventory
|
||||
extra_vars: {'foo': 'bar', 'another-foo': {'barz': 'bar2'}}
|
||||
labels:
|
||||
- "{{ lab1 }}"
|
||||
ask_inventory_on_launch: true
|
||||
ask_scm_branch_on_launch: true
|
||||
ask_limit_on_launch: true
|
||||
ask_tags_on_launch: true
|
||||
ask_variables_on_launch: true
|
||||
state: absent
|
||||
register: result
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- "result is changed"
|
||||
|
||||
- name: Create a workflow job template with exists
|
||||
workflow_job_template:
|
||||
name: "{{ wfjt_name }}"
|
||||
organization: Default
|
||||
inventory: Demo Inventory
|
||||
extra_vars: {'foo': 'bar', 'another-foo': {'barz': 'bar2'}}
|
||||
# We don't try with the label here because after we delete the first WFJT the label is delete with it because it has no references
|
||||
ask_inventory_on_launch: true
|
||||
ask_scm_branch_on_launch: true
|
||||
ask_limit_on_launch: true
|
||||
ask_tags_on_launch: true
|
||||
ask_variables_on_launch: true
|
||||
state: exists
|
||||
register: result
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- "result is changed"
|
||||
|
||||
- name: Create a workflow job template with bad label
|
||||
workflow_job_template:
|
||||
name: "{{ wfjt_name }}"
|
||||
|
||||
@@ -110,7 +110,7 @@ processes it spawns. This allows Ansible events to be captured and persisted
|
||||
into the AWX database; this process is what drives the "streaming" web UI
|
||||
you'll see if you launch a job from the AWX web interface and watch its results
|
||||
appears on the screen. AWX relies on stability in this plugin interface, the
|
||||
hierarchy of emitted events based on strategy, and _especially_ the structure
|
||||
heirarchy of emitted events based on strategy, and _especially_ the structure
|
||||
of event data to work across Ansible releases:
|
||||
|
||||

|
||||
|
||||
9
licenses/deprecated.txt
Normal file
9
licenses/deprecated.txt
Normal file
@@ -0,0 +1,9 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2017 Laurent LAPORTE
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
BIN
licenses/jwcrypto-1.4.2.tar.gz
Normal file
BIN
licenses/jwcrypto-1.4.2.tar.gz
Normal file
Binary file not shown.
165
licenses/jwcrypto.txt
Normal file
165
licenses/jwcrypto.txt
Normal file
@@ -0,0 +1,165 @@
|
||||
GNU LESSER GENERAL PUBLIC LICENSE
|
||||
Version 3, 29 June 2007
|
||||
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
|
||||
This version of the GNU Lesser General Public License incorporates
|
||||
the terms and conditions of version 3 of the GNU General Public
|
||||
License, supplemented by the additional permissions listed below.
|
||||
|
||||
0. Additional Definitions.
|
||||
|
||||
As used herein, "this License" refers to version 3 of the GNU Lesser
|
||||
General Public License, and the "GNU GPL" refers to version 3 of the GNU
|
||||
General Public License.
|
||||
|
||||
"The Library" refers to a covered work governed by this License,
|
||||
other than an Application or a Combined Work as defined below.
|
||||
|
||||
An "Application" is any work that makes use of an interface provided
|
||||
by the Library, but which is not otherwise based on the Library.
|
||||
Defining a subclass of a class defined by the Library is deemed a mode
|
||||
of using an interface provided by the Library.
|
||||
|
||||
A "Combined Work" is a work produced by combining or linking an
|
||||
Application with the Library. The particular version of the Library
|
||||
with which the Combined Work was made is also called the "Linked
|
||||
Version".
|
||||
|
||||
The "Minimal Corresponding Source" for a Combined Work means the
|
||||
Corresponding Source for the Combined Work, excluding any source code
|
||||
for portions of the Combined Work that, considered in isolation, are
|
||||
based on the Application, and not on the Linked Version.
|
||||
|
||||
The "Corresponding Application Code" for a Combined Work means the
|
||||
object code and/or source code for the Application, including any data
|
||||
and utility programs needed for reproducing the Combined Work from the
|
||||
Application, but excluding the System Libraries of the Combined Work.
|
||||
|
||||
1. Exception to Section 3 of the GNU GPL.
|
||||
|
||||
You may convey a covered work under sections 3 and 4 of this License
|
||||
without being bound by section 3 of the GNU GPL.
|
||||
|
||||
2. Conveying Modified Versions.
|
||||
|
||||
If you modify a copy of the Library, and, in your modifications, a
|
||||
facility refers to a function or data to be supplied by an Application
|
||||
that uses the facility (other than as an argument passed when the
|
||||
facility is invoked), then you may convey a copy of the modified
|
||||
version:
|
||||
|
||||
a) under this License, provided that you make a good faith effort to
|
||||
ensure that, in the event an Application does not supply the
|
||||
function or data, the facility still operates, and performs
|
||||
whatever part of its purpose remains meaningful, or
|
||||
|
||||
b) under the GNU GPL, with none of the additional permissions of
|
||||
this License applicable to that copy.
|
||||
|
||||
3. Object Code Incorporating Material from Library Header Files.
|
||||
|
||||
The object code form of an Application may incorporate material from
|
||||
a header file that is part of the Library. You may convey such object
|
||||
code under terms of your choice, provided that, if the incorporated
|
||||
material is not limited to numerical parameters, data structure
|
||||
layouts and accessors, or small macros, inline functions and templates
|
||||
(ten or fewer lines in length), you do both of the following:
|
||||
|
||||
a) Give prominent notice with each copy of the object code that the
|
||||
Library is used in it and that the Library and its use are
|
||||
covered by this License.
|
||||
|
||||
b) Accompany the object code with a copy of the GNU GPL and this license
|
||||
document.
|
||||
|
||||
4. Combined Works.
|
||||
|
||||
You may convey a Combined Work under terms of your choice that,
|
||||
taken together, effectively do not restrict modification of the
|
||||
portions of the Library contained in the Combined Work and reverse
|
||||
engineering for debugging such modifications, if you also do each of
|
||||
the following:
|
||||
|
||||
a) Give prominent notice with each copy of the Combined Work that
|
||||
the Library is used in it and that the Library and its use are
|
||||
covered by this License.
|
||||
|
||||
b) Accompany the Combined Work with a copy of the GNU GPL and this license
|
||||
document.
|
||||
|
||||
c) For a Combined Work that displays copyright notices during
|
||||
execution, include the copyright notice for the Library among
|
||||
these notices, as well as a reference directing the user to the
|
||||
copies of the GNU GPL and this license document.
|
||||
|
||||
d) Do one of the following:
|
||||
|
||||
0) Convey the Minimal Corresponding Source under the terms of this
|
||||
License, and the Corresponding Application Code in a form
|
||||
suitable for, and under terms that permit, the user to
|
||||
recombine or relink the Application with a modified version of
|
||||
the Linked Version to produce a modified Combined Work, in the
|
||||
manner specified by section 6 of the GNU GPL for conveying
|
||||
Corresponding Source.
|
||||
|
||||
1) Use a suitable shared library mechanism for linking with the
|
||||
Library. A suitable mechanism is one that (a) uses at run time
|
||||
a copy of the Library already present on the user's computer
|
||||
system, and (b) will operate properly with a modified version
|
||||
of the Library that is interface-compatible with the Linked
|
||||
Version.
|
||||
|
||||
e) Provide Installation Information, but only if you would otherwise
|
||||
be required to provide such information under section 6 of the
|
||||
GNU GPL, and only to the extent that such information is
|
||||
necessary to install and execute a modified version of the
|
||||
Combined Work produced by recombining or relinking the
|
||||
Application with a modified version of the Linked Version. (If
|
||||
you use option 4d0, the Installation Information must accompany
|
||||
the Minimal Corresponding Source and Corresponding Application
|
||||
Code. If you use option 4d1, you must provide the Installation
|
||||
Information in the manner specified by section 6 of the GNU GPL
|
||||
for conveying Corresponding Source.)
|
||||
|
||||
5. Combined Libraries.
|
||||
|
||||
You may place library facilities that are a work based on the
|
||||
Library side by side in a single library together with other library
|
||||
facilities that are not Applications and are not covered by this
|
||||
License, and convey such a combined library under terms of your
|
||||
choice, if you do both of the following:
|
||||
|
||||
a) Accompany the combined library with a copy of the same work based
|
||||
on the Library, uncombined with any other library facilities,
|
||||
conveyed under the terms of this License.
|
||||
|
||||
b) Give prominent notice with the combined library that part of it
|
||||
is a work based on the Library, and explaining where to find the
|
||||
accompanying uncombined form of the same work.
|
||||
|
||||
6. Revised Versions of the GNU Lesser General Public License.
|
||||
|
||||
The Free Software Foundation may publish revised and/or new versions
|
||||
of the GNU Lesser General Public License from time to time. Such new
|
||||
versions will be similar in spirit to the present version, but may
|
||||
differ in detail to address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the
|
||||
Library as you received it specifies that a certain numbered version
|
||||
of the GNU Lesser General Public License "or any later version"
|
||||
applies to it, you have the option of following the terms and
|
||||
conditions either of that published version or of any later version
|
||||
published by the Free Software Foundation. If the Library as you
|
||||
received it does not specify a version number of the GNU Lesser
|
||||
General Public License, you may choose any version of the GNU Lesser
|
||||
General Public License ever published by the Free Software Foundation.
|
||||
|
||||
If the Library as you received it specifies that a proxy can decide
|
||||
whether future versions of the GNU Lesser General Public License shall
|
||||
apply, that proxy's public statement of acceptance of any version is
|
||||
permanent authorization for you to choose that version for the
|
||||
Library.
|
||||
Binary file not shown.
BIN
licenses/psycopg-3.1.8.tar.gz
Normal file
BIN
licenses/psycopg-3.1.8.tar.gz
Normal file
Binary file not shown.
24
licenses/wrapt.txt
Normal file
24
licenses/wrapt.txt
Normal file
@@ -0,0 +1,24 @@
|
||||
Copyright (c) 2013-2023, Graham Dumpleton
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
|
||||
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGE.
|
||||
@@ -9,20 +9,20 @@ cryptography
|
||||
Cython<3 # Since the bump to PyYAML 5.4.1 this is now a mandatory dep
|
||||
daphne
|
||||
distro
|
||||
django==3.2.16 # see UPGRADE BLOCKERs https://github.com/ansible/awx/security/dependabot/67
|
||||
django==4.2 # see UPGRADE BLOCKERs
|
||||
django-auth-ldap
|
||||
django-cors-headers
|
||||
django-crum
|
||||
django-extensions
|
||||
django-guid==3.2.1
|
||||
django-oauth-toolkit==1.4.1
|
||||
django-oauth-toolkit<2.0.0 # Version 2.0.0 has breaking changes that will need to be worked out before upgrading
|
||||
django-polymorphic
|
||||
django-pglocks
|
||||
django-redis
|
||||
django-solo
|
||||
django-split-settings==1.0.0 # We hit a strange issue where the release process errored when upgrading past 1.0.0 see UPGRADE BLOCKERS
|
||||
django-taggit
|
||||
djangorestframework==3.13.1
|
||||
djangorestframework
|
||||
djangorestframework-yaml
|
||||
filelock
|
||||
GitPython>=3.1.30 # CVE-2022-24439
|
||||
|
||||
@@ -11,7 +11,7 @@ ansiconv==1.0.0
|
||||
# via -r /awx_devel/requirements/requirements.in
|
||||
asciichartpy==1.5.25
|
||||
# via -r /awx_devel/requirements/requirements.in
|
||||
asgiref==3.5.2
|
||||
asgiref==3.6.0
|
||||
# via
|
||||
# channels
|
||||
# channels-redis
|
||||
@@ -74,6 +74,7 @@ cryptography==38.0.4
|
||||
# adal
|
||||
# autobahn
|
||||
# azure-keyvault
|
||||
# jwcrypto
|
||||
# pyopenssl
|
||||
# service-identity
|
||||
# social-auth-core
|
||||
@@ -91,9 +92,11 @@ defusedxml==0.7.1
|
||||
# via
|
||||
# python3-openid
|
||||
# social-auth-core
|
||||
deprecated==1.2.13
|
||||
# via jwcrypto
|
||||
distro==1.8.0
|
||||
# via -r /awx_devel/requirements/requirements.in
|
||||
django==3.2.16
|
||||
django==4.2
|
||||
# via
|
||||
# -r /awx_devel/requirements/requirements.in
|
||||
# channels
|
||||
@@ -118,7 +121,7 @@ django-extensions==3.2.1
|
||||
# via -r /awx_devel/requirements/requirements.in
|
||||
django-guid==3.2.1
|
||||
# via -r /awx_devel/requirements/requirements.in
|
||||
django-oauth-toolkit==1.4.1
|
||||
django-oauth-toolkit==1.7.1
|
||||
# via -r /awx_devel/requirements/requirements.in
|
||||
django-pglocks==1.0.4
|
||||
# via -r /awx_devel/requirements/requirements.in
|
||||
@@ -133,7 +136,7 @@ django-split-settings==1.0.0
|
||||
# via -r /awx_devel/requirements/requirements.in
|
||||
django-taggit==3.1.0
|
||||
# via -r /awx_devel/requirements/requirements.in
|
||||
djangorestframework==3.13.1
|
||||
djangorestframework==3.14.0
|
||||
# via -r /awx_devel/requirements/requirements.in
|
||||
djangorestframework-yaml==2.0.0
|
||||
# via -r /awx_devel/requirements/requirements.in
|
||||
@@ -210,6 +213,8 @@ json-log-formatter==0.5.1
|
||||
# via -r /awx_devel/requirements/requirements.in
|
||||
jsonschema==4.17.3
|
||||
# via -r /awx_devel/requirements/requirements.in
|
||||
jwcrypto==1.4.2
|
||||
# via django-oauth-toolkit
|
||||
kubernetes==25.3.0
|
||||
# via openshift
|
||||
lockfile==0.12.2
|
||||
@@ -266,7 +271,7 @@ prometheus-client==0.15.0
|
||||
# via -r /awx_devel/requirements/requirements.in
|
||||
psutil==5.9.4
|
||||
# via -r /awx_devel/requirements/requirements.in
|
||||
psycopg==3.1.4
|
||||
psycopg==3.1.8
|
||||
# via -r /awx_devel/requirements/requirements.in
|
||||
psycopg2==2.9.5
|
||||
# via -r /awx_devel/requirements/requirements.in
|
||||
@@ -329,7 +334,6 @@ python3-openid==3.2.0
|
||||
# via -r /awx_devel/requirements/requirements_git.txt
|
||||
pytz==2022.6
|
||||
# via
|
||||
# django
|
||||
# djangorestframework
|
||||
# irc
|
||||
# tempora
|
||||
@@ -444,6 +448,8 @@ websocket-client==1.4.2
|
||||
# via kubernetes
|
||||
wheel==0.38.4
|
||||
# via -r /awx_devel/requirements/requirements.in
|
||||
wrapt==1.15.0
|
||||
# via deprecated
|
||||
xmlsec==1.3.13
|
||||
# via python3-saml
|
||||
yarl==1.8.1
|
||||
|
||||
Reference in New Issue
Block a user