This commit is contained in:
2025-12-15 14:06:33 +01:00
parent 554527445b
commit 97a322354f
8 changed files with 83 additions and 82 deletions

View File

@@ -330,7 +330,9 @@ func runServer(ctx context.Context) error {
// Initialize web UI // Initialize web UI
cfg := ccconf.GetPackageConfig("ui") cfg := ccconf.GetPackageConfig("ui")
web.Init(cfg) if err := web.Init(cfg); err != nil {
return fmt.Errorf("initializing web UI: %w", err)
}
// Initialize HTTP server // Initialize HTTP server
srv, err := NewServer(version, commit, date) srv, err := NewServer(version, commit, date)

View File

@@ -50,7 +50,7 @@ const (
type Server struct { type Server struct {
router *mux.Router router *mux.Router
server *http.Server server *http.Server
apiHandle *api.RestApi apiHandle *api.RestAPI
} }
func onFailureResponse(rw http.ResponseWriter, r *http.Request, err error) { func onFailureResponse(rw http.ResponseWriter, r *http.Request, err error) {
@@ -239,13 +239,13 @@ func (s *Server) init() error {
// Mount all /monitoring/... and /api/... routes. // Mount all /monitoring/... and /api/... routes.
routerConfig.SetupRoutes(secured, buildInfo) routerConfig.SetupRoutes(secured, buildInfo)
s.apiHandle.MountApiRoutes(securedapi) s.apiHandle.MountAPIRoutes(securedapi)
s.apiHandle.MountUserApiRoutes(userapi) s.apiHandle.MountUserAPIRoutes(userapi)
s.apiHandle.MountConfigApiRoutes(configapi) s.apiHandle.MountConfigAPIRoutes(configapi)
s.apiHandle.MountFrontendApiRoutes(frontendapi) s.apiHandle.MountFrontendAPIRoutes(frontendapi)
if memorystore.InternalCCMSFlag { if memorystore.InternalCCMSFlag {
s.apiHandle.MountMetricStoreApiRoutes(metricstoreapi) s.apiHandle.MountMetricStoreAPIRoutes(metricstoreapi)
} }
if config.Keys.EmbedStaticFiles { if config.Keys.EmbedStaticFiles {

View File

@@ -35,7 +35,7 @@ import (
_ "github.com/mattn/go-sqlite3" _ "github.com/mattn/go-sqlite3"
) )
func setup(t *testing.T) *api.RestApi { func setup(t *testing.T) *api.RestAPI {
const testconfig = `{ const testconfig = `{
"main": { "main": {
"addr": "0.0.0.0:8080", "addr": "0.0.0.0:8080",
@@ -228,7 +228,7 @@ func TestRestApi(t *testing.T) {
r := mux.NewRouter() r := mux.NewRouter()
r.PathPrefix("/api").Subrouter() r.PathPrefix("/api").Subrouter()
r.StrictSlash(true) r.StrictSlash(true)
restapi.MountApiRoutes(r) restapi.MountAPIRoutes(r)
var TestJobId int64 = 123 var TestJobId int64 = 123
TestClusterName := "testcluster" TestClusterName := "testcluster"

View File

@@ -34,7 +34,7 @@ type GetClustersAPIResponse struct {
// @failure 500 {object} api.ErrorResponse "Internal Server Error" // @failure 500 {object} api.ErrorResponse "Internal Server Error"
// @security ApiKeyAuth // @security ApiKeyAuth
// @router /api/clusters/ [get] // @router /api/clusters/ [get]
func (api *RestApi) getClusters(rw http.ResponseWriter, r *http.Request) { func (api *RestAPI) getClusters(rw http.ResponseWriter, r *http.Request) {
if user := repository.GetUserFromContext(r.Context()); user != nil && if user := repository.GetUserFromContext(r.Context()); user != nil &&
!user.HasRole(schema.RoleApi) { !user.HasRole(schema.RoleApi) {

View File

@@ -45,44 +45,43 @@ type StopJobAPIRequest struct {
StopTime int64 `json:"stopTime" validate:"required" example:"1649763839"` StopTime int64 `json:"stopTime" validate:"required" example:"1649763839"`
} }
// DeleteJobApiRequest model // DeleteJobAPIRequest model
type DeleteJobApiRequest struct { type DeleteJobAPIRequest struct {
JobID *int64 `json:"jobId" validate:"required" example:"123000"` // Cluster Job ID of job JobID *int64 `json:"jobId" validate:"required" example:"123000"` // Cluster Job ID of job
Cluster *string `json:"cluster" example:"fritz"` // Cluster of job Cluster *string `json:"cluster" example:"fritz"` // Cluster of job
StartTime *int64 `json:"startTime" example:"1649723812"` // Start Time of job as epoch StartTime *int64 `json:"startTime" example:"1649723812"` // Start Time of job as epoch
} }
// GetJobsApiResponse model // GetJobsAPIResponse model
type GetJobsApiResponse struct { type GetJobsAPIResponse struct {
Jobs []*schema.Job `json:"jobs"` // Array of jobs Jobs []*schema.Job `json:"jobs"` // Array of jobs
Items int `json:"items"` // Number of jobs returned Items int `json:"items"` // Number of jobs returned
Page int `json:"page"` // Page id returned Page int `json:"page"` // Page id returned
} }
// ApiTag model // APITag model
type ApiTag struct { type APITag struct {
// Tag Type // Tag Type
Type string `json:"type" example:"Debug"` Type string `json:"type" example:"Debug"`
Name string `json:"name" example:"Testjob"` // Tag Name Name string `json:"name" example:"Testjob"` // Tag Name
Scope string `json:"scope" example:"global"` // Tag Scope for Frontend Display Scope string `json:"scope" example:"global"` // Tag Scope for Frontend Display
} }
// ApiMeta model
type EditMetaRequest struct { type EditMetaRequest struct {
Key string `json:"key" example:"jobScript"` Key string `json:"key" example:"jobScript"`
Value string `json:"value" example:"bash script"` Value string `json:"value" example:"bash script"`
} }
type TagJobApiRequest []*ApiTag type TagJobAPIRequest []*APITag
type GetJobApiRequest []string type GetJobAPIRequest []string
type GetJobApiResponse struct { type GetJobAPIResponse struct {
Meta *schema.Job Meta *schema.Job
Data []*JobMetricWithName Data []*JobMetricWithName
} }
type GetCompleteJobApiResponse struct { type GetCompleteJobAPIResponse struct {
Meta *schema.Job Meta *schema.Job
Data schema.JobData Data schema.JobData
} }
@@ -112,7 +111,7 @@ type JobMetricWithName struct {
// @failure 500 {object} api.ErrorResponse "Internal Server Error" // @failure 500 {object} api.ErrorResponse "Internal Server Error"
// @security ApiKeyAuth // @security ApiKeyAuth
// @router /api/jobs/ [get] // @router /api/jobs/ [get]
func (api *RestApi) getJobs(rw http.ResponseWriter, r *http.Request) { func (api *RestAPI) getJobs(rw http.ResponseWriter, r *http.Request) {
withMetadata := false withMetadata := false
filter := &model.JobFilter{} filter := &model.JobFilter{}
page := &model.PageRequest{ItemsPerPage: 25, Page: 1} page := &model.PageRequest{ItemsPerPage: 25, Page: 1}
@@ -213,7 +212,7 @@ func (api *RestApi) getJobs(rw http.ResponseWriter, r *http.Request) {
bw := bufio.NewWriter(rw) bw := bufio.NewWriter(rw)
defer bw.Flush() defer bw.Flush()
payload := GetJobsApiResponse{ payload := GetJobsAPIResponse{
Jobs: results, Jobs: results,
Items: page.ItemsPerPage, Items: page.ItemsPerPage,
Page: page.Page, Page: page.Page,
@@ -225,7 +224,7 @@ func (api *RestApi) getJobs(rw http.ResponseWriter, r *http.Request) {
} }
} }
// getCompleteJobById godoc // getCompleteJobByID godoc
// @summary Get job meta and optional all metric data // @summary Get job meta and optional all metric data
// @tags Job query // @tags Job query
// @description Job to get is specified by database ID // @description Job to get is specified by database ID
@@ -242,7 +241,7 @@ func (api *RestApi) getJobs(rw http.ResponseWriter, r *http.Request) {
// @failure 500 {object} api.ErrorResponse "Internal Server Error" // @failure 500 {object} api.ErrorResponse "Internal Server Error"
// @security ApiKeyAuth // @security ApiKeyAuth
// @router /api/jobs/{id} [get] // @router /api/jobs/{id} [get]
func (api *RestApi) getCompleteJobById(rw http.ResponseWriter, r *http.Request) { func (api *RestAPI) getCompleteJobByID(rw http.ResponseWriter, r *http.Request) {
// Fetch job from db // Fetch job from db
id, ok := mux.Vars(r)["id"] id, ok := mux.Vars(r)["id"]
var job *schema.Job var job *schema.Job
@@ -306,7 +305,7 @@ func (api *RestApi) getCompleteJobById(rw http.ResponseWriter, r *http.Request)
bw := bufio.NewWriter(rw) bw := bufio.NewWriter(rw)
defer bw.Flush() defer bw.Flush()
payload := GetCompleteJobApiResponse{ payload := GetCompleteJobAPIResponse{
Meta: job, Meta: job,
Data: data, Data: data,
} }
@@ -317,7 +316,7 @@ func (api *RestApi) getCompleteJobById(rw http.ResponseWriter, r *http.Request)
} }
} }
// getJobById godoc // getJobByID godoc
// @summary Get job meta and configurable metric data // @summary Get job meta and configurable metric data
// @tags Job query // @tags Job query
// @description Job to get is specified by database ID // @description Job to get is specified by database ID
@@ -335,7 +334,7 @@ func (api *RestApi) getCompleteJobById(rw http.ResponseWriter, r *http.Request)
// @failure 500 {object} api.ErrorResponse "Internal Server Error" // @failure 500 {object} api.ErrorResponse "Internal Server Error"
// @security ApiKeyAuth // @security ApiKeyAuth
// @router /api/jobs/{id} [post] // @router /api/jobs/{id} [post]
func (api *RestApi) getJobById(rw http.ResponseWriter, r *http.Request) { func (api *RestAPI) getJobByID(rw http.ResponseWriter, r *http.Request) {
// Fetch job from db // Fetch job from db
id, ok := mux.Vars(r)["id"] id, ok := mux.Vars(r)["id"]
var job *schema.Job var job *schema.Job
@@ -369,7 +368,7 @@ func (api *RestApi) getJobById(rw http.ResponseWriter, r *http.Request) {
return return
} }
var metrics GetJobApiRequest var metrics GetJobAPIRequest
if err = decode(r.Body, &metrics); err != nil { if err = decode(r.Body, &metrics); err != nil {
handleError(fmt.Errorf("decoding request failed: %w", err), http.StatusBadRequest, rw) handleError(fmt.Errorf("decoding request failed: %w", err), http.StatusBadRequest, rw)
return return
@@ -412,7 +411,7 @@ func (api *RestApi) getJobById(rw http.ResponseWriter, r *http.Request) {
bw := bufio.NewWriter(rw) bw := bufio.NewWriter(rw)
defer bw.Flush() defer bw.Flush()
payload := GetJobApiResponse{ payload := GetJobAPIResponse{
Meta: job, Meta: job,
Data: res, Data: res,
} }
@@ -439,7 +438,7 @@ func (api *RestApi) getJobById(rw http.ResponseWriter, r *http.Request) {
// @failure 500 {object} api.ErrorResponse "Internal Server Error" // @failure 500 {object} api.ErrorResponse "Internal Server Error"
// @security ApiKeyAuth // @security ApiKeyAuth
// @router /api/jobs/edit_meta/{id} [post] // @router /api/jobs/edit_meta/{id} [post]
func (api *RestApi) editMeta(rw http.ResponseWriter, r *http.Request) { func (api *RestAPI) editMeta(rw http.ResponseWriter, r *http.Request) {
id, err := strconv.ParseInt(mux.Vars(r)["id"], 10, 64) id, err := strconv.ParseInt(mux.Vars(r)["id"], 10, 64)
if err != nil { if err != nil {
handleError(fmt.Errorf("parsing job ID failed: %w", err), http.StatusBadRequest, rw) handleError(fmt.Errorf("parsing job ID failed: %w", err), http.StatusBadRequest, rw)
@@ -487,7 +486,7 @@ func (api *RestApi) editMeta(rw http.ResponseWriter, r *http.Request) {
// @failure 500 {object} api.ErrorResponse "Internal Server Error" // @failure 500 {object} api.ErrorResponse "Internal Server Error"
// @security ApiKeyAuth // @security ApiKeyAuth
// @router /api/jobs/tag_job/{id} [post] // @router /api/jobs/tag_job/{id} [post]
func (api *RestApi) tagJob(rw http.ResponseWriter, r *http.Request) { func (api *RestAPI) tagJob(rw http.ResponseWriter, r *http.Request) {
id, err := strconv.ParseInt(mux.Vars(r)["id"], 10, 64) id, err := strconv.ParseInt(mux.Vars(r)["id"], 10, 64)
if err != nil { if err != nil {
handleError(fmt.Errorf("parsing job ID failed: %w", err), http.StatusBadRequest, rw) handleError(fmt.Errorf("parsing job ID failed: %w", err), http.StatusBadRequest, rw)
@@ -506,21 +505,21 @@ func (api *RestApi) tagJob(rw http.ResponseWriter, r *http.Request) {
return return
} }
var req TagJobApiRequest var req TagJobAPIRequest
if err := decode(r.Body, &req); err != nil { if err := decode(r.Body, &req); err != nil {
handleError(fmt.Errorf("decoding request failed: %w", err), http.StatusBadRequest, rw) handleError(fmt.Errorf("decoding request failed: %w", err), http.StatusBadRequest, rw)
return return
} }
for _, tag := range req { for _, tag := range req {
tagId, err := api.JobRepository.AddTagOrCreate(repository.GetUserFromContext(r.Context()), *job.ID, tag.Type, tag.Name, tag.Scope) tagID, err := api.JobRepository.AddTagOrCreate(repository.GetUserFromContext(r.Context()), *job.ID, tag.Type, tag.Name, tag.Scope)
if err != nil { if err != nil {
handleError(fmt.Errorf("adding tag failed: %w", err), http.StatusInternalServerError, rw) handleError(fmt.Errorf("adding tag failed: %w", err), http.StatusInternalServerError, rw)
return return
} }
job.Tags = append(job.Tags, &schema.Tag{ job.Tags = append(job.Tags, &schema.Tag{
ID: tagId, ID: tagID,
Type: tag.Type, Type: tag.Type,
Name: tag.Name, Name: tag.Name,
Scope: tag.Scope, Scope: tag.Scope,
@@ -551,7 +550,7 @@ func (api *RestApi) tagJob(rw http.ResponseWriter, r *http.Request) {
// @failure 500 {object} api.ErrorResponse "Internal Server Error" // @failure 500 {object} api.ErrorResponse "Internal Server Error"
// @security ApiKeyAuth // @security ApiKeyAuth
// @router /jobs/tag_job/{id} [delete] // @router /jobs/tag_job/{id} [delete]
func (api *RestApi) removeTagJob(rw http.ResponseWriter, r *http.Request) { func (api *RestAPI) removeTagJob(rw http.ResponseWriter, r *http.Request) {
id, err := strconv.ParseInt(mux.Vars(r)["id"], 10, 64) id, err := strconv.ParseInt(mux.Vars(r)["id"], 10, 64)
if err != nil { if err != nil {
handleError(fmt.Errorf("parsing job ID failed: %w", err), http.StatusBadRequest, rw) handleError(fmt.Errorf("parsing job ID failed: %w", err), http.StatusBadRequest, rw)
@@ -570,7 +569,7 @@ func (api *RestApi) removeTagJob(rw http.ResponseWriter, r *http.Request) {
return return
} }
var req TagJobApiRequest var req TagJobAPIRequest
if err := decode(r.Body, &req); err != nil { if err := decode(r.Body, &req); err != nil {
handleError(fmt.Errorf("decoding request failed: %w", err), http.StatusBadRequest, rw) handleError(fmt.Errorf("decoding request failed: %w", err), http.StatusBadRequest, rw)
return return
@@ -615,8 +614,8 @@ func (api *RestApi) removeTagJob(rw http.ResponseWriter, r *http.Request) {
// @failure 500 {object} api.ErrorResponse "Internal Server Error" // @failure 500 {object} api.ErrorResponse "Internal Server Error"
// @security ApiKeyAuth // @security ApiKeyAuth
// @router /tags/ [delete] // @router /tags/ [delete]
func (api *RestApi) removeTags(rw http.ResponseWriter, r *http.Request) { func (api *RestAPI) removeTags(rw http.ResponseWriter, r *http.Request) {
var req TagJobApiRequest var req TagJobAPIRequest
if err := decode(r.Body, &req); err != nil { if err := decode(r.Body, &req); err != nil {
handleError(fmt.Errorf("decoding request failed: %w", err), http.StatusBadRequest, rw) handleError(fmt.Errorf("decoding request failed: %w", err), http.StatusBadRequest, rw)
return return
@@ -659,7 +658,7 @@ func (api *RestApi) removeTags(rw http.ResponseWriter, r *http.Request) {
// @failure 500 {object} api.ErrorResponse "Internal Server Error" // @failure 500 {object} api.ErrorResponse "Internal Server Error"
// @security ApiKeyAuth // @security ApiKeyAuth
// @router /api/jobs/start_job/ [post] // @router /api/jobs/start_job/ [post]
func (api *RestApi) startJob(rw http.ResponseWriter, r *http.Request) { func (api *RestAPI) startJob(rw http.ResponseWriter, r *http.Request) {
req := schema.Job{ req := schema.Job{
Shared: "none", Shared: "none",
MonitoringStatus: schema.MonitoringStatusRunningOrArchiving, MonitoringStatus: schema.MonitoringStatusRunningOrArchiving,
@@ -716,7 +715,7 @@ func (api *RestApi) startJob(rw http.ResponseWriter, r *http.Request) {
cclog.Infof("new job (id: %d): cluster=%s, jobId=%d, user=%s, startTime=%d", id, req.Cluster, req.JobID, req.User, req.StartTime) cclog.Infof("new job (id: %d): cluster=%s, jobId=%d, user=%s, startTime=%d", id, req.Cluster, req.JobID, req.User, req.StartTime)
rw.Header().Add("Content-Type", "application/json") rw.Header().Add("Content-Type", "application/json")
rw.WriteHeader(http.StatusCreated) rw.WriteHeader(http.StatusCreated)
if err := json.NewEncoder(rw).Encode(DefaultApiResponse{ if err := json.NewEncoder(rw).Encode(DefaultAPIResponse{
Message: "success", Message: "success",
}); err != nil { }); err != nil {
cclog.Errorf("Failed to encode response: %v", err) cclog.Errorf("Failed to encode response: %v", err)
@@ -739,7 +738,7 @@ func (api *RestApi) startJob(rw http.ResponseWriter, r *http.Request) {
// @failure 500 {object} api.ErrorResponse "Internal Server Error" // @failure 500 {object} api.ErrorResponse "Internal Server Error"
// @security ApiKeyAuth // @security ApiKeyAuth
// @router /api/jobs/stop_job/ [post] // @router /api/jobs/stop_job/ [post]
func (api *RestApi) stopJobByRequest(rw http.ResponseWriter, r *http.Request) { func (api *RestAPI) stopJobByRequest(rw http.ResponseWriter, r *http.Request) {
// Parse request body // Parse request body
req := StopJobAPIRequest{} req := StopJobAPIRequest{}
if err := decode(r.Body, &req); err != nil { if err := decode(r.Body, &req); err != nil {
@@ -771,7 +770,7 @@ func (api *RestApi) stopJobByRequest(rw http.ResponseWriter, r *http.Request) {
api.checkAndHandleStopJob(rw, job, req) api.checkAndHandleStopJob(rw, job, req)
} }
// deleteJobById godoc // deleteJobByID godoc
// @summary Remove a job from the sql database // @summary Remove a job from the sql database
// @tags Job remove // @tags Job remove
// @description Job to remove is specified by database ID. This will not remove the job from the job archive. // @description Job to remove is specified by database ID. This will not remove the job from the job archive.
@@ -786,7 +785,7 @@ func (api *RestApi) stopJobByRequest(rw http.ResponseWriter, r *http.Request) {
// @failure 500 {object} api.ErrorResponse "Internal Server Error" // @failure 500 {object} api.ErrorResponse "Internal Server Error"
// @security ApiKeyAuth // @security ApiKeyAuth
// @router /api/jobs/delete_job/{id} [delete] // @router /api/jobs/delete_job/{id} [delete]
func (api *RestApi) deleteJobById(rw http.ResponseWriter, r *http.Request) { func (api *RestAPI) deleteJobByID(rw http.ResponseWriter, r *http.Request) {
// Fetch job (that will be stopped) from db // Fetch job (that will be stopped) from db
id, ok := mux.Vars(r)["id"] id, ok := mux.Vars(r)["id"]
var err error var err error
@@ -808,7 +807,7 @@ func (api *RestApi) deleteJobById(rw http.ResponseWriter, r *http.Request) {
} }
rw.Header().Add("Content-Type", "application/json") rw.Header().Add("Content-Type", "application/json")
rw.WriteHeader(http.StatusOK) rw.WriteHeader(http.StatusOK)
if err := json.NewEncoder(rw).Encode(DefaultApiResponse{ if err := json.NewEncoder(rw).Encode(DefaultAPIResponse{
Message: fmt.Sprintf("Successfully deleted job %s", id), Message: fmt.Sprintf("Successfully deleted job %s", id),
}); err != nil { }); err != nil {
cclog.Errorf("Failed to encode response: %v", err) cclog.Errorf("Failed to encode response: %v", err)
@@ -831,9 +830,9 @@ func (api *RestApi) deleteJobById(rw http.ResponseWriter, r *http.Request) {
// @failure 500 {object} api.ErrorResponse "Internal Server Error" // @failure 500 {object} api.ErrorResponse "Internal Server Error"
// @security ApiKeyAuth // @security ApiKeyAuth
// @router /api/jobs/delete_job/ [delete] // @router /api/jobs/delete_job/ [delete]
func (api *RestApi) deleteJobByRequest(rw http.ResponseWriter, r *http.Request) { func (api *RestAPI) deleteJobByRequest(rw http.ResponseWriter, r *http.Request) {
// Parse request body // Parse request body
req := DeleteJobApiRequest{} req := DeleteJobAPIRequest{}
if err := decode(r.Body, &req); err != nil { if err := decode(r.Body, &req); err != nil {
handleError(fmt.Errorf("parsing request body failed: %w", err), http.StatusBadRequest, rw) handleError(fmt.Errorf("parsing request body failed: %w", err), http.StatusBadRequest, rw)
return return
@@ -861,7 +860,7 @@ func (api *RestApi) deleteJobByRequest(rw http.ResponseWriter, r *http.Request)
rw.Header().Add("Content-Type", "application/json") rw.Header().Add("Content-Type", "application/json")
rw.WriteHeader(http.StatusOK) rw.WriteHeader(http.StatusOK)
if err := json.NewEncoder(rw).Encode(DefaultApiResponse{ if err := json.NewEncoder(rw).Encode(DefaultAPIResponse{
Message: fmt.Sprintf("Successfully deleted job %d", job.ID), Message: fmt.Sprintf("Successfully deleted job %d", job.ID),
}); err != nil { }); err != nil {
cclog.Errorf("Failed to encode response: %v", err) cclog.Errorf("Failed to encode response: %v", err)
@@ -883,7 +882,7 @@ func (api *RestApi) deleteJobByRequest(rw http.ResponseWriter, r *http.Request)
// @failure 500 {object} api.ErrorResponse "Internal Server Error" // @failure 500 {object} api.ErrorResponse "Internal Server Error"
// @security ApiKeyAuth // @security ApiKeyAuth
// @router /api/jobs/delete_job_before/{ts} [delete] // @router /api/jobs/delete_job_before/{ts} [delete]
func (api *RestApi) deleteJobBefore(rw http.ResponseWriter, r *http.Request) { func (api *RestAPI) deleteJobBefore(rw http.ResponseWriter, r *http.Request) {
var cnt int var cnt int
// Fetch job (that will be stopped) from db // Fetch job (that will be stopped) from db
id, ok := mux.Vars(r)["ts"] id, ok := mux.Vars(r)["ts"]
@@ -907,14 +906,14 @@ func (api *RestApi) deleteJobBefore(rw http.ResponseWriter, r *http.Request) {
rw.Header().Add("Content-Type", "application/json") rw.Header().Add("Content-Type", "application/json")
rw.WriteHeader(http.StatusOK) rw.WriteHeader(http.StatusOK)
if err := json.NewEncoder(rw).Encode(DefaultApiResponse{ if err := json.NewEncoder(rw).Encode(DefaultAPIResponse{
Message: fmt.Sprintf("Successfully deleted %d jobs", cnt), Message: fmt.Sprintf("Successfully deleted %d jobs", cnt),
}); err != nil { }); err != nil {
cclog.Errorf("Failed to encode response: %v", err) cclog.Errorf("Failed to encode response: %v", err)
} }
} }
func (api *RestApi) checkAndHandleStopJob(rw http.ResponseWriter, job *schema.Job, req StopJobAPIRequest) { func (api *RestAPI) checkAndHandleStopJob(rw http.ResponseWriter, job *schema.Job, req StopJobAPIRequest) {
// Sanity checks // Sanity checks
if job.State != schema.JobStateRunning { if job.State != schema.JobStateRunning {
handleError(fmt.Errorf("jobId %d (id %d) on %s : job has already been stopped (state is: %s)", job.JobID, job.ID, job.Cluster, job.State), http.StatusUnprocessableEntity, rw) handleError(fmt.Errorf("jobId %d (id %d) on %s : job has already been stopped (state is: %s)", job.JobID, job.ID, job.Cluster, job.State), http.StatusUnprocessableEntity, rw)
@@ -966,7 +965,7 @@ func (api *RestApi) checkAndHandleStopJob(rw http.ResponseWriter, job *schema.Jo
archiver.TriggerArchiving(job) archiver.TriggerArchiving(job)
} }
func (api *RestApi) getJobMetrics(rw http.ResponseWriter, r *http.Request) { func (api *RestAPI) getJobMetrics(rw http.ResponseWriter, r *http.Request) {
id := mux.Vars(r)["id"] id := mux.Vars(r)["id"]
metrics := r.URL.Query()["metric"] metrics := r.URL.Query()["metric"]
var scopes []schema.MetricScope var scopes []schema.MetricScope

View File

@@ -54,7 +54,7 @@ func determineState(states []string) schema.SchedulerState {
// @failure 500 {object} api.ErrorResponse "Internal Server Error" // @failure 500 {object} api.ErrorResponse "Internal Server Error"
// @security ApiKeyAuth // @security ApiKeyAuth
// @router /api/nodestats/ [post] // @router /api/nodestats/ [post]
func (api *RestApi) updateNodeStates(rw http.ResponseWriter, r *http.Request) { func (api *RestAPI) updateNodeStates(rw http.ResponseWriter, r *http.Request) {
// Parse request body // Parse request body
req := UpdateNodeStatesRequest{} req := UpdateNodeStatesRequest{}
if err := decode(r.Body, &req); err != nil { if err := decode(r.Body, &req); err != nil {

View File

@@ -50,7 +50,7 @@ const (
noticeFilePerms = 0o644 noticeFilePerms = 0o644
) )
type RestApi struct { type RestAPI struct {
JobRepository *repository.JobRepository JobRepository *repository.JobRepository
Authentication *auth.Authentication Authentication *auth.Authentication
MachineStateDir string MachineStateDir string
@@ -61,15 +61,15 @@ type RestApi struct {
RepositoryMutex sync.Mutex RepositoryMutex sync.Mutex
} }
func New() *RestApi { func New() *RestAPI {
return &RestApi{ return &RestAPI{
JobRepository: repository.GetJobRepository(), JobRepository: repository.GetJobRepository(),
MachineStateDir: config.Keys.MachineStateDir, MachineStateDir: config.Keys.MachineStateDir,
Authentication: auth.GetAuthInstance(), Authentication: auth.GetAuthInstance(),
} }
} }
func (api *RestApi) MountApiRoutes(r *mux.Router) { func (api *RestAPI) MountAPIRoutes(r *mux.Router) {
r.StrictSlash(true) r.StrictSlash(true)
// REST API Uses TokenAuth // REST API Uses TokenAuth
// User List // User List
@@ -82,14 +82,14 @@ func (api *RestApi) MountApiRoutes(r *mux.Router) {
r.HandleFunc("/jobs/start_job/", api.startJob).Methods(http.MethodPost, http.MethodPut) r.HandleFunc("/jobs/start_job/", api.startJob).Methods(http.MethodPost, http.MethodPut)
r.HandleFunc("/jobs/stop_job/", api.stopJobByRequest).Methods(http.MethodPost, http.MethodPut) r.HandleFunc("/jobs/stop_job/", api.stopJobByRequest).Methods(http.MethodPost, http.MethodPut)
r.HandleFunc("/jobs/", api.getJobs).Methods(http.MethodGet) r.HandleFunc("/jobs/", api.getJobs).Methods(http.MethodGet)
r.HandleFunc("/jobs/{id}", api.getJobById).Methods(http.MethodPost) r.HandleFunc("/jobs/{id}", api.getJobByID).Methods(http.MethodPost)
r.HandleFunc("/jobs/{id}", api.getCompleteJobById).Methods(http.MethodGet) r.HandleFunc("/jobs/{id}", api.getCompleteJobByID).Methods(http.MethodGet)
r.HandleFunc("/jobs/tag_job/{id}", api.tagJob).Methods(http.MethodPost, http.MethodPatch) r.HandleFunc("/jobs/tag_job/{id}", api.tagJob).Methods(http.MethodPost, http.MethodPatch)
r.HandleFunc("/jobs/tag_job/{id}", api.removeTagJob).Methods(http.MethodDelete) r.HandleFunc("/jobs/tag_job/{id}", api.removeTagJob).Methods(http.MethodDelete)
r.HandleFunc("/jobs/edit_meta/{id}", api.editMeta).Methods(http.MethodPost, http.MethodPatch) r.HandleFunc("/jobs/edit_meta/{id}", api.editMeta).Methods(http.MethodPost, http.MethodPatch)
r.HandleFunc("/jobs/metrics/{id}", api.getJobMetrics).Methods(http.MethodGet) r.HandleFunc("/jobs/metrics/{id}", api.getJobMetrics).Methods(http.MethodGet)
r.HandleFunc("/jobs/delete_job/", api.deleteJobByRequest).Methods(http.MethodDelete) r.HandleFunc("/jobs/delete_job/", api.deleteJobByRequest).Methods(http.MethodDelete)
r.HandleFunc("/jobs/delete_job/{id}", api.deleteJobById).Methods(http.MethodDelete) r.HandleFunc("/jobs/delete_job/{id}", api.deleteJobByID).Methods(http.MethodDelete)
r.HandleFunc("/jobs/delete_job_before/{ts}", api.deleteJobBefore).Methods(http.MethodDelete) r.HandleFunc("/jobs/delete_job_before/{ts}", api.deleteJobBefore).Methods(http.MethodDelete)
r.HandleFunc("/tags/", api.removeTags).Methods(http.MethodDelete) r.HandleFunc("/tags/", api.removeTags).Methods(http.MethodDelete)
@@ -100,16 +100,16 @@ func (api *RestApi) MountApiRoutes(r *mux.Router) {
} }
} }
func (api *RestApi) MountUserApiRoutes(r *mux.Router) { func (api *RestAPI) MountUserAPIRoutes(r *mux.Router) {
r.StrictSlash(true) r.StrictSlash(true)
// REST API Uses TokenAuth // REST API Uses TokenAuth
r.HandleFunc("/jobs/", api.getJobs).Methods(http.MethodGet) r.HandleFunc("/jobs/", api.getJobs).Methods(http.MethodGet)
r.HandleFunc("/jobs/{id}", api.getJobById).Methods(http.MethodPost) r.HandleFunc("/jobs/{id}", api.getJobByID).Methods(http.MethodPost)
r.HandleFunc("/jobs/{id}", api.getCompleteJobById).Methods(http.MethodGet) r.HandleFunc("/jobs/{id}", api.getCompleteJobByID).Methods(http.MethodGet)
r.HandleFunc("/jobs/metrics/{id}", api.getJobMetrics).Methods(http.MethodGet) r.HandleFunc("/jobs/metrics/{id}", api.getJobMetrics).Methods(http.MethodGet)
} }
func (api *RestApi) MountMetricStoreApiRoutes(r *mux.Router) { func (api *RestAPI) MountMetricStoreAPIRoutes(r *mux.Router) {
// REST API Uses TokenAuth // REST API Uses TokenAuth
// Note: StrictSlash handles trailing slash variations automatically // Note: StrictSlash handles trailing slash variations automatically
r.HandleFunc("/api/free", freeMetrics).Methods(http.MethodPost) r.HandleFunc("/api/free", freeMetrics).Methods(http.MethodPost)
@@ -123,7 +123,7 @@ func (api *RestApi) MountMetricStoreApiRoutes(r *mux.Router) {
r.HandleFunc("/api/healthcheck/", metricsHealth).Methods(http.MethodGet) r.HandleFunc("/api/healthcheck/", metricsHealth).Methods(http.MethodGet)
} }
func (api *RestApi) MountConfigApiRoutes(r *mux.Router) { func (api *RestAPI) MountConfigAPIRoutes(r *mux.Router) {
r.StrictSlash(true) r.StrictSlash(true)
// Settings Frontend Uses SessionAuth // Settings Frontend Uses SessionAuth
if api.Authentication != nil { if api.Authentication != nil {
@@ -136,7 +136,7 @@ func (api *RestApi) MountConfigApiRoutes(r *mux.Router) {
} }
} }
func (api *RestApi) MountFrontendApiRoutes(r *mux.Router) { func (api *RestAPI) MountFrontendAPIRoutes(r *mux.Router) {
r.StrictSlash(true) r.StrictSlash(true)
// Settings Frontend Uses SessionAuth // Settings Frontend Uses SessionAuth
if api.Authentication != nil { if api.Authentication != nil {
@@ -152,8 +152,8 @@ type ErrorResponse struct {
Error string `json:"error"` // Error Message Error string `json:"error"` // Error Message
} }
// DefaultApiResponse model // DefaultAPIResponse model
type DefaultApiResponse struct { type DefaultAPIResponse struct {
Message string `json:"msg"` Message string `json:"msg"`
} }
@@ -175,7 +175,7 @@ func decode(r io.Reader, val any) error {
return dec.Decode(val) return dec.Decode(val)
} }
func (api *RestApi) editNotice(rw http.ResponseWriter, r *http.Request) { func (api *RestAPI) editNotice(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) {
@@ -217,7 +217,7 @@ func (api *RestApi) editNotice(rw http.ResponseWriter, r *http.Request) {
} }
} }
func (api *RestApi) getJWT(rw http.ResponseWriter, r *http.Request) { func (api *RestAPI) getJWT(rw http.ResponseWriter, r *http.Request) {
rw.Header().Set("Content-Type", "text/plain") rw.Header().Set("Content-Type", "text/plain")
username := r.FormValue("username") username := r.FormValue("username")
me := repository.GetUserFromContext(r.Context()) me := repository.GetUserFromContext(r.Context())
@@ -244,7 +244,7 @@ func (api *RestApi) getJWT(rw http.ResponseWriter, r *http.Request) {
rw.Write([]byte(jwt)) rw.Write([]byte(jwt))
} }
func (api *RestApi) getRoles(rw http.ResponseWriter, r *http.Request) { func (api *RestAPI) getRoles(rw http.ResponseWriter, r *http.Request) {
// SecuredCheck() only worked with TokenAuth: Removed // SecuredCheck() only worked with TokenAuth: Removed
user := repository.GetUserFromContext(r.Context()) user := repository.GetUserFromContext(r.Context())
@@ -265,7 +265,7 @@ func (api *RestApi) getRoles(rw http.ResponseWriter, r *http.Request) {
} }
} }
func (api *RestApi) updateConfiguration(rw http.ResponseWriter, r *http.Request) { func (api *RestAPI) updateConfiguration(rw http.ResponseWriter, r *http.Request) {
rw.Header().Set("Content-Type", "text/plain") rw.Header().Set("Content-Type", "text/plain")
key, value := r.FormValue("key"), r.FormValue("value") key, value := r.FormValue("key"), r.FormValue("value")
@@ -278,7 +278,7 @@ func (api *RestApi) updateConfiguration(rw http.ResponseWriter, r *http.Request)
rw.Write([]byte("success")) rw.Write([]byte("success"))
} }
func (api *RestApi) putMachineState(rw http.ResponseWriter, r *http.Request) { func (api *RestAPI) putMachineState(rw http.ResponseWriter, r *http.Request) {
if api.MachineStateDir == "" { if api.MachineStateDir == "" {
handleError(fmt.Errorf("machine state not enabled"), http.StatusNotFound, rw) handleError(fmt.Errorf("machine state not enabled"), http.StatusNotFound, rw)
return return
@@ -320,7 +320,7 @@ func (api *RestApi) putMachineState(rw http.ResponseWriter, r *http.Request) {
rw.WriteHeader(http.StatusCreated) rw.WriteHeader(http.StatusCreated)
} }
func (api *RestApi) getMachineState(rw http.ResponseWriter, r *http.Request) { func (api *RestAPI) getMachineState(rw http.ResponseWriter, r *http.Request) {
if api.MachineStateDir == "" { if api.MachineStateDir == "" {
handleError(fmt.Errorf("machine state not enabled"), http.StatusNotFound, rw) handleError(fmt.Errorf("machine state not enabled"), http.StatusNotFound, rw)
return return

View File

@@ -38,7 +38,7 @@ type APIReturnedUser struct {
// @failure 500 {string} string "Internal Server Error" // @failure 500 {string} string "Internal Server Error"
// @security ApiKeyAuth // @security ApiKeyAuth
// @router /api/users/ [get] // @router /api/users/ [get]
func (api *RestApi) getUsers(rw http.ResponseWriter, r *http.Request) { func (api *RestAPI) getUsers(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) {
@@ -73,7 +73,7 @@ func (api *RestApi) getUsers(rw http.ResponseWriter, r *http.Request) {
// @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) updateUser(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) {
@@ -95,7 +95,7 @@ func (api *RestApi) updateUser(rw http.ResponseWriter, r *http.Request) {
handleError(fmt.Errorf("adding role failed: %w", err), http.StatusUnprocessableEntity, rw) handleError(fmt.Errorf("adding role failed: %w", err), http.StatusUnprocessableEntity, rw)
return return
} }
if err := json.NewEncoder(rw).Encode(DefaultApiResponse{Message: "Add Role Success"}); err != nil { if err := json.NewEncoder(rw).Encode(DefaultAPIResponse{Message: "Add Role Success"}); err != nil {
cclog.Errorf("Failed to encode response: %v", err) cclog.Errorf("Failed to encode response: %v", err)
} }
} else if delrole != "" { } else if delrole != "" {
@@ -103,7 +103,7 @@ func (api *RestApi) updateUser(rw http.ResponseWriter, r *http.Request) {
handleError(fmt.Errorf("removing role failed: %w", err), http.StatusUnprocessableEntity, rw) handleError(fmt.Errorf("removing role failed: %w", err), http.StatusUnprocessableEntity, rw)
return return
} }
if err := json.NewEncoder(rw).Encode(DefaultApiResponse{Message: "Remove Role Success"}); err != nil { if err := json.NewEncoder(rw).Encode(DefaultAPIResponse{Message: "Remove Role Success"}); err != nil {
cclog.Errorf("Failed to encode response: %v", err) cclog.Errorf("Failed to encode response: %v", err)
} }
} else if newproj != "" { } else if newproj != "" {
@@ -111,7 +111,7 @@ func (api *RestApi) updateUser(rw http.ResponseWriter, r *http.Request) {
handleError(fmt.Errorf("adding project failed: %w", err), http.StatusUnprocessableEntity, rw) handleError(fmt.Errorf("adding project failed: %w", err), http.StatusUnprocessableEntity, rw)
return return
} }
if err := json.NewEncoder(rw).Encode(DefaultApiResponse{Message: "Add Project Success"}); err != nil { if err := json.NewEncoder(rw).Encode(DefaultAPIResponse{Message: "Add Project Success"}); err != nil {
cclog.Errorf("Failed to encode response: %v", err) cclog.Errorf("Failed to encode response: %v", err)
} }
} else if delproj != "" { } else if delproj != "" {
@@ -119,7 +119,7 @@ func (api *RestApi) updateUser(rw http.ResponseWriter, r *http.Request) {
handleError(fmt.Errorf("removing project failed: %w", err), http.StatusUnprocessableEntity, rw) handleError(fmt.Errorf("removing project failed: %w", err), http.StatusUnprocessableEntity, rw)
return return
} }
if err := json.NewEncoder(rw).Encode(DefaultApiResponse{Message: "Remove Project Success"}); err != nil { if err := json.NewEncoder(rw).Encode(DefaultAPIResponse{Message: "Remove Project Success"}); err != nil {
cclog.Errorf("Failed to encode response: %v", err) cclog.Errorf("Failed to encode response: %v", err)
} }
} else { } else {
@@ -144,7 +144,7 @@ func (api *RestApi) updateUser(rw http.ResponseWriter, r *http.Request) {
// @failure 422 {object} api.ErrorResponse "Unprocessable Entity" // @failure 422 {object} api.ErrorResponse "Unprocessable Entity"
// @security ApiKeyAuth // @security ApiKeyAuth
// @router /api/users/ [post] // @router /api/users/ [post]
func (api *RestApi) createUser(rw http.ResponseWriter, r *http.Request) { func (api *RestAPI) createUser(rw http.ResponseWriter, r *http.Request) {
// SecuredCheck() only worked with TokenAuth: Removed // SecuredCheck() only worked with TokenAuth: Removed
rw.Header().Set("Content-Type", "text/plain") rw.Header().Set("Content-Type", "text/plain")
@@ -203,7 +203,7 @@ func (api *RestApi) createUser(rw http.ResponseWriter, r *http.Request) {
// @failure 422 {object} api.ErrorResponse "Unprocessable Entity" // @failure 422 {object} api.ErrorResponse "Unprocessable Entity"
// @security ApiKeyAuth // @security ApiKeyAuth
// @router /api/users/ [delete] // @router /api/users/ [delete]
func (api *RestApi) deleteUser(rw http.ResponseWriter, r *http.Request) { func (api *RestAPI) deleteUser(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) {