diff --git a/CHANGELOG.md b/CHANGELOG.md index cbbafe939e..a81f9d215c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,13 @@ # Changelog +## Unreleased + +### Features + +- Added experimental `sentry-cli build snapshots` command to upload build snapshots to a project ([#3110](https://github.com/getsentry/sentry-cli/pull/3110)). + - This command uploads files from a specified directory to Sentry's Objectstore, associating them with a snapshot identifier. + - The command is experimental and subject to breaking changes or removal in future releases. + ## 3.2.2 ### Fixes diff --git a/Cargo.lock b/Cargo.lock index c508903303..0b8a4ebca3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -193,6 +193,41 @@ dependencies = [ "wait-timeout", ] +[[package]] +name = "async-compression" +version = "0.4.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "98ec5f6c2f8bc326c994cb9e241cc257ddaba9afa8555a43cffbb5dd86efaa37" +dependencies = [ + "compression-codecs", + "compression-core", + "futures-core", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "async-stream" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b5a71a6f37880a80d1d7f19efd781e4b5de42c88f0722cc13bcb6cc2cfe8476" +dependencies = [ + "async-stream-impl", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-stream-impl" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "atomic-waker" version = "1.1.2" @@ -231,6 +266,12 @@ dependencies = [ "windows-link 0.2.0", ] +[[package]] +name = "base16ct" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" + [[package]] name = "base64" version = "0.13.1" @@ -253,6 +294,12 @@ dependencies = [ "vsimd", ] +[[package]] +name = "base64ct" +version = "1.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06" + [[package]] name = "binary-merge" version = "0.1.2" @@ -483,6 +530,23 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "compression-codecs" +version = "0.4.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0f7ac3e5b97fdce45e8922fb05cae2c37f7bbd63d30dd94821dacfd8f3f2bf2" +dependencies = [ + "compression-core", + "zstd", + "zstd-safe", +] + +[[package]] +name = "compression-core" +version = "0.4.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75984efb6ed102a0d42db99afb6c1948f0380d1d91808d5529916e6c08b49d8d" + [[package]] name = "console" version = "0.15.11" @@ -509,6 +573,12 @@ dependencies = [ "windows-sys 0.61.1", ] +[[package]] +name = "const-oid" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" + [[package]] name = "constant_time_eq" version = "0.3.1" @@ -524,6 +594,16 @@ dependencies = [ "memchr", ] +[[package]] +name = "core-foundation" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "core-foundation-sys" version = "0.8.7" @@ -588,6 +668,18 @@ version = "0.8.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" +[[package]] +name = "crypto-bigint" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" +dependencies = [ + "generic-array", + "rand_core 0.6.4", + "subtle", + "zeroize", +] + [[package]] name = "crypto-common" version = "0.1.6" @@ -649,6 +741,33 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "curve25519-dalek" +version = "4.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97fb8b7c4503de7d6ae7b42ab72a5a59857b4c937ec27a3d4539dba95b5ab2be" +dependencies = [ + "cfg-if", + "cpufeatures", + "curve25519-dalek-derive", + "digest", + "fiat-crypto", + "rustc_version", + "subtle", + "zeroize", +] + +[[package]] +name = "curve25519-dalek-derive" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "data-encoding" version = "2.9.0" @@ -671,6 +790,17 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "da692b8d1080ea3045efaab14434d40468c3d8657e42abddfffca87b428f4c1b" +[[package]] +name = "der" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb" +dependencies = [ + "const-oid", + "pem-rfc7468", + "zeroize", +] + [[package]] name = "deranged" version = "0.5.4" @@ -704,6 +834,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ "block-buffer", + "const-oid", "crypto-common", "subtle", ] @@ -784,6 +915,44 @@ version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" +[[package]] +name = "ecdsa" +version = "0.16.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca" +dependencies = [ + "der", + "digest", + "elliptic-curve", + "rfc6979", + "signature", + "spki", +] + +[[package]] +name = "ed25519" +version = "2.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "115531babc129696a58c64a4fef0a8bf9e9698629fb97e9e40767d235cfbcd53" +dependencies = [ + "pkcs8", + "signature", +] + +[[package]] +name = "ed25519-dalek" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70e796c081cee67dc755e1a36a0a172b897fab85fc3f6bc48307991f64e4eca9" +dependencies = [ + "curve25519-dalek", + "ed25519", + "serde", + "sha2", + "subtle", + "zeroize", +] + [[package]] name = "either" version = "1.15.0" @@ -799,6 +968,27 @@ dependencies = [ "string_cache", ] +[[package]] +name = "elliptic-curve" +version = "0.13.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47" +dependencies = [ + "base16ct", + "crypto-bigint", + "digest", + "ff", + "generic-array", + "group", + "hkdf", + "pem-rfc7468", + "pkcs8", + "rand_core 0.6.4", + "sec1", + "subtle", + "zeroize", +] + [[package]] name = "elsa" version = "1.11.2" @@ -857,6 +1047,22 @@ version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" +[[package]] +name = "ff" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0b50bfb653653f9ca9095b427bed08ab8d75a137839d9ad64eb11810d5b6393" +dependencies = [ + "rand_core 0.6.4", + "subtle", +] + +[[package]] +name = "fiat-crypto" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" + [[package]] name = "filetime" version = "0.2.26" @@ -897,6 +1103,21 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + [[package]] name = "form_urlencoded" version = "1.2.2" @@ -1015,6 +1236,7 @@ checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" dependencies = [ "typenum", "version_check", + "zeroize", ] [[package]] @@ -1107,6 +1329,17 @@ dependencies = [ "scroll 0.12.0", ] +[[package]] +name = "group" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" +dependencies = [ + "ff", + "rand_core 0.6.4", + "subtle", +] + [[package]] name = "h2" version = "0.4.12" @@ -1174,6 +1407,15 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +[[package]] +name = "hkdf" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7" +dependencies = [ + "hmac", +] + [[package]] name = "hmac" version = "0.12.1" @@ -1205,12 +1447,11 @@ dependencies = [ [[package]] name = "http" -version = "1.3.1" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4a85d31aea989eead29a3aaf9e1115a180df8282431156e533de47660892565" +checksum = "e3ba2a386d7f85a81f119ad7498ebe444d2e22c2af0b86b069416ace48b3311a" dependencies = [ "bytes", - "fnv", "itoa", ] @@ -1285,6 +1526,39 @@ dependencies = [ "pin-utils", "smallvec", "tokio", + "want", +] + +[[package]] +name = "hyper-rustls" +version = "0.27.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3c93eb611681b207e1fe55d5a71ecf91572ec8a6705cdb6857f7d8d5242cf58" +dependencies = [ + "http", + "hyper", + "hyper-util", + "rustls", + "rustls-pki-types", + "tokio", + "tokio-rustls", + "tower-service", +] + +[[package]] +name = "hyper-tls" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" +dependencies = [ + "bytes", + "http-body-util", + "hyper", + "hyper-util", + "native-tls", + "tokio", + "tokio-native-tls", + "tower-service", ] [[package]] @@ -1293,13 +1567,24 @@ version = "0.1.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c6995591a8f1380fcb4ba966a252a4b29188d51d2b89e3a252f5305be65aea8" dependencies = [ + "base64 0.22.1", "bytes", + "futures-channel", "futures-core", + "futures-util", "http", "http-body", "hyper", + "ipnet", + "libc", + "percent-encoding", "pin-project-lite", + "socket2", + "system-configuration", "tokio", + "tower-service", + "tracing", + "windows-registry", ] [[package]] @@ -1455,6 +1740,12 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "imagesize" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09e54e57b4c48b40f7aec75635392b12b3421fa26fe8b4332e63138ed278459c" + [[package]] name = "indent_write" version = "2.2.0" @@ -1485,6 +1776,12 @@ dependencies = [ "regex", ] +[[package]] +name = "infer" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a588916bfdfd92e71cacef98a63d9b1f0d74d6599980d11894290e7ddefffcf7" + [[package]] name = "inout" version = "0.1.4" @@ -1528,6 +1825,22 @@ dependencies = [ "libc", ] +[[package]] +name = "ipnet" +version = "2.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" + +[[package]] +name = "iri-string" +version = "0.7.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f867b9d1d896b67beb18518eda36fdb77a32ea590de864f1325b294a6d14397" +dependencies = [ + "memchr", + "serde", +] + [[package]] name = "is-terminal" version = "0.4.16" @@ -1589,19 +1902,45 @@ checksum = "72167d68f5fce3b8655487b8038691a3c9984ee769590f93f2a631f4ad64e4f5" [[package]] name = "js-sys" -version = "0.3.81" +version = "0.3.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec48937a97411dcb524a265206ccd4c90bb711fca92b2792c407f268825b9305" +checksum = "f4eacb0641a310445a4c513f2a5e23e19952e269c6a38887254d5f837a305506" dependencies = [ "once_cell", "wasm-bindgen", ] +[[package]] +name = "jsonwebtoken" +version = "10.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0529410abe238729a60b108898784df8984c87f6054c9c4fcacc47e4803c1ce1" +dependencies = [ + "base64 0.22.1", + "ed25519-dalek", + "getrandom 0.2.16", + "hmac", + "js-sys", + "p256", + "p384", + "pem", + "rand 0.8.5", + "rsa", + "serde", + "serde_json", + "sha2", + "signature", + "simple_asn1", +] + [[package]] name = "lazy_static" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" +dependencies = [ + "spin", +] [[package]] name = "leb128" @@ -1627,6 +1966,12 @@ dependencies = [ "pkg-config", ] +[[package]] +name = "libm" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" + [[package]] name = "libredox" version = "0.1.10" @@ -1742,6 +2087,12 @@ version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4facc753ae494aeb6e3c22f839b158aebd4f9270f55cd3c79906c45476c47ab4" +[[package]] +name = "mediatype" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "120fa187be19d9962f0926633453784691731018a2bf936ddb4e29101b79c4a7" + [[package]] name = "memchr" version = "2.7.6" @@ -1757,6 +2108,22 @@ dependencies = [ "libc", ] +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + +[[package]] +name = "mime_guess" +version = "2.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7c44f8e672c00fe5308fa235f821cb4198414e1c77935c1ab6948d3fd78550e" +dependencies = [ + "mime", + "unicase", +] + [[package]] name = "minimal-lexical" version = "0.2.1" @@ -1807,6 +2174,40 @@ dependencies = [ "tokio", ] +[[package]] +name = "multer" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83e87776546dc87511aa5ee218730c92b666d7264ab6ed41f9d215af9cd5224b" +dependencies = [ + "bytes", + "encoding_rs", + "futures-util", + "http", + "httparse", + "memchr", + "mime", + "spin", + "version_check", +] + +[[package]] +name = "native-tls" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87de3442987e9dbec73158d5c715e7ad9072fda936bb03d19d7fa10e00520f0e" +dependencies = [ + "libc", + "log", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework", + "security-framework-sys", + "tempfile", +] + [[package]] name = "new_debug_unreachable" version = "1.0.6" @@ -1843,11 +2244,57 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "61807f77802ff30975e01f4f071c8ba10c022052f98b3294119f3e615d13e5be" [[package]] -name = "num-conv" -version = "0.2.0" +name = "num-bigint" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" +dependencies = [ + "num-integer", + "num-traits", +] + +[[package]] +name = "num-bigint-dig" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e661dda6640fad38e827a6d4a310ff4763082116fe217f279885c97f511bb0b7" +dependencies = [ + "lazy_static", + "libm", + "num-integer", + "num-iter", + "num-traits", + "rand 0.8.5", + "smallvec", + "zeroize", +] + +[[package]] +name = "num-conv" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf97ec579c3c42f953ef76dbf8d55ac91fb219dde70e49aa4a6b7d74e9919050" +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-iter" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + [[package]] name = "num-traits" version = "0.2.19" @@ -1855,6 +2302,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ "autocfg", + "libm", ] [[package]] @@ -1872,6 +2320,43 @@ dependencies = [ "memchr", ] +[[package]] +name = "objectstore-client" +version = "0.0.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3bcaf8bc0ea7c50905631df108126c6a075ce5a9c16713ec4b68cc872a408e08" +dependencies = [ + "async-compression", + "async-stream", + "bytes", + "futures-util", + "infer", + "jsonwebtoken", + "multer", + "objectstore-types", + "percent-encoding", + "reqwest", + "sentry-core", + "serde", + "thiserror 2.0.17", + "tokio", + "tokio-util", + "url", +] + +[[package]] +name = "objectstore-types" +version = "0.0.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f190038e8988112a4e593f1796612941117d88753574ef6476f99324d4605aae" +dependencies = [ + "http", + "humantime", + "mediatype", + "serde", + "thiserror 2.0.17", +] + [[package]] name = "once_cell" version = "1.21.3" @@ -1894,6 +2379,32 @@ dependencies = [ "windows-sys 0.42.0", ] +[[package]] +name = "openssl" +version = "0.10.75" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08838db121398ad17ab8531ce9de97b244589089e290a384c900cb9ff7434328" +dependencies = [ + "bitflags 2.9.4", + "cfg-if", + "foreign-types", + "libc", + "once_cell", + "openssl-macros", + "openssl-sys", +] + +[[package]] +name = "openssl-macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "openssl-probe" version = "0.1.6" @@ -1911,9 +2422,9 @@ dependencies = [ [[package]] name = "openssl-sys" -version = "0.9.109" +version = "0.9.111" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90096e2e47630d78b7d1c20952dc621f957103f8bc2c8359ec81290d75238571" +checksum = "82cab2d520aa75e3c58898289429321eb788c3106963d0dc886ec7a5f4adc321" dependencies = [ "cc", "libc", @@ -1960,6 +2471,30 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1a80800c0488c3a21695ea981a54918fbb37abf04f4d0720c453632255e2ff0e" +[[package]] +name = "p256" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9863ad85fa8f4460f9c48cb909d38a0d689dba1f6f6988a5e3e0d31071bcd4b" +dependencies = [ + "ecdsa", + "elliptic-curve", + "primeorder", + "sha2", +] + +[[package]] +name = "p384" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe42f1670a52a47d448f14b6a5c61dd78fce51856e68edaa38f7ae3a46b8d6b6" +dependencies = [ + "ecdsa", + "elliptic-curve", + "primeorder", + "sha2", +] + [[package]] name = "parking_lot" version = "0.12.4" @@ -2033,6 +2568,25 @@ dependencies = [ "thiserror 1.0.69", ] +[[package]] +name = "pem" +version = "3.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d30c53c26bc5b31a98cd02d20f25a7c8567146caf63ed593a9d87b2775291be" +dependencies = [ + "base64 0.22.1", + "serde_core", +] + +[[package]] +name = "pem-rfc7468" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412" +dependencies = [ + "base64ct", +] + [[package]] name = "percent-encoding" version = "2.3.2" @@ -2133,6 +2687,27 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +[[package]] +name = "pkcs1" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8ffb9f10fa047879315e6625af03c164b16962a5368d724ed16323b68ace47f" +dependencies = [ + "der", + "pkcs8", + "spki", +] + +[[package]] +name = "pkcs8" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" +dependencies = [ + "der", + "spki", +] + [[package]] name = "pkg-config" version = "0.3.32" @@ -2229,6 +2804,15 @@ dependencies = [ "unicode-width 0.1.14", ] +[[package]] +name = "primeorder" +version = "0.13.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "353e1ca18966c16d9deb1c69278edbc5f194139612772bd9537af60ac231e1e6" +dependencies = [ + "elliptic-curve", +] + [[package]] name = "proc-macro2" version = "1.0.101" @@ -2298,6 +2882,8 @@ version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ + "libc", + "rand_chacha 0.3.1", "rand_core 0.6.4", ] @@ -2307,10 +2893,20 @@ version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" dependencies = [ - "rand_chacha", + "rand_chacha 0.9.0", "rand_core 0.9.3", ] +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core 0.6.4", +] + [[package]] name = "rand_chacha" version = "0.9.0" @@ -2326,6 +2922,9 @@ name = "rand_core" version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom 0.2.16", +] [[package]] name = "rand_core" @@ -2422,6 +3021,93 @@ version = "1.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ba39f3699c378cd8970968dcbff9c43159ea4cfbd88d43c00b22f2ef10a435d2" +[[package]] +name = "reqwest" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab3f43e3283ab1488b624b44b0e988d0acea0b3214e694730a055cb6b2efa801" +dependencies = [ + "base64 0.22.1", + "bytes", + "encoding_rs", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-rustls", + "hyper-tls", + "hyper-util", + "js-sys", + "log", + "mime", + "mime_guess", + "native-tls", + "percent-encoding", + "pin-project-lite", + "rustls-pki-types", + "serde", + "serde_json", + "sync_wrapper", + "tokio", + "tokio-native-tls", + "tokio-util", + "tower", + "tower-http", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "wasm-streams", + "web-sys", +] + +[[package]] +name = "rfc6979" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2" +dependencies = [ + "hmac", + "subtle", +] + +[[package]] +name = "ring" +version = "0.17.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" +dependencies = [ + "cc", + "cfg-if", + "getrandom 0.2.16", + "libc", + "untrusted", + "windows-sys 0.52.0", +] + +[[package]] +name = "rsa" +version = "0.9.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8573f03f5883dcaebdfcf4725caa1ecb9c15b2ef50c43a07b816e06799bb12d" +dependencies = [ + "const-oid", + "digest", + "num-bigint-dig", + "num-integer", + "num-traits", + "pkcs1", + "pkcs8", + "rand_core 0.6.4", + "signature", + "spki", + "subtle", + "zeroize", +] + [[package]] name = "rstest" version = "0.18.2" @@ -2520,6 +3206,39 @@ dependencies = [ "windows-sys 0.61.1", ] +[[package]] +name = "rustls" +version = "0.23.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "533f54bc6a7d4f647e46ad909549eda97bf5afc1585190ef692b4286b198bd8f" +dependencies = [ + "once_cell", + "rustls-pki-types", + "rustls-webpki", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls-pki-types" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "708c0f9d5f54ba0272468c1d306a52c495b31fa155e91bc25371e6df7996908c" +dependencies = [ + "zeroize", +] + +[[package]] +name = "rustls-webpki" +version = "0.103.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ffdfa2f5286e2247234e03f680868ac2815974dc39e00ea15adc445d0aafe52" +dependencies = [ + "ring", + "rustls-pki-types", + "untrusted", +] + [[package]] name = "rustversion" version = "1.0.22" @@ -2606,6 +3325,20 @@ version = "3.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "490dcfcbfef26be6800d11870ff2df8774fa6e86d047e3e8c8a76b25655e41ca" +[[package]] +name = "sec1" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc" +dependencies = [ + "base16ct", + "der", + "generic-array", + "pkcs8", + "subtle", + "zeroize", +] + [[package]] name = "secrecy" version = "0.8.0" @@ -2615,6 +3348,19 @@ dependencies = [ "zeroize", ] +[[package]] +name = "security-framework" +version = "2.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" +dependencies = [ + "bitflags 2.9.4", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + [[package]] name = "security-framework-sys" version = "2.15.0" @@ -2689,8 +3435,10 @@ dependencies = [ "flate2", "git2", "glob", + "http", "if_chain", "ignore", + "imagesize", "indicatif", "insta", "itertools", @@ -2702,6 +3450,7 @@ dependencies = [ "mac-process-info", "magic_string", "mockito", + "objectstore-client", "open", "openssl-probe", "parking_lot", @@ -2722,10 +3471,12 @@ dependencies = [ "serde_json", "serial_test", "sha1_smol", + "sha2", "sourcemap", "symbolic", "tempfile", "thiserror 1.0.69", + "tokio", "trycmd", "url", "uuid", @@ -2906,6 +3657,16 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" +[[package]] +name = "signature" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" +dependencies = [ + "digest", + "rand_core 0.6.4", +] + [[package]] name = "simd-adler32" version = "0.3.7" @@ -2918,6 +3679,18 @@ version = "2.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbbb5d9659141646ae647b42fe094daf6c6192d1620870b449d9557f748b2daa" +[[package]] +name = "simple_asn1" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "297f631f50729c8c99b84667867963997ec0b50f32b2a7dbcab828ef0541e8bb" +dependencies = [ + "num-bigint", + "num-traits", + "thiserror 2.0.17", + "time", +] + [[package]] name = "siphasher" version = "1.0.1" @@ -2996,6 +3769,22 @@ dependencies = [ "url", ] +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" + +[[package]] +name = "spki" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" +dependencies = [ + "base64ct", + "der", +] + [[package]] name = "stable_deref_trait" version = "1.2.0" @@ -3138,6 +3927,15 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "sync_wrapper" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" +dependencies = [ + "futures-core", +] + [[package]] name = "synstructure" version = "0.13.2" @@ -3149,6 +3947,27 @@ dependencies = [ "syn", ] +[[package]] +name = "system-configuration" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" +dependencies = [ + "bitflags 2.9.4", + "core-foundation", + "system-configuration-sys", +] + +[[package]] +name = "system-configuration-sys" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "tap" version = "1.0.1" @@ -3294,6 +4113,26 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "tokio-native-tls" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" +dependencies = [ + "native-tls", + "tokio", +] + +[[package]] +name = "tokio-rustls" +version = "0.26.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61" +dependencies = [ + "rustls", + "tokio", +] + [[package]] name = "tokio-util" version = "0.7.16" @@ -3336,6 +4175,51 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801" +[[package]] +name = "tower" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9" +dependencies = [ + "futures-core", + "futures-util", + "pin-project-lite", + "sync_wrapper", + "tokio", + "tower-layer", + "tower-service", +] + +[[package]] +name = "tower-http" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4e6559d53cc268e5031cd8429d05415bc4cb4aefc4aa5d6cc35fbf5b924a1f8" +dependencies = [ + "bitflags 2.9.4", + "bytes", + "futures-util", + "http", + "http-body", + "iri-string", + "pin-project-lite", + "tower", + "tower-layer", + "tower-service", +] + +[[package]] +name = "tower-layer" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" + +[[package]] +name = "tower-service" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" + [[package]] name = "tracing" version = "0.1.41" @@ -3367,6 +4251,12 @@ dependencies = [ "once_cell", ] +[[package]] +name = "try-lock" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" + [[package]] name = "trycmd" version = "0.14.21" @@ -3404,6 +4294,12 @@ dependencies = [ "libc", ] +[[package]] +name = "unicase" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbc4bc3a9f746d862c45cb89d705aa10f187bb96c76001afab07a0d35ce60142" + [[package]] name = "unicode-id-start" version = "1.4.0" @@ -3428,6 +4324,12 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4a1a07cc7db3810833284e8d372ccdc6da29741639ecc70c9ec107df0fa6154c" +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + [[package]] name = "url" version = "2.5.7" @@ -3508,6 +4410,15 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "want" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +dependencies = [ + "try-lock", +] + [[package]] name = "wasi" version = "0.11.1+wasi-snapshot-preview1" @@ -3540,9 +4451,9 @@ checksum = "b8dad83b4f25e74f184f64c43b150b91efe7647395b42289f38e50566d82855b" [[package]] name = "wasm-bindgen" -version = "0.2.104" +version = "0.2.112" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1da10c01ae9f1ae40cbfac0bac3b1e724b320abfcf52229f80b547c0d250e2d" +checksum = "05d7d0fce354c88b7982aec4400b3e7fcf723c32737cef571bd165f7613557ee" dependencies = [ "cfg-if", "once_cell", @@ -3552,24 +4463,24 @@ dependencies = [ ] [[package]] -name = "wasm-bindgen-backend" -version = "0.2.104" +name = "wasm-bindgen-futures" +version = "0.4.62" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "671c9a5a66f49d8a47345ab942e2cb93c7d1d0339065d4f8139c486121b43b19" +checksum = "ee85afca410ac4abba5b584b12e77ea225db6ee5471d0aebaae0861166f9378a" dependencies = [ - "bumpalo", - "log", - "proc-macro2", - "quote", - "syn", - "wasm-bindgen-shared", + "cfg-if", + "futures-util", + "js-sys", + "once_cell", + "wasm-bindgen", + "web-sys", ] [[package]] name = "wasm-bindgen-macro" -version = "0.2.104" +version = "0.2.112" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ca60477e4c59f5f2986c50191cd972e3a50d8a95603bc9434501cf156a9a119" +checksum = "55839b71ba921e4f75b674cb16f843f4b1f3b26ddfcb3454de1cf65cc021ec0f" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -3577,26 +4488,39 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.104" +version = "0.2.112" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f07d2f20d4da7b26400c9f4a0511e6e0345b040694e8a75bd41d578fa4421d7" +checksum = "caf2e969c2d60ff52e7e98b7392ff1588bffdd1ccd4769eba27222fd3d621571" dependencies = [ + "bumpalo", "proc-macro2", "quote", "syn", - "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.104" +version = "0.2.112" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bad67dc8b2a1a6e5448428adec4c3e84c43e561d8c9ee8a9e5aabeb193ec41d1" +checksum = "0861f0dcdf46ea819407495634953cdcc8a8c7215ab799a7a7ce366be71c7b30" dependencies = [ "unicode-ident", ] +[[package]] +name = "wasm-streams" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d1ec4f6517c9e11ae630e200b2b65d193279042e28edd4a2cda233e46670bbb" +dependencies = [ + "futures-util", + "js-sys", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + [[package]] name = "wasmparser" version = "0.214.0" @@ -3623,9 +4547,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.81" +version = "0.3.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9367c417a924a74cae129e6a2ae3b47fabb1f8995595ab474029da749a8be120" +checksum = "10053fbf9a374174094915bbce141e87a6bf32ecd9a002980db4b638405e8962" dependencies = [ "js-sys", "wasm-bindgen", @@ -3694,8 +4618,8 @@ dependencies = [ "windows-implement", "windows-interface", "windows-link 0.2.0", - "windows-result", - "windows-strings", + "windows-result 0.4.0", + "windows-strings 0.5.0", ] [[package]] @@ -3732,6 +4656,26 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "45e46c0661abb7180e7b9c281db115305d49ca1709ab8242adf09666d2173c65" +[[package]] +name = "windows-registry" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b8a9ed28765efc97bbc954883f4e6796c33a06546ebafacbabee9696967499e" +dependencies = [ + "windows-link 0.1.3", + "windows-result 0.3.4", + "windows-strings 0.4.2", +] + +[[package]] +name = "windows-result" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6" +dependencies = [ + "windows-link 0.1.3", +] + [[package]] name = "windows-result" version = "0.4.0" @@ -3741,6 +4685,15 @@ dependencies = [ "windows-link 0.2.0", ] +[[package]] +name = "windows-strings" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57" +dependencies = [ + "windows-link 0.1.3", +] + [[package]] name = "windows-strings" version = "0.5.0" diff --git a/Cargo.toml b/Cargo.toml index 93d064eefe..576e05cb89 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -33,8 +33,10 @@ flate2 = { version = "1.0.25", default-features = false, features = [ ] } git2 = { version = "0.20.4", default-features = false } glob = "0.3.1" +http = "1.4.0" if_chain = "1.0.2" ignore = "0.4.20" +imagesize = "0.14.0" # Do not update due to https://github.com/console-rs/indicatif/issues/317 and https://github.com/getsentry/sentry-cli/pull/1055 indicatif = "0.14.0" itertools = "0.10.5" @@ -42,6 +44,7 @@ java-properties = "2.0.0" lazy_static = "1.4.0" libc = "0.2.139" log = { version = "0.4.17", features = ["std"] } +objectstore-client = { version = "0.0.19" , default-features = false, features = ["native-tls"] } open = "3.2.0" parking_lot = "0.12.1" percent-encoding = "2.2.0" @@ -62,9 +65,11 @@ sentry = { version = "0.46.0", default-features = false, features = [ serde = { version = "1.0.152", features = ["derive"] } serde_json = "1.0.93" sha1_smol = { version = "1.0.0", features = ["serde", "std"] } +sha2 = "0.10.9" sourcemap = { version = "9.3.0", features = ["ram_bundle"] } symbolic = { version = "12.13.3", features = ["debuginfo-serde", "il2cpp"] } thiserror = "1.0.38" +tokio = { version = "1.47", features = ["rt"] } url = "2.3.1" uuid = { version = "1.3.0", features = ["v4", "serde"] } walkdir = "2.3.2" diff --git a/src/api/data_types/mod.rs b/src/api/data_types/mod.rs index 0a8b5e1d3b..8f7d5dc661 100644 --- a/src/api/data_types/mod.rs +++ b/src/api/data_types/mod.rs @@ -2,6 +2,8 @@ mod chunking; mod deploy; +mod snapshots; pub use self::chunking::*; pub use self::deploy::*; +pub use self::snapshots::*; diff --git a/src/api/data_types/snapshots.rs b/src/api/data_types/snapshots.rs new file mode 100644 index 0000000000..40d73354c6 --- /dev/null +++ b/src/api/data_types/snapshots.rs @@ -0,0 +1,30 @@ +//! Data types for the snapshots API. + +use std::collections::HashMap; + +use serde::{Deserialize, Serialize}; + +/// Response from the create snapshot endpoint. +#[derive(Debug, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct CreateSnapshotResponse { + pub artifact_id: String, + pub image_count: u64, +} + +// Keep in sync with https://github.com/getsentry/sentry/blob/master/src/sentry/preprod/snapshots/manifest.py +/// Manifest describing a set of snapshot images for an app. +#[derive(Debug, Serialize)] +pub struct SnapshotsManifest { + pub app_id: String, + pub images: HashMap, +} + +// Keep in sync with https://github.com/getsentry/sentry/blob/master/src/sentry/preprod/snapshots/manifest.py +/// Metadata for a single image in a snapshot manifest. +#[derive(Debug, Serialize)] +pub struct ImageMetadata { + pub image_file_name: String, + pub width: u32, + pub height: u32, +} diff --git a/src/api/mod.rs b/src/api/mod.rs index fa60a59607..df57300b16 100644 --- a/src/api/mod.rs +++ b/src/api/mod.rs @@ -451,6 +451,14 @@ impl AuthenticatedApi<'_> { self.api.request(method, url, None) } + /// Returns the auth info for use in external service authorization. + pub fn auth(&self) -> &Auth { + self.api + .config + .get_auth() + .expect("AuthenticatedApi can only be constructed when auth exists") + } + // High-level method implementations /// Performs an API request to verify the authentication status of the @@ -969,6 +977,35 @@ impl AuthenticatedApi<'_> { } Ok(rv) } + + /// Creates a preprod snapshot artifact for the given project. + pub fn create_preprod_snapshot( + &self, + org: &str, + project: &str, + body: &S, + ) -> ApiResult { + let path = format!( + "/projects/{}/{}/preprodartifacts/snapshots/", + PathArg(org), + PathArg(project) + ); + self.post(&path, body) + } + + /// Fetches upload options for snapshots. + pub fn fetch_snapshots_upload_options( + &self, + org: &str, + project: &str, + ) -> ApiResult { + let path = format!( + "/projects/{}/{}/preprodartifacts/snapshots/upload-options/", + PathArg(org), + PathArg(project) + ); + self.get(&path)?.convert() + } } /// Available datasets for fetching organization events @@ -1984,3 +2021,19 @@ pub struct LogEntry { pub timestamp: String, pub message: Option, } + +/// Upload options returned by the snapshots upload-options endpoint. +#[derive(Debug, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SnapshotsUploadOptions { + pub objectstore: ObjectstoreUploadOptions, +} + +/// Objectstore configuration for file uploads. +#[derive(Debug, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct ObjectstoreUploadOptions { + pub url: String, + pub scopes: Vec<(String, String)>, + pub expiration_policy: String, +} diff --git a/src/commands/build/mod.rs b/src/commands/build/mod.rs index 72b2216b78..d302aa0791 100644 --- a/src/commands/build/mod.rs +++ b/src/commands/build/mod.rs @@ -3,10 +3,12 @@ use clap::{ArgMatches, Command}; use crate::utils::args::ArgExt as _; +pub mod snapshots; pub mod upload; macro_rules! each_subcommand { ($mac:ident) => { + $mac!(snapshots); $mac!(upload); }; } diff --git a/src/commands/build/snapshots.rs b/src/commands/build/snapshots.rs new file mode 100644 index 0000000000..2ded836073 --- /dev/null +++ b/src/commands/build/snapshots.rs @@ -0,0 +1,282 @@ +use std::collections::HashMap; +use std::fs; +use std::path::{Path, PathBuf}; +use std::str::FromStr as _; + +use anyhow::{Context as _, Result}; +use clap::{Arg, ArgMatches, Command}; +use console::style; +use log::{debug, info, warn}; +use objectstore_client::{ClientBuilder, ExpirationPolicy, Usecase}; +use secrecy::ExposeSecret as _; +use sha2::{Digest as _, Sha256}; +use walkdir::WalkDir; + +use crate::api::{Api, CreateSnapshotResponse, ImageMetadata, SnapshotsManifest}; +use crate::config::{Auth, Config}; +use crate::utils::args::ArgExt as _; + +const EXPERIMENTAL_WARNING: &str = + "[EXPERIMENTAL] The \"build snapshots\" command is experimental. \ + The command is subject to breaking changes, including removal, in any Sentry CLI release."; + +const IMAGE_EXTENSIONS: &[&str] = &["png", "jpg", "jpeg"]; + +pub fn make_command(command: Command) -> Command { + command + .about("[EXPERIMENTAL] Upload build snapshots to a project.") + .long_about(format!( + "Upload build snapshots to a project.\n\n{EXPERIMENTAL_WARNING}" + )) + .org_arg() + .project_arg(false) + .arg( + Arg::new("path") + .value_name("PATH") + .help("The path to the folder containing images to upload.") + .required(true), + ) + .arg( + Arg::new("app_id") + .long("app-id") + .value_name("APP_ID") + .help("The application identifier.") + .required(true), + ) +} + +struct ImageInfo { + path: PathBuf, + relative_path: PathBuf, + width: u32, + height: u32, +} + +pub fn execute(matches: &ArgMatches) -> Result<()> { + eprintln!("{EXPERIMENTAL_WARNING}"); + + let config = Config::current(); + let org = config.get_org(matches)?; + let project = config.get_project(matches)?; + + let path = matches + .get_one::("path") + .expect("path argument is required"); + let app_id = matches + .get_one::("app_id") + .expect("app_id argument is required"); + let dir_path = Path::new(path); + if !dir_path.is_dir() { + anyhow::bail!("Path is not a directory: {}", dir_path.display()); + } + + debug!("Scanning for images in: {}", dir_path.display()); + debug!("Organization: {org}"); + debug!("Project: {project}"); + + // Collect image files and read their dimensions + let images = collect_images(dir_path); + if images.is_empty() { + println!("{} No image files found", style("!").yellow()); + return Ok(()); + } + + println!( + "{} Found {} image {}", + style(">").dim(), + style(images.len()).yellow(), + if images.len() == 1 { "file" } else { "files" } + ); + + // Upload image files to objectstore + println!( + "{} Uploading {} image {}", + style(">").dim(), + style(images.len()).yellow(), + if images.len() == 1 { "file" } else { "files" } + ); + let manifest_entries = upload_images(images, &org, &project)?; + + // Build manifest from discovered images + let manifest = SnapshotsManifest { + app_id: app_id.clone(), + images: manifest_entries, + }; + + // POST manifest to API + println!("{} Creating snapshot...", style(">").dim()); + let api = Api::current(); + let response: CreateSnapshotResponse = api + .authenticated()? + .create_preprod_snapshot(&org, &project, &manifest)? + .convert()?; + + println!( + "{} Created snapshot {} with {} {}", + style(">").dim(), + style(&response.artifact_id).yellow(), + style(response.image_count).yellow(), + if response.image_count == 1 { + "image" + } else { + "images" + } + ); + + Ok(()) +} + +fn collect_images(dir: &Path) -> Vec { + WalkDir::new(dir) + .follow_links(true) + .into_iter() + .filter_entry(|e| !is_hidden(dir, e.path())) + .filter_map(|res| match res { + Ok(entry) => Some(entry), + Err(err) => { + warn!("Failed to access file during directory walk: {err}"); + None + } + }) + .filter(|entry| entry.file_type().is_file()) + .filter(|entry| is_image_file(entry.path())) + .filter_map(|entry| collect_image_info(dir, entry.path())) + .collect() +} + +/// Builds [`ImageInfo`] for a discovered image path during snapshot collection. +/// +/// Returns `Some(ImageInfo)` when the image dimensions can be parsed, or `None` +/// when the file should be skipped (e.g. when dimensions cannot be determined). +fn collect_image_info(dir: &Path, path: &Path) -> Option { + let (width, height) = match imagesize::size(path) { + Ok(dims) => (dims.width as u32, dims.height as u32), + Err(err) => { + warn!("Could not read dimensions from {}: {err}", path.display()); + return None; + } + }; + let relative = path.strip_prefix(dir).unwrap_or(path).to_path_buf(); + + Some(ImageInfo { + path: path.to_path_buf(), + relative_path: relative, + width, + height, + }) +} + +fn compute_sha256_hash(data: &[u8]) -> String { + let mut hasher = Sha256::new(); + hasher.update(data); + let result = hasher.finalize(); + format!("{result:x}") +} + +fn is_hidden(root: &Path, path: &Path) -> bool { + path != root + && path + .file_name() + .map(|name| name.to_string_lossy().starts_with('.')) + .unwrap_or(false) +} + +fn is_image_file(path: &Path) -> bool { + path.extension() + .and_then(|ext| ext.to_str()) + .map(|ext| IMAGE_EXTENSIONS.iter().any(|e| ext.eq_ignore_ascii_case(e))) + .unwrap_or(false) +} + +fn upload_images( + images: Vec, + org: &str, + project: &str, +) -> Result> { + let api = Api::current(); + let authenticated_api = api.authenticated()?; + let options = authenticated_api.fetch_snapshots_upload_options(org, project)?; + + let expiration = ExpirationPolicy::from_str(&options.objectstore.expiration_policy) + .context("Failed to parse expiration policy from upload options")?; + + let client = ClientBuilder::new(options.objectstore.url) + .token({ + // TODO: replace with auth from `ObjectstoreUploadOptions` when appropriate + let auth = match authenticated_api.auth() { + Auth::Token(token) => token.raw().expose_secret().to_owned(), + }; + auth + }) + .build()?; + + let mut scope = Usecase::new("preprod").scope(); + for (key, value) in &options.objectstore.scopes { + scope = scope.push(key, value); + } + let session = scope.session(&client)?; + + let runtime = tokio::runtime::Builder::new_current_thread() + .enable_all() + .build() + .context("Failed to create tokio runtime")?; + + let mut many_builder = session.many(); + let mut manifest_entries = HashMap::new(); + let image_count = images.len(); + + for image in images { + debug!("Processing image: {}", image.path.display()); + + let contents = fs::read(&image.path) + .with_context(|| format!("Failed to read image: {}", image.path.display()))?; + let hash = compute_sha256_hash(&contents); + + info!("Queueing {} as {hash}", image.relative_path.display()); + + many_builder = many_builder.push( + session + .put(contents) + .key(&hash) + .expiration_policy(expiration), + ); + + let image_file_name = image + .relative_path + .file_name() + .unwrap_or_default() + .to_string_lossy() + .into_owned(); + manifest_entries.insert( + hash, + ImageMetadata { + image_file_name, + width: image.width, + height: image.height, + }, + ); + } + + let result = runtime.block_on(async { many_builder.send().error_for_failures().await }); + + match result { + Ok(()) => { + println!( + "{} Uploaded {} image {}", + style(">").dim(), + style(image_count).yellow(), + if image_count == 1 { "file" } else { "files" } + ); + Ok(manifest_entries) + } + Err(errors) => { + eprintln!("There were errors uploading images:"); + let mut error_count = 0; + for error in errors { + eprintln!(" {}", style(error).red()); + error_count += 1; + } + anyhow::bail!("Failed to upload {error_count} out of {image_count} images") + } + } +} diff --git a/tests/integration/_cases/build/build-help.trycmd b/tests/integration/_cases/build/build-help.trycmd index ca926a2b6b..a04443131c 100644 --- a/tests/integration/_cases/build/build-help.trycmd +++ b/tests/integration/_cases/build/build-help.trycmd @@ -6,8 +6,9 @@ Manage builds. Usage: sentry-cli[EXE] build [OPTIONS] Commands: - upload Upload builds to a project. - help Print this message or the help of the given subcommand(s) + snapshots [EXPERIMENTAL] Upload build snapshots to a project. + upload Upload builds to a project. + help Print this message or the help of the given subcommand(s) Options: -o, --org The organization ID or slug. diff --git a/tests/integration/_cases/build/build-snapshots-help.trycmd b/tests/integration/_cases/build/build-snapshots-help.trycmd new file mode 100644 index 0000000000..4eda1521de --- /dev/null +++ b/tests/integration/_cases/build/build-snapshots-help.trycmd @@ -0,0 +1,45 @@ +``` +$ sentry-cli build snapshots --help +? success +Upload build snapshots to a project. + +[EXPERIMENTAL] The "build snapshots" command is experimental. The command is subject to breaking +changes, including removal, in any Sentry CLI release. + +Usage: sentry-cli[EXE] build snapshots [OPTIONS] --app-id + +Arguments: + + The path to the folder containing images to upload. + +Options: + -o, --org + The organization ID or slug. + + --header + Custom headers that should be attached to all requests + in key:value format. + + -p, --project + The project ID or slug. + + --app-id + The application identifier. + + --auth-token + Use the given Sentry auth token. + + --log-level + Set the log output verbosity. [possible values: trace, debug, info, warn, error] + + --quiet + Do not print any output while preserving correct exit code. This flag is currently + implemented only for selected subcommands. + + [aliases: --silent] + + -h, --help + Print help (see a summary with '-h') + +``` + diff --git a/tests/integration/build/mod.rs b/tests/integration/build/mod.rs index f73e2c8f36..239def73c5 100644 --- a/tests/integration/build/mod.rs +++ b/tests/integration/build/mod.rs @@ -6,3 +6,8 @@ mod upload; fn command_build_help() { TestManager::new().register_trycmd_test("build/build-help.trycmd"); } + +#[test] +fn command_build_snapshots_help() { + TestManager::new().register_trycmd_test("build/build-snapshots-help.trycmd"); +}