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

Update manage filers page #215

Merged
merged 7 commits into from
Oct 4, 2024
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
120 changes: 75 additions & 45 deletions components/centraldashboard/app/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,12 @@ export const ERRORS = {
invalid_links_config: 'Cannot load dashboard menu link',
invalid_settings: 'Cannot load dashboard settings',
invalid_get_filers: 'Failed to load filers',
invalid_get_user_filers: 'Failed to load user filers',
invalid_update_filer: 'Failed to update filers'
invalid_get_existing_shares: 'Failed to load existing shares',
invalid_get_requesting_shares: 'Failed to load requesting shares',
invalid_create_requesting_shares: 'Failed to create requesting shares configmap',
invalid_update_requesting_shares: 'Failed to update requesting shares configmap',
invalid_update_existing_shares: 'Failed to update existing shares configmap',
invalid_delete_existing_shares: 'Failed to delete existing shares configmap'
};

export function apiError(a: {res: Response, error: string, code?: number}) {
Expand Down Expand Up @@ -107,71 +111,97 @@ export class Api {
})
.get(
'/filers',
async (req: Request, res: Response) => {
try{
const filePath = resolve('./filerShares.json');
const contents = await readFile(filePath, {encoding: 'utf8'});
const filers = JSON.parse(contents);
res.json(filers);
async (_: Request, res: Response) => {
const cm = await this.k8sService.getFilersListConfigMap();
let filers = [];
try {
filers=JSON.parse(cm.data["filers"]);
}catch(e){
return apiError({
res, code: 500,
error: ERRORS.invalid_get_filers,
});
res, code: 500,
error: ERRORS.invalid_get_filers,
});
}
res.json(filers);
})
.post(
'/create-filer/:namespace',
.post(
'/create-requesting-shares/:namespace',
async (req: Request, res: Response) => {
try {
const cm = await this.k8sService.createUserFilerConfigMap(req.params.namespace, req.body);
const cm = await this.k8sService.createRequestingSharesConfigMap(req.params.namespace, req.body, req.user.email);
res.json(cm.data);
}catch(e){
return apiError({
res, code: 500,
error: ERRORS.invalid_update_filer,
error: ERRORS.invalid_create_requesting_shares,
});
}
})
.get(
'/get-filer/:namespace',
async (req: Request, res: Response) => {
.get(
'/get-existing-shares/:namespace',
async (req: Request, res: Response) => {
try {
const cm = await this.k8sService.getExistingSharesConfigMap(req.params.namespace);
res.json(cm.data);
}catch(e){
return apiError({
res, code: 500,
error: ERRORS.invalid_get_existing_shares,
});
}
})
.get(
'/get-requesting-shares/:namespace',
async (req: Request, res: Response) => {
try {
const cm = await this.k8sService.getUserFilerConfigMap(req.params.namespace);
res.json(cm.data);
const cm = await this.k8sService.getRequestingSharesConfigMap(req.params.namespace);
res.json(cm.data);
}catch(e){
return apiError({
res, code: 500,
error: ERRORS.invalid_get_user_filers,
});
return apiError({
res, code: 500,
error: ERRORS.invalid_get_requesting_shares,
});
}
})
.patch(
'/update-filer/:namespace',
async (req: Request, res: Response) => {
})
.patch(
'/update-requesting-shares/:namespace',
async (req: Request, res: Response) => {
try {
const cm = await this.k8sService.updateUserFilerConfigMap(req.params.namespace, req.body);
res.json(cm.data);
const cm = await this.k8sService.updateRequestingSharesConfigMap(req.params.namespace, req.body, req.user.email);
res.json(cm.data);
}catch(e){
return apiError({
res, code: 500,
error: ERRORS.invalid_update_filer,
});
return apiError({
res, code: 500,
error: ERRORS.invalid_update_requesting_shares,
});
}
})
.patch(
'/update-existing-shares/:namespace',
async (req: Request, res: Response) => {
try {
const cm = await this.k8sService.updateExistingSharesConfigMap(req.params.namespace, req.body);
res.json(cm.data);
}catch(e){
return apiError({
res, code: 500,
error: ERRORS.invalid_update_existing_shares,
});
}
})
.delete(
'/delete-filer/:namespace',
async (req: Request, res: Response) => {
try {
await this.k8sService.deleteUserFilerConfigMap(req.params.namespace);
})
.delete(

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

how would this api call be used? :?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

https://github.com/StatCan/kubeflow/pull/215/files/7fe3b9abbb0980b241f8e8082fb091b630731190#diff-7163f508162f4fae8b88e5d727e68869921bb99817073f3c82c0379a188c12d8R201

It gets called by the manage filers page, when a user deletes their only filer share, we simply delete the existing-shares CM instead of updating it to have no values.

'/delete-existing-shares/:namespace',
async (req: Request, res: Response) => {
try {
await this.k8sService.deleteExistingSharesConfigMap(req.params.namespace);
res.json({});
}catch(e){
}catch(e){
return apiError({
res, code: 500,
error: ERRORS.invalid_update_filer,
res, code: 500,
error: ERRORS.invalid_delete_existing_shares,
});
}
});
}
});
}

resolveLanguage(requested: string[], supported: string[], defaultLang: string) {
Expand Down
86 changes: 71 additions & 15 deletions components/centraldashboard/app/k8s_service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,8 @@ interface V1BetaApplicationList {
const APP_API_GROUP = 'app.k8s.io';
const APP_API_VERSION = 'v1beta1';
const APP_API_NAME = 'applications';
const USER_FILERS_CM_NAME = 'user-filers';
const REQUESTING_SHARES_CM_NAME = 'requesting-shares';
const EXISTING_SHARES_CM_NAME = 'existing-shares';

/** Wrap Kubernetes API calls in a simpler interface for use in routes. */
export class KubernetesService {
Expand Down Expand Up @@ -106,27 +107,59 @@ export class KubernetesService {
}
}

/** Creates the user filers configmap for the central dashboard. */
async createUserFilerConfigMap(namespace: string, data: {[key:string]:string}): Promise<k8s.V1ConfigMap> {
/** Retrieves the configmap data for the list of filers. */
async getFilersListConfigMap(): Promise<k8s.V1ConfigMap> {
try {
const { body } = await this.coreAPI.readNamespacedConfigMap("filers-list", "das");
return body;
} catch (err) {
console.error('Unable to fetch fielrs list ConfigMap:', err.response?.body || err.body || err);
return null;
}
}

/** Creates the requesting shares configmap for the central dashboard. */
async createRequestingSharesConfigMap(namespace: string, data: {[key:string]:string}, email: string): Promise<k8s.V1ConfigMap> {
try {
const config = {
metadata: {
name: USER_FILERS_CM_NAME
name: REQUESTING_SHARES_CM_NAME,
labels: {
"for-ontap": "true"
},
annotations: {
"user-email": email
}
},
data
} as k8s.V1ConfigMap;
const { body } = await this.coreAPI.createNamespacedConfigMap(namespace, config);
return body;
} catch (err) {
console.error('Unable to create ConfigMap:', err.response?.body || err.body || err);
console.error('Unable to create requesting shares ConfigMap:', err.response?.body || err.body || err);
throw err;
}
}

/** Retrieves the existing shares configmap data for the central dashboard. */
async getExistingSharesConfigMap(namespace: string): Promise<k8s.V1ConfigMap> {
try {
const { body } = await this.coreAPI.readNamespacedConfigMap(EXISTING_SHARES_CM_NAME, namespace);
return body;
} catch (err) {
if(err.statusCode === 404){
//user has no user-filers yet
return new k8s.V1ConfigMap();
}
console.error('Unable to fetch ConfigMap:', err.response?.body || err.body || err);
throw err;
}
}

/** Retrieves the user filers configmap data for the central dashboard. */
async getUserFilerConfigMap(namespace: string): Promise<k8s.V1ConfigMap> {
/** Retrieves the requesting shares configmap data for the central dashboard. */
async getRequestingSharesConfigMap(namespace: string): Promise<k8s.V1ConfigMap> {
try {
const { body } = await this.coreAPI.readNamespacedConfigMap(USER_FILERS_CM_NAME, namespace);
const { body } = await this.coreAPI.readNamespacedConfigMap(REQUESTING_SHARES_CM_NAME, namespace);
return body;
} catch (err) {
if(err.statusCode === 404){
Expand All @@ -138,27 +171,50 @@ export class KubernetesService {
}
}

/** Updates the user filers configmap for the central dashboard. */
async updateUserFilerConfigMap(namespace: string, data: {[key:string]:string}): Promise<k8s.V1ConfigMap> {
/** Updates the requesting shares configmap for the central dashboard. */
async updateRequestingSharesConfigMap(namespace: string, data: {[key:string]:string}, email: string): Promise<k8s.V1ConfigMap> {
try {
const config = {
metadata: {
name: REQUESTING_SHARES_CM_NAME,
labels: {
"for-ontap": "true"
},
annotations: {
"user-email": email
}
},
data
} as k8s.V1ConfigMap;
const { body } = await this.coreAPI.replaceNamespacedConfigMap(REQUESTING_SHARES_CM_NAME, namespace, config);
return body;
} catch (err) {
console.error('Unable to patch ConfigMap:', err.response?.body || err.body || err);
throw err;
}
}

/** Updates the existing shares configmap for the central dashboard. */
async updateExistingSharesConfigMap(namespace: string, data: {[key:string]:string}): Promise<k8s.V1ConfigMap> {
try {
const config = {
metadata: {
name: USER_FILERS_CM_NAME
name: EXISTING_SHARES_CM_NAME
},
data
} as k8s.V1ConfigMap;
const { body } = await this.coreAPI.replaceNamespacedConfigMap(USER_FILERS_CM_NAME, namespace, config);
const { body } = await this.coreAPI.replaceNamespacedConfigMap(EXISTING_SHARES_CM_NAME, namespace, config);
return body;
} catch (err) {
console.error('Unable to patch ConfigMap:', err.response?.body || err.body || err);
throw err;
}
}

/** Deletes the user filers configmap for the central dashboard. */
async deleteUserFilerConfigMap(namespace: string): Promise<k8s.V1Status> {
/** Deletes the existing shares configmap for the central dashboard. */
async deleteExistingSharesConfigMap(namespace: string): Promise<k8s.V1Status> {
try {
const { body } = await this.coreAPI.deleteNamespacedConfigMap(USER_FILERS_CM_NAME, namespace);
const { body } = await this.coreAPI.deleteNamespacedConfigMap(EXISTING_SHARES_CM_NAME, namespace);
return body;
} catch (err) {
console.error('Unable to delete ConfigMap:', err.response?.body || err.body || err);
Expand Down
Loading
Loading