Split admin import and runtime safety modules
CI / Rust Checks (push) Failing after 40s
CI / UI Checks (push) Has been skipped
CI / Frontend E2E (push) Has been skipped
CI / Deployment Manifests (push) Has been skipped
Deploy / deploy (push) Successful in 1m29s

This commit is contained in:
github-ops
2026-06-21 01:48:27 +00:00
parent 5447e1bad0
commit 2dab9d666e
6 changed files with 464 additions and 426 deletions
+2 -124
View File
@@ -41,13 +41,14 @@ use time::{OffsetDateTime, format_description::well_known::Rfc3339};
use tracing::{info, instrument};
use uuid::Uuid;
mod import_export;
use crate::{
auth::{
AuthSettings, AuthenticatedSession, SessionCookie, create_session_cookie, hash_password,
hash_session_secret, verify_password,
},
error::ApiError,
import_guidance::import_guidance_warnings,
storage::LocalArtifactStorage,
};
@@ -2625,129 +2626,6 @@ impl AdminService {
Ok(())
}
#[instrument(skip(self), fields(operation_id = %operation_id.as_str(), version = query.version.unwrap_or_default(), mode = ?query.mode))]
pub async fn export_operation(
&self,
workspace_id: &WorkspaceId,
operation_id: &OperationId,
query: ExportQuery,
) -> Result<String, ApiError> {
let version = match query.version {
Some(version) => version,
None => {
self.get_operation(workspace_id, operation_id)
.await?
.current_draft_version
}
};
let record = self
.get_operation_version(workspace_id, operation_id, version)
.await?;
let document = YamlOperationDocument {
format_version: "1".to_owned(),
kind: "operation".to_owned(),
operation: RegistryOperation {
config_export: Some(ConfigExport {
format_version: "1".to_owned(),
export_mode: query.mode,
}),
..record.snapshot
},
};
serde_yaml::to_string(&document).map_err(|error| ApiError::internal(error.to_string()))
}
#[instrument(skip(self, yaml_document), fields(mode = ?query.mode))]
pub async fn import_operation(
&self,
workspace_id: &WorkspaceId,
query: ImportQuery,
yaml_document: &str,
) -> Result<ImportResponse, ApiError> {
let document: YamlOperationDocument = serde_yaml::from_str(yaml_document)
.map_err(|error| ApiError::validation(error.to_string()))?;
if document.kind != "operation" {
return Err(ApiError::validation("yaml kind must be operation"));
}
let payload = OperationPayload {
name: document.operation.name.clone(),
display_name: document.operation.display_name.clone(),
category: document.operation.category.clone(),
protocol: document.operation.protocol,
security_level: document.operation.security_level,
target: document.operation.target.clone(),
input_schema: document.operation.input_schema.clone(),
output_schema: document.operation.output_schema.clone(),
input_mapping: document.operation.input_mapping.clone(),
output_mapping: document.operation.output_mapping.clone(),
execution_config: document.operation.execution_config.clone(),
tool_description: document.operation.tool_description.clone(),
wizard_state: document.operation.wizard_state.clone(),
};
let warnings = import_guidance_warnings(&document.operation);
match query.mode {
ImportMode::Create => {
let created = self.create_operation(workspace_id, payload).await?;
Ok(ImportResponse {
operation_id: created.operation_id,
workspace_id: created.workspace_id,
version: created.version,
import_mode: ImportMode::Create,
warnings,
})
}
ImportMode::Upsert => {
if let Some(existing) = self
.find_operation_by_name(workspace_id, &document.operation.name)
.await?
{
let created = self
.create_version(
workspace_id,
&existing.id,
NewVersionPayload {
operation: payload,
change_note: Some("yaml upsert".to_owned()),
},
)
.await?;
let response = ImportResponse {
operation_id: created.operation_id,
workspace_id: created.workspace_id,
version: created.version,
import_mode: ImportMode::Upsert,
warnings,
};
info!(
operation_id = %response.operation_id,
version = response.version,
"operation imported by upsert"
);
Ok(response)
} else {
let created = self.create_operation(workspace_id, payload).await?;
let response = ImportResponse {
operation_id: created.operation_id,
workspace_id: created.workspace_id,
version: created.version,
import_mode: ImportMode::Upsert,
warnings,
};
info!(
operation_id = %response.operation_id,
version = response.version,
"operation imported by upsert"
);
Ok(response)
}
}
}
}
#[instrument(skip(self, payload), fields(operation_id = %operation_id.as_str(), sample_kind = ?sample_kind))]
pub async fn save_json_sample(
&self,
+137
View File
@@ -0,0 +1,137 @@
use crank_core::{ConfigExport, WorkspaceId};
use crank_registry::RegistryOperation;
use tracing::{info, instrument};
use crate::{
error::ApiError,
import_guidance::import_guidance_warnings,
service::{
AdminService, ExportQuery, ImportMode, ImportQuery, ImportResponse, NewVersionPayload,
OperationPayload, YamlOperationDocument,
},
};
impl AdminService {
#[instrument(skip(self), fields(operation_id = %operation_id.as_str(), version = query.version.unwrap_or_default(), mode = ?query.mode))]
pub async fn export_operation(
&self,
workspace_id: &WorkspaceId,
operation_id: &crank_core::OperationId,
query: ExportQuery,
) -> Result<String, ApiError> {
let version = match query.version {
Some(version) => version,
None => {
self.get_operation(workspace_id, operation_id)
.await?
.current_draft_version
}
};
let record = self
.get_operation_version(workspace_id, operation_id, version)
.await?;
let document = YamlOperationDocument {
format_version: "1".to_owned(),
kind: "operation".to_owned(),
operation: RegistryOperation {
config_export: Some(ConfigExport {
format_version: "1".to_owned(),
export_mode: query.mode,
}),
..record.snapshot
},
};
serde_yaml::to_string(&document).map_err(|error| ApiError::internal(error.to_string()))
}
#[instrument(skip(self, yaml_document), fields(mode = ?query.mode))]
pub async fn import_operation(
&self,
workspace_id: &WorkspaceId,
query: ImportQuery,
yaml_document: &str,
) -> Result<ImportResponse, ApiError> {
let document: YamlOperationDocument = serde_yaml::from_str(yaml_document)
.map_err(|error| ApiError::validation(error.to_string()))?;
if document.kind != "operation" {
return Err(ApiError::validation("yaml kind must be operation"));
}
let payload = OperationPayload {
name: document.operation.name.clone(),
display_name: document.operation.display_name.clone(),
category: document.operation.category.clone(),
protocol: document.operation.protocol,
security_level: document.operation.security_level,
target: document.operation.target.clone(),
input_schema: document.operation.input_schema.clone(),
output_schema: document.operation.output_schema.clone(),
input_mapping: document.operation.input_mapping.clone(),
output_mapping: document.operation.output_mapping.clone(),
execution_config: document.operation.execution_config.clone(),
tool_description: document.operation.tool_description.clone(),
wizard_state: document.operation.wizard_state.clone(),
};
let warnings = import_guidance_warnings(&document.operation);
match query.mode {
ImportMode::Create => {
let created = self.create_operation(workspace_id, payload).await?;
Ok(ImportResponse {
operation_id: created.operation_id,
workspace_id: created.workspace_id,
version: created.version,
import_mode: ImportMode::Create,
warnings,
})
}
ImportMode::Upsert => {
if let Some(existing) = self
.find_operation_by_name(workspace_id, &document.operation.name)
.await?
{
let created = self
.create_version(
workspace_id,
&existing.id,
NewVersionPayload {
operation: payload,
change_note: Some("yaml upsert".to_owned()),
},
)
.await?;
let response = ImportResponse {
operation_id: created.operation_id,
workspace_id: created.workspace_id,
version: created.version,
import_mode: ImportMode::Upsert,
warnings,
};
info!(
operation_id = %response.operation_id,
version = response.version,
"operation imported by upsert"
);
Ok(response)
} else {
let created = self.create_operation(workspace_id, payload).await?;
let response = ImportResponse {
operation_id: created.operation_id,
workspace_id: created.workspace_id,
version: created.version,
import_mode: ImportMode::Upsert,
warnings,
};
info!(
operation_id = %response.operation_id,
version = response.version,
"operation imported by upsert"
);
Ok(response)
}
}
}
}
}