Publish Clusterflux release candidate 6e83783
This commit is contained in:
parent
4d75257c6e
commit
f3640643bd
16 changed files with 4667 additions and 106 deletions
|
|
@ -3,10 +3,10 @@ use std::collections::BTreeMap;
|
|||
use clusterflux_core::{
|
||||
AgentId, AgentSignedRequest, ArtifactId, Authorization, Capability, CredentialKind,
|
||||
DataPlaneScope, Digest, DirectBulkTransferPlan, DownloadLink, EnvironmentRequirements,
|
||||
LimitKind, NodeCapabilities, NodeDescriptor, NodeEndpoint, NodeId, NodeSignedRequest,
|
||||
PanelEventKind, PanelState, Placement, ProcessId, ProjectId, ResourceLimits, SourcePreparation,
|
||||
SourceProviderKind, TaskBoundaryValue, TaskInstanceId, TaskJoinResult, TaskSpec, TenantId,
|
||||
UserId, VfsPath,
|
||||
LaunchAttemptId, LimitKind, NodeCapabilities, NodeDescriptor, NodeEndpoint, NodeId,
|
||||
NodeSignedRequest, PanelEventKind, PanelState, Placement, ProcessId, ProjectId, RequestId,
|
||||
ResourceLimits, SourcePreparation, SourceProviderKind, TaskBoundaryValue, TaskDefinitionId,
|
||||
TaskInstanceId, TaskJoinResult, TaskSpec, TenantId, UserId, VfsPath,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
|
|
@ -531,6 +531,13 @@ pub enum CoordinatorRequest {
|
|||
}
|
||||
|
||||
impl CoordinatorRequest {
|
||||
pub fn validate_external_identifiers(&self) -> Result<(), String> {
|
||||
let value = serde_json::to_value(self).map_err(|error| {
|
||||
format!("failed to validate coordinator request identifiers: {error}")
|
||||
})?;
|
||||
validate_identifier_tree(&value, "request")
|
||||
}
|
||||
|
||||
pub fn operation(&self) -> Result<String, String> {
|
||||
serde_json::to_value(self)
|
||||
.map_err(|err| format!("failed to encode coordinator request operation: {err}"))
|
||||
|
|
@ -538,6 +545,118 @@ impl CoordinatorRequest {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
enum ExternalIdentifierKind {
|
||||
Agent,
|
||||
Artifact,
|
||||
LaunchAttempt,
|
||||
Node,
|
||||
Process,
|
||||
Project,
|
||||
Request,
|
||||
TaskDefinition,
|
||||
TaskInstance,
|
||||
Tenant,
|
||||
User,
|
||||
}
|
||||
|
||||
fn identifier_kind(field: &str) -> Option<ExternalIdentifierKind> {
|
||||
match field {
|
||||
"tenant" | "target_tenant" => Some(ExternalIdentifierKind::Tenant),
|
||||
"project" => Some(ExternalIdentifierKind::Project),
|
||||
"actor_user" | "user" => Some(ExternalIdentifierKind::User),
|
||||
"agent" | "actor_agent" => Some(ExternalIdentifierKind::Agent),
|
||||
"node" | "prefer_node" | "receiver_node" => Some(ExternalIdentifierKind::Node),
|
||||
"process" => Some(ExternalIdentifierKind::Process),
|
||||
"task" | "parent_task" | "stopped_task" | "task_instance" => {
|
||||
Some(ExternalIdentifierKind::TaskInstance)
|
||||
}
|
||||
"task_definition" => Some(ExternalIdentifierKind::TaskDefinition),
|
||||
"artifact" | "required_artifacts" | "artifact_locations" => {
|
||||
Some(ExternalIdentifierKind::Artifact)
|
||||
}
|
||||
"launch_attempt" => Some(ExternalIdentifierKind::LaunchAttempt),
|
||||
"request_id" | "session_secret" | "transaction_id" | "polling_secret" | "transfer_id"
|
||||
| "enrollment_grant" | "widget_id" | "environment_id" | "admin_nonce" => {
|
||||
Some(ExternalIdentifierKind::Request)
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn validate_identifier_tree(value: &serde_json::Value, path: &str) -> Result<(), String> {
|
||||
match value {
|
||||
serde_json::Value::Object(fields) => {
|
||||
for (field, value) in fields {
|
||||
let field_path = format!("{path}.{field}");
|
||||
if let Some(kind) = identifier_kind(field) {
|
||||
validate_identifier_value(kind, value, &field_path)?;
|
||||
}
|
||||
validate_identifier_tree(value, &field_path)?;
|
||||
}
|
||||
}
|
||||
serde_json::Value::Array(values) => {
|
||||
for (index, value) in values.iter().enumerate() {
|
||||
validate_identifier_tree(value, &format!("{path}[{index}]"))?;
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn validate_identifier_value(
|
||||
kind: ExternalIdentifierKind,
|
||||
value: &serde_json::Value,
|
||||
path: &str,
|
||||
) -> Result<(), String> {
|
||||
if value.is_null() {
|
||||
return Ok(());
|
||||
}
|
||||
if let Some(values) = value.as_array() {
|
||||
for (index, value) in values.iter().enumerate() {
|
||||
validate_identifier_value(kind, value, &format!("{path}[{index}]"))?;
|
||||
}
|
||||
return Ok(());
|
||||
}
|
||||
let value = value
|
||||
.as_str()
|
||||
.ok_or_else(|| format!("external identifier {path} must be a string"))?
|
||||
.to_owned();
|
||||
let result = match kind {
|
||||
ExternalIdentifierKind::Agent => AgentId::try_new(value).map(|_| ()),
|
||||
ExternalIdentifierKind::Artifact => ArtifactId::try_new(value).map(|_| ()),
|
||||
ExternalIdentifierKind::LaunchAttempt => LaunchAttemptId::try_new(value).map(|_| ()),
|
||||
ExternalIdentifierKind::Node => NodeId::try_new(value).map(|_| ()),
|
||||
ExternalIdentifierKind::Process => ProcessId::try_new(value).map(|_| ()),
|
||||
ExternalIdentifierKind::Project => ProjectId::try_new(value).map(|_| ()),
|
||||
ExternalIdentifierKind::Request => RequestId::try_new(value).map(|_| ()),
|
||||
ExternalIdentifierKind::TaskDefinition => TaskDefinitionId::try_new(value).map(|_| ()),
|
||||
ExternalIdentifierKind::TaskInstance => TaskInstanceId::try_new(value).map(|_| ()),
|
||||
ExternalIdentifierKind::Tenant => TenantId::try_new(value).map(|_| ()),
|
||||
ExternalIdentifierKind::User => UserId::try_new(value).map(|_| ()),
|
||||
};
|
||||
result.map_err(|error| format!("malformed external identifier {path}: {error}"))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod external_identifier_tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn nested_authenticated_identifiers_are_validated() {
|
||||
let request = CoordinatorRequest::Authenticated {
|
||||
session_secret: "cli_session_valid".to_owned(),
|
||||
request: AuthenticatedCoordinatorRequest::AbortProcess {
|
||||
process: "bad process".to_owned(),
|
||||
launch_attempt: Some("attempt".to_owned()),
|
||||
},
|
||||
};
|
||||
let error = request.validate_external_identifiers().unwrap_err();
|
||||
assert!(error.contains("request.request.process"));
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(tag = "type", rename_all = "snake_case", deny_unknown_fields)]
|
||||
pub enum AuthenticatedCoordinatorRequest {
|
||||
|
|
|
|||
|
|
@ -5,6 +5,9 @@ impl CoordinatorService {
|
|||
&mut self,
|
||||
request: CoordinatorRequest,
|
||||
) -> Result<CoordinatorResponse, CoordinatorServiceError> {
|
||||
request
|
||||
.validate_external_identifiers()
|
||||
.map_err(CoordinatorServiceError::Protocol)?;
|
||||
self.pump_main_runtime_commands();
|
||||
let request_payload = serde_json::to_value(&request).map_err(|error| {
|
||||
CoordinatorServiceError::Protocol(format!(
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
use clusterflux_core::{COORDINATOR_PROTOCOL_VERSION, COORDINATOR_WIRE_REQUEST_TYPE};
|
||||
use clusterflux_core::{RequestId, COORDINATOR_PROTOCOL_VERSION, COORDINATOR_WIRE_REQUEST_TYPE};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::Value;
|
||||
|
||||
|
|
@ -44,9 +44,9 @@ impl CoordinatorRequestEnvelope {
|
|||
self.protocol_version, COORDINATOR_PROTOCOL_VERSION
|
||||
));
|
||||
}
|
||||
if self.request_id.trim().is_empty() {
|
||||
return Err("coordinator wire request_id must be non-empty".to_owned());
|
||||
}
|
||||
RequestId::try_new(self.request_id.clone())
|
||||
.map_err(|error| format!("malformed coordinator wire request_id: {error}"))?;
|
||||
self.payload.validate_external_identifiers()?;
|
||||
let payload_operation = self.payload.operation()?;
|
||||
if self.operation != payload_operation {
|
||||
return Err(format!(
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue