174 lines
4.5 KiB
Rust
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(()),
|
|
}
|
|
}
|