Skip to content

Commit

Permalink
test: update for private npm registry test server (#23572)
Browse files Browse the repository at this point in the history
Factored out from #23560 to make it
easier to review.
  • Loading branch information
bartlomieju committed Apr 26, 2024
1 parent 8c9caeb commit 0b0af5c
Show file tree
Hide file tree
Showing 7 changed files with 124 additions and 61 deletions.
3 changes: 2 additions & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion tests/util/server/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ async-stream = "0.3.3"
base64.workspace = true
bytes.workspace = true
console_static_text.workspace = true
deno_tls.workspace = true
deno_unsync = "0"
denokv_proto.workspace = true
fastwebsockets.workspace = true
Expand All @@ -44,6 +43,8 @@ pretty_assertions.workspace = true
prost.workspace = true
regex.workspace = true
reqwest.workspace = true
rustls-pemfile.workspace = true
rustls-tokio-stream.workspace = true
semver = "=1.0.14"
serde.workspace = true
serde_json.workspace = true
Expand Down
96 changes: 58 additions & 38 deletions tests/util/server/src/https.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,12 @@
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
use anyhow::anyhow;
use deno_tls::load_certs;
use deno_tls::load_private_keys;
use deno_tls::rustls;
use deno_tls::RootCertStore;
use deno_tls::TlsStream;
use futures::Stream;
use futures::StreamExt;
use rustls::Certificate;
use rustls::PrivateKey;
use rustls_tokio_stream::rustls;
use rustls_tokio_stream::TlsStream;
use std::io;
use std::io::Read;
use std::num::NonZeroUsize;
use std::result::Result;
use std::sync::Arc;
Expand Down Expand Up @@ -70,43 +68,65 @@ pub fn get_tls_config(
let key_file = std::fs::File::open(key_path)?;
let ca_file = std::fs::File::open(ca_path)?;

let err_map = |x| io::Error::new(io::ErrorKind::InvalidData, x);
let certs =
load_certs(&mut io::BufReader::new(cert_file)).map_err(err_map)?;
let certs: Vec<Certificate> = {
let mut cert_reader = io::BufReader::new(cert_file);
rustls_pemfile::certs(&mut cert_reader)
.unwrap()
.into_iter()
.map(Certificate)
.collect()
};

let mut ca_cert_reader = io::BufReader::new(ca_file);
let ca_cert = load_certs(&mut ca_cert_reader).map_err(err_map)?.remove(0);
let ca_cert = rustls_pemfile::certs(&mut ca_cert_reader)
.expect("Cannot load CA certificate")
.remove(0);

let mut key_reader = io::BufReader::new(key_file);
let mut key = vec![];
key_reader.read_to_end(&mut key)?;
let key = load_private_keys(&key).map_err(err_map)?.remove(0);

let mut root_cert_store = RootCertStore::empty();
root_cert_store.add(&ca_cert).unwrap();

// Allow (but do not require) client authentication.

let mut config = rustls::ServerConfig::builder()
.with_safe_defaults()
.with_client_cert_verifier(Arc::new(
rustls::server::AllowAnyAnonymousOrAuthenticatedClient::new(
root_cert_store,
),
))
.with_single_cert(certs, key)
.map_err(|e| anyhow!("Error setting cert: {:?}", e))
.unwrap();

match http_versions {
SupportedHttpVersions::All => {
config.alpn_protocols = vec!["h2".into(), "http/1.1".into()];
let key = {
let pkcs8_key = rustls_pemfile::pkcs8_private_keys(&mut key_reader)
.expect("Cannot load key file");
let rsa_key = rustls_pemfile::rsa_private_keys(&mut key_reader)
.expect("Cannot load key file");
if !pkcs8_key.is_empty() {
Some(pkcs8_key[0].clone())
} else if !rsa_key.is_empty() {
Some(rsa_key[0].clone())
} else {
None
}
SupportedHttpVersions::Http1Only => {}
SupportedHttpVersions::Http2Only => {
config.alpn_protocols = vec!["h2".into()];
};

match key {
Some(key) => {
let mut root_cert_store = rustls::RootCertStore::empty();
root_cert_store.add(&rustls::Certificate(ca_cert)).unwrap();

// Allow (but do not require) client authentication.

let mut config = rustls::ServerConfig::builder()
.with_safe_defaults()
.with_client_cert_verifier(Arc::new(
rustls::server::AllowAnyAnonymousOrAuthenticatedClient::new(
root_cert_store,
),
))
.with_single_cert(certs, PrivateKey(key))
.map_err(|e| anyhow!("Error setting cert: {:?}", e))
.unwrap();

match http_versions {
SupportedHttpVersions::All => {
config.alpn_protocols = vec!["h2".into(), "http/1.1".into()];
}
SupportedHttpVersions::Http1Only => {}
SupportedHttpVersions::Http2Only => {
config.alpn_protocols = vec!["h2".into()];
}
}

Ok(Arc::new(config))
}
None => Err(io::Error::new(io::ErrorKind::Other, "Cannot find key")),
}

Ok(Arc::new(config))
}
51 changes: 39 additions & 12 deletions tests/util/server/src/npm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,21 @@ use tar::Builder;
use crate::testdata_path;

pub static CUSTOM_NPM_PACKAGE_CACHE: Lazy<CustomNpmPackageCache> =
Lazy::new(CustomNpmPackageCache::default);
Lazy::new(|| {
CustomNpmPackageCache::new(format!(
"http://localhost:{}/npm/registry",
crate::servers::PORT,
))
});

pub static CUSTOM_NPM_PACKAGE_CACHE_FOR_PRIVATE_REGISTRY: Lazy<
CustomNpmPackageCache,
> = Lazy::new(|| {
CustomNpmPackageCache::new(format!(
"http://localhost:{}/npm/registry",
crate::servers::PRIVATE_NPM_REGISTRY_1_PORT
))
});

struct CustomNpmPackage {
pub registry_file: String,
Expand All @@ -25,10 +39,23 @@ struct CustomNpmPackage {

/// Creates tarballs and a registry json file for npm packages
/// in the `testdata/npm/registry/@denotest` directory.
#[derive(Default)]
pub struct CustomNpmPackageCache(Mutex<HashMap<String, CustomNpmPackage>>);
pub struct CustomNpmPackageCache {
registry_url: String,
cache: Mutex<HashMap<String, CustomNpmPackage>>,
}

impl CustomNpmPackageCache {
pub fn new(registry_url: String) -> Self {
let registry_url = registry_url
.strip_suffix('/')
.unwrap_or(&registry_url)
.to_string();
Self {
registry_url,
cache: Default::default(),
}
}

pub fn tarball_bytes(
&self,
name: &str,
Expand All @@ -51,19 +78,22 @@ impl CustomNpmPackageCache {
func: impl FnOnce(&CustomNpmPackage) -> TResult,
) -> Result<Option<TResult>> {
// it's ok if multiple threads race here as they will do the same work twice
if !self.0.lock().contains_key(package_name) {
match get_npm_package(package_name)? {
if !self.cache.lock().contains_key(package_name) {
match get_npm_package(package_name, &self.registry_url)? {
Some(package) => {
self.0.lock().insert(package_name.to_string(), package);
self.cache.lock().insert(package_name.to_string(), package);
}
None => return Ok(None),
}
}
Ok(self.0.lock().get(package_name).map(func))
Ok(self.cache.lock().get(package_name).map(func))
}
}

fn get_npm_package(package_name: &str) -> Result<Option<CustomNpmPackage>> {
fn get_npm_package(
package_name: &str,
registry_url: &str,
) -> Result<Option<CustomNpmPackage>> {
let package_folder = testdata_path().join("npm/registry").join(package_name);
if !package_folder.exists() {
return Ok(None);
Expand Down Expand Up @@ -111,10 +141,7 @@ fn get_npm_package(package_name: &str) -> Result<Option<CustomNpmPackage>> {
dist.insert("shasum".to_string(), "dummy-value".into());
dist.insert(
"tarball".to_string(),
format!(
"http://localhost:4545/npm/registry/{package_name}/{version}.tgz"
)
.into(),
format!("{registry_url}/{package_name}/{version}.tgz").into(),
);

tarballs.insert(version.clone(), tarball_bytes);
Expand Down
2 changes: 1 addition & 1 deletion tests/util/server/src/servers/grpc.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.

use deno_tls::TlsStream;
use futures::StreamExt;
use h2;
use hyper::header::HeaderName;
use hyper::header::HeaderValue;
use rustls_tokio_stream::TlsStream;
use tokio::net::TcpStream;
use tokio::task::LocalSet;

Expand Down
3 changes: 1 addition & 2 deletions tests/util/server/src/servers/hyper_utils.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.

use bytes::Bytes;
use deno_tls::TlsStream;
use futures::Future;
use futures::FutureExt;
use futures::Stream;
Expand Down Expand Up @@ -70,7 +69,7 @@ pub async fn run_server_with_acceptor<'a, A, F, S>(
error_msg: &'static str,
kind: ServerKind,
) where
A: Stream<Item = io::Result<TlsStream>> + ?Sized,
A: Stream<Item = io::Result<rustls_tokio_stream::TlsStream>> + ?Sized,
F: Fn(Request<hyper::body::Incoming>) -> S + Copy + 'static,
S: Future<Output = HandlerOutput> + 'static,
{
Expand Down
27 changes: 21 additions & 6 deletions tests/util/server/src/servers/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,10 +51,11 @@ use hyper_utils::ServerOptions;
use super::https::get_tls_listener_stream;
use super::https::SupportedHttpVersions;
use super::npm::CUSTOM_NPM_PACKAGE_CACHE;
use super::npm::CUSTOM_NPM_PACKAGE_CACHE_FOR_PRIVATE_REGISTRY;
use super::std_path;
use super::testdata_path;

const PORT: u16 = 4545;
pub(crate) const PORT: u16 = 4545;
const TEST_AUTH_TOKEN: &str = "abcdef123456789";
const TEST_BASIC_AUTH_USERNAME: &str = "testuser123";
const TEST_BASIC_AUTH_PASSWORD: &str = "testpassabc";
Expand Down Expand Up @@ -85,7 +86,7 @@ const H2_GRPC_PORT: u16 = 4246;
const H2S_GRPC_PORT: u16 = 4247;
const REGISTRY_SERVER_PORT: u16 = 4250;
const PROVENANCE_MOCK_SERVER_PORT: u16 = 4251;
const PRIVATE_NPM_REGISTRY_1_PORT: u16 = 4252;
pub(crate) const PRIVATE_NPM_REGISTRY_1_PORT: u16 = 4252;

// Use the single-threaded scheduler. The hyper server is used as a point of
// comparison for the (single-threaded!) benchmarks in cli/bench. We're not
Expand Down Expand Up @@ -1091,7 +1092,9 @@ async fn main_server(
}

// serve npm registry files
if let Some(resp) = try_serve_npm_registry(&req, file_path.clone()).await
if let Some(resp) =
try_serve_npm_registry(&req, file_path.clone(), NpmRegistryKind::Public)
.await
{
return resp;
} else if let Some(suffix) = req.uri().path().strip_prefix("/deno_std/") {
Expand Down Expand Up @@ -1120,6 +1123,11 @@ async fn main_server(

const PRIVATE_NPM_REGISTRY_AUTH_TOKEN: &str = "private-reg-token";

enum NpmRegistryKind {
Public,
Private,
}

async fn wrap_private_npm_registry1(port: u16) {
let npm_registry_addr = SocketAddr::from(([127, 0, 0, 1], port));
run_server(
Expand Down Expand Up @@ -1153,7 +1161,9 @@ async fn private_npm_registry1(
let mut file_path = testdata_path().to_path_buf();
file_path.push(&req.uri().path()[1..].replace("%2f", "/"));

if let Some(resp) = try_serve_npm_registry(&req, file_path).await {
if let Some(resp) =
try_serve_npm_registry(&req, file_path, NpmRegistryKind::Private).await
{
return resp;
}

Expand All @@ -1165,12 +1175,16 @@ async fn private_npm_registry1(

fn handle_custom_npm_registry_path(
path: &str,
registry_kind: NpmRegistryKind,
) -> Result<Option<Response<UnsyncBoxBody<Bytes, Infallible>>>, anyhow::Error> {
let parts = path
.split('/')
.filter(|p| !p.is_empty())
.collect::<Vec<_>>();
let cache = &CUSTOM_NPM_PACKAGE_CACHE;
let cache = match registry_kind {
NpmRegistryKind::Public => &CUSTOM_NPM_PACKAGE_CACHE,
NpmRegistryKind::Private => &CUSTOM_NPM_PACKAGE_CACHE_FOR_PRIVATE_REGISTRY,
};
let package_name = format!("@denotest/{}", parts[0]);
if parts.len() == 2 {
if let Some(file_bytes) =
Expand Down Expand Up @@ -1198,6 +1212,7 @@ fn should_download_npm_packages() -> bool {
async fn try_serve_npm_registry(
req: &Request<hyper::body::Incoming>,
mut file_path: PathBuf,
registry_kind: NpmRegistryKind,
) -> Option<Result<Response<UnsyncBoxBody<Bytes, Infallible>>, anyhow::Error>> {
if let Some(suffix) = req
.uri()
Expand All @@ -1207,7 +1222,7 @@ async fn try_serve_npm_registry(
{
// serve all requests to /npm/registry/@deno using the file system
// at that path
match handle_custom_npm_registry_path(suffix) {
match handle_custom_npm_registry_path(suffix, registry_kind) {
Ok(Some(response)) => return Some(Ok(response)),
Ok(None) => {} // ignore, not found
Err(err) => {
Expand Down

0 comments on commit 0b0af5c

Please sign in to comment.