Skip to content

Commit 431035e

Browse files
committed
feat: automatic superposition migrations
1 parent bdcba5f commit 431035e

File tree

7 files changed

+356
-200
lines changed

7 files changed

+356
-200
lines changed

airborne_server/.env.example

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,4 +42,5 @@ GCP_SERVICE_ACCOUNT_PATH=/app/airborne-gcp.json
4242

4343
RUST_LOG=debug,info,error,actix_web=info,error
4444
LOG_FORMAT=
45-
RUN_DB_MIGRATIONS=true
45+
SUPERPOSITION_MIGRATION_STRATEGY=PATCH
46+
MIGRATIONS_TO_RUN_ON_BOOT=db,superposition

airborne_server/src/main.rs

Lines changed: 32 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -44,9 +44,14 @@ use superposition_sdk::config::Config as SrsConfig;
4444
use tracing_actix_web::TracingLogger;
4545
use utils::{db, kms::decrypt_kms, transaction_manager::start_cleanup_job};
4646

47-
use crate::dashboard::configuration;
4847
use crate::middleware::auth::Auth;
4948
use crate::middleware::request::request_id_mw;
49+
use crate::{
50+
dashboard::configuration,
51+
utils::migrations::{
52+
get_default_configs_from_file, migrate_superposition, SuperpositionMigrationStrategy,
53+
},
54+
};
5055

5156
const MIGRATIONS: EmbeddedMigrations = embed_migrations!();
5257

@@ -109,10 +114,15 @@ async fn main() -> std::io::Result<()> {
109114
let backlog = std::env::var("BACKLOG")
110115
.map_or(1024, |v| v.parse().expect("BACKLOG must be a valid number"));
111116

112-
let run_pending_migrations = std::env::var("RUN_DB_MIGRATIONS")
113-
.ok()
114-
.and_then(|v| v.parse::<bool>().ok())
115-
.unwrap_or_default();
117+
let superposition_migration_strategy = SuperpositionMigrationStrategy::from(
118+
std::env::var("SUPERPOSITION_MIGRATION_STRATEGY").unwrap_or_else(|_| "PATCH".into()),
119+
);
120+
121+
let migrations_to_run_on_boot: Vec<String> = std::env::var("MIGRATIONS_TO_RUN_ON_BOOT")
122+
.unwrap_or_else(|_| "".into())
123+
.split(',')
124+
.map(|s| s.trim().into())
125+
.collect();
116126

117127
//Need to check if this ENV exists on pod
118128
let uses_local_stack = std::env::var("AWS_ENDPOINT_URL");
@@ -126,7 +136,7 @@ async fn main() -> std::io::Result<()> {
126136
let aws_kms_client = aws_sdk_kms::Client::new(&shared_config);
127137
let aws_cloudfront_client = aws_sdk_cloudfront::Client::new(&shared_config);
128138

129-
if run_pending_migrations {
139+
if migrations_to_run_on_boot.contains(&"db".to_string()) {
130140
info!("Running pending database migrations");
131141
let mut conn = db::establish_connection(&aws_kms_client).await;
132142
conn.run_pending_migrations(MIGRATIONS)
@@ -174,6 +184,9 @@ async fn main() -> std::io::Result<()> {
174184
organisation_creation_disabled,
175185
google_spreadsheet_id: spreadsheet_id.clone(),
176186
cloudfront_distribution_id: cf_distribution_id.clone(),
187+
default_configs: get_default_configs_from_file()
188+
.await
189+
.expect("Failed to load superposition default configs from file"),
177190
};
178191

179192
// This is required for localStack
@@ -233,6 +246,19 @@ async fn main() -> std::io::Result<()> {
233246
info!("Started transaction cleanup background job");
234247
info!("Using server prefix {}", server_path_prefix);
235248

249+
if migrations_to_run_on_boot.contains(&"superposition".to_string()) {
250+
let superposition_migration =
251+
migrate_superposition(&app_state_data, superposition_migration_strategy).await;
252+
if superposition_migration.is_err() {
253+
panic!(
254+
"Superposition migration failed: {:?}",
255+
superposition_migration.err()
256+
);
257+
} else {
258+
println!("Superposition migration completed successfully");
259+
}
260+
}
261+
236262
HttpServer::new(move || {
237263
App::new()
238264
.wrap(actix_web::middleware::from_fn(request_id_mw))

airborne_server/src/organisation/application.rs

Lines changed: 22 additions & 192 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,8 @@ use superposition_sdk::Client;
2929

3030
use crate::middleware::auth::{validate_user, AuthResponse, ADMIN};
3131
use crate::types::{ABError, AppState};
32-
use crate::utils::document::{schema_doc_to_hashmap, value_to_document};
32+
use crate::utils::document::schema_doc_to_hashmap;
33+
use crate::utils::migrations::{migrate_superposition_workspace, SuperpositionMigrationStrategy};
3334
use diesel::RunQueryDsl;
3435

3536
use crate::utils::db::models::{NewWorkspaceName, WorkspaceName};
@@ -66,7 +67,7 @@ struct ApplicationCreateRequest {
6667
application: String,
6768
}
6869

69-
fn default_config<T: Clone>(
70+
pub fn default_config<T: Clone>(
7071
superposition_client: Client,
7172
workspace_name: String,
7273
superposition_org: String,
@@ -321,7 +322,7 @@ async fn add_application(
321322
superposition_org_id_from_env
322323
);
323324
// Insert and get the inserted row (to get the id)
324-
let inserted_workspace: WorkspaceName = diesel::insert_into(workspace_names::table)
325+
let mut inserted_workspace: WorkspaceName = diesel::insert_into(workspace_names::table)
325326
.values(&new_workspace_name)
326327
.get_result(&mut conn)
327328
.map_err(|e| {
@@ -330,6 +331,7 @@ async fn add_application(
330331

331332
let generated_id = inserted_workspace.id;
332333
let generated_workspace_name = format!("workspace{}", generated_id);
334+
inserted_workspace.workspace_name = generated_workspace_name.clone();
333335

334336
// Update the workspace_name to "workspace{id}"
335337
diesel::update(workspace_names::table.filter(workspace_names::id.eq(generated_id)))
@@ -375,37 +377,15 @@ async fn add_application(
375377
}
376378
};
377379

378-
// Step 5: Create default configurations
379-
let create_default_config_string = default_config::<String>(
380-
state.superposition_client.clone(),
381-
generated_workspace_name.clone(),
382-
superposition_org_id_from_env.clone(), // Use ID from env
383-
);
384-
let create_default_config_int = default_config::<i32>(
385-
state.superposition_client.clone(),
386-
generated_workspace_name.clone(),
387-
superposition_org_id_from_env.clone(), // Use ID from env
388-
);
389-
let create_default_config_doc = default_config::<Document>(
390-
state.superposition_client.clone(),
391-
generated_workspace_name.clone(),
392-
superposition_org_id_from_env.clone(), // Use ID from env
393-
);
394-
let create_default_config_array = default_config::<Vec<Document>>(
395-
state.superposition_client.clone(),
396-
generated_workspace_name.clone(),
397-
superposition_org_id_from_env.clone(), // Use ID from env
398-
);
399-
400380
// Helper function to create default config with error handling
401-
async fn create_config_with_tx<T, E>(
402-
create_fn: impl futures::Future<Output = Result<T, E>>,
381+
async fn create_config_with_tx<E>(
382+
create_fn: impl futures::Future<Output = Result<(), E>>,
403383
key: &str,
404384
transaction: &TransactionManager,
405385
admin: &KeycloakAdmin,
406386
realm: &str,
407387
state: &web::Data<AppState>,
408-
) -> Result<T, actix_web::Error>
388+
) -> Result<(), ABError>
409389
where
410390
E: std::fmt::Display,
411391
{
@@ -423,183 +403,33 @@ async fn add_application(
423403
info!("Rollback failed: {}", rollback_err);
424404
}
425405

426-
Err(error::ErrorInternalServerError(format!(
406+
Err(ABError::InternalServerError(format!(
427407
"Failed to create configuration for {}: {}",
428408
key, e
429409
)))
430410
}
431411
}
432412
}
433413

434-
// Create all configurations with transaction-aware error handling
435-
create_config_with_tx(
436-
create_default_config_string(
437-
"config.version".to_string(),
438-
"0.0.0".to_string(),
439-
"Value indicating the version of the release config -> config".to_string(),
440-
),
441-
"config.version",
442-
&transaction,
443-
&admin,
444-
&realm,
445-
&state,
446-
)
447-
.await
448-
.map_err(|e| ABError::InternalServerError(format!("{}", e)))?;
449-
450414
create_config_with_tx(
451-
create_default_config_int(
452-
"config.release_config_timeout".to_string(),
453-
1000,
454-
"Value indicating the version of the release config".to_string(),
455-
),
456-
"config.release_config_timeout",
457-
&transaction,
458-
&admin,
459-
&realm,
460-
&state,
461-
)
462-
.await
463-
.map_err(|e| ABError::InternalServerError(format!("{}", e)))?;
464-
465-
create_config_with_tx(
466-
create_default_config_int(
467-
"config.boot_timeout".to_string(),
468-
1000,
469-
"Indicating the timeout for downloading the package block".to_string(),
470-
),
471-
"config.boot_timeout",
472-
&transaction,
473-
&admin,
474-
&realm,
475-
&state,
476-
)
477-
.await
478-
.map_err(|e| ABError::InternalServerError(format!("{}", e)))?;
479-
480-
create_config_with_tx(
481-
create_default_config_doc(
482-
"config.properties".to_string(),
483-
value_to_document(&serde_json::json!({})),
484-
"Value indicating the properties of the config".to_string(),
485-
),
486-
"config.properties",
487-
&transaction,
488-
&admin,
489-
&realm,
490-
&state,
491-
)
492-
.await
493-
.map_err(|e| ABError::InternalServerError(format!("{}", e)))?;
494-
495-
info!(
496-
"Creating default configuration (string): key=package.name, value={}",
497-
generated_workspace_name
498-
);
499-
create_config_with_tx(
500-
create_default_config_string(
501-
"package.name".to_string(),
502-
generated_workspace_name.clone(),
503-
"Value indicating the version of the release config".to_string(),
504-
),
505-
"package.name",
506-
&transaction,
507-
&admin,
508-
&realm,
509-
&state,
510-
)
511-
.await
512-
.map_err(|e| ABError::InternalServerError(format!("{}", e)))?;
513-
514-
create_config_with_tx(
515-
create_default_config_int(
516-
"package.version".to_string(),
517-
0,
518-
"Value indicating the version of the package".to_string(),
519-
),
520-
"package.version",
521-
&transaction,
522-
&admin,
523-
&realm,
524-
&state,
525-
)
526-
.await
527-
.map_err(|e| ABError::InternalServerError(format!("{}", e)))?;
528-
529-
create_config_with_tx(
530-
create_default_config_doc(
531-
"package.properties".to_string(),
532-
value_to_document(&serde_json::json!({})),
533-
"Value indicating the properties of the package".to_string(),
534-
),
535-
"package.properties",
536-
&transaction,
537-
&admin,
538-
&realm,
539-
&state,
540-
)
541-
.await
542-
.map_err(|e| ABError::InternalServerError(format!("{}", e)))?;
543-
544-
create_config_with_tx(
545-
create_default_config_string(
546-
"package.index".to_string(),
547-
"".to_string(),
548-
"Value indicating the index of the package".to_string(),
549-
),
550-
"package.index",
551-
&transaction,
552-
&admin,
553-
&realm,
554-
&state,
555-
)
556-
.await
557-
.map_err(|e| ABError::InternalServerError(format!("{}", e)))?;
558-
559-
create_config_with_tx(
560-
create_default_config_array(
561-
"package.important".to_string(),
562-
vec![],
563-
"Value indicating the important block of the package".to_string(),
564-
),
565-
"package.important",
566-
&transaction,
567-
&admin,
568-
&realm,
569-
&state,
570-
)
571-
.await
572-
.map_err(|e| ABError::InternalServerError(format!("{}", e)))?;
573-
574-
create_config_with_tx(
575-
create_default_config_array(
576-
"package.lazy".to_string(),
577-
vec![],
578-
"Value indicating the lazy block of the package".to_string(),
579-
),
580-
"package.lazy",
581-
&transaction,
582-
&admin,
583-
&realm,
584-
&state,
585-
)
586-
.await
587-
.map_err(|e| ABError::InternalServerError(format!("{}", e)))?;
588-
589-
create_config_with_tx(
590-
create_default_config_array(
591-
"resources".to_string(),
592-
vec![],
593-
"Value indicating the resources block of the release config".to_string(),
594-
),
595-
"resources",
415+
async {
416+
migrate_superposition_workspace(
417+
&inserted_workspace,
418+
&state,
419+
&SuperpositionMigrationStrategy::Patch,
420+
)
421+
.await
422+
.map_err(|e| {
423+
ABError::InternalServerError(format!("Workspace migration error: {}", e))
424+
})
425+
},
426+
"migrate_superposition_workspace",
596427
&transaction,
597428
&admin,
598429
&realm,
599430
&state,
600431
)
601-
.await
602-
.map_err(|e| ABError::InternalServerError(format!("{}", e)))?;
432+
.await?;
603433

604434
// Mark transaction as complete since all operations have succeeded
605435
transaction.set_database_inserted();

airborne_server/src/types.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ use serde::Serialize;
2020
use superposition_sdk::Client;
2121
use thiserror::Error;
2222

23-
use crate::utils::db;
23+
use crate::utils::{db, migrations::SuperpositionDefaultConfig};
2424

2525
#[derive(Clone)]
2626
pub struct AppState {
@@ -49,6 +49,7 @@ pub struct Environment {
4949
pub organisation_creation_disabled: bool,
5050
pub google_spreadsheet_id: String,
5151
pub cloudfront_distribution_id: String,
52+
pub default_configs: Vec<SuperpositionDefaultConfig>,
5253
}
5354
pub trait AppError: std::error::Error + Send + Sync + 'static {
5455
fn code(&self) -> &'static str;

airborne_server/src/utils.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ pub mod document;
2020
pub mod encryption;
2121
pub mod keycloak;
2222
pub mod kms;
23+
pub mod migrations;
2324
pub mod s3;
2425
pub mod semver;
2526
pub mod transaction_manager;

0 commit comments

Comments
 (0)