mirror of
https://github.com/ansible/awx.git
synced 2026-02-09 05:24:42 -03:30
Compare commits
23 Commits
24.6.1-doc
...
feature_ui
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
853730acb9 | ||
|
|
f1448fced1 | ||
|
|
7697b6a69b | ||
|
|
22a491c32c | ||
|
|
cbd9dce940 | ||
|
|
a4fdcc1cca | ||
|
|
df95439008 | ||
|
|
acd834df8b | ||
|
|
587f0ecf98 | ||
|
|
5a2091f7bf | ||
|
|
fa7423819a | ||
|
|
fde8af9f11 | ||
|
|
209e7e27b1 | ||
|
|
6c7d29a982 | ||
|
|
282ba36839 | ||
|
|
b727d2c3b3 | ||
|
|
7fc3d5c7c7 | ||
|
|
4e055f46c4 | ||
|
|
f595985b7c | ||
|
|
ea232315bf | ||
|
|
ee251812b5 | ||
|
|
00ba1ea569 | ||
|
|
d91af132c1 |
2
.github/actions/awx_devel_image/action.yml
vendored
2
.github/actions/awx_devel_image/action.yml
vendored
@@ -24,7 +24,7 @@ runs:
|
||||
|
||||
- name: Pre-pull latest devel image to warm cache
|
||||
shell: bash
|
||||
run: docker pull ghcr.io/${OWNER_LC}/awx_devel:${{ github.base_ref }}
|
||||
run: docker pull -q ghcr.io/${OWNER_LC}/awx_devel:${{ github.base_ref }}
|
||||
|
||||
- name: Build image for current source checkout
|
||||
shell: bash
|
||||
|
||||
48
.github/workflows/dab-release.yml
vendored
Normal file
48
.github/workflows/dab-release.yml
vendored
Normal file
@@ -0,0 +1,48 @@
|
||||
---
|
||||
name: django-ansible-base requirements update
|
||||
on:
|
||||
workflow_dispatch:
|
||||
schedule:
|
||||
- cron: '0 6 * * *' # once an day @ 6 AM
|
||||
permissions:
|
||||
pull-requests: write
|
||||
contents: write
|
||||
jobs:
|
||||
dab-pin-newest:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- id: dab-release
|
||||
name: Get current django-ansible-base release version
|
||||
uses: pozetroninc/github-action-get-latest-release@2a61c339ea7ef0a336d1daa35ef0cb1418e7676c # v0.8.0
|
||||
with:
|
||||
owner: ansible
|
||||
repo: django-ansible-base
|
||||
excludes: prerelease, draft
|
||||
|
||||
- name: Check out respository code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- id: dab-pinned
|
||||
name: Get current django-ansible-base pinned version
|
||||
run:
|
||||
echo "version=$(requirements/django-ansible-base-pinned-version.sh)" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Update django-ansible-base pinned version to upstream release
|
||||
run:
|
||||
requirements/django-ansible-base-pinned-version.sh -s ${{ steps.dab-release.outputs.release }}
|
||||
|
||||
- name: Create Pull Request
|
||||
uses: peter-evans/create-pull-request@c5a7806660adbe173f04e3e038b0ccdcd758773c # v6
|
||||
with:
|
||||
base: devel
|
||||
branch: bump-django-ansible-base
|
||||
title: Bump django-ansible-base to ${{ steps.dab-release.outputs.release }}
|
||||
body: |
|
||||
Automated .github/workflows/dab-release.yml
|
||||
|
||||
django-ansible-base upstream released version == ${{ steps.dab-release.outputs.release }}
|
||||
requirements_git.txt django-ansible-base pinned version == ${{ steps.dab-pinned.outputs.version }}
|
||||
commit-message: |
|
||||
Update django-ansible-base version to ${{ steps.dab-pinned.outputs.version }}
|
||||
add-paths:
|
||||
requirements/requirements_git.txt
|
||||
16
.github/workflows/devel_images.yml
vendored
16
.github/workflows/devel_images.yml
vendored
@@ -60,16 +60,26 @@ jobs:
|
||||
run: |
|
||||
echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u ${{ github.actor }} --password-stdin
|
||||
|
||||
- name: Setup node and npm
|
||||
- name: Setup node and npm for old UI build
|
||||
uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: '16.13.1'
|
||||
node-version: '16'
|
||||
if: matrix.build-targets.image-name == 'awx'
|
||||
|
||||
- name: Prebuild UI for awx image (to speed up build process)
|
||||
- name: Prebuild old-UI for awx image (to speed up build process)
|
||||
run: |
|
||||
sudo apt-get install gettext
|
||||
make ui-release
|
||||
if: matrix.build-targets.image-name == 'awx'
|
||||
|
||||
- name: Setup node and npm for the new UI build
|
||||
uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: '18'
|
||||
if: matrix.build-targets.image-name == 'awx'
|
||||
|
||||
- name: Prebuild new UI for awx image (to speed up build process)
|
||||
run: |
|
||||
make ui-next
|
||||
if: matrix.build-targets.image-name == 'awx'
|
||||
|
||||
|
||||
6
.github/workflows/stage.yml
vendored
6
.github/workflows/stage.yml
vendored
@@ -136,9 +136,9 @@ jobs:
|
||||
- name: Pulling images for test deployment with awx-operator
|
||||
# awx operator molecue test expect to kind load image and buildx exports image to registry and not local
|
||||
run: |
|
||||
docker pull ${AWX_OPERATOR_TEST_IMAGE}
|
||||
docker pull ${AWX_EE_TEST_IMAGE}
|
||||
docker pull ${AWX_TEST_IMAGE}:${AWX_TEST_VERSION}
|
||||
docker pull -q ${AWX_OPERATOR_TEST_IMAGE}
|
||||
docker pull -q ${AWX_EE_TEST_IMAGE}
|
||||
docker pull -q ${AWX_TEST_IMAGE}:${AWX_TEST_VERSION}
|
||||
|
||||
- name: Run test deployment with awx-operator
|
||||
working-directory: awx-operator
|
||||
|
||||
2
.github/workflows/upload_schema.yml
vendored
2
.github/workflows/upload_schema.yml
vendored
@@ -34,7 +34,7 @@ jobs:
|
||||
|
||||
- name: Pre-pull image to warm build cache
|
||||
run: |
|
||||
docker pull ghcr.io/${{ github.repository_owner }}/awx_devel:${GITHUB_REF##*/} || :
|
||||
docker pull -q ghcr.io/${{ github.repository_owner }}/awx_devel:${GITHUB_REF##*/} || :
|
||||
|
||||
- name: Build image
|
||||
run: |
|
||||
|
||||
@@ -1419,10 +1419,6 @@ class ExecutionEnvironmentAccess(BaseAccess):
|
||||
else:
|
||||
if self.user not in obj.organization.execution_environment_admin_role:
|
||||
raise PermissionDenied
|
||||
if data and 'organization' in data:
|
||||
new_org = get_object_from_data('organization', Organization, data, obj=obj)
|
||||
if not new_org or self.user not in new_org.execution_environment_admin_role:
|
||||
return False
|
||||
return self.check_related('organization', Organization, data, obj=obj, role_field='execution_environment_admin_role')
|
||||
|
||||
def can_delete(self, obj):
|
||||
@@ -2100,15 +2096,18 @@ class WorkflowJobTemplateAccess(NotificationAttachMixin, BaseAccess):
|
||||
|
||||
if not self.check_related('organization', Organization, data, role_field='workflow_admin_role', mandatory=True):
|
||||
if data.get('organization', None) is None:
|
||||
self.messages['organization'] = [_('An organization is required to create a workflow job template for normal user')]
|
||||
if self.save_messages:
|
||||
self.messages['organization'] = [_('An organization is required to create a workflow job template for normal user')]
|
||||
return False
|
||||
|
||||
if not self.check_related('inventory', Inventory, data, role_field='use_role'):
|
||||
self.messages['inventory'] = [_('You do not have use_role to the inventory')]
|
||||
if self.save_messages:
|
||||
self.messages['inventory'] = [_('You do not have use_role to the inventory')]
|
||||
return False
|
||||
|
||||
if not self.check_related('execution_environment', ExecutionEnvironment, data, role_field='read_role'):
|
||||
self.messages['execution_environment'] = [_('You do not have read_role to the execution environment')]
|
||||
if self.save_messages:
|
||||
self.messages['execution_environment'] = [_('You do not have read_role to the execution environment')]
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
@@ -277,7 +277,6 @@ def setup_managed_role_definitions(apps, schema_editor):
|
||||
to_create = {
|
||||
'object_admin': '{cls.__name__} Admin',
|
||||
'org_admin': 'Organization Admin',
|
||||
'org_audit': 'Organization Audit',
|
||||
'org_children': 'Organization {cls.__name__} Admin',
|
||||
'special': '{cls.__name__} {action}',
|
||||
}
|
||||
@@ -334,12 +333,19 @@ def setup_managed_role_definitions(apps, schema_editor):
|
||||
for perm in special_perms:
|
||||
action = perm.codename.split('_')[0]
|
||||
view_perm = Permission.objects.get(content_type=ct, codename__startswith='view_')
|
||||
perm_list = [perm, view_perm]
|
||||
# Handle special-case where adhoc role also listed use permission
|
||||
if action == 'adhoc':
|
||||
for other_perm in object_perms:
|
||||
if other_perm.codename == 'use_inventory':
|
||||
perm_list.append(other_perm)
|
||||
break
|
||||
managed_role_definitions.append(
|
||||
get_or_create_managed(
|
||||
to_create['special'].format(cls=cls, action=action.title()),
|
||||
f'Has {action} permissions to a single {cls._meta.verbose_name}',
|
||||
ct,
|
||||
[perm, view_perm],
|
||||
perm_list,
|
||||
RoleDefinition,
|
||||
)
|
||||
)
|
||||
@@ -355,18 +361,40 @@ def setup_managed_role_definitions(apps, schema_editor):
|
||||
)
|
||||
)
|
||||
|
||||
if 'org_audit' in to_create:
|
||||
audit_permissions = [perm for perm in org_perms if perm.codename.startswith('view_')]
|
||||
audit_permissions.append(Permission.objects.get(codename='audit_organization'))
|
||||
managed_role_definitions.append(
|
||||
get_or_create_managed(
|
||||
to_create['org_audit'].format(cls=Organization),
|
||||
'Has permission to view all objects inside of a single organization',
|
||||
org_ct,
|
||||
audit_permissions,
|
||||
RoleDefinition,
|
||||
)
|
||||
# Special "organization action" roles
|
||||
audit_permissions = [perm for perm in org_perms if perm.codename.startswith('view_')]
|
||||
audit_permissions.append(Permission.objects.get(codename='audit_organization'))
|
||||
managed_role_definitions.append(
|
||||
get_or_create_managed(
|
||||
'Organization Audit',
|
||||
'Has permission to view all objects inside of a single organization',
|
||||
org_ct,
|
||||
audit_permissions,
|
||||
RoleDefinition,
|
||||
)
|
||||
)
|
||||
|
||||
org_execute_permissions = {'view_jobtemplate', 'execute_jobtemplate', 'view_workflowjobtemplate', 'execute_workflowjobtemplate', 'view_organization'}
|
||||
managed_role_definitions.append(
|
||||
get_or_create_managed(
|
||||
'Organization Execute',
|
||||
'Has permission to execute all runnable objects in the organization',
|
||||
org_ct,
|
||||
[perm for perm in org_perms if perm.codename in org_execute_permissions],
|
||||
RoleDefinition,
|
||||
)
|
||||
)
|
||||
|
||||
org_approval_permissions = {'view_organization', 'view_workflowjobtemplate', 'approve_workflowjobtemplate'}
|
||||
managed_role_definitions.append(
|
||||
get_or_create_managed(
|
||||
'Organization Approval',
|
||||
'Has permission to approve any workflow steps within a single organization',
|
||||
org_ct,
|
||||
[perm for perm in org_perms if perm.codename in org_approval_permissions],
|
||||
RoleDefinition,
|
||||
)
|
||||
)
|
||||
|
||||
unexpected_role_definitions = RoleDefinition.objects.filter(managed=True).exclude(pk__in=[rd.pk for rd in managed_role_definitions])
|
||||
for role_definition in unexpected_role_definitions:
|
||||
|
||||
@@ -321,13 +321,14 @@ class Credential(PasswordFieldsModel, CommonModelNameNotUnique, ResourceMixin):
|
||||
raise ValueError('{} is not a dynamic input field'.format(field_name))
|
||||
|
||||
def validate_role_assignment(self, actor, role_definition):
|
||||
if isinstance(actor, User):
|
||||
if actor.is_superuser or Organization.access_qs(actor, 'change').filter(id=self.organization.id).exists():
|
||||
return
|
||||
if isinstance(actor, Team):
|
||||
if actor.organization == self.organization:
|
||||
return
|
||||
raise DRFValidationError({'detail': _(f"You cannot grant credential access to a {actor._meta.object_name} not in the credentials' organization")})
|
||||
if self.organization:
|
||||
if isinstance(actor, User):
|
||||
if actor.is_superuser or Organization.access_qs(actor, 'member').filter(id=self.organization.id).exists():
|
||||
return
|
||||
if isinstance(actor, Team):
|
||||
if actor.organization == self.organization:
|
||||
return
|
||||
raise DRFValidationError({'detail': _(f"You cannot grant credential access to a {actor._meta.object_name} not in the credentials' organization")})
|
||||
|
||||
|
||||
class CredentialType(CommonModelNameNotUnique):
|
||||
|
||||
@@ -68,5 +68,5 @@ class ExecutionEnvironment(CommonModel):
|
||||
raise ValidationError({'user': _('User must have view permission to Execution Environment organization')})
|
||||
if actor._meta.model_name == 'team':
|
||||
organization_cls = self._meta.get_field('organization').related_model
|
||||
if self.orgaanization not in organization_cls.access_qs(actor, 'view'):
|
||||
if self.organization not in organization_cls.access_qs(actor, 'view'):
|
||||
raise ValidationError({'team': _('Team must have view permission to Execution Environment organization')})
|
||||
|
||||
@@ -689,9 +689,15 @@ def sync_parents_to_new_rbac(instance, action, model, pk_set, reverse, **kwargs)
|
||||
|
||||
for role_id in pk_set:
|
||||
if reverse:
|
||||
child_role = Role.objects.get(id=role_id)
|
||||
try:
|
||||
child_role = Role.objects.get(id=role_id)
|
||||
except Role.DoesNotExist:
|
||||
continue
|
||||
else:
|
||||
parent_role = Role.objects.get(id=role_id)
|
||||
try:
|
||||
parent_role = Role.objects.get(id=role_id)
|
||||
except Role.DoesNotExist:
|
||||
continue
|
||||
|
||||
# To a fault, we want to avoid running this if triggered from implicit_parents management
|
||||
# we only want to do anything if we know for sure this is a non-implicit team role
|
||||
|
||||
@@ -980,5 +980,15 @@ def periodic_resource_sync():
|
||||
if acquired is False:
|
||||
logger.debug("Not running periodic_resource_sync, another task holds lock")
|
||||
return
|
||||
logger.debug("Running periodic resource sync")
|
||||
|
||||
SyncExecutor().run()
|
||||
executor = SyncExecutor()
|
||||
executor.run()
|
||||
for key, item_list in executor.results.items():
|
||||
if not item_list or key == 'noop':
|
||||
continue
|
||||
# Log creations and conflicts
|
||||
if len(item_list) > 10 and settings.LOG_AGGREGATOR_LEVEL != 'DEBUG':
|
||||
logger.info(f'Periodic resource sync {key}, first 10 items:\n{item_list[:10]}')
|
||||
else:
|
||||
logger.info(f'Periodic resource sync {key}:\n{item_list}')
|
||||
|
||||
@@ -128,7 +128,7 @@ def test_assign_credential_to_user_of_another_org(setup_managed_roles, credentia
|
||||
rd = RoleDefinition.objects.get(name="Credential Admin")
|
||||
credential.organization = organization
|
||||
credential.save(update_fields=['organization'])
|
||||
assert credential.organization not in Organization.access_qs(rando, 'change')
|
||||
assert credential.organization not in Organization.access_qs(rando, 'member')
|
||||
url = django_reverse('roleuserassignment-list')
|
||||
resp = post(url=url, data={"user": rando.id, "role_definition": rd.id, "object_id": credential.id}, user=admin_user, expect=400)
|
||||
assert "You cannot grant credential access to a User not in the credentials' organization" in str(resp.data)
|
||||
@@ -139,7 +139,7 @@ def test_assign_credential_to_user_of_another_org(setup_managed_roles, credentia
|
||||
post(url=url, data={"user": rando.id, "role_definition": rd.id, "object_id": credential.id}, user=admin_user, expect=201)
|
||||
|
||||
# can assign credential to org_admin
|
||||
assert credential.organization in Organization.access_qs(org_admin, 'change')
|
||||
assert credential.organization in Organization.access_qs(org_admin, 'member')
|
||||
post(url=url, data={"user": org_admin.id, "role_definition": rd.id, "object_id": credential.id}, user=admin_user, expect=201)
|
||||
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
from unittest import mock
|
||||
import json
|
||||
|
||||
import pytest
|
||||
|
||||
@@ -6,11 +7,13 @@ from django.contrib.contenttypes.models import ContentType
|
||||
|
||||
from crum import impersonate
|
||||
|
||||
from awx.main.models.rbac import get_role_from_object_role, give_creator_permissions
|
||||
from awx.main.fields import ImplicitRoleField
|
||||
from awx.main.models.rbac import get_role_from_object_role, give_creator_permissions, get_role_codenames, get_role_definition
|
||||
from awx.main.models import User, Organization, WorkflowJobTemplate, WorkflowJobTemplateNode, Team
|
||||
from awx.api.versioning import reverse
|
||||
|
||||
from ansible_base.rbac.models import RoleUserAssignment, RoleDefinition
|
||||
from ansible_base.rbac import permission_registry
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
@@ -24,6 +27,7 @@ from ansible_base.rbac.models import RoleUserAssignment, RoleDefinition
|
||||
'auditor_role',
|
||||
'read_role',
|
||||
'execute_role',
|
||||
'approval_role',
|
||||
'notification_admin_role',
|
||||
],
|
||||
)
|
||||
@@ -39,6 +43,37 @@ def test_round_trip_roles(organization, rando, role_name, setup_managed_roles):
|
||||
assert old_role.id == getattr(organization, role_name).id
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
@pytest.mark.parametrize('model', sorted(permission_registry.all_registered_models, key=lambda cls: cls._meta.model_name))
|
||||
def test_role_migration_matches(request, model, setup_managed_roles):
|
||||
fixture_name = model._meta.verbose_name.replace(' ', '_')
|
||||
obj = request.getfixturevalue(fixture_name)
|
||||
role_ct = 0
|
||||
for field in obj._meta.get_fields():
|
||||
if isinstance(field, ImplicitRoleField):
|
||||
if field.name == 'read_role':
|
||||
continue # intentionally left as "Compat" roles
|
||||
role_ct += 1
|
||||
old_role = getattr(obj, field.name)
|
||||
old_codenames = set(get_role_codenames(old_role))
|
||||
rd = get_role_definition(old_role)
|
||||
new_codenames = set(rd.permissions.values_list('codename', flat=True))
|
||||
# all the old roles should map to a non-Compat role definition
|
||||
if 'Compat' not in rd.name:
|
||||
model_rds = RoleDefinition.objects.filter(content_type=ContentType.objects.get_for_model(obj))
|
||||
rd_data = {}
|
||||
for rd in model_rds:
|
||||
rd_data[rd.name] = list(rd.permissions.values_list('codename', flat=True))
|
||||
assert (
|
||||
'Compat' not in rd.name
|
||||
), f'Permissions for old vs new roles did not match.\nold {field.name}: {old_codenames}\nnew:\n{json.dumps(rd_data, indent=2)}'
|
||||
assert new_codenames == set(old_codenames)
|
||||
|
||||
# In the old system these models did not have object-level roles, all others expect some model roles
|
||||
if model._meta.model_name not in ('notificationtemplate', 'executionenvironment'):
|
||||
assert role_ct > 0
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_role_naming(setup_managed_roles):
|
||||
qs = RoleDefinition.objects.filter(content_type=ContentType.objects.get(model='jobtemplate'), name__endswith='dmin')
|
||||
|
||||
@@ -3,7 +3,7 @@ import pytest
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
|
||||
from awx.main.access import ExecutionEnvironmentAccess
|
||||
from awx.main.models import ExecutionEnvironment, Organization
|
||||
from awx.main.models import ExecutionEnvironment, Organization, Team
|
||||
from awx.main.models.rbac import get_role_codenames
|
||||
|
||||
from awx.api.versioning import reverse
|
||||
@@ -77,6 +77,26 @@ def test_org_member_required_for_assignment(org_ee, ee_rd, rando, admin_user, po
|
||||
assert 'User must have view permission to Execution Environment organization' in str(r.data)
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_team_view_permission_required(org_ee, ee_rd, rando, admin_user, post):
|
||||
org2 = Organization.objects.create(name='a different team')
|
||||
team = Team.objects.create(name='a team', organization=org2)
|
||||
team.member_role.members.add(rando)
|
||||
assert org_ee not in ExecutionEnvironmentAccess(rando).get_queryset() # user can not view the EE
|
||||
url = django_reverse('roleteamassignment-list')
|
||||
r = post(url, {'role_definition': ee_rd.pk, 'team': team.id, 'object_id': org_ee.pk}, user=admin_user, expect=400)
|
||||
assert 'Team must have view permission to Execution Environment organization' in str(r.data)
|
||||
|
||||
org_view_rd = RoleDefinition.objects.create_from_permissions(
|
||||
name='organization viewer role', permissions=['view_organization'], content_type=ContentType.objects.get_for_model(Organization)
|
||||
)
|
||||
org_view_rd.give_permission(team, org_ee.organization)
|
||||
assert org_ee in ExecutionEnvironmentAccess(rando).get_queryset() # user can view the EE now
|
||||
# can give object roles to the team now
|
||||
post(url, {'role_definition': ee_rd.pk, 'team': team.id, 'object_id': org_ee.pk}, user=admin_user, expect=201)
|
||||
assert rando.has_obj_perm(org_ee, 'change')
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_give_object_permission_to_ee(org_ee, ee_rd, org_member, check_user_capabilities):
|
||||
access = ExecutionEnvironmentAccess(org_member)
|
||||
@@ -85,11 +105,29 @@ def test_give_object_permission_to_ee(org_ee, ee_rd, org_member, check_user_capa
|
||||
check_user_capabilities(org_member, org_ee, {'edit': False, 'delete': False, 'copy': False})
|
||||
|
||||
ee_rd.give_permission(org_member, org_ee)
|
||||
assert access.can_change(org_ee, {'name': 'new'})
|
||||
assert access.can_change(org_ee, {'name': 'new', 'organization': org_ee.organization.id})
|
||||
|
||||
check_user_capabilities(org_member, org_ee, {'edit': True, 'delete': True, 'copy': False})
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_need_related_organization_access(org_ee, ee_rd, org_member):
|
||||
org2 = Organization.objects.create(name='another organization')
|
||||
ee_rd.give_permission(org_member, org_ee)
|
||||
org2.member_role.members.add(org_member)
|
||||
access = ExecutionEnvironmentAccess(org_member)
|
||||
assert access.can_change(org_ee, {'name': 'new', 'organization': org_ee.organization})
|
||||
assert access.can_change(org_ee, {'name': 'new', 'organization': org_ee.organization.id})
|
||||
assert not access.can_change(org_ee, {'name': 'new', 'organization': org2.id})
|
||||
assert not access.can_change(org_ee, {'name': 'new', 'organization': org2})
|
||||
|
||||
# User can make the change if they have relevant permission to the new organization
|
||||
org_ee.organization.execution_environment_admin_role.members.add(org_member)
|
||||
org2.execution_environment_admin_role.members.add(org_member)
|
||||
assert access.can_change(org_ee, {'name': 'new', 'organization': org2.id})
|
||||
assert access.can_change(org_ee, {'name': 'new', 'organization': org2})
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
@pytest.mark.parametrize('style', ['new', 'old'])
|
||||
def test_give_org_permission_to_ee(org_ee, organization, org_member, check_user_capabilities, style, org_ee_rd):
|
||||
@@ -103,5 +141,5 @@ def test_give_org_permission_to_ee(org_ee, organization, org_member, check_user_
|
||||
else:
|
||||
organization.execution_environment_admin_role.members.add(org_member)
|
||||
|
||||
assert access.can_change(org_ee, {'name': 'new'})
|
||||
assert access.can_change(org_ee, {'name': 'new', 'organization': organization.id})
|
||||
check_user_capabilities(org_member, org_ee, {'edit': True, 'delete': True, 'copy': True})
|
||||
|
||||
@@ -48,3 +48,17 @@ def test_org_resource_role(ext_auth, organization, rando, org_admin):
|
||||
assert access.can_attach(organization, rando, 'member_role.members') == ext_auth
|
||||
organization.member_role.members.add(rando)
|
||||
assert access.can_unattach(organization, rando, 'member_role.members') == ext_auth
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_delete_org_while_workflow_active(workflow_job_template):
|
||||
'''
|
||||
Delete org while workflow job is active (i.e. changing status)
|
||||
'''
|
||||
assert workflow_job_template.organization # sanity check
|
||||
wj = workflow_job_template.create_unified_job() # status should be new
|
||||
workflow_job_template.organization.delete()
|
||||
wj.refresh_from_db()
|
||||
assert wj.status != 'pending' # sanity check
|
||||
wj.status = 'pending' # status needs to change in order to trigger workflow_job_template.save()
|
||||
wj.save(update_fields=['status'])
|
||||
|
||||
@@ -17,13 +17,13 @@ def advisory_lock(*args, lock_session_timeout_milliseconds=0, **kwargs):
|
||||
with connection.cursor() as cur:
|
||||
idle_in_transaction_session_timeout = cur.execute('SHOW idle_in_transaction_session_timeout').fetchone()[0]
|
||||
idle_session_timeout = cur.execute('SHOW idle_session_timeout').fetchone()[0]
|
||||
cur.execute(f"SET idle_in_transaction_session_timeout = {lock_session_timeout_milliseconds}")
|
||||
cur.execute(f"SET idle_session_timeout = {lock_session_timeout_milliseconds}")
|
||||
cur.execute(f"SET idle_in_transaction_session_timeout = '{lock_session_timeout_milliseconds}'")
|
||||
cur.execute(f"SET idle_session_timeout = '{lock_session_timeout_milliseconds}'")
|
||||
with django_pglocks_advisory_lock(*args, **kwargs) as internal_lock:
|
||||
yield internal_lock
|
||||
if lock_session_timeout_milliseconds > 0:
|
||||
with connection.cursor() as cur:
|
||||
cur.execute(f"SET idle_in_transaction_session_timeout = {idle_in_transaction_session_timeout}")
|
||||
cur.execute(f"SET idle_session_timeout = {idle_session_timeout}")
|
||||
cur.execute(f"SET idle_in_transaction_session_timeout = '{idle_in_transaction_session_timeout}'")
|
||||
cur.execute(f"SET idle_session_timeout = '{idle_session_timeout}'")
|
||||
else:
|
||||
yield True
|
||||
|
||||
@@ -64,7 +64,7 @@
|
||||
<div class="col-sm-6">
|
||||
</div>
|
||||
<div class="col-sm-6 footer-copyright">
|
||||
Copyright © 2021 <a href="http://www.redhat.com" target="_blank">Red Hat</a>, Inc. All Rights Reserved.
|
||||
Copyright © 2024 <a href="http://www.redhat.com" target="_blank">Red Hat</a>, Inc. All Rights Reserved.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -59,7 +59,7 @@ function ActivityStream() {
|
||||
{
|
||||
page: 1,
|
||||
page_size: 20,
|
||||
order_by: '-timestamp',
|
||||
order_by: '-id',
|
||||
},
|
||||
['id', 'page', 'page_size']
|
||||
);
|
||||
|
||||
@@ -23,7 +23,6 @@ Authentication
|
||||
.. index::
|
||||
single: social authentication
|
||||
single: authentication
|
||||
single: enterprise authentication
|
||||
pair: configuration; authentication
|
||||
|
||||
.. include:: ./configure_awx_authentication.rst
|
||||
|
||||
@@ -18,7 +18,7 @@ For example, if you uploaded a specific logo, and added the following text:
|
||||
:alt: Edit User Interface Settings form populated with custom text and logo.
|
||||
|
||||
|
||||
The Tower login dialog would look like this:
|
||||
The AWX login dialog would look like this:
|
||||
|
||||
.. image:: ../common/images/configure-awx-ui-angry-spud-login.png
|
||||
:alt: AWX login screen with custom text and logo.
|
||||
|
||||
@@ -8,7 +8,7 @@ Authentication Methods Using the API
|
||||
pair: OAuth 2 Token; authentication
|
||||
pair: SSO; authentication
|
||||
|
||||
This chapter describes the numerous enterprise authentication methods, the best use case for each, and examples:
|
||||
This chapter describes different authentication methods, the best use case for each, and examples:
|
||||
|
||||
.. contents::
|
||||
:local:
|
||||
|
||||
@@ -94,7 +94,7 @@ Field lookups may also be used for more advanced queries, by appending the looku
|
||||
|
||||
The following field lookups are supported:
|
||||
|
||||
- ``exact``: Exact match (default lookup if not specified).
|
||||
- ``exact``: Exact match (default lookup if not specified, refer to the following note for more information).
|
||||
- ``iexact``: Case-insensitive version of exact.
|
||||
- ``contains``: Field contains value.
|
||||
- ``icontains``: Case-insensitive version of contains.
|
||||
@@ -122,3 +122,18 @@ Filtering based on the requesting user's level of access by query string paramet
|
||||
|
||||
- ``role_level``: Level of role to filter on, such as ``admin_role``
|
||||
|
||||
|
||||
.. note::
|
||||
|
||||
Previous releases of AWX returned queries with **__exact** results by default, but you may find that the latest versions are returning a larger subset instead. As a workaround, set the ``limit`` to ``?limit__exact`` for the default filter. For example, ``/api/v2/jobs/?limit__exact=example.domain.com`` results in:
|
||||
|
||||
::
|
||||
|
||||
{
|
||||
"count": 1,
|
||||
"next": null,
|
||||
"previous": null,
|
||||
"results": [
|
||||
...
|
||||
|
||||
.. this note is generically written for AWX. For downstream, the change started in AAP 2.0 so we can be more specific if necessary.
|
||||
|
||||
@@ -189,7 +189,7 @@ Authentication Enhancements
|
||||
pair: features; authentication
|
||||
pair: features; OAuth 2 token
|
||||
|
||||
AWX supports LDAP, SAML, token-based authentication. Enhanced LDAP and SAML support allows you to integrate your enterprise account information in a more flexible manner. Token-based Authentication allows for easily authentication of third-party tools and services with AWX via integrated OAuth 2 token support.
|
||||
AWX supports LDAP, SAML, token-based authentication. Enhanced LDAP and SAML support allows you to integrate your account information in a more flexible manner. Token-based Authentication allows for easily authentication of third-party tools and services with AWX via integrated OAuth 2 token support.
|
||||
|
||||
Cluster Management
|
||||
~~~~~~~~~~~~~~~~~~~~
|
||||
@@ -240,9 +240,8 @@ Job Distribution
|
||||
pair: features; jobs, slicing
|
||||
pair: features; jobs, distribution
|
||||
|
||||
As automation moves enterprise-wide, the need to automate at scale grows. AWX offer the ability to take a fact gathering or
|
||||
configuration job running across thousands of machines and slice it into individual job slices that can be distributed across your AWX cluster for increased reliability, faster job completion, and better cluster utilization. If you need to change a parameter across 15,000 switches at
|
||||
scale, or gather information across your multi-thousand-node RHEL estate, you can now do so easily.
|
||||
AWX offers the ability to take a fact gathering or configuration job running across thousands of machines and slice it into individual job slices that can be distributed across your AWX cluster for increased reliability, faster job completion, and better cluster utilization.
|
||||
If you need to change a parameter across 15,000 switches at scale, or gather information across your multi-thousand-node RHEL estate, you can now do so easily.
|
||||
|
||||
|
||||
Support for deployment in a FIPS-enabled environment
|
||||
|
||||
@@ -21,7 +21,7 @@ DAB RBAC
|
||||
single: roles
|
||||
pair: DAB; RBAC
|
||||
|
||||
This section describes the latest changes to RBAC, involving use of the ``django-ansible-base`` (DAB) library, to enhance existing roles, provide a uniformed model that is compatible with platform (enterprise) components, and allow creation of custom roles. However, the internals of the system in the backend have changes implemented, but they are not reflected yet in the AWX UI. The change to the backend maintains a compatibility layer so the “old” roles in the API still exists temporarily, until a fully-functional compatible UI replaces the existing roles.
|
||||
This section describes the latest changes to RBAC, involving use of the ``django-ansible-base`` (DAB) library, to enhance existing roles, and allow creation of custom roles. However, the internals of the system in the backend have changes implemented, but they are not reflected yet in the AWX UI. The change to the backend maintains a compatibility layer so the “old” roles in the API still exists temporarily, until a fully-functional compatible UI replaces the existing roles.
|
||||
|
||||
New functionality, specifically custom roles, are possible through direct API clients or the API browser, but the presentation in the AWX UI might not reflect the changes made in the API.
|
||||
|
||||
|
||||
77
requirements/django-ansible-base-pinned-version.sh
Executable file
77
requirements/django-ansible-base-pinned-version.sh
Executable file
@@ -0,0 +1,77 @@
|
||||
#!/bin/bash
|
||||
set +x
|
||||
|
||||
# CONSTANTS
|
||||
export REGEX_LEFT='https://github.com/ansible/django-ansible-base@'
|
||||
export REGEX_RIGHT='#egg=django-ansible-base'
|
||||
|
||||
# GLOBALS
|
||||
SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
|
||||
REQ_FILE=$SCRIPT_DIR/requirements_git.txt
|
||||
|
||||
# Pin Function
|
||||
DESIRED_VERSION=''
|
||||
Pin()
|
||||
{
|
||||
export DESIRED_VERSION
|
||||
perl -p -i -e 's/\Q$ENV{REGEX_LEFT}\E(.*?)\Q$ENV{REGEX_RIGHT}\E/$ENV{REGEX_LEFT}$ENV{DESIRED_VERSION}$ENV{REGEX_RIGHT}/g' $REQ_FILE
|
||||
}
|
||||
|
||||
# Current Function
|
||||
Current()
|
||||
{
|
||||
REQUIREMENTS_LINE=$(grep django-ansible-base $REQ_FILE)
|
||||
|
||||
echo "$REQUIREMENTS_LINE" | perl -nE 'say $1 if /\Q$ENV{REGEX_LEFT}\E(.*?)\Q$ENV{REGEX_RIGHT}\E/'
|
||||
}
|
||||
|
||||
|
||||
Help()
|
||||
{
|
||||
# Display Help
|
||||
echo ""
|
||||
echo "Help:"
|
||||
echo ""
|
||||
echo "Interact with django-ansible-base in $REQ_FILE."
|
||||
echo "By default, output the current django-ansible-base pinned version."
|
||||
echo
|
||||
echo "Syntax: scriptTemplate [-s|h|v]"
|
||||
echo "options:"
|
||||
echo "s Set django-ansible-base version to pin to."
|
||||
echo "h Print this Help."
|
||||
echo "v Verbose mode."
|
||||
echo
|
||||
}
|
||||
|
||||
if [ $# -eq 0 ]; then
|
||||
Current
|
||||
exit
|
||||
fi
|
||||
|
||||
|
||||
while getopts ":hs:" option; do
|
||||
case $option in
|
||||
h) # display Help
|
||||
Help
|
||||
exit
|
||||
;;
|
||||
s)
|
||||
DESIRED_VERSION=$OPTARG;;
|
||||
:)
|
||||
echo "Option -${OPTARG} requires an argument."
|
||||
Help
|
||||
exit 1
|
||||
;;
|
||||
\?) # Invalid option
|
||||
echo "Error: Invalid option"
|
||||
echo ""
|
||||
Help
|
||||
exit;;
|
||||
esac
|
||||
done
|
||||
|
||||
if [ -n "$DESIRED_VERSION" ]; then
|
||||
Pin
|
||||
Current
|
||||
fi
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
###
|
||||
|
||||
# Build container
|
||||
FROM quay.io/centos/centos:stream9 as builder
|
||||
FROM quay.io/centos/centos:stream9 AS builder
|
||||
|
||||
ENV LANG en_US.UTF-8
|
||||
ENV LANGUAGE en_US:en
|
||||
|
||||
Reference in New Issue
Block a user