Skip to content

Commit

Permalink
Add avalanche
Browse files Browse the repository at this point in the history
  • Loading branch information
tarkah committed Nov 22, 2024
1 parent 152ce03 commit 7578c34
Show file tree
Hide file tree
Showing 23 changed files with 681 additions and 60 deletions.
20 changes: 20 additions & 0 deletions Cargo.lock

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

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ bytes = "1.5"
chrono = "0.4.30"
color-eyre = "0.6.2"
derive_more = "0.99.17"
flate2 = "1.0"
futures = "0.3.30"
hex = "0.4.3"
http = "1.0"
Expand Down
30 changes: 24 additions & 6 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -2,32 +2,50 @@ FROM rust:alpine3.20 AS rust-builder
ENV RUSTUP_HOME="/usr/local/rustup" \
CARGO_HOME="/usr/local/cargo" \
CARGO_TARGET_DIR="/tmp/target"
RUN apk add --no-cache musl-dev openssl-dev openssl-libs-static git
WORKDIR /src
RUN apk add --no-cache musl-dev openssl-dev openssl-libs-static
RUN --mount=type=cache,target=/usr/local/cargo/registry \
--mount=type=cache,target=/usr/local/cargo/git <<"EOT" /bin/sh
git clone https://github.com/serpent-os/tools /tools
cd /tools
git checkout fix/run-in-docker
cargo install --path ./boulder
EOT
RUN --mount=type=cache,target=/usr/local/cargo/registry \
--mount=type=cache,target=/usr/local/cargo/git \
--mount=type=cache,target=/tmp/target \
--mount=type=bind,target=/src <<"EOT" /bin/sh
for target in vessel summit
for target in vessel summit avalanche
do
cargo build -p "$target"
cp "/tmp/target/debug/$target" /
done
EOT

FROM alpine:3.20 AS summit
WORKDIR /app
COPY --from=rust-builder /summit .
VOLUME /app/state
VOLUME /app/config.toml
EXPOSE 5000
WORKDIR /app
COPY --from=rust-builder /summit .
CMD ["/app/summit", "0.0.0.0", "--port", "5000", "--root", "/app"]

FROM alpine:3.20 AS vessel
WORKDIR /app
COPY --from=rust-builder /vessel .
VOLUME /app/state
VOLUME /app/config.toml
VOLUME /import
EXPOSE 5001
WORKDIR /app
COPY --from=rust-builder /vessel .
CMD ["/app/vessel", "0.0.0.0", "--port", "5001", "--root", "/app", "--import", "/import"]

FROM alpine:3.20 AS avalanche
WORKDIR /app
RUN apk add --no-cache sudo git
COPY --from=rust-builder /avalanche .
COPY --from=rust-builder /usr/local/cargo/bin/boulder /usr/bin/boulder
COPY --from=rust-builder /tools/boulder/data/macros /usr/share/boulder/macros
VOLUME /app/state
VOLUME /app/config.toml
EXPOSE 5002
CMD ["/app/avalanche", "0.0.0.0", "--port", "5002", "--root", "/app"]
21 changes: 21 additions & 0 deletions crates/avalanche/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
[package]
name = "avalanche"
version = "0.1.0"
edition.workspace = true

[dependencies]
service = { path = "../service" }
service-types = { path = "../service-types" }

clap.workspace = true
color-eyre.workspace = true
flate2.workspace = true
futures.workspace = true
hex.workspace = true
http.workspace = true
itertools.workspace = true
sha2.workspace = true
thiserror.workspace = true
tokio.workspace = true
tracing.workspace = true
uuid.workspace = true
98 changes: 98 additions & 0 deletions crates/avalanche/src/api.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
use std::sync::atomic::{self, AtomicBool};

use service::{api, database, endpoint, Endpoint, State};
use thiserror::Error;
use tracing::{error, info};

use crate::Config;

static BUILD_IN_PROGRESS: AtomicBool = AtomicBool::new(false);

pub fn service(state: State, config: Config) -> api::Service {
api::Service::new()
.register::<api::v1::avalanche::Build, Error, _>(build)
.with_state(Context { state, config })
}

#[derive(Clone)]
struct Context {
state: State,
config: Config,
}

#[tracing::instrument(
skip_all,
fields(
build_id = %request.body.request.build_id,
)
)]
async fn build(request: api::Request<api::v1::avalanche::Build>, context: Context) -> Result<(), Error> {
let token = request.token.ok_or(Error::MissingRequestToken)?;

let endpoint_id = token
.decoded
.payload
.sub
.parse::<endpoint::Id>()
.map_err(Error::InvalidEndpoint)?;
let endpoint = Endpoint::get(&context.state.db, endpoint_id)
.await
.map_err(Error::LoadEndpoint)?;

let build = request.body.request;

if build.remotes.is_empty() {
return Err(Error::MissingRemotes);
}

info!(
endpoint = %endpoint.id,
"Build request received"
);

// Atomically guarantee another build isn't in progress
if BUILD_IN_PROGRESS
.compare_exchange(false, true, atomic::Ordering::SeqCst, atomic::Ordering::Relaxed)
.is_err()
{
return Err(Error::BuildInProgress);
}

// Build time!
tokio::spawn(async move {
crate::build(build, endpoint, context.state, context.config).await;
BUILD_IN_PROGRESS.store(false, atomic::Ordering::Relaxed);
});

Ok(())
}

#[derive(Debug, Error)]
pub enum Error {
/// Required token is missing from the request
#[error("Token missing from request")]
MissingRequestToken,
/// Remotes missing from request
#[error("Missing remotes")]
MissingRemotes,
/// Another build is already in progress
#[error("Another build is already in progress")]
BuildInProgress,
/// Endpoint (UUIDv4) cannot be parsed from string
#[error("invalid endpoint")]
InvalidEndpoint(#[source] uuid::Error),
/// Failed to load endpoint from DB
#[error("load endpoint")]
LoadEndpoint(#[source] database::Error),
}

impl From<&Error> for http::StatusCode {
fn from(error: &Error) -> Self {
match error {
Error::MissingRequestToken => http::StatusCode::UNAUTHORIZED,
Error::MissingRemotes | Error::InvalidEndpoint(_) => http::StatusCode::BAD_REQUEST,
Error::LoadEndpoint(_) => http::StatusCode::INTERNAL_SERVER_ERROR,
Error::BuildInProgress => http::StatusCode::SERVICE_UNAVAILABLE,
}
}
}
Loading

0 comments on commit 7578c34

Please sign in to comment.