diff --git a/backend/cmd/main.go b/backend/cmd/main.go index bb3df35..8eb1ebf 100644 --- a/backend/cmd/main.go +++ b/backend/cmd/main.go @@ -96,7 +96,8 @@ func main() { log.Printf("Warning: failed to initialize script interpreters table: %v", err) } scriptSvc := service.NewScriptServiceWithInterpreters(h.Repo, scriptRepo) - scriptHandlers := handlers.NewScriptHandlers(scriptSvc, cmdTracker) + scriptHandlers := handlers.NewScriptHandlers(scriptSvc, cmdTracker, + os.Getenv("WHEREAMI")) jobsHandlers := handlers.NewJobsHandlers(cmdTracker, scriptSvc, os.Getenv("WHEREAMI"), /* our address for redirects */ jobRepo, @@ -104,7 +105,8 @@ func main() { // Initialize script management service and handlers scriptManageSvc := service.NewScriptService(h.Repo) - scriptManageHandlers := handlers.NewScriptHandlersGroup(scriptManageSvc, cmdr) + scriptManageHandlers := handlers.NewScriptHandlersGroup(scriptManageSvc, cmdr, + os.Getenv("WHEREAMI")) agents := handlers.NewAgentsGroup(h, coll) auth := handlers.AuthGroup{Handlers: h} diff --git a/backend/docs/docs.go b/backend/docs/docs.go index bad5898..27f71e0 100644 --- a/backend/docs/docs.go +++ b/backend/docs/docs.go @@ -1651,7 +1651,7 @@ const docTemplate = `{ "Bearer": [] } ], - "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", "consumes": [ "application/json" ], @@ -1671,7 +1671,7 @@ const docTemplate = `{ "required": true }, { - "description": "Agent token and optional stdin", + "description": "Agent ID and optional stdin", "name": "body", "in": "body", "required": true, @@ -1684,7 +1684,7 @@ const docTemplate = `{ "201": { "description": "Created", "schema": { - "$ref": "#/definitions/internal_handlers.RunScriptOut" + "$ref": "#/definitions/internal_handlers.AddJobOut" } }, "400": { @@ -2118,7 +2118,7 @@ const docTemplate = `{ "201": { "description": "Created", "schema": { - "$ref": "#/definitions/internal_handlers.RunScriptOut" + "$ref": "#/definitions/internal_handlers.AddJobOut" } } } @@ -2904,32 +2904,6 @@ const docTemplate = `{ } } }, - "internal_handlers.RunScriptOut": { - "type": "object", - "properties": { - "command": { - "type": "array", - "items": { - "type": "string" - } - }, - "id": { - "type": "integer" - }, - "status": { - "type": "integer" - }, - "stderr": { - "type": "string" - }, - "stdin": { - "type": "string" - }, - "stdout": { - "type": "string" - } - } - }, "internal_handlers.RunStoredScriptIn": { "type": "object", "required": [ diff --git a/backend/docs/swagger.json b/backend/docs/swagger.json index c7416e0..6dc0564 100644 --- a/backend/docs/swagger.json +++ b/backend/docs/swagger.json @@ -1640,7 +1640,7 @@ "Bearer": [] } ], - "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", "consumes": [ "application/json" ], @@ -1660,7 +1660,7 @@ "required": true }, { - "description": "Agent token and optional stdin", + "description": "Agent ID and optional stdin", "name": "body", "in": "body", "required": true, @@ -1673,7 +1673,7 @@ "201": { "description": "Created", "schema": { - "$ref": "#/definitions/internal_handlers.RunScriptOut" + "$ref": "#/definitions/internal_handlers.AddJobOut" } }, "400": { @@ -2107,7 +2107,7 @@ "201": { "description": "Created", "schema": { - "$ref": "#/definitions/internal_handlers.RunScriptOut" + "$ref": "#/definitions/internal_handlers.AddJobOut" } } } @@ -2893,32 +2893,6 @@ } } }, - "internal_handlers.RunScriptOut": { - "type": "object", - "properties": { - "command": { - "type": "array", - "items": { - "type": "string" - } - }, - "id": { - "type": "integer" - }, - "status": { - "type": "integer" - }, - "stderr": { - "type": "string" - }, - "stdin": { - "type": "string" - }, - "stdout": { - "type": "string" - } - } - }, "internal_handlers.RunStoredScriptIn": { "type": "object", "required": [ diff --git a/backend/docs/swagger.yaml b/backend/docs/swagger.yaml index 297cda4..4872b05 100644 --- a/backend/docs/swagger.yaml +++ b/backend/docs/swagger.yaml @@ -504,23 +504,6 @@ definitions: - interpreter_id - script_text type: object - internal_handlers.RunScriptOut: - properties: - command: - items: - type: string - type: array - id: - type: integer - status: - type: integer - stderr: - type: string - stdin: - type: string - stdout: - type: string - type: object internal_handlers.RunStoredScriptIn: properties: stdin: @@ -1589,14 +1572,14 @@ paths: consumes: - application/json description: Loads a script from storage, resolves interpreter command, and - executes on the specified agent + submits it to the agent parameters: - description: Script ID in: path name: id required: true type: integer - - description: Agent token and optional stdin + - description: Agent ID and optional stdin in: body name: body required: true @@ -1608,7 +1591,7 @@ paths: "201": description: Created schema: - $ref: '#/definitions/internal_handlers.RunScriptOut' + $ref: '#/definitions/internal_handlers.AddJobOut' "400": description: Bad Request schema: @@ -1882,7 +1865,7 @@ paths: "201": description: Created schema: - $ref: '#/definitions/internal_handlers.RunScriptOut' + $ref: '#/definitions/internal_handlers.AddJobOut' security: - Bearer: [] summary: Run a script on an agent diff --git a/backend/internal/handlers/jobs.go b/backend/internal/handlers/jobs.go index 8a30c33..bc1f883 100644 --- a/backend/internal/handlers/jobs.go +++ b/backend/internal/handlers/jobs.go @@ -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). diff --git a/backend/internal/handlers/scripts.go b/backend/internal/handlers/scripts.go index 30299e5..3d8caea 100644 --- a/backend/internal/handlers/scripts.go +++ b/backend/internal/handlers/scripts.go @@ -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. diff --git a/backend/internal/handlers/scripts_manage.go b/backend/internal/handlers/scripts_manage.go index 705a0dd..43e598e 100644 --- a/backend/internal/handlers/scripts_manage.go +++ b/backend/internal/handlers/scripts_manage.go @@ -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, }) }