relay_server/services/projects/source/
mod.rs1use relay_base_schema::project::ProjectKey;
2use relay_config::{Config, RelayMode};
3#[cfg(feature = "processing")]
4use relay_redis::RedisClients;
5use relay_system::{Addr, ServiceSpawn, ServiceSpawnExt as _};
6use std::convert::Infallible;
7use std::sync::Arc;
8
9#[cfg(feature = "processing")]
10pub mod redis;
11pub mod upstream;
12
13use crate::services::projects::project::{ProjectState, Revision};
14use crate::services::upstream::UpstreamRelay;
15
16#[cfg(feature = "processing")]
17use self::redis::RedisProjectSource;
18use self::upstream::{UpstreamProjectSource, UpstreamProjectSourceService};
19
20#[derive(Clone, Debug)]
22pub struct ProjectSource {
23 config: Arc<Config>,
24 upstream_source: Addr<UpstreamProjectSource>,
25 #[cfg(feature = "processing")]
26 redis_source: Option<RedisProjectSource>,
27}
28
29impl ProjectSource {
30 pub async fn start_in(
32 services: &dyn ServiceSpawn,
33 config: Arc<Config>,
34 upstream_relay: Addr<UpstreamRelay>,
35 #[cfg(feature = "processing")] _redis: Option<RedisClients>,
36 ) -> Self {
37 let upstream_source = services.start(UpstreamProjectSourceService::new(
38 config.clone(),
39 upstream_relay,
40 ));
41
42 #[cfg(feature = "processing")]
43 let redis_source =
44 _redis.map(|pool| RedisProjectSource::new(config.clone(), pool.project_configs));
45
46 Self {
47 config,
48 upstream_source,
49 #[cfg(feature = "processing")]
50 redis_source,
51 }
52 }
53
54 pub async fn fetch(
58 self,
59 project_key: ProjectKey,
60 no_cache: bool,
61 current_revision: Revision,
62 ) -> Result<SourceProjectState, ProjectSourceError> {
63 let config = self.config.current();
64
65 match config.relay_mode() {
66 RelayMode::Proxy => return Ok(ProjectState::Dummy.into()),
67 RelayMode::Managed => (), }
69
70 #[cfg(feature = "processing")]
71 if let Some(redis_source) = self.redis_source {
72 let current_revision = current_revision.clone();
73
74 let state_fetch_result = redis_source
75 .get_config_if_changed(project_key, current_revision)
76 .await;
77
78 match state_fetch_result {
79 Ok(SourceProjectState::New(state)) => {
83 let state = state.sanitized(config.processing_enabled());
84 if !state.is_pending() {
85 return Ok(state.into());
86 }
87 }
88 Ok(SourceProjectState::NotModified) => return Ok(SourceProjectState::NotModified),
91 Err(error) => {
92 relay_log::error!(
93 error = &error as &dyn std::error::Error,
94 "failed to fetch project from Redis",
95 );
96 }
97 };
98 };
99
100 let state = self
101 .upstream_source
102 .send(FetchProjectState {
103 project_key,
104 current_revision,
105 no_cache,
106 })
107 .await
108 .map_err(|_| ProjectSourceError::FatalUpstream)??;
109
110 Ok(match state {
111 SourceProjectState::New(state) => {
112 SourceProjectState::New(state.sanitized(config.processing_enabled()))
113 }
114 SourceProjectState::NotModified => SourceProjectState::NotModified,
115 })
116 }
117}
118
119#[derive(Debug, thiserror::Error)]
120pub enum ProjectSourceError {
121 #[error("upstream error: {0}")]
123 Upstream(#[from] upstream::Error),
124 #[error("fatal upstream error")]
129 FatalUpstream,
130}
131
132impl From<Infallible> for ProjectSourceError {
133 fn from(value: Infallible) -> Self {
134 match value {}
135 }
136}
137
138#[derive(Clone, Debug)]
139pub struct FetchProjectState {
140 pub project_key: ProjectKey,
142
143 pub current_revision: Revision,
149
150 pub no_cache: bool,
152}
153
154#[derive(Debug, Clone)]
157pub enum SourceProjectState {
158 New(ProjectState),
160 NotModified,
162}
163
164impl From<ProjectState> for SourceProjectState {
165 fn from(value: ProjectState) -> Self {
166 Self::New(value)
167 }
168}