diff --git a/management/server/http/handlers/peers/peers_handler.go b/management/server/http/handlers/peers/peers_handler.go index ccd545546..5247eb16e 100644 --- a/management/server/http/handlers/peers/peers_handler.go +++ b/management/server/http/handlers/peers/peers_handler.go @@ -62,7 +62,7 @@ func (h *Handler) CreateJob(w http.ResponseWriter, r *http.Request) { return } - job, err := types.NewJob(userAuth.UserId, userAuth.AccountId, peerID, types.JobType(req.Type), req.Parameters) + job, err := types.NewJob(userAuth.UserId, userAuth.AccountId, peerID, req) if err != nil { util.WriteError(ctx, err, w) return @@ -72,7 +72,7 @@ func (h *Handler) CreateJob(w http.ResponseWriter, r *http.Request) { return } - util.WriteJSONObject(ctx, w, job) + util.WriteJSONObject(ctx, w, toSingleJobResponse(job)) } func (h *Handler) ListJobs(w http.ResponseWriter, r *http.Request) { @@ -92,7 +92,12 @@ func (h *Handler) ListJobs(w http.ResponseWriter, r *http.Request) { return } - util.WriteJSONObject(ctx, w, jobs) + respBody := make([]*api.Job, 0, len(jobs)) + for _, job := range jobs { + respBody = append(respBody, toSingleJobResponse(job)) + } + + util.WriteJSONObject(ctx, w, respBody) } func (h *Handler) GetJob(w http.ResponseWriter, r *http.Request) { @@ -113,7 +118,7 @@ func (h *Handler) GetJob(w http.ResponseWriter, r *http.Request) { return } - util.WriteJSONObject(ctx, w, job) + util.WriteJSONObject(ctx, w, toSingleJobResponse(job)) } func (h *Handler) checkPeerStatus(peer *nbpeer.Peer) (*nbpeer.Peer, error) { @@ -496,6 +501,29 @@ func toPeerListItemResponse(peer *nbpeer.Peer, groupsInfo []api.GroupMinimum, dn } } +func toSingleJobResponse(job *types.Job) *api.Job { + var parameters map[string]any + job.DecodeParameters(¶meters) + + return &api.Job{ + Id: job.ID, + Status: (*api.JobStatus)(&job.Status), + CompletedAt: job.CompletedAt, + CreatedAt: job.CreatedAt, + FailedReason: &job.FailedReason, + TriggeredBy: job.TriggeredBy, + Workload: struct { + Parameters *map[string]any "json:\"parameters,omitempty\"" + Result *string "json:\"result,omitempty\"" + Type *api.JobWorkloadType "json:\"type,omitempty\"" + }{ + Type: (*api.JobWorkloadType)(&job.Type), + Parameters: ¶meters, + Result: &job.Result, + }, + } +} + func fqdn(peer *nbpeer.Peer, dnsDomain string) string { fqdn := peer.FQDN(dnsDomain) if fqdn == "" { diff --git a/management/server/types/job.go b/management/server/types/job.go index 4f5950b92..79c0dee84 100644 --- a/management/server/types/job.go +++ b/management/server/types/job.go @@ -6,6 +6,7 @@ import ( "time" "github.com/google/uuid" + "github.com/netbirdio/netbird/shared/management/http/api" ) type JobStatus string @@ -65,19 +66,27 @@ type JobParametersBundle struct { } // NewJob creates a new job with default fields and validation -func NewJob(triggeredBy, accountID, peerID string, jobType JobType, parameters map[string]any) (*Job, error) { +func NewJob(triggeredBy, accountID, peerID string, req *api.JobRequest) (*Job, error) { + if req.Workload.Type == nil { + return nil, fmt.Errorf("Job type required") + } + + if req.Workload.Parameters == nil { + return nil, fmt.Errorf("Job parameters required") + } + job := &Job{ ID: uuid.New().String(), TriggeredBy: triggeredBy, PeerID: peerID, AccountID: accountID, - Type: jobType, + Type: JobType(*req.Workload.Type), Status: JobStatusPending, CreatedAt: time.Now().UTC(), } // Encode parameters - if err := job.encodeParameters(parameters); err != nil { + if err := job.encodeParameters(*req.Workload.Parameters); err != nil { return nil, fmt.Errorf("failed to encode job parameters: %w", err) } diff --git a/shared/management/http/api/openapi.yml b/shared/management/http/api/openapi.yml index 3c9d5c8c5..fc3b80bf3 100644 --- a/shared/management/http/api/openapi.yml +++ b/shared/management/http/api/openapi.yml @@ -37,23 +37,25 @@ components: JobRequest: type: object properties: - type: - type: string - description: The type of job to execute - example: bundle - enum: [ "bundle" ] - parameters: + workload: type: object - description: Key-value parameters required for the job - additionalProperties: true - example: - bundle_for: true - bundle_for_time: 5 - log_file_count: 2 - anonymize: false + properties: + type: + type: string + description: The type of job to execute + example: bundle + enum: [ "bundle" ] + parameters: + type: object + description: Key-value parameters required for the job + additionalProperties: true + example: + bundle_for: true + bundle_for_time: 5 + log_file_count: 2 + anonymize: false required: - - type - - parameters + - workload Job: type: object properties: @@ -61,59 +63,52 @@ components: type: string description: Primary identifier example: "123456" - createdAt: + created_at: type: string format: date-time description: When the job was created (UTC) - completedAt: + completed_at: type: string format: date-time description: When the job finished, null if still running - triggeredBy: + triggered_by: type: string description: User that triggered this job example: "user_42" - peerId: - type: string - description: Associated peer ID - example: "peer_99" - accountId: - type: string - description: Associated account ID - example: "acc_77" - type: - type: string - enum: [ bundle ] - example: bundle status: type: string enum: [ pending, succeeded, failed ] example: pending - failedReason: + failed_reason: type: string description: Why the job failed (if failed) example: "Connection timeout" - result: - type: string - description: Job output (JSON, URL, etc.) - example: "https://example.com/bundle.zip" - parameters: + workload: type: object - additionalProperties: true - description: Job configuration parameters - example: - bundle_for: true - bundle_for_time: 60 - log_file_count: 10 - anonymize: false + properties: + type: + type: string + description: The type of job to execute + example: bundle + enum: [ "bundle" ] + parameters: + type: object + description: Key-value parameters required for the job + additionalProperties: true + example: + bundle_for: true + bundle_for_time: 5 + log_file_count: 2 + anonymize: false + result: + type: string + description: Job output (JSON, URL, etc.) + example: "https://example.com/bundle.zip" required: - id - - createdAt - - triggeredBy - - peerId - - accountId - - type - - status + - created_at + - triggered_by + - workload Account: type: object properties: diff --git a/shared/management/http/api/types.gen.go b/shared/management/http/api/types.gen.go index ba29847b9..2a8100572 100644 --- a/shared/management/http/api/types.gen.go +++ b/shared/management/http/api/types.gen.go @@ -111,14 +111,14 @@ const ( JobStatusSucceeded JobStatus = "succeeded" ) -// Defines values for JobType. +// Defines values for JobWorkloadType. const ( - JobTypeBundle JobType = "bundle" + JobWorkloadTypeBundle JobWorkloadType = "bundle" ) -// Defines values for JobRequestType. +// Defines values for JobRequestWorkloadType. const ( - JobRequestTypeBundle JobRequestType = "bundle" + JobRequestWorkloadTypeBundle JobRequestWorkloadType = "bundle" ) // Defines values for NameserverNsType. @@ -662,53 +662,52 @@ type IngressPortAllocationRequestPortRangeProtocol string // Job defines model for Job. type Job struct { - // AccountId Associated account ID - AccountId string `json:"accountId"` - // CompletedAt When the job finished, null if still running - CompletedAt *time.Time `json:"completedAt,omitempty"` + CompletedAt *time.Time `json:"completed_at,omitempty"` // CreatedAt When the job was created (UTC) - CreatedAt time.Time `json:"createdAt"` + CreatedAt time.Time `json:"created_at"` // FailedReason Why the job failed (if failed) - FailedReason *string `json:"failedReason,omitempty"` + FailedReason *string `json:"failed_reason,omitempty"` // Id Primary identifier - Id string `json:"id"` - - // Parameters Job configuration parameters - Parameters *map[string]interface{} `json:"parameters,omitempty"` - - // PeerId Associated peer ID - PeerId string `json:"peerId"` - - // Result Job output (JSON, URL, etc.) - Result *string `json:"result,omitempty"` - Status JobStatus `json:"status"` + Id string `json:"id"` + Status *JobStatus `json:"status,omitempty"` // TriggeredBy User that triggered this job - TriggeredBy string `json:"triggeredBy"` - Type JobType `json:"type"` + TriggeredBy string `json:"triggered_by"` + Workload struct { + // Parameters Key-value parameters required for the job + Parameters *map[string]interface{} `json:"parameters,omitempty"` + + // Result Job output (JSON, URL, etc.) + Result *string `json:"result,omitempty"` + + // Type The type of job to execute + Type *JobWorkloadType `json:"type,omitempty"` + } `json:"workload"` } // JobStatus defines model for Job.Status. type JobStatus string -// JobType defines model for Job.Type. -type JobType string +// JobWorkloadType The type of job to execute +type JobWorkloadType string // JobRequest defines model for JobRequest. type JobRequest struct { - // Parameters Key-value parameters required for the job - Parameters map[string]interface{} `json:"parameters"` + Workload struct { + // Parameters Key-value parameters required for the job + Parameters *map[string]interface{} `json:"parameters,omitempty"` - // Type The type of job to execute - Type JobRequestType `json:"type"` + // Type The type of job to execute + Type *JobRequestWorkloadType `json:"type,omitempty"` + } `json:"workload"` } -// JobRequestType The type of job to execute -type JobRequestType string +// JobRequestWorkloadType The type of job to execute +type JobRequestWorkloadType string // Location Describe geographical location information type Location struct {