Add repository module and update dependencies

Signed-off-by: Till Wegmueller <toasterson@gmail.com>
This commit is contained in:
Till Wegmueller 2025-07-21 22:02:05 +02:00
parent bef5442afd
commit 1a5e0e053d
No known key found for this signature in database
11 changed files with 2099 additions and 118 deletions

358
Cargo.lock generated
View file

@ -1,6 +1,6 @@
# This file is automatically @generated by Cargo. # This file is automatically @generated by Cargo.
# It is not intended for manual editing. # It is not intended for manual editing.
version = 3 version = 4
[[package]] [[package]]
name = "adler" name = "adler"
@ -17,6 +17,56 @@ dependencies = [
"memchr", "memchr",
] ]
[[package]]
name = "anstream"
version = "0.6.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "301af1932e46185686725e0fad2f8f2aa7da69dd70bf6ecc44d6b703844a3933"
dependencies = [
"anstyle",
"anstyle-parse",
"anstyle-query",
"anstyle-wincon",
"colorchoice",
"is_terminal_polyfill",
"utf8parse",
]
[[package]]
name = "anstyle"
version = "1.0.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "862ed96ca487e809f1c8e5a8447f6ee2cf102f846893800b20cebdf541fc6bbd"
[[package]]
name = "anstyle-parse"
version = "0.2.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2"
dependencies = [
"utf8parse",
]
[[package]]
name = "anstyle-query"
version = "1.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6c8bdeb6047d8983be085bab0ba1472e6dc604e7041dbf6fcd5e71523014fae9"
dependencies = [
"windows-sys 0.59.0",
]
[[package]]
name = "anstyle-wincon"
version = "3.0.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "403f75924867bb1033c59fbf0797484329750cfbe3c4325cd33127941fabc882"
dependencies = [
"anstyle",
"once_cell_polyfill",
"windows-sys 0.59.0",
]
[[package]] [[package]]
name = "anyhow" name = "anyhow"
version = "1.0.70" version = "1.0.70"
@ -52,6 +102,12 @@ version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "bitflags"
version = "2.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967"
[[package]] [[package]]
name = "block-buffer" name = "block-buffer"
version = "0.9.0" version = "0.9.0"
@ -108,29 +164,63 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "71655c45cb9845d3270c9d6df84ebe72b4dad3c2ba3f7023ad47c144e4e473a5" checksum = "71655c45cb9845d3270c9d6df84ebe72b4dad3c2ba3f7023ad47c144e4e473a5"
dependencies = [ dependencies = [
"atty", "atty",
"bitflags", "bitflags 1.3.2",
"clap_derive", "clap_derive 3.2.18",
"clap_lex", "clap_lex 0.2.4",
"indexmap", "indexmap",
"once_cell", "once_cell",
"strsim", "strsim 0.10.0",
"termcolor", "termcolor",
"textwrap", "textwrap",
] ]
[[package]]
name = "clap"
version = "4.5.41"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "be92d32e80243a54711e5d7ce823c35c41c9d929dc4ab58e1276f625841aadf9"
dependencies = [
"clap_builder",
"clap_derive 4.5.41",
]
[[package]]
name = "clap_builder"
version = "4.5.41"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "707eab41e9622f9139419d573eca0900137718000c517d47da73045f54331c3d"
dependencies = [
"anstream",
"anstyle",
"clap_lex 0.7.5",
"strsim 0.11.1",
]
[[package]] [[package]]
name = "clap_derive" name = "clap_derive"
version = "3.2.18" version = "3.2.18"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ea0c8bce528c4be4da13ea6fead8965e95b6073585a2f05204bd8f4119f82a65" checksum = "ea0c8bce528c4be4da13ea6fead8965e95b6073585a2f05204bd8f4119f82a65"
dependencies = [ dependencies = [
"heck", "heck 0.4.1",
"proc-macro-error", "proc-macro-error",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 1.0.109", "syn 1.0.109",
] ]
[[package]]
name = "clap_derive"
version = "4.5.41"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ef4f52386a59ca4c860f7393bcf8abd8dfd91ecccc0f774635ff68e92eeef491"
dependencies = [
"heck 0.5.0",
"proc-macro2",
"quote",
"syn 2.0.74",
]
[[package]] [[package]]
name = "clap_lex" name = "clap_lex"
version = "0.2.4" version = "0.2.4"
@ -140,6 +230,18 @@ dependencies = [
"os_str_bytes", "os_str_bytes",
] ]
[[package]]
name = "clap_lex"
version = "0.7.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b94f61472cee1439c0b966b47e3aca9ae07e45d070759512cd390ea2bebc6675"
[[package]]
name = "colorchoice"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75"
[[package]] [[package]]
name = "core-foundation" name = "core-foundation"
version = "0.9.3" version = "0.9.3"
@ -262,33 +364,19 @@ dependencies = [
[[package]] [[package]]
name = "errno" name = "errno"
version = "0.3.1" version = "0.3.13"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4bcfec3a70f97c962c307b2d2c56e358cf1d00b558d74262b5f929ee8cc7e73a" checksum = "778e2ac28f6c47af28e4907f13ffd1e1ddbd400980a9abd7c8df189bf578a5ad"
dependencies = [ dependencies = [
"errno-dragonfly",
"libc",
"windows-sys 0.48.0",
]
[[package]]
name = "errno-dragonfly"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf"
dependencies = [
"cc",
"libc", "libc",
"windows-sys 0.59.0",
] ]
[[package]] [[package]]
name = "fastrand" name = "fastrand"
version = "1.9.0" version = "2.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be" checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be"
dependencies = [
"instant",
]
[[package]] [[package]]
name = "flate2" name = "flate2"
@ -396,7 +484,19 @@ checksum = "c85e1d9ab2eadba7e5040d4e09cbd6d072b76a557ad64e797c2cb9d4da21d7e4"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"libc", "libc",
"wasi", "wasi 0.11.0+wasi-snapshot-preview1",
]
[[package]]
name = "getrandom"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4"
dependencies = [
"cfg-if",
"libc",
"r-efi",
"wasi 0.14.2+wasi-0.2.4",
] ]
[[package]] [[package]]
@ -430,6 +530,12 @@ version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8"
[[package]]
name = "heck"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
[[package]] [[package]]
name = "hermit-abi" name = "hermit-abi"
version = "0.1.19" version = "0.1.19"
@ -448,12 +554,6 @@ dependencies = [
"libc", "libc",
] ]
[[package]]
name = "hermit-abi"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fed44880c466736ef9a5c5b5facefb5ed0785676d0c02d612db14e54f0d84286"
[[package]] [[package]]
name = "http" name = "http"
version = "0.2.9" version = "0.2.9"
@ -558,32 +658,18 @@ dependencies = [
"hashbrown", "hashbrown",
] ]
[[package]]
name = "instant"
version = "0.1.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c"
dependencies = [
"cfg-if",
]
[[package]]
name = "io-lifetimes"
version = "1.0.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c66c74d2ae7e79a5a8f7ac924adbe38ee42a859c6539ad869eb51f0b52dc220"
dependencies = [
"hermit-abi 0.3.1",
"libc",
"windows-sys 0.48.0",
]
[[package]] [[package]]
name = "ipnet" name = "ipnet"
version = "2.7.2" version = "2.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "12b6ee2129af8d4fb011108c73d99a1b83a85977f23b82460c0ae2e25bb4b57f" checksum = "12b6ee2129af8d4fb011108c73d99a1b83a85977f23b82460c0ae2e25bb4b57f"
[[package]]
name = "is_terminal_polyfill"
version = "1.70.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf"
[[package]] [[package]]
name = "itoa" name = "itoa"
version = "1.0.6" version = "1.0.6"
@ -616,9 +702,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
[[package]] [[package]]
name = "libc" name = "libc"
version = "0.2.141" version = "0.2.174"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3304a64d199bb964be99741b7a14d26972741915b3649639149b2479bb46f4b5" checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776"
[[package]] [[package]]
name = "libips" name = "libips"
@ -641,9 +727,9 @@ dependencies = [
[[package]] [[package]]
name = "linux-raw-sys" name = "linux-raw-sys"
version = "0.3.1" version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d59d8c75012853d2e872fb56bc8a2e53718e2cafe1a4c823143141c6d90c322f" checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12"
[[package]] [[package]]
name = "log" name = "log"
@ -695,7 +781,7 @@ checksum = "5b9d9a46eff5b4ff64b45a9e316a6d1e0bc719ef429cbec4dc630684212bfdf9"
dependencies = [ dependencies = [
"libc", "libc",
"log", "log",
"wasi", "wasi 0.11.0+wasi-snapshot-preview1",
"windows-sys 0.45.0", "windows-sys 0.45.0",
] ]
@ -812,9 +898,15 @@ dependencies = [
[[package]] [[package]]
name = "once_cell" name = "once_cell"
version = "1.17.1" version = "1.21.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3" checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
[[package]]
name = "once_cell_polyfill"
version = "1.70.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a4895175b425cb1f87721b59f0f286c2092bd4af812243672510e1ac53e2e0ad"
[[package]] [[package]]
name = "opaque-debug" name = "opaque-debug"
@ -828,7 +920,7 @@ version = "0.10.55"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "345df152bc43501c5eb9e4654ff05f794effb78d4efe3d53abc158baddc0703d" checksum = "345df152bc43501c5eb9e4654ff05f794effb78d4efe3d53abc158baddc0703d"
dependencies = [ dependencies = [
"bitflags", "bitflags 1.3.2",
"cfg-if", "cfg-if",
"foreign-types", "foreign-types",
"libc", "libc",
@ -956,6 +1048,7 @@ version = "0.1.0"
dependencies = [ dependencies = [
"diff-struct", "diff-struct",
"libips", "libips",
"serde",
] ]
[[package]] [[package]]
@ -967,7 +1060,7 @@ name = "pkg6dev"
version = "0.1.1" version = "0.1.1"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"clap", "clap 3.2.23",
"libips", "libips",
"userland", "userland",
] ]
@ -975,13 +1068,21 @@ dependencies = [
[[package]] [[package]]
name = "pkg6repo" name = "pkg6repo"
version = "0.0.1-placeholder" version = "0.0.1-placeholder"
dependencies = [
"anyhow",
"clap 4.5.41",
"libips",
"serde",
"serde_json",
"tempfile",
]
[[package]] [[package]]
name = "ports" name = "ports"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"clap", "clap 3.2.23",
"libips", "libips",
"reqwest", "reqwest",
"shellexpand", "shellexpand",
@ -1033,22 +1134,19 @@ dependencies = [
"proc-macro2", "proc-macro2",
] ]
[[package]]
name = "r-efi"
version = "5.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f"
[[package]] [[package]]
name = "redox_syscall" name = "redox_syscall"
version = "0.2.16" version = "0.2.16"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a"
dependencies = [ dependencies = [
"bitflags", "bitflags 1.3.2",
]
[[package]]
name = "redox_syscall"
version = "0.3.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29"
dependencies = [
"bitflags",
] ]
[[package]] [[package]]
@ -1057,8 +1155,8 @@ version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b" checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b"
dependencies = [ dependencies = [
"getrandom", "getrandom 0.2.9",
"redox_syscall 0.2.16", "redox_syscall",
"thiserror", "thiserror",
] ]
@ -1138,16 +1236,15 @@ dependencies = [
[[package]] [[package]]
name = "rustix" name = "rustix"
version = "0.37.11" version = "1.0.8"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "85597d61f83914ddeba6a47b3b8ffe7365107221c2e557ed94426489fefb5f77" checksum = "11181fbabf243db407ef8df94a6ce0b2f9a733bd8be4ad02b4eda9602296cac8"
dependencies = [ dependencies = [
"bitflags", "bitflags 2.9.1",
"errno", "errno",
"io-lifetimes",
"libc", "libc",
"linux-raw-sys", "linux-raw-sys",
"windows-sys 0.48.0", "windows-sys 0.59.0",
] ]
[[package]] [[package]]
@ -1208,7 +1305,7 @@ version = "2.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a332be01508d814fed64bf28f798a146d73792121129962fdf335bb3c49a4254" checksum = "a332be01508d814fed64bf28f798a146d73792121129962fdf335bb3c49a4254"
dependencies = [ dependencies = [
"bitflags", "bitflags 1.3.2",
"core-foundation", "core-foundation",
"core-foundation-sys", "core-foundation-sys",
"libc", "libc",
@ -1361,6 +1458,12 @@ version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
[[package]]
name = "strsim"
version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
[[package]] [[package]]
name = "strum" name = "strum"
version = "0.24.1" version = "0.24.1"
@ -1376,7 +1479,7 @@ version = "0.24.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e385be0d24f186b4ce2f9982191e7101bb737312ad61c1f2f984f34bcf85d59" checksum = "1e385be0d24f186b4ce2f9982191e7101bb737312ad61c1f2f984f34bcf85d59"
dependencies = [ dependencies = [
"heck", "heck 0.4.1",
"proc-macro2", "proc-macro2",
"quote", "quote",
"rustversion", "rustversion",
@ -1407,15 +1510,15 @@ dependencies = [
[[package]] [[package]]
name = "tempfile" name = "tempfile"
version = "3.5.0" version = "3.20.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b9fbec84f381d5795b08656e4912bec604d162bff9291d6189a78f4c8ab87998" checksum = "e8a64e3985349f2441a1a9ef0b853f869006c3855f2cda6862a94d26ebb9d6a1"
dependencies = [ dependencies = [
"cfg-if",
"fastrand", "fastrand",
"redox_syscall 0.3.5", "getrandom 0.3.3",
"once_cell",
"rustix", "rustix",
"windows-sys 0.45.0", "windows-sys 0.59.0",
] ]
[[package]] [[package]]
@ -1621,6 +1724,12 @@ dependencies = [
"url", "url",
] ]
[[package]]
name = "utf8parse"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
[[package]] [[package]]
name = "vcpkg" name = "vcpkg"
version = "0.2.15" version = "0.2.15"
@ -1649,6 +1758,15 @@ version = "0.11.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
[[package]]
name = "wasi"
version = "0.14.2+wasi-0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3"
dependencies = [
"wit-bindgen-rt",
]
[[package]] [[package]]
name = "wasm-bindgen" name = "wasm-bindgen"
version = "0.2.84" version = "0.2.84"
@ -1818,11 +1936,11 @@ dependencies = [
[[package]] [[package]]
name = "windows-sys" name = "windows-sys"
version = "0.48.0" version = "0.59.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
dependencies = [ dependencies = [
"windows-targets 0.48.0", "windows-targets 0.52.6",
] ]
[[package]] [[package]]
@ -1842,17 +1960,18 @@ dependencies = [
[[package]] [[package]]
name = "windows-targets" name = "windows-targets"
version = "0.48.0" version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7b1eb6f0cd7c80c79759c929114ef071b87354ce476d9d94271031c0497adfd5" checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
dependencies = [ dependencies = [
"windows_aarch64_gnullvm 0.48.0", "windows_aarch64_gnullvm 0.52.6",
"windows_aarch64_msvc 0.48.0", "windows_aarch64_msvc 0.52.6",
"windows_i686_gnu 0.48.0", "windows_i686_gnu 0.52.6",
"windows_i686_msvc 0.48.0", "windows_i686_gnullvm",
"windows_x86_64_gnu 0.48.0", "windows_i686_msvc 0.52.6",
"windows_x86_64_gnullvm 0.48.0", "windows_x86_64_gnu 0.52.6",
"windows_x86_64_msvc 0.48.0", "windows_x86_64_gnullvm 0.52.6",
"windows_x86_64_msvc 0.52.6",
] ]
[[package]] [[package]]
@ -1863,9 +1982,9 @@ checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8"
[[package]] [[package]]
name = "windows_aarch64_gnullvm" name = "windows_aarch64_gnullvm"
version = "0.48.0" version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
[[package]] [[package]]
name = "windows_aarch64_msvc" name = "windows_aarch64_msvc"
@ -1875,9 +1994,9 @@ checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43"
[[package]] [[package]]
name = "windows_aarch64_msvc" name = "windows_aarch64_msvc"
version = "0.48.0" version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
[[package]] [[package]]
name = "windows_i686_gnu" name = "windows_i686_gnu"
@ -1887,9 +2006,15 @@ checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f"
[[package]] [[package]]
name = "windows_i686_gnu" name = "windows_i686_gnu"
version = "0.48.0" version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241" checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
[[package]]
name = "windows_i686_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
[[package]] [[package]]
name = "windows_i686_msvc" name = "windows_i686_msvc"
@ -1899,9 +2024,9 @@ checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060"
[[package]] [[package]]
name = "windows_i686_msvc" name = "windows_i686_msvc"
version = "0.48.0" version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
[[package]] [[package]]
name = "windows_x86_64_gnu" name = "windows_x86_64_gnu"
@ -1911,9 +2036,9 @@ checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36"
[[package]] [[package]]
name = "windows_x86_64_gnu" name = "windows_x86_64_gnu"
version = "0.48.0" version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
[[package]] [[package]]
name = "windows_x86_64_gnullvm" name = "windows_x86_64_gnullvm"
@ -1923,9 +2048,9 @@ checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3"
[[package]] [[package]]
name = "windows_x86_64_gnullvm" name = "windows_x86_64_gnullvm"
version = "0.48.0" version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
[[package]] [[package]]
name = "windows_x86_64_msvc" name = "windows_x86_64_msvc"
@ -1935,9 +2060,9 @@ checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0"
[[package]] [[package]]
name = "windows_x86_64_msvc" name = "windows_x86_64_msvc"
version = "0.48.0" version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
[[package]] [[package]]
name = "winreg" name = "winreg"
@ -1947,3 +2072,12 @@ checksum = "80d0f4e272c85def139476380b12f9ac60926689dd2e01d4923222f40580869d"
dependencies = [ dependencies = [
"winapi", "winapi",
] ]
[[package]]
name = "wit-bindgen-rt"
version = "0.39.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1"
dependencies = [
"bitflags 2.9.1",
]

View file

@ -6,3 +6,4 @@ edition = "2021"
[dependencies] [dependencies]
libips = { version = "0.1.2", path = "../../libips" } libips = { version = "0.1.2", path = "../../libips" }
diff-struct = "0.5.3" diff-struct = "0.5.3"
serde = { version = "1.0.207", features = ["derive"] }

View file

@ -1,3 +1,95 @@
fn main() { use diff::Diff;
use libips::actions::File;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use libips::payload::Payload;
#[derive(Serialize, Deserialize, Debug, Clone, Diff)]
#[diff(attr(
#[derive(Debug, PartialEq)]
))]
struct Manifest {
files: HashMap<String, File>
}
fn main() {
let base = Manifest{files: HashMap::from([
("0dh5".to_string(), File{
payload: None,
path: "var/file".to_string(),
group: "bin".to_string(),
owner: "root".to_string(),
mode: "0755".to_string(),
preserve: false,
overlay: false,
original_name: "".to_string(),
revert_tag: "".to_string(),
sys_attr: "".to_string(),
properties: vec![],
facets: Default::default(),
}),
("12ds3".to_string(), File{
payload: None,
path: "var/file1".to_string(),
group: "bin".to_string(),
owner: "root".to_string(),
mode: "0755".to_string(),
preserve: false,
overlay: false,
original_name: "".to_string(),
revert_tag: "".to_string(),
sys_attr: "".to_string(),
properties: vec![],
facets: Default::default(),
}),
("654".to_string(), File{
payload: None,
path: "var/file1".to_string(),
group: "bin".to_string(),
owner: "root".to_string(),
mode: "0755".to_string(),
preserve: false,
overlay: false,
original_name: "".to_string(),
revert_tag: "".to_string(),
sys_attr: "".to_string(),
properties: vec![],
facets: Default::default(),
})
])};
let new_set = Manifest{files: HashMap::from([
("0dh5".to_string(), File{
payload: None,
path: "var/file".to_string(),
group: "bin".to_string(),
owner: "root".to_string(),
mode: "0755".to_string(),
preserve: false,
overlay: false,
original_name: "".to_string(),
revert_tag: "".to_string(),
sys_attr: "".to_string(),
properties: vec![],
facets: Default::default(),
}),
("654".to_string(), File{
payload: None,
path: "var/file1".to_string(),
group: "bin".to_string(),
owner: "root".to_string(),
mode: "0755".to_string(),
preserve: false,
overlay: false,
original_name: "".to_string(),
revert_tag: "".to_string(),
sys_attr: "".to_string(),
properties: vec![],
facets: Default::default(),
})
])};
let d = base.diff(&new_set);
println!("{:#?}", d);
} }

View file

@ -8,6 +8,7 @@ pub mod actions;
pub mod digest; pub mod digest;
pub mod payload; pub mod payload;
pub mod image; pub mod image;
pub mod repository;
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {

View file

@ -0,0 +1,609 @@
// This Source Code Form is subject to the terms of
// the Mozilla Public License, v. 2.0. If a copy of the
// MPL was not distributed with this file, You can
// obtain one at https://mozilla.org/MPL/2.0/.
use anyhow::{Result, anyhow};
use std::fs;
use std::io::Read;
use std::path::{Path, PathBuf};
use std::time::{SystemTime, UNIX_EPOCH};
use std::str::FromStr;
use sha2::{Sha256, Digest as Sha2Digest};
use std::fs::File;
use crate::actions::{Manifest, File as FileAction};
use crate::digest::Digest;
use crate::payload::{Payload, PayloadCompressionAlgorithm};
use super::{Repository, RepositoryConfig, RepositoryVersion, REPOSITORY_CONFIG_FILENAME};
/// Repository implementation that uses the local filesystem
pub struct FileBackend {
pub path: PathBuf,
pub config: RepositoryConfig,
}
/// Transaction for publishing packages
pub struct Transaction {
/// Unique ID for the transaction
id: String,
/// Path to the transaction directory
path: PathBuf,
/// Manifest being updated
manifest: Manifest,
/// Files to be published
files: Vec<(PathBuf, String)>, // (source_path, sha256)
/// Repository reference
repo: PathBuf,
}
impl Transaction {
/// Create a new transaction
pub fn new(repo_path: PathBuf) -> Result<Self> {
// Generate a unique ID based on timestamp
let timestamp = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_secs();
let id = format!("trans_{}", timestamp);
// Create transaction directory
let trans_path = repo_path.join("trans").join(&id);
fs::create_dir_all(&trans_path)?;
Ok(Transaction {
id,
path: trans_path,
manifest: Manifest::new(),
files: Vec::new(),
repo: repo_path,
})
}
/// Process a file for the transaction
///
/// Takes a FileAction and a path to a file in a prototype directory.
/// Calculates the file's checksum, copies and "compresses" the content into a temp file
/// in the transactions directory, and updates the FileAction with the hash information.
pub fn add_file(&mut self, file_action: FileAction, file_path: &Path) -> Result<()> {
// Calculate SHA256 hash of the file (uncompressed)
let hash = Self::calculate_file_hash(file_path)?;
// Create a temp file path in the transactions directory
let temp_file_name = format!("temp_{}", hash);
let temp_file_path = self.path.join(temp_file_name);
// Copy the file to the temp location (this is a placeholder for compression)
// In a real implementation, we would compress the file here
fs::copy(file_path, &temp_file_path)?;
// For now, we're using the same hash for both uncompressed and "compressed" versions
// In a real implementation with compression, we would calculate the hash of the compressed file
let compressed_hash = hash.clone();
// Add file to the list for later processing during commit
self.files.push((file_path.to_path_buf(), hash.clone()));
// Create a new FileAction with the updated information if one wasn't provided
let mut updated_file_action = file_action;
// Create a payload with the hash information if it doesn't exist
let mut payload = updated_file_action.payload.unwrap_or_else(Payload::default);
// Set the primary identifier (uncompressed hash)
payload.primary_identifier = Digest::from_str(&hash)?;
// Set the compression algorithm
payload.compression_algorithm = PayloadCompressionAlgorithm::Gzip;
// Add the compressed hash as an additional identifier
let compressed_digest = Digest::from_str(&compressed_hash)?;
payload.additional_identifiers.push(compressed_digest);
// Update the FileAction with the payload
updated_file_action.payload = Some(payload);
// Add the FileAction to the manifest
self.manifest.add_file(updated_file_action);
Ok(())
}
/// Commit the transaction
pub fn commit(self) -> Result<()> {
// Save the manifest to the transaction directory
let manifest_path = self.path.join("manifest");
// Serialize the manifest to JSON
let manifest_json = serde_json::to_string_pretty(&self.manifest)?;
fs::write(&manifest_path, manifest_json)?;
// Copy files to their final location
for (source_path, hash) in self.files {
// Create the destination path in the files directory
let dest_path = self.repo.join("file").join(&hash);
// Copy the file if it doesn't already exist
if !dest_path.exists() {
fs::copy(source_path, &dest_path)?;
}
}
// Generate a timestamp for the manifest version
let timestamp = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_secs();
// Move the manifest to its final location in the repository
// Store in both the pkg directory and the trans directory as required
let pkg_manifest_path = self.repo.join("pkg").join("manifest");
let trans_manifest_path = self.repo.join("trans").join(format!("manifest_{}", timestamp));
// Copy to pkg directory
fs::copy(&manifest_path, &pkg_manifest_path)?;
// Move to trans directory
fs::rename(manifest_path, trans_manifest_path)?;
// Clean up the transaction directory (except for the manifest which was moved)
fs::remove_dir_all(self.path)?;
Ok(())
}
/// Calculate SHA256 hash of a file
fn calculate_file_hash(file_path: &Path) -> Result<String> {
// Open the file
let mut file = File::open(file_path)?;
// Create a SHA256 hasher
let mut hasher = Sha256::new();
// Read the file in chunks and update the hasher
let mut buffer = [0; 1024];
loop {
let bytes_read = file.read(&mut buffer)?;
if bytes_read == 0 {
break;
}
hasher.update(&buffer[..bytes_read]);
}
// Get the hash result
let hash = hasher.finalize();
// Convert to hex string
let hash_str = format!("{:x}", hash);
Ok(hash_str)
}
}
impl Repository for FileBackend {
/// Create a new repository at the specified path
fn create<P: AsRef<Path>>(path: P, version: RepositoryVersion) -> Result<Self> {
let path = path.as_ref();
// Create the repository directory if it doesn't exist
fs::create_dir_all(path)?;
// Create the repository configuration
let config = RepositoryConfig {
version,
..Default::default()
};
// Create the repository structure
let repo = FileBackend {
path: path.to_path_buf(),
config,
};
// Create the repository directories
repo.create_directories()?;
// Save the repository configuration
repo.save_config()?;
Ok(repo)
}
/// Open an existing repository
fn open<P: AsRef<Path>>(path: P) -> Result<Self> {
let path = path.as_ref();
// Check if the repository directory exists
if !path.exists() {
return Err(anyhow!("Repository does not exist: {}", path.display()));
}
// Load the repository configuration
let config_path = path.join(REPOSITORY_CONFIG_FILENAME);
let config_data = fs::read_to_string(config_path)?;
let config: RepositoryConfig = serde_json::from_str(&config_data)?;
Ok(FileBackend {
path: path.to_path_buf(),
config,
})
}
/// Save the repository configuration
fn save_config(&self) -> Result<()> {
let config_path = self.path.join(REPOSITORY_CONFIG_FILENAME);
let config_data = serde_json::to_string_pretty(&self.config)?;
fs::write(config_path, config_data)?;
Ok(())
}
/// Add a publisher to the repository
fn add_publisher(&mut self, publisher: &str) -> Result<()> {
if !self.config.publishers.contains(&publisher.to_string()) {
self.config.publishers.push(publisher.to_string());
// Create publisher-specific directories
fs::create_dir_all(self.path.join("catalog").join(publisher))?;
fs::create_dir_all(self.path.join("pkg").join(publisher))?;
// Save the updated configuration
self.save_config()?;
}
Ok(())
}
/// Remove a publisher from the repository
fn remove_publisher(&mut self, publisher: &str, dry_run: bool) -> Result<()> {
if let Some(pos) = self.config.publishers.iter().position(|p| p == publisher) {
if !dry_run {
self.config.publishers.remove(pos);
// Save the updated configuration
self.save_config()?;
}
}
Ok(())
}
/// Get repository information
fn get_info(&self) -> Result<Vec<(String, usize, String, String)>> {
let mut info = Vec::new();
for publisher in &self.config.publishers {
// Count packages (this is a placeholder, actual implementation would count packages)
let package_count = 0;
// Status is always "online" for now
let status = "online".to_string();
// Updated timestamp (placeholder)
let updated = "2025-07-21T18:46:00.000000Z".to_string();
info.push((publisher.clone(), package_count, status, updated));
}
Ok(info)
}
/// Set a repository property
fn set_property(&mut self, property: &str, value: &str) -> Result<()> {
self.config.properties.insert(property.to_string(), value.to_string());
self.save_config()?;
Ok(())
}
/// Set a publisher property
fn set_publisher_property(&mut self, publisher: &str, property: &str, value: &str) -> Result<()> {
// Check if the publisher exists
if !self.config.publishers.contains(&publisher.to_string()) {
return Err(anyhow!("Publisher does not exist: {}", publisher));
}
// Create the property key in the format "publisher/property"
let key = format!("{}/{}", publisher, property);
// Set the property
self.config.properties.insert(key, value.to_string());
// Save the updated configuration
self.save_config()?;
Ok(())
}
/// List packages in the repository
fn list_packages(&self, publisher: Option<&str>, pattern: Option<&str>) -> Result<Vec<(String, String, String)>> {
let mut packages = Vec::new();
// Filter publishers if specified
let publishers = if let Some(pub_name) = publisher {
if !self.config.publishers.contains(&pub_name.to_string()) {
return Err(anyhow!("Publisher does not exist: {}", pub_name));
}
vec![pub_name.to_string()]
} else {
self.config.publishers.clone()
};
// For each publisher, list packages
for pub_name in publishers {
// In a real implementation, we would scan the repository for packages
// For now, we'll just return a placeholder
// Example package data (name, version, publisher)
let example_packages = vec![
("example/package1".to_string(), "1.0.0".to_string(), pub_name.clone()),
("example/package2".to_string(), "2.0.0".to_string(), pub_name.clone()),
];
// Filter by pattern if specified
let filtered_packages = if let Some(pat) = pattern {
example_packages.into_iter()
.filter(|(name, _, _)| name.contains(pat))
.collect()
} else {
example_packages
};
packages.extend(filtered_packages);
}
Ok(packages)
}
/// Show the contents of packages
fn show_contents(&self, publisher: Option<&str>, pattern: Option<&str>, action_types: Option<&[String]>) -> Result<Vec<(String, String, String)>> {
// This is a placeholder implementation
// In a real implementation, we would parse package manifests and extract contents
// Get the list of packages
let packages = self.list_packages(publisher, pattern)?;
// For each package, list contents
let mut contents = Vec::new();
for (pkg_name, pkg_version, pub_name) in packages {
// Example content data (package, path, type)
let example_contents = vec![
(format!("{}@{}", pkg_name, pkg_version), "/usr/bin/example".to_string(), "file".to_string()),
(format!("{}@{}", pkg_name, pkg_version), "/usr/share/doc/example".to_string(), "dir".to_string()),
];
// Filter by action type if specified
let filtered_contents = if let Some(types) = action_types {
example_contents.into_iter()
.filter(|(_, _, action_type)| types.contains(&action_type))
.collect::<Vec<_>>()
} else {
example_contents
};
contents.extend(filtered_contents);
}
Ok(contents)
}
/// Rebuild repository metadata
fn rebuild(&self, publisher: Option<&str>, no_catalog: bool, no_index: bool) -> Result<()> {
// This is a placeholder implementation
// In a real implementation, we would rebuild catalogs and search indexes
// Filter publishers if specified
let publishers = if let Some(pub_name) = publisher {
if !self.config.publishers.contains(&pub_name.to_string()) {
return Err(anyhow!("Publisher does not exist: {}", pub_name));
}
vec![pub_name.to_string()]
} else {
self.config.publishers.clone()
};
// For each publisher, rebuild metadata
for pub_name in publishers {
println!("Rebuilding metadata for publisher: {}", pub_name);
if !no_catalog {
println!("Rebuilding catalog...");
// In a real implementation, we would rebuild the catalog
}
if !no_index {
println!("Rebuilding search index...");
// In a real implementation, we would rebuild the search index
}
}
Ok(())
}
/// Refresh repository metadata
fn refresh(&self, publisher: Option<&str>, no_catalog: bool, no_index: bool) -> Result<()> {
// This is a placeholder implementation
// In a real implementation, we would refresh catalogs and search indexes
// Filter publishers if specified
let publishers = if let Some(pub_name) = publisher {
if !self.config.publishers.contains(&pub_name.to_string()) {
return Err(anyhow!("Publisher does not exist: {}", pub_name));
}
vec![pub_name.to_string()]
} else {
self.config.publishers.clone()
};
// For each publisher, refresh metadata
for pub_name in publishers {
println!("Refreshing metadata for publisher: {}", pub_name);
if !no_catalog {
println!("Refreshing catalog...");
// In a real implementation, we would refresh the catalog
}
if !no_index {
println!("Refreshing search index...");
// In a real implementation, we would refresh the search index
}
}
Ok(())
}
}
impl FileBackend {
/// Create the repository directories
fn create_directories(&self) -> Result<()> {
// Create the main repository directories
fs::create_dir_all(self.path.join("catalog"))?;
fs::create_dir_all(self.path.join("file"))?;
fs::create_dir_all(self.path.join("index"))?;
fs::create_dir_all(self.path.join("pkg"))?;
fs::create_dir_all(self.path.join("trans"))?;
Ok(())
}
#[cfg(test)]
pub fn test_publish_files(&mut self, test_dir: &Path) -> Result<()> {
println!("Testing file publishing...");
// Create a test publisher
self.add_publisher("test")?;
// Create a nested directory structure
let nested_dir = test_dir.join("nested").join("dir");
fs::create_dir_all(&nested_dir)?;
// Create a test file in the nested directory
let test_file_path = nested_dir.join("test_file.txt");
fs::write(&test_file_path, "This is a test file")?;
// Begin a transaction
let mut transaction = self.begin_transaction()?;
// Create a FileAction from the test file path
let mut file_action = FileAction::read_from_path(&test_file_path)?;
// Calculate the relative path from the test file path to the base directory
let relative_path = test_file_path.strip_prefix(test_dir)?.to_string_lossy().to_string();
// Set the relative path in the FileAction
file_action.path = relative_path;
// Add the test file to the transaction
transaction.add_file(file_action, &test_file_path)?;
// Verify that the path in the FileAction is the relative path
// The path should be "nested/dir/test_file.txt", not the full path
let expected_path = "nested/dir/test_file.txt";
let actual_path = &transaction.manifest.files[0].path;
if actual_path != expected_path {
return Err(anyhow!("Path in FileAction is incorrect. Expected: {}, Actual: {}",
expected_path, actual_path));
}
// Commit the transaction
transaction.commit()?;
// Verify the file was stored
let hash = Transaction::calculate_file_hash(&test_file_path)?;
let stored_file_path = self.path.join("file").join(&hash);
if !stored_file_path.exists() {
return Err(anyhow!("File was not stored correctly"));
}
// Verify the manifest was updated
let manifest_path = self.path.join("pkg").join("manifest");
if !manifest_path.exists() {
return Err(anyhow!("Manifest was not created"));
}
println!("File publishing test passed!");
Ok(())
}
/// Begin a new transaction for publishing
pub fn begin_transaction(&self) -> Result<Transaction> {
Transaction::new(self.path.clone())
}
/// Publish files from a prototype directory
pub fn publish_files<P: AsRef<Path>>(&self, proto_dir: P, publisher: &str) -> Result<()> {
let proto_dir = proto_dir.as_ref();
// Check if the prototype directory exists
if !proto_dir.exists() {
return Err(anyhow!("Prototype directory does not exist: {}", proto_dir.display()));
}
// Check if the publisher exists
if !self.config.publishers.contains(&publisher.to_string()) {
return Err(anyhow!("Publisher does not exist: {}", publisher));
}
// Begin a transaction
let mut transaction = self.begin_transaction()?;
// Walk the prototype directory and add files to the transaction
self.add_files_to_transaction(&mut transaction, proto_dir, proto_dir)?;
// Commit the transaction
transaction.commit()?;
Ok(())
}
/// Add files from a directory to a transaction
fn add_files_to_transaction(&self, transaction: &mut Transaction, base_dir: &Path, dir: &Path) -> Result<()> {
// Read the directory entries
for entry in fs::read_dir(dir)? {
let entry = entry?;
let path = entry.path();
if path.is_dir() {
// Recursively add files from subdirectories
self.add_files_to_transaction(transaction, base_dir, &path)?;
} else {
// Create a FileAction from the file path
let mut file_action = FileAction::read_from_path(&path)?;
// Calculate the relative path from the file path to the base directory
let relative_path = path.strip_prefix(base_dir)?.to_string_lossy().to_string();
// Set the relative path in the FileAction
file_action.path = relative_path;
// Add the file to the transaction
transaction.add_file(file_action, &path)?;
}
}
Ok(())
}
/// Store a file in the repository
pub fn store_file<P: AsRef<Path>>(&self, file_path: P) -> Result<String> {
let file_path = file_path.as_ref();
// Calculate the SHA256 hash of the file
let hash = Transaction::calculate_file_hash(file_path)?;
// Create the destination path in the files directory
let dest_path = self.path.join("file").join(&hash);
// Copy the file if it doesn't already exist
if !dest_path.exists() {
fs::copy(file_path, &dest_path)?;
}
Ok(hash)
}
}

View file

@ -0,0 +1,97 @@
// This Source Code Form is subject to the terms of
// the Mozilla Public License, v. 2.0. If a copy of the
// MPL was not distributed with this file, You can
// obtain one at https://mozilla.org/MPL/2.0/.
use anyhow::Result;
use std::path::Path;
use std::collections::HashMap;
mod file_backend;
mod rest_backend;
pub use file_backend::FileBackend;
pub use rest_backend::RestBackend;
/// Repository configuration filename
pub const REPOSITORY_CONFIG_FILENAME: &str = "pkg6.repository";
/// Repository version
#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub enum RepositoryVersion {
V4 = 4,
}
impl Default for RepositoryVersion {
fn default() -> Self {
RepositoryVersion::V4
}
}
impl std::convert::TryFrom<u32> for RepositoryVersion {
type Error = anyhow::Error;
fn try_from(value: u32) -> Result<Self, Self::Error> {
match value {
4 => Ok(RepositoryVersion::V4),
_ => Err(anyhow::anyhow!("Unsupported repository version: {}", value)),
}
}
}
/// Repository configuration
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct RepositoryConfig {
pub version: RepositoryVersion,
pub publishers: Vec<String>,
pub properties: HashMap<String, String>,
}
impl Default for RepositoryConfig {
fn default() -> Self {
RepositoryConfig {
version: RepositoryVersion::default(),
publishers: Vec::new(),
properties: HashMap::new(),
}
}
}
/// Repository trait defining the interface for all repository backends
pub trait Repository {
/// Create a new repository at the specified path
fn create<P: AsRef<Path>>(path: P, version: RepositoryVersion) -> Result<Self> where Self: Sized;
/// Open an existing repository
fn open<P: AsRef<Path>>(path: P) -> Result<Self> where Self: Sized;
/// Save the repository configuration
fn save_config(&self) -> Result<()>;
/// Add a publisher to the repository
fn add_publisher(&mut self, publisher: &str) -> Result<()>;
/// Remove a publisher from the repository
fn remove_publisher(&mut self, publisher: &str, dry_run: bool) -> Result<()>;
/// Get repository information
fn get_info(&self) -> Result<Vec<(String, usize, String, String)>>;
/// Set a repository property
fn set_property(&mut self, property: &str, value: &str) -> Result<()>;
/// Set a publisher property
fn set_publisher_property(&mut self, publisher: &str, property: &str, value: &str) -> Result<()>;
/// List packages in the repository
fn list_packages(&self, publisher: Option<&str>, pattern: Option<&str>) -> Result<Vec<(String, String, String)>>;
/// Show contents of packages
fn show_contents(&self, publisher: Option<&str>, pattern: Option<&str>, action_types: Option<&[String]>) -> Result<Vec<(String, String, String)>>;
/// Rebuild repository metadata
fn rebuild(&self, publisher: Option<&str>, no_catalog: bool, no_index: bool) -> Result<()>;
/// Refresh repository metadata
fn refresh(&self, publisher: Option<&str>, no_catalog: bool, no_index: bool) -> Result<()>;
}

View file

@ -0,0 +1,310 @@
// This Source Code Form is subject to the terms of
// the Mozilla Public License, v. 2.0. If a copy of the
// MPL was not distributed with this file, You can
// obtain one at https://mozilla.org/MPL/2.0/.
use anyhow::{Result, anyhow};
use std::path::{Path, PathBuf};
use std::collections::HashMap;
use super::{Repository, RepositoryConfig, RepositoryVersion};
/// Repository implementation that uses a REST API
pub struct RestBackend {
pub uri: String,
pub config: RepositoryConfig,
pub local_cache_path: Option<PathBuf>,
}
impl Repository for RestBackend {
/// Create a new repository at the specified URI
fn create<P: AsRef<Path>>(uri: P, version: RepositoryVersion) -> Result<Self> {
// This is a stub implementation
// In a real implementation, we would make a REST API call to create the repository
let uri_str = uri.as_ref().to_string_lossy().to_string();
// Create the repository configuration
let config = RepositoryConfig {
version,
..Default::default()
};
// Create the repository structure
let repo = RestBackend {
uri: uri_str,
config,
local_cache_path: None,
};
// In a real implementation, we would make a REST API call to create the repository structure
Ok(repo)
}
/// Open an existing repository
fn open<P: AsRef<Path>>(uri: P) -> Result<Self> {
// This is a stub implementation
// In a real implementation, we would make a REST API call to get the repository configuration
let uri_str = uri.as_ref().to_string_lossy().to_string();
// In a real implementation, we would fetch the repository configuration from the REST API
// For now, we'll just create a default configuration
let config = RepositoryConfig::default();
Ok(RestBackend {
uri: uri_str,
config,
local_cache_path: None,
})
}
/// Save the repository configuration
fn save_config(&self) -> Result<()> {
// This is a stub implementation
// In a real implementation, we would make a REST API call to save the repository configuration
// For now, just return Ok
Ok(())
}
/// Add a publisher to the repository
fn add_publisher(&mut self, publisher: &str) -> Result<()> {
// This is a stub implementation
// In a real implementation, we would make a REST API call to add the publisher
if !self.config.publishers.contains(&publisher.to_string()) {
self.config.publishers.push(publisher.to_string());
// In a real implementation, we would make a REST API call to create publisher-specific resources
// Save the updated configuration
self.save_config()?;
}
Ok(())
}
/// Remove a publisher from the repository
fn remove_publisher(&mut self, publisher: &str, dry_run: bool) -> Result<()> {
// This is a stub implementation
// In a real implementation, we would make a REST API call to remove the publisher
if let Some(pos) = self.config.publishers.iter().position(|p| p == publisher) {
if !dry_run {
self.config.publishers.remove(pos);
// In a real implementation, we would make a REST API call to remove publisher-specific resources
// Save the updated configuration
self.save_config()?;
}
}
Ok(())
}
/// Get repository information
fn get_info(&self) -> Result<Vec<(String, usize, String, String)>> {
// This is a stub implementation
// In a real implementation, we would make a REST API call to get repository information
let mut info = Vec::new();
for publisher in &self.config.publishers {
// In a real implementation, we would get this information from the REST API
let package_count = 0;
let status = "online".to_string();
let updated = "2025-07-21T18:46:00.000000Z".to_string();
info.push((publisher.clone(), package_count, status, updated));
}
Ok(info)
}
/// Set a repository property
fn set_property(&mut self, property: &str, value: &str) -> Result<()> {
// This is a stub implementation
// In a real implementation, we would make a REST API call to set the property
self.config.properties.insert(property.to_string(), value.to_string());
self.save_config()?;
Ok(())
}
/// Set a publisher property
fn set_publisher_property(&mut self, publisher: &str, property: &str, value: &str) -> Result<()> {
// This is a stub implementation
// In a real implementation, we would make a REST API call to set the publisher property
// Check if the publisher exists
if !self.config.publishers.contains(&publisher.to_string()) {
return Err(anyhow!("Publisher does not exist: {}", publisher));
}
// Create the property key in the format "publisher/property"
let key = format!("{}/{}", publisher, property);
// Set the property
self.config.properties.insert(key, value.to_string());
// Save the updated configuration
self.save_config()?;
Ok(())
}
/// List packages in the repository
fn list_packages(&self, publisher: Option<&str>, pattern: Option<&str>) -> Result<Vec<(String, String, String)>> {
// This is a stub implementation
// In a real implementation, we would make a REST API call to list packages
let mut packages = Vec::new();
// Filter publishers if specified
let publishers = if let Some(pub_name) = publisher {
if !self.config.publishers.contains(&pub_name.to_string()) {
return Err(anyhow!("Publisher does not exist: {}", pub_name));
}
vec![pub_name.to_string()]
} else {
self.config.publishers.clone()
};
// For each publisher, list packages
for pub_name in publishers {
// In a real implementation, we would get this information from the REST API
// Example package data (name, version, publisher)
let example_packages = vec![
("example/package1".to_string(), "1.0.0".to_string(), pub_name.clone()),
("example/package2".to_string(), "2.0.0".to_string(), pub_name.clone()),
];
// Filter by pattern if specified
let filtered_packages = if let Some(pat) = pattern {
example_packages.into_iter()
.filter(|(name, _, _)| name.contains(pat))
.collect()
} else {
example_packages
};
packages.extend(filtered_packages);
}
Ok(packages)
}
/// Show contents of packages
fn show_contents(&self, publisher: Option<&str>, pattern: Option<&str>, action_types: Option<&[String]>) -> Result<Vec<(String, String, String)>> {
// This is a stub implementation
// In a real implementation, we would make a REST API call to get package contents
// Get the list of packages
let packages = self.list_packages(publisher, pattern)?;
// For each package, list contents
let mut contents = Vec::new();
for (pkg_name, pkg_version, _) in packages {
// In a real implementation, we would get this information from the REST API
// Example content data (package, path, type)
let example_contents = vec![
(format!("{}@{}", pkg_name, pkg_version), "/usr/bin/example".to_string(), "file".to_string()),
(format!("{}@{}", pkg_name, pkg_version), "/usr/share/doc/example".to_string(), "dir".to_string()),
];
// Filter by action type if specified
let filtered_contents = if let Some(types) = action_types {
example_contents.into_iter()
.filter(|(_, _, action_type)| types.contains(&action_type))
.collect::<Vec<_>>()
} else {
example_contents
};
contents.extend(filtered_contents);
}
Ok(contents)
}
/// Rebuild repository metadata
fn rebuild(&self, publisher: Option<&str>, no_catalog: bool, no_index: bool) -> Result<()> {
// This is a stub implementation
// In a real implementation, we would make a REST API call to rebuild metadata
// Filter publishers if specified
let publishers = if let Some(pub_name) = publisher {
if !self.config.publishers.contains(&pub_name.to_string()) {
return Err(anyhow!("Publisher does not exist: {}", pub_name));
}
vec![pub_name.to_string()]
} else {
self.config.publishers.clone()
};
// For each publisher, rebuild metadata
for pub_name in publishers {
println!("Rebuilding metadata for publisher: {}", pub_name);
if !no_catalog {
println!("Rebuilding catalog...");
// In a real implementation, we would make a REST API call to rebuild the catalog
}
if !no_index {
println!("Rebuilding search index...");
// In a real implementation, we would make a REST API call to rebuild the search index
}
}
Ok(())
}
/// Refresh repository metadata
fn refresh(&self, publisher: Option<&str>, no_catalog: bool, no_index: bool) -> Result<()> {
// This is a stub implementation
// In a real implementation, we would make a REST API call to refresh metadata
// Filter publishers if specified
let publishers = if let Some(pub_name) = publisher {
if !self.config.publishers.contains(&pub_name.to_string()) {
return Err(anyhow!("Publisher does not exist: {}", pub_name));
}
vec![pub_name.to_string()]
} else {
self.config.publishers.clone()
};
// For each publisher, refresh metadata
for pub_name in publishers {
println!("Refreshing metadata for publisher: {}", pub_name);
if !no_catalog {
println!("Refreshing catalog...");
// In a real implementation, we would make a REST API call to refresh the catalog
}
if !no_index {
println!("Refreshing search index...");
// In a real implementation, we would make a REST API call to refresh the search index
}
}
Ok(())
}
}
impl RestBackend {
/// Set the local cache path
pub fn set_local_cache_path<P: AsRef<Path>>(&mut self, path: P) -> Result<()> {
self.local_cache_path = Some(path.as_ref().to_path_buf());
Ok(())
}
}

View file

@ -4,7 +4,7 @@ version = "0.0.1-placeholder"
authors = ["Till Wegmueller <till.wegmueller@openflowlabs.com>"] authors = ["Till Wegmueller <till.wegmueller@openflowlabs.com>"]
edition = "2018" edition = "2018"
license-file = "LICENSE" license-file = "LICENSE"
description = "The repository server for IPS written in rust" description = "The repository management utility for IPS written in rust"
repository = "https://github.com/OpenFlowLabs/pkg6dev" repository = "https://github.com/OpenFlowLabs/pkg6dev"
readme = "README.md" readme = "README.md"
keywords = ["packaging", "illumos"] keywords = ["packaging", "illumos"]
@ -12,3 +12,11 @@ keywords = ["packaging", "illumos"]
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies] [dependencies]
clap = { version = "4.4", features = ["derive"] }
anyhow = "1.0"
libips = { path = "../libips" }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
[dev-dependencies]
tempfile = "3.8"

View file

@ -1,3 +1,163 @@
# IPS Packaging utility. # pkg6repo
Work in progress pkg6repo is a Rust implementation of the Image Packaging System (IPS) repository management utility. It is designed to replace the pkgrepo command from the original IPS implementation.
## Installation
To build and install pkg6repo, you need to have Rust and Cargo installed. Then, you can build the project using:
```bash
cargo build --release
```
The binary will be available at `target/release/pkg6repo`.
## Usage
pkg6repo provides several subcommands for managing package repositories:
### Create a Repository
Create a new package repository:
```bash
pkg6repo create /path/to/repository
```
You can specify the repository version (default is 4):
```bash
pkg6repo create --version 4 /path/to/repository
```
### Add Publishers
Add publishers to a repository:
```bash
pkg6repo add-publisher -s /path/to/repository example.com
```
### Remove Publishers
Remove publishers from a repository:
```bash
pkg6repo remove-publisher -s /path/to/repository example.com
```
You can perform a dry run to see what would be removed without actually removing anything:
```bash
pkg6repo remove-publisher -n -s /path/to/repository example.com
```
### Get Repository Properties
Get repository properties:
```bash
pkg6repo get -s /path/to/repository
```
You can specify specific properties to get:
```bash
pkg6repo get -s /path/to/repository publisher/prefix
```
### Set Repository Properties
Set repository properties:
```bash
pkg6repo set -s /path/to/repository publisher/prefix=example.com
```
You can set publisher-specific properties:
```bash
pkg6repo set -s /path/to/repository -p example.com repository/origins=http://example.com/repository
```
### Display Repository Information
Display information about a repository:
```bash
pkg6repo info -s /path/to/repository
```
### List Packages
List packages in a repository:
```bash
pkg6repo list -s /path/to/repository
```
You can filter by publisher:
```bash
pkg6repo list -s /path/to/repository -p example.com
```
You can also filter by package pattern:
```bash
pkg6repo list -s /path/to/repository example/package
```
### Show Package Contents
Show contents of packages in a repository:
```bash
pkg6repo contents -s /path/to/repository
```
You can filter by package pattern:
```bash
pkg6repo contents -s /path/to/repository example/package
```
You can also filter by action type:
```bash
pkg6repo contents -s /path/to/repository -t file
```
### Rebuild Repository Metadata
Rebuild repository metadata:
```bash
pkg6repo rebuild -s /path/to/repository
```
You can skip catalog or index rebuilding:
```bash
pkg6repo rebuild -s /path/to/repository --no-catalog
pkg6repo rebuild -s /path/to/repository --no-index
```
### Refresh Repository Metadata
Refresh repository metadata:
```bash
pkg6repo refresh -s /path/to/repository
```
You can skip catalog or index refreshing:
```bash
pkg6repo refresh -s /path/to/repository --no-catalog
pkg6repo refresh -s /path/to/repository --no-index
```
## License
This project is licensed under the same license as the original IPS implementation.

View file

@ -1,3 +1,492 @@
fn main() { use clap::{Parser, Subcommand};
println!("Hello, world!"); use anyhow::{Result, anyhow};
use std::path::PathBuf;
use std::convert::TryFrom;
use libips::repository::{Repository, RepositoryVersion, FileBackend};
#[cfg(test)]
mod tests;
/// pkg6repo - Image Packaging System repository management utility
#[derive(Parser, Debug)]
#[clap(author, version, about, long_about = None)]
#[clap(propagate_version = true)]
struct App {
#[clap(subcommand)]
command: Commands,
} }
#[derive(Subcommand, Debug)]
enum Commands {
/// Create a new package repository
Create {
/// Version of the repository to create
#[clap(long, default_value = "4")]
version: u32,
/// Path or URI of the repository to create
uri_or_path: String,
},
/// Add publishers to a repository
AddPublisher {
/// Path or URI of the repository
#[clap(short = 's')]
repo_uri_or_path: String,
/// Publishers to add
publisher: Vec<String>,
},
/// Remove publishers from a repository
RemovePublisher {
/// Path or URI of the repository
#[clap(short = 's')]
repo_uri_or_path: String,
/// Perform a dry run
#[clap(short = 'n')]
dry_run: bool,
/// Wait for operation to complete
#[clap(long)]
synchronous: bool,
/// Publishers to remove
publisher: Vec<String>,
},
/// Get repository properties
Get {
/// Path or URI of the repository
#[clap(short = 's')]
repo_uri_or_path: String,
/// Output format
#[clap(short = 'F')]
format: Option<String>,
/// Omit headers
#[clap(short = 'H')]
omit_headers: bool,
/// Publisher to get properties for
#[clap(short = 'p')]
publisher: Option<Vec<String>>,
/// SSL key file
#[clap(long)]
key: Option<PathBuf>,
/// SSL certificate file
#[clap(long)]
cert: Option<PathBuf>,
/// Properties to get (section/property)
section_property: Option<Vec<String>>,
},
/// Display repository information
Info {
/// Path or URI of the repository
#[clap(short = 's')]
repo_uri_or_path: String,
/// Output format
#[clap(short = 'F')]
format: Option<String>,
/// Omit headers
#[clap(short = 'H')]
omit_headers: bool,
/// Publisher to get information for
#[clap(short = 'p')]
publisher: Option<Vec<String>>,
/// SSL key file
#[clap(long)]
key: Option<PathBuf>,
/// SSL certificate file
#[clap(long)]
cert: Option<PathBuf>,
},
/// List packages in a repository
List {
/// Path or URI of the repository
#[clap(short = 's')]
repo_uri_or_path: String,
/// Output format
#[clap(short = 'F')]
format: Option<String>,
/// Omit headers
#[clap(short = 'H')]
omit_headers: bool,
/// Publisher to list packages for
#[clap(short = 'p')]
publisher: Option<Vec<String>>,
/// SSL key file
#[clap(long)]
key: Option<PathBuf>,
/// SSL certificate file
#[clap(long)]
cert: Option<PathBuf>,
/// Package FMRI patterns to match
pkg_fmri_pattern: Option<Vec<String>>,
},
/// Show contents of packages in a repository
Contents {
/// Path or URI of the repository
#[clap(short = 's')]
repo_uri_or_path: String,
/// Show manifest contents
#[clap(short = 'm')]
manifest: bool,
/// Filter by action type
#[clap(short = 't')]
action_type: Option<Vec<String>>,
/// SSL key file
#[clap(long)]
key: Option<PathBuf>,
/// SSL certificate file
#[clap(long)]
cert: Option<PathBuf>,
/// Package FMRI patterns to match
pkg_fmri_pattern: Option<Vec<String>>,
},
/// Rebuild repository metadata
Rebuild {
/// Path or URI of the repository
#[clap(short = 's')]
repo_uri_or_path: String,
/// Publisher to rebuild metadata for
#[clap(short = 'p')]
publisher: Option<Vec<String>>,
/// SSL key file
#[clap(long)]
key: Option<PathBuf>,
/// SSL certificate file
#[clap(long)]
cert: Option<PathBuf>,
/// Skip catalog rebuild
#[clap(long)]
no_catalog: bool,
/// Skip index rebuild
#[clap(long)]
no_index: bool,
},
/// Refresh repository metadata
Refresh {
/// Path or URI of the repository
#[clap(short = 's')]
repo_uri_or_path: String,
/// Publisher to refresh metadata for
#[clap(short = 'p')]
publisher: Option<Vec<String>>,
/// SSL key file
#[clap(long)]
key: Option<PathBuf>,
/// SSL certificate file
#[clap(long)]
cert: Option<PathBuf>,
/// Skip catalog refresh
#[clap(long)]
no_catalog: bool,
/// Skip index refresh
#[clap(long)]
no_index: bool,
},
/// Set repository properties
Set {
/// Path or URI of the repository
#[clap(short = 's')]
repo_uri_or_path: String,
/// Publisher to set properties for
#[clap(short = 'p')]
publisher: Option<String>,
/// Properties to set (section/property=value)
property_value: Vec<String>,
},
}
fn main() -> Result<()> {
let cli = App::parse();
match &cli.command {
Commands::Create { version, uri_or_path } => {
println!("Creating repository version {} at {}", version, uri_or_path);
// Convert version to RepositoryVersion
let repo_version = RepositoryVersion::try_from(*version)?;
// Create the repository
let repo = FileBackend::create(uri_or_path, repo_version)?;
println!("Repository created successfully at {}", repo.path.display());
Ok(())
},
Commands::AddPublisher { repo_uri_or_path, publisher } => {
println!("Adding publishers {:?} to repository {}", publisher, repo_uri_or_path);
// Open the repository
let mut repo = FileBackend::open(repo_uri_or_path)?;
// Add each publisher
for p in publisher {
println!("Adding publisher: {}", p);
repo.add_publisher(p)?;
}
println!("Publishers added successfully");
Ok(())
},
Commands::RemovePublisher { repo_uri_or_path, dry_run, synchronous, publisher } => {
println!("Removing publishers {:?} from repository {}", publisher, repo_uri_or_path);
println!("Dry run: {}, Synchronous: {}", dry_run, synchronous);
// Open the repository
let mut repo = FileBackend::open(repo_uri_or_path)?;
// Remove each publisher
for p in publisher {
println!("Removing publisher: {}", p);
repo.remove_publisher(p, *dry_run)?;
}
if *dry_run {
println!("Dry run completed. No changes were made.");
} else {
println!("Publishers removed successfully");
}
Ok(())
},
Commands::Get { repo_uri_or_path, format, omit_headers, publisher, key, cert, section_property } => {
println!("Getting properties from repository {}", repo_uri_or_path);
// Open the repository
let repo = FileBackend::open(repo_uri_or_path)?;
// Print headers if not omitted
if !omit_headers {
println!("{:<10} {:<10} {:<20}", "SECTION", "PROPERTY", "VALUE");
}
// Print repository properties
for (key, value) in &repo.config.properties {
let parts: Vec<&str> = key.split('/').collect();
if parts.len() == 2 {
println!("{:<10} {:<10} {:<20}", parts[0], parts[1], value);
} else {
println!("{:<10} {:<10} {:<20}", "", key, value);
}
}
Ok(())
},
Commands::Info { repo_uri_or_path, format, omit_headers, publisher, key, cert } => {
println!("Displaying info for repository {}", repo_uri_or_path);
// Open the repository
let repo = FileBackend::open(repo_uri_or_path)?;
// Get repository info
let info = repo.get_info()?;
// Print headers if not omitted
if !omit_headers {
println!("{:<10} {:<8} {:<6} {:<30}", "PUBLISHER", "PACKAGES", "STATUS", "UPDATED");
}
// Print repository info
for (pub_name, pkg_count, status, updated) in info {
println!("{:<10} {:<8} {:<6} {:<30}", pub_name, pkg_count, status, updated);
}
Ok(())
},
Commands::List { repo_uri_or_path, format, omit_headers, publisher, key, cert, pkg_fmri_pattern } => {
println!("Listing packages in repository {}", repo_uri_or_path);
// Open the repository
let repo = FileBackend::open(repo_uri_or_path)?;
// Get the publisher if specified
let pub_option = if let Some(publishers) = publisher {
if !publishers.is_empty() {
Some(publishers[0].as_str())
} else {
None
}
} else {
None
};
// Get the pattern if specified
let pattern_option = if let Some(patterns) = pkg_fmri_pattern {
if !patterns.is_empty() {
Some(patterns[0].as_str())
} else {
None
}
} else {
None
};
// List packages
let packages = repo.list_packages(pub_option, pattern_option)?;
// Print headers if not omitted
if !omit_headers {
println!("{:<30} {:<15} {:<10}", "NAME", "VERSION", "PUBLISHER");
}
// Print packages
for (name, version, publisher) in packages {
println!("{:<30} {:<15} {:<10}", name, version, publisher);
}
Ok(())
},
Commands::Contents { repo_uri_or_path, manifest, action_type, key, cert, pkg_fmri_pattern } => {
println!("Showing contents in repository {}", repo_uri_or_path);
// Open the repository
let repo = FileBackend::open(repo_uri_or_path)?;
// Get the pattern if specified
let pattern_option = if let Some(patterns) = pkg_fmri_pattern {
if !patterns.is_empty() {
Some(patterns[0].as_str())
} else {
None
}
} else {
None
};
// Show contents
let contents = repo.show_contents(None, pattern_option, action_type.as_deref())?;
// Print contents
for (package, path, action_type) in contents {
if *manifest {
// If manifest option is specified, print in manifest format
println!("{} path={} type={}", action_type, path, package);
} else {
// Otherwise, print in table format
println!("{:<40} {:<30} {:<10}", package, path, action_type);
}
}
Ok(())
},
Commands::Rebuild { repo_uri_or_path, publisher, key, cert, no_catalog, no_index } => {
println!("Rebuilding repository {}", repo_uri_or_path);
// Open the repository
let repo = FileBackend::open(repo_uri_or_path)?;
// Get the publisher if specified
let pub_option = if let Some(publishers) = publisher {
if !publishers.is_empty() {
Some(publishers[0].as_str())
} else {
None
}
} else {
None
};
// Rebuild repository metadata
repo.rebuild(pub_option, *no_catalog, *no_index)?;
println!("Repository rebuilt successfully");
Ok(())
},
Commands::Refresh { repo_uri_or_path, publisher, key, cert, no_catalog, no_index } => {
println!("Refreshing repository {}", repo_uri_or_path);
// Open the repository
let repo = FileBackend::open(repo_uri_or_path)?;
// Get the publisher if specified
let pub_option = if let Some(publishers) = publisher {
if !publishers.is_empty() {
Some(publishers[0].as_str())
} else {
None
}
} else {
None
};
// Refresh repository metadata
repo.refresh(pub_option, *no_catalog, *no_index)?;
println!("Repository refreshed successfully");
Ok(())
},
Commands::Set { repo_uri_or_path, publisher, property_value } => {
println!("Setting properties for repository {}", repo_uri_or_path);
// Open the repository
let mut repo = FileBackend::open(repo_uri_or_path)?;
// Process each property=value pair
for prop_val in property_value {
// Split the property=value string
let parts: Vec<&str> = prop_val.split('=').collect();
if parts.len() != 2 {
return Err(anyhow!("Invalid property=value format: {}", prop_val));
}
let property = parts[0];
let value = parts[1];
// If a publisher is specified, set the publisher property
if let Some(pub_name) = publisher {
println!("Setting publisher property {}/{} = {}", pub_name, property, value);
repo.set_publisher_property(pub_name, property, value)?;
} else {
// Otherwise, set the repository property
println!("Setting repository property {} = {}", property, value);
repo.set_property(property, value)?;
}
}
println!("Properties set successfully");
Ok(())
},
}
}

80
pkg6repo/src/tests.rs Normal file
View file

@ -0,0 +1,80 @@
#[cfg(test)]
mod tests {
use libips::repository::{Repository, RepositoryVersion, FileBackend, REPOSITORY_CONFIG_FILENAME};
use tempfile::tempdir;
#[test]
fn test_create_repository() {
// Create a temporary directory for the test
let temp_dir = tempdir().unwrap();
let repo_path = temp_dir.path().join("repo");
// Create a repository
let _ = FileBackend::create(&repo_path, RepositoryVersion::V4).unwrap();
// Check that the repository was created
assert!(repo_path.exists());
assert!(repo_path.join("catalog").exists());
assert!(repo_path.join("file").exists());
assert!(repo_path.join("index").exists());
assert!(repo_path.join("pkg").exists());
assert!(repo_path.join("trans").exists());
assert!(repo_path.join(REPOSITORY_CONFIG_FILENAME).exists());
}
#[test]
fn test_add_publisher() {
// Create a temporary directory for the test
let temp_dir = tempdir().unwrap();
let repo_path = temp_dir.path().join("repo");
// Create a repository
let mut repo = FileBackend::create(&repo_path, RepositoryVersion::V4).unwrap();
// Add a publisher
repo.add_publisher("example.com").unwrap();
// Check that the publisher was added
assert!(repo.config.publishers.contains(&"example.com".to_string()));
assert!(repo_path.join("catalog").join("example.com").exists());
assert!(repo_path.join("pkg").join("example.com").exists());
}
#[test]
fn test_remove_publisher() {
// Create a temporary directory for the test
let temp_dir = tempdir().unwrap();
let repo_path = temp_dir.path().join("repo");
// Create a repository
let mut repo = FileBackend::create(&repo_path, RepositoryVersion::V4).unwrap();
// Add a publisher
repo.add_publisher("example.com").unwrap();
// Check that the publisher was added
assert!(repo.config.publishers.contains(&"example.com".to_string()));
// Remove the publisher
repo.remove_publisher("example.com", false).unwrap();
// Check that the publisher was removed
assert!(!repo.config.publishers.contains(&"example.com".to_string()));
}
#[test]
fn test_set_property() {
// Create a temporary directory for the test
let temp_dir = tempdir().unwrap();
let repo_path = temp_dir.path().join("repo");
// Create a repository
let mut repo = FileBackend::create(&repo_path, RepositoryVersion::V4).unwrap();
// Set a property
repo.set_property("publisher/prefix", "example.com").unwrap();
// Check that the property was set
assert_eq!(repo.config.properties.get("publisher/prefix").unwrap(), "example.com");
}
}