properly handling patch updates
Some checks failed
CI / Clippy (push) Failing after 3m6s
CI / Rustfmt (push) Failing after 3m9s
CI / Cargo Audit & Deny (push) Successful in 5m2s
CI / Tests (push) Successful in 8m15s
CI / Security Blocking Checks (push) Successful in 10s
CI / Web Advisory Checks (push) Successful in 1m4s
CI / Web Blocking Checks (push) Failing after 4m52s
Publish Images And Chart / Resolve Publish Metadata (push) Successful in 2s
CI / Security Advisory Checks (push) Successful in 1m31s
Publish Images And Chart / Publish init-user (push) Failing after 30s
Publish Images And Chart / Publish init-packs (push) Failing after 1m41s
Publish Images And Chart / Publish migrations (push) Failing after 10s
Publish Images And Chart / Publish web (push) Failing after 11s
Publish Images And Chart / Publish sensor (push) Failing after 32s
Publish Images And Chart / Publish worker (push) Failing after 11s
Publish Images And Chart / Publish executor (push) Failing after 11s
Publish Images And Chart / Publish notifier (push) Failing after 9s
Publish Images And Chart / Publish api (push) Failing after 31s
Publish Images And Chart / Publish Helm Chart (push) Has been skipped
Some checks failed
CI / Clippy (push) Failing after 3m6s
CI / Rustfmt (push) Failing after 3m9s
CI / Cargo Audit & Deny (push) Successful in 5m2s
CI / Tests (push) Successful in 8m15s
CI / Security Blocking Checks (push) Successful in 10s
CI / Web Advisory Checks (push) Successful in 1m4s
CI / Web Blocking Checks (push) Failing after 4m52s
Publish Images And Chart / Resolve Publish Metadata (push) Successful in 2s
CI / Security Advisory Checks (push) Successful in 1m31s
Publish Images And Chart / Publish init-user (push) Failing after 30s
Publish Images And Chart / Publish init-packs (push) Failing after 1m41s
Publish Images And Chart / Publish migrations (push) Failing after 10s
Publish Images And Chart / Publish web (push) Failing after 11s
Publish Images And Chart / Publish sensor (push) Failing after 32s
Publish Images And Chart / Publish worker (push) Failing after 11s
Publish Images And Chart / Publish executor (push) Failing after 11s
Publish Images And Chart / Publish notifier (push) Failing after 9s
Publish Images And Chart / Publish api (push) Failing after 31s
Publish Images And Chart / Publish Helm Chart (push) Has been skipped
This commit is contained in:
@@ -76,9 +76,8 @@ pub struct UpdateActionRequest {
|
||||
#[schema(example = 1)]
|
||||
pub runtime: Option<i64>,
|
||||
|
||||
/// Optional semver version constraint for the runtime (e.g., ">=3.12", ">=3.12,<4.0", "~18.0")
|
||||
#[schema(example = ">=3.12", nullable = true)]
|
||||
pub runtime_version_constraint: Option<Option<String>>,
|
||||
/// Optional semver version constraint patch for the runtime.
|
||||
pub runtime_version_constraint: Option<RuntimeVersionConstraintPatch>,
|
||||
|
||||
/// Parameter schema (StackStorm-style with inline required/secret)
|
||||
#[schema(value_type = Object, nullable = true)]
|
||||
@@ -89,6 +88,14 @@ pub struct UpdateActionRequest {
|
||||
pub out_schema: Option<JsonValue>,
|
||||
}
|
||||
|
||||
/// Explicit patch operation for a nullable runtime version constraint.
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, ToSchema)]
|
||||
#[serde(tag = "op", content = "value", rename_all = "snake_case")]
|
||||
pub enum RuntimeVersionConstraintPatch {
|
||||
Set(String),
|
||||
Clear,
|
||||
}
|
||||
|
||||
/// Response DTO for action information
|
||||
#[derive(Debug, Clone, Serialize, ToSchema)]
|
||||
pub struct ActionResponse {
|
||||
|
||||
@@ -97,19 +97,41 @@ pub struct UpdateArtifactRequest {
|
||||
pub retention_limit: Option<i32>,
|
||||
|
||||
/// Updated name
|
||||
pub name: Option<String>,
|
||||
pub name: Option<ArtifactStringPatch>,
|
||||
|
||||
/// Updated description
|
||||
pub description: Option<String>,
|
||||
pub description: Option<ArtifactStringPatch>,
|
||||
|
||||
/// Updated content type
|
||||
pub content_type: Option<String>,
|
||||
pub content_type: Option<ArtifactStringPatch>,
|
||||
|
||||
/// Updated execution ID (re-links artifact to a different execution)
|
||||
pub execution: Option<i64>,
|
||||
/// Updated execution patch (set a new execution ID or clear the link)
|
||||
pub execution: Option<ArtifactExecutionPatch>,
|
||||
|
||||
/// Updated structured data (replaces existing data entirely)
|
||||
pub data: Option<JsonValue>,
|
||||
pub data: Option<ArtifactJsonPatch>,
|
||||
}
|
||||
|
||||
/// Explicit patch operation for a nullable execution link.
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, ToSchema)]
|
||||
#[serde(tag = "op", content = "value", rename_all = "snake_case")]
|
||||
pub enum ArtifactExecutionPatch {
|
||||
Set(i64),
|
||||
Clear,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, ToSchema)]
|
||||
#[serde(tag = "op", content = "value", rename_all = "snake_case")]
|
||||
pub enum ArtifactStringPatch {
|
||||
Set(String),
|
||||
Clear,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, ToSchema)]
|
||||
#[serde(tag = "op", content = "value", rename_all = "snake_case")]
|
||||
pub enum ArtifactJsonPatch {
|
||||
Set(JsonValue),
|
||||
Clear,
|
||||
}
|
||||
|
||||
/// Request DTO for appending to a progress-type artifact
|
||||
|
||||
@@ -13,6 +13,7 @@ pub mod key;
|
||||
pub mod pack;
|
||||
pub mod permission;
|
||||
pub mod rule;
|
||||
pub mod runtime;
|
||||
pub mod trigger;
|
||||
pub mod webhook;
|
||||
pub mod workflow;
|
||||
@@ -55,6 +56,7 @@ pub use permission::{
|
||||
UpdateIdentityRequest,
|
||||
};
|
||||
pub use rule::{CreateRuleRequest, RuleResponse, RuleSummary, UpdateRuleRequest};
|
||||
pub use runtime::{CreateRuntimeRequest, RuntimeResponse, RuntimeSummary, UpdateRuntimeRequest};
|
||||
pub use trigger::{
|
||||
CreateSensorRequest, CreateTriggerRequest, SensorResponse, SensorSummary, TriggerResponse,
|
||||
TriggerSummary, UpdateSensorRequest, UpdateTriggerRequest,
|
||||
|
||||
@@ -129,7 +129,7 @@ pub struct UpdatePackRequest {
|
||||
|
||||
/// Pack description
|
||||
#[schema(example = "Enhanced Slack integration with new features")]
|
||||
pub description: Option<String>,
|
||||
pub description: Option<PackDescriptionPatch>,
|
||||
|
||||
/// Pack version
|
||||
#[validate(length(min = 1, max = 50))]
|
||||
@@ -165,6 +165,13 @@ pub struct UpdatePackRequest {
|
||||
pub is_standard: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, ToSchema)]
|
||||
#[serde(tag = "op", content = "value", rename_all = "snake_case")]
|
||||
pub enum PackDescriptionPatch {
|
||||
Set(String),
|
||||
Clear,
|
||||
}
|
||||
|
||||
/// Response DTO for pack information
|
||||
#[derive(Debug, Clone, Serialize, ToSchema)]
|
||||
pub struct PackResponse {
|
||||
|
||||
181
crates/api/src/dto/runtime.rs
Normal file
181
crates/api/src/dto/runtime.rs
Normal file
@@ -0,0 +1,181 @@
|
||||
//! Runtime DTOs for API requests and responses
|
||||
|
||||
use chrono::{DateTime, Utc};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::Value as JsonValue;
|
||||
use utoipa::ToSchema;
|
||||
use validator::Validate;
|
||||
|
||||
/// Request DTO for creating a runtime.
|
||||
#[derive(Debug, Clone, Deserialize, Validate, ToSchema)]
|
||||
pub struct CreateRuntimeRequest {
|
||||
/// Unique reference identifier (e.g. "core.python", "core.nodejs")
|
||||
#[validate(length(min = 1, max = 255))]
|
||||
#[schema(example = "core.python")]
|
||||
pub r#ref: String,
|
||||
|
||||
/// Optional pack reference this runtime belongs to
|
||||
#[validate(length(min = 1, max = 255))]
|
||||
#[schema(example = "core", nullable = true)]
|
||||
pub pack_ref: Option<String>,
|
||||
|
||||
/// Optional human-readable description
|
||||
#[validate(length(min = 1))]
|
||||
#[schema(example = "Python runtime with virtualenv support", nullable = true)]
|
||||
pub description: Option<String>,
|
||||
|
||||
/// Display name
|
||||
#[validate(length(min = 1, max = 255))]
|
||||
#[schema(example = "Python")]
|
||||
pub name: String,
|
||||
|
||||
/// Distribution metadata used for verification and platform support
|
||||
#[serde(default)]
|
||||
#[schema(value_type = Object, example = json!({"linux": {"supported": true}}))]
|
||||
pub distributions: JsonValue,
|
||||
|
||||
/// Optional installation metadata
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
#[schema(value_type = Object, nullable = true, example = json!({"method": "system"}))]
|
||||
pub installation: Option<JsonValue>,
|
||||
|
||||
/// Runtime execution configuration
|
||||
#[serde(default)]
|
||||
#[schema(value_type = Object, example = json!({"interpreter": {"command": "python3"}}))]
|
||||
pub execution_config: JsonValue,
|
||||
}
|
||||
|
||||
/// Request DTO for updating a runtime.
|
||||
#[derive(Debug, Clone, Deserialize, Validate, ToSchema)]
|
||||
pub struct UpdateRuntimeRequest {
|
||||
/// Optional human-readable description patch.
|
||||
pub description: Option<NullableStringPatch>,
|
||||
|
||||
/// Display name
|
||||
#[validate(length(min = 1, max = 255))]
|
||||
#[schema(example = "Python 3")]
|
||||
pub name: Option<String>,
|
||||
|
||||
/// Distribution metadata used for verification and platform support
|
||||
#[schema(value_type = Object, nullable = true)]
|
||||
pub distributions: Option<JsonValue>,
|
||||
|
||||
/// Optional installation metadata patch.
|
||||
pub installation: Option<NullableJsonPatch>,
|
||||
|
||||
/// Runtime execution configuration
|
||||
#[schema(value_type = Object, nullable = true)]
|
||||
pub execution_config: Option<JsonValue>,
|
||||
}
|
||||
|
||||
/// Explicit patch operation for nullable string fields.
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, ToSchema)]
|
||||
#[serde(tag = "op", content = "value", rename_all = "snake_case")]
|
||||
pub enum NullableStringPatch {
|
||||
#[schema(title = "SetString")]
|
||||
Set(String),
|
||||
Clear,
|
||||
}
|
||||
|
||||
/// Explicit patch operation for nullable JSON fields.
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, ToSchema)]
|
||||
#[serde(tag = "op", content = "value", rename_all = "snake_case")]
|
||||
pub enum NullableJsonPatch {
|
||||
#[schema(title = "SetJson")]
|
||||
Set(JsonValue),
|
||||
Clear,
|
||||
}
|
||||
|
||||
/// Full runtime response.
|
||||
#[derive(Debug, Clone, Serialize, ToSchema)]
|
||||
pub struct RuntimeResponse {
|
||||
#[schema(example = 1)]
|
||||
pub id: i64,
|
||||
|
||||
#[schema(example = "core.python")]
|
||||
pub r#ref: String,
|
||||
|
||||
#[schema(example = 1, nullable = true)]
|
||||
pub pack: Option<i64>,
|
||||
|
||||
#[schema(example = "core", nullable = true)]
|
||||
pub pack_ref: Option<String>,
|
||||
|
||||
#[schema(example = "Python runtime with virtualenv support", nullable = true)]
|
||||
pub description: Option<String>,
|
||||
|
||||
#[schema(example = "Python")]
|
||||
pub name: String,
|
||||
|
||||
#[schema(value_type = Object)]
|
||||
pub distributions: JsonValue,
|
||||
|
||||
#[schema(value_type = Object, nullable = true)]
|
||||
pub installation: Option<JsonValue>,
|
||||
|
||||
#[schema(value_type = Object)]
|
||||
pub execution_config: JsonValue,
|
||||
|
||||
#[schema(example = "2024-01-13T10:30:00Z")]
|
||||
pub created: DateTime<Utc>,
|
||||
|
||||
#[schema(example = "2024-01-13T10:30:00Z")]
|
||||
pub updated: DateTime<Utc>,
|
||||
}
|
||||
|
||||
/// Runtime summary for list views.
|
||||
#[derive(Debug, Clone, Serialize, ToSchema)]
|
||||
pub struct RuntimeSummary {
|
||||
#[schema(example = 1)]
|
||||
pub id: i64,
|
||||
|
||||
#[schema(example = "core.python")]
|
||||
pub r#ref: String,
|
||||
|
||||
#[schema(example = "core", nullable = true)]
|
||||
pub pack_ref: Option<String>,
|
||||
|
||||
#[schema(example = "Python runtime with virtualenv support", nullable = true)]
|
||||
pub description: Option<String>,
|
||||
|
||||
#[schema(example = "Python")]
|
||||
pub name: String,
|
||||
|
||||
#[schema(example = "2024-01-13T10:30:00Z")]
|
||||
pub created: DateTime<Utc>,
|
||||
|
||||
#[schema(example = "2024-01-13T10:30:00Z")]
|
||||
pub updated: DateTime<Utc>,
|
||||
}
|
||||
|
||||
impl From<attune_common::models::runtime::Runtime> for RuntimeResponse {
|
||||
fn from(runtime: attune_common::models::runtime::Runtime) -> Self {
|
||||
Self {
|
||||
id: runtime.id,
|
||||
r#ref: runtime.r#ref,
|
||||
pack: runtime.pack,
|
||||
pack_ref: runtime.pack_ref,
|
||||
description: runtime.description,
|
||||
name: runtime.name,
|
||||
distributions: runtime.distributions,
|
||||
installation: runtime.installation,
|
||||
execution_config: runtime.execution_config,
|
||||
created: runtime.created,
|
||||
updated: runtime.updated,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<attune_common::models::runtime::Runtime> for RuntimeSummary {
|
||||
fn from(runtime: attune_common::models::runtime::Runtime) -> Self {
|
||||
Self {
|
||||
id: runtime.id,
|
||||
r#ref: runtime.r#ref,
|
||||
pack_ref: runtime.pack_ref,
|
||||
description: runtime.description,
|
||||
name: runtime.name,
|
||||
created: runtime.created,
|
||||
updated: runtime.updated,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -54,21 +54,35 @@ pub struct UpdateTriggerRequest {
|
||||
|
||||
/// Trigger description
|
||||
#[schema(example = "Updated webhook trigger description")]
|
||||
pub description: Option<String>,
|
||||
pub description: Option<TriggerStringPatch>,
|
||||
|
||||
/// Parameter schema (StackStorm-style with inline required/secret)
|
||||
#[schema(value_type = Object, nullable = true)]
|
||||
pub param_schema: Option<JsonValue>,
|
||||
pub param_schema: Option<TriggerJsonPatch>,
|
||||
|
||||
/// Output schema
|
||||
#[schema(value_type = Object, nullable = true)]
|
||||
pub out_schema: Option<JsonValue>,
|
||||
pub out_schema: Option<TriggerJsonPatch>,
|
||||
|
||||
/// Whether the trigger is enabled
|
||||
#[schema(example = true)]
|
||||
pub enabled: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, ToSchema)]
|
||||
#[serde(tag = "op", content = "value", rename_all = "snake_case")]
|
||||
pub enum TriggerStringPatch {
|
||||
Set(String),
|
||||
Clear,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, ToSchema)]
|
||||
#[serde(tag = "op", content = "value", rename_all = "snake_case")]
|
||||
pub enum TriggerJsonPatch {
|
||||
Set(JsonValue),
|
||||
Clear,
|
||||
}
|
||||
|
||||
/// Response DTO for trigger information
|
||||
#[derive(Debug, Clone, Serialize, ToSchema)]
|
||||
pub struct TriggerResponse {
|
||||
@@ -244,13 +258,20 @@ pub struct UpdateSensorRequest {
|
||||
|
||||
/// Parameter schema (StackStorm-style with inline required/secret)
|
||||
#[schema(value_type = Object, nullable = true)]
|
||||
pub param_schema: Option<JsonValue>,
|
||||
pub param_schema: Option<SensorJsonPatch>,
|
||||
|
||||
/// Whether the sensor is enabled
|
||||
#[schema(example = false)]
|
||||
pub enabled: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, ToSchema)]
|
||||
#[serde(tag = "op", content = "value", rename_all = "snake_case")]
|
||||
pub enum SensorJsonPatch {
|
||||
Set(JsonValue),
|
||||
Clear,
|
||||
}
|
||||
|
||||
/// Response DTO for sensor information
|
||||
#[derive(Debug, Clone, Serialize, ToSchema)]
|
||||
pub struct SensorResponse {
|
||||
|
||||
@@ -31,6 +31,7 @@ use crate::dto::{
|
||||
IdentitySummary, PermissionAssignmentResponse, PermissionSetSummary, UpdateIdentityRequest,
|
||||
},
|
||||
rule::{CreateRuleRequest, RuleResponse, RuleSummary, UpdateRuleRequest},
|
||||
runtime::{CreateRuntimeRequest, RuntimeResponse, RuntimeSummary, UpdateRuntimeRequest},
|
||||
trigger::{
|
||||
CreateSensorRequest, CreateTriggerRequest, SensorResponse, SensorSummary, TriggerResponse,
|
||||
TriggerSummary, UpdateSensorRequest, UpdateTriggerRequest,
|
||||
@@ -96,6 +97,14 @@ use crate::dto::{
|
||||
crate::routes::actions::delete_action,
|
||||
crate::routes::actions::get_queue_stats,
|
||||
|
||||
// Runtimes
|
||||
crate::routes::runtimes::list_runtimes,
|
||||
crate::routes::runtimes::list_runtimes_by_pack,
|
||||
crate::routes::runtimes::get_runtime,
|
||||
crate::routes::runtimes::create_runtime,
|
||||
crate::routes::runtimes::update_runtime,
|
||||
crate::routes::runtimes::delete_runtime,
|
||||
|
||||
// Triggers
|
||||
crate::routes::triggers::list_triggers,
|
||||
crate::routes::triggers::list_enabled_triggers,
|
||||
@@ -197,6 +206,7 @@ use crate::dto::{
|
||||
ApiResponse<PackResponse>,
|
||||
ApiResponse<PackInstallResponse>,
|
||||
ApiResponse<ActionResponse>,
|
||||
ApiResponse<RuntimeResponse>,
|
||||
ApiResponse<TriggerResponse>,
|
||||
ApiResponse<SensorResponse>,
|
||||
ApiResponse<RuleResponse>,
|
||||
@@ -211,6 +221,7 @@ use crate::dto::{
|
||||
ApiResponse<QueueStatsResponse>,
|
||||
PaginatedResponse<PackSummary>,
|
||||
PaginatedResponse<ActionSummary>,
|
||||
PaginatedResponse<RuntimeSummary>,
|
||||
PaginatedResponse<TriggerSummary>,
|
||||
PaginatedResponse<SensorSummary>,
|
||||
PaginatedResponse<RuleSummary>,
|
||||
@@ -258,6 +269,12 @@ use crate::dto::{
|
||||
PermissionSetSummary,
|
||||
PermissionAssignmentResponse,
|
||||
CreatePermissionAssignmentRequest,
|
||||
|
||||
// Runtime DTOs
|
||||
CreateRuntimeRequest,
|
||||
UpdateRuntimeRequest,
|
||||
RuntimeResponse,
|
||||
RuntimeSummary,
|
||||
IdentitySummary,
|
||||
|
||||
// Action DTOs
|
||||
|
||||
@@ -15,7 +15,7 @@ use attune_common::repositories::{
|
||||
action::{ActionRepository, ActionSearchFilters, CreateActionInput, UpdateActionInput},
|
||||
pack::PackRepository,
|
||||
queue_stats::QueueStatsRepository,
|
||||
Create, Delete, FindByRef, Update,
|
||||
Create, Delete, FindByRef, Patch, Update,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
@@ -24,7 +24,7 @@ use crate::{
|
||||
dto::{
|
||||
action::{
|
||||
ActionResponse, ActionSummary, CreateActionRequest, QueueStatsResponse,
|
||||
UpdateActionRequest,
|
||||
RuntimeVersionConstraintPatch, UpdateActionRequest,
|
||||
},
|
||||
common::{PaginatedResponse, PaginationParams},
|
||||
ApiResponse, SuccessResponse,
|
||||
@@ -280,7 +280,10 @@ pub async fn update_action(
|
||||
description: request.description,
|
||||
entrypoint: request.entrypoint,
|
||||
runtime: request.runtime,
|
||||
runtime_version_constraint: request.runtime_version_constraint,
|
||||
runtime_version_constraint: request.runtime_version_constraint.map(|patch| match patch {
|
||||
RuntimeVersionConstraintPatch::Set(value) => Patch::Set(value),
|
||||
RuntimeVersionConstraintPatch::Clear => Patch::Clear,
|
||||
}),
|
||||
param_schema: request.param_schema,
|
||||
out_schema: request.out_schema,
|
||||
parameter_delivery: None,
|
||||
|
||||
@@ -36,15 +36,16 @@ use attune_common::repositories::{
|
||||
ArtifactRepository, ArtifactSearchFilters, ArtifactVersionRepository, CreateArtifactInput,
|
||||
CreateArtifactVersionInput, UpdateArtifactInput,
|
||||
},
|
||||
Create, Delete, FindById, FindByRef, Update,
|
||||
Create, Delete, FindById, FindByRef, Patch, Update,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
auth::middleware::RequireAuth,
|
||||
dto::{
|
||||
artifact::{
|
||||
AllocateFileVersionByRefRequest, AppendProgressRequest, ArtifactQueryParams,
|
||||
ArtifactResponse, ArtifactSummary, ArtifactVersionResponse, ArtifactVersionSummary,
|
||||
AllocateFileVersionByRefRequest, AppendProgressRequest, ArtifactExecutionPatch,
|
||||
ArtifactJsonPatch, ArtifactQueryParams, ArtifactResponse, ArtifactStringPatch,
|
||||
ArtifactSummary, ArtifactVersionResponse, ArtifactVersionSummary,
|
||||
CreateArtifactRequest, CreateFileVersionRequest, CreateVersionJsonRequest,
|
||||
SetDataRequest, UpdateArtifactRequest,
|
||||
},
|
||||
@@ -257,12 +258,27 @@ pub async fn update_artifact(
|
||||
visibility: request.visibility,
|
||||
retention_policy: request.retention_policy,
|
||||
retention_limit: request.retention_limit,
|
||||
name: request.name,
|
||||
description: request.description,
|
||||
content_type: request.content_type,
|
||||
name: request.name.map(|patch| match patch {
|
||||
ArtifactStringPatch::Set(value) => Patch::Set(value),
|
||||
ArtifactStringPatch::Clear => Patch::Clear,
|
||||
}),
|
||||
description: request.description.map(|patch| match patch {
|
||||
ArtifactStringPatch::Set(value) => Patch::Set(value),
|
||||
ArtifactStringPatch::Clear => Patch::Clear,
|
||||
}),
|
||||
content_type: request.content_type.map(|patch| match patch {
|
||||
ArtifactStringPatch::Set(value) => Patch::Set(value),
|
||||
ArtifactStringPatch::Clear => Patch::Clear,
|
||||
}),
|
||||
size_bytes: None, // Managed by version creation trigger
|
||||
execution: request.execution.map(Some),
|
||||
data: request.data,
|
||||
execution: request.execution.map(|patch| match patch {
|
||||
ArtifactExecutionPatch::Set(value) => Patch::Set(value),
|
||||
ArtifactExecutionPatch::Clear => Patch::Clear,
|
||||
}),
|
||||
data: request.data.map(|patch| match patch {
|
||||
ArtifactJsonPatch::Set(value) => Patch::Set(value),
|
||||
ArtifactJsonPatch::Clear => Patch::Clear,
|
||||
}),
|
||||
};
|
||||
|
||||
let updated = ArtifactRepository::update(&state.db, id, input).await?;
|
||||
@@ -1155,7 +1171,7 @@ pub async fn upload_version_by_ref(
|
||||
description: None,
|
||||
content_type: None,
|
||||
size_bytes: None,
|
||||
execution: execution_id.map(Some),
|
||||
execution: execution_id.map(Patch::Set),
|
||||
data: None,
|
||||
};
|
||||
ArtifactRepository::update(&state.db, existing.id, update_input).await?
|
||||
@@ -1303,7 +1319,7 @@ pub async fn allocate_file_version_by_ref(
|
||||
description: None,
|
||||
content_type: None,
|
||||
size_bytes: None,
|
||||
execution: request.execution.map(Some),
|
||||
execution: request.execution.map(Patch::Set),
|
||||
data: None,
|
||||
};
|
||||
ArtifactRepository::update(&state.db, existing.id, update_input).await?
|
||||
|
||||
@@ -13,6 +13,7 @@ pub mod keys;
|
||||
pub mod packs;
|
||||
pub mod permissions;
|
||||
pub mod rules;
|
||||
pub mod runtimes;
|
||||
pub mod triggers;
|
||||
pub mod webhooks;
|
||||
pub mod workflows;
|
||||
@@ -30,6 +31,7 @@ pub use keys::routes as key_routes;
|
||||
pub use packs::routes as pack_routes;
|
||||
pub use permissions::routes as permission_routes;
|
||||
pub use rules::routes as rule_routes;
|
||||
pub use runtimes::routes as runtime_routes;
|
||||
pub use triggers::routes as trigger_routes;
|
||||
pub use webhooks::routes as webhook_routes;
|
||||
pub use workflows::routes as workflow_routes;
|
||||
|
||||
@@ -16,7 +16,8 @@ use attune_common::mq::{MessageEnvelope, MessageType, PackRegisteredPayload};
|
||||
use attune_common::rbac::{Action, AuthorizationContext, Resource};
|
||||
use attune_common::repositories::{
|
||||
pack::{CreatePackInput, UpdatePackInput},
|
||||
Create, Delete, FindById, FindByRef, PackRepository, PackTestRepository, Pagination, Update,
|
||||
Create, Delete, FindById, FindByRef, PackRepository, PackTestRepository, Pagination, Patch,
|
||||
Update,
|
||||
};
|
||||
use attune_common::workflow::{PackWorkflowService, PackWorkflowServiceConfig};
|
||||
|
||||
@@ -29,8 +30,9 @@ use crate::{
|
||||
BuildPackEnvsRequest, BuildPackEnvsResponse, CreatePackRequest, DownloadPacksRequest,
|
||||
DownloadPacksResponse, GetPackDependenciesRequest, GetPackDependenciesResponse,
|
||||
InstallPackRequest, PackInstallResponse, PackResponse, PackSummary,
|
||||
PackWorkflowSyncResponse, PackWorkflowValidationResponse, RegisterPackRequest,
|
||||
RegisterPacksRequest, RegisterPacksResponse, UpdatePackRequest, WorkflowSyncResult,
|
||||
PackDescriptionPatch, PackWorkflowSyncResponse, PackWorkflowValidationResponse,
|
||||
RegisterPackRequest, RegisterPacksRequest, RegisterPacksResponse, UpdatePackRequest,
|
||||
WorkflowSyncResult,
|
||||
},
|
||||
ApiResponse, SuccessResponse,
|
||||
},
|
||||
@@ -258,7 +260,10 @@ pub async fn update_pack(
|
||||
// Create update input
|
||||
let update_input = UpdatePackInput {
|
||||
label: request.label,
|
||||
description: request.description,
|
||||
description: request.description.map(|patch| match patch {
|
||||
PackDescriptionPatch::Set(value) => Patch::Set(value),
|
||||
PackDescriptionPatch::Clear => Patch::Clear,
|
||||
}),
|
||||
version: request.version,
|
||||
conf_schema: request.conf_schema,
|
||||
config: request.config,
|
||||
@@ -876,7 +881,10 @@ async fn register_pack_internal(
|
||||
// Update existing pack in place — preserves pack ID and all child entity IDs
|
||||
let update_input = UpdatePackInput {
|
||||
label: Some(label),
|
||||
description: Some(description.unwrap_or_default()),
|
||||
description: Some(match description {
|
||||
Some(value) => Patch::Set(value),
|
||||
None => Patch::Clear,
|
||||
}),
|
||||
version: Some(version.clone()),
|
||||
conf_schema: Some(conf_schema),
|
||||
config: None, // preserve user-set config
|
||||
|
||||
303
crates/api/src/routes/runtimes.rs
Normal file
303
crates/api/src/routes/runtimes.rs
Normal file
@@ -0,0 +1,303 @@
|
||||
//! Runtime management API routes
|
||||
|
||||
use axum::{
|
||||
extract::{Path, Query, State},
|
||||
http::StatusCode,
|
||||
response::IntoResponse,
|
||||
routing::get,
|
||||
Json, Router,
|
||||
};
|
||||
use std::sync::Arc;
|
||||
use validator::Validate;
|
||||
|
||||
use attune_common::repositories::{
|
||||
pack::PackRepository,
|
||||
runtime::{CreateRuntimeInput, RuntimeRepository, UpdateRuntimeInput},
|
||||
Create, Delete, FindByRef, List, Patch, Update,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
auth::middleware::RequireAuth,
|
||||
dto::{
|
||||
common::{PaginatedResponse, PaginationParams},
|
||||
runtime::{
|
||||
CreateRuntimeRequest, NullableJsonPatch, NullableStringPatch, RuntimeResponse,
|
||||
RuntimeSummary, UpdateRuntimeRequest,
|
||||
},
|
||||
ApiResponse, SuccessResponse,
|
||||
},
|
||||
middleware::{ApiError, ApiResult},
|
||||
state::AppState,
|
||||
};
|
||||
|
||||
#[utoipa::path(
|
||||
get,
|
||||
path = "/api/v1/runtimes",
|
||||
tag = "runtimes",
|
||||
params(PaginationParams),
|
||||
responses(
|
||||
(status = 200, description = "List of runtimes", body = PaginatedResponse<RuntimeSummary>)
|
||||
),
|
||||
security(("bearer_auth" = []))
|
||||
)]
|
||||
pub async fn list_runtimes(
|
||||
State(state): State<Arc<AppState>>,
|
||||
RequireAuth(_user): RequireAuth,
|
||||
Query(pagination): Query<PaginationParams>,
|
||||
) -> ApiResult<impl IntoResponse> {
|
||||
let all_runtimes = RuntimeRepository::list(&state.db).await?;
|
||||
let total = all_runtimes.len() as u64;
|
||||
let rows: Vec<_> = all_runtimes
|
||||
.into_iter()
|
||||
.skip(pagination.offset() as usize)
|
||||
.take(pagination.limit() as usize)
|
||||
.collect();
|
||||
|
||||
let response = PaginatedResponse::new(
|
||||
rows.into_iter().map(RuntimeSummary::from).collect(),
|
||||
&pagination,
|
||||
total,
|
||||
);
|
||||
|
||||
Ok((StatusCode::OK, Json(response)))
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
get,
|
||||
path = "/api/v1/packs/{pack_ref}/runtimes",
|
||||
tag = "runtimes",
|
||||
params(
|
||||
("pack_ref" = String, Path, description = "Pack reference identifier"),
|
||||
PaginationParams
|
||||
),
|
||||
responses(
|
||||
(status = 200, description = "List of runtimes for a pack", body = PaginatedResponse<RuntimeSummary>),
|
||||
(status = 404, description = "Pack not found")
|
||||
),
|
||||
security(("bearer_auth" = []))
|
||||
)]
|
||||
pub async fn list_runtimes_by_pack(
|
||||
State(state): State<Arc<AppState>>,
|
||||
RequireAuth(_user): RequireAuth,
|
||||
Path(pack_ref): Path<String>,
|
||||
Query(pagination): Query<PaginationParams>,
|
||||
) -> ApiResult<impl IntoResponse> {
|
||||
let pack = PackRepository::find_by_ref(&state.db, &pack_ref)
|
||||
.await?
|
||||
.ok_or_else(|| ApiError::NotFound(format!("Pack '{}' not found", pack_ref)))?;
|
||||
|
||||
let all_runtimes = RuntimeRepository::find_by_pack(&state.db, pack.id).await?;
|
||||
let total = all_runtimes.len() as u64;
|
||||
let rows: Vec<_> = all_runtimes
|
||||
.into_iter()
|
||||
.skip(pagination.offset() as usize)
|
||||
.take(pagination.limit() as usize)
|
||||
.collect();
|
||||
|
||||
let response = PaginatedResponse::new(
|
||||
rows.into_iter().map(RuntimeSummary::from).collect(),
|
||||
&pagination,
|
||||
total,
|
||||
);
|
||||
|
||||
Ok((StatusCode::OK, Json(response)))
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
get,
|
||||
path = "/api/v1/runtimes/{ref}",
|
||||
tag = "runtimes",
|
||||
params(("ref" = String, Path, description = "Runtime reference identifier")),
|
||||
responses(
|
||||
(status = 200, description = "Runtime details", body = ApiResponse<RuntimeResponse>),
|
||||
(status = 404, description = "Runtime not found")
|
||||
),
|
||||
security(("bearer_auth" = []))
|
||||
)]
|
||||
pub async fn get_runtime(
|
||||
State(state): State<Arc<AppState>>,
|
||||
RequireAuth(_user): RequireAuth,
|
||||
Path(runtime_ref): Path<String>,
|
||||
) -> ApiResult<impl IntoResponse> {
|
||||
let runtime = RuntimeRepository::find_by_ref(&state.db, &runtime_ref)
|
||||
.await?
|
||||
.ok_or_else(|| ApiError::NotFound(format!("Runtime '{}' not found", runtime_ref)))?;
|
||||
|
||||
Ok((
|
||||
StatusCode::OK,
|
||||
Json(ApiResponse::new(RuntimeResponse::from(runtime))),
|
||||
))
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
post,
|
||||
path = "/api/v1/runtimes",
|
||||
tag = "runtimes",
|
||||
request_body = CreateRuntimeRequest,
|
||||
responses(
|
||||
(status = 201, description = "Runtime created successfully", body = ApiResponse<RuntimeResponse>),
|
||||
(status = 400, description = "Validation error"),
|
||||
(status = 404, description = "Pack not found"),
|
||||
(status = 409, description = "Runtime with same ref already exists")
|
||||
),
|
||||
security(("bearer_auth" = []))
|
||||
)]
|
||||
pub async fn create_runtime(
|
||||
State(state): State<Arc<AppState>>,
|
||||
RequireAuth(_user): RequireAuth,
|
||||
Json(request): Json<CreateRuntimeRequest>,
|
||||
) -> ApiResult<impl IntoResponse> {
|
||||
request.validate()?;
|
||||
|
||||
if RuntimeRepository::find_by_ref(&state.db, &request.r#ref)
|
||||
.await?
|
||||
.is_some()
|
||||
{
|
||||
return Err(ApiError::Conflict(format!(
|
||||
"Runtime with ref '{}' already exists",
|
||||
request.r#ref
|
||||
)));
|
||||
}
|
||||
|
||||
let (pack_id, pack_ref) = if let Some(ref pack_ref_str) = request.pack_ref {
|
||||
let pack = PackRepository::find_by_ref(&state.db, pack_ref_str)
|
||||
.await?
|
||||
.ok_or_else(|| ApiError::NotFound(format!("Pack '{}' not found", pack_ref_str)))?;
|
||||
(Some(pack.id), Some(pack.r#ref))
|
||||
} else {
|
||||
(None, None)
|
||||
};
|
||||
|
||||
let runtime = RuntimeRepository::create(
|
||||
&state.db,
|
||||
CreateRuntimeInput {
|
||||
r#ref: request.r#ref,
|
||||
pack: pack_id,
|
||||
pack_ref,
|
||||
description: request.description,
|
||||
name: request.name,
|
||||
distributions: request.distributions,
|
||||
installation: request.installation,
|
||||
execution_config: request.execution_config,
|
||||
},
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok((
|
||||
StatusCode::CREATED,
|
||||
Json(ApiResponse::with_message(
|
||||
RuntimeResponse::from(runtime),
|
||||
"Runtime created successfully",
|
||||
)),
|
||||
))
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
put,
|
||||
path = "/api/v1/runtimes/{ref}",
|
||||
tag = "runtimes",
|
||||
params(("ref" = String, Path, description = "Runtime reference identifier")),
|
||||
request_body = UpdateRuntimeRequest,
|
||||
responses(
|
||||
(status = 200, description = "Runtime updated successfully", body = ApiResponse<RuntimeResponse>),
|
||||
(status = 400, description = "Validation error"),
|
||||
(status = 404, description = "Runtime not found")
|
||||
),
|
||||
security(("bearer_auth" = []))
|
||||
)]
|
||||
pub async fn update_runtime(
|
||||
State(state): State<Arc<AppState>>,
|
||||
RequireAuth(_user): RequireAuth,
|
||||
Path(runtime_ref): Path<String>,
|
||||
Json(request): Json<UpdateRuntimeRequest>,
|
||||
) -> ApiResult<impl IntoResponse> {
|
||||
request.validate()?;
|
||||
|
||||
let existing_runtime = RuntimeRepository::find_by_ref(&state.db, &runtime_ref)
|
||||
.await?
|
||||
.ok_or_else(|| ApiError::NotFound(format!("Runtime '{}' not found", runtime_ref)))?;
|
||||
|
||||
let runtime = RuntimeRepository::update(
|
||||
&state.db,
|
||||
existing_runtime.id,
|
||||
UpdateRuntimeInput {
|
||||
description: request.description.map(|patch| match patch {
|
||||
NullableStringPatch::Set(value) => Patch::Set(value),
|
||||
NullableStringPatch::Clear => Patch::Clear,
|
||||
}),
|
||||
name: request.name,
|
||||
distributions: request.distributions,
|
||||
installation: request.installation.map(|patch| match patch {
|
||||
NullableJsonPatch::Set(value) => Patch::Set(value),
|
||||
NullableJsonPatch::Clear => Patch::Clear,
|
||||
}),
|
||||
execution_config: request.execution_config,
|
||||
},
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok((
|
||||
StatusCode::OK,
|
||||
Json(ApiResponse::with_message(
|
||||
RuntimeResponse::from(runtime),
|
||||
"Runtime updated successfully",
|
||||
)),
|
||||
))
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
delete,
|
||||
path = "/api/v1/runtimes/{ref}",
|
||||
tag = "runtimes",
|
||||
params(("ref" = String, Path, description = "Runtime reference identifier")),
|
||||
responses(
|
||||
(status = 200, description = "Runtime deleted successfully", body = SuccessResponse),
|
||||
(status = 404, description = "Runtime not found")
|
||||
),
|
||||
security(("bearer_auth" = []))
|
||||
)]
|
||||
pub async fn delete_runtime(
|
||||
State(state): State<Arc<AppState>>,
|
||||
RequireAuth(_user): RequireAuth,
|
||||
Path(runtime_ref): Path<String>,
|
||||
) -> ApiResult<impl IntoResponse> {
|
||||
let runtime = RuntimeRepository::find_by_ref(&state.db, &runtime_ref)
|
||||
.await?
|
||||
.ok_or_else(|| ApiError::NotFound(format!("Runtime '{}' not found", runtime_ref)))?;
|
||||
|
||||
let deleted = RuntimeRepository::delete(&state.db, runtime.id).await?;
|
||||
if !deleted {
|
||||
return Err(ApiError::NotFound(format!(
|
||||
"Runtime '{}' not found",
|
||||
runtime_ref
|
||||
)));
|
||||
}
|
||||
|
||||
Ok((
|
||||
StatusCode::OK,
|
||||
Json(SuccessResponse::new(format!(
|
||||
"Runtime '{}' deleted successfully",
|
||||
runtime_ref
|
||||
))),
|
||||
))
|
||||
}
|
||||
|
||||
pub fn routes() -> Router<Arc<AppState>> {
|
||||
Router::new()
|
||||
.route("/runtimes", get(list_runtimes).post(create_runtime))
|
||||
.route(
|
||||
"/runtimes/{ref}",
|
||||
get(get_runtime).put(update_runtime).delete(delete_runtime),
|
||||
)
|
||||
.route("/packs/{pack_ref}/runtimes", get(list_runtimes_by_pack))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_runtime_routes_structure() {
|
||||
let _router = routes();
|
||||
}
|
||||
}
|
||||
@@ -17,7 +17,7 @@ use attune_common::repositories::{
|
||||
CreateSensorInput, CreateTriggerInput, SensorRepository, SensorSearchFilters,
|
||||
TriggerRepository, TriggerSearchFilters, UpdateSensorInput, UpdateTriggerInput,
|
||||
},
|
||||
Create, Delete, FindByRef, Update,
|
||||
Create, Delete, FindByRef, Patch, Update,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
@@ -25,8 +25,9 @@ use crate::{
|
||||
dto::{
|
||||
common::{PaginatedResponse, PaginationParams},
|
||||
trigger::{
|
||||
CreateSensorRequest, CreateTriggerRequest, SensorResponse, SensorSummary,
|
||||
TriggerResponse, TriggerSummary, UpdateSensorRequest, UpdateTriggerRequest,
|
||||
CreateSensorRequest, CreateTriggerRequest, SensorJsonPatch, SensorResponse,
|
||||
SensorSummary, TriggerJsonPatch, TriggerResponse, TriggerStringPatch,
|
||||
TriggerSummary, UpdateSensorRequest, UpdateTriggerRequest,
|
||||
},
|
||||
ApiResponse, SuccessResponse,
|
||||
},
|
||||
@@ -274,10 +275,19 @@ pub async fn update_trigger(
|
||||
// Create update input
|
||||
let update_input = UpdateTriggerInput {
|
||||
label: request.label,
|
||||
description: request.description,
|
||||
description: request.description.map(|patch| match patch {
|
||||
TriggerStringPatch::Set(value) => Patch::Set(value),
|
||||
TriggerStringPatch::Clear => Patch::Clear,
|
||||
}),
|
||||
enabled: request.enabled,
|
||||
param_schema: request.param_schema,
|
||||
out_schema: request.out_schema,
|
||||
param_schema: request.param_schema.map(|patch| match patch {
|
||||
TriggerJsonPatch::Set(value) => Patch::Set(value),
|
||||
TriggerJsonPatch::Clear => Patch::Clear,
|
||||
}),
|
||||
out_schema: request.out_schema.map(|patch| match patch {
|
||||
TriggerJsonPatch::Set(value) => Patch::Set(value),
|
||||
TriggerJsonPatch::Clear => Patch::Clear,
|
||||
}),
|
||||
};
|
||||
|
||||
let trigger = TriggerRepository::update(&state.db, existing_trigger.id, update_input).await?;
|
||||
@@ -722,7 +732,10 @@ pub async fn update_sensor(
|
||||
trigger: None,
|
||||
trigger_ref: None,
|
||||
enabled: request.enabled,
|
||||
param_schema: request.param_schema,
|
||||
param_schema: request.param_schema.map(|patch| match patch {
|
||||
SensorJsonPatch::Set(value) => Patch::Set(value),
|
||||
SensorJsonPatch::Clear => Patch::Clear,
|
||||
}),
|
||||
config: None,
|
||||
};
|
||||
|
||||
|
||||
@@ -47,6 +47,7 @@ impl Server {
|
||||
let api_v1 = Router::new()
|
||||
.merge(routes::pack_routes())
|
||||
.merge(routes::action_routes())
|
||||
.merge(routes::runtime_routes())
|
||||
.merge(routes::rule_routes())
|
||||
.merge(routes::execution_routes())
|
||||
.merge(routes::trigger_routes())
|
||||
|
||||
Reference in New Issue
Block a user