Skip to content

Commit 5c02bae

Browse files
committed
Add update tool
1 parent afb5a7f commit 5c02bae

File tree

9 files changed

+119
-41
lines changed

9 files changed

+119
-41
lines changed

.env.sample

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,14 @@ export TEST_DATABASE_URL=
3333
# not needed if the S3 bucket is in US standard
3434
# export S3_REGION=
3535

36+
# Credentials for uploading index metadata to S3. You can leave these commented
37+
# out if you're not publishing to s3 from your crates.io instance.
38+
# export S3_INDEX_BUCKET
39+
# export S3_INDEX_ACCESS_KEY
40+
# export S3_INDEX_SECRET_KEY
41+
# not needed if the S3 bucket is in US standard
42+
# export S3_INDEX_REGION
43+
3644
# Upstream location of the registry index. Background jobs will push to
3745
# this URL. The default points to a local index for development.
3846
# Run `./script/init-local-index.sh` to initialize this repo.

cargo-registry-index/lib.rs

Lines changed: 48 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -293,18 +293,33 @@ impl Repository {
293293
.join(Self::relative_index_file(name))
294294
}
295295

296+
/// Returns the relative path to the crate index file.
297+
/// Does not perform conversion to lowercase.
298+
fn relative_index_file_helper(name: &str) -> Vec<&str> {
299+
match name.len() {
300+
1 => vec!["1", &name],
301+
2 => vec!["2", &name],
302+
3 => vec!["3", &name[..1], &name],
303+
_ => vec![&name[0..2], &name[2..4], &name],
304+
}
305+
}
306+
296307
/// Returns the relative path to the crate index file that corresponds to
297-
/// the given crate name.
308+
/// the given crate name as a path.
298309
///
299310
/// see <https://doc.rust-lang.org/cargo/reference/registries.html#index-format>
300311
pub fn relative_index_file(name: &str) -> PathBuf {
301312
let name = name.to_lowercase();
302-
match name.len() {
303-
1 => Path::new("1").join(&name),
304-
2 => Path::new("2").join(&name),
305-
3 => Path::new("3").join(&name[..1]).join(&name),
306-
_ => Path::new(&name[0..2]).join(&name[2..4]).join(&name),
307-
}
313+
Self::relative_index_file_helper(&name).iter().collect()
314+
}
315+
316+
/// Returns the relative path to the crate index file that corresponds to
317+
/// the given crate name as a string.
318+
///
319+
/// see <https://doc.rust-lang.org/cargo/reference/registries.html#index-format>
320+
pub fn relative_index_file_as_string(name: &str) -> String {
321+
let name = name.to_lowercase();
322+
Self::relative_index_file_helper(&name).join("/")
308323
}
309324

310325
/// Returns the [Object ID](git2::Oid) of the currently checked out commit
@@ -343,6 +358,32 @@ impl Repository {
343358
self.push("refs/heads/master")
344359
}
345360

361+
/// Gets a list of files that have been modified since a given commit (or None for all files).
362+
pub fn get_files_modified_since(&self, starting_commit: Option<&str>) -> anyhow::Result<Vec<PathBuf>>{
363+
let starting_commit = match starting_commit {
364+
Some(starting_commit) => {
365+
let oid = git2::Oid::from_str(starting_commit).context("failed to parse commit into Oid")?;
366+
let commit = self.repository.find_commit(oid).context("failed to find commit")?;
367+
Some(commit.as_object().peel_to_tree().context("failed to find tree for commit")?)
368+
}
369+
None => None,
370+
};
371+
372+
let head = self.repository.find_commit(self.head_oid()?)?.as_object().peel_to_tree()?;
373+
let diff = self.repository
374+
.diff_tree_to_tree(starting_commit.as_ref(), Some(&head), None)
375+
.context("failed to run diff")?;
376+
let mut files = Vec::new();
377+
for delta in diff.deltas() {
378+
let file = delta.new_file();
379+
if file.exists() {
380+
files.push(file.path().unwrap().to_path_buf());
381+
}
382+
}
383+
384+
Ok(files)
385+
}
386+
346387
/// Push the current branch to the provided refname
347388
fn push(&self, refspec: &str) -> anyhow::Result<()> {
348389
let mut ref_status = Ok(());

src/admin/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,4 @@ pub mod render_readmes;
88
pub mod test_pagerduty;
99
pub mod transfer_crates;
1010
pub mod verify_token;
11+
pub mod upload_index;

src/admin/render_readmes.rs

Lines changed: 4 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@ use flate2::read::GzDecoder;
1414
use reqwest::{blocking::Client, header};
1515
use tar::{self, Archive};
1616

17-
const CACHE_CONTROL_README: &str = "public,max-age=604800";
1817
const USER_AGENT: &str = "crates-admin";
1918

2019
#[derive(clap::Parser, Debug)]
@@ -111,24 +110,14 @@ pub fn run(opts: Opts) -> anyhow::Result<()> {
111110
let handle = thread::spawn::<_, anyhow::Result<()>>(move || {
112111
println!("[{}-{}] Rendering README...", krate_name, version.num);
113112
let readme = get_readme(base_config.uploader(), &client, &version, &krate_name)?;
114-
let content_length = readme.len() as u64;
115-
let content = std::io::Cursor::new(readme);
116-
let readme_path = format!("readmes/{0}/{0}-{1}.html", krate_name, version.num);
117-
let mut extra_headers = header::HeaderMap::new();
118-
extra_headers.insert(
119-
header::CACHE_CONTROL,
120-
header::HeaderValue::from_static(CACHE_CONTROL_README),
121-
);
122113

123114
base_config
124115
.uploader()
125-
.upload(
116+
.upload_readme(
126117
&client,
127-
&readme_path,
128-
content,
129-
content_length,
130-
"text/html",
131-
extra_headers,
118+
&krate_name,
119+
&version.num,
120+
readme,
132121
)
133122
.context("Failed to upload rendered README file to S3")?;
134123

src/bin/crates-admin.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
use cargo_registry::admin::{
44
delete_crate, delete_version, migrate, populate, render_readmes, test_pagerduty,
5-
transfer_crates, verify_token,
5+
transfer_crates, verify_token, upload_index,
66
};
77

88
#[derive(clap::Parser, Debug)]
@@ -22,6 +22,7 @@ enum SubCommand {
2222
TransferCrates(transfer_crates::Opts),
2323
VerifyToken(verify_token::Opts),
2424
Migrate(migrate::Opts),
25+
UploadIndex(upload_index::Opts),
2526
}
2627

2728
fn main() -> anyhow::Result<()> {
@@ -38,6 +39,7 @@ fn main() -> anyhow::Result<()> {
3839
SubCommand::TransferCrates(opts) => transfer_crates::run(opts),
3940
SubCommand::VerifyToken(opts) => verify_token::run(opts).unwrap(),
4041
SubCommand::Migrate(opts) => migrate::run(opts)?,
42+
SubCommand::UploadIndex(opts) => upload_index::run(opts)?,
4143
}
4244

4345
Ok(())

src/config/base.rs

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,15 @@ impl Base {
8383
// sniff/record it, but everywhere else we use https
8484
"http",
8585
),
86+
index_bucket: s3::Bucket::new(
87+
String::from("alexcrichton-test"),
88+
None,
89+
dotenv::var("S3_INDEX_ACCESS_KEY").unwrap_or_default(),
90+
dotenv::var("S3_INDEX_SECRET_KEY").unwrap_or_default(),
91+
// When testing we route all API traffic over HTTP so we can
92+
// sniff/record it, but everywhere else we use https
93+
"http",
94+
),
8695
cdn: None,
8796
};
8897
Self {
@@ -104,6 +113,13 @@ impl Base {
104113
env("S3_SECRET_KEY"),
105114
"https",
106115
),
116+
index_bucket: s3::Bucket::new(
117+
env("S3_INDEX_BUCKET"),
118+
dotenv::var("S3_INDEX_REGION").ok(),
119+
env("S3_INDEX_ACCESS_KEY"),
120+
env("S3_INDEX_SECRET_KEY"),
121+
"https",
122+
),
107123
cdn: dotenv::var("S3_CDN").ok(),
108124
}
109125
}
@@ -117,6 +133,13 @@ impl Base {
117133
dotenv::var("S3_SECRET_KEY").unwrap_or_default(),
118134
"https",
119135
),
136+
index_bucket: s3::Bucket::new(
137+
env("S3_INDEX_BUCKET"),
138+
dotenv::var("S3_INDEX_REGION").ok(),
139+
dotenv::var("S3_INDEX_ACCESS_KEY").unwrap_or_default(),
140+
dotenv::var("S3_INDEX_SECRET_KEY").unwrap_or_default(),
141+
"https",
142+
),
120143
cdn: dotenv::var("S3_CDN").ok(),
121144
}
122145
}

src/uploaders.rs

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ pub enum Uploader {
2121
/// For test usage with `TestApp::with_proxy()`, the recording proxy is used.
2222
S3 {
2323
bucket: s3::Bucket,
24+
index_bucket: s3::Bucket,
2425
cdn: Option<String>,
2526
},
2627

@@ -29,6 +30,11 @@ pub enum Uploader {
2930
Local,
3031
}
3132

33+
pub enum UploadType {
34+
Default,
35+
Index,
36+
}
37+
3238
impl Uploader {
3339
/// Returns the URL of an uploaded crate's version archive.
3440
///
@@ -74,7 +80,6 @@ impl Uploader {
7480

7581
/// Returns the internal path of an uploaded crate's version archive.
7682
fn crate_path(name: &str, version: &str) -> String {
77-
// No slash in front so we can use join
7883
format!("crates/{name}/{name}-{version}.crate")
7984
}
8085

@@ -85,13 +90,8 @@ impl Uploader {
8590

8691
/// Returns the internal path of an uploaded crate's index file.
8792
fn index_path(name: &str) -> String {
88-
let prefix = match name.len() {
89-
1 => String::from("1"),
90-
2 => String::from("2"),
91-
3 => format!("3/{}", &name[..1]),
92-
_ => format!("{}/{}", &name[0..2], &name[2..4]),
93-
};
94-
format!("index/{prefix}/{name}")
93+
let path = cargo_registry_index::Repository::relative_index_file_as_string(name);
94+
format!("index/{}", path)
9595
}
9696

9797
/// Uploads a file using the configured uploader (either `S3`, `Local`).
@@ -110,9 +110,14 @@ impl Uploader {
110110
content_length: u64,
111111
content_type: &str,
112112
extra_headers: header::HeaderMap,
113+
upload_type: UploadType,
113114
) -> Result<Option<String>> {
114115
match *self {
115-
Uploader::S3 { ref bucket, .. } => {
116+
Uploader::S3 { ref bucket, ref index_bucket, .. } => {
117+
let bucket = match upload_type {
118+
UploadType::Index => index_bucket,
119+
UploadType::Default => bucket,
120+
};
116121
bucket.put(
117122
client,
118123
path,
@@ -157,6 +162,7 @@ impl Uploader {
157162
content_length,
158163
"application/x-tar",
159164
extra_headers,
165+
UploadType::Default,
160166
)
161167
.map_err(|e| internal(&format_args!("failed to upload crate: {}", e)))?;
162168
Ok(())
@@ -184,6 +190,7 @@ impl Uploader {
184190
content_length,
185191
"text/html",
186192
extra_headers,
193+
UploadType::Default,
187194
)?;
188195
Ok(())
189196
}
@@ -209,6 +216,7 @@ impl Uploader {
209216
content_length,
210217
"text/plain",
211218
extra_headers,
219+
UploadType::Index,
212220
)?;
213221
Ok(())
214222
}

src/worker/dump_db.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use std::{
44
};
55

66
use self::configuration::VisibilityConfig;
7-
use crate::{background_jobs::Environment, uploaders::Uploader};
7+
use crate::{background_jobs::Environment, uploaders::{Uploader, UploadType}};
88
use reqwest::header;
99
use swirl::PerformError;
1010

@@ -200,6 +200,7 @@ impl DumpTarball {
200200
content_length,
201201
"application/gzip",
202202
header::HeaderMap::new(),
203+
UploadType::Default,
203204
)?;
204205
Ok(content_length)
205206
}

src/worker/git.rs

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ use std::process::Command;
99
use swirl::PerformError;
1010

1111
#[swirl::background_job]
12-
pub fn add_crate(env: &Environment, krate: Crate) -> Result<(), PerformError> {
12+
pub fn add_crate(env: &Environment, conn: &PgConnection, krate: Crate) -> Result<(), PerformError> {
1313
use std::io::prelude::*;
1414

1515
let repo = env.lock_index()?;
@@ -20,20 +20,25 @@ pub fn add_crate(env: &Environment, krate: Crate) -> Result<(), PerformError> {
2020
let mut file = OpenOptions::new()
2121
.append(true)
2222
.create(true)
23-
.read(true)
2423
.open(&dst)?;
2524
serde_json::to_writer(&mut file, &krate)?;
2625
file.write_all(b"\n")?;
2726

28-
file.rewind()?;
29-
let mut contents = String::new();
30-
file.read_to_string(&mut contents)?;
31-
env.uploader
32-
.upload_index(env.http_client(), &krate.name, contents)?;
33-
3427
let message: String = format!("Updating crate `{}#{}`", krate.name, krate.vers);
3528
repo.commit_and_push(&message, &dst)?;
3629

30+
// Queue another background job to update the http-based index as well.
31+
update_crate_index(krate.name.clone()).enqueue(&conn)?;
32+
Ok(())
33+
}
34+
35+
#[swirl::background_job]
36+
pub fn update_crate_index(env: &Environment, crate_name: String) -> Result<(), PerformError> {
37+
let repo = env.lock_index()?;
38+
let dst = repo.index_file(&crate_name);
39+
let contents = std::fs::read_to_string(dst)?;
40+
env.uploader
41+
.upload_index(env.http_client(), &crate_name, contents)?;
3742
Ok(())
3843
}
3944

0 commit comments

Comments
 (0)