Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1,600 changes: 1,043 additions & 557 deletions Cargo.lock

Large diffs are not rendered by default.

17 changes: 10 additions & 7 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ name = "promote-release"
version = "0.1.0"
authors = ["Alex Crichton <alex@alexcrichton.com>"]
license = "MIT OR Apache-2.0"
edition = "2018"
edition = "2024"

build = "build.rs"

Expand All @@ -14,19 +14,22 @@ fs2 = "0.4"
serde = { version = "1", features = ["derive"] }
serde_json = "1"
tar = "0.4"
toml = "0.7"
rand = "0.8"
toml = "1.1.0"
rand = "0.10"
xz2 = "0.1"
anyhow = "1.0.32"
rayon = "1.4.0"
sha2 = "0.10"
hex = "0.4.2"
pgp = "0.10"
rsa = "0.8"
base64 = "0.13"
base64 = "0.22"
chrono = "0.4.19"
git2 = "0.17"
git2 = "0.20.4"
tempfile = "3.1.0"
hyper = { version = "0.14", features = ["server", "tcp", "runtime", "http1"] }
tokio = { version = "1", features = ["sync"] }
tokio = { version = "1", features = ["rt", "time", "net", "sync", "macros"] }
hyper = "1.8.1"
hyper-util = { version = "0.1.20", features = ["http1", "server", "server-graceful", "tokio"] }
num_cpus = "1.13.0"
http-body-util = { version = "0.1.3", features = ["full"] }
bytes = "1.11.1"
11 changes: 8 additions & 3 deletions run.sh
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
# system. This requires docker and docker-compose to be installed.

set -euo pipefail
IFS=$'\n\t'

if [[ "$#" -lt 1 ]] || [[ "$#" -gt 2 ]]; then
echo "Usage: $0 <channel> [commit]"
Expand All @@ -12,7 +11,13 @@ fi
channel="$1"
override_commit="${2-}"

container_id="$(docker compose ps -q local)"
if command -v docker-compose 2>&1; then
compose_cmd="docker-compose"
else
compose_cmd="docker compose"
fi

container_id="$($compose_cmd ps -q local)"
if [[ "${container_id}" == "" ]]; then
container_status="missing"
else
Expand All @@ -31,4 +36,4 @@ fi
cargo build --release

# Run the command inside the docker environment.
docker compose exec -T local /src/local/run.sh "${channel}" "${override_commit}"
$compose_cmd exec -T local /src/local/run.sh "${channel}" "${override_commit}"
2 changes: 1 addition & 1 deletion src/branching.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use crate::{
github::{FullCommitData, RepositoryClient},
Context,
github::{FullCommitData, RepositoryClient},
};

impl Context {
Expand Down
2 changes: 1 addition & 1 deletion src/config.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use crate::Context;
use crate::discourse::Discourse;
use crate::fastly::Fastly;
use crate::github::Github;
use crate::Context;
use anyhow::{Context as _, Error};
use std::env::VarError;
use std::str::FromStr;
Expand Down
16 changes: 8 additions & 8 deletions src/github.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use crate::curl_helper::BodyExt;
use anyhow::Context;
use base64::prelude::{BASE64_STANDARD, BASE64_URL_SAFE_NO_PAD, Engine as _};
use curl::easy::Easy;
use rsa::pkcs1::DecodeRsaPrivateKey;
use sha2::Digest;
Expand Down Expand Up @@ -39,25 +40,24 @@ impl Github {
let header = r#"{"alg":"RS256","typ":"JWT"}"#;
let payload = serde_json::to_string(&payload).unwrap();

let encoding = base64::URL_SAFE_NO_PAD;
let signature = self
.key
.sign(
rsa::pkcs1v15::Pkcs1v15Sign::new::<sha2::Sha256>(),
&sha2::Sha256::new()
.chain_update(format!(
"{}.{}",
base64::encode_config(header, encoding),
base64::encode_config(&payload, encoding),
BASE64_URL_SAFE_NO_PAD.encode(header),
BASE64_URL_SAFE_NO_PAD.encode(&payload),
))
.finalize(),
)
.unwrap();
format!(
"{}.{}.{}",
base64::encode_config(header, encoding),
base64::encode_config(&payload, encoding),
base64::encode_config(signature, encoding),
BASE64_URL_SAFE_NO_PAD.encode(header),
BASE64_URL_SAFE_NO_PAD.encode(&payload),
BASE64_URL_SAFE_NO_PAD.encode(signature),
)
}

Expand Down Expand Up @@ -309,7 +309,7 @@ impl RepositoryClient<'_> {
.with_body(Request {
branch,
message: "Creating file via promote-release automation",
content: &base64::encode(content),
content: &BASE64_STANDARD.encode(content),
})
.send()?;
Ok(())
Expand Down Expand Up @@ -502,7 +502,7 @@ impl GitFile {
pub(crate) fn content(&self) -> anyhow::Result<String> {
if let GitFile::File { encoding, content } = self {
assert_eq!(encoding, "base64");
Ok(String::from_utf8(base64::decode(content.trim())?)?)
Ok(String::from_utf8(BASE64_STANDARD.decode(content.trim())?)?)
} else {
panic!("content() on {:?}", self);
}
Expand Down
22 changes: 11 additions & 11 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,6 @@ use chrono::Utc;
use curl::easy::Easy;
use fs2::FileExt;
use github::{CreateTag, Github};
use rand::prelude::Distribution;
use rand::thread_rng;
use serde::Deserialize;
use tempfile::NamedTempFile;

Expand Down Expand Up @@ -299,11 +297,11 @@ impl Context {
for entry in archive.entries()? {
let entry = entry?;
let path = entry.path()?;
if let Some(path) = path.iter().nth(1) {
if path == Path::new("version") {
version_file = Some(entry);
break;
}
if let Some(path) = path.iter().nth(1)
&& path == Path::new("version")
{
version_file = Some(entry);
break;
}
}
if let Some(mut entry) = version_file {
Expand Down Expand Up @@ -635,7 +633,7 @@ impl Context {
"Items": paths,
"Quantity": paths.len(),
},
"CallerReference": format!("rct-{}", rand::random::<usize>()),
"CallerReference": format!("rct-{}", rand::random::<u64>()),
})
.to_string();
let dst = self.work.join("payload.json");
Expand Down Expand Up @@ -665,7 +663,9 @@ impl Context {
None => {
println!();
println!("WARNING! Skipped Fastly invalidation of: {paths:?}");
println!("Set PROMOTE_RELEASE_FASTLY_API_TOKEN and PROMOTE_RELEASE_FASTLY_SERVICE_ID if you want to invalidate Fastly");
println!(
"Set PROMOTE_RELEASE_FASTLY_API_TOKEN and PROMOTE_RELEASE_FASTLY_SERVICE_ID if you want to invalidate Fastly"
);
println!();
return Ok(());
}
Expand All @@ -691,7 +691,7 @@ impl Context {
eprintln!("warning: failed to update manifests.txt, retrying...");
attempts += 1;

let delay = rand::distributions::Uniform::new(1, 10).sample(&mut thread_rng());
let delay = rand::random_range(1..10);
std::thread::sleep(Duration::from_secs(delay));
}
}
Expand Down Expand Up @@ -1010,7 +1010,7 @@ impl Context {
cmd
}

fn download_top_level_manifest(&mut self) -> Result<toml::Value, Error> {
fn download_top_level_manifest(&mut self) -> Result<toml::Table, Error> {
let url = format!(
"{}/{}/channel-rust-{}.toml",
self.config.upload_addr, self.config.upload_dir, self.config.channel
Expand Down
1 change: 1 addition & 0 deletions src/recompress.rs
Original file line number Diff line number Diff line change
Expand Up @@ -293,6 +293,7 @@ impl Context {
let mut tasks = Vec::new();
for _ in 0..self.config.num_threads {
tasks.push(s.spawn(|| {
#[allow(clippy::let_and_return)]
while let Some(xz_path) = {
// Extra block is needed to make sure the lock guard drops before we enter the
// loop iteration, because while-let is desugared to a loop + match, and match
Expand Down
2 changes: 1 addition & 1 deletion src/sign.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
use anyhow::Error;
use chrono::Utc;
use pgp::{
Deserializable, SignedSecretKey,
armor::BlockType,
crypto::hash::HashAlgorithm,
packet::{self, Packet, SignatureConfig, SignatureType, SignatureVersion},
types::{KeyTrait, SecretKeyTrait},
Deserializable, SignedSecretKey,
};
use rayon::prelude::*;
use sha2::Digest;
Expand Down
109 changes: 74 additions & 35 deletions src/smoke_test.rs
Original file line number Diff line number Diff line change
@@ -1,51 +1,86 @@
use anyhow::Error;
use hyper::{Body, Request, Response, Server, StatusCode};
use bytes::Bytes;
use http_body_util::Full;
use hyper::body::Incoming;
use hyper::{Request, Response, StatusCode};
use std::thread::JoinHandle;
use std::time::Duration;
use std::{net::SocketAddr, sync::Arc};
use std::{path::PathBuf, process::Command};
use tempfile::TempDir;
use tokio::{runtime::Runtime, sync::oneshot::Sender};
use tokio::runtime::Runtime;

use crate::config::Channel;

pub(crate) struct SmokeTester {
runtime: JoinHandle<Runtime>,
thread: JoinHandle<Runtime>,
server_addr: SocketAddr,
shutdown_send: Sender<()>,
shutdown: tokio::sync::oneshot::Sender<()>,
}

impl SmokeTester {
pub(crate) fn new(paths: &[PathBuf]) -> Result<Self, Error> {
let addr = SocketAddr::from(([127, 0, 0, 1], 0));

let paths = Arc::new(paths.to_vec());
let service = hyper::service::make_service_fn(move |_| {
let service = move || {
let paths = paths.clone();
async move {
Ok::<_, Error>(hyper::service::service_fn(move |req| {
let paths = paths.clone();
async move { server_handler(req, paths) }
}))
}
});
hyper::service::service_fn(move |req: hyper::Request<hyper::body::Incoming>| {
let paths = paths.clone();
async move { server_handler(req, paths) }
})
};

let (shutdown_send, shutdown_recv) = tokio::sync::oneshot::channel::<()>();
let server_mtx = std::sync::Arc::new(std::sync::Mutex::new(None));
let server_mtx_external = server_mtx.clone();
let runtime = std::thread::spawn(move || {
let runtime = tokio::runtime::Builder::new_current_thread()
.enable_all()
.build()
.unwrap();
let _guard = runtime.enter();
let server = Server::bind(&addr).serve(service);
let server_addr = server.local_addr();
*server_mtx.lock().unwrap() = Some(server_addr);
let server = server.with_graceful_shutdown(async {
shutdown_recv.await.unwrap();
eprintln!("Shutting down smoke test server...");
let runtime = tokio::runtime::Builder::new_current_thread()
.enable_all()
.build()
.unwrap();
let (tx, mut rx) = tokio::sync::oneshot::channel();
let thread = std::thread::spawn(move || {
runtime.block_on(async move {
let listener = tokio::net::TcpListener::bind(addr)
.await
.unwrap_or_else(|e| {
panic!("Failed to bind to {addr:?}: {e:?}");
});
let graceful = hyper_util::server::graceful::GracefulShutdown::new();
let mut server = hyper::server::conn::http1::Builder::new();
if cfg!(test) {
server.auto_date_header(false);
}
let server_addr = listener.local_addr().expect("local_addr successful");
*server_mtx.lock().unwrap() = Some(server_addr);
loop {
tokio::select! {
c = listener.accept() => {
let (stream, _peer_addr) = match c {
Ok(c) => c,
Err(e) => {
eprintln!("accept error: {:?}", e);
tokio::time::sleep(Duration::from_secs(1)).await;
continue;
}
};
let stream = hyper_util::rt::TokioIo::new(Box::pin(stream));

let conn = server.serve_connection(stream, service());
let conn = graceful.watch(conn);

tokio::spawn(async move {
if let Err(e) = conn.await {
eprintln!("connection error: {e:?}");
}
});
}
_ = &mut rx => {
graceful.shutdown().await;
break;
}
}
}
});
runtime.block_on(server).unwrap();
runtime
});

Expand All @@ -54,16 +89,16 @@ impl SmokeTester {
match value {
None => {
eprintln!("Waiting for server to boot...");
std::thread::sleep(std::time::Duration::from_secs(1));
std::thread::sleep(std::time::Duration::from_millis(10));
}
Some(other) => break other,
}
};

Ok(Self {
runtime,
thread,
server_addr,
shutdown_send,
shutdown: tx,
})
}

Expand Down Expand Up @@ -105,16 +140,17 @@ impl SmokeTester {
cargo(&["run"])?;

// Finally shut down the HTTP server and the tokio reactor.
self.shutdown_send
.send(())
.expect("failed to send shutdown message to the server");
self.runtime.join().unwrap().shutdown_background();
let _ = self.shutdown.send(());
self.thread.join().unwrap().shutdown_background();

Ok(())
}
}

fn server_handler(req: Request<Body>, paths: Arc<Vec<PathBuf>>) -> Result<Response<Body>, Error> {
fn server_handler(
req: Request<Incoming>,
paths: Arc<Vec<PathBuf>>,
) -> Result<Response<Full<Bytes>>, Error> {
let file_name = match req.uri().path().split('/').next_back() {
Some(file_name) => file_name,
None => return not_found(),
Expand All @@ -129,8 +165,11 @@ fn server_handler(req: Request<Body>, paths: Arc<Vec<PathBuf>>) -> Result<Respon
not_found()
}

fn not_found() -> Result<Response<Body>, Error> {
fn not_found() -> Result<Response<Full<Bytes>>, Error> {
let mut response = Response::new("404: Not Found\n".into());
*response.status_mut() = StatusCode::NOT_FOUND;
Ok(response)
}

#[cfg(test)]
mod test;
Loading
Loading