diff --git a/.forgejo/workflows/build-image.yml b/.forgejo/workflows/build-image.yml new file mode 100644 index 0000000..3794bd8 --- /dev/null +++ b/.forgejo/workflows/build-image.yml @@ -0,0 +1,44 @@ +on: + schedule: + - cron: '30 1 * * *' + workflow_dispatch: + +jobs: + build: + runs-on: docker + container: + image: ubuntu:latest + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: login to docker registry + uses: docker/login-action@v3 + with: + registry: git.ragarock.moe + username: silvana + password: ${{ secrets.REGISTRY_TOKEN }} + + - name: setup buildx + uses: docker/setup-buildx-action@v3 + + - name: build and push + uses: docker/build-push-action@v6 + with: + push: true + tags: git.ragarock.moe/${{ env.GITHUB_REPOSITORY }}:latest + + # - name: Clear image from cache + # run: | + # docker images git.ragarock.moe/${{ env.GITHUB_REPOSITORY }} -q | sort -u | xargs -r docker rmi --force + # docker system prune --force --all + + # - name: Build + # run: docker build . -t git.ragarock.moe/${{ env.GITHUB_REPOSITORY }}:latest + + # - name: Login + # run: docker login git.ragarock.moe --username silvana --password ${{ secrets.FORGEJO_TOKEN_WRITE_PACKAGE }} + + # - name: Publish + # run: docker push git.ragarock.moe/${{ env.GITHUB_REPOSITORY }}:latest diff --git a/.forgejo/workflows/push-next.yml b/.forgejo/workflows/push-next.yml new file mode 100644 index 0000000..8aca99b --- /dev/null +++ b/.forgejo/workflows/push-next.yml @@ -0,0 +1,45 @@ +on: + push: + branches: + - next +jobs: + test: + runs-on: docker + container: + image: git.ragarock.moe/silvana/rust:latest + strategy: + matrix: + toolchain: + - name: stable + - name: nightly + + steps: + - uses: actions/checkout@v4 + + - name: Ignored Files + run: check-for-ignored + # run: | + # IGNORED=$(git ls-files --cached -i --exclude-standard) + # if [ -n "$IGNORED" ] + # then + # echo "Ignored files present:\n$IGNORED" + # exit 1 + # fi + + - name: Machete + run: cargo machete + + - name: Format + run: cargo +${{ matrix.toolchain.name }} fmt --check + + - name: Clippy + run: cargo +${{ matrix.toolchain.name }} clippy + + - name: Test + run: cargo +${{ matrix.toolchain.name }} test + + - name: Build + run: cargo +${{ matrix.toolchain.name }} build + + - name: Run + run: cargo +${{ matrix.toolchain.name }} run diff --git a/.forgejo/workflows/test.yml b/.forgejo/workflows/test.yml deleted file mode 100644 index 195e8c5..0000000 --- a/.forgejo/workflows/test.yml +++ /dev/null @@ -1,46 +0,0 @@ -on: - push: - branches: - - next -jobs: - test: - runs-on: docker - strategy: - matrix: - toolchain: - - name: stable - - name: nightly - - name: v1.81.0 - - steps: - - uses: actions/checkout@v4 - - - name: Machete - uses: https://git.kemitix.net/kemitix/rust@next - with: - args: cargo machete - - - name: Format - uses: https://git.kemitix.net/kemitix/rust@next - with: - args: ${{ matrix.toolchain.name }} cargo fmt --check - - - name: Clippy - uses: https://git.kemitix.net/kemitix/rust@next - with: - args: ${{ matrix.toolchain.name }} cargo clippy - - - name: Test - uses: https://git.kemitix.net/kemitix/rust@next - with: - args: ${{ matrix.toolchain.name }} cargo test - - - name: Build - uses: https://git.kemitix.net/kemitix/rust@next - with: - args: ${{ matrix.toolchain.name }} cargo build - - - name: Run - uses: https://git.kemitix.net/kemitix/rust@next - with: - args: ${{ matrix.toolchain.name }} cargo run diff --git a/.gitignore b/.gitignore index ea8c4bf..96ef6c0 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ /target +Cargo.lock diff --git a/Cargo.toml b/Cargo.toml index 7d75412..4b31261 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,3 +4,5 @@ version = "0.1.0" edition = "2021" [dependencies] +kxio = "5.1" +tokio = { version = "1.43", features = ["full"] } diff --git a/Dockerfile b/Dockerfile index 0d98419..15fbcaf 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,19 +1,9 @@ -FROM docker.io/rust:1.81.0-slim-bookworm +FROM docker.io/rust:1.84.0-alpine3.21 -# nodejs - runtime used by forgejo/github actions -# curl - to download cargo-binstall -# clang-16 & mold - faster linkers for rust -# pkg-config - required to compile some rust `-sys` packages -# libssl-dev - build dependency for git-next -# libdbus-1-dev - linux os interop (e.g. desktop notifications) -# git - git -RUN apt-get update \ - && \ - apt-get satisfy -y "nodejs (>=18.19.0), curl (>=7.88.1), pkg-config (>=1.8.1), libssl-dev (>=3.0.14), git (>=2.39.2), libdbus-1-dev (>= 1.14.10)" \ - && \ - rm -r /var/lib/apt/lists/* +LABEL org.opencontainers.image.source=https://git.ragarock.moe/silvana/rust -RUN curl -L https://github.com/cargo-bins/cargo-binstall/releases/download/v1.9.0/cargo-binstall-x86_64-unknown-linux-musl.tgz -o cargo-binstall.tgz && \ +RUN apk add --no-cache curl=8.11.1-r0 +RUN curl -L https://github.com/cargo-bins/cargo-binstall/releases/download/v1.10.19/cargo-binstall-x86_64-unknown-linux-musl.tgz -o cargo-binstall.tgz && \ tar -xzf cargo-binstall.tgz && \ rm cargo-binstall.tgz && \ mv cargo-binstall /usr/local/bin/ @@ -21,12 +11,46 @@ RUN curl -L https://github.com/cargo-bins/cargo-binstall/releases/download/v1.9. RUN cargo binstall -y \ cargo-chef@0.1 \ cargo-hack@0.6 \ - cargo-machete@0.6.2 \ + cargo-machete@0.7 \ + cargo-mutants@25.0 \ release-plz@0.3 -COPY entrypoint.sh / +# should be a no-op if the FROM line is up-to-date +RUN rustup update stable -RUN git config --global user.email "action@git.kemitix.net" && \ - git config --global user.name "ForgeJo Action. See: https://git.kemitix.net/kemitix/rust" +RUN rustup component add --toolchain stable-x86_64-unknown-linux-musl rustfmt clippy -ENTRYPOINT [ "/entrypoint.sh" ] +# install nightly +RUN rustup install nightly && rustup component add --toolchain nightly rustfmt clippy + +# nodejs - runtime used by forgejo/github actions +# curl - to download cargo-binstall +# clang & mold - faster linkers for rust +# pkgconfig - required to compile some rust `-sys` packages +# openssl-dev - build dependency for git-next +# dbus-dev - linux os interop (e.g. desktop notifications) +# perl - native-tls(vendored) +# git - git +RUN apk add --no-cache \ + bash \ + nodejs \ + build-base \ + pkgconfig \ + libssl3 \ + openssl-dev \ + perl \ + dbus-dev \ + git + + # clang \ + + # mold \ + + # dbus-dev \ + +RUN git config --global user.email "holo@ragarock.moe" && \ + git config --global user.name "ForgeJo Action. See: https://git.ragarock.moe/silvana/rust" + +COPY scripts/ /usr/local/bin/ + +WORKDIR /app diff --git a/README.md b/README.md index f432e3b..cf908c1 100644 --- a/README.md +++ b/README.md @@ -11,24 +11,25 @@ on: [push] jobs: test: runs-on: docker + container: + image: + git.kemitix.net/kemitix/rust:v3.0.0 steps: - - uses: https://git.kemitix.net/kemitix/rust@v1.80.0 - with: - args: nightly cargo test - - uses: https://git.kemitix.net/kemitix/rust@v1.80.0 - with: - args: v1.79.0 cargo build + - name: test with nightly + run: cargo +nightly test + - name: build with v1.74.1 + run: cargo +1.74.1 cargo build + - name: test with stable + run: cargo test ``` -The `args` is one of the following: +## Toolchains -- <COMMAND> -- [ nightly | stable | v1.xx.x ] <COMMAND> - -`COMMAND` is the command you want to run. The optional prefix is the Rust toolchain, or version. Allowed values are `nightly`, `stable` or a Rust version. - -Where the optional prefix is not given, the `stable` toolchain will be used. +The available toolchain in the image are: +- `nightly` +- stable +- 1.74.1 ## Contents @@ -37,13 +38,57 @@ Where the optional prefix is not given, the `stable` toolchain will be used. - git - cargo - cargo-binstall +- cargo-mutants +- cargo-chef +- cargo-hack +- release-plz +- dbus-dev +- perl -## Binary size +### Scripts -To reduce the size of the debug binary, add the following to the `.cargo/config.toml` file in you project. +- `check-for-ignored` + +Checks for files that are being tracked by Git but should be ignored according +to the `.gitignore` file. + +#### Usage + +```yaml +steps: + - name: Check for Ignored Files + run: check-for-ignored +``` + +## Caveats + +### openssl + +The alpine linux install doesn't build with this dependency. You can either compile `native-tls` with the `vendored` feature, or not use `openssl`. + +#### vendoered native-tls + +This crate *must* use the `vendored` feature in order to compile in the Alpine Linux image. ```toml -[profile.dev] -debug = 0 -strip = "debuginfo" +native-tls = { version = "0.2", features = ["vendored"] } +``` + +#### Don't use `openssl` + +Check that none of your dependencies require `openssl`: + +```bash +cargo tree --edges normal -i openssl +``` + +This will list the tree of dependencies that are bringing in `openssl`. + +If you do need ssl/tls, try using `rustls`. e.g. + +```toml +reqwest = { version = "0.12", default-features = false, features = [ + "json", + "rustls-tls", +] } ``` diff --git a/entrypoint.sh b/entrypoint.sh deleted file mode 100755 index 195dda1..0000000 --- a/entrypoint.sh +++ /dev/null @@ -1,50 +0,0 @@ -#!/usr/bin/env bash - -set -e - -echo "INPUT_ARGS: ${INPUT_ARGS}" - -# split input into an array -read -ra ARGS <<<"${INPUT_ARGS}" - -# default toolchain -TOOLCHAIN="stable" -echo "Default toolchain: ${TOOLCHAIN}" - -# if first parameter is 'nightly'... -if test "${ARGS[0]}" == "nightly"; then - TOOLCHAIN="nightly" - ARGS=("${ARGS[@]:1}") -fi -if test "${ARGS[0]}" == "stable"; then - TOOLCHAIN="stable" # redundant as this is the default - ARGS=("${ARGS[@]:1}") -fi -if [[ "${ARGS[0]}" == v1* ]]; then - TOOLCHAIN="${ARGS[0]:1}" - ARGS=("${ARGS[@]:1}") -fi -echo "Selected toolchain: ${TOOLCHAIN}" - -echo ">>> Update toolchain" -rustup update "${TOOLCHAIN}" -echo ">>> Install rustfmt and clippy" -rustup component add --toolchain "${TOOLCHAIN}" rustfmt clippy - -if test "${ARGS[0]}" == "cargo";then - PRE_COMMAND="cargo +${TOOLCHAIN} " -else - PRE_COMMAND="${ARGS[0]}" -fi -ARGS=("${ARGS[@]:1}") - -# ensure toolchain is up-to-date -# recombine remaining arguments -COMMAND=$( - IFS=" " - echo "${ARGS[*]}" -) - -# execute command -echo ">>> ${PRE_COMMAND} ${COMMAND}" -${PRE_COMMAND} ${COMMAND} diff --git a/justfile b/justfile new file mode 100644 index 0000000..373ea93 --- /dev/null +++ b/justfile @@ -0,0 +1,19 @@ +image := "git.ragarock.moe/silvana/rust:test" + +build: + docker build . -t {{ image }} + +test: build + docker run --rm -v $PWD:/app/ {{ image }} cargo test + +clippy: build + docker run --rm -v $PWD:/app/ {{ image }} cargo clippy + +run: build + docker run --rm -v $PWD:/app/ {{ image }} cargo run + +fmt: build + docker run --rm -v $PWD:/app/ {{ image }} cargo fmt + +shell: build + docker run --rm -it -v $PWD:/app/ {{ image }} bash diff --git a/mise.toml b/mise.toml new file mode 100644 index 0000000..0d130ca --- /dev/null +++ b/mise.toml @@ -0,0 +1,10 @@ +[tasks.test] +description = "Test the image" +run = [ + "cargo machete", + "cargo fmt --check", + "cargo clippy", + "cargo test", + "cargo build", + "cargo run" +] diff --git a/scripts/check-for-ignored b/scripts/check-for-ignored new file mode 100755 index 0000000..90f8626 --- /dev/null +++ b/scripts/check-for-ignored @@ -0,0 +1,30 @@ +#!/usr/bin/env bash + +# Exit on error +set -e + +# Check if git is installed +if ! command -v git &> /dev/null; then + echo "Error: git is not installed" + exit 1 +fi + +# Check if we're in a git repository +if ! git rev-parse --is-inside-work-tree &> /dev/null; then + echo "Error: not in a git repository" + exit 1 +fi + +echo "Checking for tracked files that should be ignored..." + +# Find files that are both tracked and ignored +ignored_files=$(git ls-files --cached -i --exclude-standard) + +if [[ -z "$ignored_files" ]]; then + echo "No tracked files are marked as ignored" + exit 0 +else + echo "The following tracked files are marked as ignored:" + echo "$ignored_files" + exit 1 +fi diff --git a/src/kxio.rs b/src/kxio.rs new file mode 100644 index 0000000..1d56825 --- /dev/null +++ b/src/kxio.rs @@ -0,0 +1,105 @@ +/// This is an example to show fetching a file from a webiste and saving to a file +/// +/// The example consts of: +/// +/// - The main program, in `main()` - demonstrates how to setup `kxio` for use in prod +/// - A test module - demonstrates how to use `kxio` in tests +/// - sample functions - showing how to use `kxio` the body of your program, and be testable +/// +/// NOTE: running this program with `cargo run --example get` will create and delete the file +/// `example-readme.md` in the current directory. +use std::path::Path; + +use kxio::fs::FileHandle; + +// #[tokio::main] +pub async fn main() -> kxio::Result<()> { + // Create a `Net` object for making real network requests. + let net: kxio::net::Net = kxio::net::new(); + + // Create a `FileSystem` object for accessing files within the current directory. + // The object created will return a `PathTraveral` error result if there is an attempt to\ + // access a file outside of this directory. + let current_dir = std::env::current_dir().map_err(kxio::fs::Error::Io)?; + let fs: kxio::fs::FileSystem = kxio::fs::new(current_dir); + + // The URL we will fetch - the readme for this library. + let url = "https://git.kemitix.net/kemitix/kxio/raw/branch/main/README.md"; + + // Create a PathBuf to a file within the directory that the `fs` object has access to. + let file_path = fs.base().join("example-readme.md"); + + // Create a generic handle for the file. This doesn't open the file, and always succeeds. + let path = fs.path(&file_path); + + // Other options are; + // `fs.file(&file_path)` - for a file + // `fs.dir(&dir_path)` - for a directory + + // Checks if the path exists (whether a file, directory, etc) + if path.exists()? { + eprintln!("The file {path} already exists. Aborting!"); + return Ok(()); + } + + // Passes a reference to the `fs` and `net` objects for use by your program. + // Your programs should not know whether they are handling a mock or the real thing. + // Any file or network access should be made using these handlers to be properly testable. + let file = download_and_save_to_file(url, &file_path, &fs, &net).await?; + read_file(&file)?; + delete_file(file)?; + + Ok(()) +} + +/// An function that uses a `FileSystem` and a `Net` object to interact with the outside world. +async fn download_and_save_to_file( + url: &str, + file_path: &Path, + // The filesystem abstraction + fs: &kxio::fs::FileSystem, + // The network abstraction + net: &kxio::net::Net, +) -> kxio::Result { + println!("fetching: {url}"); + + // Makes a GET request that can be mocked in a test + let response = net.get(url).header("key", "value").send().await?; + + // As you can see, we use [reqwest] under the hood. + // + // If you need to create a more complex request than the [kxio] fluent API allows, you + // can create a request using [reqwest] and pass it to [net.send(request)]. + + let body = response.text().await?; + println!("fetched {} bytes", body.bytes().len()); + + // Uses the file system abstraction to create a handle for a file. + let file: kxio::fs::PathReal = fs.file(file_path); + println!("writing file: {file}"); + // Writes the body to the file. + file.write(body)?; + + Ok(file) +} + +/// A function that reads the file contents +fn read_file(file: &FileHandle) -> kxio::Result<()> { + println!("reading file: {file}"); + + // Creates a `Reader` which loaded the file into memory. + let reader: kxio::fs::Reader = file.reader()?; + let contents: &str = reader.as_str()?; + println!("{contents}"); + + Ok(()) +} + +/// A function that deletes the file +fn delete_file(file: FileHandle) -> kxio::Result<()> { + println!("deleting file: {file}"); + + file.remove()?; + + Ok(()) +} diff --git a/src/main.rs b/src/main.rs index e7a11a9..93488b4 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,3 +1,17 @@ -fn main() { +// +mod kxio; + +fn main() -> Result<(), Box> { println!("Hello, world!"); + + let rt = tokio::runtime::Runtime::new()?; + Ok(rt.block_on(crate::kxio::main())?) +} + +#[cfg(test)] +mod tests { + #[test] + fn passes() { + println!("passes okay"); + } }