Skip to content

Commit

Permalink
add download endpoint for rustdoc archive
Browse files Browse the repository at this point in the history
  • Loading branch information
syphar committed Oct 8, 2022
1 parent dd66a73 commit 517d344
Show file tree
Hide file tree
Showing 13 changed files with 407 additions and 10 deletions.
9 changes: 9 additions & 0 deletions src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@ pub struct Config {
#[cfg(test)]
pub(crate) s3_bucket_is_temporary: bool,

// CloudFront domain which we can access
// public S3 files through
pub(crate) s3_static_domain: String,

// Github authentication
pub(crate) github_accesstoken: Option<String>,
pub(crate) github_updater_min_rate_limit: u32,
Expand Down Expand Up @@ -67,6 +71,8 @@ pub struct Config {
// CloudFront distribution ID for the web server.
// Will be used for invalidation-requests.
pub cloudfront_distribution_id_web: Option<String>,
/// same for the `static.docs.rs` distribution
pub cloudfront_distribution_id_static: Option<String>,

// Build params
pub(crate) build_attempts: u16,
Expand Down Expand Up @@ -125,6 +131,8 @@ impl Config {
#[cfg(test)]
s3_bucket_is_temporary: false,

s3_static_domain: env("S3_STATIC_DOMAIN", "https://static.docs.rs".to_string())?,

github_accesstoken: maybe_env("DOCSRS_GITHUB_ACCESSTOKEN")?,
github_updater_min_rate_limit: env("DOCSRS_GITHUB_UPDATER_MIN_RATE_LIMIT", 2500)?,

Expand All @@ -148,6 +156,7 @@ impl Config {
cdn_backend: env("DOCSRS_CDN_BACKEND", CdnKind::Dummy)?,

cloudfront_distribution_id_web: maybe_env("CLOUDFRONT_DISTRIBUTION_ID_WEB")?,
cloudfront_distribution_id_static: maybe_env("CLOUDFRONT_DISTRIBUTION_ID_STATIC")?,

local_archive_cache_path: env(
"DOCSRS_ARCHIVE_INDEX_CACHE_PATH",
Expand Down
4 changes: 4 additions & 0 deletions src/db/file.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,12 @@ pub fn add_path_into_remote_archive<P: AsRef<Path>>(
storage: &Storage,
archive_path: &str,
path: P,
public_access: bool,
) -> Result<(Value, CompressionAlgorithm)> {
let (file_list, algorithm) = storage.store_all_in_archive(archive_path, path.as_ref())?;
if public_access {
storage.set_public_access(archive_path, true)?;
}
Ok((
file_list_to_json(file_list.into_iter().collect()),
algorithm,
Expand Down
5 changes: 5 additions & 0 deletions src/db/migrate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -848,6 +848,11 @@ pub fn migrate(version: Option<Version>, conn: &mut Client) -> crate::error::Res
"CREATE INDEX builds_release_id_idx ON builds (rid);",
"DROP INDEX builds_release_id_idx;",
),
sql_migration!(
context, 35, "add public visibility to files table",
"ALTER TABLE files ADD COLUMN public BOOL NOT NULL DEFAULT FALSE;",
"ALTER TABLE files DROP COLUMN public;"
),

];

Expand Down
2 changes: 2 additions & 0 deletions src/docbuilder/rustwide_builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -410,6 +410,7 @@ impl RustwideBuilder {
&self.storage,
&rustdoc_archive_path(name, version),
local_storage.path(),
true,
)?;
algs.insert(new_alg);
};
Expand All @@ -421,6 +422,7 @@ impl RustwideBuilder {
&self.storage,
&source_archive_path(name, version),
build.host_source_dir(),
false,
)?;
algs.insert(new_alg);
files_list
Expand Down
26 changes: 26 additions & 0 deletions src/storage/database.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,32 @@ impl DatabaseBackend {
Ok(conn.query(query, &[&path])?[0].get(0))
}

pub(super) fn get_public_access(&self, path: &str) -> Result<bool> {
match self.pool.get()?.query_opt(
"SELECT public
FROM files
WHERE path = $1",
&[&path],
)? {
Some(row) => Ok(row.get(0)),
None => Err(super::PathNotFoundError.into()),
}
}

pub(super) fn set_public_access(&self, path: &str, public: bool) -> Result<()> {
if self.pool.get()?.execute(
"UPDATE files
SET public = $2
WHERE path = $1",
&[&path, &public],
)? == 1
{
Ok(())
} else {
Err(super::PathNotFoundError.into())
}
}

pub(super) fn get(
&self,
path: &str,
Expand Down
57 changes: 55 additions & 2 deletions src/storage/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,20 @@ impl Storage {
}
}

pub(crate) fn get_public_access(&self, path: &str) -> Result<bool> {
match &self.backend {
StorageBackend::Database(db) => db.get_public_access(path),
StorageBackend::S3(s3) => s3.get_public_access(path),
}
}

pub(crate) fn set_public_access(&self, path: &str, public: bool) -> Result<()> {
match &self.backend {
StorageBackend::Database(db) => db.set_public_access(path, public),
StorageBackend::S3(s3) => s3.set_public_access(path, public),
}
}

fn max_file_size_for(&self, path: &str) -> usize {
if path.ends_with(".html") {
self.config.max_file_size_html
Expand Down Expand Up @@ -620,9 +634,38 @@ mod backend_tests {
Ok(())
}

fn test_set_public(storage: &Storage) -> Result<()> {
let path: &str = "foo/bar.txt";

storage.store_blobs(vec![Blob {
path: path.into(),
mime: "text/plain".into(),
date_updated: Utc::now(),
compression: None,
content: b"test content\n".to_vec(),
}])?;

assert!(!storage.get_public_access(path)?);
storage.set_public_access(path, true)?;
assert!(storage.get_public_access(path)?);
storage.set_public_access(path, false)?;
assert!(!storage.get_public_access(path)?);

for path in &["bar.txt", "baz.txt", "foo/baz.txt"] {
assert!(storage
.set_public_access(path, true)
.unwrap_err()
.downcast_ref::<PathNotFoundError>()
.is_some());
}

Ok(())
}

fn test_get_object(storage: &Storage) -> Result<()> {
let path: &str = "foo/bar.txt";
let blob = Blob {
path: "foo/bar.txt".into(),
path: path.into(),
mime: "text/plain".into(),
date_updated: Utc::now(),
compression: None,
Expand All @@ -631,16 +674,25 @@ mod backend_tests {

storage.store_blobs(vec![blob.clone()])?;

let found = storage.get("foo/bar.txt", std::usize::MAX)?;
let found = storage.get(path, std::usize::MAX)?;
assert_eq!(blob.mime, found.mime);
assert_eq!(blob.content, found.content);

// default visibility is private
assert!(!storage.get_public_access(path)?);

for path in &["bar.txt", "baz.txt", "foo/baz.txt"] {
assert!(storage
.get(path, std::usize::MAX)
.unwrap_err()
.downcast_ref::<PathNotFoundError>()
.is_some());

assert!(storage
.get_public_access(path)
.unwrap_err()
.downcast_ref::<PathNotFoundError>()
.is_some());
}

Ok(())
Expand Down Expand Up @@ -1028,6 +1080,7 @@ mod backend_tests {
test_delete_prefix_without_matches,
test_delete_percent,
test_exists_without_remote_archive,
test_set_public,
}

tests_with_metrics {
Expand Down
70 changes: 69 additions & 1 deletion src/storage/s3.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use crate::{Config, Metrics};
use anyhow::{Context, Error};
use aws_sdk_s3::{
error,
model::{Delete, ObjectIdentifier},
model::{Delete, ObjectIdentifier, Tag, Tagging},
types::SdkError,
Client, Endpoint, Region, RetryConfig,
};
Expand All @@ -16,6 +16,9 @@ use futures_util::{
use std::{io::Write, sync::Arc};
use tokio::runtime::Runtime;

const PUBLIC_ACCESS_TAG: &str = "static-cloudfront-access";
const PUBLIC_ACCESS_VALUE: &str = "allow";

pub(super) struct S3Backend {
client: Client,
runtime: Arc<Runtime>,
Expand Down Expand Up @@ -90,6 +93,71 @@ impl S3Backend {
})
}

pub(super) fn get_public_access(&self, path: &str) -> Result<bool, Error> {
self.runtime.block_on(async {
match self
.client
.get_object_tagging()
.bucket(&self.bucket)
.key(path)
.send()
.await
{
Ok(tags) => Ok(tags
.tag_set()
.map(|tags| {
tags.iter()
.filter(|tag| tag.key() == Some(PUBLIC_ACCESS_TAG))
.any(|tag| tag.value() == Some(PUBLIC_ACCESS_VALUE))
})
.unwrap_or(false)),
Err(SdkError::ServiceError { err, raw }) => {
if raw.http().status() == http::StatusCode::NOT_FOUND {
Err(super::PathNotFoundError.into())
} else {
Err(err.into())
}
}
Err(other) => Err(other.into()),
}
})
}

pub(super) fn set_public_access(&self, path: &str, public: bool) -> Result<(), Error> {
self.runtime.block_on(async {
match self
.client
.put_object_tagging()
.bucket(&self.bucket)
.key(path)
.tagging(if public {
Tagging::builder()
.tag_set(
Tag::builder()
.key(PUBLIC_ACCESS_TAG)
.value(PUBLIC_ACCESS_VALUE)
.build(),
)
.build()
} else {
Tagging::builder().build()
})
.send()
.await
{
Ok(_) => Ok(()),
Err(SdkError::ServiceError { err, raw }) => {
if raw.http().status() == http::StatusCode::NOT_FOUND {
Err(super::PathNotFoundError.into())
} else {
Err(err.into())
}
}
Err(other) => Err(other.into()),
}
})
}

pub(super) fn get(
&self,
path: &str,
Expand Down
18 changes: 13 additions & 5 deletions src/test/fakes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -316,13 +316,21 @@ impl<'a> FakeRelease<'a> {
source_directory.display()
);
if archive_storage {
let archive = match kind {
FileKind::Rustdoc => rustdoc_archive_path(&package.name, &package.version),
FileKind::Sources => source_archive_path(&package.name, &package.version),
let (archive, public) = match kind {
FileKind::Rustdoc => {
(rustdoc_archive_path(&package.name, &package.version), true)
}
FileKind::Sources => {
(source_archive_path(&package.name, &package.version), false)
}
};
log::debug!("store in archive: {:?}", archive);
let (files_list, new_alg) =
crate::db::add_path_into_remote_archive(&storage, &archive, source_directory)?;
let (files_list, new_alg) = crate::db::add_path_into_remote_archive(
&storage,
&archive,
source_directory,
public,
)?;
let mut hm = HashSet::new();
hm.insert(new_alg);
Ok((files_list, hm))
Expand Down
4 changes: 4 additions & 0 deletions src/web/routes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,10 @@ pub(super) fn build_routes() -> Routes {
"/crate/:name/:version/builds",
super::builds::build_list_handler,
);
routes.internal_page(
"/crate/:name/:version/download",
super::rustdoc::download_handler,
);
routes.static_resource(
"/crate/:name/:version/builds.json",
super::builds::build_list_handler,
Expand Down
Loading

0 comments on commit 517d344

Please sign in to comment.