Route source access through semantic helpers

This commit is contained in:
andrussal 2026-03-08 13:56:12 +01:00
parent 3ea3fffd1f
commit eeb0573798
6 changed files with 109 additions and 81 deletions

View File

@ -117,6 +117,16 @@ impl<E: Application, Caps> Scenario<E, Caps> {
&self.sources &self.sources
} }
#[must_use]
pub fn attached_source(&self) -> Option<&AttachSource> {
self.sources.attached_source()
}
#[must_use]
pub fn external_nodes(&self) -> &[ExternalNodeSource] {
self.sources.external_nodes()
}
#[must_use] #[must_use]
pub const fn source_orchestration_plan(&self) -> &SourceOrchestrationPlan { pub const fn source_orchestration_plan(&self) -> &SourceOrchestrationPlan {
&self.source_orchestration_plan &self.source_orchestration_plan

View File

@ -43,6 +43,38 @@ impl AttachSource {
services, services,
} }
} }
#[must_use]
pub fn compose_project(&self) -> Option<&str> {
match self {
Self::Compose { project, .. } => project.as_deref(),
Self::K8s { .. } => None,
}
}
#[must_use]
pub fn compose_services(&self) -> Option<&[String]> {
match self {
Self::Compose { services, .. } => Some(services),
Self::K8s { .. } => None,
}
}
#[must_use]
pub fn k8s_namespace(&self) -> Option<&str> {
match self {
Self::K8s { namespace, .. } => namespace.as_deref(),
Self::Compose { .. } => None,
}
}
#[must_use]
pub fn k8s_label_selector(&self) -> Option<&str> {
match self {
Self::K8s { label_selector, .. } => Some(label_selector),
Self::Compose { .. } => None,
}
}
} }
/// Static external node endpoint that should be included in the runtime /// Static external node endpoint that should be included in the runtime
@ -140,6 +172,14 @@ impl ScenarioSources {
Self::ExternalOnly { external } Self::ExternalOnly { external }
} }
#[must_use]
pub fn attached_source(&self) -> Option<&AttachSource> {
match self {
Self::Attached { attach, .. } => Some(attach),
Self::Managed { .. } | Self::ExternalOnly { .. } => None,
}
}
#[must_use] #[must_use]
pub fn external_nodes(&self) -> &[ExternalNodeSource] { pub fn external_nodes(&self) -> &[ExternalNodeSource] {
match self { match self {

View File

@ -87,14 +87,15 @@ fn to_discovery_error(source: DynError) -> AttachProviderError {
fn compose_attach_request( fn compose_attach_request(
source: &AttachSource, source: &AttachSource,
) -> Result<ComposeAttachRequest<'_>, AttachProviderError> { ) -> Result<ComposeAttachRequest<'_>, AttachProviderError> {
let AttachSource::Compose { project, services } = source else { let services =
return Err(AttachProviderError::UnsupportedSource { source
attach_source: source.clone(), .compose_services()
}); .ok_or_else(|| AttachProviderError::UnsupportedSource {
}; attach_source: source.clone(),
})?;
let project = project let project = source
.as_deref() .compose_project()
.ok_or_else(|| AttachProviderError::Discovery { .ok_or_else(|| AttachProviderError::Discovery {
source: ComposeAttachDiscoveryError::MissingProjectName.into(), source: ComposeAttachDiscoveryError::MissingProjectName.into(),
})?; })?;
@ -173,13 +174,12 @@ impl<E: ComposeDeployEnv> ClusterWaitHandle<E> for ComposeAttachedClusterWait<E>
} }
fn compose_wait_request(source: &AttachSource) -> Result<ComposeAttachRequest<'_>, DynError> { fn compose_wait_request(source: &AttachSource) -> Result<ComposeAttachRequest<'_>, DynError> {
let AttachSource::Compose { project, services } = source else { let project = source
return Err("compose cluster wait requires a compose attach source".into()); .compose_project()
}; .ok_or_else(|| DynError::from("compose cluster wait requires a compose attach source"))?;
let services = source
let project = project .compose_services()
.as_deref() .ok_or_else(|| DynError::from("compose cluster wait requires a compose attach source"))?;
.ok_or(ComposeAttachDiscoveryError::MissingProjectName)?;
Ok(ComposeAttachRequest { project, services }) Ok(ComposeAttachRequest { project, services })
} }

View File

@ -6,9 +6,9 @@ use testing_framework_core::{
ApplicationExternalProvider, AttachSource, CleanupGuard, ClusterWaitHandle, ApplicationExternalProvider, AttachSource, CleanupGuard, ClusterWaitHandle,
DeploymentPolicy, FeedHandle, FeedRuntime, HttpReadinessRequirement, Metrics, NodeClients, DeploymentPolicy, FeedHandle, FeedRuntime, HttpReadinessRequirement, Metrics, NodeClients,
NodeControlHandle, ObservabilityCapabilityProvider, ObservabilityInputs, NodeControlHandle, ObservabilityCapabilityProvider, ObservabilityInputs,
RequiresNodeControl, RunContext, Runner, Scenario, ScenarioSources, RequiresNodeControl, RunContext, Runner, Scenario, SourceOrchestrationPlan,
SourceOrchestrationPlan, SourceProviders, StaticManagedProvider, SourceProviders, StaticManagedProvider, build_source_orchestration_plan,
build_source_orchestration_plan, orchestrate_sources_with_providers, orchestrate_sources_with_providers,
}, },
topology::DeploymentDescriptor, topology::DeploymentDescriptor,
}; };
@ -214,21 +214,15 @@ impl<E: ComposeDeployEnv> DeploymentOrchestrator<E> {
return Ok(None); return Ok(None);
} }
let ScenarioSources::Attached { attach, .. } = scenario.sources() else { let attach = scenario
return Err(ComposeRunnerError::InternalInvariant { .attached_source()
.ok_or(ComposeRunnerError::InternalInvariant {
message: "attached node control requested outside attached source mode", message: "attached node control requested outside attached source mode",
}); })?;
};
let AttachSource::Compose { project, .. } = attach else { let Some(project_name) = attach
return Err(ComposeRunnerError::InternalInvariant { .compose_project()
message: "compose deployer requires compose attach source for node control", .map(str::trim)
});
};
let Some(project_name) = project
.as_ref()
.map(|value| value.trim())
.filter(|value| !value.is_empty()) .filter(|value| !value.is_empty())
else { else {
return Err(ComposeRunnerError::InternalInvariant { return Err(ComposeRunnerError::InternalInvariant {
@ -248,11 +242,11 @@ impl<E: ComposeDeployEnv> DeploymentOrchestrator<E> {
where where
Caps: Send + Sync, Caps: Send + Sync,
{ {
let ScenarioSources::Attached { attach, .. } = scenario.sources() else { let attach = scenario
return Err(ComposeRunnerError::InternalInvariant { .attached_source()
.ok_or(ComposeRunnerError::InternalInvariant {
message: "compose attached cluster wait requested outside attached source mode", message: "compose attached cluster wait requested outside attached source mode",
}); })?;
};
Ok(Arc::new(ComposeAttachedClusterWait::<E>::new( Ok(Arc::new(ComposeAttachedClusterWait::<E>::new(
compose_runner_host(), compose_runner_host(),
@ -378,13 +372,10 @@ where
E: ComposeDeployEnv, E: ComposeDeployEnv,
Caps: Send + Sync, Caps: Send + Sync,
{ {
let project_name = match scenario.sources() { let project_name = scenario
ScenarioSources::Attached { .attached_source()
attach: AttachSource::Compose { project, .. }, .and_then(|attach| attach.compose_project())
.. .map(ToOwned::to_owned);
} => project.clone(),
_ => None,
};
ComposeDeploymentMetadata { project_name } ComposeDeploymentMetadata { project_name }
} }

View File

@ -91,11 +91,7 @@ fn to_discovery_error(source: DynError) -> AttachProviderError {
} }
fn k8s_attach_request(source: &AttachSource) -> Result<K8sAttachRequest<'_>, AttachProviderError> { fn k8s_attach_request(source: &AttachSource) -> Result<K8sAttachRequest<'_>, AttachProviderError> {
let AttachSource::K8s { let Some(label_selector) = source.k8s_label_selector() else {
namespace,
label_selector,
} = source
else {
return Err(AttachProviderError::UnsupportedSource { return Err(AttachProviderError::UnsupportedSource {
attach_source: source.clone(), attach_source: source.clone(),
}); });
@ -108,7 +104,7 @@ fn k8s_attach_request(source: &AttachSource) -> Result<K8sAttachRequest<'_>, Att
} }
Ok(K8sAttachRequest { Ok(K8sAttachRequest {
namespace: namespace.as_deref().unwrap_or("default"), namespace: source.k8s_namespace().unwrap_or("default"),
label_selector, label_selector,
}) })
} }
@ -247,20 +243,16 @@ impl<E: K8sDeployEnv> ClusterWaitHandle<E> for K8sAttachedClusterWait<E> {
} }
fn k8s_wait_request(source: &AttachSource) -> Result<K8sAttachRequest<'_>, DynError> { fn k8s_wait_request(source: &AttachSource) -> Result<K8sAttachRequest<'_>, DynError> {
let AttachSource::K8s { let label_selector = source
namespace, .k8s_label_selector()
label_selector, .ok_or_else(|| DynError::from("k8s cluster wait requires a k8s attach source"))?;
} = source
else {
return Err("k8s cluster wait requires a k8s attach source".into());
};
if label_selector.trim().is_empty() { if label_selector.trim().is_empty() {
return Err(K8sAttachDiscoveryError::EmptyLabelSelector.into()); return Err(K8sAttachDiscoveryError::EmptyLabelSelector.into());
} }
Ok(K8sAttachRequest { Ok(K8sAttachRequest {
namespace: namespace.as_deref().unwrap_or("default"), namespace: source.k8s_namespace().unwrap_or("default"),
label_selector, label_selector,
}) })
} }

View File

@ -5,12 +5,11 @@ use kube::Client;
use reqwest::Url; use reqwest::Url;
use testing_framework_core::{ use testing_framework_core::{
scenario::{ scenario::{
Application, ApplicationExternalProvider, AttachSource, CleanupGuard, ClusterWaitHandle, Application, ApplicationExternalProvider, CleanupGuard, ClusterWaitHandle, Deployer,
Deployer, DynError, FeedHandle, FeedRuntime, HttpReadinessRequirement, Metrics, DynError, FeedHandle, FeedRuntime, HttpReadinessRequirement, Metrics, MetricsError,
MetricsError, NodeClients, ObservabilityCapabilityProvider, ObservabilityInputs, NodeClients, ObservabilityCapabilityProvider, ObservabilityInputs, RequiresNodeControl,
RequiresNodeControl, RunContext, Runner, Scenario, ScenarioSources, RunContext, Runner, Scenario, SourceOrchestrationPlan, SourceProviders,
SourceOrchestrationPlan, SourceProviders, StaticManagedProvider, StaticManagedProvider, build_source_orchestration_plan, orchestrate_sources_with_providers,
build_source_orchestration_plan, orchestrate_sources_with_providers,
}, },
topology::DeploymentDescriptor, topology::DeploymentDescriptor,
}; };
@ -250,22 +249,18 @@ where
E: K8sDeployEnv, E: K8sDeployEnv,
Caps: Send + Sync, Caps: Send + Sync,
{ {
match scenario.sources() { let namespace = scenario
ScenarioSources::Attached { .attached_source()
attach: .and_then(|attach| attach.k8s_namespace())
AttachSource::K8s { .map(ToOwned::to_owned);
namespace, let label_selector = scenario
label_selector, .attached_source()
}, .and_then(|attach| attach.k8s_label_selector())
.. .map(ToOwned::to_owned);
} => K8sDeploymentMetadata {
namespace: namespace.clone(), K8sDeploymentMetadata {
label_selector: Some(label_selector.clone()), namespace,
}, label_selector,
_ => K8sDeploymentMetadata {
namespace: None,
label_selector: None,
},
} }
} }
@ -277,11 +272,11 @@ where
E: K8sDeployEnv, E: K8sDeployEnv,
Caps: Send + Sync, Caps: Send + Sync,
{ {
let ScenarioSources::Attached { attach, .. } = scenario.sources() else { let attach = scenario
return Err(K8sRunnerError::InternalInvariant { .attached_source()
.ok_or_else(|| K8sRunnerError::InternalInvariant {
message: "k8s attached cluster wait requested outside attached source mode".to_owned(), message: "k8s attached cluster wait requested outside attached source mode".to_owned(),
}); })?;
};
Ok(Arc::new(K8sAttachedClusterWait::<E>::new( Ok(Arc::new(K8sAttachedClusterWait::<E>::new(
client, client,