This commit is contained in:
@@ -260,6 +260,13 @@ func main() {
|
|||||||
scriptsGroup.PUT("/:id", scriptManageHandlers.UpdateScript)
|
scriptsGroup.PUT("/:id", scriptManageHandlers.UpdateScript)
|
||||||
scriptsGroup.DELETE("/:id", scriptManageHandlers.DeleteScript)
|
scriptsGroup.DELETE("/:id", scriptManageHandlers.DeleteScript)
|
||||||
scriptsGroup.POST("/:id/run", scriptManageHandlers.RunScriptByID)
|
scriptsGroup.POST("/:id/run", scriptManageHandlers.RunScriptByID)
|
||||||
|
|
||||||
|
// Folder management
|
||||||
|
scriptsGroup.POST("/folder", scriptManageHandlers.CreateFolder)
|
||||||
|
scriptsGroup.DELETE("/folder", scriptManageHandlers.DeleteFolder)
|
||||||
|
|
||||||
|
// Get script by path
|
||||||
|
scriptsGroup.GET("/by-path", scriptManageHandlers.GetScriptByPath)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+378
-17
@@ -965,12 +965,7 @@ const docTemplate = `{
|
|||||||
},
|
},
|
||||||
"/jobs": {
|
"/jobs": {
|
||||||
"post": {
|
"post": {
|
||||||
"security": [
|
"description": "Sends a command to the specified agent and returns a URL to wait for the result",
|
||||||
{
|
|
||||||
"Bearer": []
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"description": "Sends a command to the specified agent, waits for execution, and returns the result",
|
|
||||||
"consumes": [
|
"consumes": [
|
||||||
"application/json"
|
"application/json"
|
||||||
],
|
],
|
||||||
@@ -980,7 +975,7 @@ const docTemplate = `{
|
|||||||
"tags": [
|
"tags": [
|
||||||
"jobs"
|
"jobs"
|
||||||
],
|
],
|
||||||
"summary": "Create and run a job on an agent",
|
"summary": "Submit a job to an agent",
|
||||||
"parameters": [
|
"parameters": [
|
||||||
{
|
{
|
||||||
"description": "Job request",
|
"description": "Job request",
|
||||||
@@ -1002,6 +997,148 @@ const docTemplate = `{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/jobs/check_cmd": {
|
||||||
|
"post": {
|
||||||
|
"description": "Validates that a command binary exists on the system",
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"jobs"
|
||||||
|
],
|
||||||
|
"summary": "Check command path",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"description": "Command to check",
|
||||||
|
"name": "body",
|
||||||
|
"in": "body",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/internal_handlers.CheckCmdIn"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/internal_handlers.CheckCmdOut"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"404": {
|
||||||
|
"description": "Not Found",
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/jobs/metrics": {
|
||||||
|
"get": {
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"Bearer": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Returns total, successful, failed, and pending job counts over the given period",
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"jobs"
|
||||||
|
],
|
||||||
|
"summary": "Get job metrics",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"default": "24h",
|
||||||
|
"description": "Time period (e.g. 1h, 24h, 7d)",
|
||||||
|
"name": "period",
|
||||||
|
"in": "query"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/internal_handlers.JobMetricsOut"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "Bad Request",
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/jobs/{id}/wait": {
|
||||||
|
"post": {
|
||||||
|
"description": "Long-polls for a job result. Returns immediately if the job is already finished.",
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"jobs"
|
||||||
|
],
|
||||||
|
"summary": "Wait for job result",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "integer",
|
||||||
|
"description": "Job ID",
|
||||||
|
"name": "id",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Agent reference",
|
||||||
|
"name": "body",
|
||||||
|
"in": "body",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/internal_handlers.WaitJobIn"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/internal_handlers.JobResult"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "Bad Request",
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"404": {
|
||||||
|
"description": "Not Found",
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"/logs": {
|
"/logs": {
|
||||||
"get": {
|
"get": {
|
||||||
"security": [
|
"security": [
|
||||||
@@ -1580,6 +1717,138 @@ const docTemplate = `{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/scripts/by-path": {
|
||||||
|
"get": {
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"Bearer": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Returns a script by its full path (e.g. 'deploy/nginx/restart.sh')",
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"scripts"
|
||||||
|
],
|
||||||
|
"summary": "Get script by path",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "Script path",
|
||||||
|
"name": "path",
|
||||||
|
"in": "query",
|
||||||
|
"required": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/gitea_d3m0k1d_ru_d3m0k1d_HellreigN_backend_internal_repository.Script"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "Bad Request",
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"404": {
|
||||||
|
"description": "Not Found",
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/scripts/folder": {
|
||||||
|
"post": {
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"Bearer": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Creates a virtual folder by creating a placeholder script with the folder path",
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"scripts"
|
||||||
|
],
|
||||||
|
"summary": "Create folder",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"description": "Folder path",
|
||||||
|
"name": "body",
|
||||||
|
"in": "body",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/internal_handlers.CreateFolderRequest"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"201": {
|
||||||
|
"description": "Folder created",
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"delete": {
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"Bearer": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Deletes all scripts that start with the given folder path",
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"scripts"
|
||||||
|
],
|
||||||
|
"summary": "Delete folder",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"description": "Folder path",
|
||||||
|
"name": "body",
|
||||||
|
"in": "body",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/internal_handlers.DeleteFolderRequest"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "Folder deleted with count of deleted scripts",
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"/scripts/interpreters": {
|
"/scripts/interpreters": {
|
||||||
"get": {
|
"get": {
|
||||||
"security": [
|
"security": [
|
||||||
@@ -2337,16 +2606,7 @@ const docTemplate = `{
|
|||||||
"id": {
|
"id": {
|
||||||
"type": "integer"
|
"type": "integer"
|
||||||
},
|
},
|
||||||
"status": {
|
"wait_url": {
|
||||||
"type": "integer"
|
|
||||||
},
|
|
||||||
"stderr": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"stdin": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"stdout": {
|
|
||||||
"type": "string"
|
"type": "string"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2382,6 +2642,50 @@ const docTemplate = `{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"internal_handlers.CheckCmdIn": {
|
||||||
|
"type": "object",
|
||||||
|
"required": [
|
||||||
|
"command"
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"command": {
|
||||||
|
"type": "string",
|
||||||
|
"example": "bash"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"internal_handlers.CheckCmdOut": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"exists": {
|
||||||
|
"type": "boolean"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"internal_handlers.CreateFolderRequest": {
|
||||||
|
"type": "object",
|
||||||
|
"required": [
|
||||||
|
"path"
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"path": {
|
||||||
|
"type": "string",
|
||||||
|
"example": "deploy/nginx"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"internal_handlers.DeleteFolderRequest": {
|
||||||
|
"type": "object",
|
||||||
|
"required": [
|
||||||
|
"path"
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"path": {
|
||||||
|
"type": "string",
|
||||||
|
"example": "deploy/nginx"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"internal_handlers.InsertLogRequest": {
|
"internal_handlers.InsertLogRequest": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"required": [
|
"required": [
|
||||||
@@ -2422,6 +2726,52 @@ const docTemplate = `{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"internal_handlers.JobMetricsOut": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"failed": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"pending": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"period": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"success": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"total": {
|
||||||
|
"type": "integer"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"internal_handlers.JobResult": {
|
||||||
|
"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.RegisterRequest": {
|
"internal_handlers.RegisterRequest": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"required": [
|
"required": [
|
||||||
@@ -2509,6 +2859,17 @@ const docTemplate = `{
|
|||||||
"type": "string"
|
"type": "string"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"internal_handlers.WaitJobIn": {
|
||||||
|
"type": "object",
|
||||||
|
"required": [
|
||||||
|
"agent_id"
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"agent_id": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"securityDefinitions": {
|
"securityDefinitions": {
|
||||||
|
|||||||
+378
-17
@@ -954,12 +954,7 @@
|
|||||||
},
|
},
|
||||||
"/jobs": {
|
"/jobs": {
|
||||||
"post": {
|
"post": {
|
||||||
"security": [
|
"description": "Sends a command to the specified agent and returns a URL to wait for the result",
|
||||||
{
|
|
||||||
"Bearer": []
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"description": "Sends a command to the specified agent, waits for execution, and returns the result",
|
|
||||||
"consumes": [
|
"consumes": [
|
||||||
"application/json"
|
"application/json"
|
||||||
],
|
],
|
||||||
@@ -969,7 +964,7 @@
|
|||||||
"tags": [
|
"tags": [
|
||||||
"jobs"
|
"jobs"
|
||||||
],
|
],
|
||||||
"summary": "Create and run a job on an agent",
|
"summary": "Submit a job to an agent",
|
||||||
"parameters": [
|
"parameters": [
|
||||||
{
|
{
|
||||||
"description": "Job request",
|
"description": "Job request",
|
||||||
@@ -991,6 +986,148 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/jobs/check_cmd": {
|
||||||
|
"post": {
|
||||||
|
"description": "Validates that a command binary exists on the system",
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"jobs"
|
||||||
|
],
|
||||||
|
"summary": "Check command path",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"description": "Command to check",
|
||||||
|
"name": "body",
|
||||||
|
"in": "body",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/internal_handlers.CheckCmdIn"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/internal_handlers.CheckCmdOut"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"404": {
|
||||||
|
"description": "Not Found",
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/jobs/metrics": {
|
||||||
|
"get": {
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"Bearer": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Returns total, successful, failed, and pending job counts over the given period",
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"jobs"
|
||||||
|
],
|
||||||
|
"summary": "Get job metrics",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"default": "24h",
|
||||||
|
"description": "Time period (e.g. 1h, 24h, 7d)",
|
||||||
|
"name": "period",
|
||||||
|
"in": "query"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/internal_handlers.JobMetricsOut"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "Bad Request",
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/jobs/{id}/wait": {
|
||||||
|
"post": {
|
||||||
|
"description": "Long-polls for a job result. Returns immediately if the job is already finished.",
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"jobs"
|
||||||
|
],
|
||||||
|
"summary": "Wait for job result",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "integer",
|
||||||
|
"description": "Job ID",
|
||||||
|
"name": "id",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Agent reference",
|
||||||
|
"name": "body",
|
||||||
|
"in": "body",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/internal_handlers.WaitJobIn"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/internal_handlers.JobResult"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "Bad Request",
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"404": {
|
||||||
|
"description": "Not Found",
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"/logs": {
|
"/logs": {
|
||||||
"get": {
|
"get": {
|
||||||
"security": [
|
"security": [
|
||||||
@@ -1569,6 +1706,138 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/scripts/by-path": {
|
||||||
|
"get": {
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"Bearer": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Returns a script by its full path (e.g. 'deploy/nginx/restart.sh')",
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"scripts"
|
||||||
|
],
|
||||||
|
"summary": "Get script by path",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "Script path",
|
||||||
|
"name": "path",
|
||||||
|
"in": "query",
|
||||||
|
"required": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/gitea_d3m0k1d_ru_d3m0k1d_HellreigN_backend_internal_repository.Script"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "Bad Request",
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"404": {
|
||||||
|
"description": "Not Found",
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/scripts/folder": {
|
||||||
|
"post": {
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"Bearer": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Creates a virtual folder by creating a placeholder script with the folder path",
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"scripts"
|
||||||
|
],
|
||||||
|
"summary": "Create folder",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"description": "Folder path",
|
||||||
|
"name": "body",
|
||||||
|
"in": "body",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/internal_handlers.CreateFolderRequest"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"201": {
|
||||||
|
"description": "Folder created",
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"delete": {
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"Bearer": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Deletes all scripts that start with the given folder path",
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"scripts"
|
||||||
|
],
|
||||||
|
"summary": "Delete folder",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"description": "Folder path",
|
||||||
|
"name": "body",
|
||||||
|
"in": "body",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/internal_handlers.DeleteFolderRequest"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "Folder deleted with count of deleted scripts",
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"/scripts/interpreters": {
|
"/scripts/interpreters": {
|
||||||
"get": {
|
"get": {
|
||||||
"security": [
|
"security": [
|
||||||
@@ -2326,16 +2595,7 @@
|
|||||||
"id": {
|
"id": {
|
||||||
"type": "integer"
|
"type": "integer"
|
||||||
},
|
},
|
||||||
"status": {
|
"wait_url": {
|
||||||
"type": "integer"
|
|
||||||
},
|
|
||||||
"stderr": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"stdin": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"stdout": {
|
|
||||||
"type": "string"
|
"type": "string"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2371,6 +2631,50 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"internal_handlers.CheckCmdIn": {
|
||||||
|
"type": "object",
|
||||||
|
"required": [
|
||||||
|
"command"
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"command": {
|
||||||
|
"type": "string",
|
||||||
|
"example": "bash"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"internal_handlers.CheckCmdOut": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"exists": {
|
||||||
|
"type": "boolean"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"internal_handlers.CreateFolderRequest": {
|
||||||
|
"type": "object",
|
||||||
|
"required": [
|
||||||
|
"path"
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"path": {
|
||||||
|
"type": "string",
|
||||||
|
"example": "deploy/nginx"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"internal_handlers.DeleteFolderRequest": {
|
||||||
|
"type": "object",
|
||||||
|
"required": [
|
||||||
|
"path"
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"path": {
|
||||||
|
"type": "string",
|
||||||
|
"example": "deploy/nginx"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"internal_handlers.InsertLogRequest": {
|
"internal_handlers.InsertLogRequest": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"required": [
|
"required": [
|
||||||
@@ -2411,6 +2715,52 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"internal_handlers.JobMetricsOut": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"failed": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"pending": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"period": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"success": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"total": {
|
||||||
|
"type": "integer"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"internal_handlers.JobResult": {
|
||||||
|
"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.RegisterRequest": {
|
"internal_handlers.RegisterRequest": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"required": [
|
"required": [
|
||||||
@@ -2498,6 +2848,17 @@
|
|||||||
"type": "string"
|
"type": "string"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"internal_handlers.WaitJobIn": {
|
||||||
|
"type": "object",
|
||||||
|
"required": [
|
||||||
|
"agent_id"
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"agent_id": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"securityDefinitions": {
|
"securityDefinitions": {
|
||||||
|
|||||||
+246
-10
@@ -348,13 +348,7 @@ definitions:
|
|||||||
type: array
|
type: array
|
||||||
id:
|
id:
|
||||||
type: integer
|
type: integer
|
||||||
status:
|
wait_url:
|
||||||
type: integer
|
|
||||||
stderr:
|
|
||||||
type: string
|
|
||||||
stdin:
|
|
||||||
type: string
|
|
||||||
stdout:
|
|
||||||
type: string
|
type: string
|
||||||
type: object
|
type: object
|
||||||
internal_handlers.AgentInfo:
|
internal_handlers.AgentInfo:
|
||||||
@@ -380,6 +374,35 @@ definitions:
|
|||||||
example: agent-001
|
example: agent-001
|
||||||
type: string
|
type: string
|
||||||
type: object
|
type: object
|
||||||
|
internal_handlers.CheckCmdIn:
|
||||||
|
properties:
|
||||||
|
command:
|
||||||
|
example: bash
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- command
|
||||||
|
type: object
|
||||||
|
internal_handlers.CheckCmdOut:
|
||||||
|
properties:
|
||||||
|
exists:
|
||||||
|
type: boolean
|
||||||
|
type: object
|
||||||
|
internal_handlers.CreateFolderRequest:
|
||||||
|
properties:
|
||||||
|
path:
|
||||||
|
example: deploy/nginx
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- path
|
||||||
|
type: object
|
||||||
|
internal_handlers.DeleteFolderRequest:
|
||||||
|
properties:
|
||||||
|
path:
|
||||||
|
example: deploy/nginx
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- path
|
||||||
|
type: object
|
||||||
internal_handlers.InsertLogRequest:
|
internal_handlers.InsertLogRequest:
|
||||||
properties:
|
properties:
|
||||||
agent:
|
agent:
|
||||||
@@ -407,6 +430,36 @@ definitions:
|
|||||||
required:
|
required:
|
||||||
- logs
|
- logs
|
||||||
type: object
|
type: object
|
||||||
|
internal_handlers.JobMetricsOut:
|
||||||
|
properties:
|
||||||
|
failed:
|
||||||
|
type: integer
|
||||||
|
pending:
|
||||||
|
type: integer
|
||||||
|
period:
|
||||||
|
type: string
|
||||||
|
success:
|
||||||
|
type: integer
|
||||||
|
total:
|
||||||
|
type: integer
|
||||||
|
type: object
|
||||||
|
internal_handlers.JobResult:
|
||||||
|
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.RegisterRequest:
|
internal_handlers.RegisterRequest:
|
||||||
properties:
|
properties:
|
||||||
csr:
|
csr:
|
||||||
@@ -465,6 +518,13 @@ definitions:
|
|||||||
required:
|
required:
|
||||||
- token
|
- token
|
||||||
type: object
|
type: object
|
||||||
|
internal_handlers.WaitJobIn:
|
||||||
|
properties:
|
||||||
|
agent_id:
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- agent_id
|
||||||
|
type: object
|
||||||
info:
|
info:
|
||||||
contact: {}
|
contact: {}
|
||||||
paths:
|
paths:
|
||||||
@@ -1079,8 +1139,8 @@ paths:
|
|||||||
post:
|
post:
|
||||||
consumes:
|
consumes:
|
||||||
- application/json
|
- application/json
|
||||||
description: Sends a command to the specified agent, waits for execution, and
|
description: Sends a command to the specified agent and returns a URL to wait
|
||||||
returns the result
|
for the result
|
||||||
parameters:
|
parameters:
|
||||||
- description: Job request
|
- description: Job request
|
||||||
in: body
|
in: body
|
||||||
@@ -1095,9 +1155,101 @@ paths:
|
|||||||
description: Created
|
description: Created
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/definitions/internal_handlers.AddJobOut'
|
$ref: '#/definitions/internal_handlers.AddJobOut'
|
||||||
|
summary: Submit a job to an agent
|
||||||
|
tags:
|
||||||
|
- jobs
|
||||||
|
/jobs/{id}/wait:
|
||||||
|
post:
|
||||||
|
consumes:
|
||||||
|
- application/json
|
||||||
|
description: Long-polls for a job result. Returns immediately if the job is
|
||||||
|
already finished.
|
||||||
|
parameters:
|
||||||
|
- description: Job ID
|
||||||
|
in: path
|
||||||
|
name: id
|
||||||
|
required: true
|
||||||
|
type: integer
|
||||||
|
- description: Agent reference
|
||||||
|
in: body
|
||||||
|
name: body
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/internal_handlers.WaitJobIn'
|
||||||
|
produces:
|
||||||
|
- application/json
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: OK
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/internal_handlers.JobResult'
|
||||||
|
"400":
|
||||||
|
description: Bad Request
|
||||||
|
schema:
|
||||||
|
additionalProperties:
|
||||||
|
type: string
|
||||||
|
type: object
|
||||||
|
"404":
|
||||||
|
description: Not Found
|
||||||
|
schema:
|
||||||
|
additionalProperties:
|
||||||
|
type: string
|
||||||
|
type: object
|
||||||
|
summary: Wait for job result
|
||||||
|
tags:
|
||||||
|
- jobs
|
||||||
|
/jobs/check_cmd:
|
||||||
|
post:
|
||||||
|
consumes:
|
||||||
|
- application/json
|
||||||
|
description: Validates that a command binary exists on the system
|
||||||
|
parameters:
|
||||||
|
- description: Command to check
|
||||||
|
in: body
|
||||||
|
name: body
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/internal_handlers.CheckCmdIn'
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: OK
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/internal_handlers.CheckCmdOut'
|
||||||
|
"404":
|
||||||
|
description: Not Found
|
||||||
|
schema:
|
||||||
|
additionalProperties:
|
||||||
|
type: string
|
||||||
|
type: object
|
||||||
|
summary: Check command path
|
||||||
|
tags:
|
||||||
|
- jobs
|
||||||
|
/jobs/metrics:
|
||||||
|
get:
|
||||||
|
description: Returns total, successful, failed, and pending job counts over
|
||||||
|
the given period
|
||||||
|
parameters:
|
||||||
|
- default: 24h
|
||||||
|
description: Time period (e.g. 1h, 24h, 7d)
|
||||||
|
in: query
|
||||||
|
name: period
|
||||||
|
type: string
|
||||||
|
produces:
|
||||||
|
- application/json
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: OK
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/internal_handlers.JobMetricsOut'
|
||||||
|
"400":
|
||||||
|
description: Bad Request
|
||||||
|
schema:
|
||||||
|
additionalProperties:
|
||||||
|
type: string
|
||||||
|
type: object
|
||||||
security:
|
security:
|
||||||
- Bearer: []
|
- Bearer: []
|
||||||
summary: Create and run a job on an agent
|
summary: Get job metrics
|
||||||
tags:
|
tags:
|
||||||
- jobs
|
- jobs
|
||||||
/logs:
|
/logs:
|
||||||
@@ -1468,6 +1620,90 @@ paths:
|
|||||||
summary: Run script by ID
|
summary: Run script by ID
|
||||||
tags:
|
tags:
|
||||||
- scripts
|
- scripts
|
||||||
|
/scripts/by-path:
|
||||||
|
get:
|
||||||
|
description: Returns a script by its full path (e.g. 'deploy/nginx/restart.sh')
|
||||||
|
parameters:
|
||||||
|
- description: Script path
|
||||||
|
in: query
|
||||||
|
name: path
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
produces:
|
||||||
|
- application/json
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: OK
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/gitea_d3m0k1d_ru_d3m0k1d_HellreigN_backend_internal_repository.Script'
|
||||||
|
"400":
|
||||||
|
description: Bad Request
|
||||||
|
schema:
|
||||||
|
additionalProperties:
|
||||||
|
type: string
|
||||||
|
type: object
|
||||||
|
"404":
|
||||||
|
description: Not Found
|
||||||
|
schema:
|
||||||
|
additionalProperties:
|
||||||
|
type: string
|
||||||
|
type: object
|
||||||
|
security:
|
||||||
|
- Bearer: []
|
||||||
|
summary: Get script by path
|
||||||
|
tags:
|
||||||
|
- scripts
|
||||||
|
/scripts/folder:
|
||||||
|
delete:
|
||||||
|
consumes:
|
||||||
|
- application/json
|
||||||
|
description: Deletes all scripts that start with the given folder path
|
||||||
|
parameters:
|
||||||
|
- description: Folder path
|
||||||
|
in: body
|
||||||
|
name: body
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/internal_handlers.DeleteFolderRequest'
|
||||||
|
produces:
|
||||||
|
- application/json
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: Folder deleted with count of deleted scripts
|
||||||
|
schema:
|
||||||
|
additionalProperties: true
|
||||||
|
type: object
|
||||||
|
security:
|
||||||
|
- Bearer: []
|
||||||
|
summary: Delete folder
|
||||||
|
tags:
|
||||||
|
- scripts
|
||||||
|
post:
|
||||||
|
consumes:
|
||||||
|
- application/json
|
||||||
|
description: Creates a virtual folder by creating a placeholder script with
|
||||||
|
the folder path
|
||||||
|
parameters:
|
||||||
|
- description: Folder path
|
||||||
|
in: body
|
||||||
|
name: body
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/internal_handlers.CreateFolderRequest'
|
||||||
|
produces:
|
||||||
|
- application/json
|
||||||
|
responses:
|
||||||
|
"201":
|
||||||
|
description: Folder created
|
||||||
|
schema:
|
||||||
|
additionalProperties:
|
||||||
|
type: string
|
||||||
|
type: object
|
||||||
|
security:
|
||||||
|
- Bearer: []
|
||||||
|
summary: Create folder
|
||||||
|
tags:
|
||||||
|
- scripts
|
||||||
/scripts/interpreters:
|
/scripts/interpreters:
|
||||||
get:
|
get:
|
||||||
description: Returns all script interpreters available in the system
|
description: Returns all script interpreters available in the system
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"gitea.d3m0k1d.ru/d3m0k1d/HellreigN/backend/internal/grpcsrv/commander"
|
"gitea.d3m0k1d.ru/d3m0k1d/HellreigN/backend/internal/grpcsrv/commander"
|
||||||
"gitea.d3m0k1d.ru/d3m0k1d/HellreigN/backend/internal/models"
|
"gitea.d3m0k1d.ru/d3m0k1d/HellreigN/backend/internal/models"
|
||||||
@@ -63,6 +64,12 @@ func (sh *ScriptHandlersGroup) CreateScript(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Validate path
|
||||||
|
if err := validateScriptPath(req.Path); err != nil {
|
||||||
|
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
script, err := sh.svc.Repo.CreateScript(req)
|
script, err := sh.svc.Repo.CreateScript(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if isUniqueConstraint(err) {
|
if isUniqueConstraint(err) {
|
||||||
@@ -133,6 +140,14 @@ func (sh *ScriptHandlersGroup) UpdateScript(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Validate path if it's being updated
|
||||||
|
if req.Path != nil {
|
||||||
|
if err := validateScriptPath(*req.Path); err != nil {
|
||||||
|
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
script, err := sh.svc.Repo.UpdateScript(id, req)
|
script, err := sh.svc.Repo.UpdateScript(id, req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, repository.ErrNotFound) {
|
if errors.Is(err, repository.ErrNotFound) {
|
||||||
@@ -255,6 +270,136 @@ type RunStoredScriptIn struct {
|
|||||||
Stdin *string `json:"stdin"`
|
Stdin *string `json:"stdin"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CreateFolderRequest is the request body for creating a script folder.
|
||||||
|
type CreateFolderRequest struct {
|
||||||
|
Path string `json:"path" binding:"required" example:"deploy/nginx" description:"Folder path (e.g. 'deploy/nginx')"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteFolderRequest is the request body for deleting a script folder.
|
||||||
|
type DeleteFolderRequest struct {
|
||||||
|
Path string `json:"path" binding:"required" example:"deploy/nginx" description:"Folder path to delete"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateFolder creates a virtual folder in the script tree.
|
||||||
|
// @Summary Create folder
|
||||||
|
// @Description Creates a virtual folder by creating a placeholder script with the folder path
|
||||||
|
// @Tags scripts
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Param body body CreateFolderRequest true "Folder path"
|
||||||
|
// @Success 201 {object} map[string]string "Folder created"
|
||||||
|
// @Security Bearer
|
||||||
|
// @Router /scripts/folder [post]
|
||||||
|
func (sh *ScriptHandlersGroup) CreateFolder(c *gin.Context) {
|
||||||
|
var req CreateFolderRequest
|
||||||
|
if err := c.ShouldBindJSON(&req); err != nil {
|
||||||
|
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid request body"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate folder path
|
||||||
|
if err := validateScriptPath(req.Path); err != nil {
|
||||||
|
c.JSON(http.StatusBadRequest, gin.H{"error": fmt.Sprintf("invalid folder path: %v", err)})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a placeholder script with the folder path to ensure the folder exists in the tree
|
||||||
|
// The placeholder uses ".folder" as content and interpreter_id 0 (will be resolved at runtime)
|
||||||
|
_, err := sh.svc.Repo.CreateScript(repository.ScriptCreate{
|
||||||
|
Path: req.Path,
|
||||||
|
Content: "",
|
||||||
|
InterpreterID: 0,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
if isUniqueConstraint(err) {
|
||||||
|
c.JSON(http.StatusConflict, gin.H{"error": "folder with this path already exists"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to create folder"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(http.StatusCreated, gin.H{"message": "folder created", "path": req.Path})
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteFolder deletes all scripts under a given path prefix.
|
||||||
|
// @Summary Delete folder
|
||||||
|
// @Description Deletes all scripts that start with the given folder path
|
||||||
|
// @Tags scripts
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Param body body DeleteFolderRequest true "Folder path"
|
||||||
|
// @Success 200 {object} map[string]interface{} "Folder deleted with count of deleted scripts"
|
||||||
|
// @Security Bearer
|
||||||
|
// @Router /scripts/folder [delete]
|
||||||
|
func (sh *ScriptHandlersGroup) DeleteFolder(c *gin.Context) {
|
||||||
|
var req DeleteFolderRequest
|
||||||
|
if err := c.ShouldBindJSON(&req); err != nil {
|
||||||
|
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid request body"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate folder path
|
||||||
|
if err := validateScriptPath(req.Path); err != nil {
|
||||||
|
c.JSON(http.StatusBadRequest, gin.H{"error": fmt.Sprintf("invalid folder path: %v", err)})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get all scripts and filter by path prefix
|
||||||
|
allScripts, err := sh.svc.Repo.ListScripts()
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to list scripts"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
prefix := req.Path + "/"
|
||||||
|
deletedCount := 0
|
||||||
|
for _, script := range allScripts {
|
||||||
|
// Delete scripts that are in this folder (path starts with prefix)
|
||||||
|
// or the folder placeholder itself (exact match)
|
||||||
|
if script.Path == req.Path || strings.HasPrefix(script.Path, prefix) {
|
||||||
|
if err := sh.svc.Repo.DeleteScript(script.ID); err != nil && !errors.Is(err, repository.ErrNotFound) {
|
||||||
|
c.JSON(http.StatusInternalServerError, gin.H{"error": fmt.Sprintf("failed to delete script %s: %v", script.Path, err)})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
deletedCount++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, gin.H{"message": "folder deleted", "path": req.Path, "deleted_count": deletedCount})
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetScriptByPath returns a script by its path.
|
||||||
|
// @Summary Get script by path
|
||||||
|
// @Description Returns a script by its full path (e.g. 'deploy/nginx/restart.sh')
|
||||||
|
// @Tags scripts
|
||||||
|
// @Produce json
|
||||||
|
// @Param path query string true "Script path"
|
||||||
|
// @Success 200 {object} repository.Script
|
||||||
|
// @Failure 400 {object} map[string]string
|
||||||
|
// @Failure 404 {object} map[string]string
|
||||||
|
// @Security Bearer
|
||||||
|
// @Router /scripts/by-path [get]
|
||||||
|
func (sh *ScriptHandlersGroup) GetScriptByPath(c *gin.Context) {
|
||||||
|
path := c.Query("path")
|
||||||
|
if path == "" {
|
||||||
|
c.JSON(http.StatusBadRequest, gin.H{"error": "path query parameter is required"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
script, err := sh.svc.Repo.GetScriptByPath(path)
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, repository.ErrNotFound) {
|
||||||
|
c.JSON(http.StatusNotFound, gin.H{"error": "script not found"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to get script"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, script)
|
||||||
|
}
|
||||||
|
|
||||||
// isUniqueConstraint checks if the error is a SQLite UNIQUE constraint violation.
|
// isUniqueConstraint checks if the error is a SQLite UNIQUE constraint violation.
|
||||||
func isUniqueConstraint(err error) bool {
|
func isUniqueConstraint(err error) bool {
|
||||||
return err != nil && (err.Error() != "" && contains(err.Error(), "UNIQUE constraint"))
|
return err != nil && (err.Error() != "" && contains(err.Error(), "UNIQUE constraint"))
|
||||||
@@ -272,3 +417,28 @@ func searchSubstring(s, substr string) bool {
|
|||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// validateScriptPath validates that a script path is well-formed.
|
||||||
|
// Rules: non-empty, no leading slash, no double slashes, no trailing slash, no empty segments.
|
||||||
|
func validateScriptPath(path string) error {
|
||||||
|
if path == "" {
|
||||||
|
return fmt.Errorf("path cannot be empty")
|
||||||
|
}
|
||||||
|
if strings.HasPrefix(path, "/") {
|
||||||
|
return fmt.Errorf("path cannot start with '/'")
|
||||||
|
}
|
||||||
|
if strings.HasSuffix(path, "/") {
|
||||||
|
return fmt.Errorf("path cannot end with '/'")
|
||||||
|
}
|
||||||
|
if strings.Contains(path, "//") {
|
||||||
|
return fmt.Errorf("path cannot contain '//'")
|
||||||
|
}
|
||||||
|
// Check for empty segments (e.g. "a//b" already caught, but "a/ /b" should be allowed)
|
||||||
|
segments := strings.Split(path, "/")
|
||||||
|
for i, seg := range segments {
|
||||||
|
if strings.TrimSpace(seg) == "" {
|
||||||
|
return fmt.Errorf("path segment %d cannot be empty or whitespace", i+1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user