feat: added openapi generation for agent
This commit is contained in:
@@ -13,3 +13,4 @@ serde = { version = "1.0.228", features = ["std", "derive"] }
|
||||
tokio-cron-scheduler = { version = "0.15.1", features = ["signal"] }
|
||||
clap = { version = "4", features = ["derive", "env"] }
|
||||
nix = { version = "0.30.1", features = ["user", "fs"] }
|
||||
utoipa = { version = "5.4.0", features = ["macros", "axum_extras", "chrono", "decimal", "uuid", "time", "openapi_extensions"] }
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#![forbid(unsafe_code)]
|
||||
|
||||
mod commands;
|
||||
mod openapi;
|
||||
mod routes;
|
||||
|
||||
use axum::routing::get;
|
||||
@@ -13,6 +14,7 @@ use tokio::net::UnixListener;
|
||||
use tracing::{error, info, warn};
|
||||
|
||||
use crate::commands::NginxService;
|
||||
use crate::openapi::{GenerateOpenapiArgs, generate_openapi_doc};
|
||||
use crate::routes::{status, validate, validate_and_reload, write_config};
|
||||
|
||||
const SOCK_ENV: &str = "YANPM_AGENT_SOCK";
|
||||
@@ -43,6 +45,19 @@ struct Args {
|
||||
/// GID to set on the unix socket, default: current user's primary group
|
||||
#[arg(long, default_value_t = String::from(SOCK_GID_DEFAULT), env = SOCK_GID_ENV)]
|
||||
sock_gid: String,
|
||||
|
||||
#[command(subcommand)]
|
||||
command: Option<SubCommand>,
|
||||
}
|
||||
|
||||
#[derive(clap::Subcommand, Debug)]
|
||||
pub enum SubCommand {
|
||||
/// Generate OpenAPI spec to file or stdout
|
||||
GenerateOpenapi {
|
||||
/// Output file path.
|
||||
#[arg(short = 'o', long)]
|
||||
output: String,
|
||||
},
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
@@ -59,6 +74,18 @@ async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
||||
|
||||
let args = Args::parse();
|
||||
|
||||
if let Some(cmd) = &args.command {
|
||||
match cmd {
|
||||
SubCommand::GenerateOpenapi { output } => {
|
||||
generate_openapi_doc(&GenerateOpenapiArgs {
|
||||
output: output.clone(),
|
||||
})
|
||||
.await?;
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let (sock, nginx_config_dir, sock_perm, sock_gid) = get_args(&args).await?;
|
||||
|
||||
let path = PathBuf::from(&sock);
|
||||
|
||||
45
apps/agent/src/openapi.rs
Normal file
45
apps/agent/src/openapi.rs
Normal file
@@ -0,0 +1,45 @@
|
||||
use tracing::info;
|
||||
use utoipa::OpenApi;
|
||||
|
||||
pub mod tag {
|
||||
/// nginx
|
||||
pub const NGINX_TAG: &str = "Nginx Agent";
|
||||
}
|
||||
|
||||
#[derive(utoipa::OpenApi)]
|
||||
#[openapi(
|
||||
paths(
|
||||
crate::routes::status,
|
||||
crate::routes::validate,
|
||||
crate::routes::validate_and_reload,
|
||||
crate::routes::write_config,
|
||||
),
|
||||
components(
|
||||
schemas(crate::routes::StatusResp),
|
||||
schemas(crate::routes::ValidateAndReloadResp),
|
||||
schemas(crate::routes::ValidateBody),
|
||||
schemas(crate::routes::WriteConfigBody),
|
||||
schemas(crate::routes::ValidateAndReloadBody),
|
||||
),
|
||||
tags(
|
||||
(name = tag::NGINX_TAG, description = "Nginx Agent API"),
|
||||
)
|
||||
)]
|
||||
struct ApiDoc;
|
||||
|
||||
pub struct GenerateOpenapiArgs {
|
||||
pub output: String,
|
||||
}
|
||||
|
||||
pub async fn generate_openapi_doc(
|
||||
args: &GenerateOpenapiArgs,
|
||||
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
||||
info!("Generating OpenAPI documentation...");
|
||||
let doc = ApiDoc::openapi();
|
||||
let json = doc
|
||||
.to_pretty_json()
|
||||
.expect("Failed to serialize OpenAPI doc to JSON");
|
||||
std::fs::write(&args.output, json).expect("Failed to write OpenAPI doc to file");
|
||||
info!("OpenAPI documentation generated at {}", args.output);
|
||||
Ok(())
|
||||
}
|
||||
@@ -9,28 +9,46 @@ use tracing::warn;
|
||||
|
||||
use crate::commands::NginxService;
|
||||
|
||||
#[derive(Serialize)]
|
||||
#[derive(Serialize, utoipa::ToSchema)]
|
||||
pub struct StatusResp {
|
||||
pub ok: bool,
|
||||
}
|
||||
|
||||
/// Health check endpoint
|
||||
#[utoipa::path(
|
||||
get,
|
||||
path = "/status",
|
||||
responses(
|
||||
(status = 200, description = "Status response", body = StatusResp)
|
||||
),
|
||||
tag = crate::openapi::tag::NGINX_TAG
|
||||
)]
|
||||
pub async fn status() -> impl IntoResponse {
|
||||
let resp = StatusResp { ok: true };
|
||||
(axum::http::StatusCode::OK, axum::Json(resp))
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
#[derive(Serialize, utoipa::ToSchema)]
|
||||
pub struct ValidateAndReloadResp {
|
||||
pub rc: i32,
|
||||
pub ro: String,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[derive(Deserialize, utoipa::ToSchema)]
|
||||
pub struct ValidateBody {
|
||||
config_name: String,
|
||||
timestamp: u64,
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
post,
|
||||
path = "/validate",
|
||||
request_body = ValidateBody,
|
||||
responses(
|
||||
(status = 200, description = "Validation response", body = serde_json::Value)
|
||||
),
|
||||
tag = crate::openapi::tag::NGINX_TAG
|
||||
)]
|
||||
pub async fn validate(
|
||||
State(nginx_controller): State<Arc<NginxService>>,
|
||||
Json(payload): Json<Value>,
|
||||
@@ -57,12 +75,21 @@ pub async fn validate(
|
||||
(axum::http::StatusCode::OK, axum::Json(resp)).into_response()
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[derive(Deserialize, utoipa::ToSchema)]
|
||||
pub struct ValidateAndReloadBody {
|
||||
config_name: String,
|
||||
timestamp: u64,
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
post,
|
||||
path = "/validate_and_reload",
|
||||
request_body = ValidateAndReloadBody,
|
||||
responses(
|
||||
(status = 200, description = "Validate and reload response", body = ValidateAndReloadResp)
|
||||
),
|
||||
tag = crate::openapi::tag::NGINX_TAG
|
||||
)]
|
||||
pub async fn validate_and_reload(
|
||||
State(nginx_controller): State<Arc<NginxService>>,
|
||||
Json(payload): Json<Value>,
|
||||
@@ -96,13 +123,23 @@ pub async fn validate_and_reload(
|
||||
(axum::http::StatusCode::OK, axum::Json(resp)).into_response()
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[derive(Deserialize, utoipa::ToSchema)]
|
||||
pub struct WriteConfigBody {
|
||||
config_name: String,
|
||||
timestamp: u64,
|
||||
content: String,
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
post,
|
||||
path = "/write_config",
|
||||
request_body = WriteConfigBody,
|
||||
responses(
|
||||
(status = 200, description = "Write config response"),
|
||||
(status = 500, description = "Internal server error", body = serde_json::Value)
|
||||
),
|
||||
tag = crate::openapi::tag::NGINX_TAG
|
||||
)]
|
||||
pub async fn write_config(
|
||||
State(nginx_controller): State<Arc<NginxService>>,
|
||||
Json(payload): Json<Value>,
|
||||
|
||||
215
apps/agent/swagger.json
Normal file
215
apps/agent/swagger.json
Normal file
@@ -0,0 +1,215 @@
|
||||
{
|
||||
"openapi": "3.1.0",
|
||||
"info": {
|
||||
"title": "yanpm-agent",
|
||||
"description": "",
|
||||
"license": {
|
||||
"name": ""
|
||||
},
|
||||
"version": "0.1.0"
|
||||
},
|
||||
"paths": {
|
||||
"/status": {
|
||||
"get": {
|
||||
"tags": [
|
||||
"Nginx Agent"
|
||||
],
|
||||
"summary": "Health check endpoint",
|
||||
"operationId": "status",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Status response",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/StatusResp"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/validate": {
|
||||
"post": {
|
||||
"tags": [
|
||||
"Nginx Agent"
|
||||
],
|
||||
"operationId": "validate",
|
||||
"requestBody": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/ValidateBody"
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": true
|
||||
},
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Validation response",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/validate_and_reload": {
|
||||
"post": {
|
||||
"tags": [
|
||||
"Nginx Agent"
|
||||
],
|
||||
"operationId": "validate_and_reload",
|
||||
"requestBody": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/ValidateAndReloadBody"
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": true
|
||||
},
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Validate and reload response",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/ValidateAndReloadResp"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/write_config": {
|
||||
"post": {
|
||||
"tags": [
|
||||
"Nginx Agent"
|
||||
],
|
||||
"operationId": "write_config",
|
||||
"requestBody": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/WriteConfigBody"
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": true
|
||||
},
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Write config response"
|
||||
},
|
||||
"500": {
|
||||
"description": "Internal server error",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"components": {
|
||||
"schemas": {
|
||||
"StatusResp": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"ok"
|
||||
],
|
||||
"properties": {
|
||||
"ok": {
|
||||
"type": "boolean"
|
||||
}
|
||||
}
|
||||
},
|
||||
"ValidateAndReloadBody": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"config_name",
|
||||
"timestamp"
|
||||
],
|
||||
"properties": {
|
||||
"config_name": {
|
||||
"type": "string"
|
||||
},
|
||||
"timestamp": {
|
||||
"type": "integer",
|
||||
"format": "int64",
|
||||
"minimum": 0
|
||||
}
|
||||
}
|
||||
},
|
||||
"ValidateAndReloadResp": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"rc",
|
||||
"ro"
|
||||
],
|
||||
"properties": {
|
||||
"rc": {
|
||||
"type": "integer",
|
||||
"format": "int32"
|
||||
},
|
||||
"ro": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"ValidateBody": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"config_name",
|
||||
"timestamp"
|
||||
],
|
||||
"properties": {
|
||||
"config_name": {
|
||||
"type": "string"
|
||||
},
|
||||
"timestamp": {
|
||||
"type": "integer",
|
||||
"format": "int64",
|
||||
"minimum": 0
|
||||
}
|
||||
}
|
||||
},
|
||||
"WriteConfigBody": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"config_name",
|
||||
"timestamp",
|
||||
"content"
|
||||
],
|
||||
"properties": {
|
||||
"config_name": {
|
||||
"type": "string"
|
||||
},
|
||||
"content": {
|
||||
"type": "string"
|
||||
},
|
||||
"timestamp": {
|
||||
"type": "integer",
|
||||
"format": "int64",
|
||||
"minimum": 0
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"tags": [
|
||||
{
|
||||
"name": "Nginx Agent",
|
||||
"description": "Nginx Agent API"
|
||||
}
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user