init: basic github actions impl in rust

This commit is contained in:
Erica Marigold 2023-09-30 12:39:29 +05:30
parent 3474131750
commit cc1a388eef
No known key found for this signature in database
GPG key ID: 7843994FD1386E35
9 changed files with 1407 additions and 0 deletions

5
package/action/.gitignore vendored Normal file
View file

@ -0,0 +1,5 @@
# Cargo
target/
# Downloaded files
lune-*

5
package/action/.vscode/settings.json vendored Normal file
View file

@ -0,0 +1,5 @@
{
"rust-analyzer.linkedProjects": [
"./Cargo.toml"
]
}

1065
package/action/Cargo.lock generated Normal file

File diff suppressed because it is too large Load diff

18
package/action/Cargo.toml Normal file
View file

@ -0,0 +1,18 @@
[package]
name = "action"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
actions-core = "0.0.2"
anyhow = "1.0.75"
better-panic = "0.3.0"
directories = "5.0.1"
home = "0.5.5"
serde = { version = "1.0.188", features = ["derive"] }
serde_json = "1.0.107"
ureq = "2.7.1"
url = "2.4.1"
zip = "0.6.6"

View file

@ -0,0 +1,116 @@
use std::{
env,
fs::File,
io::{Error, ErrorKind, Read, Seek, Write},
path::PathBuf,
str::FromStr,
};
use anyhow::Result;
use directories::BaseDirs;
use url::Url;
use zip::ZipArchive;
use crate::{
github::{FetchResult, Fetcher, GitHub, Method},
types::{LuneReleaseData, LuneReleaseFetched},
};
fn parse_release_data(data: &LuneReleaseData) -> LuneReleaseFetched {
let unknown = &String::from("UNKNOWN");
let mut version = unknown.to_owned();
let mut download = unknown.to_owned();
let mut artifact_name = unknown.to_owned();
for asset in &data.assets {
let parts = asset
.name
.split("-")
.map(|s| s.to_string())
.collect::<Vec<String>>();
// [0] => always "lune"
// [1] => version
// [2] => platform
// [3] => arch .. ".zip"
if parts[2] == env::consts::OS && parts[3].trim_end_matches(".zip") == env::consts::ARCH {
version = (&parts[1]).to_owned();
download = (&asset.browser_download_url).to_owned();
artifact_name = (&asset.name).to_owned();
}
}
LuneReleaseFetched {
version,
download,
artifact_name,
raw: data.clone(),
}
}
pub fn download_release() -> Result<(PathBuf, LuneReleaseFetched)> {
let parsed_release_data =
parse_release_data(&GitHub::new(("filiptibell", "lune")).fetch_releases()?);
let fetcher = Fetcher::new();
let resp = fetcher.fetch::<_, ()>(
Method::Get,
Url::from_str(&parsed_release_data.download.as_str())?,
false,
)?;
match resp {
FetchResult::Response(res) => {
let mut zip_archive = File::create(&parsed_release_data.artifact_name)?;
let mut bytes = res
.into_reader()
.bytes()
.map(|elem| elem.unwrap())
.collect::<Vec<u8>>();
zip_archive.write_all(&mut bytes)?
}
FetchResult::Result(_) => unreachable!("Fetcher returned Result instead of Response"),
};
Ok((
PathBuf::from(&parsed_release_data.artifact_name),
parsed_release_data,
))
}
pub fn install_lune(lune_archive_reader: impl Read + Seek, meta: LuneReleaseFetched) -> Result<()> {
let dir_name = meta.artifact_name.trim_end_matches(".zip");
let mut lune_zip = ZipArchive::new(lune_archive_reader)?;
lune_zip.extract(dir_name)?;
let bin_name = match env::consts::OS {
"windows" => "lune.exe",
"macos" | "linux" => "lune",
_ => panic!("unsupported platform"),
};
let bin_base_dir = BaseDirs::new()
.ok_or(Error::new(
ErrorKind::NotFound,
"failed to create BaseDirs instance",
))?
.home_dir()
.join("bin");
if !bin_base_dir.exists() {
std::fs::create_dir(&bin_base_dir)?;
}
// TODO: macOS and windows support
let lune_bin_path = bin_base_dir.join(bin_name);
File::create(&lune_bin_path)?;
std::fs::rename(PathBuf::from(dir_name).join(bin_name), lune_bin_path)?;
Ok(())
}

View file

@ -0,0 +1,153 @@
use std::{collections::HashMap, str::FromStr, time::Duration};
use anyhow::Result;
use serde::{de::DeserializeOwned, Deserialize};
use ureq::Response;
use url::Url;
use crate::types::LuneReleaseData;
pub struct GitHub {
fetcher: Fetcher,
repository: Repository,
}
impl GitHub {
pub fn new(repo: (&str, &str)) -> Self {
let fetcher = Fetcher::new();
Self {
fetcher: (&fetcher).to_owned(),
repository: Repository::new(repo.0, repo.1, fetcher),
}
}
pub fn fetch_releases(&self) -> Result<LuneReleaseData> {
let api_uri = Url::from_str(self.repository.api_url().as_str())?
.join("releases/")?
.join("latest")?;
Ok(
match self
.fetcher
.fetch::<_, LuneReleaseData>(Method::Get, api_uri, true)?
{
FetchResult::Result(res) => res,
FetchResult::Response(_) => {
unreachable!("Fetcher returned Response instead of Result")
}
},
)
}
}
pub struct Repository {
fetcher: Fetcher,
scope: String,
repo: String,
}
#[derive(Debug, Deserialize, Clone)]
pub struct RepositoryMeta {
pub full_name: String,
pub name: String,
pub description: String,
pub license: HashMap<String, String>,
pub topics: Vec<String>,
pub forks: u32,
pub open_issues: u32,
pub stars: u32,
}
impl Repository {
pub fn new<T>(scope: T, repo: T, fetcher: Fetcher) -> Self
where
T: ToString,
{
Self {
fetcher,
scope: scope.to_string(),
repo: repo.to_string(),
}
}
pub fn scope(&self) -> &String {
return &self.scope;
}
pub fn repo(&self) -> &String {
return &self.repo;
}
pub fn api_url(&self) -> String {
return format!("https://api.github.com/repos/{}/{}/", self.scope, self.repo);
}
pub fn fetch_meta(&self) -> Result<RepositoryMeta> {
Ok(
match self.fetcher.fetch::<_, RepositoryMeta>(
Method::Get,
Url::from_str(self.api_url().as_str())?,
true,
)? {
FetchResult::Result(deserialized) => deserialized,
FetchResult::Response(_) => {
unreachable!("Fetcher returned Response instead of Result")
}
},
)
}
}
#[derive(Debug, Clone)]
pub struct Fetcher {
client: ureq::Agent,
}
pub enum Method {
Get,
Post,
}
pub enum FetchResult<D> {
Result(D),
Response(Response),
}
impl Fetcher {
pub fn new() -> Self {
Self {
client: ureq::builder()
.redirects(2)
.https_only(true)
.timeout(Duration::from_secs(30))
.user_agent("lune-action/0.1.0")
.build(),
}
}
pub fn fetch<U, D>(
&self,
method: Method,
uri: U,
to_deserialize: bool,
) -> Result<FetchResult<D>>
where
U: Into<url::Url>,
D: DeserializeOwned,
{
let method = match method {
Method::Get => "GET",
Method::Post => "POST",
};
let req = self.client.request_url(method, &uri.into());
Ok(match to_deserialize {
true => {
FetchResult::Result(serde_json::from_reader::<_, D>(req.call()?.into_reader())?)
}
false => FetchResult::Response(req.call()?),
})
}
}

View file

@ -0,0 +1,3 @@
pub mod types;
pub mod github;
pub mod download;

View file

@ -0,0 +1,21 @@
use std::fs::File;
use action::download::{download_release, install_lune};
fn main() {
better_panic::install();
let (zip_path, meta) = download_release().expect("failed to download latest lune release");
install_lune(
File::open(&zip_path).expect(
format!(
"failed to open downloaded lune release zip file @ {}",
zip_path.to_string_lossy().to_string()
)
.as_str(),
),
meta,
)
.expect("failed to install lune. did we not have perms to write to the required directories?");
}

View file

@ -0,0 +1,21 @@
use serde::Deserialize;
#[derive(Deserialize, Debug, Clone)]
pub struct LuneReleaseData {
pub name: String,
pub assets: Vec<LuneAssetData>
}
#[derive(Deserialize, Debug, Clone)]
pub struct LuneAssetData {
pub browser_download_url: String,
pub name: String,
}
#[derive(Deserialize, Debug, Clone)]
pub struct LuneReleaseFetched {
pub version: String,
pub download: String,
pub artifact_name: String,
pub raw: LuneReleaseData,
}