OXYGENHUB

Coverage / crates/hub/src/db/terraform.rs 95.3%

102 of 107 lines run, 16 of 36 functions · run #478 on main · run missed

1
//! Terraform and OpenTofu states (migration 0079, oxygenhub#252).
2
3
use super::{now_ms, Pool};
4
use sqlx::FromRow;
5
6
#[derive(Clone, Debug, FromRow)]
7
pub struct TfState {
8
    pub repo_id: i64,
9
    pub name: String,
10
    /// Sealed with the hub's secrets key; empty once deleted.
11
    pub body: Vec<u8>,
12
    pub serial: i64,
13
    pub lock_id: String,
14
    pub lock_info: String,
15
    pub locked_by: String,
16
    pub locked_ms: Option<i64>,
17
    pub updated_by: String,
18
    pub updated_ms: i64,
19
}
20
2119×
pub async fn get(pool: &Pool, repo_id: i64, name: &str) -> sqlx::Result<Option<TfState>> {
2219×
    sqlx::query_as("SELECT * FROM terraform_states WHERE repo_id = $1 AND name = $2").bind(repo_id).bind(name).fetch_optional(pool).await
2319×
}
24
253×
pub async fn of_repo(pool: &Pool, repo_id: i64) -> sqlx::Result<Vec<TfState>> {
263×
    sqlx::query_as("SELECT * FROM terraform_states WHERE repo_id = $1 ORDER BY name").bind(repo_id).fetch_all(pool).await
273×
}
28
29
/// How many earlier versions a state keeps.
301×
pub async fn versions(pool: &Pool, repo_id: i64, name: &str) -> sqlx::Result<i64> {
311×
    sqlx::query_scalar("SELECT COUNT(*) FROM terraform_state_versions WHERE repo_id = $1 AND name = $2")
321×
        .bind(repo_id)
331×
        .bind(name)
341×
        .fetch_one(pool)
351×
        .await
361×
}
37
38
/// A kept version of a state (#255), without its body.
39
#[derive(Clone, Debug, FromRow)]
40
pub struct TfVersion {
41
    pub id: i64,
42
    pub serial: i64,
43
    pub created_by: String,
44
    pub created_ms: i64,
45
}
46
47
/// A state's kept versions, newest first.
485×
pub async fn version_list(pool: &Pool, repo_id: i64, name: &str) -> sqlx::Result<Vec<TfVersion>> {
495×
    sqlx::query_as(
505×
        "SELECT id, serial, created_by, created_ms FROM terraform_state_versions WHERE repo_id = $1 AND name = $2 ORDER BY id DESC",
515×
    )
525×
    .bind(repo_id)
535×
    .bind(name)
545×
    .fetch_all(pool)
555×
    .await
565×
}
57
58
/// One kept version's sealed body and serial.
596×
pub async fn version_body(pool: &Pool, repo_id: i64, name: &str, id: i64) -> sqlx::Result<Option<(Vec<u8>, i64)>> {
606×
    sqlx::query_as("SELECT body, serial FROM terraform_state_versions WHERE repo_id = $1 AND name = $2 AND id = $3")
616×
        .bind(repo_id)
626×
        .bind(name)
636×
        .bind(id)
646×
        .fetch_optional(pool)
656×
        .await
666×
}
67
68
/// Store a new body; the one it replaces becomes a version, and only the
69
/// last `keep` versions stay.
705×
pub async fn put(pool: &Pool, repo_id: i64, name: &str, body: &[u8], serial: i64, by: &str, keep: i64) -> sqlx::Result<()> {
715×
    let now = now_ms();
725×
    let mut tx = pool.begin().await?;
735×
    sqlx::query(
745×
        "INSERT INTO terraform_state_versions (repo_id, name, body, serial, created_by, created_ms) \
755×
         SELECT repo_id, name, body, serial, updated_by, updated_ms FROM terraform_states \
765×
         WHERE repo_id = $1 AND name = $2 AND length(body) > 0",
775×
    )
785×
    .bind(repo_id)
795×
    .bind(name)
805×
    .execute(&mut *tx)
815×
    .await?;
825×
    sqlx::query(
835×
        "INSERT INTO terraform_states (repo_id, name, body, serial, updated_by, updated_ms) VALUES ($1, $2, $3, $4, $5, $6) \
845×
         ON CONFLICT (repo_id, name) DO UPDATE SET body = $3, serial = $4, updated_by = $5, updated_ms = $6",
855×
    )
865×
    .bind(repo_id)
875×
    .bind(name)
885×
    .bind(body)
895×
    .bind(serial)
905×
    .bind(by)
915×
    .bind(now)
925×
    .execute(&mut *tx)
935×
    .await?;
945×
    sqlx::query(
955×
        "DELETE FROM terraform_state_versions WHERE repo_id = $1 AND name = $2 AND id NOT IN \
965×
         (SELECT id FROM terraform_state_versions WHERE repo_id = $1 AND name = $2 ORDER BY id DESC LIMIT $3)",
975×
    )
985×
    .bind(repo_id)
995×
    .bind(name)
1005×
    .bind(keep)
1015×
    .execute(&mut *tx)
1025×
    .await?;
1035×
    tx.commit().await
1045×
}
105
106
/// Delete a state: its body goes (its versions stay, for a mistake).
1070×
pub async fn delete(pool: &Pool, repo_id: i64, name: &str) -> sqlx::Result<()> {
1080×
    sqlx::query("DELETE FROM terraform_states WHERE repo_id = $1 AND name = $2").bind(repo_id).bind(name).execute(pool).await?;
1090×
    Ok(())
1100×
}
111
112
/// Take the lock; false when someone holds it.
1132×
pub async fn lock(pool: &Pool, repo_id: i64, name: &str, id: &str, info: &str, by: &str) -> sqlx::Result<bool> {
1142×
    let now = now_ms();
1152×
    let r = sqlx::query(
1162×
        "INSERT INTO terraform_states (repo_id, name, body, lock_id, lock_info, locked_by, locked_ms, updated_ms) \
1172×
         VALUES ($1, $2, $3, $4, $5, $6, $7, $7) \
1182×
         ON CONFLICT (repo_id, name) DO UPDATE SET lock_id = $4, lock_info = $5, locked_by = $6, locked_ms = $7 \
1192×
         WHERE terraform_states.lock_id = ''",
1202×
    )
1212×
    .bind(repo_id)
1222×
    .bind(name)
1232×
    .bind(Vec::<u8>::new())
1242×
    .bind(id)
1252×
    .bind(info)
1262×
    .bind(by)
1272×
    .bind(now)
1282×
    .execute(pool)
1292×
    .await?;
1302×
    Ok(r.rows_affected() == 1)
1312×
}
132
1332×
pub async fn unlock(pool: &Pool, repo_id: i64, name: &str) -> sqlx::Result<()> {
1342×
    sqlx::query(
1352×
        "UPDATE terraform_states SET lock_id = '', lock_info = '', locked_by = '', locked_ms = NULL WHERE repo_id = $1 AND name = $2",
1362×
    )
1372×
    .bind(repo_id)
1382×
    .bind(name)
1392×
    .execute(pool)
1402×
    .await?;
1412×
    Ok(())
1422×
}