mirror of
https://github.com/rustfs/rustfs.git
synced 2026-08-08 22:33:22 +00:00
refactor(plugins): harden target-plugin system and admin plugin contract (#4217)
This commit is contained in:
@@ -550,7 +550,6 @@ mod tests {
|
||||
use rustfs_targets::store::{Key, Store};
|
||||
use rustfs_targets::target::{EntityTarget, QueuedPayload, QueuedPayloadMeta};
|
||||
use rustfs_targets::{StoreError, Target, TargetError};
|
||||
use serde::{Serialize, de::DeserializeOwned};
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||
@@ -574,7 +573,7 @@ mod tests {
|
||||
#[async_trait]
|
||||
impl<E> Target<E> for TestTarget
|
||||
where
|
||||
E: Send + Sync + 'static + Clone + Serialize + DeserializeOwned,
|
||||
E: rustfs_targets::PluginEvent,
|
||||
{
|
||||
fn id(&self) -> TargetID {
|
||||
self.id.clone()
|
||||
|
||||
@@ -78,6 +78,8 @@ pub struct ExtensionSchema {
|
||||
pub disabled_by_default: bool,
|
||||
}
|
||||
|
||||
/// Every supported hook point runs strictly after authentication and
|
||||
/// authorization; pre-auth hook points are intentionally not representable.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum S3HookPoint {
|
||||
@@ -87,12 +89,6 @@ pub enum S3HookPoint {
|
||||
PostAuthListObjects,
|
||||
}
|
||||
|
||||
impl S3HookPoint {
|
||||
pub const fn is_post_auth(self) -> bool {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct S3HookContract {
|
||||
@@ -693,7 +689,6 @@ mod tests {
|
||||
bypasses_iam: false,
|
||||
};
|
||||
|
||||
assert!(contract.hook_points.iter().all(|hook_point| hook_point.is_post_auth()));
|
||||
assert!(validate_s3_hook_contract(&contract).is_ok());
|
||||
assert_eq!(S3_POST_AUTH_HOOK_CAPABILITY, "s3.hook.post_auth.v1");
|
||||
}
|
||||
|
||||
@@ -420,7 +420,6 @@ mod tests {
|
||||
store::{Key, Store},
|
||||
target::{EntityTarget, QueuedPayload, QueuedPayloadMeta},
|
||||
};
|
||||
use serde::{Serialize, de::DeserializeOwned};
|
||||
use std::sync::{
|
||||
Arc,
|
||||
atomic::{AtomicUsize, Ordering},
|
||||
@@ -502,7 +501,7 @@ mod tests {
|
||||
#[async_trait]
|
||||
impl<E> Target<E> for TestTarget
|
||||
where
|
||||
E: Send + Sync + 'static + Clone + Serialize + DeserializeOwned,
|
||||
E: rustfs_targets::PluginEvent,
|
||||
{
|
||||
fn id(&self) -> TargetID {
|
||||
self.id.clone()
|
||||
|
||||
@@ -177,7 +177,6 @@ mod tests {
|
||||
use rustfs_targets::store::{Key, Store};
|
||||
use rustfs_targets::target::{EntityTarget, QueuedPayload, QueuedPayloadMeta};
|
||||
use rustfs_targets::{ReplayWorkerManager, SharedTarget, StoreError, Target, TargetError};
|
||||
use serde::{Serialize, de::DeserializeOwned};
|
||||
use std::sync::Arc;
|
||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||
use tokio::sync::{RwLock, Semaphore};
|
||||
@@ -200,7 +199,7 @@ mod tests {
|
||||
#[async_trait]
|
||||
impl<E> Target<E> for TestTarget
|
||||
where
|
||||
E: Send + Sync + 'static + Clone + Serialize + DeserializeOwned,
|
||||
E: rustfs_targets::PluginEvent,
|
||||
{
|
||||
fn id(&self) -> TargetID {
|
||||
self.id.clone()
|
||||
|
||||
@@ -79,7 +79,6 @@ mod tests {
|
||||
use rustfs_targets::store::{Key, Store};
|
||||
use rustfs_targets::target::{EntityTarget, QueuedPayload, QueuedPayloadMeta, TargetDeliverySnapshot};
|
||||
use rustfs_targets::{ReplayWorkerManager, StoreError, Target, TargetError};
|
||||
use serde::{Serialize, de::DeserializeOwned};
|
||||
use std::sync::{
|
||||
Arc,
|
||||
atomic::{AtomicU64, Ordering},
|
||||
@@ -128,7 +127,7 @@ mod tests {
|
||||
#[async_trait]
|
||||
impl<E> Target<E> for TestTarget
|
||||
where
|
||||
E: Send + Sync + 'static + Clone + Serialize + DeserializeOwned,
|
||||
E: rustfs_targets::PluginEvent,
|
||||
{
|
||||
fn id(&self) -> TargetID {
|
||||
self.id.clone()
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use crate::plugin::PluginEvent;
|
||||
use crate::plugin::{
|
||||
BuiltinTargetAdminDescriptor, BuiltinTargetDescriptor, TargetAdminMetadata, TargetPluginDescriptor, TargetRequestValidator,
|
||||
boxed_target,
|
||||
@@ -36,8 +37,6 @@ use rustfs_config::{
|
||||
AUDIT_POSTGRES_SUB_SYS, AUDIT_PULSAR_SUB_SYS, AUDIT_REDIS_SUB_SYS, AUDIT_WEBHOOK_SUB_SYS,
|
||||
},
|
||||
};
|
||||
use serde::Serialize;
|
||||
use serde::de::DeserializeOwned;
|
||||
|
||||
use crate::config::{
|
||||
build_amqp_args, build_kafka_args, build_mqtt_args, build_mysql_args, build_nats_args, build_postgres_args,
|
||||
@@ -57,7 +56,7 @@ fn build_descriptor<E, Create, Validate>(
|
||||
create_target: Create,
|
||||
) -> BuiltinTargetDescriptor<E>
|
||||
where
|
||||
E: Send + Sync + 'static + Clone + Serialize + DeserializeOwned,
|
||||
E: PluginEvent,
|
||||
Create: Fn(String, &KVS) -> Result<BoxedTarget<E>, TargetError> + Send + Sync + 'static,
|
||||
Validate: Fn(&KVS) -> Result<(), TargetError> + Send + Sync + 'static,
|
||||
{
|
||||
@@ -145,7 +144,7 @@ pub fn builtin_audit_target_admin_descriptors() -> Vec<BuiltinTargetAdminDescrip
|
||||
|
||||
pub fn builtin_audit_target_descriptors<E>() -> Vec<BuiltinTargetDescriptor<E>>
|
||||
where
|
||||
E: Send + Sync + 'static + Clone + Serialize + DeserializeOwned,
|
||||
E: PluginEvent,
|
||||
{
|
||||
vec![
|
||||
build_descriptor(
|
||||
@@ -317,7 +316,7 @@ pub fn builtin_notify_target_admin_descriptors() -> Vec<BuiltinTargetAdminDescri
|
||||
|
||||
pub fn builtin_notify_target_descriptors<E>() -> Vec<BuiltinTargetDescriptor<E>>
|
||||
where
|
||||
E: Send + Sync + 'static + Clone + Serialize + DeserializeOwned,
|
||||
E: PluginEvent,
|
||||
{
|
||||
vec![
|
||||
build_descriptor(
|
||||
|
||||
@@ -341,7 +341,6 @@ mod tests {
|
||||
S3HookPoint::PostAuthListObjects,
|
||||
]
|
||||
);
|
||||
assert!(contract.hook_points.iter().all(|hook_point| hook_point.is_post_auth()));
|
||||
assert!(!contract.mutates_object_data);
|
||||
assert!(!contract.bypasses_iam);
|
||||
assert!(validate_s3_hook_contract(&contract).is_ok());
|
||||
|
||||
@@ -33,6 +33,14 @@ pub struct ExampleInstallableTargetPlugin {
|
||||
pub valid_fields: Vec<String>,
|
||||
}
|
||||
|
||||
/// Test/demo fixture describing what an installable external plugin would
|
||||
/// look like. It must never be exposed through production admin responses;
|
||||
/// it exists so control-plane planning and contract tests can exercise the
|
||||
/// external-plugin shape before a real marketplace source exists.
|
||||
/// Test/demo fixture describing what an installable external plugin would
|
||||
/// look like. It must never be exposed through production admin responses;
|
||||
/// it exists so control-plane planning and contract tests can exercise the
|
||||
/// external-plugin shape before a real marketplace source exists.
|
||||
pub fn example_external_webhook_plugin() -> ExampleInstallableTargetPlugin {
|
||||
let base = TargetPluginManifest {
|
||||
plugin_id: "external:webhook-sidecar",
|
||||
@@ -55,7 +63,7 @@ pub fn example_external_webhook_plugin() -> ExampleInstallableTargetPlugin {
|
||||
artifact_id: "sidecar-linux-amd64",
|
||||
target_triple: "x86_64-unknown-linux-gnu",
|
||||
download_uri: "https://plugins.example.test/webhook-sidecar.tar.zst",
|
||||
digest_sha256: "0123456789abcdef0123456789abcdef",
|
||||
digest_sha256: "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef",
|
||||
signature_uri: "https://plugins.example.test/webhook-sidecar.tar.zst.sig",
|
||||
provenance_uri: "https://plugins.example.test/webhook-sidecar.tar.zst.intoto.jsonl",
|
||||
size_bytes: 8192,
|
||||
@@ -88,7 +96,7 @@ pub fn example_external_webhook_plugin() -> ExampleInstallableTargetPlugin {
|
||||
manifest,
|
||||
installation: external_target_plugin_installation(
|
||||
base.version,
|
||||
"0123456789abcdef0123456789abcdef",
|
||||
"0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef",
|
||||
"sidecar-linux-amd64",
|
||||
Some("2026-05-13T20:00:00Z".to_string()),
|
||||
),
|
||||
|
||||
@@ -22,6 +22,8 @@ use serde::{Deserialize, Serialize};
|
||||
use thiserror::Error;
|
||||
use url::Url;
|
||||
|
||||
const SHA256_HEX_DIGEST_LEN: usize = 64;
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum TargetPluginInstallState {
|
||||
@@ -274,8 +276,8 @@ pub enum TargetPluginExternalActionError {
|
||||
#[error("external plugin circuit breaker is open")]
|
||||
CircuitBreakerOpen,
|
||||
|
||||
#[error("external plugin {plugin_id} has no installable artifact")]
|
||||
MissingArtifact { plugin_id: String },
|
||||
#[error("external plugin {plugin_id} has no installable artifact for host target triple {target_triple}")]
|
||||
MissingArtifactForHost { plugin_id: String, target_triple: String },
|
||||
}
|
||||
|
||||
pub fn plan_external_target_plugin_action(
|
||||
@@ -283,12 +285,13 @@ pub fn plan_external_target_plugin_action(
|
||||
action: TargetPluginExternalAction,
|
||||
installation: &TargetPluginInstallation,
|
||||
gate: &TargetPluginExternalFlowGate,
|
||||
host_target_triple: &str,
|
||||
) -> Result<TargetPluginExternalActionDecision, TargetPluginExternalActionError> {
|
||||
validate_external_action_subject(manifest)?;
|
||||
validate_external_action_gate(gate)?;
|
||||
|
||||
match action {
|
||||
TargetPluginExternalAction::Install => plan_external_install(manifest, action, gate),
|
||||
TargetPluginExternalAction::Install => plan_external_install(manifest, action, gate, host_target_triple),
|
||||
TargetPluginExternalAction::Enable => {
|
||||
require_installed(manifest.plugin_id, installation)?;
|
||||
Ok(TargetPluginExternalActionDecision {
|
||||
@@ -332,7 +335,9 @@ impl Default for TargetPluginInstallPolicy {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
allowed_providers: vec!["rustfs".to_string(), "rustfs-labs".to_string()],
|
||||
allowed_download_hosts: vec!["plugins.example.test".to_string()],
|
||||
// Deny-by-default: operators must explicitly allow the hosts
|
||||
// artifacts may be downloaded from before any install can plan.
|
||||
allowed_download_hosts: Vec::new(),
|
||||
require_https: true,
|
||||
require_signature: true,
|
||||
require_provenance: true,
|
||||
@@ -382,10 +387,12 @@ pub fn validate_external_plugin_installation(
|
||||
if artifact.size_bytes == 0 {
|
||||
return Err(format!("artifact {} must declare a non-zero size", artifact.artifact_id));
|
||||
}
|
||||
if artifact.digest_sha256.len() < 16 || !artifact.digest_sha256.chars().all(|ch| ch.is_ascii_hexdigit()) {
|
||||
if artifact.digest_sha256.len() != SHA256_HEX_DIGEST_LEN
|
||||
|| !artifact.digest_sha256.chars().all(|ch| ch.is_ascii_hexdigit())
|
||||
{
|
||||
return Err(format!(
|
||||
"artifact {} has invalid digest_sha256 {}",
|
||||
artifact.artifact_id, artifact.digest_sha256
|
||||
"artifact {} has invalid digest_sha256 {} (expected {} hex characters)",
|
||||
artifact.artifact_id, artifact.digest_sha256, SHA256_HEX_DIGEST_LEN
|
||||
));
|
||||
}
|
||||
if policy.require_signature && artifact.signature_uri.is_empty() {
|
||||
@@ -429,6 +436,7 @@ fn plan_external_install(
|
||||
manifest: &TargetPluginMarketplaceManifest,
|
||||
action: TargetPluginExternalAction,
|
||||
gate: &TargetPluginExternalFlowGate,
|
||||
host_target_triple: &str,
|
||||
) -> Result<TargetPluginExternalActionDecision, TargetPluginExternalActionError> {
|
||||
let install_manifest = TargetPluginManifest {
|
||||
plugin_id: manifest.plugin_id,
|
||||
@@ -449,9 +457,15 @@ fn plan_external_install(
|
||||
|
||||
let artifact = manifest
|
||||
.distribution
|
||||
.and_then(|distribution| distribution.artifacts.first())
|
||||
.ok_or_else(|| TargetPluginExternalActionError::MissingArtifact {
|
||||
.and_then(|distribution| {
|
||||
distribution
|
||||
.artifacts
|
||||
.iter()
|
||||
.find(|artifact| artifact.target_triple == host_target_triple)
|
||||
})
|
||||
.ok_or_else(|| TargetPluginExternalActionError::MissingArtifactForHost {
|
||||
plugin_id: manifest.plugin_id.to_string(),
|
||||
target_triple: host_target_triple.to_string(),
|
||||
})?;
|
||||
|
||||
Ok(TargetPluginExternalActionDecision {
|
||||
@@ -542,6 +556,24 @@ mod tests {
|
||||
use crate::{SidecarRuntimePolicy, SidecarRuntimeSafetyChecks};
|
||||
use std::time::Duration;
|
||||
|
||||
const TEST_HOST_TRIPLE: &str = "x86_64-unknown-linux-gnu";
|
||||
|
||||
fn policy_allowing_example_host() -> TargetPluginInstallPolicy {
|
||||
TargetPluginInstallPolicy {
|
||||
allowed_download_hosts: vec!["plugins.example.test".to_string()],
|
||||
..TargetPluginInstallPolicy::default()
|
||||
}
|
||||
}
|
||||
|
||||
fn verified_gate_with_example_host() -> TargetPluginExternalFlowGate {
|
||||
let mut gate = TargetPluginExternalFlowGate::verified(
|
||||
SidecarRuntimePolicy::verified_external(16, Duration::from_secs(5), 3),
|
||||
SidecarRuntimeSafetyChecks::verified(0),
|
||||
);
|
||||
gate.install_policy = policy_allowing_example_host();
|
||||
gate
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn builtin_installation_maps_to_virtual_installed_revision() {
|
||||
let installation = builtin_target_plugin_installation(&builtin_target_manifest("webhook"));
|
||||
@@ -656,6 +688,7 @@ mod tests {
|
||||
validation_error: None,
|
||||
},
|
||||
&gate,
|
||||
TEST_HOST_TRIPLE,
|
||||
);
|
||||
|
||||
assert_eq!(result, Err(TargetPluginExternalActionError::ExternalFlowDisabled));
|
||||
@@ -681,6 +714,7 @@ mod tests {
|
||||
validation_error: None,
|
||||
},
|
||||
&gate,
|
||||
TEST_HOST_TRIPLE,
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
@@ -697,7 +731,7 @@ mod tests {
|
||||
artifact_id: "sidecar-linux-amd64",
|
||||
target_triple: "x86_64-unknown-linux-gnu",
|
||||
download_uri: "https://plugins.example.test/webhook-sidecar.tar.zst",
|
||||
digest_sha256: "0123456789abcdef0123456789abcdef",
|
||||
digest_sha256: "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef",
|
||||
signature_uri: "https://plugins.example.test/webhook-sidecar.tar.zst.sig",
|
||||
provenance_uri: "",
|
||||
size_bytes: 8192,
|
||||
@@ -707,10 +741,7 @@ mod tests {
|
||||
example.manifest.distribution = Some(TargetPluginDistributionManifest {
|
||||
artifacts: MISSING_PROVENANCE_ARTIFACTS,
|
||||
});
|
||||
let gate = TargetPluginExternalFlowGate::verified(
|
||||
SidecarRuntimePolicy::verified_external(16, Duration::from_secs(5), 3),
|
||||
SidecarRuntimeSafetyChecks::verified(0),
|
||||
);
|
||||
let gate = verified_gate_with_example_host();
|
||||
|
||||
let result = plan_external_target_plugin_action(
|
||||
&example.manifest,
|
||||
@@ -722,6 +753,7 @@ mod tests {
|
||||
validation_error: None,
|
||||
},
|
||||
&gate,
|
||||
TEST_HOST_TRIPLE,
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
@@ -749,6 +781,7 @@ mod tests {
|
||||
TargetPluginExternalAction::Enable,
|
||||
&example.installation,
|
||||
&gate,
|
||||
TEST_HOST_TRIPLE,
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
@@ -773,6 +806,7 @@ mod tests {
|
||||
TargetPluginExternalAction::Enable,
|
||||
&example.installation,
|
||||
&gate,
|
||||
TEST_HOST_TRIPLE,
|
||||
);
|
||||
|
||||
assert_eq!(result, Err(TargetPluginExternalActionError::CircuitBreakerOpen));
|
||||
@@ -781,10 +815,7 @@ mod tests {
|
||||
#[test]
|
||||
fn external_actions_plan_install_disable_and_rollback_without_execution() {
|
||||
let example = example_external_webhook_plugin();
|
||||
let gate = TargetPluginExternalFlowGate::verified(
|
||||
SidecarRuntimePolicy::verified_external(16, Duration::from_secs(5), 3),
|
||||
SidecarRuntimeSafetyChecks::verified(0),
|
||||
);
|
||||
let gate = verified_gate_with_example_host();
|
||||
|
||||
let install = plan_external_target_plugin_action(
|
||||
&example.manifest,
|
||||
@@ -796,6 +827,7 @@ mod tests {
|
||||
validation_error: None,
|
||||
},
|
||||
&gate,
|
||||
TEST_HOST_TRIPLE,
|
||||
)
|
||||
.expect("verified external install action should plan");
|
||||
assert_eq!(install.installation.install_state, TargetPluginInstallState::Installed);
|
||||
@@ -807,6 +839,7 @@ mod tests {
|
||||
TargetPluginExternalAction::Disable,
|
||||
&example.installation,
|
||||
&gate,
|
||||
TEST_HOST_TRIPLE,
|
||||
)
|
||||
.expect("verified external disable action should plan");
|
||||
assert_eq!(disable.operational_state.enable_state, TargetPluginEnableState::Disabled);
|
||||
@@ -836,6 +869,7 @@ mod tests {
|
||||
validation_error: None,
|
||||
},
|
||||
&gate,
|
||||
TEST_HOST_TRIPLE,
|
||||
)
|
||||
.expect("verified external rollback action should plan");
|
||||
|
||||
@@ -860,13 +894,13 @@ mod tests {
|
||||
artifact_id: "sidecar-linux-amd64",
|
||||
target_triple: "x86_64-unknown-linux-gnu",
|
||||
download_uri: "https://plugins.example.test/webhook-sidecar.tar.zst",
|
||||
digest_sha256: "0123456789abcdef0123456789abcdef",
|
||||
digest_sha256: "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef",
|
||||
signature_uri: "https://plugins.example.test/webhook-sidecar.tar.zst.sig",
|
||||
provenance_uri: "https://plugins.example.test/webhook-sidecar.tar.zst.intoto.jsonl",
|
||||
size_bytes: 8192,
|
||||
}],
|
||||
};
|
||||
let policy = TargetPluginInstallPolicy::default();
|
||||
let policy = policy_allowing_example_host();
|
||||
|
||||
let result = validate_external_plugin_installation(
|
||||
&manifest,
|
||||
@@ -882,18 +916,56 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn validate_external_installation_rejects_disallowed_provider() {
|
||||
fn default_install_policy_denies_all_download_hosts() {
|
||||
let policy = TargetPluginInstallPolicy::default();
|
||||
assert!(policy.allowed_download_hosts.is_empty());
|
||||
|
||||
let manifest = TargetPluginManifest {
|
||||
plugin_id: "external:webhook-sidecar",
|
||||
display_name: "Webhook Sidecar",
|
||||
provider: "unknown-vendor",
|
||||
provider: "rustfs-labs",
|
||||
version: "1.0.0",
|
||||
target_type: "webhook",
|
||||
supported_domains: &[],
|
||||
secret_fields: &[],
|
||||
};
|
||||
let policy = TargetPluginInstallPolicy::default();
|
||||
let result = validate_external_plugin_installation(
|
||||
&manifest,
|
||||
&TargetPluginExternalRuntimeContract {
|
||||
protocol_version: crate::SIDECAR_RUNTIME_PROTOCOL_VERSION,
|
||||
transport: TargetPluginRuntimeTransport::Grpc,
|
||||
},
|
||||
Some(TargetPluginDistributionManifest {
|
||||
artifacts: &[TargetPluginArtifactManifest {
|
||||
artifact_id: "sidecar-linux-amd64",
|
||||
target_triple: "x86_64-unknown-linux-gnu",
|
||||
download_uri: "https://plugins.example.test/webhook-sidecar.tar.zst",
|
||||
digest_sha256: "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef",
|
||||
signature_uri: "https://plugins.example.test/webhook-sidecar.tar.zst.sig",
|
||||
provenance_uri: "https://plugins.example.test/webhook-sidecar.tar.zst.intoto.jsonl",
|
||||
size_bytes: 8192,
|
||||
}],
|
||||
}),
|
||||
&policy,
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
result.as_ref().map_err(String::as_str),
|
||||
Err("artifact sidecar-linux-amd64 download host plugins.example.test is not allowed")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn validate_external_installation_rejects_truncated_digest() {
|
||||
let manifest = TargetPluginManifest {
|
||||
plugin_id: "external:webhook-sidecar",
|
||||
display_name: "Webhook Sidecar",
|
||||
provider: "rustfs-labs",
|
||||
version: "1.0.0",
|
||||
target_type: "webhook",
|
||||
supported_domains: &[],
|
||||
secret_fields: &[],
|
||||
};
|
||||
let result = validate_external_plugin_installation(
|
||||
&manifest,
|
||||
&TargetPluginExternalRuntimeContract {
|
||||
@@ -911,6 +983,74 @@ mod tests {
|
||||
size_bytes: 8192,
|
||||
}],
|
||||
}),
|
||||
&policy_allowing_example_host(),
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
result.as_ref().map_err(String::as_str),
|
||||
Err(
|
||||
"artifact sidecar-linux-amd64 has invalid digest_sha256 0123456789abcdef0123456789abcdef (expected 64 hex characters)"
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn external_install_requires_artifact_for_host_target_triple() {
|
||||
let example = example_external_webhook_plugin();
|
||||
let gate = verified_gate_with_example_host();
|
||||
|
||||
let result = plan_external_target_plugin_action(
|
||||
&example.manifest,
|
||||
TargetPluginExternalAction::Install,
|
||||
&TargetPluginInstallation {
|
||||
install_state: TargetPluginInstallState::NotInstalled,
|
||||
current_revision: None,
|
||||
previous_revision: None,
|
||||
validation_error: None,
|
||||
},
|
||||
&gate,
|
||||
"aarch64-apple-darwin",
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
result,
|
||||
Err(TargetPluginExternalActionError::MissingArtifactForHost {
|
||||
plugin_id: "external:webhook-sidecar".to_string(),
|
||||
target_triple: "aarch64-apple-darwin".to_string(),
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn validate_external_installation_rejects_disallowed_provider() {
|
||||
let manifest = TargetPluginManifest {
|
||||
plugin_id: "external:webhook-sidecar",
|
||||
display_name: "Webhook Sidecar",
|
||||
provider: "unknown-vendor",
|
||||
version: "1.0.0",
|
||||
target_type: "webhook",
|
||||
supported_domains: &[],
|
||||
secret_fields: &[],
|
||||
};
|
||||
let policy = policy_allowing_example_host();
|
||||
|
||||
let result = validate_external_plugin_installation(
|
||||
&manifest,
|
||||
&TargetPluginExternalRuntimeContract {
|
||||
protocol_version: crate::SIDECAR_RUNTIME_PROTOCOL_VERSION,
|
||||
transport: TargetPluginRuntimeTransport::Grpc,
|
||||
},
|
||||
Some(TargetPluginDistributionManifest {
|
||||
artifacts: &[TargetPluginArtifactManifest {
|
||||
artifact_id: "sidecar-linux-amd64",
|
||||
target_triple: "x86_64-unknown-linux-gnu",
|
||||
download_uri: "https://plugins.example.test/webhook-sidecar.tar.zst",
|
||||
digest_sha256: "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef",
|
||||
signature_uri: "https://plugins.example.test/webhook-sidecar.tar.zst.sig",
|
||||
provenance_uri: "https://plugins.example.test/webhook-sidecar.tar.zst.intoto.jsonl",
|
||||
size_bytes: 8192,
|
||||
}],
|
||||
}),
|
||||
&policy,
|
||||
);
|
||||
|
||||
@@ -928,7 +1068,7 @@ mod tests {
|
||||
supported_domains: &[],
|
||||
secret_fields: &[],
|
||||
};
|
||||
let policy = TargetPluginInstallPolicy::default();
|
||||
let policy = policy_allowing_example_host();
|
||||
|
||||
let result = validate_external_plugin_installation(
|
||||
&manifest,
|
||||
@@ -941,7 +1081,7 @@ mod tests {
|
||||
artifact_id: "sidecar-linux-amd64",
|
||||
target_triple: "x86_64-unknown-linux-gnu",
|
||||
download_uri: "https://plugins.example.test/webhook-sidecar.tar.zst",
|
||||
digest_sha256: "0123456789abcdef0123456789abcdef",
|
||||
digest_sha256: "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef",
|
||||
signature_uri: "",
|
||||
provenance_uri: "https://plugins.example.test/webhook-sidecar.intoto.jsonl",
|
||||
size_bytes: 8192,
|
||||
@@ -967,7 +1107,7 @@ mod tests {
|
||||
supported_domains: &[],
|
||||
secret_fields: &[],
|
||||
};
|
||||
let policy = TargetPluginInstallPolicy::default();
|
||||
let policy = policy_allowing_example_host();
|
||||
|
||||
let result = validate_external_plugin_installation(
|
||||
&manifest,
|
||||
@@ -980,7 +1120,7 @@ mod tests {
|
||||
artifact_id: "sidecar-linux-amd64",
|
||||
target_triple: "x86_64-unknown-linux-gnu",
|
||||
download_uri: "https://plugins.example.test/webhook-sidecar.tar.zst",
|
||||
digest_sha256: "0123456789abcdef0123456789abcdef",
|
||||
digest_sha256: "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef",
|
||||
signature_uri: "https://plugins.example.test/webhook-sidecar.sig",
|
||||
provenance_uri: "",
|
||||
size_bytes: 8192,
|
||||
|
||||
@@ -60,8 +60,8 @@ pub use manifest::{
|
||||
};
|
||||
pub use net::*;
|
||||
pub use plugin::{
|
||||
BuiltinTargetAdminDescriptor, BuiltinTargetDescriptor, TargetAdminMetadata, TargetPluginDescriptor, TargetPluginRegistry,
|
||||
TargetRequestValidator, boxed_target,
|
||||
BuiltinTargetAdminDescriptor, BuiltinTargetDescriptor, PluginEvent, TargetAdminMetadata, TargetPluginDescriptor,
|
||||
TargetPluginRegistry, TargetRequestValidator, boxed_target,
|
||||
};
|
||||
pub use runtime::{
|
||||
ReplayEvent, ReplayWorkerManager, RuntimeActivation, RuntimeStatusSnapshot, RuntimeTargetHealthSnapshot,
|
||||
|
||||
@@ -13,6 +13,9 @@
|
||||
// limitations under the License.
|
||||
|
||||
use crate::domain::TargetDomain;
|
||||
use std::collections::HashMap;
|
||||
use std::sync::{Mutex, OnceLock};
|
||||
|
||||
use rustfs_config::{
|
||||
AMQP_PASSWORD, AMQP_TLS_CLIENT_CERT, AMQP_TLS_CLIENT_KEY, KAFKA_SASL_PASSWORD, KAFKA_TLS_CLIENT_CERT, KAFKA_TLS_CLIENT_KEY,
|
||||
MQTT_PASSWORD, MQTT_TLS_CLIENT_CERT, MQTT_TLS_CLIENT_KEY, MYSQL_DSN_STRING, MYSQL_TLS_CLIENT_CERT, MYSQL_TLS_CLIENT_KEY,
|
||||
@@ -210,10 +213,22 @@ fn builtin_plugin_id(target_type: &'static str) -> &'static str {
|
||||
"mysql" => "builtin:mysql",
|
||||
"redis" => "builtin:redis",
|
||||
"postgres" => "builtin:postgres",
|
||||
_ => "custom:target",
|
||||
_ => custom_plugin_id(target_type),
|
||||
}
|
||||
}
|
||||
|
||||
/// Interns a unique `custom:<target_type>` plugin id per non-builtin target type.
|
||||
/// A shared fallback id would make distinct custom plugins collide in every
|
||||
/// identity-keyed surface (registry keys, canonical instance ids, admin catalog).
|
||||
/// The leak is bounded by the number of distinct registered target types.
|
||||
fn custom_plugin_id(target_type: &'static str) -> &'static str {
|
||||
static CUSTOM_PLUGIN_IDS: OnceLock<Mutex<HashMap<&'static str, &'static str>>> = OnceLock::new();
|
||||
let ids = CUSTOM_PLUGIN_IDS.get_or_init(|| Mutex::new(HashMap::new()));
|
||||
let mut ids = ids.lock().unwrap_or_else(std::sync::PoisonError::into_inner);
|
||||
ids.entry(target_type)
|
||||
.or_insert_with(|| Box::leak(format!("custom:{target_type}").into_boxed_str()))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{
|
||||
@@ -263,6 +278,18 @@ mod tests {
|
||||
assert_eq!(manifest.supported_domains, &[TargetDomain::Audit, TargetDomain::Notify]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn custom_target_types_get_unique_stable_plugin_ids() {
|
||||
let first = builtin_target_manifest("custom-a");
|
||||
let second = builtin_target_manifest("custom-b");
|
||||
|
||||
assert_eq!(first.plugin_id, "custom:custom-a");
|
||||
assert_eq!(second.plugin_id, "custom:custom-b");
|
||||
assert_ne!(first.plugin_id, second.plugin_id);
|
||||
// Interned: repeated lookups return the same &'static str.
|
||||
assert!(std::ptr::eq(first.plugin_id, builtin_target_manifest("custom-a").plugin_id));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn builtin_kafka_manifest_marks_sasl_password_secret() {
|
||||
let manifest = builtin_target_manifest("kafka");
|
||||
|
||||
@@ -23,12 +23,20 @@ use serde::Serialize;
|
||||
use serde::de::DeserializeOwned;
|
||||
use std::collections::HashSet;
|
||||
use std::sync::Arc;
|
||||
use tracing::{error, info};
|
||||
use tracing::{error, info, warn};
|
||||
|
||||
type BoxedTarget<E> = Box<dyn Target<E> + Send + Sync>;
|
||||
type TargetCreateFn<E> = Arc<dyn Fn(String, &KVS) -> Result<BoxedTarget<E>, TargetError> + Send + Sync>;
|
||||
type TargetValidateFn = Arc<dyn Fn(&KVS) -> Result<(), TargetError> + Send + Sync>;
|
||||
|
||||
/// Event payload contract shared by all target plugin machinery.
|
||||
///
|
||||
/// Blanket-implemented for every type meeting the bounds; it exists solely to
|
||||
/// keep this composite bound spelled in one place instead of on every generic.
|
||||
pub trait PluginEvent: Send + Sync + Clone + Serialize + DeserializeOwned + 'static {}
|
||||
|
||||
impl<T> PluginEvent for T where T: Send + Sync + Clone + Serialize + DeserializeOwned + 'static {}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum TargetRequestValidator {
|
||||
Webhook,
|
||||
@@ -105,7 +113,7 @@ impl BuiltinTargetAdminDescriptor {
|
||||
#[derive(Clone)]
|
||||
pub struct TargetPluginDescriptor<E>
|
||||
where
|
||||
E: Send + Sync + 'static + Clone + Serialize + DeserializeOwned,
|
||||
E: PluginEvent,
|
||||
{
|
||||
create_target: TargetCreateFn<E>,
|
||||
manifest: TargetPluginManifest,
|
||||
@@ -117,7 +125,7 @@ where
|
||||
|
||||
impl<E> TargetPluginDescriptor<E>
|
||||
where
|
||||
E: Send + Sync + 'static + Clone + Serialize + DeserializeOwned,
|
||||
E: PluginEvent,
|
||||
{
|
||||
pub fn new<Create, Validate>(
|
||||
target_type: &'static str,
|
||||
@@ -186,7 +194,7 @@ where
|
||||
#[derive(Clone)]
|
||||
pub struct BuiltinTargetDescriptor<E>
|
||||
where
|
||||
E: Send + Sync + 'static + Clone + Serialize + DeserializeOwned,
|
||||
E: PluginEvent,
|
||||
{
|
||||
plugin: TargetPluginDescriptor<E>,
|
||||
admin: TargetAdminMetadata,
|
||||
@@ -194,7 +202,7 @@ where
|
||||
|
||||
impl<E> BuiltinTargetDescriptor<E>
|
||||
where
|
||||
E: Send + Sync + 'static + Clone + Serialize + DeserializeOwned,
|
||||
E: PluginEvent,
|
||||
{
|
||||
pub fn new(subsystem: &'static str, request_validator: TargetRequestValidator, plugin: TargetPluginDescriptor<E>) -> Self {
|
||||
Self {
|
||||
@@ -226,7 +234,7 @@ where
|
||||
|
||||
impl<E> From<BuiltinTargetDescriptor<E>> for BuiltinTargetAdminDescriptor
|
||||
where
|
||||
E: Send + Sync + 'static + Clone + Serialize + DeserializeOwned,
|
||||
E: PluginEvent,
|
||||
{
|
||||
fn from(descriptor: BuiltinTargetDescriptor<E>) -> Self {
|
||||
Self::new(
|
||||
@@ -239,14 +247,14 @@ where
|
||||
|
||||
pub struct TargetPluginRegistry<E>
|
||||
where
|
||||
E: Send + Sync + 'static + Clone + Serialize + DeserializeOwned,
|
||||
E: PluginEvent,
|
||||
{
|
||||
plugins: HashMap<String, TargetPluginDescriptor<E>>,
|
||||
}
|
||||
|
||||
impl<E> Default for TargetPluginRegistry<E>
|
||||
where
|
||||
E: Send + Sync + 'static + Clone + Serialize + DeserializeOwned,
|
||||
E: PluginEvent,
|
||||
{
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
@@ -255,14 +263,22 @@ where
|
||||
|
||||
impl<E> TargetPluginRegistry<E>
|
||||
where
|
||||
E: Send + Sync + 'static + Clone + Serialize + DeserializeOwned,
|
||||
E: PluginEvent,
|
||||
{
|
||||
pub fn new() -> Self {
|
||||
Self { plugins: HashMap::new() }
|
||||
}
|
||||
|
||||
pub fn register(&mut self, plugin: TargetPluginDescriptor<E>) -> Option<TargetPluginDescriptor<E>> {
|
||||
self.plugins.insert(plugin.target_type().to_string(), plugin)
|
||||
let replaced = self.plugins.insert(plugin.target_type().to_string(), plugin);
|
||||
if let Some(previous) = &replaced {
|
||||
warn!(
|
||||
target_type = %previous.target_type(),
|
||||
plugin_id = %previous.manifest().plugin_id,
|
||||
"replacing previously registered target plugin descriptor"
|
||||
);
|
||||
}
|
||||
replaced
|
||||
}
|
||||
|
||||
pub fn register_all<I>(&mut self, plugins: I)
|
||||
@@ -291,12 +307,18 @@ where
|
||||
plugin.create_target(id, config)
|
||||
}
|
||||
|
||||
/// Creates every enabled target instance found in `config`.
|
||||
///
|
||||
/// Creation is fault-isolated per instance: one broken target must not
|
||||
/// prevent the remaining targets from activating, so failures are logged
|
||||
/// and summarized instead of aborting the whole activation.
|
||||
pub async fn create_targets_from_config(
|
||||
&self,
|
||||
config: &Config,
|
||||
route_prefix: &str,
|
||||
) -> Result<Vec<BoxedTarget<E>>, TargetError> {
|
||||
let mut successful_targets = Vec::new();
|
||||
let mut failed_targets = 0usize;
|
||||
|
||||
for (target_type, plugin) in &self.plugins {
|
||||
info!(target_type = %target_type, "Start working on target type");
|
||||
@@ -308,13 +330,25 @@ where
|
||||
successful_targets.push(target);
|
||||
}
|
||||
Err(err) => {
|
||||
failed_targets += 1;
|
||||
error!(target_type = %target_type, instance_id = %id, error = %err, "Failed to create target");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
info!(count = successful_targets.len(), "All target processing completed");
|
||||
if failed_targets > 0 {
|
||||
warn!(
|
||||
created = successful_targets.len(),
|
||||
failed = failed_targets,
|
||||
"Some configured targets failed to create and were skipped"
|
||||
);
|
||||
}
|
||||
info!(
|
||||
count = successful_targets.len(),
|
||||
failed = failed_targets,
|
||||
"All target processing completed"
|
||||
);
|
||||
Ok(successful_targets)
|
||||
}
|
||||
|
||||
@@ -334,7 +368,7 @@ where
|
||||
|
||||
pub fn boxed_target<E, T>(target: T) -> BoxedTarget<E>
|
||||
where
|
||||
E: Send + Sync + 'static + Clone + Serialize + DeserializeOwned,
|
||||
E: PluginEvent,
|
||||
T: Target<E> + Send + Sync + 'static,
|
||||
{
|
||||
Box::new(target)
|
||||
@@ -343,6 +377,7 @@ where
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{TargetPluginDescriptor, TargetPluginRegistry};
|
||||
use crate::PluginEvent;
|
||||
use crate::runtime::adapter::BuiltinPluginRuntimeAdapter;
|
||||
use crate::store::{Key, Store};
|
||||
use crate::target::{EntityTarget, QueuedPayload, QueuedPayloadMeta};
|
||||
@@ -350,7 +385,6 @@ mod tests {
|
||||
use async_trait::async_trait;
|
||||
use rustfs_config::ENABLE_KEY;
|
||||
use rustfs_config::server_config::{Config, KVS};
|
||||
use serde::{Serialize, de::DeserializeOwned};
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
@@ -363,7 +397,7 @@ mod tests {
|
||||
#[async_trait]
|
||||
impl<E> Target<E> for TestTarget
|
||||
where
|
||||
E: Send + Sync + 'static + Clone + Serialize + DeserializeOwned,
|
||||
E: PluginEvent,
|
||||
{
|
||||
fn id(&self) -> crate::arn::TargetID {
|
||||
self.id.clone()
|
||||
|
||||
@@ -16,10 +16,9 @@ use super::{
|
||||
ReplayEvent, ReplayWorkerManager, RuntimeActivation, RuntimeStatusSnapshot, RuntimeTargetHealthSnapshot,
|
||||
TargetRuntimeManager, activate_targets_with_replay, init_target_and_optionally_start_replay, start_replay_worker,
|
||||
};
|
||||
use crate::plugin::PluginEvent;
|
||||
use crate::{Target, TargetError};
|
||||
use async_trait::async_trait;
|
||||
use serde::Serialize;
|
||||
use serde::de::DeserializeOwned;
|
||||
use std::future::Future;
|
||||
use std::pin::Pin;
|
||||
use std::sync::Arc;
|
||||
@@ -33,7 +32,7 @@ type ReplayStartObserver = Arc<dyn Fn(&str, bool) + Send + Sync>;
|
||||
#[async_trait]
|
||||
pub trait PluginRuntimeAdapter<E>: Send + Sync
|
||||
where
|
||||
E: Send + Sync + 'static + Clone + Serialize + DeserializeOwned,
|
||||
E: PluginEvent,
|
||||
{
|
||||
async fn activate_with_replay(&self, targets: Vec<Box<dyn Target<E> + Send + Sync>>) -> RuntimeActivation<E>;
|
||||
|
||||
@@ -66,7 +65,7 @@ where
|
||||
#[derive(Clone)]
|
||||
pub struct BuiltinPluginRuntimeAdapter<E>
|
||||
where
|
||||
E: Send + Sync + 'static + Clone + Serialize + DeserializeOwned,
|
||||
E: PluginEvent,
|
||||
{
|
||||
replay_hook: ReplayHook<E>,
|
||||
replay_start_observer: ReplayStartObserver,
|
||||
@@ -78,7 +77,7 @@ where
|
||||
|
||||
impl<E> BuiltinPluginRuntimeAdapter<E>
|
||||
where
|
||||
E: Send + Sync + 'static + Clone + Serialize + DeserializeOwned,
|
||||
E: PluginEvent,
|
||||
{
|
||||
pub fn new(
|
||||
replay_hook: ReplayHook<E>,
|
||||
@@ -102,7 +101,7 @@ where
|
||||
#[async_trait]
|
||||
impl<E> PluginRuntimeAdapter<E> for BuiltinPluginRuntimeAdapter<E>
|
||||
where
|
||||
E: Send + Sync + 'static + Clone + Serialize + DeserializeOwned,
|
||||
E: PluginEvent,
|
||||
{
|
||||
async fn activate_with_replay(&self, targets: Vec<Box<dyn Target<E> + Send + Sync>>) -> RuntimeActivation<E> {
|
||||
let replay_hook = Arc::clone(&self.replay_hook);
|
||||
@@ -184,12 +183,12 @@ where
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{BuiltinPluginRuntimeAdapter, PluginRuntimeAdapter};
|
||||
use crate::PluginEvent;
|
||||
use crate::arn::TargetID;
|
||||
use crate::store::{Key, QueueStore, Store};
|
||||
use crate::target::{EntityTarget, QueuedPayload, QueuedPayloadMeta};
|
||||
use crate::{StoreError, Target, TargetError};
|
||||
use async_trait::async_trait;
|
||||
use serde::{Serialize, de::DeserializeOwned};
|
||||
use std::sync::Arc;
|
||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||
use std::time::Duration;
|
||||
@@ -230,7 +229,7 @@ mod tests {
|
||||
#[async_trait]
|
||||
impl<E> Target<E> for TestTarget
|
||||
where
|
||||
E: Send + Sync + 'static + Clone + Serialize + DeserializeOwned,
|
||||
E: PluginEvent,
|
||||
{
|
||||
fn id(&self) -> TargetID {
|
||||
self.id.clone()
|
||||
|
||||
@@ -22,12 +22,11 @@ pub mod tls;
|
||||
|
||||
use crate::Target;
|
||||
use crate::arn::TargetID;
|
||||
use crate::plugin::PluginEvent;
|
||||
use crate::store::{Key, Store, ensure_store_entry_raw_readable};
|
||||
use crate::target::QueuedPayload;
|
||||
use crate::target::TargetDeliverySnapshot;
|
||||
use crate::{StoreError, TargetError};
|
||||
use serde::Serialize;
|
||||
use serde::de::DeserializeOwned;
|
||||
use std::sync::Arc;
|
||||
use std::{collections::HashMap, fmt::Debug};
|
||||
use std::{future::Future, pin::Pin, time::Duration};
|
||||
@@ -78,7 +77,7 @@ impl ReplayWorkerManager {
|
||||
|
||||
pub struct RuntimeActivation<E>
|
||||
where
|
||||
E: Send + Sync + 'static + Clone + Serialize + DeserializeOwned,
|
||||
E: PluginEvent,
|
||||
{
|
||||
pub replay_workers: ReplayWorkerManager,
|
||||
pub targets: Vec<SharedTarget<E>>,
|
||||
@@ -119,7 +118,7 @@ pub struct RuntimeTargetHealthSnapshot {
|
||||
|
||||
pub enum ReplayEvent<E>
|
||||
where
|
||||
E: Send + Sync + 'static + Clone + Serialize + DeserializeOwned,
|
||||
E: PluginEvent,
|
||||
{
|
||||
Delivered {
|
||||
key: Key,
|
||||
@@ -159,14 +158,14 @@ where
|
||||
/// be layered on top in later phases.
|
||||
pub struct TargetRuntimeManager<E>
|
||||
where
|
||||
E: Send + Sync + 'static + Clone + Serialize + DeserializeOwned,
|
||||
E: PluginEvent,
|
||||
{
|
||||
targets: HashMap<String, SharedTarget<E>>,
|
||||
}
|
||||
|
||||
impl<E> Default for TargetRuntimeManager<E>
|
||||
where
|
||||
E: Send + Sync + 'static + Clone + Serialize + DeserializeOwned,
|
||||
E: PluginEvent,
|
||||
{
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
@@ -175,7 +174,7 @@ where
|
||||
|
||||
impl<E> Debug for TargetRuntimeManager<E>
|
||||
where
|
||||
E: Send + Sync + 'static + Clone + Serialize + DeserializeOwned,
|
||||
E: PluginEvent,
|
||||
{
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("TargetRuntimeManager")
|
||||
@@ -186,7 +185,7 @@ where
|
||||
|
||||
impl<E> TargetRuntimeManager<E>
|
||||
where
|
||||
E: Send + Sync + 'static + Clone + Serialize + DeserializeOwned,
|
||||
E: PluginEvent,
|
||||
{
|
||||
pub fn new() -> Self {
|
||||
Self { targets: HashMap::new() }
|
||||
@@ -320,7 +319,7 @@ pub async fn init_target_and_optionally_start_replay<E, F, G>(
|
||||
start_replay: G,
|
||||
) -> Option<(SharedTarget<E>, Option<mpsc::Sender<()>>)>
|
||||
where
|
||||
E: Send + Sync + 'static + Clone + Serialize + DeserializeOwned,
|
||||
E: PluginEvent,
|
||||
F: FnOnce(&str, bool),
|
||||
G: FnOnce(Box<dyn Store<QueuedPayload, Error = StoreError, Key = Key> + Send>, SharedTarget<E>) -> mpsc::Sender<()>,
|
||||
{
|
||||
@@ -356,7 +355,7 @@ pub async fn activate_targets_with_replay<E, F, Fut>(
|
||||
mut activate_one: F,
|
||||
) -> RuntimeActivation<E>
|
||||
where
|
||||
E: Send + Sync + 'static + Clone + Serialize + DeserializeOwned,
|
||||
E: PluginEvent,
|
||||
F: FnMut(Box<dyn Target<E> + Send + Sync>) -> Fut,
|
||||
Fut: Future<Output = Option<(SharedTarget<E>, Option<mpsc::Sender<()>>)>>,
|
||||
{
|
||||
@@ -388,7 +387,7 @@ pub fn start_replay_worker<E>(
|
||||
idle_sleep: Duration,
|
||||
) -> mpsc::Sender<()>
|
||||
where
|
||||
E: Send + Sync + 'static + Clone + Serialize + DeserializeOwned,
|
||||
E: PluginEvent,
|
||||
{
|
||||
let (cancel_tx, cancel_rx) = mpsc::channel(1);
|
||||
|
||||
@@ -408,7 +407,7 @@ async fn stream_replay_worker<E>(
|
||||
batch_timeout: Duration,
|
||||
idle_sleep: Duration,
|
||||
) where
|
||||
E: Send + Sync + 'static + Clone + Serialize + DeserializeOwned,
|
||||
E: PluginEvent,
|
||||
{
|
||||
const MAX_RETRIES: usize = 5;
|
||||
const BASE_RETRY_DELAY: Duration = Duration::from_secs(2);
|
||||
@@ -469,7 +468,7 @@ async fn stream_replay_worker<E>(
|
||||
hook: &ReplayHook<E>,
|
||||
semaphore: Option<Arc<Semaphore>>,
|
||||
) where
|
||||
E: Send + Sync + 'static + Clone + Serialize + DeserializeOwned,
|
||||
E: PluginEvent,
|
||||
{
|
||||
if batch_keys.is_empty() {
|
||||
return;
|
||||
@@ -553,13 +552,13 @@ async fn stream_replay_worker<E>(
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::TargetRuntimeManager;
|
||||
use crate::PluginEvent;
|
||||
use crate::StoreError;
|
||||
use crate::arn::TargetID;
|
||||
use crate::store::{Key, Store};
|
||||
use crate::target::{EntityTarget, QueuedPayload, QueuedPayloadMeta};
|
||||
use crate::{Target, TargetError};
|
||||
use async_trait::async_trait;
|
||||
use serde::{Serialize, de::DeserializeOwned};
|
||||
use std::sync::Arc;
|
||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||
|
||||
@@ -581,7 +580,7 @@ mod tests {
|
||||
#[async_trait]
|
||||
impl<E> Target<E> for TestTarget
|
||||
where
|
||||
E: Send + Sync + 'static + Clone + Serialize + DeserializeOwned,
|
||||
E: PluginEvent,
|
||||
{
|
||||
fn id(&self) -> TargetID {
|
||||
self.id.clone()
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
//! Queue-store mode uses the shared target store and replays the same raw JSON
|
||||
//! body through `send_raw_from_store`.
|
||||
|
||||
use crate::plugin::PluginEvent;
|
||||
use crate::{
|
||||
StoreError, Target,
|
||||
arn::TargetID,
|
||||
@@ -42,8 +43,6 @@ use lapin::{
|
||||
use parking_lot::Mutex;
|
||||
use rustfs_config::{AMQP_TLS_CA, AMQP_TLS_CLIENT_CERT, AMQP_TLS_CLIENT_KEY};
|
||||
use rustfs_tls_runtime::load_cert_bundle_der_bytes;
|
||||
use serde::Serialize;
|
||||
use serde::de::DeserializeOwned;
|
||||
use std::fmt;
|
||||
use std::path::Path;
|
||||
use std::sync::Arc;
|
||||
@@ -298,7 +297,7 @@ pub struct AMQPConnection {
|
||||
|
||||
pub struct AMQPTarget<E>
|
||||
where
|
||||
E: Send + Sync + 'static + Clone + Serialize + DeserializeOwned,
|
||||
E: PluginEvent,
|
||||
{
|
||||
id: TargetID,
|
||||
args: AMQPArgs,
|
||||
@@ -314,7 +313,7 @@ where
|
||||
|
||||
impl<E> AMQPTarget<E>
|
||||
where
|
||||
E: Send + Sync + 'static + Clone + Serialize + DeserializeOwned,
|
||||
E: PluginEvent,
|
||||
{
|
||||
pub fn clone_box(&self) -> Box<dyn Target<E> + Send + Sync> {
|
||||
Box::new(AMQPTarget::<E> {
|
||||
@@ -464,7 +463,7 @@ where
|
||||
#[async_trait]
|
||||
impl<E> ReloadableTargetTls for AMQPTarget<E>
|
||||
where
|
||||
E: Send + Sync + 'static + Clone + Serialize + DeserializeOwned,
|
||||
E: PluginEvent,
|
||||
{
|
||||
type Material = AMQPConnection;
|
||||
|
||||
@@ -500,7 +499,7 @@ where
|
||||
#[async_trait]
|
||||
impl<E> Target<E> for AMQPTarget<E>
|
||||
where
|
||||
E: Send + Sync + 'static + Clone + Serialize + DeserializeOwned,
|
||||
E: PluginEvent,
|
||||
{
|
||||
fn id(&self) -> TargetID {
|
||||
self.id.clone()
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use crate::plugin::PluginEvent;
|
||||
use crate::{
|
||||
StoreError, Target,
|
||||
arn::TargetID,
|
||||
@@ -31,8 +32,6 @@ use async_trait::async_trait;
|
||||
use rustfs_kafka_async::error::{ConnectionError, Error as KafkaError};
|
||||
use rustfs_kafka_async::{AsyncProducer, AsyncProducerConfig, Record, RequiredAcks, SaslConfig, SecurityConfig};
|
||||
use rustfs_tls_runtime::{load_cert_bundle_der_bytes, load_private_key};
|
||||
use serde::Serialize;
|
||||
use serde::de::DeserializeOwned;
|
||||
use std::{fmt, marker::PhantomData, sync::Arc, time::Duration};
|
||||
use tokio::sync::Mutex;
|
||||
use tracing::{debug, error, info, instrument, warn};
|
||||
@@ -228,7 +227,7 @@ impl KafkaArgs {
|
||||
/// A target that sends events to an Apache Kafka topic
|
||||
pub struct KafkaTarget<E>
|
||||
where
|
||||
E: Send + Sync + 'static + Clone + Serialize + DeserializeOwned,
|
||||
E: PluginEvent,
|
||||
{
|
||||
id: TargetID,
|
||||
args: KafkaArgs,
|
||||
@@ -245,7 +244,7 @@ where
|
||||
|
||||
impl<E> KafkaTarget<E>
|
||||
where
|
||||
E: Send + Sync + 'static + Clone + Serialize + DeserializeOwned,
|
||||
E: PluginEvent,
|
||||
{
|
||||
fn map_kafka_error(err: KafkaError, context: &str) -> TargetError {
|
||||
match err {
|
||||
@@ -397,7 +396,7 @@ where
|
||||
#[async_trait]
|
||||
impl<E> Target<E> for KafkaTarget<E>
|
||||
where
|
||||
E: Send + Sync + 'static + Clone + Serialize + DeserializeOwned,
|
||||
E: PluginEvent,
|
||||
{
|
||||
fn id(&self) -> TargetID {
|
||||
self.id.clone()
|
||||
@@ -490,7 +489,7 @@ where
|
||||
#[async_trait]
|
||||
impl<E> ReloadableTargetTls for KafkaTarget<E>
|
||||
where
|
||||
E: Send + Sync + 'static + Clone + Serialize + DeserializeOwned,
|
||||
E: PluginEvent,
|
||||
{
|
||||
type Material = Arc<AsyncProducer>;
|
||||
|
||||
|
||||
@@ -13,11 +13,11 @@
|
||||
// limitations under the License.
|
||||
|
||||
use crate::arn::TargetID;
|
||||
use crate::plugin::PluginEvent;
|
||||
use crate::store::{Key, QueueStore, Store};
|
||||
use crate::{StoreError, TargetError, TargetLog};
|
||||
use async_trait::async_trait;
|
||||
use rustfs_s3_types::EventName;
|
||||
use serde::de::DeserializeOwned;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::fmt::Formatter;
|
||||
use std::future::Future;
|
||||
@@ -96,7 +96,7 @@ impl TargetDeliveryCounters {
|
||||
#[async_trait]
|
||||
pub trait Target<E>: Send + Sync + 'static
|
||||
where
|
||||
E: Send + Sync + 'static + Clone + Serialize + DeserializeOwned,
|
||||
E: PluginEvent,
|
||||
{
|
||||
/// Returns the ID of the target
|
||||
fn id(&self) -> TargetID;
|
||||
@@ -428,7 +428,7 @@ pub fn decode_object_name(encoded: &str) -> Result<String, TargetError> {
|
||||
|
||||
pub(crate) fn build_queued_payload<E>(event: &EntityTarget<E>) -> Result<QueuedPayload, TargetError>
|
||||
where
|
||||
E: Send + Sync + 'static + Clone + Serialize + DeserializeOwned,
|
||||
E: PluginEvent,
|
||||
{
|
||||
build_queued_payload_with_records(event, vec![event.data.clone()])
|
||||
}
|
||||
@@ -438,7 +438,7 @@ pub(crate) fn build_queued_payload_with_records<E, R>(
|
||||
records: Vec<R>,
|
||||
) -> Result<QueuedPayload, TargetError>
|
||||
where
|
||||
E: Send + Sync + 'static + Clone + Serialize + DeserializeOwned,
|
||||
E: PluginEvent,
|
||||
R: Serialize,
|
||||
{
|
||||
let object_name = decode_object_name(&event.object_name)?;
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use crate::plugin::PluginEvent;
|
||||
use crate::{
|
||||
StoreError, Target,
|
||||
arn::TargetID,
|
||||
@@ -39,8 +40,6 @@ use rustfs_config::{
|
||||
};
|
||||
use rustfs_tls_runtime::{load_certs, load_private_key};
|
||||
use rustls::ClientConfig;
|
||||
use serde::Serialize;
|
||||
use serde::de::DeserializeOwned;
|
||||
use std::fmt;
|
||||
use std::sync::Arc;
|
||||
use std::{
|
||||
@@ -524,7 +523,7 @@ struct BgTaskManager {
|
||||
/// A target that sends events to an MQTT broker
|
||||
pub struct MQTTTarget<E>
|
||||
where
|
||||
E: Send + Sync + 'static + Clone + Serialize + DeserializeOwned,
|
||||
E: PluginEvent,
|
||||
{
|
||||
id: TargetID,
|
||||
args: MQTTArgs,
|
||||
@@ -544,7 +543,7 @@ where
|
||||
|
||||
impl<E> MQTTTarget<E>
|
||||
where
|
||||
E: Send + Sync + 'static + Clone + Serialize + DeserializeOwned,
|
||||
E: PluginEvent,
|
||||
{
|
||||
/// Creates a new MQTTTarget
|
||||
#[instrument(skip(args), fields(target_id_as_string = %id))]
|
||||
@@ -844,7 +843,7 @@ where
|
||||
#[async_trait]
|
||||
impl<E> ReloadableTargetTls for MQTTTarget<E>
|
||||
where
|
||||
E: Send + Sync + 'static + Clone + Serialize + DeserializeOwned,
|
||||
E: PluginEvent,
|
||||
{
|
||||
type Material = MqttOptions;
|
||||
|
||||
@@ -1130,7 +1129,7 @@ fn is_fatal_mqtt_error(err: &ConnectionError) -> bool {
|
||||
#[async_trait]
|
||||
impl<E> Target<E> for MQTTTarget<E>
|
||||
where
|
||||
E: Send + Sync + 'static + Clone + Serialize + DeserializeOwned,
|
||||
E: PluginEvent,
|
||||
{
|
||||
fn id(&self) -> TargetID {
|
||||
self.id.clone()
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use crate::plugin::PluginEvent;
|
||||
use crate::{
|
||||
StoreError, Target,
|
||||
arn::TargetID,
|
||||
@@ -31,8 +32,6 @@ use async_trait::async_trait;
|
||||
use mysql_async::{Conn, Opts, OptsBuilder, Pool, PoolConstraints, PoolOpts, SslOpts, prelude::Queryable};
|
||||
use rustfs_config::{MYSQL_TLS_CA, MYSQL_TLS_CLIENT_CERT, MYSQL_TLS_CLIENT_KEY};
|
||||
use rustfs_tls_runtime::{load_certs, load_private_key};
|
||||
use serde::Serialize;
|
||||
use serde::de::DeserializeOwned;
|
||||
use std::fmt;
|
||||
use std::marker::PhantomData;
|
||||
use std::path::{Path, PathBuf};
|
||||
@@ -505,7 +504,7 @@ async fn validate_existing_schema(conn: &mut Conn, table: &str) -> Result<(), Ta
|
||||
/// ```
|
||||
pub struct MySqlTarget<E>
|
||||
where
|
||||
E: Send + Sync + 'static + Clone + Serialize + DeserializeOwned,
|
||||
E: PluginEvent,
|
||||
{
|
||||
/// Unique target identifier (name + type)
|
||||
id: TargetID,
|
||||
@@ -528,7 +527,7 @@ where
|
||||
|
||||
impl<E> MySqlTarget<E>
|
||||
where
|
||||
E: Send + Sync + 'static + Clone + Serialize + DeserializeOwned,
|
||||
E: PluginEvent,
|
||||
{
|
||||
/// Creates a new MySqlTarget.
|
||||
///
|
||||
@@ -770,7 +769,7 @@ pub(crate) fn map_mysql_error(err: mysql_async::Error, operation: &str) -> Targe
|
||||
#[async_trait]
|
||||
impl<E> Target<E> for MySqlTarget<E>
|
||||
where
|
||||
E: Send + Sync + 'static + Clone + Serialize + DeserializeOwned,
|
||||
E: PluginEvent,
|
||||
{
|
||||
fn id(&self) -> TargetID {
|
||||
self.id.clone()
|
||||
@@ -927,7 +926,7 @@ where
|
||||
#[async_trait]
|
||||
impl<E> ReloadableTargetTls for MySqlTarget<E>
|
||||
where
|
||||
E: Send + Sync + 'static + Clone + Serialize + DeserializeOwned,
|
||||
E: PluginEvent,
|
||||
{
|
||||
type Material = Pool;
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use crate::plugin::PluginEvent;
|
||||
use crate::{
|
||||
StoreError, Target,
|
||||
arn::TargetID,
|
||||
@@ -29,8 +30,6 @@ use crate::{
|
||||
};
|
||||
use async_trait::async_trait;
|
||||
use rustfs_config::{NATS_CREDENTIALS_FILE, NATS_TLS_CA, NATS_TLS_CLIENT_CERT, NATS_TLS_CLIENT_KEY};
|
||||
use serde::Serialize;
|
||||
use serde::de::DeserializeOwned;
|
||||
use std::fmt;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::str::FromStr;
|
||||
@@ -192,7 +191,7 @@ pub async fn connect_nats(args: &NATSArgs) -> Result<async_nats::Client, TargetE
|
||||
|
||||
pub struct NATSTarget<E>
|
||||
where
|
||||
E: Send + Sync + 'static + Clone + Serialize + DeserializeOwned,
|
||||
E: PluginEvent,
|
||||
{
|
||||
id: TargetID,
|
||||
args: NATSArgs,
|
||||
@@ -210,7 +209,7 @@ where
|
||||
|
||||
impl<E> NATSTarget<E>
|
||||
where
|
||||
E: Send + Sync + 'static + Clone + Serialize + DeserializeOwned,
|
||||
E: PluginEvent,
|
||||
{
|
||||
pub fn clone_box(&self) -> Box<dyn Target<E> + Send + Sync> {
|
||||
Box::new(NATSTarget::<E> {
|
||||
@@ -318,7 +317,7 @@ where
|
||||
#[async_trait]
|
||||
impl<E> Target<E> for NATSTarget<E>
|
||||
where
|
||||
E: Send + Sync + 'static + Clone + Serialize + DeserializeOwned,
|
||||
E: PluginEvent,
|
||||
{
|
||||
fn id(&self) -> TargetID {
|
||||
self.id.clone()
|
||||
@@ -415,7 +414,7 @@ where
|
||||
#[async_trait]
|
||||
impl<E> ReloadableTargetTls for NATSTarget<E>
|
||||
where
|
||||
E: Send + Sync + 'static + Clone + Serialize + DeserializeOwned,
|
||||
E: PluginEvent,
|
||||
{
|
||||
type Material = async_nats::Client;
|
||||
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
//! Connection pooling is delegated to `deadpool-postgres`; the pool itself
|
||||
//! is `Clone`, so no `Mutex` is required around it.
|
||||
|
||||
use crate::plugin::PluginEvent;
|
||||
use crate::{
|
||||
StoreError, Target,
|
||||
arn::TargetID,
|
||||
@@ -44,8 +45,6 @@ use async_trait::async_trait;
|
||||
use deadpool_postgres::{Manager, ManagerConfig, Pool, RecyclingMethod};
|
||||
use rustfs_config::{POSTGRES_DSN_STRING, POSTGRES_TLS_CA, POSTGRES_TLS_CLIENT_CERT, POSTGRES_TLS_CLIENT_KEY};
|
||||
use rustfs_tls_runtime::{load_certs, load_private_key};
|
||||
use serde::Serialize;
|
||||
use serde::de::DeserializeOwned;
|
||||
use std::fmt;
|
||||
use std::path::Path;
|
||||
use std::sync::Arc;
|
||||
@@ -555,7 +554,7 @@ fn resolve_payload_key(payload: &serde_json::Value, meta: &QueuedPayloadMeta) ->
|
||||
/// the legacy inline fingerprint check is used as a fallback.
|
||||
pub struct PostgresTarget<E>
|
||||
where
|
||||
E: Send + Sync + 'static + Clone + Serialize + DeserializeOwned,
|
||||
E: PluginEvent,
|
||||
{
|
||||
id: TargetID,
|
||||
args: PostgresArgs,
|
||||
@@ -573,7 +572,7 @@ where
|
||||
|
||||
impl<E> PostgresTarget<E>
|
||||
where
|
||||
E: Send + Sync + 'static + Clone + Serialize + DeserializeOwned,
|
||||
E: PluginEvent,
|
||||
{
|
||||
pub fn clone_box(&self) -> Box<dyn Target<E> + Send + Sync> {
|
||||
Box::new(PostgresTarget::<E> {
|
||||
@@ -692,7 +691,7 @@ where
|
||||
#[async_trait]
|
||||
impl<E> ReloadableTargetTls for PostgresTarget<E>
|
||||
where
|
||||
E: Send + Sync + 'static + Clone + Serialize + DeserializeOwned,
|
||||
E: PluginEvent,
|
||||
{
|
||||
type Material = Pool;
|
||||
|
||||
@@ -727,7 +726,7 @@ where
|
||||
#[async_trait]
|
||||
impl<E> Target<E> for PostgresTarget<E>
|
||||
where
|
||||
E: Send + Sync + 'static + Clone + Serialize + DeserializeOwned,
|
||||
E: PluginEvent,
|
||||
{
|
||||
fn id(&self) -> TargetID {
|
||||
self.id.clone()
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use crate::plugin::PluginEvent;
|
||||
use crate::{
|
||||
StoreError, Target,
|
||||
arn::TargetID,
|
||||
@@ -30,8 +31,6 @@ use crate::{
|
||||
use async_trait::async_trait;
|
||||
use pulsar::{Authentication, Producer, Pulsar, TokioExecutor};
|
||||
use rustfs_tls_runtime::load_cert_bundle_der_bytes;
|
||||
use serde::Serialize;
|
||||
use serde::de::DeserializeOwned;
|
||||
use std::fmt;
|
||||
use std::path::Path;
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
@@ -186,7 +185,7 @@ pub async fn connect_pulsar(args: &PulsarArgs) -> Result<Pulsar<TokioExecutor>,
|
||||
|
||||
pub struct PulsarTarget<E>
|
||||
where
|
||||
E: Send + Sync + 'static + Clone + Serialize + DeserializeOwned,
|
||||
E: PluginEvent,
|
||||
{
|
||||
id: TargetID,
|
||||
args: PulsarArgs,
|
||||
@@ -203,7 +202,7 @@ where
|
||||
|
||||
impl<E> PulsarTarget<E>
|
||||
where
|
||||
E: Send + Sync + 'static + Clone + Serialize + DeserializeOwned,
|
||||
E: PluginEvent,
|
||||
{
|
||||
pub fn clone_box(&self) -> Box<dyn Target<E> + Send + Sync> {
|
||||
Box::new(PulsarTarget::<E> {
|
||||
@@ -339,7 +338,7 @@ where
|
||||
#[async_trait]
|
||||
impl<E> ReloadableTargetTls for PulsarTarget<E>
|
||||
where
|
||||
E: Send + Sync + 'static + Clone + Serialize + DeserializeOwned,
|
||||
E: PluginEvent,
|
||||
{
|
||||
type Material = Pulsar<TokioExecutor>;
|
||||
|
||||
@@ -384,7 +383,7 @@ where
|
||||
#[async_trait]
|
||||
impl<E> Target<E> for PulsarTarget<E>
|
||||
where
|
||||
E: Send + Sync + 'static + Clone + Serialize + DeserializeOwned,
|
||||
E: PluginEvent,
|
||||
{
|
||||
fn id(&self) -> TargetID {
|
||||
self.id.clone()
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use crate::plugin::PluginEvent;
|
||||
use crate::{
|
||||
StoreError, Target,
|
||||
arn::TargetID,
|
||||
@@ -37,8 +38,6 @@ use redis::{
|
||||
use rustfs_config::{REDIS_TLS_CA, REDIS_TLS_CLIENT_CERT, REDIS_TLS_CLIENT_KEY, REDIS_TLS_POLICY};
|
||||
use rustls::pki_types::CertificateDer;
|
||||
use rustls::pki_types::pem::PemObject;
|
||||
use serde::Serialize;
|
||||
use serde::de::DeserializeOwned;
|
||||
use std::fmt;
|
||||
use std::io::BufReader;
|
||||
use std::path::Path;
|
||||
@@ -300,7 +299,7 @@ fn validate_redis_tls_config(url: &Url, tls: &RedisTlsConfig) -> Result<(), Targ
|
||||
|
||||
pub struct RedisTarget<E>
|
||||
where
|
||||
E: Send + Sync + 'static + Clone + Serialize + DeserializeOwned,
|
||||
E: PluginEvent,
|
||||
{
|
||||
id: TargetID,
|
||||
args: RedisArgs,
|
||||
@@ -326,7 +325,7 @@ where
|
||||
|
||||
impl<E> RedisTarget<E>
|
||||
where
|
||||
E: Send + Sync + 'static + Clone + Serialize + DeserializeOwned,
|
||||
E: PluginEvent,
|
||||
{
|
||||
#[instrument(skip(args), fields(target_id_as_string = %id))]
|
||||
pub fn new(id: String, args: RedisArgs) -> Result<Self, TargetError> {
|
||||
@@ -518,7 +517,7 @@ where
|
||||
#[async_trait]
|
||||
impl<E> Target<E> for RedisTarget<E>
|
||||
where
|
||||
E: Send + Sync + 'static + Clone + Serialize + DeserializeOwned,
|
||||
E: PluginEvent,
|
||||
{
|
||||
fn id(&self) -> TargetID {
|
||||
self.id.clone()
|
||||
@@ -796,7 +795,7 @@ fn compute_retry_delay(attempt: usize, min_delay: Duration, max_delay: Duration)
|
||||
#[async_trait]
|
||||
impl<E> ReloadableTargetTls for RedisTarget<E>
|
||||
where
|
||||
E: Send + Sync + 'static + Clone + Serialize + DeserializeOwned,
|
||||
E: PluginEvent,
|
||||
{
|
||||
type Material = Client;
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use crate::plugin::PluginEvent;
|
||||
use crate::{
|
||||
StoreError, Target,
|
||||
arn::TargetID,
|
||||
@@ -32,8 +33,6 @@ use parking_lot::Mutex;
|
||||
use reqwest::{Client, StatusCode, Url};
|
||||
use rustfs_tls_runtime::load_cert_bundle_der_bytes;
|
||||
use rustfs_utils::egress::validate_outbound_url;
|
||||
use serde::Serialize;
|
||||
use serde::de::DeserializeOwned;
|
||||
use std::{
|
||||
fmt,
|
||||
marker::PhantomData,
|
||||
@@ -132,7 +131,7 @@ impl WebhookArgs {
|
||||
/// A target that sends events to a webhook
|
||||
pub struct WebhookTarget<E>
|
||||
where
|
||||
E: Send + Sync + 'static + Clone + Serialize + DeserializeOwned,
|
||||
E: PluginEvent,
|
||||
{
|
||||
id: TargetID,
|
||||
args: WebhookArgs,
|
||||
@@ -152,7 +151,7 @@ where
|
||||
|
||||
impl<E> WebhookTarget<E>
|
||||
where
|
||||
E: Send + Sync + 'static + Clone + Serialize + DeserializeOwned,
|
||||
E: PluginEvent,
|
||||
{
|
||||
/// Clones the WebhookTarget, creating a new instance with the same configuration
|
||||
pub fn clone_box(&self) -> Box<dyn Target<E> + Send + Sync> {
|
||||
@@ -476,7 +475,7 @@ where
|
||||
#[async_trait]
|
||||
impl<E> Target<E> for WebhookTarget<E>
|
||||
where
|
||||
E: Send + Sync + 'static + Clone + Serialize + DeserializeOwned,
|
||||
E: PluginEvent,
|
||||
{
|
||||
fn id(&self) -> TargetID {
|
||||
self.id.clone()
|
||||
@@ -643,7 +642,7 @@ where
|
||||
#[async_trait]
|
||||
impl<E> ReloadableTargetTls for WebhookTarget<E>
|
||||
where
|
||||
E: Send + Sync + 'static + Clone + Serialize + DeserializeOwned,
|
||||
E: PluginEvent,
|
||||
{
|
||||
type Material = Client;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user