mirror of
https://github.com/ansible/awx.git
synced 2026-01-11 01:57:35 -03:30
Add instance groups roles (#13584)
* adding roles to instance groups added ResourceMixin to Instancegroup and changed the filtered_queryset * added necessary changes to rebuild relationship between IG and roles * added description to InstanceGroupAccess * preliminary ui plug for demo purposes * preliminary ui plug for demo purposes added inventory special logic for use_role to allow attaching instance groups added more tests to handle those cases * Add access_list to InstanceGroup * scratch branch to test migration work * refactored to shorten logic * Added migration and am removing logic that enabled Org admin permissions * Add Obj admin role to JT, Inv, Org * Changed tests to reflect new permissions * refactored some of the tests * cleaned up more tests and reworded help on InstanceGroupAccess * Removed unnecessary delete of Route for instance group perms change * Fix UI tests and migration * fixed permissions on prompt for InstanceGroups * added related object roles endpoint * added ui/api function for options instance_groups * separate the migrations in order to avoid issues with migrations not being finished * changed migrations parent class to disable the activity stream error in migrations * Added logging to migration as activitystream is disabled * added clarifying comment to jobtemlateaccess and linted UI addition * renamed migrations to avoid collisions * Rename migrations to avoid collisions
This commit is contained in:
parent
7a45048463
commit
a63067da38
@ -5471,6 +5471,8 @@ class InstanceGroupSerializer(BaseSerializer):
|
||||
res = super(InstanceGroupSerializer, self).get_related(obj)
|
||||
res['jobs'] = self.reverse('api:instance_group_unified_jobs_list', kwargs={'pk': obj.pk})
|
||||
res['instances'] = self.reverse('api:instance_group_instance_list', kwargs={'pk': obj.pk})
|
||||
res['access_list'] = self.reverse('api:instance_group_access_list', kwargs={'pk': obj.pk})
|
||||
res['object_roles'] = self.reverse('api:instance_group_object_role_list', kwargs={'pk': obj.pk})
|
||||
if obj.credential:
|
||||
res['credential'] = self.reverse('api:credential_detail', kwargs={'pk': obj.credential_id})
|
||||
|
||||
|
||||
@ -3,7 +3,14 @@
|
||||
|
||||
from django.urls import re_path
|
||||
|
||||
from awx.api.views import InstanceGroupList, InstanceGroupDetail, InstanceGroupUnifiedJobsList, InstanceGroupInstanceList
|
||||
from awx.api.views import (
|
||||
InstanceGroupList,
|
||||
InstanceGroupDetail,
|
||||
InstanceGroupUnifiedJobsList,
|
||||
InstanceGroupInstanceList,
|
||||
InstanceGroupAccessList,
|
||||
InstanceGroupObjectRolesList,
|
||||
)
|
||||
|
||||
|
||||
urls = [
|
||||
@ -11,6 +18,8 @@ urls = [
|
||||
re_path(r'^(?P<pk>[0-9]+)/$', InstanceGroupDetail.as_view(), name='instance_group_detail'),
|
||||
re_path(r'^(?P<pk>[0-9]+)/jobs/$', InstanceGroupUnifiedJobsList.as_view(), name='instance_group_unified_jobs_list'),
|
||||
re_path(r'^(?P<pk>[0-9]+)/instances/$', InstanceGroupInstanceList.as_view(), name='instance_group_instance_list'),
|
||||
re_path(r'^(?P<pk>[0-9]+)/access_list/$', InstanceGroupAccessList.as_view(), name='instance_group_access_list'),
|
||||
re_path(r'^(?P<pk>[0-9]+)/object_roles/$', InstanceGroupObjectRolesList.as_view(), name='instance_group_object_role_list'),
|
||||
]
|
||||
|
||||
__all__ = ['urls']
|
||||
|
||||
@ -466,6 +466,23 @@ class InstanceGroupUnifiedJobsList(SubListAPIView):
|
||||
relationship = "unifiedjob_set"
|
||||
|
||||
|
||||
class InstanceGroupAccessList(ResourceAccessList):
|
||||
model = models.User # needs to be User for AccessLists
|
||||
parent_model = models.InstanceGroup
|
||||
|
||||
|
||||
class InstanceGroupObjectRolesList(SubListAPIView):
|
||||
model = models.Role
|
||||
serializer_class = serializers.RoleSerializer
|
||||
parent_model = models.InstanceGroup
|
||||
search_fields = ('role_field', 'content_type__model')
|
||||
|
||||
def get_queryset(self):
|
||||
po = self.get_parent_object()
|
||||
content_type = ContentType.objects.get_for_model(self.parent_model)
|
||||
return models.Role.objects.filter(content_type=content_type, object_id=po.pk)
|
||||
|
||||
|
||||
class InstanceGroupInstanceList(InstanceGroupMembershipMixin, SubListAttachDetachAPIView):
|
||||
name = _("Instance Group's Instances")
|
||||
model = models.Instance
|
||||
|
||||
@ -588,17 +588,39 @@ class InstanceAccess(BaseAccess):
|
||||
|
||||
|
||||
class InstanceGroupAccess(BaseAccess):
|
||||
"""
|
||||
I can see Instance Groups when I am:
|
||||
- a superuser(system administrator)
|
||||
- at least read_role on the instance group
|
||||
I can edit Instance Groups when I am:
|
||||
- a superuser
|
||||
- admin role on the Instance group
|
||||
I can add/delete Instance Groups:
|
||||
- a superuser(system administrator)
|
||||
I can use Instance Groups when I have:
|
||||
- use_role on the instance group
|
||||
"""
|
||||
|
||||
model = InstanceGroup
|
||||
prefetch_related = ('instances',)
|
||||
|
||||
def filtered_queryset(self):
|
||||
return InstanceGroup.objects.filter(organization__in=Organization.accessible_pk_qs(self.user, 'admin_role')).distinct()
|
||||
return self.model.accessible_objects(self.user, 'read_role')
|
||||
|
||||
@check_superuser
|
||||
def can_use(self, obj):
|
||||
return self.user in obj.use_role
|
||||
|
||||
def can_add(self, data):
|
||||
return self.user.is_superuser
|
||||
|
||||
@check_superuser
|
||||
def can_change(self, obj, data):
|
||||
return self.user.is_superuser
|
||||
return self.can_admin(obj)
|
||||
|
||||
@check_superuser
|
||||
def can_admin(self, obj):
|
||||
return self.user in obj.admin_role
|
||||
|
||||
def can_delete(self, obj):
|
||||
if obj.name in [settings.DEFAULT_EXECUTION_QUEUE_NAME, settings.DEFAULT_CONTROL_PLANE_QUEUE_NAME]:
|
||||
@ -845,7 +867,7 @@ class OrganizationAccess(NotificationAttachMixin, BaseAccess):
|
||||
return RoleAccess(self.user).can_attach(rel_role, sub_obj, 'members', *args, **kwargs)
|
||||
|
||||
if relationship == "instance_groups":
|
||||
if self.user.is_superuser:
|
||||
if self.user in obj.admin_role and self.user in sub_obj.use_role:
|
||||
return True
|
||||
return False
|
||||
return super(OrganizationAccess, self).can_attach(obj, sub_obj, relationship, *args, **kwargs)
|
||||
@ -934,7 +956,7 @@ class InventoryAccess(BaseAccess):
|
||||
|
||||
def can_attach(self, obj, sub_obj, relationship, *args, **kwargs):
|
||||
if relationship == "instance_groups":
|
||||
if self.user.can_access(type(sub_obj), "read", sub_obj) and self.user in obj.organization.admin_role:
|
||||
if self.user in sub_obj.use_role and self.user in obj.admin_role:
|
||||
return True
|
||||
return False
|
||||
return super(InventoryAccess, self).can_attach(obj, sub_obj, relationship, *args, **kwargs)
|
||||
@ -1671,11 +1693,12 @@ class JobTemplateAccess(NotificationAttachMixin, UnifiedCredentialsMixin, BaseAc
|
||||
return self.user.is_superuser or self.user in obj.admin_role
|
||||
|
||||
@check_superuser
|
||||
# object here is the job template. sub_object here is what is being attached
|
||||
def can_attach(self, obj, sub_obj, relationship, data, skip_sub_obj_read_check=False):
|
||||
if relationship == "instance_groups":
|
||||
if not obj.organization:
|
||||
return False
|
||||
return self.user.can_access(type(sub_obj), "read", sub_obj) and self.user in obj.organization.admin_role
|
||||
return self.user in sub_obj.use_role and self.user in obj.admin_role
|
||||
return super(JobTemplateAccess, self).can_attach(obj, sub_obj, relationship, data, skip_sub_obj_read_check=skip_sub_obj_read_check)
|
||||
|
||||
@check_superuser
|
||||
@ -1852,8 +1875,6 @@ class JobLaunchConfigAccess(UnifiedCredentialsMixin, BaseAccess):
|
||||
def _related_filtered_queryset(self, cls):
|
||||
if cls is Label:
|
||||
return LabelAccess(self.user).filtered_queryset()
|
||||
elif cls is InstanceGroup:
|
||||
return InstanceGroupAccess(self.user).filtered_queryset()
|
||||
else:
|
||||
return cls._accessible_pk_qs(cls, self.user, 'use_role')
|
||||
|
||||
|
||||
48
awx/main/migrations/0177_instance_group_role_addition.py
Normal file
48
awx/main/migrations/0177_instance_group_role_addition.py
Normal file
@ -0,0 +1,48 @@
|
||||
# Generated by Django 3.2.16 on 2023-02-17 02:45
|
||||
|
||||
import awx.main.fields
|
||||
from django.db import migrations
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
('main', '0176_inventorysource_scm_branch'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='instancegroup',
|
||||
name='admin_role',
|
||||
field=awx.main.fields.ImplicitRoleField(
|
||||
editable=False,
|
||||
null='True',
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
parent_role=['singleton:system_administrator'],
|
||||
related_name='+',
|
||||
to='main.role',
|
||||
),
|
||||
preserve_default='True',
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='instancegroup',
|
||||
name='read_role',
|
||||
field=awx.main.fields.ImplicitRoleField(
|
||||
editable=False,
|
||||
null='True',
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
parent_role=['singleton:system_auditor', 'use_role', 'admin_role'],
|
||||
related_name='+',
|
||||
to='main.role',
|
||||
),
|
||||
preserve_default='True',
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='instancegroup',
|
||||
name='use_role',
|
||||
field=awx.main.fields.ImplicitRoleField(
|
||||
editable=False, null='True', on_delete=django.db.models.deletion.CASCADE, parent_role=['admin_role'], related_name='+', to='main.role'
|
||||
),
|
||||
preserve_default='True',
|
||||
),
|
||||
]
|
||||
18
awx/main/migrations/0178_instance_group_admin_migration.py
Normal file
18
awx/main/migrations/0178_instance_group_admin_migration.py
Normal file
@ -0,0 +1,18 @@
|
||||
# Generated by Django 3.2.16 on 2023-02-17 02:45
|
||||
|
||||
from django.db import migrations
|
||||
from awx.main.migrations import _rbac as rbac
|
||||
from awx.main.migrations import _migration_utils as migration_utils
|
||||
from awx.main.migrations import _OrgAdmin_to_use_ig as oamigrate
|
||||
from awx.main.migrations import ActivityStreamDisabledMigration
|
||||
|
||||
|
||||
class Migration(ActivityStreamDisabledMigration):
|
||||
dependencies = [
|
||||
('main', '0177_instance_group_role_addition'),
|
||||
]
|
||||
operations = [
|
||||
migrations.RunPython(migration_utils.set_current_apps_for_migrations),
|
||||
migrations.RunPython(rbac.create_roles),
|
||||
migrations.RunPython(oamigrate.migrate_org_admin_to_use),
|
||||
]
|
||||
20
awx/main/migrations/_OrgAdmin_to_use_ig.py
Normal file
20
awx/main/migrations/_OrgAdmin_to_use_ig.py
Normal file
@ -0,0 +1,20 @@
|
||||
import logging
|
||||
|
||||
from awx.main.models import Organization
|
||||
|
||||
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():
|
||||
igs = list(org.instance_groups.all())
|
||||
if not igs:
|
||||
continue
|
||||
for admin in org.admin_role.members.filter(is_superuser=False):
|
||||
for ig in igs:
|
||||
ig.use_role.members.add(admin)
|
||||
roles_added += 1
|
||||
if roles_added:
|
||||
logger.info(f'Migration converted {roles_added} from organization admin to use role')
|
||||
@ -29,6 +29,7 @@ def create_roles(apps, schema_editor):
|
||||
'Project',
|
||||
'Credential',
|
||||
'JobTemplate',
|
||||
'InstanceGroup',
|
||||
]
|
||||
]
|
||||
|
||||
|
||||
@ -17,15 +17,20 @@ from django.db.models import Sum
|
||||
import redis
|
||||
from solo.models import SingletonModel
|
||||
|
||||
# AWX
|
||||
from awx import __version__ as awx_application_version
|
||||
from awx.api.versioning import reverse
|
||||
from awx.main.fields import JSONBlob
|
||||
from awx.main.fields import JSONBlob, ImplicitRoleField
|
||||
from awx.main.managers import InstanceManager, UUID_DEFAULT
|
||||
from awx.main.constants import JOB_FOLDER_PREFIX
|
||||
from awx.main.models.base import BaseModel, HasEditsMixin, prevent_search
|
||||
from awx.main.models.rbac import (
|
||||
ROLE_SINGLETON_SYSTEM_ADMINISTRATOR,
|
||||
ROLE_SINGLETON_SYSTEM_AUDITOR,
|
||||
)
|
||||
from awx.main.models.unified_jobs import UnifiedJob
|
||||
from awx.main.utils.common import get_corrected_cpu, get_cpu_effective_capacity, get_corrected_memory, get_mem_effective_capacity
|
||||
from awx.main.models.mixins import RelatedJobsMixin
|
||||
from awx.main.models.mixins import RelatedJobsMixin, ResourceMixin
|
||||
|
||||
# ansible-runner
|
||||
from ansible_runner.utils.capacity import get_cpu_count, get_mem_in_bytes
|
||||
@ -352,7 +357,7 @@ class Instance(HasPolicyEditsMixin, BaseModel):
|
||||
self.save_health_data(awx_application_version, get_cpu_count(), get_mem_in_bytes(), update_last_seen=True, errors=errors)
|
||||
|
||||
|
||||
class InstanceGroup(HasPolicyEditsMixin, BaseModel, RelatedJobsMixin):
|
||||
class InstanceGroup(HasPolicyEditsMixin, BaseModel, RelatedJobsMixin, ResourceMixin):
|
||||
"""A model representing a Queue/Group of AWX Instances."""
|
||||
|
||||
name = models.CharField(max_length=250, unique=True)
|
||||
@ -379,6 +384,24 @@ class InstanceGroup(HasPolicyEditsMixin, BaseModel, RelatedJobsMixin):
|
||||
default='',
|
||||
)
|
||||
)
|
||||
admin_role = ImplicitRoleField(
|
||||
parent_role=[
|
||||
'singleton:' + ROLE_SINGLETON_SYSTEM_ADMINISTRATOR,
|
||||
]
|
||||
)
|
||||
use_role = ImplicitRoleField(
|
||||
parent_role=[
|
||||
'admin_role',
|
||||
]
|
||||
)
|
||||
read_role = ImplicitRoleField(
|
||||
parent_role=[
|
||||
'singleton:' + ROLE_SINGLETON_SYSTEM_AUDITOR,
|
||||
'use_role',
|
||||
'admin_role',
|
||||
]
|
||||
)
|
||||
|
||||
max_concurrent_jobs = models.IntegerField(default=0, help_text=_("Maximum number of concurrent jobs to run on this group. Zero means no limit."))
|
||||
max_forks = models.IntegerField(default=0, help_text=_("Max forks to execute on this group. Zero means no limit."))
|
||||
policy_instance_percentage = models.IntegerField(default=0, help_text=_("Percentage of Instances to automatically assign to this group"))
|
||||
|
||||
@ -99,12 +99,12 @@ def test_instance_dup(org_admin, organization, project, instance_factory, instan
|
||||
list_response = get(reverse('api:instance_list'), user=system_auditor)
|
||||
api_num_instances_auditor = list(list_response.data.items())[0][1]
|
||||
|
||||
ig_all.read_role.members.add(org_admin)
|
||||
list_response2 = get(reverse('api:instance_list'), user=org_admin)
|
||||
api_num_instances_oa = list(list_response2.data.items())[0][1]
|
||||
|
||||
assert api_num_instances_auditor == actual_num_instances
|
||||
# Note: The org_admin will not see the default 'tower' node
|
||||
# (instance fixture) because it is not in its group, as expected
|
||||
# Note: The org_admin will not see instances unless at least read_role to the IG has been assigned
|
||||
assert api_num_instances_oa == (actual_num_instances - 1)
|
||||
|
||||
|
||||
|
||||
16
awx/main/tests/functional/test_org_admin_migration.py
Normal file
16
awx/main/tests/functional/test_org_admin_migration.py
Normal file
@ -0,0 +1,16 @@
|
||||
import pytest
|
||||
|
||||
from django.apps import apps
|
||||
|
||||
from awx.main.models import InstanceGroup
|
||||
from awx.main.migrations import _OrgAdmin_to_use_ig as orgadmin
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_migrate_admin_role(org_admin, organization):
|
||||
instance_group = InstanceGroup.objects.create(name='test')
|
||||
organization.admin_role.members.add(org_admin)
|
||||
organization.instance_groups.add(instance_group)
|
||||
orgadmin.migrate_org_admin_to_use(apps, None)
|
||||
assert org_admin in instance_group.use_role.members.all()
|
||||
assert instance_group.use_role.members.count() == 1
|
||||
@ -6,7 +6,47 @@ from awx.main.access import (
|
||||
InventoryAccess,
|
||||
JobTemplateAccess,
|
||||
)
|
||||
from awx.main.models import Organization
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
@pytest.mark.parametrize(
|
||||
"obj_perm,allowed,readonly,partial", [("admin_role", True, True, True), ("use_role", False, True, True), ("read_role", False, True, False)]
|
||||
)
|
||||
def test_ig_role_base_visibility(default_instance_group, rando, obj_perm, allowed, partial, readonly):
|
||||
if obj_perm:
|
||||
getattr(default_instance_group, obj_perm).members.add(rando)
|
||||
|
||||
assert readonly == InstanceGroupAccess(rando).can_read(default_instance_group)
|
||||
assert partial == InstanceGroupAccess(rando).can_use(default_instance_group)
|
||||
assert not InstanceGroupAccess(rando).can_add(default_instance_group)
|
||||
assert allowed == InstanceGroupAccess(rando).can_admin(default_instance_group)
|
||||
assert allowed == InstanceGroupAccess(rando).can_change(default_instance_group, {'name': 'New Name'})
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
@pytest.mark.parametrize(
|
||||
"obj_perm,subobj_perm,allowed", [('admin_role', 'use_role', True), ('admin_role', 'read_role', False), ('admin_role', 'admin_role', True)]
|
||||
)
|
||||
def test_ig_role_based_associability(default_instance_group, rando, organization, job_template_factory, obj_perm, subobj_perm, allowed):
|
||||
objects = job_template_factory('jt', organization=organization, project='p', inventory='i', credential='c')
|
||||
if obj_perm:
|
||||
getattr(objects.job_template, obj_perm).members.add(rando)
|
||||
getattr(objects.inventory, obj_perm).members.add(rando)
|
||||
getattr(objects.organization, obj_perm).members.add(rando)
|
||||
if subobj_perm:
|
||||
getattr(default_instance_group, subobj_perm).members.add(rando)
|
||||
|
||||
assert allowed == JobTemplateAccess(rando).can_attach(objects.job_template, default_instance_group, 'instance_groups', None)
|
||||
assert allowed == InventoryAccess(rando).can_attach(objects.inventory, default_instance_group, 'instance_groups', None)
|
||||
assert allowed == OrganizationAccess(rando).can_attach(objects.organization, default_instance_group, 'instance_groups', None)
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_ig_use_with_org_admin(default_instance_group, rando, org_admin):
|
||||
default_instance_group.use_role.members.add(rando)
|
||||
|
||||
assert list(InstanceGroupAccess(org_admin).get_queryset()) != [default_instance_group]
|
||||
assert list(InstanceGroupAccess(rando).get_queryset()) == [default_instance_group]
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
@ -24,7 +64,7 @@ def test_ig_admin_user_visibility(organization, default_instance_group, admin, s
|
||||
assert len(InstanceGroupAccess(system_auditor).get_queryset()) == 1
|
||||
assert len(InstanceGroupAccess(org_admin).get_queryset()) == 0
|
||||
organization.instance_groups.add(default_instance_group)
|
||||
assert len(InstanceGroupAccess(org_admin).get_queryset()) == 1
|
||||
assert len(InstanceGroupAccess(org_admin).get_queryset()) == 0
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
@ -37,16 +77,6 @@ def test_ig_normal_user_associability(organization, default_instance_group, user
|
||||
assert not access.can_attach(organization, default_instance_group, 'instance_groups', None)
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_access_via_two_organizations(rando, default_instance_group):
|
||||
for org_name in ['org1', 'org2']:
|
||||
org = Organization.objects.create(name=org_name)
|
||||
org.instance_groups.add(default_instance_group)
|
||||
org.admin_role.members.add(rando)
|
||||
access = InstanceGroupAccess(rando)
|
||||
assert list(access.get_queryset()) == [default_instance_group]
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_ig_associability(organization, default_instance_group, admin, system_auditor, org_admin, org_member, job_template_factory):
|
||||
admin_access = OrganizationAccess(admin)
|
||||
@ -72,7 +102,7 @@ def test_ig_associability(organization, default_instance_group, admin, system_au
|
||||
omember_access = InventoryAccess(org_member)
|
||||
|
||||
assert admin_access.can_attach(objects.inventory, default_instance_group, 'instance_groups', None)
|
||||
assert oadmin_access.can_attach(objects.inventory, default_instance_group, 'instance_groups', None)
|
||||
assert not oadmin_access.can_attach(objects.inventory, default_instance_group, 'instance_groups', None)
|
||||
assert not auditor_access.can_attach(objects.inventory, default_instance_group, 'instance_groups', None)
|
||||
assert not omember_access.can_attach(objects.inventory, default_instance_group, 'instance_groups', None)
|
||||
|
||||
@ -82,6 +112,6 @@ def test_ig_associability(organization, default_instance_group, admin, system_au
|
||||
omember_access = JobTemplateAccess(org_member)
|
||||
|
||||
assert admin_access.can_attach(objects.job_template, default_instance_group, 'instance_groups', None)
|
||||
assert oadmin_access.can_attach(objects.job_template, default_instance_group, 'instance_groups', None)
|
||||
assert not oadmin_access.can_attach(objects.job_template, default_instance_group, 'instance_groups', None)
|
||||
assert not auditor_access.can_attach(objects.job_template, default_instance_group, 'instance_groups', None)
|
||||
assert not omember_access.can_attach(objects.job_template, default_instance_group, 'instance_groups', None)
|
||||
|
||||
@ -148,7 +148,7 @@ class TestWorkflowJobTemplateNodeAccess:
|
||||
elif permission_type == 'instance_groups':
|
||||
sub_obj = InstanceGroup.objects.create()
|
||||
org = Organization.objects.create()
|
||||
org.admin_role.members.add(rando) # only admins can see IGs
|
||||
sub_obj.use_role.members.add(rando) # only admins can see IGs
|
||||
org.instance_groups.add(sub_obj)
|
||||
|
||||
access = WorkflowJobTemplateNodeAccess(rando)
|
||||
|
||||
@ -8,6 +8,7 @@ class InstanceGroups extends Base {
|
||||
this.associateInstance = this.associateInstance.bind(this);
|
||||
this.disassociateInstance = this.disassociateInstance.bind(this);
|
||||
this.readInstanceOptions = this.readInstanceOptions.bind(this);
|
||||
this.readInstanceGroupOptions = this.readInstanceGroupOptions.bind(this);
|
||||
this.readInstances = this.readInstances.bind(this);
|
||||
this.readJobs = this.readJobs.bind(this);
|
||||
}
|
||||
@ -33,6 +34,10 @@ class InstanceGroups extends Base {
|
||||
return this.http.options(`${this.baseUrl}${id}/instances/`);
|
||||
}
|
||||
|
||||
readInstanceGroupOptions(id) {
|
||||
return this.http.options(`${this.baseUrl}${id}/`);
|
||||
}
|
||||
|
||||
readJobs(id) {
|
||||
return this.http.get(`${this.baseUrl}${id}/jobs/`);
|
||||
}
|
||||
|
||||
@ -6,6 +6,7 @@ import {
|
||||
InventoriesAPI,
|
||||
ProjectsAPI,
|
||||
OrganizationsAPI,
|
||||
InstanceGroupsAPI,
|
||||
} from 'api';
|
||||
|
||||
export default function getResourceAccessConfig() {
|
||||
@ -210,5 +211,32 @@ export default function getResourceAccessConfig() {
|
||||
fetchItems: (queryParams) => OrganizationsAPI.read(queryParams),
|
||||
fetchOptions: () => OrganizationsAPI.readOptions(),
|
||||
},
|
||||
{
|
||||
selectedResource: 'Instance Groups',
|
||||
label: t`Instance Groups`,
|
||||
searchColumns: [
|
||||
{
|
||||
name: t`Name`,
|
||||
key: 'name__icontains',
|
||||
isDefault: true,
|
||||
},
|
||||
{
|
||||
name: t`Created By (Username)`,
|
||||
key: 'created_by__username__icontains',
|
||||
},
|
||||
{
|
||||
name: t`Modified By (Username)`,
|
||||
key: 'modified_by__username__icontains',
|
||||
},
|
||||
],
|
||||
sortColumns: [
|
||||
{
|
||||
name: t`Name`,
|
||||
key: 'name',
|
||||
},
|
||||
],
|
||||
fetchItems: (queryParams) => InstanceGroupsAPI.read(queryParams),
|
||||
fetchOptions: () => InstanceGroupsAPI.readOptions(),
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
@ -184,7 +184,6 @@ function getRouteConfig(userProfile = {}) {
|
||||
deleteRouteGroup('settings');
|
||||
deleteRoute('management_jobs');
|
||||
if (userProfile?.isOrgAdmin) return routeConfig;
|
||||
deleteRoute('instance_groups');
|
||||
deleteRoute('topology_view');
|
||||
deleteRoute('instances');
|
||||
if (!userProfile?.isNotificationAdmin) deleteRoute('notification_templates');
|
||||
|
||||
@ -127,6 +127,7 @@ describe('getRouteConfig', () => {
|
||||
'/teams',
|
||||
'/credential_types',
|
||||
'/notification_templates',
|
||||
'/instance_groups',
|
||||
'/applications',
|
||||
'/execution_environments',
|
||||
]);
|
||||
@ -150,6 +151,7 @@ describe('getRouteConfig', () => {
|
||||
'/users',
|
||||
'/teams',
|
||||
'/credential_types',
|
||||
'/instance_groups',
|
||||
'/applications',
|
||||
'/execution_environments',
|
||||
]);
|
||||
@ -173,6 +175,7 @@ describe('getRouteConfig', () => {
|
||||
'/users',
|
||||
'/teams',
|
||||
'/credential_types',
|
||||
'/instance_groups',
|
||||
'/applications',
|
||||
'/execution_environments',
|
||||
]);
|
||||
@ -201,6 +204,7 @@ describe('getRouteConfig', () => {
|
||||
'/teams',
|
||||
'/credential_types',
|
||||
'/notification_templates',
|
||||
'/instance_groups',
|
||||
'/applications',
|
||||
'/execution_environments',
|
||||
]);
|
||||
|
||||
@ -21,9 +21,11 @@ function ContainerGroupEdit({ instanceGroup }) {
|
||||
result: initialPodSpec,
|
||||
} = useRequest(
|
||||
useCallback(async () => {
|
||||
const { data } = await InstanceGroupsAPI.readOptions();
|
||||
return data.actions.POST.pod_spec_override.default;
|
||||
}, []),
|
||||
const { data } = await InstanceGroupsAPI.readInstanceGroupOptions(
|
||||
instanceGroup.id
|
||||
);
|
||||
return data.actions.PUT.pod_spec_override.default;
|
||||
}, [instanceGroup.id]),
|
||||
{
|
||||
initialPodSpec: {},
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user