feat(server/forgejo): verify branches exist in repo
Some checks failed
ci/woodpecker/push/docker Pipeline was successful
ci/woodpecker/push/release Pipeline was successful
ci/woodpecker/push/todo-check Pipeline was successful
ci/woodpecker/push/build Pipeline failed

This commit is contained in:
Paul Campbell 2024-04-09 14:52:12 +01:00
parent b093c002d4
commit ca37045e3a
3 changed files with 125 additions and 30 deletions

View file

@ -11,7 +11,7 @@ use super::{LoadedConfig, RepoActor};
pub async fn load(details: RepoDetails, addr: Addr<RepoActor>, net: Network) {
let config = match details.forge.forge_type {
ForgeType::ForgeJo => forge::forgejo::config::load(details, &net).await,
ForgeType::ForgeJo => forge::forgejo::config::load(&details, &net).await,
};
match config {
Ok(config) => addr.do_send(LoadedConfig(config)),

View file

@ -34,6 +34,10 @@ impl RepoConfig {
pub(crate) fn load(toml: &str) -> Result<Self, OneOf<(toml::de::Error,)>> {
toml::from_str(toml).map_err(OneOf::new)
}
pub(crate) const fn branches(&self) -> &RepoBranches {
&self.branches
}
}
impl Display for RepoConfig {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
@ -46,6 +50,19 @@ pub struct RepoBranches {
next: String,
dev: String,
}
impl RepoBranches {
pub fn main(&self) -> BranchName {
BranchName(self.main.clone())
}
pub fn next(&self) -> BranchName {
BranchName(self.next.clone())
}
pub fn dev(&self) -> BranchName {
BranchName(self.dev.clone())
}
}
impl Display for RepoBranches {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "{:?}", self)

View file

@ -3,26 +3,37 @@ use kxio::network::{self, Network};
use terrors::OneOf;
use tracing::{error, info};
use crate::server::config::{RepoConfig, RepoDetails};
use crate::server::config::{BranchName, RepoConfig, RepoDetails};
#[derive(Debug)]
pub struct RepoConfigFileNotFound;
#[derive(Debug)]
pub struct RepoConfigIsNotFile;
#[derive(Debug)]
pub struct RepoConfigDecodeError;
#[derive(Debug)]
pub struct RepoConfigParseError;
#[derive(Debug)]
pub struct RepoConfigUnknownError(pub network::StatusCode);
type RepoConfigLoadErrors = (
RepoConfigFileNotFound,
RepoConfigIsNotFile,
RepoConfigDecodeError,
RepoConfigParseError,
RepoConfigUnknownError,
RepoConfigBranchNotFound,
);
pub async fn load(
details: RepoDetails,
details: &RepoDetails,
net: &Network,
) -> Result<
RepoConfig,
OneOf<(
RepoConfigFileNotFound,
RepoConfigIsNotFile,
RepoConfigDecodeError,
RepoConfigParseError,
RepoConfigUnknownError,
)>,
> {
let hostname = details.forge.hostname;
let path = details.repo;
) -> Result<RepoConfig, OneOf<RepoConfigLoadErrors>> {
let hostname = &details.forge.hostname;
let path = &details.repo;
let filepath = ".git-next.toml";
let branch = details.branch;
let token = details.forge.token;
let branch = &details.branch;
let token = &details.forge.token;
let url = network::NetUrl::new(format!(
"https://{hostname}/api/v1/repos/{path}/contents/{filepath}?ref={branch}&token={token}"
));
@ -49,7 +60,7 @@ pub async fn load(
)),
})?;
let status = response.status_code();
match response.response_body() {
let config = match response.response_body() {
Some(body) => {
// we need to decode (see encoding field) the value of 'content' from the response
match body.content_type {
@ -61,7 +72,11 @@ pub async fn load(
error!(%status, "Failed to fetch repo config file");
Err(OneOf::new(RepoConfigUnknownError(status)))
}
}
}?;
let config = validate(details, config, net)
.await
.map_err(OneOf::broaden)?;
Ok(config)
}
fn decode_config(
@ -81,17 +96,6 @@ fn decode_config(
}
}
#[derive(Debug)]
pub struct RepoConfigFileNotFound;
#[derive(Debug)]
pub struct RepoConfigIsNotFile;
#[derive(Debug)]
pub struct RepoConfigDecodeError;
#[derive(Debug)]
pub struct RepoConfigParseError;
#[derive(Debug)]
pub struct RepoConfigUnknownError(pub network::StatusCode);
#[derive(Clone, Debug, serde::Deserialize)]
struct ForgeContentsResponse {
#[serde(rename = "type")]
@ -110,3 +114,77 @@ enum ForgeContentsType {
#[serde(rename = "submodule")]
Submodule,
}
#[derive(Debug)]
pub struct RepoConfigBranchNotFound(pub BranchName);
type RepoConfigValidateErrors = (RepoConfigBranchNotFound, RepoConfigUnknownError);
pub async fn validate(
details: &RepoDetails,
config: RepoConfig,
net: &Network,
) -> Result<RepoConfig, OneOf<RepoConfigValidateErrors>> {
let hostname = &details.forge.hostname;
let path = &details.repo;
let token = &details.forge.token;
let url = network::NetUrl::new(format!(
"https://{hostname}/api/v1/repos/{path}/branches?token={token}"
));
info!(%url, "Listing branches");
let request = network::NetRequest::new(
network::RequestMethod::Get,
url,
network::NetRequestHeaders::new(),
network::RequestBody::None,
network::ResponseType::Json,
None,
network::NetRequestLogging::Both,
);
let result = net.get::<BranchList>(request).await;
let response = result.map_err(|e| {
error!(?e, "Failed to list branches");
OneOf::new(RepoConfigUnknownError(
network::StatusCode::INTERNAL_SERVER_ERROR,
))
})?;
let branches = response.response_body().unwrap_or_default();
if !branches
.iter()
.any(|branch| branch.name() == config.branches().main())
{
return Err(OneOf::new(RepoConfigBranchNotFound(
config.branches().main(),
)));
}
if !branches
.iter()
.any(|branch| branch.name() == config.branches().next())
{
return Err(OneOf::new(RepoConfigBranchNotFound(
config.branches().next(),
)));
}
if !branches
.iter()
.any(|branch| branch.name() == config.branches().dev())
{
return Err(OneOf::new(RepoConfigBranchNotFound(
config.branches().dev(),
)));
}
Ok(config)
}
type BranchList = Vec<Branch>;
#[derive(Debug, serde::Deserialize)]
struct Branch {
name: String,
}
impl Branch {
fn name(&self) -> BranchName {
BranchName(self.name.clone())
}
}