Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

mgr/dashboard: delete-ceph-authx #50918

Merged
merged 1 commit into from
Apr 19, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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 = ''
nizamial09 marked this conversation as resolved.
Show resolved Hide resolved


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