1
Fork 0

gitea: Implement commit statuses

For now this is just a simple implementation only supporting `put`
steps. `in` is a noop, so `no_get: true` is recommended.
This commit is contained in:
Lucas Schwiderski 2023-11-23 11:39:12 +01:00
parent 3c5067f7ac
commit 0b40c16db4
8 changed files with 194 additions and 37 deletions

View file

@ -0,0 +1,2 @@
#!/bin/sh
/bin/gitea status check "$@"

2
images/gitea/shims/status/in Executable file
View file

@ -0,0 +1,2 @@
#!/bin/sh
/bin/gitea status in "$@"

2
images/gitea/shims/status/out Executable file
View file

@ -0,0 +1,2 @@
#!/bin/sh
/bin/gitea status out "$@"

View file

@ -7,8 +7,6 @@ use cli_table::{Cell, Style, Table};
use color_eyre::eyre::{self, Context}; use color_eyre::eyre::{self, Context};
use color_eyre::{Help, Report, Result}; use color_eyre::{Help, Report, Result};
use globwalk::{DirEntry, GlobWalkerBuilder}; use globwalk::{DirEntry, GlobWalkerBuilder};
use reqwest::blocking::Client;
use reqwest::header::HeaderMap;
use reqwest::StatusCode; use reqwest::StatusCode;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use serde_json::json; use serde_json::json;
@ -16,7 +14,8 @@ use time::format_description::well_known::Iso8601;
use url::Url; use url::Url;
use crate::types::{Package, PackageType}; use crate::types::{Package, PackageType};
use crate::{Action, USER_AGENT}; use crate::util::make_client;
use crate::Action;
#[derive(Deserialize, Serialize, Debug)] #[derive(Deserialize, Serialize, Debug)]
struct Source { struct Source {
@ -56,22 +55,8 @@ struct Config {
params: Option<Params>, params: Option<Params>,
} }
fn make_client(src: &Source) -> Result<Client> {
let mut headers = HeaderMap::new();
headers.insert(
"Authorization",
format!("token {}", src.access_token).try_into()?,
);
Client::builder()
.default_headers(headers)
.user_agent(USER_AGENT)
.build()
.map_err(From::from)
}
fn action_check(conf: Config) -> Result<()> { fn action_check(conf: Config) -> Result<()> {
let client = make_client(&conf.source)?; let client = make_client(&conf.source.access_token)?;
let url = conf let url = conf
.source .source
.url .url
@ -158,7 +143,7 @@ fn action_out(conf: Config, dir: impl AsRef<Path>) -> Result<()> {
eyre::bail!("`params.globs` must not be empty"); eyre::bail!("`params.globs` must not be empty");
} }
let client = make_client(&conf.source)?; let client = make_client(&conf.source.access_token)?;
let url = conf let url = conf
.source .source
.url .url

View file

@ -6,15 +6,15 @@ use cli_table::format::Justify;
use cli_table::{print_stderr, Cell, Style, Table}; use cli_table::{print_stderr, Cell, Style, Table};
use color_eyre::eyre::{self, Context}; use color_eyre::eyre::{self, Context};
use color_eyre::Result; use color_eyre::Result;
use reqwest::blocking::{Client, Response}; use reqwest::blocking::Response;
use reqwest::header::HeaderMap;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use serde_json::json; use serde_json::json;
use time::OffsetDateTime; use time::OffsetDateTime;
use url::Url; use url::Url;
use crate::types::PullRequest; use crate::types::PullRequest;
use crate::{Action, USER_AGENT}; use crate::util::make_client;
use crate::Action;
#[derive(Deserialize, Serialize, Debug)] #[derive(Deserialize, Serialize, Debug)]
struct Source { struct Source {
@ -59,22 +59,8 @@ struct Config {
version: Option<Version>, version: Option<Version>,
} }
fn make_client(src: &Source) -> Result<Client> {
let mut headers = HeaderMap::new();
headers.insert(
"Authorization",
format!("token {}", src.access_token).try_into()?,
);
Client::builder()
.default_headers(headers)
.user_agent(USER_AGENT)
.build()
.map_err(From::from)
}
fn fetch(src: &Source) -> Result<Response> { fn fetch(src: &Source) -> Result<Response> {
let client = make_client(&src).wrap_err("Failed to create HTTP client")?; let client = make_client(&src.access_token).wrap_err("Failed to create HTTP client")?;
let url = src let url = src
.url .url
.join(&format!("api/v1/repos/{}/{}/pulls", src.owner, src.repo)) .join(&format!("api/v1/repos/{}/{}/pulls", src.owner, src.repo))

View file

@ -0,0 +1,154 @@
use color_eyre::eyre;
use color_eyre::eyre::WrapErr;
use color_eyre::Help;
use color_eyre::Result;
use color_eyre::SectionExt;
use reqwest::StatusCode;
use serde::Deserialize;
use serde::Serialize;
use serde_json::json;
use std::io;
use std::io::Read;
use std::path::Path;
use url::Url;
use crate::util::make_client;
use crate::Action;
#[derive(Deserialize, Serialize, Debug)]
struct Source {
access_token: String,
owner: String,
url: Url,
repo: String,
sha: String,
context: String,
description: Option<String>,
target_url: Option<String>,
}
#[derive(Deserialize, Serialize, Debug, Copy, Clone)]
#[serde(rename_all = "snake_case")]
enum State {
Pending,
Success,
Error,
Failure,
Warning,
}
impl std::fmt::Display for State {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
State::Pending => write!(f, "pending"),
State::Success => write!(f, "success"),
State::Error => write!(f, "error"),
State::Failure => write!(f, "failure"),
State::Warning => write!(f, "warning"),
}
}
}
#[derive(Deserialize, Serialize, Debug)]
struct Params {
state: State,
description: Option<String>,
}
#[derive(Deserialize, Serialize, Debug)]
struct Config {
/// Resource configuration.
/// Passed verbatim from the definition in the pipeline.
source: Source,
params: Params,
}
fn action_out(conf: Config, _dir: impl AsRef<Path>) -> Result<()> {
let params = conf.params;
let description = params.description.or(conf.source.description);
let client = make_client(&conf.source.access_token)?;
let url = conf
.source
.url
.join(&format!(
// The trailing slash is required, to make sure this entire URL is used
// when `join`ing the file name later. Otherwise, the last component
// would be considered a "file" part itself, and replaced by a future `join`.
"api/v1/repos/{}/{}/statuses/{}",
conf.source.owner, conf.source.repo, conf.source.sha
))
.wrap_err("Invalid URL")?;
let body = json!({
"context": conf.source.context,
"description": description,
"state": params.state,
"target_url": conf.source.target_url,
});
let res = client
.post(url.clone())
.json(&body)
.send()
.wrap_err_with(|| format!("Failed to send request 'POST {}'", url))?;
match res.status() {
StatusCode::CREATED => {
eprintln!(
"Created status '{}' on commit '{}'",
params.state, conf.source.sha
);
}
StatusCode::BAD_REQUEST => {
eyre::bail!(
"Invalid request: {:?}. state={}, context={}, description={:?}, target_url={:?}",
res.text(),
params.state,
conf.source.context,
description,
conf.source.target_url
)
}
code => {
eyre::bail!("Unexpected status code {}\ntext = {:?}", code, res.text());
}
}
println!("{{ \"version\": {{}} }}");
Ok(())
}
pub(crate) fn run(action: &Action) -> Result<()> {
// TODO: Gitea does actually support fetching statuses, making `check` and `in` viable,
// theoretically. But it also doesn't make much sense to implement them.
match action {
Action::Check => {
// Dummy implemented that always reports nothing.
println!("[]");
Ok(())
}
Action::In { dest: _ } => {
println!("{{}}");
Ok(())
}
Action::Out { src } => {
let config: Config = {
let mut buf = String::new();
io::stdin()
.read_to_string(&mut buf)
.wrap_err("Failed to read from stdin")?;
if buf.is_empty() {
eyre::bail!("No data received on stdin");
}
serde_json::from_str(&buf)
.wrap_err("Failed to parse stdin")
.with_section(|| buf.header("JSON"))?
};
action_out(config, src)
}
}
}

View file

@ -23,6 +23,10 @@ enum Commands {
#[command(subcommand)] #[command(subcommand)]
action: Action, action: Action,
}, },
Status {
#[command(subcommand)]
action: Action,
},
} }
#[derive(Clone, Debug, Subcommand)] #[derive(Clone, Debug, Subcommand)]
@ -33,9 +37,11 @@ pub(crate) enum Action {
} }
mod types; mod types;
mod util;
mod cmd { mod cmd {
pub mod package; pub mod package;
pub mod pr; pub mod pr;
pub mod status;
} }
fn main() -> Result<()> { fn main() -> Result<()> {
@ -45,5 +51,6 @@ fn main() -> Result<()> {
match &cli.command { match &cli.command {
Commands::Pr { action } => cmd::pr::run(action), Commands::Pr { action } => cmd::pr::run(action),
Commands::Package { action } => cmd::package::run(action), Commands::Package { action } => cmd::package::run(action),
Commands::Status { action } => cmd::status::run(action),
} }
} }

19
images/gitea/src/util.rs Normal file
View file

@ -0,0 +1,19 @@
use color_eyre::Result;
use reqwest::blocking::Client;
use reqwest::header::HeaderMap;
use crate::USER_AGENT;
pub(crate) fn make_client(access_token: impl AsRef<str>) -> Result<Client> {
let mut headers = HeaderMap::new();
headers.insert(
"Authorization",
format!("token {}", access_token.as_ref()).try_into()?,
);
Client::builder()
.default_headers(headers)
.user_agent(USER_AGENT)
.build()
.map_err(From::from)
}