feat!(backend): unify script run and ad-hoc job run

This commit is contained in:
2026-04-05 04:10:59 +03:00
parent add1242b97
commit 8226429b5b
7 changed files with 95 additions and 174 deletions
+29 -15
View File
@@ -72,35 +72,49 @@ func (h *JobsHandlers) AddJob(c *gin.Context) {
return
}
agent, ok := h.tracker.GetAgent(in.AgentID)
if !ok {
c.Status(http.StatusNotFound)
c.Error(fmt.Errorf("agent not found"))
return
}
command, err := resolveCommand(c, h.svc, in.InterpreterID, in.Command)
result, err := h.runCommand(c, in.AgentID, in.InterpreterID, in.Command, in.Stdin)
if err != nil {
c.Error(err)
return
}
c.JSON(http.StatusCreated, result)
}
// runCommand resolves command, submits a job to the agent, and returns AddJobOut.
// Shared between jobs and scripts handlers.
func (h *JobsHandlers) runCommand(
c *gin.Context,
agentID string,
interpID int64,
command string,
stdin *string,
) (*AddJobOut, error) {
agent, ok := h.tracker.GetAgent(agentID)
if !ok {
return nil, fmt.Errorf("agent not found")
}
cmd, err := resolveCommand(c, h.svc, interpID, command)
if err != nil {
return nil, err
}
jid, err := agent.AddJob(models.JobForInsert{
Command: command,
Stdin: in.Stdin,
Command: cmd,
Stdin: stdin,
})
if err != nil {
c.Error(err)
return
return nil, err
}
waitURL := fmt.Sprintf("%s/api/v1/jobs/%d/wait", h.whereami, jid)
c.JSON(http.StatusCreated, AddJobOut{
return &AddJobOut{
ID: jid,
Command: command,
Command: cmd,
WaitURL: waitURL,
})
}, nil
}
// WaitJob waits for a submitted job to complete (long-poll).
+37 -57
View File
@@ -13,12 +13,13 @@ import (
)
type ScriptHandlers struct {
svc *service.ScriptService
tracker *commander.ConnTracker
svc *service.ScriptService
tracker *commander.ConnTracker
whereami string
}
func NewScriptHandlers(svc *service.ScriptService, tracker *commander.ConnTracker) ScriptHandlers {
return ScriptHandlers{svc: svc, tracker: tracker}
func NewScriptHandlers(svc *service.ScriptService, tracker *commander.ConnTracker, whereami string) ScriptHandlers {
return ScriptHandlers{svc: svc, tracker: tracker, whereami: whereami}
}
type RunScriptIn struct {
@@ -28,73 +29,52 @@ type RunScriptIn struct {
Stdin *string `json:"stdin"`
}
type RunScriptOut struct {
ID int64 `json:"id"`
Command []string `json:"command"`
Stdin *string `json:"stdin"`
Stdout string `json:"stdout"`
Stderr string `json:"stderr"`
Status int32 `json:"status"`
}
// RunScript executes a script on a target agent.
// RunScript submits a script as a job and returns a wait_url for the result.
// @Summary Run a script on an agent
// @Description Resolves interpreter argv[] and sends the full command to the agent
// @Tags scripts
// @Accept json
// @Produce json
// @Param body body RunScriptIn true "Script request"
// @Success 201 {object} RunScriptOut
// @Success 201 {object} AddJobOut
// @Security Bearer
// @Router /scripts/run [post]
func (h *ScriptHandlers) RunScript(c *gin.Context) {
err := func() error {
var in RunScriptIn
if err := c.Bind(&in); err != nil {
return err
}
var in RunScriptIn
if err := c.Bind(&in); err != nil {
c.Error(err)
return
}
command, err := h.svc.ResolveCommand(
c.Request.Context(),
in.InterpreterID,
in.ScriptText,
)
if err != nil {
return err
}
agent, ok := h.tracker.GetAgent(in.AgentID)
if !ok {
c.Status(http.StatusNotFound)
c.Error(fmt.Errorf("agent not found"))
return
}
agent, ok := h.tracker.GetAgent(in.AgentID)
if !ok {
c.Status(http.StatusNotFound)
return fmt.Errorf("agent not found")
}
jid, err := agent.AddJob(models.JobForInsert{
Command: command,
Stdin: in.Stdin,
})
if err != nil {
return err
}
job, err := agent.WaitJob(jid)
if err != nil {
return err
}
c.JSON(http.StatusCreated, RunScriptOut{
ID: job.ID,
Command: job.Command,
Stdin: job.Stdin,
Stdout: job.Stdout,
Stderr: job.Stderr,
Status: job.Status,
})
return nil
}()
command, err := h.svc.ResolveCommand(c.Request.Context(), in.InterpreterID, in.ScriptText)
if err != nil {
c.Error(err)
return
}
jid, err := agent.AddJob(models.JobForInsert{
Command: command,
Stdin: in.Stdin,
})
if err != nil {
c.Error(err)
return
}
waitURL := fmt.Sprintf("%s/api/v1/jobs/%d/wait", h.whereami, jid)
c.JSON(http.StatusCreated, AddJobOut{
ID: jid,
Command: command,
WaitURL: waitURL,
})
}
// ListInterpreters returns all registered script interpreters.
+13 -19
View File
@@ -16,13 +16,14 @@ import (
// ScriptHandlersGroup handles script management routes.
type ScriptHandlersGroup struct {
svc *service.ScriptService
cmder *commander.Commander
svc *service.ScriptService
cmder *commander.Commander
whereami string
}
// NewScriptHandlersGroup creates a new ScriptHandlersGroup.
func NewScriptHandlersGroup(svc *service.ScriptService, cmder *commander.Commander) *ScriptHandlersGroup {
return &ScriptHandlersGroup{svc: svc, cmder: cmder}
func NewScriptHandlersGroup(svc *service.ScriptService, cmder *commander.Commander, whereami string) *ScriptHandlersGroup {
return &ScriptHandlersGroup{svc: svc, cmder: cmder, whereami: whereami}
}
// GetTree returns the script directory tree.
@@ -192,13 +193,13 @@ func (sh *ScriptHandlersGroup) DeleteScript(c *gin.Context) {
// RunScriptByID executes a stored script on a target agent.
// @Summary Run script by ID
// @Description Loads a script from storage, resolves interpreter command, and executes on the specified agent
// @Description Loads a script from storage, resolves interpreter command, and submits it to the agent
// @Tags scripts
// @Accept json
// @Produce json
// @Param id path int true "Script ID"
// @Param body body RunStoredScriptIn true "Agent token and optional stdin"
// @Success 201 {object} RunScriptOut
// @Param body body RunStoredScriptIn true "Agent ID and optional stdin"
// @Success 201 {object} AddJobOut
// @Failure 400 {object} map[string]string
// @Failure 404 {object} map[string]string
// @Failure 500 {object} map[string]string
@@ -248,19 +249,12 @@ func (sh *ScriptHandlersGroup) RunScriptByID(c *gin.Context) {
return
}
job, err := agent.WaitJob(jid)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": fmt.Sprintf("job execution failed: %v", err)})
return
}
waitURL := fmt.Sprintf("%s/api/v1/jobs/%d/wait", sh.whereami, jid)
c.JSON(http.StatusCreated, RunScriptOut{
ID: job.ID,
Command: job.Command,
Stdin: job.Stdin,
Stdout: job.Stdout,
Stderr: job.Stderr,
Status: job.Status,
c.JSON(http.StatusCreated, AddJobOut{
ID: jid,
Command: command,
WaitURL: waitURL,
})
}