Skip to content

Commit 33a48ab

Browse files
committed
Generate the index from the database
When a crate is modified, rather than editing the existing index file in git, this change re-generates the entire file from the database. This makes the jobs idempotent and prevents the index from getting out of sync with the database. The `delete_version` and `delete_crate` tasks now also modify the index. Test file changes are caused because the tests were inserting versions in to the DB without adding them to the index.
1 parent 2e58c24 commit 33a48ab

12 files changed

+213
-171
lines changed

src/admin/delete_crate.rs

+6-8
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
1-
use crate::{admin::dialoguer, config, db, models::Crate, schema::crates};
1+
use crate::{admin::dialoguer, db, models::Crate, schema::crates};
22

33
use diesel::prelude::*;
4-
use reqwest::blocking::Client;
54

65
#[derive(clap::Parser, Debug)]
76
#[command(
@@ -30,10 +29,6 @@ pub fn run(opts: Opts) {
3029
fn delete(opts: Opts, conn: &mut PgConnection) {
3130
let krate: Crate = Crate::by_name(&opts.crate_name).first(conn).unwrap();
3231

33-
let config = config::Base::from_environment();
34-
let uploader = config.uploader();
35-
let client = Client::new();
36-
3732
if !opts.yes {
3833
let prompt = format!(
3934
"Are you sure you want to delete {} ({})?",
@@ -44,7 +39,8 @@ fn delete(opts: Opts, conn: &mut PgConnection) {
4439
}
4540
}
4641

47-
println!("deleting the crate");
42+
let message = format!("Deleting crate `{}`", opts.crate_name);
43+
println!("{message}");
4844
let n = diesel::delete(crates::table.find(krate.id))
4945
.execute(conn)
5046
.unwrap();
@@ -54,5 +50,7 @@ fn delete(opts: Opts, conn: &mut PgConnection) {
5450
panic!("aborting transaction");
5551
}
5652

57-
uploader.delete_index(&client, &krate.name).unwrap();
53+
crate::worker::sync(krate.name, message, false)
54+
.enqueue(conn)
55+
.unwrap();
5856
}

src/admin/delete_version.rs

+6-1
Original file line numberDiff line numberDiff line change
@@ -49,12 +49,17 @@ fn delete(opts: Opts, conn: &mut PgConnection) {
4949
}
5050
}
5151

52-
println!("deleting version {} ({})", v.num, v.id);
52+
let message = format!("Deleting crate `{}#{}`", opts.crate_name, opts.version);
53+
println!("{message}");
5354
diesel::delete(versions::table.find(&v.id))
5455
.execute(conn)
5556
.unwrap();
5657

5758
if !opts.yes && !dialoguer::confirm("commit?") {
5859
panic!("aborting transaction");
5960
}
61+
62+
crate::worker::sync(krate.name, message, false)
63+
.enqueue(conn)
64+
.unwrap();
6065
}

src/background_jobs.rs

+17-10
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ pub enum Job {
1717
DumpDb(DumpDbJob),
1818
IndexAddCrate(IndexAddCrateJob),
1919
IndexSquash,
20-
IndexSyncToHttp(IndexSyncToHttpJob),
20+
IndexSync(IndexSyncJob),
2121
IndexUpdateYanked(IndexUpdateYankedJob),
2222
NormalizeIndex(NormalizeIndexJob),
2323
RenderAndUploadReadme(RenderAndUploadReadmeJob),
@@ -43,7 +43,7 @@ impl Job {
4343
const DUMP_DB: &str = "dump_db";
4444
const INDEX_ADD_CRATE: &str = "add_crate";
4545
const INDEX_SQUASH: &str = "squash_index";
46-
const INDEX_SYNC_TO_HTTP: &str = "update_crate_index";
46+
const INDEX_SYNC: &str = "update_crate_index";
4747
const INDEX_UPDATE_YANKED: &str = "sync_yanked";
4848
const NORMALIZE_INDEX: &str = "normalize_index";
4949
const RENDER_AND_UPLOAD_README: &str = "render_and_upload_readme";
@@ -55,7 +55,7 @@ impl Job {
5555
Job::DumpDb(_) => Self::DUMP_DB,
5656
Job::IndexAddCrate(_) => Self::INDEX_ADD_CRATE,
5757
Job::IndexSquash => Self::INDEX_SQUASH,
58-
Job::IndexSyncToHttp(_) => Self::INDEX_SYNC_TO_HTTP,
58+
Job::IndexSync(_) => Self::INDEX_SYNC,
5959
Job::IndexUpdateYanked(_) => Self::INDEX_UPDATE_YANKED,
6060
Job::NormalizeIndex(_) => Self::NORMALIZE_INDEX,
6161
Job::RenderAndUploadReadme(_) => Self::RENDER_AND_UPLOAD_README,
@@ -69,7 +69,7 @@ impl Job {
6969
Job::DumpDb(inner) => serde_json::to_value(inner),
7070
Job::IndexAddCrate(inner) => serde_json::to_value(inner),
7171
Job::IndexSquash => Ok(serde_json::Value::Null),
72-
Job::IndexSyncToHttp(inner) => serde_json::to_value(inner),
72+
Job::IndexSync(inner) => serde_json::to_value(inner),
7373
Job::IndexUpdateYanked(inner) => serde_json::to_value(inner),
7474
Job::NormalizeIndex(inner) => serde_json::to_value(inner),
7575
Job::RenderAndUploadReadme(inner) => serde_json::to_value(inner),
@@ -97,7 +97,7 @@ impl Job {
9797
Self::DUMP_DB => Job::DumpDb(from_value(value)?),
9898
Self::INDEX_ADD_CRATE => Job::IndexAddCrate(from_value(value)?),
9999
Self::INDEX_SQUASH => Job::IndexSquash,
100-
Self::INDEX_SYNC_TO_HTTP => Job::IndexSyncToHttp(from_value(value)?),
100+
Self::INDEX_SYNC => Job::IndexSync(from_value(value)?),
101101
Self::INDEX_UPDATE_YANKED => Job::IndexUpdateYanked(from_value(value)?),
102102
Self::NORMALIZE_INDEX => Job::NormalizeIndex(from_value(value)?),
103103
Self::RENDER_AND_UPLOAD_README => Job::RenderAndUploadReadme(from_value(value)?),
@@ -120,9 +120,13 @@ impl Job {
120120
worker::perform_daily_db_maintenance(&mut *fresh_connection(pool)?)
121121
}
122122
Job::DumpDb(args) => worker::perform_dump_db(env, args.database_url, args.target_name),
123-
Job::IndexAddCrate(args) => worker::perform_index_add_crate(env, conn, &args.krate),
123+
Job::IndexAddCrate(args) => {
124+
worker::perform_index_add_crate(env, conn, &args.krate, &args.version_num)
125+
}
124126
Job::IndexSquash => worker::perform_index_squash(env),
125-
Job::IndexSyncToHttp(args) => worker::perform_index_sync_to_http(env, args.crate_name),
127+
Job::IndexSync(args) => {
128+
worker::perform_index_sync(env, conn, &args.krate, &args.message, args.force)
129+
}
126130
Job::IndexUpdateYanked(args) => {
127131
worker::perform_index_update_yanked(env, conn, &args.krate, &args.version_num)
128132
}
@@ -164,12 +168,15 @@ pub struct DumpDbJob {
164168

165169
#[derive(Serialize, Deserialize)]
166170
pub struct IndexAddCrateJob {
167-
pub(super) krate: cargo_registry_index::Crate,
171+
pub(super) krate: String,
172+
pub(super) version_num: String,
168173
}
169174

170175
#[derive(Serialize, Deserialize)]
171-
pub struct IndexSyncToHttpJob {
172-
pub(super) crate_name: String,
176+
pub struct IndexSyncJob {
177+
pub(super) krate: String,
178+
pub(super) message: String,
179+
pub(super) force: bool,
173180
}
174181

175182
#[derive(Serialize, Deserialize)]

src/controllers/krate/publish.rs

+18-69
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,14 @@ use flate2::read::GzDecoder;
66
use hex::ToHex;
77
use hyper::body::Buf;
88
use sha2::{Digest, Sha256};
9-
use std::collections::BTreeMap;
109
use std::io::Read;
1110
use std::path::Path;
1211

1312
use crate::controllers::cargo_prelude::*;
1413
use crate::controllers::util::RequestPartsExt;
1514
use crate::models::{
16-
insert_version_owner_action, Category, Crate, DependencyKind, Keyword, NewCrate, NewVersion,
17-
Rights, VersionAction,
15+
insert_version_owner_action, Category, Crate, Keyword, NewCrate, NewVersion, Rights,
16+
VersionAction,
1817
};
1918
use crate::worker;
2019

@@ -199,8 +198,8 @@ pub async fn publish(app: AppState, req: BytesRequest) -> AppResult<Json<GoodCra
199198
// to get here, and max upload sizes are way less than i32 max
200199
content_length as i32,
201200
user.id,
202-
hex_cksum.clone(),
203-
links.clone(),
201+
hex_cksum,
202+
links,
204203
)?
205204
.save(conn, &verified_email_address)?;
206205

@@ -213,7 +212,7 @@ pub async fn publish(app: AppState, req: BytesRequest) -> AppResult<Json<GoodCra
213212
)?;
214213

215214
// Link this new version to all dependencies
216-
let git_deps = add_dependencies(conn, &new_crate.deps, version.id)?;
215+
add_dependencies(conn, &new_crate.deps, version.id)?;
217216

218217
// Update all keywords for this crate
219218
Keyword::update_crate(conn, &krate, &keywords)?;
@@ -247,31 +246,7 @@ pub async fn publish(app: AppState, req: BytesRequest) -> AppResult<Json<GoodCra
247246
.uploader()
248247
.upload_crate(app.http_client(), tarball_bytes, &krate, vers)?;
249248

250-
let (features, features2): (BTreeMap<_, _>, BTreeMap<_, _>) =
251-
features.into_iter().partition(|(_k, vals)| {
252-
!vals
253-
.iter()
254-
.any(|v| v.starts_with("dep:") || v.contains("?/"))
255-
});
256-
let (features2, v) = if features2.is_empty() {
257-
(None, None)
258-
} else {
259-
(Some(features2), Some(2))
260-
};
261-
262-
// Register this crate in our local git repo.
263-
let git_crate = cargo_registry_index::Crate {
264-
name: name.0,
265-
vers: vers.to_string(),
266-
cksum: hex_cksum,
267-
features,
268-
features2,
269-
deps: git_deps,
270-
yanked: Some(false),
271-
links,
272-
v,
273-
};
274-
worker::add_crate(git_crate).enqueue(conn)?;
249+
worker::add_crate(name.0, vers.to_string()).enqueue(conn)?;
275250

276251
// The `other` field on `PublishWarnings` was introduced to handle a temporary warning
277252
// that is no longer needed. As such, crates.io currently does not return any `other`
@@ -349,11 +324,11 @@ pub fn add_dependencies(
349324
conn: &mut PgConnection,
350325
deps: &[EncodableCrateDependency],
351326
target_version_id: i32,
352-
) -> AppResult<Vec<cargo_registry_index::Dependency>> {
327+
) -> AppResult<()> {
353328
use self::dependencies::dsl::*;
354329
use diesel::insert_into;
355330

356-
let git_and_new_dependencies = deps
331+
let new_dependencies = deps
357332
.iter()
358333
.map(|dep| {
359334
if let Some(registry) = &dep.registry {
@@ -373,51 +348,25 @@ pub fn add_dependencies(
373348
}
374349
}
375350

376-
// If this dependency has an explicit name in `Cargo.toml` that
377-
// means that the `name` we have listed is actually the package name
378-
// that we're depending on. The `name` listed in the index is the
379-
// Cargo.toml-written-name which is what cargo uses for
380-
// `--extern foo=...`
381-
let (name, package) = match &dep.explicit_name_in_toml {
382-
Some(explicit) => (explicit.to_string(), Some(dep.name.to_string())),
383-
None => (dep.name.to_string(), None),
384-
};
385-
386351
Ok((
387-
cargo_registry_index::Dependency {
388-
name,
389-
req: dep.version_req.to_string(),
390-
features: dep.features.iter().map(|s| s.0.to_string()).collect(),
391-
optional: dep.optional,
392-
default_features: dep.default_features,
393-
target: dep.target.clone(),
394-
kind: dep.kind.or(Some(DependencyKind::Normal)).map(|dk| dk.into()),
395-
package,
396-
},
397-
(
398-
version_id.eq(target_version_id),
399-
crate_id.eq(krate.id),
400-
req.eq(dep.version_req.to_string()),
401-
dep.kind.map(|k| kind.eq(k as i32)),
402-
optional.eq(dep.optional),
403-
default_features.eq(dep.default_features),
404-
features.eq(&dep.features),
405-
target.eq(dep.target.as_deref()),
406-
explicit_name.eq(dep.explicit_name_in_toml.as_deref())
407-
),
352+
version_id.eq(target_version_id),
353+
crate_id.eq(krate.id),
354+
req.eq(dep.version_req.to_string()),
355+
dep.kind.map(|k| kind.eq(k as i32)),
356+
optional.eq(dep.optional),
357+
default_features.eq(dep.default_features),
358+
features.eq(&dep.features),
359+
target.eq(dep.target.as_deref()),
360+
explicit_name.eq(dep.explicit_name_in_toml.as_deref())
408361
))
409362
})
410363
.collect::<Result<Vec<_>, _>>()?;
411364

412-
let (mut git_deps, new_dependencies): (Vec<_>, Vec<_>) =
413-
git_and_new_dependencies.into_iter().unzip();
414-
git_deps.sort();
415-
416365
insert_into(dependencies)
417366
.values(&new_dependencies)
418367
.execute(conn)?;
419368

420-
Ok(git_deps)
369+
Ok(())
421370
}
422371

423372
fn verify_tarball(

src/models/dependency.rs

+10
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,16 @@ pub enum DependencyKind {
4444
// if you add a kind here, be sure to update `from_row` below.
4545
}
4646

47+
impl From<cargo_registry_index::DependencyKind> for DependencyKind {
48+
fn from(dk: cargo_registry_index::DependencyKind) -> Self {
49+
match dk {
50+
cargo_registry_index::DependencyKind::Normal => DependencyKind::Normal,
51+
cargo_registry_index::DependencyKind::Build => DependencyKind::Build,
52+
cargo_registry_index::DependencyKind::Dev => DependencyKind::Dev,
53+
}
54+
}
55+
}
56+
4757
impl From<DependencyKind> for IndexDependencyKind {
4858
fn from(dk: DependencyKind) -> Self {
4959
match dk {

src/models/krate.rs

+71
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
use std::collections::BTreeMap;
2+
13
use chrono::NaiveDateTime;
24
use diesel::associations::Identifiable;
35
use diesel::pg::Pg;
@@ -432,6 +434,75 @@ impl Crate {
432434

433435
Ok(rows.records_and_total())
434436
}
437+
438+
/// Serialize the crate as an index metadata file
439+
pub fn index_metadata(&self, conn: &mut PgConnection) -> QueryResult<String> {
440+
let mut versions: Vec<Version> = self.all_versions().load(conn)?;
441+
versions.sort_by_cached_key(|k| {
442+
semver::Version::parse(&k.num)
443+
.expect("version was valid semver when inserted into the database")
444+
});
445+
446+
let mut body = Vec::new();
447+
for version in versions {
448+
let mut deps: Vec<cargo_registry_index::Dependency> = version
449+
.dependencies(conn)?
450+
.into_iter()
451+
.map(|(dep, name)| {
452+
// If this dependency has an explicit name in `Cargo.toml` that
453+
// means that the `name` we have listed is actually the package name
454+
// that we're depending on. The `name` listed in the index is the
455+
// Cargo.toml-written-name which is what cargo uses for
456+
// `--extern foo=...`
457+
let (name, package) = match dep.explicit_name {
458+
Some(explicit_name) => (explicit_name, Some(name)),
459+
None => (name, None),
460+
};
461+
cargo_registry_index::Dependency {
462+
name,
463+
req: dep.req,
464+
features: dep.features,
465+
optional: dep.optional,
466+
default_features: dep.default_features,
467+
kind: Some(dep.kind.into()),
468+
package,
469+
target: dep.target,
470+
}
471+
})
472+
.collect();
473+
deps.sort();
474+
475+
let features: BTreeMap<String, Vec<String>> =
476+
serde_json::from_value(version.features).unwrap_or_default();
477+
let (features, features2): (BTreeMap<_, _>, BTreeMap<_, _>) =
478+
features.into_iter().partition(|(_k, vals)| {
479+
!vals
480+
.iter()
481+
.any(|v| v.starts_with("dep:") || v.contains("?/"))
482+
});
483+
let (features, features2, v) = if features2.is_empty() {
484+
(features, None, None)
485+
} else {
486+
(features, Some(features2), Some(2))
487+
};
488+
489+
let krate = cargo_registry_index::Crate {
490+
name: self.name.clone(),
491+
vers: version.num.to_string(),
492+
cksum: version.checksum,
493+
yanked: Some(version.yanked),
494+
deps,
495+
features,
496+
links: version.links,
497+
features2,
498+
v,
499+
};
500+
serde_json::to_writer(&mut body, &krate).unwrap();
501+
body.push(b'\n');
502+
}
503+
let body = String::from_utf8(body).unwrap();
504+
Ok(body)
505+
}
435506
}
436507

437508
#[cfg(test)]

0 commit comments

Comments
 (0)