OXYGENHUB
◆ v0.9.8 oxygenhub / crates / hub / src / db / runner_updates.rs
5.8 KiB · Rust · last changed Runner updates: a build that rolls back on one runner is held 10 days ago source blame history raw
8501fd58 Andy McCall ↶ 1
//! Runner updates (oxygenhub#322): the policy at each level and the test
2
//! groups runners are put in.
3
4
use super::{now_ms, Pool};
5
use sqlx::FromRow;
6
7
/// One level's settings; `None` inherits from the next level out.
8
#[derive(Clone, Debug, Default, PartialEq, Eq, FromRow)]
9
pub struct Policy {
10
    pub scope: String,
11
    pub key: String,
12
    pub updates: Option<bool>,
13
    pub channel: Option<String>,
14
    pub window_from: Option<i64>,
15
    pub window_to: Option<i64>,
16
    pub dry_run: Option<bool>,
17
}
18
19
impl Policy {
20
    /// Nothing set: the level inherits everything.
21
    pub fn is_empty(&self) -> bool {
22
        self.updates.is_none() && self.channel.is_none() && self.window_from.is_none() && self.dry_run.is_none()
23
    }
24
}
25
26
#[derive(Clone, Debug, PartialEq, Eq, FromRow)]
27
pub struct Group {
28
    pub id: i64,
29
    /// Empty: a group of the site's runners.
30
    pub owner: String,
31
    pub name: String,
32
    /// A share of the runners in reach, by a stable hash of their ids;
33
    /// `None`: only the chosen ones.
34
    pub percent: Option<i64>,
35
    pub created_ms: i64,
36
}
37
38
pub async fn policies(pool: &Pool) -> sqlx::Result<Vec<Policy>> {
39
    sqlx::query_as("SELECT * FROM runner_update_policies").fetch_all(pool).await
40
}
41
42
pub async fn policy(pool: &Pool, scope: &str, key: &str) -> sqlx::Result<Policy> {
43
    let p: Option<Policy> = sqlx::query_as("SELECT * FROM runner_update_policies WHERE scope = $1 AND key = $2")
44
        .bind(scope)
45
        .bind(key)
46
        .fetch_optional(pool)
47
        .await?;
48
    Ok(p.unwrap_or(Policy { scope: scope.into(), key: key.into(), ..Policy::default() }))
49
}
50
51
/// Set a level's settings; one with nothing set is removed.
52
pub async fn set_policy(pool: &Pool, p: &Policy) -> sqlx::Result<()> {
53
    sqlx::query("DELETE FROM runner_update_policies WHERE scope = $1 AND key = $2").bind(&p.scope).bind(&p.key).execute(pool).await?;
54
    if p.is_empty() {
55
        return Ok(());
56
    }
57
    sqlx::query(
58
        "INSERT INTO runner_update_policies (scope, key, updates, channel, window_from, window_to, dry_run)
59
         VALUES ($1, $2, $3, $4, $5, $6, $7)",
60
    )
61
    .bind(&p.scope)
62
    .bind(&p.key)
63
    .bind(p.updates)
64
    .bind(&p.channel)
65
    .bind(p.window_from)
66
    .bind(p.window_to)
67
    .bind(p.dry_run)
68
    .execute(pool)
69
    .await?;
70
    Ok(())
71
}
72
73
pub async fn groups(pool: &Pool) -> sqlx::Result<Vec<Group>> {
74
    sqlx::query_as("SELECT * FROM runner_groups ORDER BY owner, name").fetch_all(pool).await
75
}
76
77
pub async fn group(pool: &Pool, id: i64) -> sqlx::Result<Option<Group>> {
78
    sqlx::query_as("SELECT * FROM runner_groups WHERE id = $1").bind(id).fetch_optional(pool).await
79
}
80
81
pub async fn create_group(pool: &Pool, owner: &str, name: &str, percent: Option<i64>) -> sqlx::Result<Group> {
82
    sqlx::query_as("INSERT INTO runner_groups (owner, name, percent, created_ms) VALUES ($1, $2, $3, $4) RETURNING *")
83
        .bind(owner)
84
        .bind(name)
85
        .bind(percent)
86
        .bind(now_ms())
87
        .fetch_one(pool)
88
        .await
89
}
90
91
pub async fn set_percent(pool: &Pool, id: i64, percent: Option<i64>) -> sqlx::Result<()> {
92
    sqlx::query("UPDATE runner_groups SET percent = $1 WHERE id = $2").bind(percent).bind(id).execute(pool).await?;
93
    Ok(())
94
}
95
96
/// Remove a group, its members and its policy.
97
pub async fn delete_group(pool: &Pool, id: i64) -> sqlx::Result<()> {
98
    sqlx::query("DELETE FROM runner_group_members WHERE group_id = $1").bind(id).execute(pool).await?;
99
    sqlx::query("DELETE FROM runner_update_policies WHERE scope = 'group' AND key = $1").bind(id.to_string()).execute(pool).await?;
100
    sqlx::query("DELETE FROM runner_groups WHERE id = $1").bind(id).execute(pool).await?;
101
    Ok(())
102
}
103
104
/// Every chosen member, as (group, runner).
105
pub async fn members(pool: &Pool) -> sqlx::Result<Vec<(i64, i64)>> {
106
    sqlx::query_as("SELECT group_id, runner_id FROM runner_group_members").fetch_all(pool).await
107
}
108
109
/// Make `runners` the group's chosen members.
110
pub async fn set_members(pool: &Pool, group: i64, runners: &[i64]) -> sqlx::Result<()> {
111
    sqlx::query("DELETE FROM runner_group_members WHERE group_id = $1").bind(group).execute(pool).await?;
112
    for r in runners {
113
        sqlx::query("INSERT INTO runner_group_members (group_id, runner_id) VALUES ($1, $2)").bind(group).bind(r).execute(pool).await?;
114
    }
115
    Ok(())
116
}
117
118
/// What a runner said about its update.
119
pub async fn report(pool: &Pool, runner: i64, note: &str) -> sqlx::Result<()> {
120
    sqlx::query("UPDATE flow_runners SET update_note = $1, update_ms = $2 WHERE id = $3")
121
        .bind(note)
122
        .bind(now_ms())
123
        .bind(runner)
124
        .execute(pool)
125
        .await?;
126
    Ok(())
127
}
faf5246c Andy McCall ↶ 128
129
/// A build held back from every runner (#326).
130
#[derive(Clone, Debug, PartialEq, Eq, FromRow)]
131
pub struct Hold {
132
    pub version: String,
133
    /// The runner it rolled back on.
134
    pub runner: String,
135
    pub why: String,
136
    pub at_ms: i64,
137
}
138
139
/// Hold `version` back, the first time it rolls back.
140
pub async fn hold(pool: &Pool, version: &str, runner: &str, why: &str) -> sqlx::Result<()> {
141
    sqlx::query("INSERT INTO runner_update_holds (version, runner, why, at_ms) VALUES ($1, $2, $3, $4) ON CONFLICT (version) DO NOTHING")
142
        .bind(version)
143
        .bind(runner)
144
        .bind(why)
145
        .bind(now_ms())
146
        .execute(pool)
147
        .await?;
148
    Ok(())
149
}
150
151
pub async fn holds(pool: &Pool) -> sqlx::Result<Vec<Hold>> {
152
    sqlx::query_as("SELECT * FROM runner_update_holds ORDER BY at_ms DESC").fetch_all(pool).await
153
}
154
155
pub async fn held(pool: &Pool, version: &str) -> sqlx::Result<Option<Hold>> {
156
    sqlx::query_as("SELECT * FROM runner_update_holds WHERE version = $1").bind(version).fetch_optional(pool).await
157
}
158
159
/// Lift the hold on `version`; whether there was one.
160
pub async fn lift(pool: &Pool, version: &str) -> sqlx::Result<bool> {
161
    Ok(sqlx::query("DELETE FROM runner_update_holds WHERE version = $1").bind(version).execute(pool).await?.rows_affected() > 0)
162
}