Skip to content

Commit 391b790

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

File tree

7 files changed

+149
-46
lines changed

7 files changed

+149
-46
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: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
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::runtime::Builder::new_current_thread()
27+
.enable_all()
28+
.build()
29+
.expect("failed to build runtime")
30+
.block_on(async {
31+
config
32+
.into_storage(false)
33+
.await
34+
.expect("Failed to create storage")
35+
})
36+
}
37+
38+
impl<M> MigrationWithData<M>
39+
where
40+
M: MigrationTraitWithData,
41+
{
42+
#[allow(clippy::expect_used)]
43+
pub fn new(migration: M) -> Self {
44+
Self {
45+
storage: STORAGE.clone(),
46+
migration,
47+
}
48+
}
49+
}
50+
51+
impl<M> From<M> for MigrationWithData<M>
52+
where
53+
M: MigrationTraitWithData,
54+
{
55+
fn from(value: M) -> Self {
56+
MigrationWithData::new(value)
57+
}
58+
}
59+
60+
pub struct SchemaDataManager<'c> {
61+
pub manager: &'c SchemaManager<'c>,
62+
storage: &'c DispatchBackend,
63+
}
64+
65+
impl<'c> SchemaDataManager<'c> {
66+
pub fn new(manager: &'c SchemaManager<'c>, storage: &'c DispatchBackend) -> Self {
67+
Self { manager, storage }
68+
}
69+
70+
pub async fn process<D>(&self, f: impl Handler<D>) -> Result<(), DbErr>
71+
where
72+
D: Document,
73+
{
74+
self.manager.process(self.storage, f).await
75+
}
76+
}
77+
78+
#[async_trait::async_trait]
79+
pub trait MigrationTraitWithData {
80+
async fn up(&self, manager: &SchemaDataManager) -> Result<(), DbErr>;
81+
async fn down(&self, manager: &SchemaDataManager) -> Result<(), DbErr>;
82+
}
83+
84+
#[async_trait::async_trait]
85+
impl<M> MigrationTrait for MigrationWithData<M>
86+
where
87+
M: MigrationTraitWithData + MigrationName + Send + Sync,
88+
{
89+
async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
90+
self.migration
91+
.up(&SchemaDataManager::new(manager, &self.storage))
92+
.await
93+
}
94+
95+
async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
96+
self.migration
97+
.down(&SchemaDataManager::new(manager, &self.storage))
98+
.await
99+
}
100+
}
101+
102+
impl<M> MigrationName for MigrationWithData<M>
103+
where
104+
M: MigrationTraitWithData + MigrationName + Send + Sync,
105+
{
106+
fn name(&self) -> &str {
107+
self.migration.name()
108+
}
109+
}

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
}

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)