feat!(backend): unify script run and ad-hoc job run
This commit is contained in:
+4
-2
@@ -96,7 +96,8 @@ func main() {
|
|||||||
log.Printf("Warning: failed to initialize script interpreters table: %v", err)
|
log.Printf("Warning: failed to initialize script interpreters table: %v", err)
|
||||||
}
|
}
|
||||||
scriptSvc := service.NewScriptServiceWithInterpreters(h.Repo, scriptRepo)
|
scriptSvc := service.NewScriptServiceWithInterpreters(h.Repo, scriptRepo)
|
||||||
scriptHandlers := handlers.NewScriptHandlers(scriptSvc, cmdTracker)
|
scriptHandlers := handlers.NewScriptHandlers(scriptSvc, cmdTracker,
|
||||||
|
os.Getenv("WHEREAMI"))
|
||||||
jobsHandlers := handlers.NewJobsHandlers(cmdTracker, scriptSvc,
|
jobsHandlers := handlers.NewJobsHandlers(cmdTracker, scriptSvc,
|
||||||
os.Getenv("WHEREAMI"), /* our address for redirects */
|
os.Getenv("WHEREAMI"), /* our address for redirects */
|
||||||
jobRepo,
|
jobRepo,
|
||||||
@@ -104,7 +105,8 @@ func main() {
|
|||||||
|
|
||||||
// Initialize script management service and handlers
|
// Initialize script management service and handlers
|
||||||
scriptManageSvc := service.NewScriptService(h.Repo)
|
scriptManageSvc := service.NewScriptService(h.Repo)
|
||||||
scriptManageHandlers := handlers.NewScriptHandlersGroup(scriptManageSvc, cmdr)
|
scriptManageHandlers := handlers.NewScriptHandlersGroup(scriptManageSvc, cmdr,
|
||||||
|
os.Getenv("WHEREAMI"))
|
||||||
|
|
||||||
agents := handlers.NewAgentsGroup(h, coll)
|
agents := handlers.NewAgentsGroup(h, coll)
|
||||||
auth := handlers.AuthGroup{Handlers: h}
|
auth := handlers.AuthGroup{Handlers: h}
|
||||||
|
|||||||
+4
-30
@@ -1651,7 +1651,7 @@ const docTemplate = `{
|
|||||||
"Bearer": []
|
"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": [
|
"consumes": [
|
||||||
"application/json"
|
"application/json"
|
||||||
],
|
],
|
||||||
@@ -1671,7 +1671,7 @@ const docTemplate = `{
|
|||||||
"required": true
|
"required": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"description": "Agent token and optional stdin",
|
"description": "Agent ID and optional stdin",
|
||||||
"name": "body",
|
"name": "body",
|
||||||
"in": "body",
|
"in": "body",
|
||||||
"required": true,
|
"required": true,
|
||||||
@@ -1684,7 +1684,7 @@ const docTemplate = `{
|
|||||||
"201": {
|
"201": {
|
||||||
"description": "Created",
|
"description": "Created",
|
||||||
"schema": {
|
"schema": {
|
||||||
"$ref": "#/definitions/internal_handlers.RunScriptOut"
|
"$ref": "#/definitions/internal_handlers.AddJobOut"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"400": {
|
"400": {
|
||||||
@@ -2118,7 +2118,7 @@ const docTemplate = `{
|
|||||||
"201": {
|
"201": {
|
||||||
"description": "Created",
|
"description": "Created",
|
||||||
"schema": {
|
"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": {
|
"internal_handlers.RunStoredScriptIn": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"required": [
|
"required": [
|
||||||
|
|||||||
@@ -1640,7 +1640,7 @@
|
|||||||
"Bearer": []
|
"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": [
|
"consumes": [
|
||||||
"application/json"
|
"application/json"
|
||||||
],
|
],
|
||||||
@@ -1660,7 +1660,7 @@
|
|||||||
"required": true
|
"required": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"description": "Agent token and optional stdin",
|
"description": "Agent ID and optional stdin",
|
||||||
"name": "body",
|
"name": "body",
|
||||||
"in": "body",
|
"in": "body",
|
||||||
"required": true,
|
"required": true,
|
||||||
@@ -1673,7 +1673,7 @@
|
|||||||
"201": {
|
"201": {
|
||||||
"description": "Created",
|
"description": "Created",
|
||||||
"schema": {
|
"schema": {
|
||||||
"$ref": "#/definitions/internal_handlers.RunScriptOut"
|
"$ref": "#/definitions/internal_handlers.AddJobOut"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"400": {
|
"400": {
|
||||||
@@ -2107,7 +2107,7 @@
|
|||||||
"201": {
|
"201": {
|
||||||
"description": "Created",
|
"description": "Created",
|
||||||
"schema": {
|
"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": {
|
"internal_handlers.RunStoredScriptIn": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"required": [
|
"required": [
|
||||||
|
|||||||
@@ -504,23 +504,6 @@ definitions:
|
|||||||
- interpreter_id
|
- interpreter_id
|
||||||
- script_text
|
- script_text
|
||||||
type: object
|
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:
|
internal_handlers.RunStoredScriptIn:
|
||||||
properties:
|
properties:
|
||||||
stdin:
|
stdin:
|
||||||
@@ -1589,14 +1572,14 @@ paths:
|
|||||||
consumes:
|
consumes:
|
||||||
- application/json
|
- application/json
|
||||||
description: Loads a script from storage, resolves interpreter command, and
|
description: Loads a script from storage, resolves interpreter command, and
|
||||||
executes on the specified agent
|
submits it to the agent
|
||||||
parameters:
|
parameters:
|
||||||
- description: Script ID
|
- description: Script ID
|
||||||
in: path
|
in: path
|
||||||
name: id
|
name: id
|
||||||
required: true
|
required: true
|
||||||
type: integer
|
type: integer
|
||||||
- description: Agent token and optional stdin
|
- description: Agent ID and optional stdin
|
||||||
in: body
|
in: body
|
||||||
name: body
|
name: body
|
||||||
required: true
|
required: true
|
||||||
@@ -1608,7 +1591,7 @@ paths:
|
|||||||
"201":
|
"201":
|
||||||
description: Created
|
description: Created
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/definitions/internal_handlers.RunScriptOut'
|
$ref: '#/definitions/internal_handlers.AddJobOut'
|
||||||
"400":
|
"400":
|
||||||
description: Bad Request
|
description: Bad Request
|
||||||
schema:
|
schema:
|
||||||
@@ -1882,7 +1865,7 @@ paths:
|
|||||||
"201":
|
"201":
|
||||||
description: Created
|
description: Created
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/definitions/internal_handlers.RunScriptOut'
|
$ref: '#/definitions/internal_handlers.AddJobOut'
|
||||||
security:
|
security:
|
||||||
- Bearer: []
|
- Bearer: []
|
||||||
summary: Run a script on an agent
|
summary: Run a script on an agent
|
||||||
|
|||||||
@@ -72,35 +72,49 @@ func (h *JobsHandlers) AddJob(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
agent, ok := h.tracker.GetAgent(in.AgentID)
|
result, err := h.runCommand(c, in.AgentID, in.InterpreterID, in.Command, in.Stdin)
|
||||||
if !ok {
|
|
||||||
c.Status(http.StatusNotFound)
|
|
||||||
c.Error(fmt.Errorf("agent not found"))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
command, err := resolveCommand(c, h.svc, in.InterpreterID, in.Command)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.Error(err)
|
c.Error(err)
|
||||||
return
|
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{
|
jid, err := agent.AddJob(models.JobForInsert{
|
||||||
Command: command,
|
Command: cmd,
|
||||||
Stdin: in.Stdin,
|
Stdin: stdin,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.Error(err)
|
return nil, err
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
waitURL := fmt.Sprintf("%s/api/v1/jobs/%d/wait", h.whereami, jid)
|
waitURL := fmt.Sprintf("%s/api/v1/jobs/%d/wait", h.whereami, jid)
|
||||||
|
|
||||||
c.JSON(http.StatusCreated, AddJobOut{
|
return &AddJobOut{
|
||||||
ID: jid,
|
ID: jid,
|
||||||
Command: command,
|
Command: cmd,
|
||||||
WaitURL: waitURL,
|
WaitURL: waitURL,
|
||||||
})
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// WaitJob waits for a submitted job to complete (long-poll).
|
// WaitJob waits for a submitted job to complete (long-poll).
|
||||||
|
|||||||
@@ -13,12 +13,13 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type ScriptHandlers struct {
|
type ScriptHandlers struct {
|
||||||
svc *service.ScriptService
|
svc *service.ScriptService
|
||||||
tracker *commander.ConnTracker
|
tracker *commander.ConnTracker
|
||||||
|
whereami string
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewScriptHandlers(svc *service.ScriptService, tracker *commander.ConnTracker) ScriptHandlers {
|
func NewScriptHandlers(svc *service.ScriptService, tracker *commander.ConnTracker, whereami string) ScriptHandlers {
|
||||||
return ScriptHandlers{svc: svc, tracker: tracker}
|
return ScriptHandlers{svc: svc, tracker: tracker, whereami: whereami}
|
||||||
}
|
}
|
||||||
|
|
||||||
type RunScriptIn struct {
|
type RunScriptIn struct {
|
||||||
@@ -28,73 +29,52 @@ type RunScriptIn struct {
|
|||||||
Stdin *string `json:"stdin"`
|
Stdin *string `json:"stdin"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type RunScriptOut struct {
|
// RunScript submits a script as a job and returns a wait_url for the result.
|
||||||
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.
|
|
||||||
// @Summary Run a script on an agent
|
// @Summary Run a script on an agent
|
||||||
// @Description Resolves interpreter argv[] and sends the full command to the agent
|
// @Description Resolves interpreter argv[] and sends the full command to the agent
|
||||||
// @Tags scripts
|
// @Tags scripts
|
||||||
// @Accept json
|
// @Accept json
|
||||||
// @Produce json
|
// @Produce json
|
||||||
// @Param body body RunScriptIn true "Script request"
|
// @Param body body RunScriptIn true "Script request"
|
||||||
// @Success 201 {object} RunScriptOut
|
// @Success 201 {object} AddJobOut
|
||||||
// @Security Bearer
|
// @Security Bearer
|
||||||
// @Router /scripts/run [post]
|
// @Router /scripts/run [post]
|
||||||
func (h *ScriptHandlers) RunScript(c *gin.Context) {
|
func (h *ScriptHandlers) RunScript(c *gin.Context) {
|
||||||
err := func() error {
|
var in RunScriptIn
|
||||||
var in RunScriptIn
|
if err := c.Bind(&in); err != nil {
|
||||||
if err := c.Bind(&in); err != nil {
|
c.Error(err)
|
||||||
return err
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
command, err := h.svc.ResolveCommand(
|
agent, ok := h.tracker.GetAgent(in.AgentID)
|
||||||
c.Request.Context(),
|
if !ok {
|
||||||
in.InterpreterID,
|
c.Status(http.StatusNotFound)
|
||||||
in.ScriptText,
|
c.Error(fmt.Errorf("agent not found"))
|
||||||
)
|
return
|
||||||
if err != nil {
|
}
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
agent, ok := h.tracker.GetAgent(in.AgentID)
|
command, err := h.svc.ResolveCommand(c.Request.Context(), in.InterpreterID, in.ScriptText)
|
||||||
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
|
|
||||||
}()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.Error(err)
|
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.
|
// ListInterpreters returns all registered script interpreters.
|
||||||
|
|||||||
@@ -16,13 +16,14 @@ import (
|
|||||||
|
|
||||||
// ScriptHandlersGroup handles script management routes.
|
// ScriptHandlersGroup handles script management routes.
|
||||||
type ScriptHandlersGroup struct {
|
type ScriptHandlersGroup struct {
|
||||||
svc *service.ScriptService
|
svc *service.ScriptService
|
||||||
cmder *commander.Commander
|
cmder *commander.Commander
|
||||||
|
whereami string
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewScriptHandlersGroup creates a new ScriptHandlersGroup.
|
// NewScriptHandlersGroup creates a new ScriptHandlersGroup.
|
||||||
func NewScriptHandlersGroup(svc *service.ScriptService, cmder *commander.Commander) *ScriptHandlersGroup {
|
func NewScriptHandlersGroup(svc *service.ScriptService, cmder *commander.Commander, whereami string) *ScriptHandlersGroup {
|
||||||
return &ScriptHandlersGroup{svc: svc, cmder: cmder}
|
return &ScriptHandlersGroup{svc: svc, cmder: cmder, whereami: whereami}
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetTree returns the script directory tree.
|
// 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.
|
// RunScriptByID executes a stored script on a target agent.
|
||||||
// @Summary Run script by ID
|
// @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
|
// @Tags scripts
|
||||||
// @Accept json
|
// @Accept json
|
||||||
// @Produce json
|
// @Produce json
|
||||||
// @Param id path int true "Script ID"
|
// @Param id path int true "Script ID"
|
||||||
// @Param body body RunStoredScriptIn true "Agent token and optional stdin"
|
// @Param body body RunStoredScriptIn true "Agent ID and optional stdin"
|
||||||
// @Success 201 {object} RunScriptOut
|
// @Success 201 {object} AddJobOut
|
||||||
// @Failure 400 {object} map[string]string
|
// @Failure 400 {object} map[string]string
|
||||||
// @Failure 404 {object} map[string]string
|
// @Failure 404 {object} map[string]string
|
||||||
// @Failure 500 {object} map[string]string
|
// @Failure 500 {object} map[string]string
|
||||||
@@ -248,19 +249,12 @@ func (sh *ScriptHandlersGroup) RunScriptByID(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
job, err := agent.WaitJob(jid)
|
waitURL := fmt.Sprintf("%s/api/v1/jobs/%d/wait", sh.whereami, jid)
|
||||||
if err != nil {
|
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{"error": fmt.Sprintf("job execution failed: %v", err)})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
c.JSON(http.StatusCreated, RunScriptOut{
|
c.JSON(http.StatusCreated, AddJobOut{
|
||||||
ID: job.ID,
|
ID: jid,
|
||||||
Command: job.Command,
|
Command: command,
|
||||||
Stdin: job.Stdin,
|
WaitURL: waitURL,
|
||||||
Stdout: job.Stdout,
|
|
||||||
Stderr: job.Stderr,
|
|
||||||
Status: job.Status,
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user