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:
parent
3c5067f7ac
commit
0b40c16db4
8 changed files with 194 additions and 37 deletions
2
images/gitea/shims/status/check
Executable file
2
images/gitea/shims/status/check
Executable file
|
@ -0,0 +1,2 @@
|
|||
#!/bin/sh
|
||||
/bin/gitea status check "$@"
|
2
images/gitea/shims/status/in
Executable file
2
images/gitea/shims/status/in
Executable file
|
@ -0,0 +1,2 @@
|
|||
#!/bin/sh
|
||||
/bin/gitea status in "$@"
|
2
images/gitea/shims/status/out
Executable file
2
images/gitea/shims/status/out
Executable file
|
@ -0,0 +1,2 @@
|
|||
#!/bin/sh
|
||||
/bin/gitea status out "$@"
|
|
@ -7,8 +7,6 @@ use cli_table::{Cell, Style, Table};
|
|||
use color_eyre::eyre::{self, Context};
|
||||
use color_eyre::{Help, Report, Result};
|
||||
use globwalk::{DirEntry, GlobWalkerBuilder};
|
||||
use reqwest::blocking::Client;
|
||||
use reqwest::header::HeaderMap;
|
||||
use reqwest::StatusCode;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::json;
|
||||
|
@ -16,7 +14,8 @@ use time::format_description::well_known::Iso8601;
|
|||
use url::Url;
|
||||
|
||||
use crate::types::{Package, PackageType};
|
||||
use crate::{Action, USER_AGENT};
|
||||
use crate::util::make_client;
|
||||
use crate::Action;
|
||||
|
||||
#[derive(Deserialize, Serialize, Debug)]
|
||||
struct Source {
|
||||
|
@ -56,22 +55,8 @@ struct Config {
|
|||
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<()> {
|
||||
let client = make_client(&conf.source)?;
|
||||
let client = make_client(&conf.source.access_token)?;
|
||||
let url = conf
|
||||
.source
|
||||
.url
|
||||
|
@ -158,7 +143,7 @@ fn action_out(conf: Config, dir: impl AsRef<Path>) -> Result<()> {
|
|||
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
|
||||
.source
|
||||
.url
|
||||
|
|
|
@ -6,15 +6,15 @@ use cli_table::format::Justify;
|
|||
use cli_table::{print_stderr, Cell, Style, Table};
|
||||
use color_eyre::eyre::{self, Context};
|
||||
use color_eyre::Result;
|
||||
use reqwest::blocking::{Client, Response};
|
||||
use reqwest::header::HeaderMap;
|
||||
use reqwest::blocking::Response;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::json;
|
||||
use time::OffsetDateTime;
|
||||
use url::Url;
|
||||
|
||||
use crate::types::PullRequest;
|
||||
use crate::{Action, USER_AGENT};
|
||||
use crate::util::make_client;
|
||||
use crate::Action;
|
||||
|
||||
#[derive(Deserialize, Serialize, Debug)]
|
||||
struct Source {
|
||||
|
@ -59,22 +59,8 @@ struct Config {
|
|||
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> {
|
||||
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
|
||||
.url
|
||||
.join(&format!("api/v1/repos/{}/{}/pulls", src.owner, src.repo))
|
||||
|
|
154
images/gitea/src/cmd/status.rs
Normal file
154
images/gitea/src/cmd/status.rs
Normal 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)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -23,6 +23,10 @@ enum Commands {
|
|||
#[command(subcommand)]
|
||||
action: Action,
|
||||
},
|
||||
Status {
|
||||
#[command(subcommand)]
|
||||
action: Action,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Subcommand)]
|
||||
|
@ -33,9 +37,11 @@ pub(crate) enum Action {
|
|||
}
|
||||
|
||||
mod types;
|
||||
mod util;
|
||||
mod cmd {
|
||||
pub mod package;
|
||||
pub mod pr;
|
||||
pub mod status;
|
||||
}
|
||||
|
||||
fn main() -> Result<()> {
|
||||
|
@ -45,5 +51,6 @@ fn main() -> Result<()> {
|
|||
match &cli.command {
|
||||
Commands::Pr { action } => cmd::pr::run(action),
|
||||
Commands::Package { action } => cmd::package::run(action),
|
||||
Commands::Status { action } => cmd::status::run(action),
|
||||
}
|
||||
}
|
||||
|
|
19
images/gitea/src/util.rs
Normal file
19
images/gitea/src/util.rs
Normal 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)
|
||||
}
|
Loading…
Add table
Reference in a new issue