Skip to content

Commit 0bbaea6

Browse files
authored
Implement prune job (#147)
1 parent e84a34d commit 0bbaea6

File tree

12 files changed

+195
-0
lines changed

12 files changed

+195
-0
lines changed

flat-manager-client

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -965,6 +965,20 @@ async def purge_command(session, args):
965965
return job
966966

967967

968+
async def prune_command(session, args):
969+
resp = await session.post(
970+
urljoin(args.manager_url, "api/v1/prune"),
971+
headers={"Authorization": "Bearer " + args.token},
972+
json={"repo": args.repo},
973+
)
974+
async with resp:
975+
if resp.status >= 500:
976+
raise ServerApiError(resp, await resp.text())
977+
elif resp.status != 200:
978+
raise ApiError(resp, await resp.text())
979+
return await resp.json()
980+
981+
968982
async def create_token_command(session, args):
969983
data = await create_token(
970984
session,
@@ -1096,6 +1110,11 @@ if __name__ == "__main__":
10961110
purge_parser.add_argument("build_url", help="remote build url")
10971111
purge_parser.set_defaults(func=purge_command)
10981112

1113+
prune_parser = subparsers.add_parser("prune", help="Prune repository")
1114+
prune_parser.add_argument("manager_url", help="remote repo manager url")
1115+
prune_parser.add_argument("repo", help="repo name")
1116+
prune_parser.set_defaults(func=prune_command)
1117+
10991118
create_token_parser = subparsers.add_parser(
11001119
"create-token", help="Create subset token"
11011120
)
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
-- Remove the constraint for job kinds
2+
ALTER TABLE jobs DROP CONSTRAINT chk_job_kind;
3+
4+
-- Add back the original constraint without the prune job type
5+
ALTER TABLE jobs ADD CONSTRAINT chk_job_kind CHECK (kind IN (0, 1, 2, 3, 4)); -- Excluding 5 (Prune)
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
-- Migration to add the "prune" job type to the jobs table
2+
3+
ALTER TABLE jobs
4+
ADD CONSTRAINT chk_job_kind CHECK (kind IN (0, 1, 2, 3, 4, 5)); -- 0=commit, 1=publish, 2=updaterepo, 3=republish, 4=check, 5=prune

src/api/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
pub mod build;
22
pub mod delta;
3+
pub mod prune;
34
pub mod repo;
45
pub mod status;
56
pub mod tokens;

src/api/prune.rs

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
use actix_web::{web, HttpRequest, HttpResponse};
2+
use futures3::TryFutureExt;
3+
use serde::Deserialize;
4+
5+
use crate::db::Db;
6+
use crate::errors::ApiError;
7+
use crate::tokens::{ClaimsScope, ClaimsValidator};
8+
9+
#[derive(Deserialize)]
10+
pub struct PruneArgs {
11+
repo: String,
12+
}
13+
14+
pub fn handle_prune(
15+
args: web::Json<PruneArgs>,
16+
db: web::Data<Db>,
17+
req: HttpRequest,
18+
) -> impl futures::Future<Item = HttpResponse, Error = ApiError> {
19+
Box::pin(handle_prune_async(args, db, req)).compat()
20+
}
21+
22+
async fn handle_prune_async(
23+
args: web::Json<PruneArgs>,
24+
db: web::Data<Db>,
25+
req: HttpRequest,
26+
) -> Result<HttpResponse, ApiError> {
27+
req.validate_claims(|claims| {
28+
if !claims.scope.contains(&ClaimsScope::TokenManagement) {
29+
return Err(ApiError::NotEnoughPermissions(
30+
"Missing TokenManagement scope".to_string(),
31+
));
32+
}
33+
Ok(())
34+
})?;
35+
36+
// Create a new prune job
37+
let job = db.get_ref().start_prune_job(args.repo.clone()).await?;
38+
39+
Ok(HttpResponse::Ok().json(job))
40+
}

src/app.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,10 @@ pub fn create_app(
181181
.service(
182182
web::resource("/delta/upload/{repo}")
183183
.route(web::post().to_async(api::delta::delta_upload)),
184+
)
185+
.service(
186+
web::resource("/prune")
187+
.route(web::post().to_async(api::prune::handle_prune)),
184188
),
185189
)
186190
.service(

src/db.rs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -272,6 +272,21 @@ impl Db {
272272
.await
273273
}
274274

275+
pub async fn start_prune_job(&self, repo: String) -> Result<Job, ApiError> {
276+
self.run_in_transaction(move |conn| {
277+
diesel::insert_into(schema::jobs::table)
278+
.values(NewJob {
279+
kind: JobKind::Prune.to_db(),
280+
contents: json!({}).to_string(),
281+
start_after: None,
282+
repo: Some(repo),
283+
})
284+
.get_result(conn)
285+
.map_err(ApiError::from)
286+
})
287+
.await
288+
}
289+
275290
/* Checks */
276291

277292
pub async fn get_check_by_job_id(&self, job: i32) -> Result<Check, ApiError> {

src/jobs/job_instance.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ use diesel::pg::PgConnection;
55
use super::check_job::CheckJobInstance;
66
use super::commit_job::CommitJobInstance;
77
use super::job_executor::JobExecutor;
8+
use super::prune_job::PruneJobInstance;
89
use super::publish_job::PublishJobInstance;
910
use super::republish_job::RepublishJobInstance;
1011
use super::update_repo_job::UpdateRepoJobInstance;
@@ -18,6 +19,7 @@ pub fn new_job_instance(executor: &JobExecutor, job: Job) -> Box<dyn JobInstance
1819
}
1920
Some(JobKind::Republish) => RepublishJobInstance::new(job),
2021
Some(JobKind::Check) => CheckJobInstance::new(job),
22+
Some(JobKind::Prune) => PruneJobInstance::new(job),
2123
_ => InvalidJobInstance::new(job, JobError::new("Unknown job type")),
2224
}
2325
}

src/jobs/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ mod commit_job;
66
mod job_executor;
77
mod job_instance;
88
mod job_queue;
9+
mod prune_job;
910
mod publish_job;
1011
mod republish_job;
1112
mod update_repo_job;

src/jobs/prune_job.rs

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
use crate::errors::{JobError, JobResult};
2+
use crate::jobs::job_executor::JobExecutor;
3+
use crate::jobs::job_instance::JobInstance;
4+
use crate::models::Job;
5+
use diesel::pg::PgConnection;
6+
use log::info;
7+
use serde::{Deserialize, Serialize};
8+
use std::process::Command;
9+
10+
#[derive(Debug, Serialize, Deserialize)]
11+
pub struct PruneJob {}
12+
13+
pub struct PruneJobInstance {
14+
job: Job,
15+
}
16+
17+
impl PruneJobInstance {
18+
#[allow(clippy::new_ret_no_self)]
19+
pub fn new(job: Job) -> Box<dyn JobInstance> {
20+
match serde_json::from_str::<PruneJob>(&job.contents) {
21+
Ok(_) => Box::new(PruneJobInstance { job }),
22+
Err(e) => super::job_instance::InvalidJobInstance::new(
23+
job,
24+
JobError::new(&format!("Invalid prune job contents: {}", e)),
25+
),
26+
}
27+
}
28+
}
29+
30+
impl JobInstance for PruneJobInstance {
31+
fn get_job_id(&self) -> i32 {
32+
self.job.id
33+
}
34+
35+
fn handle_job(
36+
&mut self,
37+
executor: &JobExecutor,
38+
conn: &mut PgConnection,
39+
) -> JobResult<serde_json::Value> {
40+
info!("#{}: Handling Job Prune", &self.job.id);
41+
42+
// Get repo config
43+
let config = &executor.config;
44+
let repo = self
45+
.job
46+
.repo
47+
.as_ref()
48+
.ok_or_else(|| JobError::new("No repo specified"))?;
49+
let repoconfig = config
50+
.get_repoconfig(repo)
51+
.map_err(|_e| JobError::new(&format!("Can't find repo {}", repo)))?;
52+
53+
let repo_path = repoconfig.get_abs_repo_path();
54+
55+
// First do a dry run
56+
job_log_and_info!(self.job.id, conn, "Running prune dry-run");
57+
let mut cmd = Command::new("flatpak");
58+
cmd.arg("build-update-repo")
59+
.arg("-v")
60+
.arg("--no-update-summary")
61+
.arg("--no-update-appstream")
62+
.arg("--prune-dry-run")
63+
.arg("--prune-depth=3")
64+
.arg(&repo_path);
65+
66+
super::utils::do_command(cmd)?;
67+
68+
// Then do the actual prune
69+
job_log_and_info!(self.job.id, conn, "Running actual prune");
70+
let mut cmd = Command::new("flatpak");
71+
cmd.arg("build-update-repo")
72+
.arg("-v")
73+
.arg("--no-update-summary")
74+
.arg("--no-update-appstream")
75+
.arg("--prune")
76+
.arg("--prune-depth=3")
77+
.arg(&repo_path);
78+
79+
super::utils::do_command(cmd)?;
80+
81+
Ok(serde_json::json!({}))
82+
}
83+
84+
// Higher order than Publish/UpdateRepo to prevent them from running while prune is in queue
85+
fn order(&self) -> i32 {
86+
100
87+
}
88+
}

0 commit comments

Comments
 (0)