Initial stabilization #1

Merged
dzu merged 22 commits from stabilize-v1 into main 2026-02-21 04:54:42 +01:00
4 changed files with 1347 additions and 96 deletions

View file

@ -0,0 +1,26 @@
name: Build and update the action
on:
workflow_dispatch:
push:
branches:
- main
- dev
jobs:
build-and-upload:
runs-on: docker
steps:
- name: Build and update the action
uses: https://git.dzuchun.ing/actions/anode-build@main
with:
src-repository: https://git.dzuchun.ing/actions/cache-save-src.git
src-ref: ${{ forgejo.ref_name }}
src-token: ${{ secrets.GIT_TOKEN }}
dst-repository: https://git.dzuchun.ing/actions/cache-save.git
dst-ref: ${{ forgejo.ref_name }}
dst-token: ${{ secrets.GIT_TOKEN }}
dst-email: forgejo-actions@dzuchun.ing
opt-release: ${{ forgejo.ref_name == 'main' }}
opt-build-std: ${{ forgejo.ref_name == 'main' }}
opt-upx: ${{ forgejo.ref_name == 'main' }}

1216
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -4,11 +4,23 @@ version = "0.1.0"
edition = "2024" edition = "2024"
[dependencies] [dependencies]
anode = { git = "https://git.dzuchun.ing/dzu/anode.git", version = "0.1.0" } anyhow = "1.0.101"
thiserror = "2.0.18" flate2 = "1.1.9"
secrecy = "0.10.3"
tar = "0.4.44"
tracing = { version = "0.1.44", features = ["attributes"] }
[build-dependencies] [dependencies.anode]
anode-build = { git = "https://git.dzuchun.ing/dzu/anode.git", version = "0.1.0" } git = "https://git.dzuchun.ing/dzu/anode.git"
rev = "98addf4d54ae16c21979a1dae63d17738b3a277b"
[dependencies.anode-cache-common]
git = "https://git.dzuchun.ing/actions/cache-common-src.git"
rev = "5656a3ff6f58a48e3365c221fb2102918db6c284"
[build-dependencies.anode-build]
git = "https://git.dzuchun.ing/dzu/anode.git"
rev = "98addf4d54ae16c21979a1dae63d17738b3a277b"
[profile.release] [profile.release]
opt-level = "s" opt-level = "s"

View file

@ -1,43 +1,11 @@
use std::{ffi::OsString, path::PathBuf, str::FromStr, string::FromUtf8Error}; use std::path::PathBuf;
fn paths_parser(input: OsString) -> Result<Vec<PathBuf>, FromUtf8Error> { use anode_cache_common::{CacheKey, Client, actions_token, key_parser, paths_parser};
let input = String::from_utf8(input.into_encoded_bytes())?; use anyhow::{Context, Result};
let mut paths = Vec::new(); use flate2::{Compression, GzBuilder};
for path in input.split('\n') { use secrecy::ExposeSecret;
paths.push(match PathBuf::from_str(path) { use tar::{Builder, HeaderMode};
Ok(ok) => ok, use tracing::{debug, info_span, warn};
});
}
Ok(paths)
}
#[derive(Debug)]
struct CacheKey(String);
#[derive(Debug, thiserror::Error)]
enum CacheKeyError {
#[error(transparent)]
Utf8(#[from] FromUtf8Error),
#[error("Cache key is at most 512 characters long. {len} characters long key provided")]
Length { len: usize },
#[error("Cache key cannot contain commas. Comma found at offset {offset}")]
Comma { offset: usize },
}
fn key_parser(input: OsString) -> Result<CacheKey, CacheKeyError> {
let input = String::from_utf8(input.into_encoded_bytes())?;
let mut len = 0;
for (offset, ch) in input.char_indices() {
if ch == ',' {
return Err(CacheKeyError::Comma { offset });
}
len += 1;
}
if len > 512 {
return Err(CacheKeyError::Length { len });
}
Ok(CacheKey(input))
}
#[derive(Debug, anode::Input)] #[derive(Debug, anode::Input)]
#[name = "Cache save"] #[name = "Cache save"]
@ -54,9 +22,60 @@ struct Input {
/// Must be in UTF-8 /// Must be in UTF-8
#[parser(key_parser)] #[parser(key_parser)]
key: CacheKey, key: CacheKey,
/// If the debug log should be printed
///
/// If omitted, defined by compiler settings (print debug if `debug_assertions` is enabled)
debug: Option<bool>,
} }
fn main() { fn main() -> Result<()> {
let input = Input::get(); // parse inputs
println!("Inputs: {input:#?}"); let Input { path, key, debug } = Input::get();
anode_cache_common::setup_logger(debug.unwrap_or(cfg!(debug_assertions)));
let actions_token = actions_token().context("reading actions token")?;
let cache_url = std::env::var("ACTIONS_CACHE_URL").context("reading action cache url")?;
// set up http client
let client = Client::new(actions_token.expose_secret(), cache_url)
.context("intializing cache client")?;
debug!("Cache client initialized");
// collect and compress the cached files
let mut output = Vec::<u8>::with_capacity(1024 * 1024); // assume large cache size
let out_comp = GzBuilder::new().write(&mut output, Compression::best());
{
let mut archive = Builder::new(out_comp);
archive.mode(HeaderMode::Complete);
archive.follow_symlinks(false);
debug!(paths = ?path);
for path in path {
let _span = info_span!("cache-entry");
let _span = _span.enter();
if !path.exists() {
warn!("Path does not exist, and will not be cached");
continue;
}
if path.is_dir() {
debug!("Caching directory");
// we actually want to write out all the directory contents
archive.append_dir_all(&path, &path).unwrap();
} else if path.is_file() {
debug!("Caching a file");
// rest of the file types are handled by the existing inner code
archive.append_path(path).unwrap();
} else {
warn!("Path is neither directory, nor a file and will not be cached");
}
}
archive.finish().unwrap();
}
debug!("Cache archive cleated");
client
.save_cache(&key, output)
.context("saving the cache")?;
Ok(())
} }