Compare commits
No commits in common. "10e274005365936b7f31b7efb91cef8aa9c1eff2" and "78eb0887cc182b689c4ca1457fa4522602fe3c79" have entirely different histories.
10e2740053
...
78eb0887cc
21 changed files with 0 additions and 2405 deletions
|
@ -1,3 +0,0 @@
|
||||||
.git/
|
|
||||||
target/
|
|
||||||
README.adoc
|
|
1524
images/gitea/Cargo.lock
generated
1524
images/gitea/Cargo.lock
generated
File diff suppressed because it is too large
Load diff
|
@ -1,27 +0,0 @@
|
||||||
[package]
|
|
||||||
name = "gitea"
|
|
||||||
version = "0.1.2"
|
|
||||||
edition = "2021"
|
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
clap = { version = "4.2.0", features = ["cargo", "color", "unicode", "std", "derive"] }
|
|
||||||
color-eyre = "0.6.2"
|
|
||||||
reqwest = { version = "0.11.16", default-features = false, features = ["blocking", "json", "rustls-tls-native-roots"] }
|
|
||||||
serde_json = "1.0.95"
|
|
||||||
serde = { version = "1.0.159", features = ["derive"] }
|
|
||||||
time = { version = "0.3.20", features = ["formatting", "parsing", "serde"] }
|
|
||||||
url = { version = "2.3.1", features = ["serde"] }
|
|
||||||
cli-table = "0.4.7"
|
|
||||||
globwalk = "0.8.1"
|
|
||||||
|
|
||||||
[profile.release]
|
|
||||||
strip = "debuginfo"
|
|
||||||
lto = true
|
|
||||||
|
|
||||||
[profile.dev.package.backtrace]
|
|
||||||
opt-level = 3
|
|
||||||
|
|
||||||
[patch.crates-io]
|
|
||||||
color-eyre = { git = "https://github.com/sclu1034/color-eyre.git", branch = "fork" }
|
|
|
@ -1,40 +0,0 @@
|
||||||
FROM rust:alpine AS builder
|
|
||||||
|
|
||||||
# Use a dummy project to ensure the crate index is up to date.
|
|
||||||
RUN set -e; \
|
|
||||||
cargo new --color always /tmp/dummy; \
|
|
||||||
cargo add --color always --manifest-path /tmp/dummy/Cargo.toml serde; \
|
|
||||||
rm -rf /tmp/dummy; \
|
|
||||||
cargo new --color always --bin /app
|
|
||||||
|
|
||||||
WORKDIR /app
|
|
||||||
|
|
||||||
RUN apk add --no-cache \
|
|
||||||
# openssl-dev \
|
|
||||||
# pkgconfig \
|
|
||||||
musl-dev
|
|
||||||
|
|
||||||
# Build dependencies with a dummy project to cache those regardless of changes
|
|
||||||
# in the actual source code
|
|
||||||
COPY ./Cargo.toml ./Cargo.lock /app/
|
|
||||||
RUN cargo build --color always --release --locked
|
|
||||||
|
|
||||||
COPY . .
|
|
||||||
RUN touch -a -m ./src/main.rs && cargo build --color always --release --locked
|
|
||||||
|
|
||||||
FROM alpine AS final
|
|
||||||
|
|
||||||
RUN apk add --no-cache ca-certificates
|
|
||||||
|
|
||||||
ENV RUST_BACKTRACE=1
|
|
||||||
ENV RUST_LOG=info
|
|
||||||
|
|
||||||
COPY --from=builder /app/target/release/gitea /bin/
|
|
||||||
|
|
||||||
# Put this last, as it's the only thing that's different between variants.
|
|
||||||
# This way, the previous layers can all be shared.
|
|
||||||
|
|
||||||
ARG VARIANT="missing build arg 'VARIANT'"
|
|
||||||
ARG VERSION="0.1.0"
|
|
||||||
LABEL version="$VERSION"
|
|
||||||
COPY ./shims/${VARIANT}/* /opt/resource/
|
|
|
@ -1,23 +0,0 @@
|
||||||
= Gitea Concourse Resource
|
|
||||||
|
|
||||||
== Actions
|
|
||||||
|
|
||||||
[source,yaml]
|
|
||||||
----
|
|
||||||
- name: prs
|
|
||||||
type: gitea-pr
|
|
||||||
icon: git
|
|
||||||
source:
|
|
||||||
access_token: ((my_cred.token))
|
|
||||||
repo: some-repo
|
|
||||||
owner: some_user_or_org
|
|
||||||
hostname: https://example.com
|
|
||||||
----
|
|
||||||
|
|
||||||
=== `check`
|
|
||||||
|
|
||||||
Returns the list of currently active pull requests
|
|
||||||
|
|
||||||
=== `in`
|
|
||||||
|
|
||||||
Writes information about the currently active pull requests to `$1/prs.json`, which can be consumed by a `load_var` step.
|
|
|
@ -1,2 +0,0 @@
|
||||||
#!/bin/sh
|
|
||||||
/bin/gitea package check "$@"
|
|
|
@ -1,2 +0,0 @@
|
||||||
#!/bin/sh
|
|
||||||
/bin/gitea package in "$@"
|
|
|
@ -1,2 +0,0 @@
|
||||||
#!/bin/sh
|
|
||||||
/bin/gitea package out "$@"
|
|
|
@ -1,2 +0,0 @@
|
||||||
#!/bin/sh
|
|
||||||
/bin/gitea pr check "$@"
|
|
|
@ -1,2 +0,0 @@
|
||||||
#!/bin/sh
|
|
||||||
/bin/gitea pr in "$@"
|
|
|
@ -1,2 +0,0 @@
|
||||||
#!/bin/sh
|
|
||||||
/bin/gitea pr out "$@"
|
|
|
@ -1,2 +0,0 @@
|
||||||
#!/bin/sh
|
|
||||||
/bin/gitea status check "$@"
|
|
|
@ -1,2 +0,0 @@
|
||||||
#!/bin/sh
|
|
||||||
/bin/gitea status in "$@"
|
|
|
@ -1,2 +0,0 @@
|
||||||
#!/bin/sh
|
|
||||||
/bin/gitea status out "$@"
|
|
|
@ -1,286 +0,0 @@
|
||||||
use std::fs;
|
|
||||||
use std::io::{self, Read, Write};
|
|
||||||
use std::path::Path;
|
|
||||||
|
|
||||||
use cli_table::format::Justify;
|
|
||||||
use cli_table::{Cell, Style, Table};
|
|
||||||
use color_eyre::eyre::{self, Context};
|
|
||||||
use color_eyre::{Help, Report, Result};
|
|
||||||
use globwalk::{DirEntry, GlobWalkerBuilder};
|
|
||||||
use reqwest::StatusCode;
|
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
use serde_json::json;
|
|
||||||
use time::format_description::well_known::Iso8601;
|
|
||||||
use url::Url;
|
|
||||||
|
|
||||||
use crate::types::{Package, PackageType};
|
|
||||||
use crate::util::make_client;
|
|
||||||
use crate::Action;
|
|
||||||
|
|
||||||
#[derive(Deserialize, Serialize, Debug)]
|
|
||||||
struct Source {
|
|
||||||
access_token: String,
|
|
||||||
owner: String,
|
|
||||||
url: Url,
|
|
||||||
r#type: PackageType,
|
|
||||||
name: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Deserialize, Serialize, Debug)]
|
|
||||||
struct Version {
|
|
||||||
version: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Deserialize, Serialize, Debug)]
|
|
||||||
struct Params {
|
|
||||||
version: String,
|
|
||||||
globs: Vec<String>,
|
|
||||||
#[serde(default)]
|
|
||||||
fail_fast: bool,
|
|
||||||
#[serde(default)]
|
|
||||||
r#override: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[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>,
|
|
||||||
params: Option<Params>,
|
|
||||||
}
|
|
||||||
|
|
||||||
fn action_check(conf: Config) -> Result<()> {
|
|
||||||
let client = make_client(&conf.source.access_token)?;
|
|
||||||
let url = conf
|
|
||||||
.source
|
|
||||||
.url
|
|
||||||
.join(&format!("api/v1/packages/{}", conf.source.owner))
|
|
||||||
.wrap_err("Invalid URL")?;
|
|
||||||
|
|
||||||
let mut pkgs: Vec<Package> = client
|
|
||||||
.get(url)
|
|
||||||
.query(&[
|
|
||||||
("type", conf.source.r#type.to_string()),
|
|
||||||
("q", conf.source.name),
|
|
||||||
])
|
|
||||||
.send()?
|
|
||||||
.json()?;
|
|
||||||
|
|
||||||
if pkgs.is_empty() {
|
|
||||||
return io::stdout().write_all(b"[]").map_err(From::from);
|
|
||||||
}
|
|
||||||
|
|
||||||
pkgs.sort_unstable_by_key(|pkg| pkg.created_at);
|
|
||||||
|
|
||||||
let table: Vec<_> = pkgs
|
|
||||||
.iter()
|
|
||||||
.map(|pkg| {
|
|
||||||
vec![
|
|
||||||
pkg.id.cell().justify(Justify::Center),
|
|
||||||
pkg.name.clone().cell(),
|
|
||||||
pkg.owner.login.clone().cell(),
|
|
||||||
pkg.version.clone().cell(),
|
|
||||||
pkg.created_at
|
|
||||||
.format(&Iso8601::DEFAULT)
|
|
||||||
.unwrap_or_default()
|
|
||||||
.cell(),
|
|
||||||
]
|
|
||||||
})
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
let table = table
|
|
||||||
.table()
|
|
||||||
.title(vec![
|
|
||||||
"#".cell().bold(true),
|
|
||||||
"Name".cell().bold(true),
|
|
||||||
"Owner".cell().bold(true),
|
|
||||||
"Version".cell().bold(true),
|
|
||||||
"Created At".cell().bold(true),
|
|
||||||
])
|
|
||||||
.bold(true);
|
|
||||||
|
|
||||||
let _ = cli_table::print_stderr(table);
|
|
||||||
|
|
||||||
let newest = pkgs
|
|
||||||
.last()
|
|
||||||
.expect("List of packages should not be empty.")
|
|
||||||
.version
|
|
||||||
.clone();
|
|
||||||
|
|
||||||
let out = if let Some(prev) = conf.version {
|
|
||||||
if prev.version == newest {
|
|
||||||
vec![prev]
|
|
||||||
} else {
|
|
||||||
vec![prev, Version { version: newest }]
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
vec![Version { version: newest }]
|
|
||||||
};
|
|
||||||
|
|
||||||
serde_json::to_writer_pretty(io::stdout(), &out)
|
|
||||||
.wrap_err("Failed to write result to stdout")?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn action_out(conf: Config, dir: impl AsRef<Path>) -> Result<()> {
|
|
||||||
let dir = dir
|
|
||||||
.as_ref()
|
|
||||||
.canonicalize()
|
|
||||||
.wrap_err_with(|| format!("Invalid file path '{}'", dir.as_ref().display()))?;
|
|
||||||
|
|
||||||
let Some(params) = conf.params else {
|
|
||||||
eyre::bail!("Missing params");
|
|
||||||
};
|
|
||||||
|
|
||||||
if params.globs.is_empty() {
|
|
||||||
eyre::bail!("`params.globs` must not be empty");
|
|
||||||
}
|
|
||||||
|
|
||||||
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/packages/{}/{}/{}/{}/",
|
|
||||||
conf.source.owner, conf.source.r#type, conf.source.name, params.version
|
|
||||||
))
|
|
||||||
.wrap_err("Invalid URL")?;
|
|
||||||
|
|
||||||
let walker = GlobWalkerBuilder::from_patterns(dir, ¶ms.globs)
|
|
||||||
.max_depth(3)
|
|
||||||
.file_type(globwalk::FileType::FILE)
|
|
||||||
.build()
|
|
||||||
.wrap_err("Failed to create glob walker")?;
|
|
||||||
|
|
||||||
let handle_glob = |entry| {
|
|
||||||
let entry: DirEntry = entry?;
|
|
||||||
|
|
||||||
let name = entry.file_name();
|
|
||||||
let path = entry.path();
|
|
||||||
let url = url.join(&name.to_string_lossy()).wrap_err("Invalid url")?;
|
|
||||||
|
|
||||||
if params.r#override {
|
|
||||||
client
|
|
||||||
.delete(url.clone())
|
|
||||||
.send()
|
|
||||||
.wrap_err_with(|| format!("Failed request 'DELETE {}'", &url))?;
|
|
||||||
}
|
|
||||||
|
|
||||||
let data = fs::read(path)
|
|
||||||
.wrap_err_with(|| format!("Failed to read package file '{}'", path.display()))?;
|
|
||||||
|
|
||||||
let res = client
|
|
||||||
.put(url.clone())
|
|
||||||
.body(data)
|
|
||||||
.send()
|
|
||||||
.wrap_err_with(|| format!("Failed request 'PUT {}'", &url))?;
|
|
||||||
|
|
||||||
match res.status() {
|
|
||||||
StatusCode::CREATED => {
|
|
||||||
eprintln!(
|
|
||||||
"Uploaded file '{}', version={}, package={}",
|
|
||||||
name.to_string_lossy(),
|
|
||||||
params.version,
|
|
||||||
conf.source.name
|
|
||||||
);
|
|
||||||
}
|
|
||||||
StatusCode::BAD_REQUEST => {
|
|
||||||
eyre::bail!(
|
|
||||||
"Package, version or file name are invalid. package={}, version={}, file={}",
|
|
||||||
conf.source.name,
|
|
||||||
params.version,
|
|
||||||
name.to_string_lossy(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
StatusCode::CONFLICT => {
|
|
||||||
eyre::bail!(
|
|
||||||
"File '{}' already exists. version={}, package={}",
|
|
||||||
name.to_string_lossy(),
|
|
||||||
params.version,
|
|
||||||
conf.source.name
|
|
||||||
);
|
|
||||||
}
|
|
||||||
StatusCode::INTERNAL_SERVER_ERROR => {
|
|
||||||
eyre::bail!(
|
|
||||||
"Internal server error: {} {}",
|
|
||||||
res.status(),
|
|
||||||
res.text().unwrap_or_default()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
code => {
|
|
||||||
eyre::bail!("Unexpected status code {}\ntext = {:?}", code, res.text());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok::<_, Report>(())
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut has_entry = false;
|
|
||||||
|
|
||||||
let mut errors = Vec::new();
|
|
||||||
for entry in walker {
|
|
||||||
has_entry = true;
|
|
||||||
if let Err(err) = handle_glob(entry) {
|
|
||||||
errors.push(err);
|
|
||||||
if params.fail_fast {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if !errors.is_empty() {
|
|
||||||
let mut err = eyre::eyre!("Failed to upload package with {} errors", errors.len());
|
|
||||||
|
|
||||||
for e in errors {
|
|
||||||
err = err.report(e);
|
|
||||||
}
|
|
||||||
|
|
||||||
return Err(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
if !has_entry {
|
|
||||||
eyre::bail!("Globs didn't produce any files");
|
|
||||||
}
|
|
||||||
|
|
||||||
let out = json!({
|
|
||||||
"version": {
|
|
||||||
"version": params.version
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
serde_json::to_writer_pretty(io::stdout(), &out)
|
|
||||||
.wrap_err("Failed to write result to stdout")?;
|
|
||||||
|
|
||||||
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 stdin")?
|
|
||||||
};
|
|
||||||
|
|
||||||
match action {
|
|
||||||
Action::Check => action_check(config),
|
|
||||||
Action::In { dest: _ } => Ok(()),
|
|
||||||
Action::Out { src } => action_out(config, src),
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,171 +0,0 @@
|
||||||
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;
|
|
||||||
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 stdin")?
|
|
||||||
};
|
|
||||||
|
|
||||||
match action {
|
|
||||||
Action::Check => action_check(config),
|
|
||||||
Action::In { dest } => action_in(config, dest),
|
|
||||||
Action::Out { src: _ } => Ok(()),
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,154 +0,0 @@
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,56 +0,0 @@
|
||||||
use std::path::PathBuf;
|
|
||||||
|
|
||||||
use clap::{Parser, Subcommand};
|
|
||||||
use color_eyre::Result;
|
|
||||||
|
|
||||||
static USER_AGENT: &str = concat!(env!("CARGO_PKG_NAME"), "/", env!("CARGO_PKG_VERSION"));
|
|
||||||
|
|
||||||
#[derive(Debug, Parser)]
|
|
||||||
#[command(author, version, about, long_about = None)]
|
|
||||||
#[command(propagate_version = true)]
|
|
||||||
struct Cli {
|
|
||||||
#[command(subcommand)]
|
|
||||||
command: Commands,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, Subcommand)]
|
|
||||||
enum Commands {
|
|
||||||
Pr {
|
|
||||||
#[command(subcommand)]
|
|
||||||
action: Action,
|
|
||||||
},
|
|
||||||
Package {
|
|
||||||
#[command(subcommand)]
|
|
||||||
action: Action,
|
|
||||||
},
|
|
||||||
Status {
|
|
||||||
#[command(subcommand)]
|
|
||||||
action: Action,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, Subcommand)]
|
|
||||||
pub(crate) enum Action {
|
|
||||||
Check,
|
|
||||||
In { dest: PathBuf },
|
|
||||||
Out { src: PathBuf },
|
|
||||||
}
|
|
||||||
|
|
||||||
mod types;
|
|
||||||
mod util;
|
|
||||||
mod cmd {
|
|
||||||
pub mod package;
|
|
||||||
pub mod pr;
|
|
||||||
pub mod status;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn main() -> Result<()> {
|
|
||||||
color_eyre::install()?;
|
|
||||||
let cli = Cli::parse();
|
|
||||||
|
|
||||||
match &cli.command {
|
|
||||||
Commands::Pr { action } => cmd::pr::run(action),
|
|
||||||
Commands::Package { action } => cmd::package::run(action),
|
|
||||||
Commands::Status { action } => cmd::status::run(action),
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,46 +0,0 @@
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
use time::OffsetDateTime;
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, Deserialize)]
|
|
||||||
pub struct Ref {
|
|
||||||
pub r#ref: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, Deserialize)]
|
|
||||||
pub struct User {
|
|
||||||
pub login: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, Deserialize)]
|
|
||||||
pub struct PullRequest {
|
|
||||||
pub number: u64,
|
|
||||||
pub title: String,
|
|
||||||
pub user: User,
|
|
||||||
pub base: Ref,
|
|
||||||
pub head: Ref,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Copy, Clone, Deserialize, Serialize, Debug)]
|
|
||||||
#[serde(rename_all = "snake_case")]
|
|
||||||
pub enum PackageType {
|
|
||||||
Generic,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl std::fmt::Display for PackageType {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
||||||
match self {
|
|
||||||
PackageType::Generic => write!(f, "generic"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, Deserialize)]
|
|
||||||
pub struct Package {
|
|
||||||
#[serde(with = "time::serde::iso8601")]
|
|
||||||
pub created_at: OffsetDateTime,
|
|
||||||
pub id: u64,
|
|
||||||
pub name: String,
|
|
||||||
pub owner: User,
|
|
||||||
pub r#type: PackageType,
|
|
||||||
pub version: String,
|
|
||||||
}
|
|
|
@ -1,19 +0,0 @@
|
||||||
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)
|
|
||||||
}
|
|
|
@ -1,38 +0,0 @@
|
||||||
#!/bin/sh
|
|
||||||
|
|
||||||
set -e
|
|
||||||
|
|
||||||
cargo build
|
|
||||||
|
|
||||||
run() {
|
|
||||||
./target/debug/gitea package "$1" <<EOF
|
|
||||||
{
|
|
||||||
$2
|
|
||||||
"source": {
|
|
||||||
"access_token": "$GITEA_ACCESS_TOKEN",
|
|
||||||
"owner": "concourse",
|
|
||||||
"url": "https://git.sclu1034.dev",
|
|
||||||
"type": "generic",
|
|
||||||
"name": "test"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
EOF
|
|
||||||
}
|
|
||||||
|
|
||||||
if [ -n "$1" ]; then
|
|
||||||
run "$@"
|
|
||||||
echo ""
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "$(tput bold)Test: Check existing versions$(tput sgr0)"
|
|
||||||
run check
|
|
||||||
printf "\n\n"
|
|
||||||
|
|
||||||
echo "$(tput bold)Test: Check 'v0.1.0'$(tput sgr0)"
|
|
||||||
run check '
|
|
||||||
"params": {
|
|
||||||
"version": "0.1.0"
|
|
||||||
},
|
|
||||||
'
|
|
||||||
printf "\n"
|
|
Loading…
Add table
Add a link
Reference in a new issue