Skip to content

Commit eaf921b

Browse files
Merge pull request #601 from integer32llc/local-upload
Local upload instead of S3 to enable publishing in development
2 parents a7e8b07 + 3681b77 commit eaf921b

File tree

15 files changed

+285
-136
lines changed

15 files changed

+285
-136
lines changed

.env.sample

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@ export TEST_DATABASE_URL=
1616

1717
# Credentials for uploading packages to S3. You can leave these blank if
1818
# you're not publishing to s3 from your crates.io instance.
19-
# When running `cargo test`, set S3_BUCKET to `alexcrichton-test`.
2019
export S3_BUCKET=
2120
export S3_ACCESS_KEY=
2221
export S3_SECRET_KEY=

.travis.yml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,6 @@ env:
4545
global:
4646
- DATABASE_URL=postgres://postgres:@localhost/cargo_registry_test
4747
- TEST_DATABASE_URL=postgres://postgres:@localhost/cargo_registry_test
48-
- S3_BUCKET=alexcrichton-test
4948

5049
notifications:
5150
email:

README.md

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -102,16 +102,7 @@ After following the above instructions:
102102
export TEST_DATABASE_URL=postgres://postgres@localhost/cargo_registry_test
103103
```
104104

105-
2. In your `.env` file, set the s3 bucket to `alexcrichton-test`. No actual
106-
requests to s3 will be made; the requests and responses are recorded in
107-
files in `tests/http-data` and the s3 bucket name needs to match the
108-
requests in the files.
109-
110-
```
111-
export S3_BUCKET=alexcrichton-test
112-
```
113-
114-
3. Run the backend API server tests:
105+
2. Run the backend API server tests:
115106

116107
```
117108
cargo test

src/app.rs

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ use conduit_middleware::Middleware;
77
use git2;
88
use oauth2;
99
use r2d2;
10-
use s3;
1110
use curl::easy::Easy;
1211

1312
use {db, Config};
@@ -23,8 +22,7 @@ pub struct App {
2322

2423
/// The GitHub OAuth2 configuration
2524
pub github: oauth2::Config,
26-
pub bucket: s3::Bucket,
27-
pub s3_proxy: Option<String>,
25+
2826
pub session_key: String,
2927
pub git_repo: Mutex<git2::Repository>,
3028
pub git_repo_checkout: PathBuf,
@@ -64,12 +62,6 @@ impl App {
6462
database: db::pool(&config.db_url, db_config),
6563
diesel_database: db::diesel_pool(&config.db_url, diesel_db_config),
6664
github: github,
67-
bucket: s3::Bucket::new(config.s3_bucket.clone(),
68-
config.s3_region.clone(),
69-
config.s3_access_key.clone(),
70-
config.s3_secret_key.clone(),
71-
config.api_protocol()),
72-
s3_proxy: config.s3_proxy.clone(),
7365
session_key: config.session_key.clone(),
7466
git_repo: Mutex::new(repo),
7567
git_repo_checkout: config.git_repo_checkout.clone(),
@@ -79,7 +71,7 @@ impl App {
7971

8072
pub fn handle(&self) -> Easy {
8173
let mut handle = Easy::new();
82-
if let Some(ref proxy) = self.s3_proxy {
74+
if let Some(ref proxy) = self.config.uploader.proxy() {
8375
handle.proxy(proxy).unwrap();
8476
}
8577
return handle

src/bin/fill-in-user-id.rs

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,26 +14,25 @@ extern crate rustc_serialize;
1414

1515
use std::path::PathBuf;
1616

17-
use cargo_registry::{http, env, App};
17+
use cargo_registry::{http, env, App, Replica};
1818
use cargo_registry::util::{CargoResult, human};
1919

2020
#[allow(dead_code)]
2121
fn main() {
2222
git2::Repository::init("tmp/test").unwrap();
23+
let api_protocol = String::from("https");
24+
let uploader = cargo_registry::Uploader::NoOp;
2325
let config = cargo_registry::Config {
24-
s3_bucket: String::new(),
25-
s3_access_key: String::new(),
26-
s3_secret_key: String::new(),
27-
s3_region: None,
28-
s3_proxy: None,
26+
uploader: uploader,
2927
session_key: String::new(),
3028
git_repo_checkout: PathBuf::from("tmp/test"),
3129
gh_client_id: env("GH_CLIENT_ID"),
3230
gh_client_secret: env("GH_CLIENT_SECRET"),
3331
db_url: env("DATABASE_URL"),
3432
env: cargo_registry::Env::Production,
3533
max_upload_size: 0,
36-
mirror: false,
34+
mirror: Replica::Primary,
35+
api_protocol: api_protocol,
3736
};
3837
let app = cargo_registry::App::new(&config);
3938
{

src/bin/server.rs

Lines changed: 58 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,9 @@ extern crate conduit_middleware;
55
extern crate civet;
66
extern crate git2;
77
extern crate env_logger;
8+
extern crate s3;
89

9-
use cargo_registry::env;
10+
use cargo_registry::{env, Env, Uploader, Replica};
1011
use civet::Server;
1112
use std::env;
1213
use std::fs::{self, File};
@@ -38,26 +39,73 @@ fn main() {
3839
cfg.set_str("user.name", "bors").unwrap();
3940
cfg.set_str("user.email", "[email protected]").unwrap();
4041

42+
let api_protocol = String::from("https");
43+
let mirror = if env::var("MIRROR").is_ok() {
44+
Replica::ReadOnlyMirror
45+
} else {
46+
Replica::Primary
47+
};
48+
4149
let heroku = env::var("HEROKU").is_ok();
4250
let cargo_env = if heroku {
43-
cargo_registry::Env::Production
51+
Env::Production
4452
} else {
45-
cargo_registry::Env::Development
53+
Env::Development
4654
};
55+
56+
let uploader = match (cargo_env, mirror) {
57+
(Env::Production, Replica::Primary) => {
58+
// `env` panics if these vars are not set
59+
Uploader::S3 {
60+
bucket: s3::Bucket::new(env("S3_BUCKET"),
61+
env::var("S3_REGION").ok(),
62+
env("S3_ACCESS_KEY"),
63+
env("S3_SECRET_KEY"),
64+
&api_protocol),
65+
proxy: None,
66+
}
67+
},
68+
(Env::Production, Replica::ReadOnlyMirror) => {
69+
// Read-only mirrors don't need access key or secret key,
70+
// but they might have them. Definitely need bucket though.
71+
Uploader::S3 {
72+
bucket: s3::Bucket::new(env("S3_BUCKET"),
73+
env::var("S3_REGION").ok(),
74+
env::var("S3_ACCESS_KEY").unwrap_or(String::new()),
75+
env::var("S3_SECRET_KEY").unwrap_or(String::new()),
76+
&api_protocol),
77+
proxy: None,
78+
}
79+
},
80+
_ => {
81+
if env::var("S3_BUCKET").is_ok() {
82+
println!("Using S3 uploader");
83+
Uploader::S3 {
84+
bucket: s3::Bucket::new(env("S3_BUCKET"),
85+
env::var("S3_REGION").ok(),
86+
env::var("S3_ACCESS_KEY").unwrap_or(String::new()),
87+
env::var("S3_SECRET_KEY").unwrap_or(String::new()),
88+
&api_protocol),
89+
proxy: None,
90+
}
91+
} else {
92+
println!("Using local uploader, crate files will be in the dist directory");
93+
Uploader::Local
94+
}
95+
},
96+
};
97+
4798
let config = cargo_registry::Config {
48-
s3_bucket: env("S3_BUCKET"),
49-
s3_access_key: env("S3_ACCESS_KEY"),
50-
s3_secret_key: env("S3_SECRET_KEY"),
51-
s3_region: env::var("S3_REGION").ok(),
52-
s3_proxy: None,
99+
uploader: uploader,
53100
session_key: env("SESSION_KEY"),
54101
git_repo_checkout: checkout,
55102
gh_client_id: env("GH_CLIENT_ID"),
56103
gh_client_secret: env("GH_CLIENT_SECRET"),
57104
db_url: env("DATABASE_URL"),
58105
env: cargo_env,
59106
max_upload_size: 10 * 1024 * 1024,
60-
mirror: env::var("MIRROR").is_ok(),
107+
mirror: mirror,
108+
api_protocol: api_protocol,
61109
};
62110
let app = cargo_registry::App::new(&config);
63111
let app = cargo_registry::middleware(Arc::new(app));
@@ -69,7 +117,7 @@ fn main() {
69117
} else {
70118
env::var("PORT").ok().and_then(|s| s.parse().ok()).unwrap_or(8888)
71119
};
72-
let threads = if cargo_env == cargo_registry::Env::Development {1} else {50};
120+
let threads = if cargo_env == Env::Development {1} else {50};
73121
let mut cfg = civet::Config::new();
74122
cfg.port(port).threads(threads).keep_alive(true);
75123
let _a = Server::start(cfg, app);

src/config.rs

Lines changed: 4 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,16 @@
11
use std::path::PathBuf;
2+
use {Uploader, Replica};
23

34
#[derive(Clone)]
45
pub struct Config {
5-
pub s3_bucket: String,
6-
pub s3_region: Option<String>,
7-
pub s3_access_key: String,
8-
pub s3_secret_key: String,
9-
pub s3_proxy: Option<String>,
6+
pub uploader: Uploader,
107
pub session_key: String,
118
pub git_repo_checkout: PathBuf,
129
pub gh_client_id: String,
1310
pub gh_client_secret: String,
1411
pub db_url: String,
1512
pub env: ::Env,
1613
pub max_upload_size: u64,
17-
pub mirror: bool,
18-
}
19-
20-
impl Config {
21-
pub fn api_protocol(&self) -> &'static str {
22-
// When testing we route all API traffic over HTTP so we can
23-
// sniff/record it, but everywhere else we use https
24-
if self.env == ::Env::Test {"http"} else {"https"}
25-
}
14+
pub mirror: Replica,
15+
pub api_protocol: String,
2616
}

src/http.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ use std::str;
1212
/// parse_github_response to handle the "common" processing of responses.
1313
pub fn github(app: &App, url: &str, auth: &Token)
1414
-> Result<(Easy, Vec<u8>), curl::Error> {
15-
let url = format!("{}://api.github.com{}", app.config.api_protocol(), url);
15+
let url = format!("{}://api.github.com{}", app.config.api_protocol, url);
1616
info!("GITHUB HTTP: {}", url);
1717

1818
let mut headers = List::new();

src/krate.rs

Lines changed: 9 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,9 @@
11
use std::ascii::AsciiExt;
22
use std::cmp;
33
use std::collections::HashMap;
4-
use std::io::prelude::*;
5-
use std::io;
6-
use std::mem;
7-
use std::sync::Arc;
84

95
use conduit::{Request, Response};
106
use conduit_router::RequestParams;
11-
use curl::easy::Easy;
127
use diesel::prelude::*;
138
use diesel::pg::PgConnection;
149
use diesel::pg::upsert::*;
@@ -23,7 +18,7 @@ use semver;
2318
use time::{Timespec, Duration};
2419
use url::Url;
2520

26-
use {Model, User, Keyword, Version, Category, Badge};
21+
use {Model, User, Keyword, Version, Category, Badge, Replica};
2722
use app::{App, RequestApp};
2823
use db::RequestTransaction;
2924
use dependency::{Dependency, EncodableDependency};
@@ -36,7 +31,7 @@ use upload;
3631
use user::RequestUser;
3732
use owner::{EncodableOwner, Owner, Rights, OwnerKind, Team, rights, CrateOwner};
3833
use util::errors::NotFound;
39-
use util::{LimitErrorReader, HashingReader};
34+
use util::{read_le_u32, read_fill};
4035
use util::{RequestUtils, CargoResult, internal, ChainError, human};
4136
use version::EncodableVersion;
4237
use schema::*;
@@ -524,10 +519,6 @@ impl Crate {
524519
Ok(())
525520
}
526521

527-
pub fn s3_path(&self, version: &str) -> String {
528-
format!("/crates/{}/{}-{}.crate", self.name, self.name, version)
529-
}
530-
531522
pub fn add_version(&mut self,
532523
conn: &GenericConnection,
533524
ver: &semver::Version,
@@ -890,45 +881,10 @@ pub fn new(req: &mut Request) -> CargoResult<Response> {
890881
)?;
891882
let max_version = krate.max_version(req.tx()?)?;
892883

893-
// Upload the crate to S3
894-
let mut handle = req.app().handle();
895-
let path = krate.s3_path(&vers.to_string());
896-
let (response, cksum) = {
897-
let length = read_le_u32(req.body())?;
898-
let body = LimitErrorReader::new(req.body(), max);
899-
let mut body = HashingReader::new(body);
900-
let mut response = Vec::new();
901-
{
902-
let mut s3req = app.bucket.put(&mut handle, &path, &mut body,
903-
"application/x-tar",
904-
length as u64);
905-
s3req.write_function(|data| {
906-
response.extend(data);
907-
Ok(data.len())
908-
}).unwrap();
909-
s3req.perform().chain_error(|| {
910-
internal(format!("failed to upload to S3: `{}`", path))
911-
})?;
912-
}
913-
(response, body.finalize())
914-
};
915-
if handle.response_code().unwrap() != 200 {
916-
let response = String::from_utf8_lossy(&response);
917-
return Err(internal(format!("failed to get a 200 response from S3: {}",
918-
response)))
919-
}
920-
884+
// Upload the crate, return way to delete the crate from the server
921885
// If the git commands fail below, we shouldn't keep the crate on the
922886
// server.
923-
struct Bomb { app: Arc<App>, path: Option<String>, handle: Easy }
924-
impl Drop for Bomb {
925-
fn drop(&mut self) {
926-
if let Some(ref path) = self.path {
927-
drop(self.app.bucket.delete(&mut self.handle, &path).perform());
928-
}
929-
}
930-
}
931-
let mut bomb = Bomb { app: app.clone(), path: Some(path), handle: handle };
887+
let (cksum, mut bomb) = app.config.uploader.upload(req, &krate, max, &vers)?;
932888

933889
// Register this crate in our local git repo.
934890
let git_crate = git::Crate {
@@ -1003,28 +959,6 @@ fn parse_new_headers(req: &mut Request) -> CargoResult<(upload::NewCrate, User)>
1003959
Ok((new, user.clone()))
1004960
}
1005961

1006-
fn read_le_u32<R: Read + ?Sized>(r: &mut R) -> io::Result<u32> {
1007-
let mut b = [0; 4];
1008-
read_fill(r, &mut b)?;
1009-
Ok(((b[0] as u32) << 0) |
1010-
((b[1] as u32) << 8) |
1011-
((b[2] as u32) << 16) |
1012-
((b[3] as u32) << 24))
1013-
}
1014-
1015-
fn read_fill<R: Read + ?Sized>(r: &mut R, mut slice: &mut [u8])
1016-
-> io::Result<()> {
1017-
while slice.len() > 0 {
1018-
let n = r.read(slice)?;
1019-
if n == 0 {
1020-
return Err(io::Error::new(io::ErrorKind::Other,
1021-
"end of file reached"))
1022-
}
1023-
slice = &mut mem::replace(&mut slice, &mut [])[n..];
1024-
}
1025-
Ok(())
1026-
}
1027-
1028962
/// Handles the `GET /crates/:crate_id/:version/download` route.
1029963
pub fn download(req: &mut Request) -> CargoResult<Response> {
1030964
let crate_name = &req.params()["crate_id"];
@@ -1034,15 +968,16 @@ pub fn download(req: &mut Request) -> CargoResult<Response> {
1034968
// API-only mirrors won't have any crates in their database, and
1035969
// incrementing the download count will look up the crate in the
1036970
// database. Mirrors just want to pass along a redirect URL.
1037-
if req.app().config.mirror {
971+
if req.app().config.mirror == Replica::ReadOnlyMirror {
1038972
let _ = increment_download_counts(req, crate_name, version);
1039973
} else {
1040974
increment_download_counts(req, crate_name, version)?;
1041975
}
1042976

1043-
let redirect_url = format!("https://{}/crates/{}/{}-{}.crate",
1044-
req.app().bucket.host(),
1045-
crate_name, crate_name, version);
977+
let redirect_url = req.app().config.uploader
978+
.crate_location(crate_name, version).ok_or_else(||
979+
human("crate files not found")
980+
)?;
1046981

1047982
if req.wants_json() {
1048983
#[derive(RustcEncodable)]

0 commit comments

Comments
 (0)