Skip to content

Commit 99810bd

Browse files
authored
Merge pull request #5200 from arlosi/invalidate
Add CloudFront invalidation for index files
2 parents 2a78bdd + aa62cc6 commit 99810bd

File tree

9 files changed

+216
-4
lines changed

9 files changed

+216
-4
lines changed

.env.sample

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,12 @@ export TEST_DATABASE_URL=
4141
# not needed if the S3 bucket is in US standard
4242
# export S3_INDEX_REGION=
4343

44+
# Credentials for invalidating cached files on CloudFront. You can leave these
45+
# commented out if you're not using CloudFront caching for the index files.
46+
# export CLOUDFRONT_DISTRIBUTION=
47+
# export CLOUDFRONT_ACCESS_KEY=
48+
# export CLOUDFRONT_SECRET_KEY=
49+
4450
# Upstream location of the registry index. Background jobs will push to
4551
# this URL. The default points to a local index for development.
4652
# Run `./script/init-local-index.sh` to initialize this repo.

Cargo.lock

Lines changed: 93 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ path = "src/tests/all.rs"
2323

2424
[dependencies]
2525
anyhow = "=1.0.65"
26+
aws-sigv4 = "=0.48.0"
2627
base64 = "=0.13.0"
2728
cargo-registry-index = { path = "cargo-registry-index" }
2829
cargo-registry-markdown = { path = "cargo-registry-markdown" }

src/background_jobs.rs

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ use swirl::PerformError;
66

77
use crate::db::{DieselPool, DieselPooledConn, PoolError};
88
use crate::uploaders::Uploader;
9+
use crate::worker::cloudfront::CloudFront;
910
use cargo_registry_index::Repository;
1011

1112
impl<'a> swirl::db::BorrowedConnection<'a> for DieselPool {
@@ -24,6 +25,7 @@ pub struct Environment {
2425
index: Arc<Mutex<Repository>>,
2526
pub uploader: Uploader,
2627
http_client: AssertUnwindSafe<Client>,
28+
cloudfront: Option<CloudFront>,
2729
}
2830

2931
impl Clone for Environment {
@@ -32,24 +34,37 @@ impl Clone for Environment {
3234
index: self.index.clone(),
3335
uploader: self.uploader.clone(),
3436
http_client: AssertUnwindSafe(self.http_client.0.clone()),
37+
cloudfront: self.cloudfront.clone(),
3538
}
3639
}
3740
}
3841

3942
impl Environment {
40-
pub fn new(index: Repository, uploader: Uploader, http_client: Client) -> Self {
41-
Self::new_shared(Arc::new(Mutex::new(index)), uploader, http_client)
43+
pub fn new(
44+
index: Repository,
45+
uploader: Uploader,
46+
http_client: Client,
47+
cloudfront: Option<CloudFront>,
48+
) -> Self {
49+
Self::new_shared(
50+
Arc::new(Mutex::new(index)),
51+
uploader,
52+
http_client,
53+
cloudfront,
54+
)
4255
}
4356

4457
pub fn new_shared(
4558
index: Arc<Mutex<Repository>>,
4659
uploader: Uploader,
4760
http_client: Client,
61+
cloudfront: Option<CloudFront>,
4862
) -> Self {
4963
Self {
5064
index,
5165
uploader,
5266
http_client: AssertUnwindSafe(http_client),
67+
cloudfront,
5368
}
5469
}
5570

@@ -63,4 +78,8 @@ impl Environment {
6378
pub(crate) fn http_client(&self) -> &Client {
6479
&self.http_client
6580
}
81+
82+
pub(crate) fn cloudfront(&self) -> Option<&CloudFront> {
83+
self.cloudfront.as_ref()
84+
}
6685
}

src/bin/background-worker.rs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
#![warn(clippy::all, rust_2018_idioms)]
1414

1515
use cargo_registry::config;
16+
use cargo_registry::worker::cloudfront::CloudFront;
1617
use cargo_registry::{background_jobs::*, db};
1718
use cargo_registry_index::{Repository, RepositoryConfig};
1819
use diesel::r2d2;
@@ -52,12 +53,19 @@ fn main() {
5253
));
5354
println!("Index cloned");
5455

56+
let cloudfront = CloudFront::from_environment();
57+
5558
let build_runner = || {
5659
let client = Client::builder()
5760
.timeout(Duration::from_secs(45))
5861
.build()
5962
.expect("Couldn't build client");
60-
let environment = Environment::new_shared(repository.clone(), uploader.clone(), client);
63+
let environment = Environment::new_shared(
64+
repository.clone(),
65+
uploader.clone(),
66+
client,
67+
cloudfront.clone(),
68+
);
6169
let db_config = r2d2::Pool::builder().min_idle(Some(0));
6270
swirl::Runner::builder(environment)
6371
.connection_pool_builder(&db_url, db_config)

src/tests/util/test_app.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -236,6 +236,7 @@ impl TestAppBuilder {
236236
index,
237237
app.config.uploader().clone(),
238238
app.http_client().clone(),
239+
None,
239240
);
240241

241242
Some(

src/worker/cloudfront.rs

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
use std::time::SystemTime;
2+
3+
use aws_sigv4::{
4+
http_request::{self, SignableRequest, SigningSettings},
5+
SigningParams,
6+
};
7+
use reqwest::blocking::Client;
8+
9+
#[derive(Clone)]
10+
pub struct CloudFront {
11+
distribution_id: String,
12+
access_key: String,
13+
secret_key: String,
14+
}
15+
16+
impl CloudFront {
17+
pub fn from_environment() -> Option<Self> {
18+
let distribution_id = dotenv::var("CLOUDFRONT_DISTRIBUTION").ok()?;
19+
let access_key =
20+
dotenv::var("CLOUDFRONT_ACCESS_KEY").expect("missing CLOUDFRONT_ACCESS_KEY");
21+
let secret_key =
22+
dotenv::var("CLOUDFRONT_SECRET_KEY").expect("missing CLOUDFRONT_SECRET_KEY");
23+
Some(Self {
24+
distribution_id,
25+
access_key,
26+
secret_key,
27+
})
28+
}
29+
30+
/// Invalidate a file on CloudFront
31+
///
32+
/// `path` is the path to the file to invalidate, such as `config.json`, or `re/ge/regex`
33+
pub fn invalidate(&self, client: &Client, path: &str) -> anyhow::Result<()> {
34+
let path = path.trim_start_matches('/');
35+
let url = format!(
36+
"https://cloudfront.amazonaws.com/2020-05-31/distribution/{}/invalidation",
37+
self.distribution_id
38+
);
39+
let now = chrono::offset::Utc::now().timestamp_micros();
40+
let body = format!(
41+
r#"
42+
<?xml version="1.0" encoding="UTF-8"?>
43+
<InvalidationBatch xmlns="http://cloudfront.amazonaws.com/doc/2020-05-31/">
44+
<CallerReference>{now}</CallerReference>
45+
<Paths>
46+
<Items>
47+
<Path>/{path}</Path>
48+
</Items>
49+
<Quantity>1</Quantity>
50+
</Paths>
51+
</InvalidationBatch>
52+
"#
53+
);
54+
55+
let request = http::Request::post(&url).body(&body)?;
56+
let request = SignableRequest::from(&request);
57+
let params = SigningParams::builder()
58+
.access_key(&self.access_key)
59+
.secret_key(&self.secret_key)
60+
.region("us-east-1") // cloudfront is a regionless service, use the default region for signing.
61+
.service_name("cloudfront")
62+
.settings(SigningSettings::default())
63+
.time(SystemTime::now())
64+
.build()
65+
.unwrap(); // all required fields are set
66+
let (mut signature_headers, _) = http_request::sign(request, &params).unwrap().into_parts();
67+
client
68+
.post(url)
69+
.headers(signature_headers.take_headers().unwrap_or_default())
70+
.body(body)
71+
.send()?
72+
.error_for_status()?;
73+
Ok(())
74+
}
75+
}

src/worker/git.rs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use crate::background_jobs::Environment;
22
use crate::schema;
33
use anyhow::Context;
4-
use cargo_registry_index::Crate;
4+
use cargo_registry_index::{Crate, Repository};
55
use chrono::Utc;
66
use diesel::prelude::*;
77
use std::fs::{self, OpenOptions};
@@ -44,6 +44,14 @@ pub fn update_crate_index(env: &Environment, crate_name: String) -> Result<(), P
4444
env.uploader
4545
.sync_index(env.http_client(), &crate_name, contents)?;
4646

47+
if let Some(cloudfront) = env.cloudfront() {
48+
trace!(?crate_name, "invalidate CloudFront");
49+
cloudfront.invalidate(
50+
env.http_client(),
51+
&Repository::relative_index_file_for_url(&crate_name),
52+
)?;
53+
}
54+
4755
Ok(())
4856
}
4957

src/worker/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
//! the daily database maintenance, but also operations like rendering READMEs
44
//! and uploading them to S3.
55
6+
pub mod cloudfront;
67
mod daily_db_maintenance;
78
pub mod dump_db;
89
mod git;

0 commit comments

Comments
 (0)