Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions api/migrations/20251210154013_Back_up_table.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
CREATE TABLE app_backup (
app_name VARCHAR(512) PRIMARY KEY,
app JSONB NOT NULL,
infrastructure_payload JSONB NOT NULL,
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW()
);
71 changes: 67 additions & 4 deletions api/res/openapi.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,18 @@ paths:
'200':
description: ''
content:
application/vnd.prevant.v2+json:
schema:
type: object
additionalProperties:
$ref: '#/components/schemas/App'
application/json:
schema:
type: object
properties:
"^[a-zA-Z0-9_-]":
# TODO: This is an array and the documentation seems to be wrong all the time…
$ref: '#/components/schemas/Service'
additionalProperties:
type: array
items:
$ref: '#/components/schemas/Service'
'500':
description: Server error
content:
Expand Down Expand Up @@ -209,6 +214,29 @@ paths:
application/problem+json:
schema:
$ref: '#/components/schemas/ProblemDetails'
/apps/{appName}/states/:
put:
summary: Allows to change the state of an application to be deployed or backed up.
parameters:
- $ref: '#/components/parameters/appName'
- $ref: '#/components/parameters/preferAsync'
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
status:
$ref: '#/components/schemas/AppStatus'
responses:
'202':
description: ""
content:
application/json:
schema:
$ref: '#/components/schemas/App'

/apps/{appName}/states/{serviceName}/:
put:
summary: Changes the state of a service
Expand Down Expand Up @@ -377,6 +405,41 @@ components:
pattern: ^wait=(\d+)$
example: wait=20
schemas:
Owner:
type: object
properties:
sub:
type: string
example: alice
iss:
type: string
example: https://gitlab.com
name:
type: string
example: Alice
required:
- sub
- iss
App:
type: object
properties:
services:
type: array
items:
$ref: '#/components/schemas/Service'
owners:
type: array
items:
$ref: '#/components/schemas/Owner'
status:
$ref: '#/components/schemas/AppStatus'
required:
- services
AppStatus:
type: string
enum:
- deployed
- backed-up
Service:
type: object
properties:
Expand Down
52 changes: 44 additions & 8 deletions api/src/apps/host_meta_cache.rs
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,40 @@ pub fn new(config: Config) -> (HostMetaCache, HostMetaCrawler) {
}

impl HostMetaCache {
pub fn assign_host_meta_data_for_app(
&self,
app_name: &AppName,
app: App,
request_info: &RequestInfo,
) -> AppWithHostMeta {
let reader = self.reader_factory.handle();

let mut services_with_host_meta = Vec::with_capacity(app.services().len());

let (services, owners) = app.into_services_and_owners();
for service in services.into_iter() {
let service_id = service.id.clone();
let key = Key {
app_name: app_name.clone(),
service_id,
};

let web_host_meta = match reader.get_one(&key) {
Some(value) => value.web_host_meta.with_base_url(request_info.base_url()),
None => WebHostMeta::empty(),
};

services_with_host_meta.push(ServiceWithHostMeta::from_service_and_web_host_meta(
service,
web_host_meta,
request_info.base_url().clone(),
app_name,
));
}

AppWithHostMeta::new(services_with_host_meta, owners)
}

pub fn assign_host_meta_data(
&self,
apps: HashMap<AppName, App>,
Expand Down Expand Up @@ -319,14 +353,16 @@ impl HostMetaCrawler {
running_services_without_host_meta.into_iter()
{
let now = Utc::now();
debug!(
"Resolving web host meta data for app {app_name} and the services: {}.",
running_services_without_host_meta
.iter()
.map(|(_k, service)| service.service_name())
.fold(String::new(), |a, b| a + &b + ", ")
.trim_end_matches(", ")
);
if log::log_enabled!(log::Level::Debug) {
debug!(
"Resolving web host meta data for app {app_name} and the services: {}.",
running_services_without_host_meta
.iter()
.map(|(_k, service)| service.service_name())
.fold(String::new(), |a, b| a + b + ", ")
.trim_end_matches(", ")
);
}

let duration_prevant_startup = now.signed_duration_since(since_timestamp);
let resolved_host_meta_infos = Self::resolve_host_meta(
Expand Down
67 changes: 51 additions & 16 deletions api/src/apps/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
mod fairing;
mod host_meta_cache;
mod queue;
mod repository;
mod routes;

use crate::config::ReplicateApplicationCondition;
Expand All @@ -51,6 +52,7 @@ use log::error;
use log::trace;
pub use queue::AppProcessingQueue;
pub use queue::AppTaskQueueProducer;
pub use repository::AppRepository;
pub use routes::{apps_routes, delete_app_sync, AppV1};
use std::collections::{HashMap, HashSet};
use std::convert::From;
Expand All @@ -74,10 +76,7 @@ impl Clone for Apps {
}

impl Apps {
pub fn new(
config: Config,
infrastructure: Box<dyn Infrastructure>,
) -> Result<Apps, AppsError> {
pub fn new(config: Config, infrastructure: Box<dyn Infrastructure>) -> Result<Apps, AppsError> {
Ok(Apps {
config,
infrastructure,
Expand Down Expand Up @@ -121,12 +120,23 @@ impl Apps {
Ok(self.infrastructure.fetch_apps().await?)
}

pub async fn fetch_app_as_backup_based_infrastructure_payload(
&self,
app_name: &AppName,
) -> Result<Option<Vec<serde_json::Value>>, AppsError> {
Ok(self
.infrastructure
.fetch_app_as_backup_based_infrastructure_payload(app_name)
.await?)
}

/// Provides a [`Receiver`](tokio::sync::watch::Receiver) that notifies about changes of the
/// list of running [`apps`](AppsService::fetch_apps).
pub async fn app_updates(&self) -> Receiver<HashMap<AppName, App>> {
let infrastructure = dyn_clone::clone_box(&*self.infrastructure);
let (tx, rx) = tokio::sync::watch::channel::<HashMap<AppName, App>>(HashMap::new());

// TODO: we should return this and spawn on liftoff
tokio::spawn(async move {
loop {
debug!("Fetching list of apps to send updates.");
Expand Down Expand Up @@ -225,15 +235,13 @@ impl Apps {
) {
(None, _) => None,
(Some(validator), None) => Some(
UserDefinedParameters::new(serde_json::json!({}), &validator).map_err(|e| {
AppsError::InvalidUserDefinedParameters { err: e.to_string() }
})?,
UserDefinedParameters::new(serde_json::json!({}), &validator)
.map_err(|e| AppsError::InvalidUserDefinedParameters { err: e.to_string() })?,
),
(Some(validator), Some(value)) => Some(
UserDefinedParameters::new(value, &validator)
.map_err(|e| AppsError::InvalidUserDefinedParameters { err: e.to_string() })?,
),
(Some(validator), Some(value)) => {
Some(UserDefinedParameters::new(value, &validator).map_err(|e| {
AppsError::InvalidUserDefinedParameters { err: e.to_string() }
})?)
}
};

if let Some(app_limit) = self.config.app_limit() {
Expand Down Expand Up @@ -368,6 +376,17 @@ impl Apps {
Ok(apps)
}

async fn restore_app_partially(
&self,
app_name: &AppName,
infrastructure_payload: &[serde_json::Value],
) -> Result<App, AppsError> {
Ok(self
.infrastructure
.restore_infrastructure_objects_partially(app_name, infrastructure_payload)
.await?)
}

/// Deletes all services for the given `app_name`.
async fn delete_app(&self, app_name: &AppName) -> Result<App, AppsError> {
let app = self.infrastructure.stop_services(app_name).await?;
Expand All @@ -381,6 +400,24 @@ impl Apps {
}
}

async fn delete_app_partially(
&self,
app_name: &AppName,
infrastructure_payload: &[serde_json::Value],
) -> Result<App, AppsError> {
let Some(app) = self.infrastructure.fetch_app(app_name).await? else {
return Err(AppsError::AppNotFound {
app_name: app_name.clone(),
});
};

self.infrastructure
.delete_infrastructure_objects_partially(app_name, infrastructure_payload)
.await?;

Ok(app)
}

pub async fn stream_logs<'a>(
&'a self,
app_name: &'a AppName,
Expand Down Expand Up @@ -1379,8 +1416,7 @@ Log msg 3 of service-a of app master
}

#[tokio::test]
async fn do_not_create_app_when_exceeding_application_number_limit(
) -> Result<(), AppsError> {
async fn do_not_create_app_when_exceeding_application_number_limit() -> Result<(), AppsError> {
let config = config_from_str!(
r#"
[applications]
Expand Down Expand Up @@ -1418,8 +1454,7 @@ Log msg 3 of service-a of app master
}

#[tokio::test]
async fn do_update_app_when_exceeding_application_number_limit() -> Result<(), AppsError>
{
async fn do_update_app_when_exceeding_application_number_limit() -> Result<(), AppsError> {
let config = config_from_str!(
r#"
[applications]
Expand Down
Loading
Loading