mirror of
https://github.com/ClusterCockpit/cc-backend
synced 2026-06-11 22:27:29 +02:00
Compare commits
9 Commits
release/v1
...
v1.5.4
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c94f5918f3 | ||
|
|
dd8c0e1b9f | ||
|
|
879f730876 | ||
|
|
d74ae77c8e | ||
|
|
cdb4f06fea | ||
|
|
1ebde74774 | ||
|
|
40722d72f5 | ||
|
|
f4384668e5 | ||
|
|
e06982db00 |
15
CLAUDE.md
15
CLAUDE.md
@@ -341,21 +341,6 @@ records, archives) at scale. All code changes must prioritize maximum throughput
|
|||||||
and minimal latency. Avoid unnecessary allocations, prefer streaming over
|
and minimal latency. Avoid unnecessary allocations, prefer streaming over
|
||||||
buffering, and be mindful of lock contention. When in doubt, benchmark.
|
buffering, and be mindful of lock contention. When in doubt, benchmark.
|
||||||
|
|
||||||
### Commit Message Convention
|
|
||||||
|
|
||||||
Commits must use conventional commit prefixes so goreleaser can generate the
|
|
||||||
changelog automatically. Only commits with these prefixes appear in releases:
|
|
||||||
|
|
||||||
| Prefix | Changelog group |
|
|
||||||
|---------|------------------------|
|
|
||||||
| `feat:` | New Features |
|
|
||||||
| `fix:` | Bug fixes |
|
|
||||||
| `sec:` | Security updates |
|
|
||||||
| `docs:` | Documentation updates |
|
|
||||||
|
|
||||||
Scoped variants are also recognised, e.g. `feat(api):`, `fix(deps):`.
|
|
||||||
Commits without one of these prefixes are excluded from the changelog.
|
|
||||||
|
|
||||||
### Change Impact Analysis
|
### Change Impact Analysis
|
||||||
|
|
||||||
For any significant change, you MUST:
|
For any significant change, you MUST:
|
||||||
|
|||||||
73
README.md
73
README.md
@@ -1,9 +1,10 @@
|
|||||||
# NOTE
|
# NOTE
|
||||||
|
|
||||||
While we do our best to keep the main branch in a usable state, there is no
|
While we do our best to keep the master branch in a usable state, there is no guarantee the master branch works.
|
||||||
guarantee the main branch works. Please do not use it for production!
|
Please do not use it for production!
|
||||||
|
|
||||||
Please have a look at the [Release Notes](https://github.com/ClusterCockpit/cc-backend/blob/main/ReleaseNotes.md)
|
Please have a look at the [Release
|
||||||
|
Notes](https://github.com/ClusterCockpit/cc-backend/blob/master/ReleaseNotes.md)
|
||||||
for breaking changes!
|
for breaking changes!
|
||||||
|
|
||||||
# ClusterCockpit REST and GraphQL API backend
|
# ClusterCockpit REST and GraphQL API backend
|
||||||
@@ -40,7 +41,7 @@ For real-time integration with HPC systems, the backend can subscribe to
|
|||||||
state updates, providing an alternative to REST API polling.
|
state updates, providing an alternative to REST API polling.
|
||||||
|
|
||||||
Completed batch jobs are stored in a file-based job archive according to
|
Completed batch jobs are stored in a file-based job archive according to
|
||||||
[this specification](https://github.com/ClusterCockpit/cc-specifications/tree/main/job-archive).
|
[this specification](https://github.com/ClusterCockpit/cc-specifications/tree/master/job-archive).
|
||||||
The backend supports authentication via local accounts, an external LDAP
|
The backend supports authentication via local accounts, an external LDAP
|
||||||
directory, and JWT tokens. Authorization for APIs is implemented with
|
directory, and JWT tokens. Authorization for APIs is implemented with
|
||||||
[JWT](https://jwt.io/) tokens created with public/private key encryption.
|
[JWT](https://jwt.io/) tokens created with public/private key encryption.
|
||||||
@@ -242,73 +243,73 @@ The effective configuration is logged at startup for verification.
|
|||||||
|
|
||||||
## Project file structure
|
## Project file structure
|
||||||
|
|
||||||
- [`.github/`](https://github.com/ClusterCockpit/cc-backend/tree/main/.github)
|
- [`.github/`](https://github.com/ClusterCockpit/cc-backend/tree/master/.github)
|
||||||
GitHub Actions workflows and dependabot configuration for CI/CD.
|
GitHub Actions workflows and dependabot configuration for CI/CD.
|
||||||
- [`api/`](https://github.com/ClusterCockpit/cc-backend/tree/main/api)
|
- [`api/`](https://github.com/ClusterCockpit/cc-backend/tree/master/api)
|
||||||
contains the API schema files for the REST and GraphQL APIs. The REST API is
|
contains the API schema files for the REST and GraphQL APIs. The REST API is
|
||||||
documented in the OpenAPI 3.0 format in
|
documented in the OpenAPI 3.0 format in
|
||||||
[./api/swagger.yaml](./api/swagger.yaml). The GraphQL schema is in
|
[./api/swagger.yaml](./api/swagger.yaml). The GraphQL schema is in
|
||||||
[./api/schema.graphqls](./api/schema.graphqls).
|
[./api/schema.graphqls](./api/schema.graphqls).
|
||||||
- [`cmd/cc-backend`](https://github.com/ClusterCockpit/cc-backend/tree/main/cmd/cc-backend)
|
- [`cmd/cc-backend`](https://github.com/ClusterCockpit/cc-backend/tree/master/cmd/cc-backend)
|
||||||
contains the main application entry point and CLI implementation.
|
contains the main application entry point and CLI implementation.
|
||||||
- [`configs/`](https://github.com/ClusterCockpit/cc-backend/tree/main/configs)
|
- [`configs/`](https://github.com/ClusterCockpit/cc-backend/tree/master/configs)
|
||||||
contains documentation about configuration and command line options and required
|
contains documentation about configuration and command line options and required
|
||||||
environment variables. Sample configuration files are provided.
|
environment variables. Sample configuration files are provided.
|
||||||
- [`init/`](https://github.com/ClusterCockpit/cc-backend/tree/main/init)
|
- [`init/`](https://github.com/ClusterCockpit/cc-backend/tree/master/init)
|
||||||
contains an example of setting up systemd for production use.
|
contains an example of setting up systemd for production use.
|
||||||
- [`internal/`](https://github.com/ClusterCockpit/cc-backend/tree/main/internal)
|
- [`internal/`](https://github.com/ClusterCockpit/cc-backend/tree/master/internal)
|
||||||
contains library source code that is not intended for use by others.
|
contains library source code that is not intended for use by others.
|
||||||
- [`api`](https://github.com/ClusterCockpit/cc-backend/tree/main/internal/api)
|
- [`api`](https://github.com/ClusterCockpit/cc-backend/tree/master/internal/api)
|
||||||
REST API handlers and NATS integration
|
REST API handlers and NATS integration
|
||||||
- [`archiver`](https://github.com/ClusterCockpit/cc-backend/tree/main/internal/archiver)
|
- [`archiver`](https://github.com/ClusterCockpit/cc-backend/tree/master/internal/archiver)
|
||||||
Job archiving functionality
|
Job archiving functionality
|
||||||
- [`auth`](https://github.com/ClusterCockpit/cc-backend/tree/main/internal/auth)
|
- [`auth`](https://github.com/ClusterCockpit/cc-backend/tree/master/internal/auth)
|
||||||
Authentication (local, LDAP, OIDC) and JWT token handling
|
Authentication (local, LDAP, OIDC) and JWT token handling
|
||||||
- [`config`](https://github.com/ClusterCockpit/cc-backend/tree/main/internal/config)
|
- [`config`](https://github.com/ClusterCockpit/cc-backend/tree/master/internal/config)
|
||||||
Configuration management and validation
|
Configuration management and validation
|
||||||
- [`graph`](https://github.com/ClusterCockpit/cc-backend/tree/main/internal/graph)
|
- [`graph`](https://github.com/ClusterCockpit/cc-backend/tree/master/internal/graph)
|
||||||
GraphQL schema and resolvers
|
GraphQL schema and resolvers
|
||||||
- [`importer`](https://github.com/ClusterCockpit/cc-backend/tree/main/internal/importer)
|
- [`importer`](https://github.com/ClusterCockpit/cc-backend/tree/master/internal/importer)
|
||||||
Job data import and database initialization
|
Job data import and database initialization
|
||||||
- [`metricdispatch`](https://github.com/ClusterCockpit/cc-backend/tree/main/internal/metricdispatch)
|
- [`metricdispatch`](https://github.com/ClusterCockpit/cc-backend/tree/master/internal/metricdispatch)
|
||||||
Dispatches metric data loading to appropriate backends
|
Dispatches metric data loading to appropriate backends
|
||||||
- [`repository`](https://github.com/ClusterCockpit/cc-backend/tree/main/internal/repository)
|
- [`repository`](https://github.com/ClusterCockpit/cc-backend/tree/master/internal/repository)
|
||||||
Database repository layer for jobs and metadata
|
Database repository layer for jobs and metadata
|
||||||
- [`routerConfig`](https://github.com/ClusterCockpit/cc-backend/tree/main/internal/routerConfig)
|
- [`routerConfig`](https://github.com/ClusterCockpit/cc-backend/tree/master/internal/routerConfig)
|
||||||
HTTP router configuration and middleware
|
HTTP router configuration and middleware
|
||||||
- [`tagger`](https://github.com/ClusterCockpit/cc-backend/tree/main/internal/tagger)
|
- [`tagger`](https://github.com/ClusterCockpit/cc-backend/tree/master/internal/tagger)
|
||||||
Job classification and application detection
|
Job classification and application detection
|
||||||
- [`taskmanager`](https://github.com/ClusterCockpit/cc-backend/tree/main/internal/taskmanager)
|
- [`taskmanager`](https://github.com/ClusterCockpit/cc-backend/tree/master/internal/taskmanager)
|
||||||
Background task management and scheduled jobs
|
Background task management and scheduled jobs
|
||||||
- [`metricstoreclient`](https://github.com/ClusterCockpit/cc-backend/tree/main/internal/metricstoreclient)
|
- [`metricstoreclient`](https://github.com/ClusterCockpit/cc-backend/tree/master/internal/metricstoreclient)
|
||||||
Client for cc-metric-store queries
|
Client for cc-metric-store queries
|
||||||
- [`pkg/`](https://github.com/ClusterCockpit/cc-backend/tree/main/pkg)
|
- [`pkg/`](https://github.com/ClusterCockpit/cc-backend/tree/master/pkg)
|
||||||
contains Go packages that can be used by other projects.
|
contains Go packages that can be used by other projects.
|
||||||
- [`archive`](https://github.com/ClusterCockpit/cc-backend/tree/main/pkg/archive)
|
- [`archive`](https://github.com/ClusterCockpit/cc-backend/tree/master/pkg/archive)
|
||||||
Job archive backend implementations (filesystem, S3, SQLite)
|
Job archive backend implementations (filesystem, S3, SQLite)
|
||||||
- [`metricstore`](https://github.com/ClusterCockpit/cc-backend/tree/main/pkg/metricstore)
|
- [`metricstore`](https://github.com/ClusterCockpit/cc-backend/tree/master/pkg/metricstore)
|
||||||
In-memory metric data store with checkpointing and metric loading
|
In-memory metric data store with checkpointing and metric loading
|
||||||
- [`tools/`](https://github.com/ClusterCockpit/cc-backend/tree/main/tools)
|
- [`tools/`](https://github.com/ClusterCockpit/cc-backend/tree/master/tools)
|
||||||
Additional command line helper tools.
|
Additional command line helper tools.
|
||||||
- [`archive-manager`](https://github.com/ClusterCockpit/cc-backend/tree/main/tools/archive-manager)
|
- [`archive-manager`](https://github.com/ClusterCockpit/cc-backend/tree/master/tools/archive-manager)
|
||||||
Commands for getting infos about an existing job archive, importing jobs
|
Commands for getting infos about an existing job archive, importing jobs
|
||||||
between archive backends, and converting archives between JSON and Parquet formats.
|
between archive backends, and converting archives between JSON and Parquet formats.
|
||||||
- [`archive-migration`](https://github.com/ClusterCockpit/cc-backend/tree/main/tools/archive-migration)
|
- [`archive-migration`](https://github.com/ClusterCockpit/cc-backend/tree/master/tools/archive-migration)
|
||||||
Tool for migrating job archives between formats.
|
Tool for migrating job archives between formats.
|
||||||
- [`convert-pem-pubkey`](https://github.com/ClusterCockpit/cc-backend/tree/main/tools/convert-pem-pubkey)
|
- [`convert-pem-pubkey`](https://github.com/ClusterCockpit/cc-backend/tree/master/tools/convert-pem-pubkey)
|
||||||
Tool to convert external pubkey for use in `cc-backend`.
|
Tool to convert external pubkey for use in `cc-backend`.
|
||||||
- [`gen-keypair`](https://github.com/ClusterCockpit/cc-backend/tree/main/tools/gen-keypair)
|
- [`gen-keypair`](https://github.com/ClusterCockpit/cc-backend/tree/master/tools/gen-keypair)
|
||||||
contains a small application to generate a compatible JWT keypair. You find
|
contains a small application to generate a compatible JWT keypair. You find
|
||||||
documentation on how to use it
|
documentation on how to use it
|
||||||
[here](https://github.com/ClusterCockpit/cc-backend/blob/main/docs/JWT-Handling.md).
|
[here](https://github.com/ClusterCockpit/cc-backend/blob/master/docs/JWT-Handling.md).
|
||||||
- [`web/`](https://github.com/ClusterCockpit/cc-backend/tree/main/web)
|
- [`web/`](https://github.com/ClusterCockpit/cc-backend/tree/master/web)
|
||||||
Server-side templates and frontend-related files:
|
Server-side templates and frontend-related files:
|
||||||
- [`frontend`](https://github.com/ClusterCockpit/cc-backend/tree/main/web/frontend)
|
- [`frontend`](https://github.com/ClusterCockpit/cc-backend/tree/master/web/frontend)
|
||||||
Svelte components and static assets for the frontend UI
|
Svelte components and static assets for the frontend UI
|
||||||
- [`templates`](https://github.com/ClusterCockpit/cc-backend/tree/main/web/templates)
|
- [`templates`](https://github.com/ClusterCockpit/cc-backend/tree/master/web/templates)
|
||||||
Server-side Go templates, including monitoring views
|
Server-side Go templates, including monitoring views
|
||||||
- [`gqlgen.yml`](https://github.com/ClusterCockpit/cc-backend/blob/main/gqlgen.yml)
|
- [`gqlgen.yml`](https://github.com/ClusterCockpit/cc-backend/blob/master/gqlgen.yml)
|
||||||
Configures the behaviour and generation of
|
Configures the behaviour and generation of
|
||||||
[gqlgen](https://github.com/99designs/gqlgen).
|
[gqlgen](https://github.com/99designs/gqlgen).
|
||||||
- [`startDemo.sh`](https://github.com/ClusterCockpit/cc-backend/blob/main/startDemo.sh)
|
- [`startDemo.sh`](https://github.com/ClusterCockpit/cc-backend/blob/master/startDemo.sh)
|
||||||
is a shell script that sets up demo data, and builds and starts `cc-backend`.
|
is a shell script that sets up demo data, and builds and starts `cc-backend`.
|
||||||
|
|||||||
@@ -1016,8 +1016,11 @@
|
|||||||
"/api/user/{id}": {
|
"/api/user/{id}": {
|
||||||
"post": {
|
"post": {
|
||||||
"description": "Allows admins to add/remove roles and projects for a user",
|
"description": "Allows admins to add/remove roles and projects for a user",
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
"produces": [
|
"produces": [
|
||||||
"text/plain"
|
"application/json"
|
||||||
],
|
],
|
||||||
"tags": [
|
"tags": [
|
||||||
"User"
|
"User"
|
||||||
@@ -1032,35 +1035,26 @@
|
|||||||
"required": true
|
"required": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "string",
|
"description": "Single Field Changes",
|
||||||
"description": "Role to add",
|
"name": "request",
|
||||||
"name": "add-role",
|
"in": "body",
|
||||||
"in": "formData"
|
"required": true,
|
||||||
},
|
"schema": {
|
||||||
{
|
"$ref": "#/definitions/api.UpdateUserAPIRequest"
|
||||||
"type": "string",
|
}
|
||||||
"description": "Role to remove",
|
|
||||||
"name": "remove-role",
|
|
||||||
"in": "formData"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "string",
|
|
||||||
"description": "Project to add",
|
|
||||||
"name": "add-project",
|
|
||||||
"in": "formData"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "string",
|
|
||||||
"description": "Project to remove",
|
|
||||||
"name": "remove-project",
|
|
||||||
"in": "formData"
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"responses": {
|
"responses": {
|
||||||
"200": {
|
"200": {
|
||||||
"description": "Success message",
|
"description": "OK",
|
||||||
"schema": {
|
"schema": {
|
||||||
"type": "string"
|
"$ref": "#/definitions/api.DefaultAPIResponse"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "Bad Request",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/api.ErrorResponse"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"403": {
|
"403": {
|
||||||
@@ -1934,6 +1928,31 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"api.UpdateUserAPIRequest": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"add-role": {
|
||||||
|
"description": "Role to add to user $ID",
|
||||||
|
"type": "string",
|
||||||
|
"example": "user"
|
||||||
|
},
|
||||||
|
"remove-role": {
|
||||||
|
"description": "Role to remove from user $ID",
|
||||||
|
"type": "string",
|
||||||
|
"example": "user"
|
||||||
|
},
|
||||||
|
"add-project": {
|
||||||
|
"description": "Project to add to user $ID managed array",
|
||||||
|
"type": "string",
|
||||||
|
"example": "abcd100"
|
||||||
|
},
|
||||||
|
"remove-project": {
|
||||||
|
"description": "Project to remove from user $ID managed array",
|
||||||
|
"type": "string",
|
||||||
|
"example": "abcd100"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"api.DefaultAPIResponse": {
|
"api.DefaultAPIResponse": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
|
|||||||
@@ -31,6 +31,25 @@ definitions:
|
|||||||
example: Debug
|
example: Debug
|
||||||
type: string
|
type: string
|
||||||
type: object
|
type: object
|
||||||
|
api.UpdateUserAPIRequest:
|
||||||
|
properties:
|
||||||
|
add-project:
|
||||||
|
description: Project to add to user $ID managed array
|
||||||
|
example: abcd100
|
||||||
|
type: string
|
||||||
|
add-role:
|
||||||
|
description: Role to add to user $ID
|
||||||
|
example: user
|
||||||
|
type: string
|
||||||
|
remove-project:
|
||||||
|
description: Project to remove from user $ID managed array
|
||||||
|
example: abcd100
|
||||||
|
type: string
|
||||||
|
remove-role:
|
||||||
|
description: Role to remove from user $ID
|
||||||
|
example: user
|
||||||
|
type: string
|
||||||
|
type: object
|
||||||
api.DefaultAPIResponse:
|
api.DefaultAPIResponse:
|
||||||
properties:
|
properties:
|
||||||
msg:
|
msg:
|
||||||
@@ -1430,6 +1449,8 @@ paths:
|
|||||||
- Nodestates
|
- Nodestates
|
||||||
/api/user/{id}:
|
/api/user/{id}:
|
||||||
post:
|
post:
|
||||||
|
consumes:
|
||||||
|
- application/json
|
||||||
description: Allows admins to add/remove roles and projects for a user
|
description: Allows admins to add/remove roles and projects for a user
|
||||||
parameters:
|
parameters:
|
||||||
- description: Username
|
- description: Username
|
||||||
@@ -1437,29 +1458,23 @@ paths:
|
|||||||
name: id
|
name: id
|
||||||
required: true
|
required: true
|
||||||
type: string
|
type: string
|
||||||
- description: Role to add
|
- description: Single Field Changes
|
||||||
in: formData
|
in: body
|
||||||
name: add-role
|
name: request
|
||||||
type: string
|
required: true
|
||||||
- description: Role to remove
|
schema:
|
||||||
in: formData
|
$ref: '#/definitions/api.UpdateUserAPIRequest'
|
||||||
name: remove-role
|
|
||||||
type: string
|
|
||||||
- description: Project to add
|
|
||||||
in: formData
|
|
||||||
name: add-project
|
|
||||||
type: string
|
|
||||||
- description: Project to remove
|
|
||||||
in: formData
|
|
||||||
name: remove-project
|
|
||||||
type: string
|
|
||||||
produces:
|
produces:
|
||||||
- text/plain
|
- application/json
|
||||||
responses:
|
responses:
|
||||||
"200":
|
"200":
|
||||||
description: Success message
|
description: OK
|
||||||
schema:
|
schema:
|
||||||
type: string
|
$ref: '#/definitions/api.DefaultAPIResponse'
|
||||||
|
"400":
|
||||||
|
description: Bad Request
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/api.ErrorResponse'
|
||||||
"403":
|
"403":
|
||||||
description: Forbidden
|
description: Forbidden
|
||||||
schema:
|
schema:
|
||||||
|
|||||||
@@ -1023,8 +1023,11 @@ const docTemplate = `{
|
|||||||
"/api/user/{id}": {
|
"/api/user/{id}": {
|
||||||
"post": {
|
"post": {
|
||||||
"description": "Allows admins to add/remove roles and projects for a user",
|
"description": "Allows admins to add/remove roles and projects for a user",
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
"produces": [
|
"produces": [
|
||||||
"text/plain"
|
"application/json"
|
||||||
],
|
],
|
||||||
"tags": [
|
"tags": [
|
||||||
"User"
|
"User"
|
||||||
@@ -1039,35 +1042,26 @@ const docTemplate = `{
|
|||||||
"required": true
|
"required": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "string",
|
"description": "Single Field Changes",
|
||||||
"description": "Role to add",
|
"name": "request",
|
||||||
"name": "add-role",
|
"in": "body",
|
||||||
"in": "formData"
|
"required": true,
|
||||||
},
|
"schema": {
|
||||||
{
|
"$ref": "#/definitions/api.UpdateUserAPIRequest"
|
||||||
"type": "string",
|
}
|
||||||
"description": "Role to remove",
|
|
||||||
"name": "remove-role",
|
|
||||||
"in": "formData"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "string",
|
|
||||||
"description": "Project to add",
|
|
||||||
"name": "add-project",
|
|
||||||
"in": "formData"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "string",
|
|
||||||
"description": "Project to remove",
|
|
||||||
"name": "remove-project",
|
|
||||||
"in": "formData"
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"responses": {
|
"responses": {
|
||||||
"200": {
|
"200": {
|
||||||
"description": "Success message",
|
"description": "OK",
|
||||||
"schema": {
|
"schema": {
|
||||||
"type": "string"
|
"$ref": "#/definitions/api.DefaultAPIResponse"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "Bad Request",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/api.ErrorResponse"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"403": {
|
"403": {
|
||||||
@@ -1941,6 +1935,31 @@ const docTemplate = `{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"api.UpdateUserAPIRequest": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"add-role": {
|
||||||
|
"description": "Role to add to user $ID",
|
||||||
|
"type": "string",
|
||||||
|
"example": "user"
|
||||||
|
},
|
||||||
|
"remove-role": {
|
||||||
|
"description": "Role to remove from user $ID",
|
||||||
|
"type": "string",
|
||||||
|
"example": "user"
|
||||||
|
},
|
||||||
|
"add-project": {
|
||||||
|
"description": "Project to add to user $ID managed array",
|
||||||
|
"type": "string",
|
||||||
|
"example": "abcd100"
|
||||||
|
},
|
||||||
|
"remove-project": {
|
||||||
|
"description": "Project to remove from user $ID managed array",
|
||||||
|
"type": "string",
|
||||||
|
"example": "abcd100"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"api.DefaultAPIResponse": {
|
"api.DefaultAPIResponse": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
|
|||||||
@@ -79,6 +79,8 @@ func (api *RestAPI) MountAPIRoutes(r chi.Router) {
|
|||||||
// REST API Uses TokenAuth
|
// REST API Uses TokenAuth
|
||||||
// User List
|
// User List
|
||||||
r.Get("/users/", api.getUsers)
|
r.Get("/users/", api.getUsers)
|
||||||
|
// User Edit
|
||||||
|
r.Post("/user/{id}", api.updateUserByRequest)
|
||||||
// Cluster List
|
// Cluster List
|
||||||
r.Get("/clusters/", api.getClusters)
|
r.Get("/clusters/", api.getClusters)
|
||||||
// Slurm node state
|
// Slurm node state
|
||||||
@@ -152,7 +154,7 @@ func (api *RestAPI) MountConfigAPIRoutes(r chi.Router) {
|
|||||||
r.Put("/config/users/", api.createUser)
|
r.Put("/config/users/", api.createUser)
|
||||||
r.Get("/config/users/", api.getUsers)
|
r.Get("/config/users/", api.getUsers)
|
||||||
r.Delete("/config/users/", api.deleteUser)
|
r.Delete("/config/users/", api.deleteUser)
|
||||||
r.Post("/config/user/{id}", api.updateUser)
|
r.Post("/config/user/{id}", api.updateUserByForm)
|
||||||
r.Post("/config/notice/", api.editNotice)
|
r.Post("/config/notice/", api.editNotice)
|
||||||
r.Get("/config/taggers/", api.getTaggers)
|
r.Get("/config/taggers/", api.getTaggers)
|
||||||
r.Post("/config/taggers/run/", api.runTagger)
|
r.Post("/config/taggers/run/", api.runTagger)
|
||||||
|
|||||||
@@ -24,6 +24,14 @@ type APIReturnedUser struct {
|
|||||||
Projects []string `json:"projects"`
|
Projects []string `json:"projects"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// UpdateUserAPIRequest model
|
||||||
|
type UpdateUserAPIRequest struct {
|
||||||
|
NewRole string `json:"add-role" example:"user"` // Role to add to user $ID
|
||||||
|
DelRole string `json:"remove-role" example:"user"` // Role to remove from user $ID
|
||||||
|
NewProj string `json:"add-project" example:"abcd100"` // Project to add to user $ID managed array
|
||||||
|
DelProj string `json:"remove-project" example:"abcd100"` // Project to remove from user $ID managed array
|
||||||
|
}
|
||||||
|
|
||||||
// getUsers godoc
|
// getUsers godoc
|
||||||
// @summary Returns a list of users
|
// @summary Returns a list of users
|
||||||
// @tags User
|
// @tags User
|
||||||
@@ -58,22 +66,74 @@ func (api *RestAPI) getUsers(rw http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// updateUser godoc
|
// updateUserByRequest godoc
|
||||||
// @summary Update user roles and projects
|
// @summary Update user roles and projects
|
||||||
// @tags User
|
// @tags User
|
||||||
// @description Allows admins to add/remove roles and projects for a user
|
// @description Allows admins to add/remove roles and projects for a user
|
||||||
// @produce plain
|
// @accept json
|
||||||
|
// @produce json
|
||||||
// @param id path string true "Username"
|
// @param id path string true "Username"
|
||||||
// @param add-role formData string false "Role to add"
|
// @param request body api.UpdateUserAPIRequest true "Single Field Changes"
|
||||||
// @param remove-role formData string false "Role to remove"
|
|
||||||
// @param add-project formData string false "Project to add"
|
|
||||||
// @param remove-project formData string false "Project to remove"
|
|
||||||
// @success 200 {string} string "Success message"
|
// @success 200 {string} string "Success message"
|
||||||
// @failure 403 {object} api.ErrorResponse "Forbidden"
|
// @failure 403 {object} api.ErrorResponse "Forbidden"
|
||||||
// @failure 422 {object} api.ErrorResponse "Unprocessable Entity"
|
// @failure 422 {object} api.ErrorResponse "Unprocessable Entity"
|
||||||
// @security ApiKeyAuth
|
// @security ApiKeyAuth
|
||||||
// @router /api/user/{id} [post]
|
// @router /api/user/{id} [post]
|
||||||
func (api *RestAPI) updateUser(rw http.ResponseWriter, r *http.Request) {
|
func (api *RestAPI) updateUserByRequest(rw http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
|
if user := repository.GetUserFromContext(r.Context()); !user.HasRole(schema.RoleAdmin) {
|
||||||
|
handleError(fmt.Errorf("only admins are allowed to update a user"), http.StatusForbidden, rw)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get Values
|
||||||
|
var req UpdateUserAPIRequest
|
||||||
|
if err := decode(r.Body, &req); err != nil {
|
||||||
|
handleError(fmt.Errorf("decoding request failed: %w", err), http.StatusBadRequest, rw)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
rw.Header().Set("Content-Type", "application/json")
|
||||||
|
|
||||||
|
// Handle role updates
|
||||||
|
if req.NewRole != "" {
|
||||||
|
if err := repository.GetUserRepository().AddRole(r.Context(), chi.URLParam(r, "id"), req.NewRole); err != nil {
|
||||||
|
handleError(fmt.Errorf("adding role failed: %w", err), http.StatusUnprocessableEntity, rw)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err := json.NewEncoder(rw).Encode(DefaultAPIResponse{Message: fmt.Sprintf("Add Role Success for user %s", chi.URLParam(r, "id"))}); err != nil {
|
||||||
|
cclog.Errorf("Failed to encode response: %v", err)
|
||||||
|
}
|
||||||
|
} else if req.DelRole != "" {
|
||||||
|
if err := repository.GetUserRepository().RemoveRole(r.Context(), chi.URLParam(r, "id"), req.DelRole); err != nil {
|
||||||
|
handleError(fmt.Errorf("removing role failed: %w", err), http.StatusUnprocessableEntity, rw)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err := json.NewEncoder(rw).Encode(DefaultAPIResponse{Message: fmt.Sprintf("Remove Role Success for user %s", chi.URLParam(r, "id"))}); err != nil {
|
||||||
|
cclog.Errorf("Failed to encode response: %v", err)
|
||||||
|
}
|
||||||
|
} else if req.NewProj != "" {
|
||||||
|
if err := repository.GetUserRepository().AddProject(r.Context(), chi.URLParam(r, "id"), req.NewProj); err != nil {
|
||||||
|
handleError(fmt.Errorf("adding project failed: %w", err), http.StatusUnprocessableEntity, rw)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err := json.NewEncoder(rw).Encode(DefaultAPIResponse{Message: fmt.Sprintf("Add Project Success for user %s", chi.URLParam(r, "id"))}); err != nil {
|
||||||
|
cclog.Errorf("Failed to encode response: %v", err)
|
||||||
|
}
|
||||||
|
} else if req.DelProj != "" {
|
||||||
|
if err := repository.GetUserRepository().RemoveProject(r.Context(), chi.URLParam(r, "id"), req.DelProj); err != nil {
|
||||||
|
handleError(fmt.Errorf("removing project failed: %w", err), http.StatusUnprocessableEntity, rw)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err := json.NewEncoder(rw).Encode(DefaultAPIResponse{Message: fmt.Sprintf("Remove Project Success for user %s", chi.URLParam(r, "id"))}); err != nil {
|
||||||
|
cclog.Errorf("Failed to encode response: %v", err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
handleError(fmt.Errorf("no operation specified: must provide add-role, remove-role, add-project, or remove-project"), http.StatusBadRequest, rw)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (api *RestAPI) updateUserByForm(rw http.ResponseWriter, r *http.Request) {
|
||||||
// SecuredCheck() only worked with TokenAuth: Removed
|
// SecuredCheck() only worked with TokenAuth: Removed
|
||||||
|
|
||||||
if user := repository.GetUserFromContext(r.Context()); !user.HasRole(schema.RoleAdmin) {
|
if user := repository.GetUserFromContext(r.Context()); !user.HasRole(schema.RoleAdmin) {
|
||||||
|
|||||||
@@ -650,7 +650,7 @@ func securedCheck(user *schema.User, r *http.Request) error {
|
|||||||
}
|
}
|
||||||
// If SplitHostPort fails, IPAddress is already just a host (no port)
|
// If SplitHostPort fails, IPAddress is already just a host (no port)
|
||||||
|
|
||||||
// If nothing declared in config: Continue
|
// If nothing declared in config: Continue // FIXME: Allow All If Not Declared?
|
||||||
if len(config.Keys.APIAllowedIPs) == 0 {
|
if len(config.Keys.APIAllowedIPs) == 0 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user