diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml new file mode 100644 index 0000000..89fc496 --- /dev/null +++ b/.github/workflows/ci.yaml @@ -0,0 +1,49 @@ +name: CI Pipeline + +on: + push: + branches: + - main + paths-ignore: + - "README.md" + pull_request: + branches: + - main + paths-ignore: + - "README.md" + +concurrency: + group: "${{ github.workflow }} @ ${{ github.head_ref || github.ref }}" + cancel-in-progress: true + +env: + RUST_VERSION: 1.81.0 + CARGO_TERM_COLOR: always + +jobs: + format-and-clippy: + uses: ./.github/workflows/format-workflow.yaml + secrets: inherit + tests: + uses: ./.github/workflows/tests-workflow.yaml + secrets: inherit + build: + runs-on: ubuntu-latest + needs: [format-and-clippy, tests] + strategy: + fail-fast: true + matrix: + include: + - name: "library" + path: "." + - name: "client" + path: "snap-kube-client" + steps: + - uses: actions/checkout@v4 + - uses: actions-rust-lang/setup-rust-toolchain@v1 + with: + components: rustfmt, clippy + toolchain: ${{ env.RUST_VERSION }} + - name: Cargo Build ${{ matrix.name }} + run: cargo build --verbose + working-directory: ${{ matrix.path }} diff --git a/.github/workflows/format-workflow.yaml b/.github/workflows/format-workflow.yaml new file mode 100644 index 0000000..dab76a1 --- /dev/null +++ b/.github/workflows/format-workflow.yaml @@ -0,0 +1,22 @@ +name: Format & Clippy Pipeline + +on: + workflow_call: + +env: + RUST_VERSION: 1.81.0 + +jobs: + format-and-clippy: + name: Cargo format & Clippy + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions-rust-lang/setup-rust-toolchain@v1 + with: + components: rustfmt, clippy + toolchain: ${{ env.RUST_VERSION }} + - name: Rustfmt Check + uses: actions-rust-lang/rustfmt@v1 + - name: Lint with Clippy + run: cargo clippy --all diff --git a/.github/workflows/tests-workflow.yaml b/.github/workflows/tests-workflow.yaml new file mode 100644 index 0000000..82adc44 --- /dev/null +++ b/.github/workflows/tests-workflow.yaml @@ -0,0 +1,24 @@ +name: Tests Pipeline + +on: + workflow_call: + +env: + RUST_VERSION: 1.81.0 + +jobs: + tests: + name: Run Tests + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions-rust-lang/setup-rust-toolchain@v1 + with: + components: rustfmt, clippy + toolchain: ${{ env.RUST_VERSION }} + - name: Install cargo-nextest + uses: baptiste0928/cargo-install@v3 + with: + crate: cargo-nextest + - name: Run tests + run: cargo nextest run --all diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ed26a7c --- /dev/null +++ b/.gitignore @@ -0,0 +1,21 @@ +# Generated by Cargo +# will have compiled files and executables +debug/ +target/ + +# These are backup files generated by rustfmt +**/*.rs.bk + +# Added by cargo +/target +.idea/ +.DS_Store +.zed/ + + +# Other +script.sh +*.cast +*.gif +data/ +*.tgz diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..80fb91a --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,2942 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "addr2line" +version = "0.24.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler2" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" + +[[package]] +name = "ahash" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" +dependencies = [ + "cfg-if", + "getrandom", + "once_cell", + "version_check", + "zerocopy", +] + +[[package]] +name = "aho-corasick" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +dependencies = [ + "memchr", +] + +[[package]] +name = "allocator-api2" +version = "0.2.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c6cb57a04249c6480766f7f7cef5467412af1490f8d1e243141daddada3264f" + +[[package]] +name = "anstream" +version = "0.6.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64e15c1ab1f89faffbf04a634d5e1962e9074f2741eef6d97f3c4e322426d526" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bec1de6f59aedf83baf9ff929c98f2ad654b97c9510f4e70cf6f661d49fd5b1" + +[[package]] +name = "anstyle-parse" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb47de1e80c2b463c735db5b217a0ddc39d612e7ac9e2e96a5aed1f57616c1cb" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d36fc52c7f6c869915e99412912f22093507da8d9e942ceaf66fe4b7c14422a" +dependencies = [ + "windows-sys 0.52.0", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5bf74e1b6e971609db8ca7a9ce79fd5768ab6ae46441c572e46cf596f59e57f8" +dependencies = [ + "anstyle", + "windows-sys 0.52.0", +] + +[[package]] +name = "anyhow" +version = "1.0.90" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37bf3594c4c988a53154954629820791dde498571819ae4ca50ca811e060cc95" + +[[package]] +name = "async-broadcast" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20cd0e2e25ea8e5f7e9df04578dc6cf5c83577fd09b1a46aaf5c85e1c33f2a7e" +dependencies = [ + "event-listener", + "event-listener-strategy", + "futures-core", + "pin-project-lite", +] + +[[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 = "async-trait" +version = "0.1.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "721cae7de5c34fbb2acd27e21e6d2cf7b886dce0c27388d46c4e6c47ea4318dd" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "autocfg" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" + +[[package]] +name = "aws-config" +version = "1.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7198e6f03240fdceba36656d8be440297b6b82270325908c7381f37d826a74f6" +dependencies = [ + "aws-credential-types", + "aws-runtime", + "aws-sdk-sso", + "aws-sdk-ssooidc", + "aws-sdk-sts", + "aws-smithy-async", + "aws-smithy-http", + "aws-smithy-json", + "aws-smithy-runtime", + "aws-smithy-runtime-api", + "aws-smithy-types", + "aws-types", + "bytes", + "fastrand", + "hex", + "http 0.2.12", + "ring", + "time", + "tokio", + "tracing", + "url", + "zeroize", +] + +[[package]] +name = "aws-credential-types" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60e8f6b615cb5fc60a98132268508ad104310f0cfb25a1c22eee76efdf9154da" +dependencies = [ + "aws-smithy-async", + "aws-smithy-runtime-api", + "aws-smithy-types", + "zeroize", +] + +[[package]] +name = "aws-runtime" +version = "1.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a10d5c055aa540164d9561a0e2e74ad30f0dcf7393c3a92f6733ddf9c5762468" +dependencies = [ + "aws-credential-types", + "aws-sigv4", + "aws-smithy-async", + "aws-smithy-http", + "aws-smithy-runtime", + "aws-smithy-runtime-api", + "aws-smithy-types", + "aws-types", + "bytes", + "fastrand", + "http 0.2.12", + "http-body 0.4.6", + "once_cell", + "percent-encoding", + "pin-project-lite", + "tracing", + "uuid", +] + +[[package]] +name = "aws-sdk-ec2" +version = "1.78.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85202834a2ab5de58fd02e4d1322484549615beb2125acb8031cea244d482ac3" +dependencies = [ + "aws-credential-types", + "aws-runtime", + "aws-smithy-async", + "aws-smithy-http", + "aws-smithy-json", + "aws-smithy-query", + "aws-smithy-runtime", + "aws-smithy-runtime-api", + "aws-smithy-types", + "aws-smithy-xml", + "aws-types", + "fastrand", + "http 0.2.12", + "once_cell", + "regex-lite", + "tracing", +] + +[[package]] +name = "aws-sdk-sso" +version = "1.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dc2faec3205d496c7e57eff685dd944203df7ce16a4116d0281c44021788a7b" +dependencies = [ + "aws-credential-types", + "aws-runtime", + "aws-smithy-async", + "aws-smithy-http", + "aws-smithy-json", + "aws-smithy-runtime", + "aws-smithy-runtime-api", + "aws-smithy-types", + "aws-types", + "bytes", + "http 0.2.12", + "once_cell", + "regex-lite", + "tracing", +] + +[[package]] +name = "aws-sdk-ssooidc" +version = "1.47.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c93c241f52bc5e0476e259c953234dab7e2a35ee207ee202e86c0095ec4951dc" +dependencies = [ + "aws-credential-types", + "aws-runtime", + "aws-smithy-async", + "aws-smithy-http", + "aws-smithy-json", + "aws-smithy-runtime", + "aws-smithy-runtime-api", + "aws-smithy-types", + "aws-types", + "bytes", + "http 0.2.12", + "once_cell", + "regex-lite", + "tracing", +] + +[[package]] +name = "aws-sdk-sts" +version = "1.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b259429be94a3459fa1b00c5684faee118d74f9577cc50aebadc36e507c63b5f" +dependencies = [ + "aws-credential-types", + "aws-runtime", + "aws-smithy-async", + "aws-smithy-http", + "aws-smithy-json", + "aws-smithy-query", + "aws-smithy-runtime", + "aws-smithy-runtime-api", + "aws-smithy-types", + "aws-smithy-xml", + "aws-types", + "http 0.2.12", + "once_cell", + "regex-lite", + "tracing", +] + +[[package]] +name = "aws-sigv4" +version = "1.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc8db6904450bafe7473c6ca9123f88cc11089e41a025408f992db4e22d3be68" +dependencies = [ + "aws-credential-types", + "aws-smithy-http", + "aws-smithy-runtime-api", + "aws-smithy-types", + "bytes", + "form_urlencoded", + "hex", + "hmac", + "http 0.2.12", + "http 1.1.0", + "once_cell", + "percent-encoding", + "sha2", + "time", + "tracing", +] + +[[package]] +name = "aws-smithy-async" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62220bc6e97f946ddd51b5f1361f78996e704677afc518a4ff66b7a72ea1378c" +dependencies = [ + "futures-util", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "aws-smithy-http" +version = "0.60.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c8bc3e8fdc6b8d07d976e301c02fe553f72a39b7a9fea820e023268467d7ab6" +dependencies = [ + "aws-smithy-runtime-api", + "aws-smithy-types", + "bytes", + "bytes-utils", + "futures-core", + "http 0.2.12", + "http-body 0.4.6", + "once_cell", + "percent-encoding", + "pin-project-lite", + "pin-utils", + "tracing", +] + +[[package]] +name = "aws-smithy-json" +version = "0.60.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4683df9469ef09468dad3473d129960119a0d3593617542b7d52086c8486f2d6" +dependencies = [ + "aws-smithy-types", +] + +[[package]] +name = "aws-smithy-query" +version = "0.60.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2fbd61ceb3fe8a1cb7352e42689cec5335833cd9f94103a61e98f9bb61c64bb" +dependencies = [ + "aws-smithy-types", + "urlencoding", +] + +[[package]] +name = "aws-smithy-runtime" +version = "1.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a065c0fe6fdbdf9f11817eb68582b2ab4aff9e9c39e986ae48f7ec576c6322db" +dependencies = [ + "aws-smithy-async", + "aws-smithy-http", + "aws-smithy-runtime-api", + "aws-smithy-types", + "bytes", + "fastrand", + "h2", + "http 0.2.12", + "http-body 0.4.6", + "http-body 1.0.1", + "httparse", + "hyper 0.14.31", + "hyper-rustls 0.24.2", + "once_cell", + "pin-project-lite", + "pin-utils", + "rustls 0.21.12", + "tokio", + "tracing", +] + +[[package]] +name = "aws-smithy-runtime-api" +version = "1.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e086682a53d3aa241192aa110fa8dfce98f2f5ac2ead0de84d41582c7e8fdb96" +dependencies = [ + "aws-smithy-async", + "aws-smithy-types", + "bytes", + "http 0.2.12", + "http 1.1.0", + "pin-project-lite", + "tokio", + "tracing", + "zeroize", +] + +[[package]] +name = "aws-smithy-types" +version = "1.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147100a7bea70fa20ef224a6bad700358305f5dc0f84649c53769761395b355b" +dependencies = [ + "base64-simd", + "bytes", + "bytes-utils", + "futures-core", + "http 0.2.12", + "http 1.1.0", + "http-body 0.4.6", + "http-body 1.0.1", + "http-body-util", + "itoa", + "num-integer", + "pin-project-lite", + "pin-utils", + "ryu", + "serde", + "time", + "tokio", + "tokio-util", +] + +[[package]] +name = "aws-smithy-xml" +version = "0.60.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab0b0166827aa700d3dc519f72f8b3a91c35d0b8d042dc5d643a91e6f80648fc" +dependencies = [ + "xmlparser", +] + +[[package]] +name = "aws-types" +version = "1.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5221b91b3e441e6675310829fd8984801b772cb1546ef6c0e54dec9f1ac13fef" +dependencies = [ + "aws-credential-types", + "aws-smithy-async", + "aws-smithy-runtime-api", + "aws-smithy-types", + "rustc_version", + "tracing", +] + +[[package]] +name = "backoff" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b62ddb9cb1ec0a098ad4bbf9344d0713fa193ae1a80af55febcff2627b6a00c1" +dependencies = [ + "getrandom", + "instant", + "rand", +] + +[[package]] +name = "backtrace" +version = "0.3.74" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a" +dependencies = [ + "addr2line", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", + "windows-targets 0.52.6", +] + +[[package]] +name = "base64" +version = "0.21.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" + +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "base64-simd" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "339abbe78e73178762e23bea9dfd08e697eb3f3301cd4be981c0f78ba5859195" +dependencies = [ + "outref", + "vsimd", +] + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "bytes" +version = "1.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "428d9aa8fbc0670b7b8d6030a7fadd0f86151cae55e4dbbece15f3780a3dfaf3" + +[[package]] +name = "bytes-utils" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dafe3a8757b027e2be6e4e5601ed563c55989fcf1546e933c66c8eb3a058d35" +dependencies = [ + "bytes", + "either", +] + +[[package]] +name = "cc" +version = "1.1.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b16803a61b81d9eabb7eae2588776c4c1e584b738ede45fdbb4c972cec1e9945" +dependencies = [ + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "chrono" +version = "0.4.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401" +dependencies = [ + "num-traits", + "serde", +] + +[[package]] +name = "clap" +version = "4.5.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b97f376d85a664d5837dbae44bf546e6477a679ff6610010f17276f686d867e8" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.5.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19bc80abd44e4bed93ca373a0704ccbd1b710dc5749406201bb018272808dc54" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ac6a0c7b1a9e9a5186361f67dfa1b88213572f427fb9ab038efb2bd8c582dab" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "clap_lex" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1462739cb27611015575c0c11df5df7601141071f07518d56fcc1be504cbec97" + +[[package]] +name = "colorchoice" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3fd119d74b830634cea2a0f58bbd0d54540518a14397557951e79340abc28c0" + +[[package]] +name = "colored" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cbf2150cce219b664a8a70df7a1f933836724b503f8a413af9365b4dcc4d90b8" +dependencies = [ + "lazy_static", + "windows-sys 0.48.0", +] + +[[package]] +name = "concurrent-queue" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" +dependencies = [ + "crossbeam-utils", +] + +[[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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "cpufeatures" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "608697df725056feaccfa42cffdaeeec3fccc4ffc38358ecd19b243e716a78e0" +dependencies = [ + "libc", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "darling" +version = "0.20.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f63b86c8a8826a49b8c21f08a2d07338eec8d900540f8630dc76284be802989" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.20.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95133861a8032aaea082871032f5815eb9e98cef03fa916ab4500513994df9e5" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn", +] + +[[package]] +name = "darling_macro" +version = "0.20.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806" +dependencies = [ + "darling_core", + "quote", + "syn", +] + +[[package]] +name = "deranged" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" +dependencies = [ + "powerfmt", +] + +[[package]] +name = "diff" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8" + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", + "subtle", +] + +[[package]] +name = "downcast" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1435fa1053d8b2fbbe9be7e97eca7f33d37b28409959813daefc1446a14247f1" + +[[package]] +name = "dyn-clone" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d6ef0072f8a535281e4876be788938b528e9a1d43900b82c2569af7da799125" + +[[package]] +name = "educe" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d7bc049e1bd8cdeb31b68bbd586a9464ecf9f3944af3958a7a9d0f8b9799417" +dependencies = [ + "enum-ordinalize", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "either" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" + +[[package]] +name = "enum-ordinalize" +version = "4.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fea0dcfa4e54eeb516fe454635a95753ddd39acda650ce703031c6973e315dd5" +dependencies = [ + "enum-ordinalize-derive", +] + +[[package]] +name = "enum-ordinalize-derive" +version = "4.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d28318a75d4aead5c4db25382e8ef717932d0346600cacae6357eb5941bc5ff" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + +[[package]] +name = "event-listener" +version = "5.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6032be9bd27023a771701cc49f9f053c751055f71efb2e0ae5c15809093675ba" +dependencies = [ + "concurrent-queue", + "parking", + "pin-project-lite", +] + +[[package]] +name = "event-listener-strategy" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f214dc438f977e6d4e3500aaa277f5ad94ca83fbbd9b1a15713ce2344ccc5a1" +dependencies = [ + "event-listener", + "pin-project-lite", +] + +[[package]] +name = "fastrand" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8c02a5121d4ea3eb16a80748c74f5549a5665e4c21333c6098f283870fbdea6" + +[[package]] +name = "fluent-uri" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17c704e9dbe1ddd863da1e6ff3567795087b1eb201ce80d8fa81162e1516500d" +dependencies = [ + "bitflags 1.3.2", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "form_urlencoded" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "fragile" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c2141d6d6c8512188a7891b4b01590a45f6dac67afb4f255c4124dbb86d4eaa" + +[[package]] +name = "futures" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" + +[[package]] +name = "futures-io" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" + +[[package]] +name = "futures-macro" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "futures-sink" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" + +[[package]] +name = "futures-task" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" + +[[package]] +name = "futures-util" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "gimli" +version = "0.31.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" + +[[package]] +name = "h2" +version = "0.3.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81fe527a889e1532da5c525686d96d4c2e74cdd345badf8dfef9f6b39dd5f5e8" +dependencies = [ + "bytes", + "fnv", + "futures-core", + "futures-sink", + "futures-util", + "http 0.2.12", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "hashbrown" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" +dependencies = [ + "ahash", + "allocator-api2", +] + +[[package]] +name = "hashbrown" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e087f84d4f86bf4b218b927129862374b72199ae7d8657835f1e89000eea4fb" + +[[package]] +name = "headers" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "322106e6bd0cba2d5ead589ddb8150a13d7c4217cf80d7c4f682ca994ccc6aa9" +dependencies = [ + "base64 0.21.7", + "bytes", + "headers-core", + "http 1.1.0", + "httpdate", + "mime", + "sha1", +] + +[[package]] +name = "headers-core" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54b4a22553d4242c49fddb9ba998a99962b5cc6f22cb5a3482bec22522403ce4" +dependencies = [ + "http 1.1.0", +] + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "hermit-abi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest", +] + +[[package]] +name = "home" +version = "0.5.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5" +dependencies = [ + "windows-sys 0.52.0", +] + +[[package]] +name = "http" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21b9ddb458710bc376481b842f5da65cdf31522de232c1ca8146abce2a358258" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http-body" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" +dependencies = [ + "bytes", + "http 0.2.12", + "pin-project-lite", +] + +[[package]] +name = "http-body" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" +dependencies = [ + "bytes", + "http 1.1.0", +] + +[[package]] +name = "http-body-util" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "793429d76616a256bcb62c2a2ec2bed781c8307e797e2598c50010f2bee2544f" +dependencies = [ + "bytes", + "futures-util", + "http 1.1.0", + "http-body 1.0.1", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d71d3574edd2771538b901e6549113b4006ece66150fb69c0fb6d9a2adae946" + +[[package]] +name = "httpdate" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" + +[[package]] +name = "hyper" +version = "0.14.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c08302e8fa335b151b788c775ff56e7a03ae64ff85c548ee820fecb70356e85" +dependencies = [ + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "h2", + "http 0.2.12", + "http-body 0.4.6", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "socket2", + "tokio", + "tower-service", + "tracing", + "want", +] + +[[package]] +name = "hyper" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbbff0a806a4728c99295b254c8838933b5b082d75e3cb70c8dab21fdfbcfa9a" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "http 1.1.0", + "http-body 1.0.1", + "httparse", + "itoa", + "pin-project-lite", + "smallvec", + "tokio", + "want", +] + +[[package]] +name = "hyper-http-proxy" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d06dbdfbacf34d996c6fb540a71a684a7aae9056c71951163af8a8a4c07b9a4" +dependencies = [ + "bytes", + "futures-util", + "headers", + "http 1.1.0", + "hyper 1.5.0", + "hyper-rustls 0.27.3", + "hyper-util", + "pin-project-lite", + "rustls-native-certs 0.7.3", + "tokio", + "tokio-rustls 0.26.0", + "tower-service", +] + +[[package]] +name = "hyper-rustls" +version = "0.24.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec3efd23720e2049821a693cbc7e65ea87c72f1c58ff2f9522ff332b1491e590" +dependencies = [ + "futures-util", + "http 0.2.12", + "hyper 0.14.31", + "log", + "rustls 0.21.12", + "rustls-native-certs 0.6.3", + "tokio", + "tokio-rustls 0.24.1", +] + +[[package]] +name = "hyper-rustls" +version = "0.27.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08afdbb5c31130e3034af566421053ab03787c640246a446327f550d11bcb333" +dependencies = [ + "futures-util", + "http 1.1.0", + "hyper 1.5.0", + "hyper-util", + "log", + "rustls 0.23.15", + "rustls-native-certs 0.8.0", + "rustls-pki-types", + "tokio", + "tokio-rustls 0.26.0", + "tower-service", +] + +[[package]] +name = "hyper-timeout" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3203a961e5c83b6f5498933e78b6b263e208c197b63e9c6c53cc82ffd3f63793" +dependencies = [ + "hyper 1.5.0", + "hyper-util", + "pin-project-lite", + "tokio", + "tower-service", +] + +[[package]] +name = "hyper-util" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41296eb09f183ac68eec06e03cdbea2e759633d4067b2f6552fc2e009bcad08b" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "http 1.1.0", + "http-body 1.0.1", + "hyper 1.5.0", + "pin-project-lite", + "socket2", + "tokio", + "tower-service", + "tracing", +] + +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + +[[package]] +name = "idna" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" +dependencies = [ + "unicode-bidi", + "unicode-normalization", +] + +[[package]] +name = "indexmap" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "707907fe3c25f5424cce2cb7e1cbcafee6bdbe735ca90ef77c29e84591e5b9da" +dependencies = [ + "equivalent", + "hashbrown 0.15.0", +] + +[[package]] +name = "instant" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0242819d153cba4b4b05a5a8f2a7e9bbf97b6055b2a002b395c96b5ff3c0222" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "is_terminal_polyfill" +version = "1.70.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" + +[[package]] +name = "itoa" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" + +[[package]] +name = "json-patch" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b1fb8864823fad91877e6caea0baca82e49e8db50f8e5c9f9a453e27d3330fc" +dependencies = [ + "jsonptr", + "serde", + "serde_json", + "thiserror", +] + +[[package]] +name = "jsonpath-rust" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19d8fe85bd70ff715f31ce8c739194b423d79811a19602115d611a3ec85d6200" +dependencies = [ + "lazy_static", + "once_cell", + "pest", + "pest_derive", + "regex", + "serde_json", + "thiserror", +] + +[[package]] +name = "jsonptr" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c6e529149475ca0b2820835d3dce8fcc41c6b943ca608d32f35b449255e4627" +dependencies = [ + "fluent-uri", + "serde", + "serde_json", +] + +[[package]] +name = "k8s-openapi" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8847402328d8301354c94d605481f25a6bdc1ed65471fd96af8eca71141b13" +dependencies = [ + "base64 0.22.1", + "chrono", + "serde", + "serde-value", + "serde_json", +] + +[[package]] +name = "kube" +version = "0.96.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "efffeb3df0bd4ef3e5d65044573499c0e4889b988070b08c50b25b1329289a1f" +dependencies = [ + "k8s-openapi", + "kube-client", + "kube-core", + "kube-derive", + "kube-runtime", +] + +[[package]] +name = "kube-client" +version = "0.96.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8bf471ece8ff8d24735ce78dac4d091e9fcb8d74811aeb6b75de4d1c3f5de0f1" +dependencies = [ + "base64 0.22.1", + "bytes", + "chrono", + "either", + "futures", + "home", + "http 1.1.0", + "http-body 1.0.1", + "http-body-util", + "hyper 1.5.0", + "hyper-http-proxy", + "hyper-rustls 0.27.3", + "hyper-timeout", + "hyper-util", + "jsonpath-rust", + "k8s-openapi", + "kube-core", + "pem", + "rustls 0.23.15", + "rustls-pemfile 2.2.0", + "secrecy", + "serde", + "serde_json", + "serde_yaml", + "thiserror", + "tokio", + "tokio-util", + "tower", + "tower-http", + "tracing", +] + +[[package]] +name = "kube-core" +version = "0.96.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f42346d30bb34d1d7adc5c549b691bce7aa3a1e60254e68fab7e2d7b26fe3d77" +dependencies = [ + "chrono", + "form_urlencoded", + "http 1.1.0", + "json-patch", + "k8s-openapi", + "schemars", + "serde", + "serde-value", + "serde_json", + "thiserror", +] + +[[package]] +name = "kube-custom-resources-rs" +version = "2024.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56f7dde892ac9018d3af4a70b9e6a40ef7983149eef1cf6bece5b40be925ea63" +dependencies = [ + "k8s-openapi", + "kube", + "schemars", + "serde", + "serde_json", +] + +[[package]] +name = "kube-derive" +version = "0.96.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9364e04cc5e0482136c6ee8b7fb7551812da25802249f35b3def7aaa31e82ad" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "serde_json", + "syn", +] + +[[package]] +name = "kube-runtime" +version = "0.96.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3fbf1f6ffa98e65f1d2a9a69338bb60605d46be7edf00237784b89e62c9bd44" +dependencies = [ + "ahash", + "async-broadcast", + "async-stream", + "async-trait", + "backoff", + "educe", + "futures", + "hashbrown 0.14.5", + "json-patch", + "jsonptr", + "k8s-openapi", + "kube-client", + "parking_lot", + "pin-project", + "serde", + "serde_json", + "thiserror", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "libc" +version = "0.2.161" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9489c2807c139ffd9c1794f4af0ebe86a828db53ecdc7fea2111d0fed085d1" + +[[package]] +name = "lock_api" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" + +[[package]] +name = "memchr" +version = "2.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" + +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + +[[package]] +name = "miniz_oxide" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2d80299ef12ff69b16a84bb182e3b9df68b5a91574d3d4fa6e41b65deec4df1" +dependencies = [ + "adler2", +] + +[[package]] +name = "mio" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80e04d1dcff3aae0704555fe5fee3bcfaf3d1fdf8a7e521d5b9d2b42acb52cec" +dependencies = [ + "hermit-abi", + "libc", + "wasi", + "windows-sys 0.52.0", +] + +[[package]] +name = "mockall" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4c28b3fb6d753d28c20e826cd46ee611fda1cf3cde03a443a974043247c065a" +dependencies = [ + "cfg-if", + "downcast", + "fragile", + "mockall_derive", + "predicates", + "predicates-tree", +] + +[[package]] +name = "mockall_derive" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "341014e7f530314e9a1fdbc7400b244efea7122662c96bfa248c31da5bfb2020" +dependencies = [ + "cfg-if", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "nu-ansi-term" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" +dependencies = [ + "overload", + "winapi", +] + +[[package]] +name = "num-conv" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" + +[[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-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "object" +version = "0.36.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aedf0a2d09c573ed1d8d85b30c119153926a2b36dce0ab28322c09a117a4683e" +dependencies = [ + "memchr", +] + +[[package]] +name = "once_cell" +version = "1.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" + +[[package]] +name = "openssl-probe" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" + +[[package]] +name = "ordered-float" +version = "2.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68f19d67e5a2795c94e73e0bb1cc1a7edeb2e28efd39e2e1c9b7a40c1108b11c" +dependencies = [ + "num-traits", +] + +[[package]] +name = "outref" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4030760ffd992bef45b0ae3f10ce1aba99e33464c90d14dd7c039884963ddc7a" + +[[package]] +name = "overload" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" + +[[package]] +name = "parking" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" + +[[package]] +name = "parking_lot" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-targets 0.52.6", +] + +[[package]] +name = "pem" +version = "3.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e459365e590736a54c3fa561947c84837534b8e9af6fc5bf781307e82658fae" +dependencies = [ + "base64 0.22.1", + "serde", +] + +[[package]] +name = "percent-encoding" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" + +[[package]] +name = "pest" +version = "2.7.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "879952a81a83930934cbf1786752d6dedc3b1f29e8f8fb2ad1d0a36f377cf442" +dependencies = [ + "memchr", + "thiserror", + "ucd-trie", +] + +[[package]] +name = "pest_derive" +version = "2.7.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d214365f632b123a47fd913301e14c946c61d1c183ee245fa76eb752e59a02dd" +dependencies = [ + "pest", + "pest_generator", +] + +[[package]] +name = "pest_generator" +version = "2.7.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb55586734301717aea2ac313f50b2eb8f60d2fc3dc01d190eefa2e625f60c4e" +dependencies = [ + "pest", + "pest_meta", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "pest_meta" +version = "2.7.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b75da2a70cf4d9cb76833c990ac9cd3923c9a8905a8929789ce347c84564d03d" +dependencies = [ + "once_cell", + "pest", + "sha2", +] + +[[package]] +name = "pin-project" +version = "1.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf123a161dde1e524adf36f90bc5d8d3462824a9c43553ad07a8183161189ec" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4502d8515ca9f32f1fb543d987f63d95a14934883db45bdb48060b6b69257f8" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + +[[package]] +name = "ppv-lite86" +version = "0.2.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "predicates" +version = "3.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e9086cc7640c29a356d1a29fd134380bee9d8f79a17410aa76e7ad295f42c97" +dependencies = [ + "anstyle", + "predicates-core", +] + +[[package]] +name = "predicates-core" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae8177bee8e75d6846599c6b9ff679ed51e882816914eec639944d7c9aa11931" + +[[package]] +name = "predicates-tree" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41b740d195ed3166cd147c8047ec98db0e22ec019eb8eeb76d343b795304fb13" +dependencies = [ + "predicates-core", + "termtree", +] + +[[package]] +name = "pretty_assertions" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ae130e2f271fbc2ac3a40fb1d07180839cdbbe443c7a27e1e3c13c5cac0116d" +dependencies = [ + "diff", + "yansi", +] + +[[package]] +name = "proc-macro2" +version = "1.0.88" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c3a7fc5db1e57d5a779a352c8cdb57b29aa4c40cc69c3a68a7fedc815fbf2f9" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[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", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + +[[package]] +name = "redox_syscall" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b6dfecf2c74bce2466cabf93f6664d6998a69eb21e39f4207930065b27b771f" +dependencies = [ + "bitflags 2.6.0", +] + +[[package]] +name = "regex" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38200e5ee88914975b69f657f0801b6f6dccafd44fd9326302a4aaeecfacb1d8" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "368758f23274712b504848e9d5a6f010445cc8b87a7cdb4d7cbee666c1288da3" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-lite" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53a49587ad06b26609c52e423de037e7f57f20d53535d66e08c695f347df952a" + +[[package]] +name = "regex-syntax" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" + +[[package]] +name = "ring" +version = "0.17.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d" +dependencies = [ + "cc", + "cfg-if", + "getrandom", + "libc", + "spin", + "untrusted", + "windows-sys 0.52.0", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" + +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + +[[package]] +name = "rustls" +version = "0.21.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f56a14d1f48b391359b22f731fd4bd7e43c97f3c50eee276f3aa09c94784d3e" +dependencies = [ + "log", + "ring", + "rustls-webpki 0.101.7", + "sct", +] + +[[package]] +name = "rustls" +version = "0.23.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fbb44d7acc4e873d613422379f69f237a1b141928c02f6bc6ccfddddc2d7993" +dependencies = [ + "log", + "once_cell", + "ring", + "rustls-pki-types", + "rustls-webpki 0.102.8", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls-native-certs" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9aace74cb666635c918e9c12bc0d348266037aa8eb599b5cba565709a8dff00" +dependencies = [ + "openssl-probe", + "rustls-pemfile 1.0.4", + "schannel", + "security-framework", +] + +[[package]] +name = "rustls-native-certs" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5bfb394eeed242e909609f56089eecfe5fda225042e8b171791b9c95f5931e5" +dependencies = [ + "openssl-probe", + "rustls-pemfile 2.2.0", + "rustls-pki-types", + "schannel", + "security-framework", +] + +[[package]] +name = "rustls-native-certs" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcaf18a4f2be7326cd874a5fa579fae794320a0f388d365dca7e480e55f83f8a" +dependencies = [ + "openssl-probe", + "rustls-pemfile 2.2.0", + "rustls-pki-types", + "schannel", + "security-framework", +] + +[[package]] +name = "rustls-pemfile" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c" +dependencies = [ + "base64 0.21.7", +] + +[[package]] +name = "rustls-pemfile" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50" +dependencies = [ + "rustls-pki-types", +] + +[[package]] +name = "rustls-pki-types" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16f1201b3c9a7ee8039bcadc17b7e605e2945b27eee7631788c1bd2b0643674b" + +[[package]] +name = "rustls-webpki" +version = "0.101.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765" +dependencies = [ + "ring", + "untrusted", +] + +[[package]] +name = "rustls-webpki" +version = "0.102.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64ca1bc8749bd4cf37b5ce386cc146580777b4e8572c7b97baf22c83f444bee9" +dependencies = [ + "ring", + "rustls-pki-types", + "untrusted", +] + +[[package]] +name = "ryu" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" + +[[package]] +name = "schannel" +version = "0.1.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01227be5826fa0690321a2ba6c5cd57a19cf3f6a09e76973b58e61de6ab9d1c1" +dependencies = [ + "windows-sys 0.59.0", +] + +[[package]] +name = "schemars" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09c024468a378b7e36765cd36702b7a90cc3cba11654f6685c8f233408e89e92" +dependencies = [ + "dyn-clone", + "schemars_derive", + "serde", + "serde_json", +] + +[[package]] +name = "schemars_derive" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1eee588578aff73f856ab961cd2f79e36bc45d7ded33a7562adba4667aecc0e" +dependencies = [ + "proc-macro2", + "quote", + "serde_derive_internals", + "syn", +] + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "sct" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414" +dependencies = [ + "ring", + "untrusted", +] + +[[package]] +name = "secrecy" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e891af845473308773346dc847b2c23ee78fe442e0472ac50e22a18a93d3ae5a" +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.6.0", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea4a292869320c0272d7bc55a5a6aafaff59b4f63404a003887b679a2e05b4b6" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "semver" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" + +[[package]] +name = "serde" +version = "1.0.210" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8e3592472072e6e22e0a54d5904d9febf8508f65fb8552499a1abc7d1078c3a" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde-value" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3a1a3341211875ef120e117ea7fd5228530ae7e7036a779fdc9117be6b3282c" +dependencies = [ + "ordered-float", + "serde", +] + +[[package]] +name = "serde_derive" +version = "1.0.210" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_derive_internals" +version = "0.29.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.129" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dbcf9b78a125ee667ae19388837dd12294b858d101fdd393cb9d5501ef09eb2" +dependencies = [ + "itoa", + "memchr", + "ryu", + "serde", +] + +[[package]] +name = "serde_yaml" +version = "0.9.34+deprecated" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47" +dependencies = [ + "indexmap", + "itoa", + "ryu", + "serde", + "unsafe-libyaml", +] + +[[package]] +name = "sha1" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "sha2" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "signal-hook-registry" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" +dependencies = [ + "libc", +] + +[[package]] +name = "slab" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" +dependencies = [ + "autocfg", +] + +[[package]] +name = "smallvec" +version = "1.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" + +[[package]] +name = "snap-kube" +version = "0.1.1" +dependencies = [ + "anyhow", + "async-trait", + "aws-config", + "aws-sdk-ec2", + "clap", + "colored", + "k8s-openapi", + "kube", + "kube-custom-resources-rs", + "mockall", + "pretty_assertions", + "schemars", + "serde", + "serde_json", + "tokio", + "tracing", + "tracing-subscriber", +] + +[[package]] +name = "snap-kube-client" +version = "0.1.1" +dependencies = [ + "anyhow", + "async-trait", + "aws-config", + "aws-sdk-ec2", + "clap", + "colored", + "k8s-openapi", + "kube", + "kube-custom-resources-rs", + "mockall", + "pretty_assertions", + "schemars", + "serde", + "serde_json", + "snap-kube", + "tokio", + "tracing", + "tracing-subscriber", +] + +[[package]] +name = "socket2" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce305eb0b4296696835b71df73eb912e0f1ffd2556a501fcede6e0c50349191c" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + +[[package]] +name = "syn" +version = "2.0.79" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89132cd0bf050864e1d38dc3bbc07a0eb8e7530af26344d3d2bbbef83499f590" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "sync_wrapper" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" + +[[package]] +name = "termtree" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3369f5ac52d5eb6ab48c6b4ffdc8efbcad6b89c765749064ba298f2c68a16a76" + +[[package]] +name = "thiserror" +version = "1.0.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d50af8abc119fb8bb6dbabcfa89656f46f84aa0ac7688088608076ad2b459a84" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08904e7672f5eb876eaaf87e0ce17857500934f4981c4a0ab2b4aa98baac7fc3" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "thread_local" +version = "1.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c" +dependencies = [ + "cfg-if", + "once_cell", +] + +[[package]] +name = "time" +version = "0.3.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885" +dependencies = [ + "deranged", + "num-conv", + "powerfmt", + "serde", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" + +[[package]] +name = "time-macros" +version = "0.2.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f252a68540fde3a3877aeea552b832b40ab9a69e318efd078774a01ddee1ccf" +dependencies = [ + "num-conv", + "time-core", +] + +[[package]] +name = "tinyvec" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "445e881f4f6d382d5f27c034e25eb92edd7c784ceab92a0937db7f2e9471b938" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "tokio" +version = "1.40.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2b070231665d27ad9ec9b8df639893f46727666c6767db40317fbe920a5d998" +dependencies = [ + "backtrace", + "bytes", + "libc", + "mio", + "parking_lot", + "pin-project-lite", + "signal-hook-registry", + "socket2", + "tokio-macros", + "windows-sys 0.52.0", +] + +[[package]] +name = "tokio-macros" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tokio-rustls" +version = "0.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081" +dependencies = [ + "rustls 0.21.12", + "tokio", +] + +[[package]] +name = "tokio-rustls" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c7bc40d0e5a97695bb96e27995cd3a08538541b0a846f65bba7a359f36700d4" +dependencies = [ + "rustls 0.23.15", + "rustls-pki-types", + "tokio", +] + +[[package]] +name = "tokio-util" +version = "0.7.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61e7c3654c13bcd040d4a03abee2c75b1d14a37b423cf5a813ceae1cc903ec6a" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "slab", + "tokio", +] + +[[package]] +name = "tower" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2873938d487c3cfb9aed7546dc9f2711d867c9f90c46b889989a2cb84eba6b4f" +dependencies = [ + "futures-core", + "futures-util", + "pin-project-lite", + "sync_wrapper", + "tokio", + "tokio-util", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tower-http" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8437150ab6bbc8c5f0f519e3d5ed4aa883a83dd4cdd3d1b21f9482936046cb97" +dependencies = [ + "base64 0.22.1", + "bitflags 2.6.0", + "bytes", + "http 1.1.0", + "http-body 1.0.1", + "mime", + "pin-project-lite", + "tower-layer", + "tower-service", + "tracing", +] + +[[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.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" +dependencies = [ + "log", + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tracing-core" +version = "0.1.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" +dependencies = [ + "once_cell", + "valuable", +] + +[[package]] +name = "tracing-log" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" +dependencies = [ + "log", + "once_cell", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b" +dependencies = [ + "nu-ansi-term", + "sharded-slab", + "smallvec", + "thread_local", + "tracing-core", + "tracing-log", +] + +[[package]] +name = "try-lock" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" + +[[package]] +name = "typenum" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" + +[[package]] +name = "ucd-trie" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971" + +[[package]] +name = "unicode-bidi" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ab17db44d7388991a428b2ee655ce0c212e862eff1768a455c58f9aad6e7893" + +[[package]] +name = "unicode-ident" +version = "1.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" + +[[package]] +name = "unicode-normalization" +version = "0.1.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5033c97c4262335cded6d6fc3e5c18ab755e1a3dc96376350f3d8e9f009ad956" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "unsafe-libyaml" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861" + +[[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.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22784dbdf76fdde8af1aeda5622b546b422b6fc585325248a2bf9f5e41e94d6c" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", +] + +[[package]] +name = "urlencoding" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da" + +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + +[[package]] +name = "uuid" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8c5f0a0af699448548ad1a2fbf920fb4bee257eae39953ba95cb84891a0446a" + +[[package]] +name = "valuable" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "vsimd" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c3082ca00d5a5ef149bb8b555a72ae84c9c59f7250f013ac822ac2e49b19c64" + +[[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.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.5", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "xmlparser" +version = "0.13.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66fee0b777b0f5ac1c69bb06d361268faafa61cd4682ae064a171c16c433e9e4" + +[[package]] +name = "yansi" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049" + +[[package]] +name = "zerocopy" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" +dependencies = [ + "byteorder", + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "zeroize" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..1fdc334 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,66 @@ +[package] +name = "snap-kube" +version = "0.1.1" +edition = "2021" +license = "MIT" +description = "The snap-kube is a Rust-based tool that can backup and restore Kubernetes PVCs across namespaces using AWS EBS snapshots" +readme = "README.md" +homepage = "https://github.com/nikoshet/snap-kube" +repository = "https://github.com/nikoshet/snap-kube" +keywords = ["k8s", "pvc", "snapshot", "ebs", "aws"] +documentation = "https://docs.rs/snap-kube" +exclude = ["script.sh"] + +[workspace] +members = ["snap-kube-client"] + +[workspace.dependencies] +anyhow = "1.0.89" +async-trait = "0.1.83" +aws-config = "1.5.7" +aws-sdk-ec2 = "1.75.0" +clap = { version = "4.5.20", features = ["derive"] } +colored = "2.1.0" +k8s-openapi = { version = "0.23.0" , features = ["v1_30"] } +kube = { version = "0.96.0", features = ["runtime", "derive"] } +kube-custom-resources-rs = { version = "2024.9.1", features = ["snapshot_storage_k8s_io"] } +pretty_assertions = "1.4.1" +schemars = "0.8.21" +serde = "1.0.210" +serde_json = "1.0.128" +tokio = { version = "1", features = ["full"] } +tracing = "0.1.40" +tracing-subscriber = "0.3.18" +snap-kube = { path = ".", version = "0.1" } +mockall = "0.13" + +[dependencies] +anyhow.workspace = true +async-trait.workspace = true +aws-config.workspace = true +aws-sdk-ec2.workspace = true +clap.workspace = true +colored.workspace = true +k8s-openapi.workspace = true +kube.workspace = true +kube-custom-resources-rs.workspace = true +pretty_assertions.workspace = true +schemars.workspace = true +serde.workspace = true +serde_json.workspace = true +tokio.workspace = true +tracing.workspace = true +tracing-subscriber.workspace = true +mockall.workspace = true + +[lib] +test = true +edition = "2021" +crate-type = ["lib"] +name = "snap_kube" + +[features] +default = ["full"] +full = ["backup", "restore"] +backup = [] +restore = [] diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..aac5a1d --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2024 Nick Nikitas + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..77a2484 --- /dev/null +++ b/README.md @@ -0,0 +1,166 @@ +

SnapKube

+
+ + A Rust 🦀 tool supports (for now) PVC snapshots across Kubernetes namespaces + +
+ +
+ +
+ + + actions status + + + Crates.io version + + + docs.rs docs + + + Download + +
+ +## Table of Contents +1. [Overview](#Overview) +2. [Modes of Operation](#modes-of-operation) +2. [Features](#Features) +3. [Prerequisites](#Prerequisites) +4. [Installation](#Installation) + - [Client](#client) + - [Library](#Library) +5. [Example](#Example) +6. [Tested Versions](#Tested-Versions) +7. [License](#License) + +## Overview +The SnapKube Tool is a Rust-based utility that allows Kubernetes users to backup and restore Persistent Volume Claim (PVC) snapshots. The tool provides robust mechanisms to back up data to AWS Elastic Block Store (EBS) and restore it to any Kubernetes namespace. This tool is designed to work with Kubernetes VolumeSnapshot resources, making backup and restoration operations seamless. + + +## Modes of Operation + +The tool supports three primary modes of operation: + +| Mode | Description | +|---------|--------------------------------------------------| +| Backup | Create snapshots of one or more PVCs. | +| Restore | Restore PVCs from existing snapshots. | +| Full | Run both backup and restore operations in a single process. | + + +## Features +- **Backup**: Create Kubernetes VolumeSnapshots from existing PVCs +- **Restore**: Restore PVCs to any namespace from a VolumeSnapshot +- **Flexible Configuration**: The user can either snapshot a specific PVC, or all the PVCs in a specific namespace using the relative flags +- **AWS EBS Integration**: Natively supports backup and restoration to AWS Elastic Block Store +- **Conditional Compilation**: Enable or disable specific modes (backup, restore, full) via Rust feature flags, optimizing binary size and performance. +- **Error Handling**: Robust error handling and retries to ensure operations complete reliably + +## Prerequisites +Before using SnapKube, please ensure you have the following: +- You need Rust installed to compile the tool. Install Rust via rustup +- An AWS Account with the appropriate access policy +- AWS EBS CSI Driver: Required to be installed in your Kubernetes cluster, which is a CSI Driver to manage the lifecycle of EBS Volumes +- CSI Snapshot Controller: A snapshot-controller that supports handling the VolumeSnapshot and VolumeSnapshotContent Objects +- A specific VolumeSnapshotClass for the CSI driver +- Kubernetes CLI + +## Installation + +### Client +In order to use the tool as a client, you can use `cargo`. + +``` +cargo install snap-kube-client +``` + +The tool provides 3 features for running it, which are `backup` `restore`, and `full` (default). +```shell +Usage: snap-kube-client full [OPTIONS] --source-ns --target-ns --volume-snapshot-class --volume-snapshot-name-prefix --target-snapshot-content-name-prefix --storage-class-name + +Options: + --region + Region where the EBS volumes are stored [default: eu-west-1] + --source-ns + Source namespace + --target-ns + Target namespace + --volume-snapshot-class + VolumeSnapshotClass name + --pvc-name + PVC name [default: ] + --include-all-pvcs + Include all PVCs in the namespace + --volume-snapshot-name-prefix + VolumeSnapshot name prefix + --target-snapshot-content-name-prefix + Target VolumeSnapshotContent name prefix + --storage-class-name + StorageClass name + --vsc-retain-policy + VSC Retain Policy [default: delete] [possible values: retain, delete] + -h, --help + Print help + -V, --version + Print version +``` + +### Library +To install the tool as a library, you can add it to your `Cargo.toml`: +``` +cargo add snap-kube +``` +or +``` +[dependencies] +snap-client = "0.X" +``` + +Run the tool: +- For **full** mode: +``` +cargo run full +``` + +- For **backup** mode: +``` +cargo run --no-default-features --features backup -- backup +``` + +- For **restore** mode: +``` +cargo run --no-default-features --features restore -- restore +``` + +## Example + +- Build and run the Rust tool +```shell +cargo fmt --all +cargo clippy --all +cargo nextest run --all +cargo build + +RUST_LOG=info \ + cargo run full \ + --source-ns "source-ns" \ + --target-ns "target-ns" \ + --volume-snapshot-class "volumesnapshotclass-name" \ + --include-all-pvcs \ + --volume-snapshot-name-prefix "prefix-vs" \ + --target-snapshot-content-name-prefix "prefix-vsc" \ + --storage-class-name "ebs-test-sc" +``` + +## Tested Versions + +- Kubernetes v1.30 +- Rust v1.81.0 +- Amazon EBS CSI Driver v1.35.0-eksbuild.1 +- CSI Snapshot Controller: v8.0.0-eksbuild.1 + +## License +This project is licensed under the MIT License diff --git a/snap-kube-client/Cargo.toml b/snap-kube-client/Cargo.toml new file mode 100644 index 0000000..b7cbc8f --- /dev/null +++ b/snap-kube-client/Cargo.toml @@ -0,0 +1,37 @@ +[package] +name = "snap-kube-client" +version = "0.1.1" +edition = "2021" +license = "MIT" +description = "The snap-kube-client is a Rust-based client that can backup and restore Kubernetes PVCs across namespaces using AWS EBS snapshots" +readme = "../README.md" +homepage = "https://github.com/nikoshet/snap-kube" +repository = "https://github.com/nikoshet/snap-kube" +keywords = ["k8s", "pvc", "snapshot", "ebs", "aws"] +documentation = "https://docs.rs/snap-kube-client" + +[dependencies] +anyhow.workspace = true +async-trait.workspace = true +aws-config.workspace = true +aws-sdk-ec2.workspace = true +clap.workspace = true +colored.workspace = true +k8s-openapi.workspace = true +kube.workspace = true +kube-custom-resources-rs.workspace = true +pretty_assertions.workspace = true +schemars.workspace = true +serde.workspace = true +serde_json.workspace = true +tokio.workspace = true +tracing.workspace = true +tracing-subscriber.workspace = true +snap-kube.workspace = true +mockall = "0.13.0" + +[features] +default = ["full"] +full = ["backup", "restore"] +backup = [] +restore = [] diff --git a/snap-kube-client/src/main.rs b/snap-kube-client/src/main.rs new file mode 100644 index 0000000..b75841a --- /dev/null +++ b/snap-kube-client/src/main.rs @@ -0,0 +1,236 @@ +use anyhow::Result; +use clap::{Parser, Subcommand}; +use colored::Colorize; +#[cfg(feature = "backup")] +use snap_kube::backup::{backup_operator::BackupOperator, backup_payload::BackupPayload}; +#[cfg(feature = "restore")] +use snap_kube::k8s_ops::vsc::retain_policy::VSCRetainPolicy; +#[cfg(feature = "restore")] +use snap_kube::restore::{restore_operator::RestoreOperator, restore_payload::RestorePayload}; +use tracing::info; + +#[derive(Parser)] +#[command(author, version, about, long_about = None)] +#[command(propagate_version = true)] +struct Cli { + #[command(subcommand)] + command: Commands, +} + +#[derive(Subcommand)] +enum Commands { + #[cfg(feature = "backup")] + Backup { + /// Region where the EBS volumes are stored + #[arg(long, required = false, default_value = "eu-west-1")] + region: String, + /// Source namespace + #[arg(long, required = true)] + source_ns: String, + /// VolumeSnapshotClass name + #[arg(long, required = true)] + volume_snapshot_class: String, + /// PVC name + #[arg(long, required = false, conflicts_with = "include_all_pvcs")] + pvc_name: Option, + /// Include all PVCs in the namespace + #[arg( + long, + required = false, + default_value = "false", + conflicts_with = "pvc_name" + )] + include_all_pvcs: bool, + /// VolumeSnapshot name prefix + #[arg(long, required = true)] + volume_snapshot_name_prefix: String, + }, + #[cfg(feature = "restore")] + Restore { + /// Source namespace + #[arg(long, required = true)] + source_ns: String, + /// Target namespace + #[arg(long, required = true)] + target_ns: String, + /// VolumeSnapshotClass name + #[arg(long, required = true)] + volume_snapshot_class: String, + /// PVC name + #[arg(long, required = false, conflicts_with = "include_all_pvcs")] + pvc_name: Option, + /// Include all PVCs in the namespace + #[arg( + long, + required = false, + default_value = "false", + conflicts_with = "pvc_name" + )] + include_all_pvcs: bool, + /// VolumeSnapshot name prefix + #[arg(long, required = true)] + volume_snapshot_name_prefix: String, + /// Target VolumeSnapshotContent name prefix + #[arg(long, required = true)] + target_snapshot_content_name_prefix: String, + /// StorageClass name + #[arg(long, required = true)] + storage_class_name: String, + /// VSC Retain Policy + #[arg(long, required = false, default_value = "delete")] + #[clap(value_enum)] + vsc_retain_policy: VSCRetainPolicy, + }, + #[cfg(feature = "full")] + Full { + /// Region where the EBS volumes are stored + #[arg(long, required = false, default_value = "eu-west-1")] + region: String, + /// Source namespace + #[arg(long, required = true)] + source_ns: String, + /// Target namespace + #[arg(long, required = true)] + target_ns: String, + /// VolumeSnapshotClass name + #[arg(long, required = true)] + volume_snapshot_class: String, + /// PVC name + #[arg(long, required = false, conflicts_with = "include_all_pvcs")] + pvc_name: Option, + /// Include all PVCs in the namespace + #[arg( + long, + required = false, + default_value = "false", + conflicts_with = "pvc_name" + )] + include_all_pvcs: bool, + /// VolumeSnapshot name prefix + #[arg(long, required = true)] + volume_snapshot_name_prefix: String, + /// Target VolumeSnapshotContent name prefix + #[arg(long, required = true)] + target_snapshot_content_name_prefix: String, + /// StorageClass name + #[arg(long, required = true)] + storage_class_name: String, + /// VSC Retain Policy + #[arg(long, required = false, default_value = "delete")] + #[clap(value_enum)] + vsc_retain_policy: VSCRetainPolicy, + }, +} + +#[tokio::main] +async fn main() -> Result<()> { + tracing_subscriber::fmt::init(); + + let cli = Cli::parse(); + match cli.command { + #[cfg(feature = "backup")] + Commands::Backup { + region, + source_ns, + volume_snapshot_class, + pvc_name, + include_all_pvcs, + volume_snapshot_name_prefix, + } => { + let backup_payload = BackupPayload::new( + region, + source_ns, + volume_snapshot_class, + pvc_name, + include_all_pvcs, + volume_snapshot_name_prefix, + ); + + info!("{}", "Starting Backup process...".bold().blue()); + BackupOperator::backup(backup_payload).await?; + info!( + "{}", + "Backup process completed successfully!".bold().green() + ); + } + #[cfg(feature = "restore")] + Commands::Restore { + source_ns, + target_ns, + volume_snapshot_class, + pvc_name, + include_all_pvcs, + volume_snapshot_name_prefix, + target_snapshot_content_name_prefix, + storage_class_name, + vsc_retain_policy, + } => { + let restore_payload = RestorePayload::new( + source_ns.clone(), + target_ns.clone(), + volume_snapshot_class.clone(), + pvc_name.clone(), + include_all_pvcs, + volume_snapshot_name_prefix.clone(), + target_snapshot_content_name_prefix.clone(), + storage_class_name.clone(), + vsc_retain_policy, + ); + info!("{}", "Starting Restore process...".bold().blue()); + RestoreOperator::restore(restore_payload).await?; + info!( + "{}", + "Restore process completed successfully!".bold().green() + ); + } + #[cfg(feature = "full")] + Commands::Full { + region, + source_ns, + target_ns, + volume_snapshot_class, + pvc_name, + include_all_pvcs, + volume_snapshot_name_prefix, + target_snapshot_content_name_prefix, + storage_class_name, + vsc_retain_policy, + } => { + let backup_payload = BackupPayload::new( + region.clone(), + source_ns.clone(), + volume_snapshot_class.clone(), + pvc_name.clone(), + include_all_pvcs, + volume_snapshot_name_prefix.clone(), + ); + + let restore_payload = RestorePayload::new( + source_ns.clone(), + target_ns.clone(), + volume_snapshot_class.clone(), + pvc_name.clone(), + include_all_pvcs, + volume_snapshot_name_prefix.clone(), + target_snapshot_content_name_prefix.clone(), + storage_class_name.clone(), + vsc_retain_policy, + ); + + info!("{}", "Starting Backup process...".bold().blue()); + BackupOperator::backup(backup_payload).await?; + info!( + "{}", + "Backup process completed successfully!".bold().green() + ); + + info!("{}", "Starting Restore process...".bold().blue()); + RestoreOperator::restore(restore_payload).await?; + info!( + "{}", + "Restore process completed successfully!".bold().green() + ); + } + }; + Ok(()) +} diff --git a/src/aws_ops/ebs.rs b/src/aws_ops/ebs.rs new file mode 100644 index 0000000..f8cb4ea --- /dev/null +++ b/src/aws_ops/ebs.rs @@ -0,0 +1,43 @@ +use super::region::get_region_config; +use anyhow::Result; +use aws_sdk_ec2::Client as EbsClient; + +/// Create an EBS client +/// +/// # Arguments +/// +/// * `region` - AWS region +/// +/// # Returns +/// +/// EBS client +pub async fn create_ebs_client(region: Option) -> Result { + let region_config = get_region_config(region).await; + let ebs_client = EbsClient::new(®ion_config); + Ok(ebs_client) +} + +/// Get the progress of an EBS snapshot +/// +/// # Arguments +/// +/// * `ebs_client` - EBS client +/// * `snapshot_id` - Snapshot ID +/// +/// # Returns +/// +/// Progress of the snapshot +pub async fn get_ebs_snapshot_progress( + ebs_client: EbsClient, + snapshot_id: String, +) -> Result { + let resp = ebs_client + .describe_snapshots() + .snapshot_ids(snapshot_id) + .send() + .await?; + + let snapshot = resp.snapshots.unwrap().pop().unwrap(); + let progress = snapshot.progress.unwrap(); + Ok(progress) +} diff --git a/src/aws_ops/mod.rs b/src/aws_ops/mod.rs new file mode 100644 index 0000000..36bc242 --- /dev/null +++ b/src/aws_ops/mod.rs @@ -0,0 +1,4 @@ +#[cfg(feature = "backup")] +pub mod ebs; +#[cfg(feature = "backup")] +mod region; diff --git a/src/aws_ops/region.rs b/src/aws_ops/region.rs new file mode 100644 index 0000000..59383f9 --- /dev/null +++ b/src/aws_ops/region.rs @@ -0,0 +1,27 @@ +use aws_config::meta::region::RegionProviderChain; +use aws_config::retry::RetryConfig; +use aws_config::{BehaviorVersion, Region, SdkConfig}; + +/// +/// Get the AWS region configuration +/// +/// # Arguments +/// +/// * `region` - AWS region +/// +/// # Returns +/// +/// AWS configuration +pub async fn get_region_config(region: Option) -> SdkConfig { + let main_region = region.unwrap_or("eu-west-1".to_string()); + + let region_provider = RegionProviderChain::first_try(Region::new(main_region)) + .or_default_provider() + .or_else(Region::new("eu-west-1")); + + aws_config::defaults(BehaviorVersion::latest()) + .region(region_provider) + .retry_config(RetryConfig::adaptive()) + .load() + .await +} diff --git a/src/backup/backup_operator.rs b/src/backup/backup_operator.rs new file mode 100644 index 0000000..e95f37d --- /dev/null +++ b/src/backup/backup_operator.rs @@ -0,0 +1,120 @@ +use super::backup_payload::BackupPayload; +use crate::{ + aws_ops::ebs::create_ebs_client, + k8s_ops::{ + pvc::persistent_volume_claims::{check_if_pvc_exists, get_pvcs_available, KubePvcApi}, + vs::{ + volume_snapshots::wait_untill_snapshot_is_ready, + volume_snapshots_operator::VolumeSnapshotOperator, + }, + vsc::retain_policy::VSCRetainPolicy, + }, +}; +use anyhow::{bail, Result}; +use kube::{api::PostParams, Api, Client}; +use kube_custom_resources_rs::snapshot_storage_k8s_io::v1::{ + volumesnapshotcontents::VolumeSnapshotContent, + volumesnapshots::{VolumeSnapshot, VolumeSnapshotStatus}, +}; +use tracing::info; + +/// A struct for backing up a PVC to a VolumeSnapshot +pub struct BackupOperator; + +impl BackupOperator { + /// Takes a backup of one or more PVCs from a specific namespace to a VolumeSnapshot/VolumeSnapshotContent + pub async fn backup(backup_payload: BackupPayload) -> Result<()> { + // Create a Kubernetes client + let k8s_client = Client::try_default().await?; + + // Create an AWS EBS client + let ebs_client = create_ebs_client(Some(backup_payload.region().to_string())) + .await + .unwrap(); + + // Define the VolumeSnapshot and VolumeSnapshotContent APIs + let restore_k8s_apis_struct = BackupKubernetesApisStruct { + source_vs_api: Api::namespaced(k8s_client.clone(), backup_payload.source_ns()), + source_pvcs_api: KubePvcApi { + api: Api::namespaced(k8s_client.clone(), backup_payload.source_ns()), + }, + vsc_api: Api::all(k8s_client.clone()), + }; + + // Check if we will backup all PVCs in the namespace + let pvcs = if backup_payload.include_all_pvcs() { + get_pvcs_available(&restore_k8s_apis_struct.source_pvcs_api).await? + } else { + vec![backup_payload + .pvc_name() + .unwrap_or_else(|| panic!("PVC name is required when include_all_pvcs is false")) + .to_string()] + }; + + // We will iterate over the PVCs vector and backup each PVC + for pvc in pvcs { + info!("Backing up PVC: {}", pvc); + let volume_snapshot_name = format!("{}-{}", backup_payload.vs_name_prefix(), pvc); + + // Check if the PVC exists, it should exist + check_if_pvc_exists(&restore_k8s_apis_struct.source_pvcs_api, &pvc, true).await?; + + let vs_operator = VolumeSnapshotOperator::new( + volume_snapshot_name.to_string(), + backup_payload.source_ns.to_string(), + backup_payload.volume_snapshot_class().to_string(), + Some(pvc), + None, + ); + + let volume_snapshot = + vs_operator.construct_volume_snapshot_resource(None, None, VSCRetainPolicy::Delete); + + let pp = PostParams::default(); + let status: VolumeSnapshotStatus = match restore_k8s_apis_struct + .source_vs_api + .create(&pp, &volume_snapshot) + .await + { + Ok(snapshot) => { + info!( + "{}", + format!( + "Created VolumeSnapshot: {} on namespace: {}", + snapshot.metadata.name.clone().unwrap(), + backup_payload.source_ns() + ) + ); + wait_untill_snapshot_is_ready( + &restore_k8s_apis_struct.source_vs_api, + &restore_k8s_apis_struct.vsc_api, + &ebs_client, + &volume_snapshot_name, + ) + .await? + } + Err(e) => { + bail!("Failed to create VolumeSnapshot: {}", e); + } + }; + + let bound_vsc_name = status.bound_volume_snapshot_content_name.unwrap(); + let restore_size = status.restore_size.unwrap(); + info!( + "{}", + format!( + "VolumeSnapshot is ready! VS name: {}, Bound VSC name: {}, Restore size: {}", + volume_snapshot_name, bound_vsc_name, restore_size + ) + ); + } + Ok(()) + } +} + +/// A struct for holding the Kubernetes APIs for the backup operation +struct BackupKubernetesApisStruct { + source_vs_api: Api, + source_pvcs_api: KubePvcApi, + vsc_api: Api, +} diff --git a/src/backup/backup_payload.rs b/src/backup/backup_payload.rs new file mode 100644 index 0000000..138305b --- /dev/null +++ b/src/backup/backup_payload.rs @@ -0,0 +1,66 @@ +pub struct BackupPayload { + pub region: String, + pub source_ns: String, + pub volume_snapshot_class: String, + pub pvc_name: Option, + pub include_all_pvcs: bool, + pub vs_name_prefix: String, +} + +impl BackupPayload { + /// Creates a new BackupPayload + /// + /// # Arguments + /// + /// * `region` - AWS region + /// * `source_ns` - Source namespace + /// * `volume_snapshot_class` - VolumeSnapshotClass name + /// * `pvc_name` - PVC name + /// * `include_all_pvcs` - Include all PVCs in the namespace + /// * `vs_name_prefix` - VolumeSnapshot name prefix + /// + /// # Returns + /// + /// A new BackupPayload instance + pub fn new( + region: impl Into, + source_ns: impl Into, + volume_snapshot_class: impl Into, + pvc_name: Option>, + include_all_pvcs: bool, + vs_name_prefix: impl Into, + ) -> Self { + Self { + region: region.into(), + source_ns: source_ns.into(), + volume_snapshot_class: volume_snapshot_class.into(), + pvc_name: pvc_name.map(|pvc_name| pvc_name.into()), + include_all_pvcs, + vs_name_prefix: vs_name_prefix.into(), + } + } + + pub fn region(&self) -> &str { + &self.region + } + + pub fn source_ns(&self) -> &str { + &self.source_ns + } + + pub fn volume_snapshot_class(&self) -> &str { + &self.volume_snapshot_class + } + + pub fn pvc_name(&self) -> Option<&str> { + self.pvc_name.as_deref() + } + + pub fn include_all_pvcs(&self) -> bool { + self.include_all_pvcs + } + + pub fn vs_name_prefix(&self) -> &str { + &self.vs_name_prefix + } +} diff --git a/src/backup/mod.rs b/src/backup/mod.rs new file mode 100644 index 0000000..78a581b --- /dev/null +++ b/src/backup/mod.rs @@ -0,0 +1,4 @@ +#[cfg(feature = "backup")] +pub mod backup_operator; +#[cfg(feature = "backup")] +pub mod backup_payload; diff --git a/src/k8s_ops/mod.rs b/src/k8s_ops/mod.rs new file mode 100644 index 0000000..f738c13 --- /dev/null +++ b/src/k8s_ops/mod.rs @@ -0,0 +1,3 @@ +pub mod pvc; +pub mod vs; +pub mod vsc; diff --git a/src/k8s_ops/pvc/mod.rs b/src/k8s_ops/pvc/mod.rs new file mode 100644 index 0000000..d465ab1 --- /dev/null +++ b/src/k8s_ops/pvc/mod.rs @@ -0,0 +1,8 @@ +pub mod persistent_volume_claims; +#[cfg(feature = "restore")] +pub mod persistent_volume_claims_operator; +#[cfg(feature = "restore")] +pub mod persistent_volume_claims_payload; + +#[cfg(test)] +mod persistent_volume_claims_tests; diff --git a/src/k8s_ops/pvc/persistent_volume_claims.rs b/src/k8s_ops/pvc/persistent_volume_claims.rs new file mode 100644 index 0000000..f2435b7 --- /dev/null +++ b/src/k8s_ops/pvc/persistent_volume_claims.rs @@ -0,0 +1,94 @@ +use anyhow::Result; +use async_trait::async_trait; +use k8s_openapi::api::core::v1::PersistentVolumeClaim; +use kube::{api::ListParams, Api}; +use tracing::info; + +#[cfg(test)] +use mockall::automock; + +#[cfg_attr(test, automock)] +#[async_trait] +pub trait PvcApiTrait { + async fn list_pvcs(&self) -> Result>; + async fn get(&self, name: &str) -> Result; + async fn create(&self, pvc: PersistentVolumeClaim) -> Result; +} + +pub struct KubePvcApi { + pub api: Api, +} + +/// Implement the PvcApi trait for PVC Api +/// This will allow us to call the functions defined in the PvcApi trait on an instance of KubePvcApi. +/// This is useful for testing, as we can mock the KubePvcApi struct and implement the PvcApi trait +/// to return mock data. +/// This way, we can test the functions that use the KubePvcApi struct without actually making +/// calls to the Kubernetes API. +#[async_trait] +impl PvcApiTrait for KubePvcApi { + async fn list_pvcs(&self) -> Result> { + let pvcs = self.api.list(&ListParams::default()).await?; + Ok(pvcs.items) + } + + async fn get(&self, name: &str) -> Result { + let pvc = self.api.get(name).await?; + Ok(pvc) + } + + async fn create(&self, pvc: PersistentVolumeClaim) -> Result { + let pvc = self.api.create(&Default::default(), &pvc).await?; + Ok(pvc) + } +} + +/// Get the list of PersistentVolumeClaims available +pub async fn get_pvcs_available(pvc_api: &impl PvcApiTrait) -> Result> { + let pvc_list: Vec<_> = match pvc_api.list_pvcs().await { + Ok(pvc) => pvc, + Err(e) => panic!("Failed to list PVCs: {}", e), + } + .into_iter() + .map(|pvc| pvc.metadata.name.unwrap()) + .collect(); + info!("PVCs available: {:?}", pvc_list); + Ok(pvc_list) +} + +pub async fn check_if_pvc_exists( + target_pvc_api: &impl PvcApiTrait, + //&kube::Api, + pvc_name: &str, + should_exist: bool, +) -> Result> { + match target_pvc_api.get(pvc_name).await { + Ok(pvc) => { + if should_exist { + info!( + "{}", + format!( + "PVC exists: {} on target namespace {:?}", + pvc.metadata.name.clone().unwrap(), + pvc.metadata.namespace.clone().unwrap() + ) + ); + Ok(Some(pvc)) + } else { + panic!( + "PVC does not exist: {} on target namespace {:?}", + pvc.metadata.name.clone().unwrap(), + pvc.metadata.namespace.clone().unwrap() + ); + } + } + Err(e) => { + if should_exist { + panic!("Failed to get PVC: {}", e); + } else { + info!("PVC does not exist: {}", pvc_name); + Ok(None) + } + } + } +} diff --git a/src/k8s_ops/pvc/persistent_volume_claims_operator.rs b/src/k8s_ops/pvc/persistent_volume_claims_operator.rs new file mode 100644 index 0000000..ae017bc --- /dev/null +++ b/src/k8s_ops/pvc/persistent_volume_claims_operator.rs @@ -0,0 +1,110 @@ +use super::persistent_volume_claims_payload::PVCOperatorPayload; +use k8s_openapi::{ + api::core::v1::{ + PersistentVolumeClaim, PersistentVolumeClaimSpec, TypedLocalObjectReference, + TypedObjectReference, VolumeResourceRequirements, + }, + apimachinery::pkg::api::resource::Quantity, +}; +use kube::api::ObjectMeta; +use std::collections::BTreeMap; + +enum PVCResourceValues { + AccessModes, + K8sKind, + ApiGroup, + VolumeMode, + StorageClass, +} + +impl PVCResourceValues { + pub fn get_value(&self) -> String { + match self { + PVCResourceValues::AccessModes => "ReadWriteOnce".to_string(), + PVCResourceValues::K8sKind => "VolumeSnapshot".to_string(), + PVCResourceValues::ApiGroup => "snapshot.storage.k8s.io".to_string(), + PVCResourceValues::VolumeMode => "Filesystem".to_string(), + PVCResourceValues::StorageClass => "gp3".to_string(), + } + } +} + +pub struct PVCOperator { + pvc_operator_payload: PVCOperatorPayload, +} + +impl PVCOperator { + pub fn new(pvc_operator_payload: PVCOperatorPayload) -> Self { + Self { + pvc_operator_payload, + } + } + + /// Construct a PersistentVolumeClaim resource + /// + /// # Arguments + /// + /// * `pvc_name` - Name of the PersistentVolumeClaim resource + /// * `namespace` - Namespace of the PersistentVolumeClaim resource + /// * `storage_class` - Name of the StorageClass resource + /// * `access_modes` - Access modes for the PersistentVolumeClaim resource + /// * `volume_snapshot_name` - Name of the VolumeSnapshot resource + /// * `restore_size` - Size of the PersistentVolumeClaim resource + /// + /// # Returns + /// + /// PersistentVolumeClaim resource + pub fn construct_persistent_volume_claim_resource(&self) -> PersistentVolumeClaim { + // Create a base labels map + // Always add the VSc name + let labels = BTreeMap::from([( + "snap-kube/volume-snapshot-name".to_string(), + self.pvc_operator_payload.pvc_name().to_string(), + )]); + + PersistentVolumeClaim { + metadata: ObjectMeta { + name: Some(String::from(self.pvc_operator_payload.pvc_name())), + namespace: Some(String::from(self.pvc_operator_payload.namespace())), + labels: Some(labels), + ..Default::default() + }, + spec: Some(PersistentVolumeClaimSpec { + access_modes: Some( + self.pvc_operator_payload + .access_modes() + .unwrap_or(vec![PVCResourceValues::AccessModes.get_value()]), + ), + storage_class_name: Some( + self.pvc_operator_payload + .storage_class() + .unwrap_or(&PVCResourceValues::StorageClass.get_value()) + .to_string(), + ), + data_source: Some(TypedLocalObjectReference { + name: String::from(self.pvc_operator_payload.volume_snapshot_name()), + kind: PVCResourceValues::K8sKind.get_value(), + api_group: Some(PVCResourceValues::ApiGroup.get_value()), + }), + data_source_ref: Some(TypedObjectReference { + name: String::from(self.pvc_operator_payload.volume_snapshot_name()), + kind: PVCResourceValues::K8sKind.get_value(), + api_group: Some(PVCResourceValues::ApiGroup.get_value()), + namespace: Some(String::from(self.pvc_operator_payload.namespace())), + }), + volume_mode: Some(PVCResourceValues::VolumeMode.get_value()), + volume_name: Default::default(), + resources: Some(VolumeResourceRequirements { + requests: Some(BTreeMap::from([( + "storage".to_string(), + Quantity(String::from(self.pvc_operator_payload.restore_size())), + )])), + ..Default::default() + }), + selector: Default::default(), + volume_attributes_class_name: Default::default(), + }), + ..Default::default() + } + } +} diff --git a/src/k8s_ops/pvc/persistent_volume_claims_payload.rs b/src/k8s_ops/pvc/persistent_volume_claims_payload.rs new file mode 100644 index 0000000..e50c909 --- /dev/null +++ b/src/k8s_ops/pvc/persistent_volume_claims_payload.rs @@ -0,0 +1,66 @@ +pub struct PVCOperatorPayload { + pub pvc_name: String, + pub namespace: String, + pub storage_class: Option, + pub access_modes: Option>, + pub volume_snapshot_name: String, + pub restore_size: String, +} + +impl PVCOperatorPayload { + /// Creates a new PVCOperatorPayload + /// + /// # Arguments + /// + /// * `pvc_name` - Name of the PersistentVolumeClaim resource + /// * `namespace` - Namespace of the PersistentVolumeClaim resource + /// * `storage_class` - Name of the StorageClass resource + /// * `access_modes` - Access modes for the PersistentVolumeClaim resource + /// * `volume_snapshot_name` - Name of the VolumeSnapshot resource + /// * `restore_size` - Size of the PersistentVolumeClaim resource + /// + /// # Returns + /// + /// A new PVCOperatorPayload instance + pub fn new( + pvc_name: impl Into, + namespace: impl Into, + storage_class: impl Into>, + access_modes: impl Into>>, + volume_snapshot_name: impl Into, + restore_size: impl Into, + ) -> Self { + Self { + pvc_name: pvc_name.into(), + namespace: namespace.into(), + storage_class: storage_class.into(), + access_modes: access_modes.into(), + volume_snapshot_name: volume_snapshot_name.into(), + restore_size: restore_size.into(), + } + } + + pub fn pvc_name(&self) -> &str { + &self.pvc_name + } + + pub fn namespace(&self) -> &str { + &self.namespace + } + + pub fn storage_class(&self) -> Option<&str> { + self.storage_class.as_deref() + } + + pub fn access_modes(&self) -> Option> { + self.access_modes.clone() + } + + pub fn volume_snapshot_name(&self) -> &str { + &self.volume_snapshot_name + } + + pub fn restore_size(&self) -> &str { + &self.restore_size + } +} diff --git a/src/k8s_ops/pvc/persistent_volume_claims_tests.rs b/src/k8s_ops/pvc/persistent_volume_claims_tests.rs new file mode 100644 index 0000000..9e59a2a --- /dev/null +++ b/src/k8s_ops/pvc/persistent_volume_claims_tests.rs @@ -0,0 +1,187 @@ +#[cfg(test)] +mod tests { + use crate::k8s_ops::pvc::{ + persistent_volume_claims::{MockPvcApiTrait, PvcApiTrait}, + persistent_volume_claims_operator::PVCOperator, + persistent_volume_claims_payload::PVCOperatorPayload, + }; + use k8s_openapi::{ + api::core::v1::{ + PersistentVolumeClaim, PersistentVolumeClaimSpec, TypedLocalObjectReference, + TypedObjectReference, VolumeResourceRequirements, + }, + apimachinery::pkg::api::resource::Quantity, + }; + use kube::api::ObjectMeta; + use mockall::predicate; + use pretty_assertions::assert_eq; + use std::collections::BTreeMap; + + #[test] + fn test_construct_persistent_volume_claim_resource() { + let pvc_operator_payload = PVCOperatorPayload::new( + String::from("test-pvc"), + String::from("test-ns"), + String::from("gp3"), + Some(vec![String::from("ReadWriteOnce")]), + String::from("test-vs"), + "1Gi".to_string(), + ); + + let pvc_operator = PVCOperator::new(pvc_operator_payload); + + let pvc = pvc_operator.construct_persistent_volume_claim_resource(); + + let labels = BTreeMap::from([( + "snap-kube/volume-snapshot-name".to_string(), + "test-pvc".to_string(), + )]); + + let expected_pvc = PersistentVolumeClaim { + metadata: ObjectMeta { + name: Some("test-pvc".to_string()), + namespace: Some("test-ns".to_string()), + labels: Some(labels), + ..Default::default() + }, + spec: Some(PersistentVolumeClaimSpec { + access_modes: Some(vec!["ReadWriteOnce".to_string()]), + storage_class_name: Some("gp3".to_string()), + data_source: Some(TypedLocalObjectReference { + name: "test-vs".to_string(), + kind: "VolumeSnapshot".to_string(), + api_group: Some("snapshot.storage.k8s.io".to_string()), + }), + data_source_ref: Some(TypedObjectReference { + name: "test-vs".to_string(), + kind: "VolumeSnapshot".to_string(), + api_group: Some("snapshot.storage.k8s.io".to_string()), + namespace: Some("test-ns".to_string()), + }), + volume_mode: Some("Filesystem".to_string()), + volume_name: Default::default(), + resources: Some(VolumeResourceRequirements { + requests: Some(BTreeMap::from([( + "storage".to_string(), + Quantity("1Gi".to_string()), + )])), + ..Default::default() + }), + selector: Default::default(), + volume_attributes_class_name: Default::default(), + }), + ..Default::default() + }; + + assert_eq!(pvc, expected_pvc); + } + + #[tokio::test] + async fn test_create_pvc() { + let mut mock_pvc_api = MockPvcApiTrait::new(); + + let pvc = PersistentVolumeClaim { + metadata: ObjectMeta { + name: Some("test-pvc".to_string()), + namespace: Some("test-ns".to_string()), + ..Default::default() + }, + ..Default::default() + }; + + mock_pvc_api + .expect_create() + .with(predicate::eq(pvc.clone())) + .times(1) + .returning(|_| { + Ok(PersistentVolumeClaim { + metadata: ObjectMeta { + name: Some("test-pvc".to_string()), + namespace: Some("test-ns".to_string()), + ..Default::default() + }, + ..Default::default() + }) + }); + + let result = mock_pvc_api.create(pvc).await; + assert!(result.is_ok()); + assert_eq!( + result.as_ref().cloned().unwrap().metadata.name.unwrap(), + "test-pvc" + ); + assert_eq!(result.unwrap().metadata.namespace.unwrap(), "test-ns"); + } + + #[tokio::test] + async fn test_get_pvc() { + let mut mock_pvc_api = MockPvcApiTrait::new(); + + mock_pvc_api + .expect_get() + .with(predicate::eq("test-pvc")) + .times(1) + .returning(|_| { + Ok(PersistentVolumeClaim { + metadata: ObjectMeta { + name: Some("test-pvc".to_string()), + namespace: Some("test-ns".to_string()), + ..Default::default() + }, + ..Default::default() + }) + }); + + let result = mock_pvc_api.get("test-pvc").await; + assert!(result.is_ok()); + assert_eq!( + result.as_ref().cloned().unwrap().metadata.name.unwrap(), + "test-pvc" + ); + assert_eq!(result.unwrap().metadata.namespace.unwrap(), "test-ns"); + } + + #[tokio::test] + async fn test_list_pvcs() { + let mut mock_pvc_api = MockPvcApiTrait::new(); + + mock_pvc_api.expect_list_pvcs().times(1).returning(|| { + Ok(vec![PersistentVolumeClaim { + metadata: ObjectMeta { + name: Some("test-pvc".to_string()), + namespace: Some("test-ns".to_string()), + ..Default::default() + }, + ..Default::default() + }]) + }); + + let result = mock_pvc_api.list_pvcs().await; + + assert!(result.is_ok()); + assert_eq!(result.as_ref().unwrap().len(), 1); + assert_eq!( + result + .as_ref() + .unwrap() + .get(0) + .unwrap() + .metadata + .name + .clone() + .unwrap(), + "test-pvc" + ); + assert_eq!( + result + .unwrap() + .get(0) + .unwrap() + .metadata + .namespace + .clone() + .unwrap(), + "test-ns" + ); + } +} diff --git a/src/k8s_ops/vs/mod.rs b/src/k8s_ops/vs/mod.rs new file mode 100644 index 0000000..1754d95 --- /dev/null +++ b/src/k8s_ops/vs/mod.rs @@ -0,0 +1,6 @@ +#[cfg(feature = "backup")] +pub mod volume_snapshots; +pub mod volume_snapshots_operator; + +#[cfg(test)] +mod volume_snapshots_tests; diff --git a/src/k8s_ops/vs/volume_snapshots.rs b/src/k8s_ops/vs/volume_snapshots.rs new file mode 100644 index 0000000..2f6596f --- /dev/null +++ b/src/k8s_ops/vs/volume_snapshots.rs @@ -0,0 +1,61 @@ +use crate::aws_ops::ebs::get_ebs_snapshot_progress; +use crate::k8s_ops::vsc::volume_snapshot_contents::get_snapshot_handle; +use anyhow::Result; +use aws_sdk_ec2::Client as EbsClient; +use kube_custom_resources_rs::snapshot_storage_k8s_io::v1::volumesnapshotcontents::VolumeSnapshotContent; +use kube_custom_resources_rs::snapshot_storage_k8s_io::v1::volumesnapshots::{ + VolumeSnapshot, VolumeSnapshotStatus, +}; +use std::time::Duration; +use tokio::time::sleep; +use tracing::{info, warn}; + +/// Wait untill the VolumeSnapshot is ready +/// +/// # Arguments +/// +/// * `vs_api` - Api object for VolumeSnapshot +/// * `vsc_api` - Api object for VolumeSnapshotContent +/// * `ebs_client` - EBS Client object +/// * `volume_snapshot_name` - Name of the VolumeSnapshot resource +/// +/// # Returns +/// +/// VolumeSnapshotStatus +pub async fn wait_untill_snapshot_is_ready( + vs_api: &kube::Api, + vsc_api: &kube::Api, + ebs_client: &EbsClient, + volume_snapshot_name: &str, +) -> Result { + loop { + let snapshot = vs_api.get(volume_snapshot_name).await?; + if let Some(status) = snapshot.status { + if status.ready_to_use.unwrap_or(false) { + info!("Snapshot is ready: {:?}", status); + return Ok(status); + } + info!("Waiting for VolumeSnapshot to be ready..."); + + let vsc_name = status.bound_volume_snapshot_content_name.unwrap(); + match get_snapshot_handle(vsc_api.clone(), &vsc_name).await { + Ok(snapshot_handle) => { + let progress = + get_ebs_snapshot_progress(ebs_client.clone(), snapshot_handle.clone()) + .await?; + info!( + "{}", + format!( + "Progress for EBS snapshot {} regarding VS {} is: {}", + snapshot_handle, volume_snapshot_name, progress + ) + ); + } + Err(e) => { + warn!("Failed to get snapshot handle: {}", e); + } + } + sleep(Duration::from_secs(5)).await; + } + } +} diff --git a/src/k8s_ops/vs/volume_snapshots_operator.rs b/src/k8s_ops/vs/volume_snapshots_operator.rs new file mode 100644 index 0000000..4cac327 --- /dev/null +++ b/src/k8s_ops/vs/volume_snapshots_operator.rs @@ -0,0 +1,113 @@ +use std::collections::BTreeMap; + +use crate::k8s_ops::vsc::retain_policy::VSCRetainPolicy; +use kube::api::ObjectMeta; +use kube_custom_resources_rs::snapshot_storage_k8s_io::v1::volumesnapshots::{ + VolumeSnapshot, VolumeSnapshotSource, VolumeSnapshotSpec, +}; + +enum VSResourceValues { + Finalizers, +} + +impl VSResourceValues { + pub fn get_value(&self) -> String { + match self { + VSResourceValues::Finalizers => { + "snapshot.storage.kubernetes.io/volumesnapshot-bound-protection".to_string() + } + } + } +} + +pub struct VolumeSnapshotOperator { + pub name: String, + pub namespace: String, + pub volume_snapshot_class: String, + pub source_pvc_name: Option, + pub vsc_name: Option, +} + +impl VolumeSnapshotOperator { + pub fn new( + name: String, + namespace: String, + volume_snapshot_class: String, + source_pvc_name: Option, + vsc_name: Option, + ) -> Self { + Self { + name, + namespace, + volume_snapshot_class, + source_pvc_name, + vsc_name, + } + } + + /// Construct a VolumeSnapshot resource + /// + /// # Arguments + /// + /// * `name` - Name of the VolumeSnapshot resource + /// * `namespace` - Namespace of the VolumeSnapshot resource + /// * `volume_snapshot_class` - Name of the VolumeSnapshotClass resource + /// * `source_pvc_name` - Name of the PersistentVolumeClaim resource + /// * `vsc_name` - Name of the VolumeSnapshotContent resource + /// * `snapshot_handle` - Handle - Snapshot ID of the source volume + /// * `restore_size` - Size of the restored volume + /// + /// # Returns + /// + /// VolumeSnapshot resource + pub fn construct_volume_snapshot_resource( + &self, + snapshot_handle: Option, + restore_size: Option, + vsc_retain_policy: VSCRetainPolicy, + ) -> VolumeSnapshot { + // Create a base annotations map with always-included entries + let mut annotations = BTreeMap::from([ + ("snap-kube/csi-driver-name".into(), "ebs.csi.aws.com".into()), + ( + "snap-kube/csi-vsc-deletion-policy".into(), + vsc_retain_policy.to_string(), + ), + ]); + + // If a snapshot handle is provided, insert the corresponding annotation + if let Some(handle) = snapshot_handle { + annotations.insert("snap-kube/csi-volumesnapshot-handle".into(), handle); + } + // If a restore size is provided, insert the corresponding annotation + if let Some(size) = restore_size { + annotations.insert("snap-kube/csi-volumesnapshot-restore-size".into(), size); + } + + // Create a base labels map + // Always add the namespace name + let labels = BTreeMap::from([( + "app.kubernetes.io/instance".to_string(), + self.namespace.clone(), + )]); + + VolumeSnapshot { + metadata: ObjectMeta { + name: Some(self.name.clone()), + namespace: Some(self.namespace.clone()), + annotations: Some(annotations), + finalizers: Some(vec![VSResourceValues::Finalizers.get_value()]), + labels: Some(labels), + ..Default::default() + }, + spec: VolumeSnapshotSpec { + volume_snapshot_class_name: Some(self.volume_snapshot_class.clone()), + source: VolumeSnapshotSource { + persistent_volume_claim_name: self.source_pvc_name.clone(), + volume_snapshot_content_name: self.vsc_name.clone(), + }, + }, + ..Default::default() + } + } +} diff --git a/src/k8s_ops/vs/volume_snapshots_tests.rs b/src/k8s_ops/vs/volume_snapshots_tests.rs new file mode 100644 index 0000000..5840ee4 --- /dev/null +++ b/src/k8s_ops/vs/volume_snapshots_tests.rs @@ -0,0 +1,91 @@ +#[cfg(test)] +mod tests { + use crate::k8s_ops::{ + vs::volume_snapshots_operator::VolumeSnapshotOperator, vsc::retain_policy::VSCRetainPolicy, + }; + use pretty_assertions::assert_eq; + + #[tokio::test] + async fn test_construct_volume_snapshot_resource() { + let vs_operator = VolumeSnapshotOperator::new( + "test-volume-snapshot".to_string(), + "default".to_string(), + "ebs.csi.aws.com".to_string(), + Some("test-pvc".to_string()), + Some("test-volume-snapshot-content".to_string()), + ); + let volume_snapshot = vs_operator.construct_volume_snapshot_resource( + Some("test-snapshot-handle".to_string()), + Some("1Gi".to_string()), + VSCRetainPolicy::Delete, + ); + assert_eq!( + volume_snapshot.metadata.name.unwrap(), + "test-volume-snapshot" + ); + assert_eq!(volume_snapshot.metadata.namespace.unwrap(), "default"); + assert_eq!( + volume_snapshot + .metadata + .annotations + .as_ref() + .unwrap() + .get("snap-kube/csi-driver-name"), + Some(&"ebs.csi.aws.com".to_string()) + ); + assert_eq!( + volume_snapshot + .metadata + .annotations + .as_ref() + .unwrap() + .get("snap-kube/csi-vsc-deletion-policy"), + Some(&"Delete".to_string()) + ); + assert_eq!( + volume_snapshot + .metadata + .annotations + .as_ref() + .unwrap() + .get("snap-kube/csi-volumesnapshot-handle"), + Some(&"test-snapshot-handle".to_string()) + ); + assert_eq!( + volume_snapshot + .metadata + .annotations + .unwrap() + .get("snap-kube/csi-volumesnapshot-restore-size"), + Some(&"1Gi".to_string()) + ); + assert_eq!( + volume_snapshot + .metadata + .labels + .unwrap() + .get("app.kubernetes.io/instance"), + Some(&"default".to_string()) + ); + assert_eq!( + volume_snapshot.spec.volume_snapshot_class_name.unwrap(), + "ebs.csi.aws.com" + ); + assert_eq!( + volume_snapshot + .spec + .source + .persistent_volume_claim_name + .unwrap(), + "test-pvc" + ); + assert_eq!( + volume_snapshot + .spec + .source + .volume_snapshot_content_name + .unwrap(), + "test-volume-snapshot-content" + ); + } +} diff --git a/src/k8s_ops/vsc/mod.rs b/src/k8s_ops/vsc/mod.rs new file mode 100644 index 0000000..e558ec7 --- /dev/null +++ b/src/k8s_ops/vsc/mod.rs @@ -0,0 +1,7 @@ +pub mod retain_policy; +pub mod volume_snapshot_contents; +#[cfg(feature = "restore")] +pub mod volume_snapshot_contents_operator; + +#[cfg(test)] +mod volume_snapshot_contents_tests; diff --git a/src/k8s_ops/vsc/retain_policy.rs b/src/k8s_ops/vsc/retain_policy.rs new file mode 100644 index 0000000..9e0cc40 --- /dev/null +++ b/src/k8s_ops/vsc/retain_policy.rs @@ -0,0 +1,53 @@ +use clap::ValueEnum; +use kube_custom_resources_rs::snapshot_storage_k8s_io::v1::volumesnapshotcontents::VolumeSnapshotContentDeletionPolicy; +use std::fmt::{self, Display, Formatter}; + +/// Represents the VolumeSnapshotContent Retain Policy +/// +/// It can be either Retain or Delete +#[derive(ValueEnum, Clone, Debug, Copy, PartialEq, Eq)] +pub enum VSCRetainPolicy { + Retain, + Delete, +} + +impl Display for VSCRetainPolicy { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + match self { + VSCRetainPolicy::Retain => write!(f, "Retain"), + VSCRetainPolicy::Delete => write!(f, "Delete"), + } + } +} + +impl From for VolumeSnapshotContentDeletionPolicy { + fn from(vsc_retain_policy: VSCRetainPolicy) -> Self { + match vsc_retain_policy { + VSCRetainPolicy::Retain => VolumeSnapshotContentDeletionPolicy::Retain, + VSCRetainPolicy::Delete => VolumeSnapshotContentDeletionPolicy::Delete, + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_vsc_retain_policy_display() { + assert_eq!(VSCRetainPolicy::Retain.to_string(), "Retain"); + assert_eq!(VSCRetainPolicy::Delete.to_string(), "Delete"); + } + + #[test] + fn test_vsc_retain_policy_into() { + assert_eq!( + VolumeSnapshotContentDeletionPolicy::from(VSCRetainPolicy::Retain), + VolumeSnapshotContentDeletionPolicy::Retain + ); + assert_eq!( + VolumeSnapshotContentDeletionPolicy::from(VSCRetainPolicy::Delete), + VolumeSnapshotContentDeletionPolicy::Delete + ); + } +} diff --git a/src/k8s_ops/vsc/volume_snapshot_contents.rs b/src/k8s_ops/vsc/volume_snapshot_contents.rs new file mode 100644 index 0000000..279eb5f --- /dev/null +++ b/src/k8s_ops/vsc/volume_snapshot_contents.rs @@ -0,0 +1,26 @@ +use anyhow::{bail, Result}; +use kube::Api; +use kube_custom_resources_rs::snapshot_storage_k8s_io::v1::volumesnapshotcontents::VolumeSnapshotContent; + +/// Get the snapshot handle from the VolumeSnapshotContent +/// +/// # Arguments +/// +/// * `vsc_api` - Api object for VolumeSnapshotContent +/// * `volume_snapshot_content_name` - Name of the VolumeSnapshotContent resource +/// +/// # Returns +/// +/// Snapshot handle +pub async fn get_snapshot_handle( + vsc_api: Api, + volume_snapshot_content_name: &str, +) -> Result { + let volume_snapshot_content = vsc_api.get(volume_snapshot_content_name).await?; + + if let Some(status) = volume_snapshot_content.status { + Ok(status.snapshot_handle.unwrap()) + } else { + bail!("Status of VolumeSnapshotContent is not available") + } +} diff --git a/src/k8s_ops/vsc/volume_snapshot_contents_operator.rs b/src/k8s_ops/vsc/volume_snapshot_contents_operator.rs new file mode 100644 index 0000000..b912d9b --- /dev/null +++ b/src/k8s_ops/vsc/volume_snapshot_contents_operator.rs @@ -0,0 +1,103 @@ +use super::retain_policy::VSCRetainPolicy; +use kube::api::ObjectMeta; +use kube_custom_resources_rs::snapshot_storage_k8s_io::v1::volumesnapshotcontents::{ + VolumeSnapshotContent, VolumeSnapshotContentSource, VolumeSnapshotContentSpec, + VolumeSnapshotContentStatus, VolumeSnapshotContentVolumeSnapshotRef, +}; + +enum VSCResourceValues { + ApiVersion, + Kind, + Driver, + SourceVolumeMode, +} + +impl VSCResourceValues { + pub fn get_value(&self) -> String { + match self { + VSCResourceValues::ApiVersion => "snapshot.storage.k8s.io/v1".to_string(), + VSCResourceValues::Kind => "VolumeSnapshot".to_string(), + VSCResourceValues::Driver => "ebs.csi.aws.com".to_string(), + VSCResourceValues::SourceVolumeMode => "Filesystem".to_string(), + } + } +} + +pub struct VolumeSnapshotContentOperator { + pub name: String, + pub namespace: String, + pub volume_snapshot_name: String, + pub volume_snapshot_class: Option, + pub source_volume_handle: Option, + pub vsc_retain_policy: VSCRetainPolicy, +} + +impl VolumeSnapshotContentOperator { + pub fn new( + name: String, + namespace: String, + volume_snapshot_name: String, + volume_snapshot_class: Option, + source_volume_handle: Option, + vsc_retain_policy: VSCRetainPolicy, + ) -> Self { + Self { + name, + namespace, + volume_snapshot_name, + volume_snapshot_class, + source_volume_handle, + vsc_retain_policy, + } + } + + /// Construct a VolumeSnapshotContent resource + /// + /// # Arguments + /// + /// * `name` - Name of the VolumeSnapshotContent resource + /// * `namespace` - Namespace of the VolumeSnapshotContent resource + /// * `volume_snapshot_name` - Name of the VolumeSnapshot resource + /// * `volume_snapshot_class` - Name of the VolumeSnapshotClass resource + /// * `source_volume_handle` - Handle - Snapshot ID of the source volume + /// + /// # Returns + /// + /// VolumeSnapshotContent resource + pub fn construct_volume_snapshot_content_resource(&self) -> VolumeSnapshotContent { + VolumeSnapshotContent { + metadata: ObjectMeta { + name: Some(self.name.clone()), + namespace: Some(self.namespace.clone()), + ..Default::default() + }, + spec: VolumeSnapshotContentSpec { + volume_snapshot_ref: VolumeSnapshotContentVolumeSnapshotRef { + api_version: Some(VSCResourceValues::ApiVersion.get_value()), + kind: Some(VSCResourceValues::Kind.get_value()), + name: Some(self.volume_snapshot_name.clone()), + namespace: Some(self.namespace.clone()), + field_path: Default::default(), + resource_version: Default::default(), + uid: Default::default(), + }, + deletion_policy: self.vsc_retain_policy.into(), + driver: VSCResourceValues::Driver.get_value(), + source: VolumeSnapshotContentSource { + snapshot_handle: self.source_volume_handle.clone(), + ..Default::default() + }, + volume_snapshot_class_name: self.volume_snapshot_class.clone(), + source_volume_mode: Some(VSCResourceValues::SourceVolumeMode.get_value()), + }, + status: Some(VolumeSnapshotContentStatus { + snapshot_handle: self.source_volume_handle.clone(), + creation_time: Default::default(), + ready_to_use: Default::default(), + restore_size: Default::default(), + error: Default::default(), + volume_group_snapshot_handle: Default::default(), + }), + } + } +} diff --git a/src/k8s_ops/vsc/volume_snapshot_contents_tests.rs b/src/k8s_ops/vsc/volume_snapshot_contents_tests.rs new file mode 100644 index 0000000..b4984b6 --- /dev/null +++ b/src/k8s_ops/vsc/volume_snapshot_contents_tests.rs @@ -0,0 +1,61 @@ +#[cfg(test)] +mod tests { + use crate::k8s_ops::vsc::{ + retain_policy::VSCRetainPolicy, + volume_snapshot_contents_operator::VolumeSnapshotContentOperator, + }; + + #[tokio::test] + async fn test_construct_volume_snapshot_content_resource() { + let vsc_operator = VolumeSnapshotContentOperator::new( + "test-volume-snapshot-content".to_string(), + "default".to_string(), + "test-volume-snapshot".to_string(), + Some("ebs.csi.aws.com".to_string()), + Some("test-snapshot-handle".to_string()), + VSCRetainPolicy::Delete, + ); + let volume_snapshot_content = vsc_operator.construct_volume_snapshot_content_resource(); + assert_eq!( + volume_snapshot_content.metadata.name.unwrap(), + "test-volume-snapshot-content" + ); + assert_eq!( + volume_snapshot_content.metadata.namespace.unwrap(), + "default" + ); + assert_eq!( + volume_snapshot_content + .spec + .volume_snapshot_ref + .name + .unwrap(), + "test-volume-snapshot" + ); + assert_eq!( + volume_snapshot_content + .spec + .volume_snapshot_class_name + .unwrap(), + "ebs.csi.aws.com" + ); + assert_eq!( + volume_snapshot_content.spec.source.snapshot_handle.unwrap(), + "test-snapshot-handle" + ); + assert_eq!( + volume_snapshot_content.spec.source_volume_mode.unwrap(), + "Filesystem" + ); + assert_eq!( + volume_snapshot_content + .status + .as_ref() + .unwrap() + .snapshot_handle + .as_ref() + .unwrap(), + "test-snapshot-handle" + ); + } +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..d6cb0a4 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,4 @@ +pub mod aws_ops; +pub mod backup; +pub mod k8s_ops; +pub mod restore; diff --git a/src/restore/mod.rs b/src/restore/mod.rs new file mode 100644 index 0000000..5bf367a --- /dev/null +++ b/src/restore/mod.rs @@ -0,0 +1,4 @@ +#[cfg(feature = "restore")] +pub mod restore_operator; +#[cfg(feature = "restore")] +pub mod restore_payload; diff --git a/src/restore/restore_operator.rs b/src/restore/restore_operator.rs new file mode 100644 index 0000000..282744e --- /dev/null +++ b/src/restore/restore_operator.rs @@ -0,0 +1,192 @@ +use crate::k8s_ops::{ + pvc::{ + persistent_volume_claims::{check_if_pvc_exists, get_pvcs_available, KubePvcApi}, + persistent_volume_claims_operator::PVCOperator, + persistent_volume_claims_payload::PVCOperatorPayload, + }, + vs::volume_snapshots_operator::VolumeSnapshotOperator, + vsc::{ + volume_snapshot_contents::get_snapshot_handle, + volume_snapshot_contents_operator::VolumeSnapshotContentOperator, + }, +}; +use anyhow::{bail, Result}; +use kube::{api::PostParams, Api, Client}; +use kube_custom_resources_rs::snapshot_storage_k8s_io::v1::{ + volumesnapshotcontents::VolumeSnapshotContent, + volumesnapshots::{VolumeSnapshot, VolumeSnapshotStatus}, +}; +use tracing::info; + +use super::restore_payload::RestorePayload; + +/// A struct for restoring a PVC from a VolumeSnapshot +pub struct RestoreOperator; + +impl RestoreOperator { + /// Restores one or more PVCs from a VolumeSnapshot to a specific namespace + pub async fn restore(restore_payload: RestorePayload) -> Result<()> { + // Create a Kubernetes client + let k8s_client = Client::try_default().await?; + + // Define the VolumeSnapshot, VolumeSnapshotContent and PersistentVolumeClaim APIs + let restore_k8s_apis_struct = RestoreKubernetesApisStruct { + source_vs_api: Api::namespaced(k8s_client.clone(), restore_payload.source_ns()), + source_pvcs_api: KubePvcApi { + api: Api::namespaced(k8s_client.clone(), restore_payload.source_ns()), + }, + target_vs_api: Api::namespaced(k8s_client.clone(), restore_payload.target_ns()), + target_pvcs_api: KubePvcApi { + api: Api::namespaced(k8s_client.clone(), restore_payload.target_ns()), + }, + vsc_api: Api::all(k8s_client.clone()), + }; + + // Check if we will restore all PVCs in the namespace + let pvcs = if restore_payload.include_all_pvcs() { + get_pvcs_available(&restore_k8s_apis_struct.source_pvcs_api).await? + } else { + vec![restore_payload + .pvc_name() + .unwrap_or_else(|| panic!("PVC name is required when include_all_pvcs is false")) + .to_string()] + }; + + // We will iterate over the PVCs vector and restore each PVC + + for pvc in pvcs { + info!("Restoring PVC: {}", pvc); + let volume_snapshot_name = format!("{}-{}", restore_payload.vs_name_prefix(), pvc); + let volume_snapshot_content_name = + format!("{}-{}", restore_payload.vsc_name_prefix(), pvc); + + // Check if the PVC exists in the target namespace, it should not exist + check_if_pvc_exists(&restore_k8s_apis_struct.target_pvcs_api, &pvc, false).await?; + + let status: VolumeSnapshotStatus = match restore_k8s_apis_struct + .source_vs_api + .get(&volume_snapshot_name) + .await + { + Ok(snapshot) => snapshot.status.unwrap(), + Err(e) => { + bail!("Failed to get VolumeSnapshot status: {}", e) + } + }; + + let bound_vsc_name = status.bound_volume_snapshot_content_name.unwrap(); + let restore_size = status.restore_size.unwrap(); + + let snapshot_handle = + get_snapshot_handle(restore_k8s_apis_struct.vsc_api.clone(), &bound_vsc_name) + .await?; + + let vsc_operator = VolumeSnapshotContentOperator::new( + volume_snapshot_content_name.clone(), + restore_payload.target_ns().to_string(), + volume_snapshot_name.clone(), + Some(restore_payload.volume_snapshot_class().to_string()), + Some(snapshot_handle.clone()), + *restore_payload.vsc_retain_policy(), + ); + + let snapshot_content = vsc_operator.construct_volume_snapshot_content_resource(); + + let pp = PostParams::default(); + match restore_k8s_apis_struct + .vsc_api + .create(&pp, &snapshot_content) + .await + { + Ok(snapshot_content) => { + info!( + "{}", + format!( + "Created VolumeSnapshotContent: {} on namespace: {}", + snapshot_content.metadata.name.clone().unwrap(), + restore_payload.target_ns() + ) + ) + } + Err(e) => panic!("Failed to create VolumeSnapshotContent: {}", e), + } + + let vs_operator = VolumeSnapshotOperator::new( + volume_snapshot_name.clone(), + restore_payload.target_ns().to_string(), + restore_payload.volume_snapshot_class().to_string(), + None, + Some(volume_snapshot_content_name), + ); + + let target_volume_snapshot = vs_operator.construct_volume_snapshot_resource( + Some(snapshot_handle.to_string()), + Some(restore_size.to_string()), + *restore_payload.vsc_retain_policy(), + ); + + info!("Creating VolumeSnapshot in the target namespace..."); + let pp = PostParams::default(); + match restore_k8s_apis_struct + .target_vs_api + .create(&pp, &target_volume_snapshot) + .await + { + Ok(target_volume_snapshot) => { + info!( + "{}", + format!( + "Created VolumeSnapshot: {} on namespace: {}", + target_volume_snapshot.metadata.name.clone().unwrap(), + restore_payload.target_ns() + ) + ) + } + Err(e) => panic!("Failed to create VolumeSnapshot: {}", e), + } + + // Restore the PVC for each pvc available + let pvc_payload = PVCOperatorPayload::new( + pvc, + restore_payload.target_ns(), + Some(restore_payload.storage_class_name().to_string()), + None, + volume_snapshot_name, + restore_size, + ); + + let pvc_operator = PVCOperator::new(pvc_payload); + let pvc = pvc_operator.construct_persistent_volume_claim_resource(); + + info!("Restoring PVC..."); + let pp = PostParams::default(); + match restore_k8s_apis_struct + .target_pvcs_api + .api + .create(&pp, &pvc) + .await + { + Ok(pvc) => info!( + "{}", + format!( + "Restored PVC: {} on namespace: {}", + pvc.metadata.name.clone().unwrap(), + restore_payload.target_ns() + ) + ), + Err(e) => panic!("Failed to restore PVC: {}", e), + } + } + + Ok(()) + } +} + +/// A struct for holding the Kubernetes APIs for the restore operation +struct RestoreKubernetesApisStruct { + source_vs_api: Api, + source_pvcs_api: KubePvcApi, + target_vs_api: Api, + target_pvcs_api: KubePvcApi, + vsc_api: Api, +} diff --git a/src/restore/restore_payload.rs b/src/restore/restore_payload.rs new file mode 100644 index 0000000..ec0a2dc --- /dev/null +++ b/src/restore/restore_payload.rs @@ -0,0 +1,76 @@ +use crate::k8s_ops::vsc::retain_policy::VSCRetainPolicy; + +pub struct RestorePayload { + pub source_ns: String, + pub target_ns: String, + pub volume_snapshot_class: String, + pub pvc_name: Option, + pub include_all_pvcs: bool, + pub vs_name_prefix: String, + pub vsc_name_prefix: String, + pub storage_class_name: String, + pub vsc_retain_policy: VSCRetainPolicy, +} + +impl RestorePayload { + #[allow(clippy::too_many_arguments)] + pub fn new( + source_ns: impl Into, + target_ns: impl Into, + volume_snapshot_class: impl Into, + pvc_name: Option>, + include_all_pvcs: bool, + vs_name_prefix: impl Into, + vsc_name_prefix: impl Into, + storage_class_name: impl Into, + vsc_retain_policy: VSCRetainPolicy, + ) -> Self { + Self { + source_ns: source_ns.into(), + target_ns: target_ns.into(), + volume_snapshot_class: volume_snapshot_class.into(), + pvc_name: pvc_name.map(|pvc_name| pvc_name.into()), + include_all_pvcs, + vs_name_prefix: vs_name_prefix.into(), + vsc_name_prefix: vsc_name_prefix.into(), + storage_class_name: storage_class_name.into(), + vsc_retain_policy, + } + } + + pub fn source_ns(&self) -> &str { + &self.source_ns + } + + pub fn target_ns(&self) -> &str { + &self.target_ns + } + + pub fn volume_snapshot_class(&self) -> &str { + &self.volume_snapshot_class + } + + pub fn pvc_name(&self) -> Option<&str> { + self.pvc_name.as_deref() + } + + pub fn include_all_pvcs(&self) -> bool { + self.include_all_pvcs + } + + pub fn vs_name_prefix(&self) -> &str { + &self.vs_name_prefix + } + + pub fn vsc_name_prefix(&self) -> &str { + &self.vsc_name_prefix + } + + pub fn storage_class_name(&self) -> &str { + &self.storage_class_name + } + + pub fn vsc_retain_policy(&self) -> &VSCRetainPolicy { + &self.vsc_retain_policy + } +}