Skip to content

Commit

Permalink
mgr/dashboard: delete-ceph-authx
Browse files Browse the repository at this point in the history
Fixes: https://tracker.ceph.com/issues/59365
Signed-off-by: Pedro Gonzalez Gomez <pegonzal@redhat.com>
  • Loading branch information
Pegonzal authored and Pedro Gonzalez Gomez committed Apr 14, 2023
1 parent 071167f commit 6b5a00f
Show file tree
Hide file tree
Showing 8 changed files with 142 additions and 5 deletions.
24 changes: 22 additions & 2 deletions src/pybind/mgr/dashboard/controllers/_crud.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,8 @@ class TableAction(NamedTuple):
name: str
permission: str
icon: str
routerLink: str # redirect to...
routerLink: str = '' # redirect to...
click: str = ''


class TableComponent(SerializableClass):
Expand All @@ -76,6 +77,7 @@ def __init__(self) -> None:

class Icon(Enum):
add = 'fa fa-plus'
destroy = 'fa fa-times'


class Validator(Enum):
Expand Down Expand Up @@ -272,6 +274,7 @@ def __init__(self):
self.permissions = []
self.actions = []
self.forms = []
self.columnKey = ''
self.detail_columns = []


Expand All @@ -285,18 +288,20 @@ class CRUDResourceMethod(NamedTuple):
doc: EndpointDoc


# pylint: disable=R0902
class CRUDEndpoint:
# for testing purposes
CRUDClass: Optional[RESTController] = None
CRUDClassMetadata: Optional[RESTController] = None

# pylint: disable=R0902
def __init__(self, router: APIRouter, doc: APIDoc,
set_column: Optional[Dict[str, Dict[str, str]]] = None,
actions: Optional[List[TableAction]] = None,
permissions: Optional[List[str]] = None, forms: Optional[List[Form]] = None,
column_key: Optional[str] = None,
meta: CRUDMeta = CRUDMeta(), get_all: Optional[CRUDCollectionMethod] = None,
create: Optional[CRUDCollectionMethod] = None,
delete: Optional[CRUDCollectionMethod] = None,
detail_columns: Optional[List[str]] = None):
self.router = router
self.doc = doc
Expand All @@ -306,7 +311,9 @@ def __init__(self, router: APIRouter, doc: APIDoc,
self.meta = meta
self.get_all = get_all
self.create = create
self.delete = delete
self.permissions = permissions if permissions is not None else []
self.column_key = column_key if column_key is not None else ''
self.detail_columns = detail_columns if detail_columns is not None else []

def __call__(self, cls: Any):
Expand Down Expand Up @@ -338,6 +345,12 @@ def list(self, *args, **kwargs):
def create(self, *args, **kwargs):
return outer_self.create.func(self, *args, **kwargs) # type: ignore

if self.delete:
@self.delete.doc
@wraps(self.delete.func)
def delete(self, *args, **kwargs):
return outer_self.delete.func(self, *args, **kwargs) # type: ignore

cls.CRUDClass = CRUDClass

def create_meta_class(self, cls):
Expand All @@ -346,6 +359,7 @@ def _list(self):
self.generate_actions()
self.generate_forms()
self.set_permissions()
self.set_column_key()
self.get_detail_columns()
return serialize(self.__class__.outer_self.meta)

Expand Down Expand Up @@ -385,6 +399,11 @@ def generate_forms(self):
def set_permissions(self):
if self.__class__.outer_self.permissions:
self.outer_self.meta.permissions.extend(self.__class__.outer_self.permissions)

def set_column_key(self):
if self.__class__.outer_self.column_key:
self.outer_self.meta.columnKey = self.__class__.outer_self.column_key

class_name = self.router.path.replace('/', '')
meta_class = type(f'{class_name}_CRUDClassMetadata',
(RESTController,),
Expand All @@ -394,6 +413,7 @@ def set_permissions(self):
'generate_actions': generate_actions,
'generate_forms': generate_forms,
'set_permissions': set_permissions,
'set_column_key': set_column_key,
'get_detail_columns': get_detail_columns,
'outer_self': self,
})
Expand Down
25 changes: 24 additions & 1 deletion src/pybind/mgr/dashboard/controllers/ceph_users.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,22 @@ def user_create(_, user_entity: str, capabilities: List[Cap]):
raise DashboardException(msg, code=500)
return f"Successfully created user '{user_entity}'"

@staticmethod
def user_delete(_, user_entity: str):
"""
Delete a ceph user and it's defined capabilities.
:param user_entity: Entity to dlelete
"""
logger.debug("Sending command 'auth del' of entity '%s'", user_entity)
try:
CephService.send_command('mon', 'auth del', entity=user_entity)
except SendCommandError as ex:
msg = f'{ex} in command {ex.prefix}'
if ex.errno == -EINVAL:
raise DashboardException(msg, code=400)
raise DashboardException(msg, code=500)
return f"Successfully eleted user '{user_entity}'"


create_cap_container = ArrayHorizontalContainer('Capabilities', 'capabilities', fields=[
FormField('Entity', 'entity',
Expand All @@ -82,10 +98,13 @@ def user_create(_, user_entity: str, capabilities: List[Cap]):
set_column={"caps": {"cellTemplate": "badgeDict"}},
actions=[
TableAction(name='create', permission='create', icon=Icon.add.value,
routerLink='/cluster/user/create')
routerLink='/cluster/user/create'),
TableAction(name='Delete', permission='delete', icon=Icon.destroy.value,
click='delete')
],
permissions=[Scope.CONFIG_OPT],
forms=[create_form],
column_key='entity',
get_all=CRUDCollectionMethod(
func=CephUserEndpoints.user_list,
doc=EndpointDoc("Get Ceph Users")
Expand All @@ -94,6 +113,10 @@ def user_create(_, user_entity: str, capabilities: List[Cap]):
func=CephUserEndpoints.user_create,
doc=EndpointDoc("Create Ceph User")
),
delete=CRUDCollectionMethod(
func=CephUserEndpoints.user_delete,
doc=EndpointDoc("Delete Ceph User")
),
meta=CRUDMeta()
)
class CephUser(NamedTuple):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { RouterTestingModule } from '@angular/router/testing';
import { NgbDropdownModule, NgbTooltipModule } from '@ng-bootstrap/ng-bootstrap';
import { NgxDatatableModule } from '@swimlane/ngx-datatable';
import { NgxPipeFunctionModule } from 'ngx-pipe-function';
import { ToastrModule } from 'ngx-toastr';

import { ComponentsModule } from '~/app/shared/components/components.module';
import { PipesModule } from '~/app/shared/pipes/pipes.module';
Expand Down Expand Up @@ -36,7 +37,8 @@ describe('CRUDTableComponent', () => {
NgbTooltipModule,
RouterTestingModule,
NgxPipeFunctionModule,
HttpClientTestingModule
HttpClientTestingModule,
ToastrModule.forRoot()
]
});
beforeEach(() => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,13 @@ import { CrudMetadata } from '~/app/shared/models/crud-table-metadata';
import { DataGatewayService } from '~/app/shared/services/data-gateway.service';
import { TimerService } from '~/app/shared/services/timer.service';
import { CdTableSelection } from '../../models/cd-table-selection';
import { FinishedTask } from '../../models/finished-task';
import { Permission, Permissions } from '../../models/permissions';
import { AuthStorageService } from '../../services/auth-storage.service';
import { TaskWrapperService } from '../../services/task-wrapper.service';
import { ModalService } from '../../services/modal.service';
import { NgbModalRef } from '@ng-bootstrap/ng-bootstrap';
import { CriticalConfirmationModalComponent } from '../../components/critical-confirmation-modal/critical-confirmation-modal.component';

@Component({
selector: 'cd-crud-table',
Expand All @@ -32,12 +37,16 @@ export class CRUDTableComponent implements OnInit {
selection = new CdTableSelection();
expandedRow: any = null;
tabs = {};
resource: string;
modalRef: NgbModalRef;

constructor(
private authStorageService: AuthStorageService,
private timerService: TimerService,
private dataGatewayService: DataGatewayService,
private activatedRoute: ActivatedRoute
private activatedRoute: ActivatedRoute,
private taskWrapper: TaskWrapperService,
private modalService: ModalService
) {
this.permissions = this.authStorageService.getPermissions();
}
Expand All @@ -55,6 +64,9 @@ export class CRUDTableComponent implements OnInit {
.subscribe((response: CrudMetadata) => this.processMeta(response));
this.data$ = this.timerService.get(() => this.dataGatewayService.list(resource));
});
this.activatedRoute.data.subscribe((data: any) => {
this.resource = data.resource;
});
}

processMeta(meta: CrudMetadata) {
Expand Down Expand Up @@ -86,6 +98,34 @@ export class CRUDTableComponent implements OnInit {
return !col['isHidden'];
});
this.meta = meta;
for (let i = 0; i < this.meta.actions.length; i++) {
if (this.meta.actions[i].click.toString() !== '') {
this.meta.actions[i].click = this[this.meta.actions[i].click.toString()].bind(this);
}
}
}

delete() {
const selectedKey = this.selection.first()[this.meta.columnKey];
this.modalRef = this.modalService.show(CriticalConfirmationModalComponent, {
itemDescription: $localize`${this.meta.columnKey}`,
itemNames: [selectedKey],
submitAction: () => {
this.taskWrapper
.wrapTaskAroundCall({
task: new FinishedTask('crud-component/id', selectedKey),
call: this.dataGatewayService.delete(this.resource, selectedKey)
})
.subscribe({
error: () => {
this.modalRef.close();
},
complete: () => {
this.modalRef.close();
}
});
}
});
}

updateSelection(selection: CdTableSelection) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,5 @@ export class CrudMetadata {
permissions: string[];
actions: CdTableAction[];
forms: any;
columnKey: string;
}
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,15 @@ export class DataGatewayService {
});
}

delete(dataPath: string, key: string): Observable<any> {
const { url, version } = this.getUrlAndVersion(dataPath);

return this.http.delete<any>(`${url}/${key}`, {
headers: { Accept: `application/vnd.ceph.api.v${version}+json` },
observe: 'response'
});
}

form(dataPath: string): Observable<JsonFormUISchema> {
const cacheable = this.getCacheable(dataPath, 'get');
if (this.cache[cacheable] === undefined) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -343,6 +343,9 @@ export class TaskMessageService {
),
'crud-component': this.newTaskMessage(this.commonOperations.create, (metadata) =>
this.crudMessage(metadata)
),
'crud-component/id': this.newTaskMessage(this.commonOperations.delete, (id) =>
this.crudMessageId(id)
)
};

Expand Down Expand Up @@ -399,6 +402,10 @@ export class TaskMessageService {
return $localize`${message}`;
}

crudMessageId(id: string) {
return $localize`${id}`;
}

_getTaskTitle(task: Task) {
if (task.name && task.name.startsWith('progress/')) {
// we don't fill the failure string because, at least for now, all
Expand Down
35 changes: 35 additions & 0 deletions src/pybind/mgr/dashboard/openapi.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2235,6 +2235,41 @@ paths:
summary: Create Ceph User
tags:
- Cluster
/api/cluster/user/{user_entity}:
delete:
description: "\n Delete a ceph user and it's defined capabilities.\n\
\ :param user_entity: Entity to dlelete\n "
parameters:
- in: path
name: user_entity
required: true
schema:
type: string
responses:
'202':
content:
application/vnd.ceph.api.v1.0+json:
type: object
description: Operation is still executing. Please check the task queue.
'204':
content:
application/vnd.ceph.api.v1.0+json:
type: object
description: Resource deleted.
'400':
description: Operation exception. Please check the response body for details.
'401':
description: Unauthenticated access. Please login first.
'403':
description: Unauthorized access. Please check your permissions.
'500':
description: Unexpected error. Please check the response body for the stack
trace.
security:
- jwt: []
summary: Delete Ceph User
tags:
- Cluster
/api/cluster_conf:
get:
parameters: []
Expand Down

0 comments on commit 6b5a00f

Please sign in to comment.