1
Fork 0
ci-images/images/gitea/src/cmd/pr.rs

174 lines
4.5 KiB
Rust

use std::fs;
use std::io::{self, Read};
use std::path::Path;
use cli_table::format::Justify;
use cli_table::{print_stderr, Cell, Style, Table};
use color_eyre::eyre::{self, Context};
use color_eyre::{Result, Section as _, SectionExt as _};
use reqwest::blocking::Response;
use serde::{Deserialize, Serialize};
use serde_json::json;
use time::OffsetDateTime;
use url::Url;
use crate::types::PullRequest;
use crate::util::make_client;
use crate::Action;
#[derive(Deserialize, Serialize, Debug)]
struct Source {
access_token: String,
owner: String,
repo: String,
url: Url,
}
#[derive(Deserialize, Serialize, Debug)]
struct Version {
prs: String,
#[serde(with = "time::serde::iso8601")]
timestamp: OffsetDateTime,
}
impl<'a, I: Iterator<Item = &'a PullRequest>> From<I> for Version {
fn from(prs: I) -> Self {
Self {
prs: prs.fold(String::new(), |mut s, pr| {
if !s.is_empty() {
s.push(',');
}
s.push_str(&pr.number.to_string());
s
}),
timestamp: OffsetDateTime::now_utc(),
}
}
}
#[derive(Deserialize, Serialize, Debug)]
struct Config {
/// Resource configuration.
/// Passed verbatim from the definition in the pipeline.
source: Source,
/// For 'check':
/// Previous version of this resource.
/// Will be empty for the very first request.
/// For 'in':
/// The version metadata to create this as.
version: Option<Version>,
}
fn fetch(src: &Source) -> Result<Response> {
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))
.wrap_err("Invalid URL")?;
client
.get(url)
.query(&[("sort", "oldest"), ("state", "open")])
.send()
.map_err(From::from)
}
fn action_check(conf: Config) -> Result<()> {
let prs: Vec<PullRequest> = fetch(&conf.source)?.json()?;
let version = Version::from(prs.iter());
let out = if let Some(prev) = conf.version {
if prev.prs == version.prs {
vec![prev]
} else {
vec![prev, version]
}
} else {
vec![version]
};
let table: Vec<_> = prs
.iter()
.map(|pr| {
vec![
pr.number.cell().justify(Justify::Center),
pr.title.clone().cell(),
pr.user.login.clone().cell(),
pr.base.r#ref.clone().cell(),
pr.head.r#ref.clone().cell(),
]
})
.collect();
let table = table
.table()
.title(vec![
"#".cell().bold(true),
"Title".cell().bold(true),
"User".cell().bold(true),
"Base".cell().bold(true),
"Head".cell().bold(true),
])
.bold(true);
let _ = print_stderr(table);
serde_json::to_writer_pretty(io::stdout(), &out)
.wrap_err("Failed to write result to stdout")?;
Ok(())
}
fn action_in(conf: Config, dest: impl AsRef<Path>) -> Result<()> {
let old_version = if let Some(version) = conf.version {
version
} else {
eyre::bail!("Version missing in 'in' action.");
};
let bytes = fetch(&conf.source)?.bytes()?;
let prs: Vec<PullRequest> = serde_json::from_slice(&bytes)?;
let version = Version::from(prs.iter());
{
if version.prs != old_version.prs {
eyre::bail!("Version to fetch does not match current resource.");
}
}
let path = dest.as_ref().join("prs.json");
let _ = fs::create_dir_all(dest);
fs::write(&path, &bytes)?;
let out = json!({
"version": version,
});
serde_json::to_writer_pretty(io::stdout(), &out)?;
Ok(())
}
pub(crate) fn run(action: &Action) -> Result<()> {
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 config")
.with_suggestion(|| "Double-check the `source` and `params` sections in your pipeline")
.with_section(|| buf.header("JSON"))?
};
match action {
Action::Check => action_check(config),
Action::In { dest } => action_in(config, dest),
Action::Out { src: _ } => Ok(()),
}
}