Skip to content

Commit ef04e5d

Browse files
committed
feat: allow running data migrations as part of migrations
1 parent 5855e02 commit ef04e5d

File tree

8 files changed

+157
-60
lines changed

8 files changed

+157
-60
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

migration/Cargo.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,10 @@ trustify-common = { workspace = true }
1414
trustify-entity = { workspace = true }
1515
trustify-module-storage = { workspace = true }
1616

17+
anyhow = { workspace = true }
1718
bytes = { workspace = true }
19+
clap = { workspace = true, features = ["derive", "env"] }
1820
futures-util = { workspace = true }
19-
anyhow = { workspace = true }
2021
sea-orm = { workspace = true }
2122
sea-orm-migration = { workspace = true, features = ["runtime-tokio-rustls", "sqlx-postgres", "with-uuid"] }
2223
serde-cyclonedx = { workspace = true }

migration/src/data/migration.rs

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
use crate::{
2+
async_trait,
3+
data::{Document, DocumentProcessor, Handler},
4+
};
5+
use clap::Parser;
6+
use sea_orm::DbErr;
7+
use sea_orm_migration::{MigrationName, MigrationTrait, SchemaManager};
8+
use std::{ffi::OsString, sync::LazyLock};
9+
use trustify_module_storage::{config::StorageConfig, service::dispatch::DispatchBackend};
10+
11+
pub struct MigrationWithData<M>
12+
where
13+
M: MigrationTraitWithData,
14+
{
15+
pub storage: DispatchBackend,
16+
pub migration: M,
17+
}
18+
19+
static STORAGE: LazyLock<DispatchBackend> = LazyLock::new(init_storage);
20+
21+
#[allow(clippy::expect_used)]
22+
fn init_storage() -> DispatchBackend {
23+
// create from env-vars only
24+
let config = StorageConfig::parse_from::<_, OsString>(vec![]);
25+
26+
tokio::task::block_in_place(|| {
27+
tokio::runtime::Handle::current().block_on(async {
28+
config
29+
.into_storage(false)
30+
.await
31+
.expect("Failed to create storage")
32+
})
33+
})
34+
}
35+
36+
impl<M> MigrationWithData<M>
37+
where
38+
M: MigrationTraitWithData,
39+
{
40+
#[allow(clippy::expect_used)]
41+
pub fn new(migration: M) -> Self {
42+
Self {
43+
storage: STORAGE.clone(),
44+
migration,
45+
}
46+
}
47+
}
48+
49+
impl<M> From<M> for MigrationWithData<M>
50+
where
51+
M: MigrationTraitWithData,
52+
{
53+
fn from(value: M) -> Self {
54+
MigrationWithData::new(value)
55+
}
56+
}
57+
58+
pub struct SchemaDataManager<'c> {
59+
pub manager: &'c SchemaManager<'c>,
60+
storage: &'c DispatchBackend,
61+
}
62+
63+
impl<'c> SchemaDataManager<'c> {
64+
pub fn new(manager: &'c SchemaManager<'c>, storage: &'c DispatchBackend) -> Self {
65+
Self { manager, storage }
66+
}
67+
68+
pub async fn process<D>(&self, f: impl Handler<D>) -> Result<(), DbErr>
69+
where
70+
D: Document,
71+
{
72+
self.manager.process(self.storage, f).await
73+
}
74+
}
75+
76+
#[async_trait::async_trait]
77+
pub trait MigrationTraitWithData {
78+
async fn up(&self, manager: &SchemaDataManager) -> Result<(), DbErr>;
79+
async fn down(&self, manager: &SchemaDataManager) -> Result<(), DbErr>;
80+
}
81+
82+
#[async_trait::async_trait]
83+
impl<M> MigrationTrait for MigrationWithData<M>
84+
where
85+
M: MigrationTraitWithData + MigrationName + Send + Sync,
86+
{
87+
async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
88+
self.migration
89+
.up(&SchemaDataManager::new(manager, &self.storage))
90+
.await
91+
}
92+
93+
async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
94+
self.migration
95+
.down(&SchemaDataManager::new(manager, &self.storage))
96+
.await
97+
}
98+
}
99+
100+
impl<M> MigrationName for MigrationWithData<M>
101+
where
102+
M: MigrationTraitWithData + MigrationName + Send + Sync,
103+
{
104+
fn name(&self) -> &str {
105+
self.migration.name()
106+
}
107+
}

migration/src/data/mod.rs

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,13 @@
1+
mod migration;
2+
3+
pub use migration::*;
4+
15
use anyhow::{anyhow, bail};
26
use bytes::BytesMut;
3-
use futures_util::stream::TryStreamExt;
4-
use futures_util::{StreamExt, stream};
7+
use futures_util::{
8+
StreamExt,
9+
stream::{self, TryStreamExt},
10+
};
511
use sea_orm::{
612
ConnectionTrait, DatabaseTransaction, DbErr, EntityTrait, ModelTrait, TransactionTrait,
713
};
@@ -16,6 +22,7 @@ pub enum Sbom {
1622
Spdx(spdx_rs::models::SPDX),
1723
}
1824

25+
#[allow(async_fn_in_trait)]
1926
pub trait Document: Sized + Send + Sync {
2027
type Model: Send;
2128

@@ -70,6 +77,7 @@ impl Document for Sbom {
7077
}
7178
}
7279

80+
#[allow(async_fn_in_trait)]
7381
pub trait Handler<D>: Send
7482
where
7583
D: Document,
@@ -93,11 +101,7 @@ pub trait DocumentProcessor {
93101
}
94102

95103
impl<'c> DocumentProcessor for SchemaManager<'c> {
96-
async fn process<D>(
97-
&self,
98-
storage: &DispatchBackend,
99-
f: impl Handler<D>,
100-
) -> anyhow::Result<(), DbErr>
104+
async fn process<D>(&self, storage: &DispatchBackend, f: impl Handler<D>) -> Result<(), DbErr>
101105
where
102106
D: Document,
103107
{

migration/src/lib.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
pub use sea_orm_migration::prelude::*;
22

33
mod data;
4+
use crate::data::MigrationWithData;
45

56
mod m0000010_init;
67
mod m0000020_add_sbom_group;
@@ -60,7 +61,9 @@ impl MigratorTrait for Migrator {
6061
Box::new(m0001110_sbom_node_checksum_indexes::Migration),
6162
Box::new(m0001120_sbom_external_node_indexes::Migration),
6263
Box::new(m0001130_gover_cmp::Migration),
63-
Box::new(m0001140_example_data_migration::Migration),
64+
Box::new(MigrationWithData::new(
65+
m0001140_example_data_migration::Migration,
66+
)),
6467
]
6568
}
6669
}
Lines changed: 18 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,52 +1,40 @@
11
use crate::{
2-
data::{DocumentProcessor, Sbom},
2+
data::{MigrationTraitWithData, Sbom, SchemaDataManager},
33
sbom,
44
sea_orm::{ActiveModelTrait, IntoActiveModel, Set},
55
};
66
use sea_orm_migration::prelude::*;
7-
use trustify_module_storage::service::{dispatch::DispatchBackend, fs::FileSystemBackend};
87

98
#[derive(DeriveMigrationName)]
109
pub struct Migration;
1110

1211
#[async_trait::async_trait]
13-
impl MigrationTrait for Migration {
14-
async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
15-
// TODO: make this configurable
16-
let (storage, _tmp) = FileSystemBackend::for_test()
17-
.await
18-
.map_err(|err| DbErr::Migration(format!("failed to create storage backend: {err}")))?;
19-
let storage = DispatchBackend::Filesystem(storage);
20-
21-
// process data
22-
12+
impl MigrationTraitWithData for Migration {
13+
async fn up(&self, manager: &SchemaDataManager) -> Result<(), DbErr> {
2314
manager
24-
.process(
25-
&storage,
26-
sbom!(async |sbom, model, tx| {
27-
let mut model = model.into_active_model();
28-
match sbom {
29-
Sbom::CycloneDx(_sbom) => {
30-
// TODO: just an example
31-
model.authors = Set(vec![]);
32-
}
33-
Sbom::Spdx(_sbom) => {
34-
// TODO: just an example
35-
model.authors = Set(vec![]);
36-
}
15+
.process(sbom!(async |sbom, model, tx| {
16+
let mut model = model.into_active_model();
17+
match sbom {
18+
Sbom::CycloneDx(_sbom) => {
19+
// TODO: just an example
20+
model.authors = Set(vec![]);
21+
}
22+
Sbom::Spdx(_sbom) => {
23+
// TODO: just an example
24+
model.authors = Set(vec![]);
3725
}
26+
}
3827

39-
model.save(tx).await?;
28+
model.save(tx).await?;
4029

41-
Ok(())
42-
}),
43-
)
30+
Ok(())
31+
}))
4432
.await?;
4533

4634
Ok(())
4735
}
4836

49-
async fn down(&self, _manager: &SchemaManager) -> Result<(), DbErr> {
37+
async fn down(&self, _manager: &SchemaDataManager) -> Result<(), DbErr> {
5038
Ok(())
5139
}
5240
}

server/src/profile/api.rs

Lines changed: 10 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,10 @@ use actix_web::middleware;
66

77
use crate::{endpoints, profile::spawn_db_check, sample_data};
88
use actix_web::web;
9-
use anyhow::Context;
109
use bytesize::ByteSize;
1110
use futures::FutureExt;
1211
use humantime::parse_duration;
13-
use std::{env, fs::create_dir_all, path::PathBuf, process::ExitCode, sync::Arc};
12+
use std::{env, process::ExitCode, sync::Arc};
1413
use tokio_schedule::{Job, every};
1514
use trustify_auth::{
1615
auth::AuthConfigArguments,
@@ -32,10 +31,7 @@ use trustify_infrastructure::{
3231
use trustify_module_analysis::{config::AnalysisConfig, service::AnalysisService};
3332
use trustify_module_fundamental::purl::service::PurlService;
3433
use trustify_module_ingestor::graph::Graph;
35-
use trustify_module_storage::{
36-
config::{StorageConfig, StorageStrategy},
37-
service::{dispatch::DispatchBackend, fs::FileSystemBackend, s3::S3Backend},
38-
};
34+
use trustify_module_storage::{config::StorageConfig, service::dispatch::DispatchBackend};
3935
use trustify_module_ui::{UI, endpoints::UiResources};
4036
use utoipa::openapi::{Info, License};
4137

@@ -337,7 +333,7 @@ impl InitData {
337333
let _ = embedded_oidc.0.await?;
338334
Ok::<_, anyhow::Error>(())
339335
}
340-
.boxed_local(),
336+
.boxed_local(),
341337
);
342338
}
343339

@@ -406,11 +402,11 @@ pub(crate) struct Config {
406402
pub(crate) fn configure(svc: &mut utoipa_actix_web::service_config::ServiceConfig, config: Config) {
407403
let Config {
408404
config:
409-
ModuleConfig {
410-
ingestor,
411-
fundamental,
412-
ui,
413-
},
405+
ModuleConfig {
406+
ingestor,
407+
fundamental,
408+
ui,
409+
},
414410
db,
415411
storage,
416412
auth,
@@ -437,7 +433,7 @@ pub(crate) fn configure(svc: &mut utoipa_actix_web::service_config::ServiceConfi
437433
svc.wrap(middleware::NormalizePath::new(
438434
middleware::TrailingSlash::Always,
439435
))
440-
.wrap(new_auth(auth.clone()))
436+
.wrap(new_auth(auth.clone()))
441437
})
442438
.configure(|svc| {
443439
trustify_module_graphql::endpoints::configure(svc, db.clone());
@@ -559,7 +555,7 @@ mod test {
559555
.apply_openapi(None, None)
560556
.configure(|svc| post_configure(svc, PostConfig { ui })),
561557
)
562-
.await;
558+
.await;
563559

564560
// main UI
565561

trustd/src/db.rs

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,6 @@
11
use postgresql_embedded::{PostgreSQL, VersionReq};
2-
use std::collections::HashMap;
3-
use std::env;
4-
use std::fs::create_dir_all;
5-
use std::process::ExitCode;
6-
use std::time::Duration;
7-
use trustify_common::config::Database;
8-
use trustify_common::db;
2+
use std::{collections::HashMap, env, fs::create_dir_all, process::ExitCode, time::Duration};
3+
use trustify_common::{config::Database, db};
94
use trustify_infrastructure::otel::{Tracing, init_tracing};
105

116
#[derive(clap::Args, Debug)]
@@ -40,6 +35,7 @@ impl Run {
4035
Err(e) => Err(e),
4136
}
4237
}
38+
4339
async fn refresh(self) -> anyhow::Result<ExitCode> {
4440
match db::Database::new(&self.database).await {
4541
Ok(db) => {
@@ -49,6 +45,7 @@ impl Run {
4945
Err(e) => Err(e),
5046
}
5147
}
48+
5249
async fn migrate(self) -> anyhow::Result<ExitCode> {
5350
match db::Database::new(&self.database).await {
5451
Ok(db) => {

0 commit comments

Comments
 (0)