diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index c08407b6..a00892e2 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,4 +1,4 @@ -# Copyright 2022-2023, axodotdev +# Copyright 2022-2024, axodotdev # SPDX-License-Identifier: MIT or Apache-2.0 # # CI that: @@ -6,15 +6,14 @@ # * checks for a Git Tag that looks like a release # * builds artifacts with cargo-dist (archives, installers, hashes) # * uploads those artifacts to temporary workflow zip -# * on success, uploads the artifacts to a Github Release +# * on success, uploads the artifacts to a GitHub Release # -# Note that the Github Release will be created with a generated +# Note that the GitHub Release will be created with a generated # title/body based on your changelogs. name: Release - permissions: - contents: write + "contents": "write" # This task will run whenever you push a git tag that looks like a version # like "1.0.0", "v0.1.0-prerelease.1", "my-app/0.1.0", "releases/v1.0.0", etc. @@ -31,22 +30,22 @@ permissions: # packages versioned/released in lockstep). # # If you push multiple tags at once, separate instances of this workflow will -# spin up, creating an independent announcement for each one. However Github +# spin up, creating an independent announcement for each one. However, GitHub # will hard limit this to 3 tags per commit, as it will assume more tags is a # mistake. # # If there's a prerelease-style suffix to the version, then the release(s) # will be marked as a prerelease. on: + pull_request: push: tags: - '**[0-9]+.[0-9]+.[0-9]+*' - pull_request: jobs: # Run 'cargo dist plan' (or host) to determine what tasks we need to do plan: - runs-on: ubuntu-latest + runs-on: "ubuntu-20.04" outputs: val: ${{ steps.plan.outputs.manifest }} tag: ${{ !github.event.pull_request && github.ref_name || '' }} @@ -62,7 +61,12 @@ jobs: # we specify bash to get pipefail; it guards against the `curl` command # failing. otherwise `sh` won't catch that `curl` returned non-0 shell: bash - run: "curl --proto '=https' --tlsv1.2 -LsSf https://github.com/axodotdev/cargo-dist/releases/download/v0.6.2/cargo-dist-installer.sh | sh" + run: "curl --proto '=https' --tlsv1.2 -LsSf https://github.com/axodotdev/cargo-dist/releases/download/v0.19.1/cargo-dist-installer.sh | sh" + - name: Cache cargo-dist + uses: actions/upload-artifact@v4 + with: + name: cargo-dist-cache + path: ~/.cargo/bin/cargo-dist # sure would be cool if github gave us proper conditionals... # so here's a doubly-nested ternary-via-truthiness to try to provide the best possible # functionality based on whether this is a pull_request, and whether it's from a fork. @@ -70,15 +74,15 @@ jobs: # but also really annoying to build CI around when it needs secrets to work right.) - id: plan run: | - cargo dist ${{ !github.event.pull_request && format('host --steps=create --tag={0}', github.ref_name) || (github.event.pull_request.head.repo.fork && 'plan' || 'host --steps=check') }} --output-format=json > dist-manifest.json + cargo dist ${{ (!github.event.pull_request && format('host --steps=create --tag={0}', github.ref_name)) || 'plan' }} --output-format=json > plan-dist-manifest.json echo "cargo dist ran successfully" - cat dist-manifest.json - echo "manifest=$(jq -c "." dist-manifest.json)" >> "$GITHUB_OUTPUT" + cat plan-dist-manifest.json + echo "manifest=$(jq -c "." plan-dist-manifest.json)" >> "$GITHUB_OUTPUT" - name: "Upload dist-manifest.json" - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: - name: artifacts - path: dist-manifest.json + name: artifacts-plan-dist-manifest + path: plan-dist-manifest.json # Build and packages all the platform-specific things build-local-artifacts: @@ -105,18 +109,21 @@ jobs: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} BUILD_MANIFEST_NAME: target/distrib/${{ join(matrix.targets, '-') }}-dist-manifest.json steps: + - name: enable windows longpaths + run: | + git config --global core.longpaths true - uses: actions/checkout@v4 with: submodules: recursive - - uses: swatinem/rust-cache@v2 - name: Install cargo-dist run: ${{ matrix.install_dist }} # Get the dist-manifest - name: Fetch local artifacts - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: - name: artifacts + pattern: artifacts-* path: target/distrib/ + merge-multiple: true - name: Install dependencies run: | ${{ matrix.packages_install }} @@ -134,14 +141,14 @@ jobs: run: | # Parse out what we just built and upload it to scratch storage echo "paths<> "$GITHUB_OUTPUT" - jq --raw-output ".artifacts[]?.path | select( . != null )" dist-manifest.json >> "$GITHUB_OUTPUT" + jq --raw-output ".upload_files[]" dist-manifest.json >> "$GITHUB_OUTPUT" echo "EOF" >> "$GITHUB_OUTPUT" cp dist-manifest.json "$BUILD_MANIFEST_NAME" - name: "Upload artifacts" - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: - name: artifacts + name: artifacts-build-local-${{ join(matrix.targets, '_') }} path: | ${{ steps.cargo-dist.outputs.paths }} ${{ env.BUILD_MANIFEST_NAME }} @@ -159,14 +166,19 @@ jobs: - uses: actions/checkout@v4 with: submodules: recursive - - name: Install cargo-dist - run: "curl --proto '=https' --tlsv1.2 -LsSf https://github.com/axodotdev/cargo-dist/releases/download/v0.6.2/cargo-dist-installer.sh | sh" + - name: Install cached cargo-dist + uses: actions/download-artifact@v4 + with: + name: cargo-dist-cache + path: ~/.cargo/bin/ + - run: chmod +x ~/.cargo/bin/cargo-dist # Get all the local artifacts for the global tasks to use (for e.g. checksums) - name: Fetch local artifacts - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: - name: artifacts + pattern: artifacts-* path: target/distrib/ + merge-multiple: true - id: cargo-dist shell: bash run: | @@ -175,14 +187,14 @@ jobs: # Parse out what we just built and upload it to scratch storage echo "paths<> "$GITHUB_OUTPUT" - jq --raw-output ".artifacts[]?.path | select( . != null )" dist-manifest.json >> "$GITHUB_OUTPUT" + jq --raw-output ".upload_files[]" dist-manifest.json >> "$GITHUB_OUTPUT" echo "EOF" >> "$GITHUB_OUTPUT" cp dist-manifest.json "$BUILD_MANIFEST_NAME" - name: "Upload artifacts" - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: - name: artifacts + name: artifacts-build-global path: | ${{ steps.cargo-dist.outputs.paths }} ${{ env.BUILD_MANIFEST_NAME }} @@ -203,15 +215,19 @@ jobs: - uses: actions/checkout@v4 with: submodules: recursive - - name: Install cargo-dist - run: "curl --proto '=https' --tlsv1.2 -LsSf https://github.com/axodotdev/cargo-dist/releases/download/v0.6.2/cargo-dist-installer.sh | sh" + - name: Install cached cargo-dist + uses: actions/download-artifact@v4 + with: + name: cargo-dist-cache + path: ~/.cargo/bin/ + - run: chmod +x ~/.cargo/bin/cargo-dist # Fetch artifacts from scratch-storage - name: Fetch artifacts - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: - name: artifacts + pattern: artifacts-* path: target/distrib/ - # This is a harmless no-op for Github Releases, hosting for that happens in "announce" + merge-multiple: true - id: host shell: bash run: | @@ -220,12 +236,34 @@ jobs: cat dist-manifest.json echo "manifest=$(jq -c "." dist-manifest.json)" >> "$GITHUB_OUTPUT" - name: "Upload dist-manifest.json" - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: - name: artifacts + # Overwrite the previous copy + name: artifacts-dist-manifest path: dist-manifest.json + # Create a GitHub Release while uploading all files to it + - name: "Download GitHub Artifacts" + uses: actions/download-artifact@v4 + with: + pattern: artifacts-* + path: artifacts + merge-multiple: true + - name: Cleanup + run: | + # Remove the granular manifests + rm -f artifacts/*-dist-manifest.json + - name: Create GitHub Release + env: + PRERELEASE_FLAG: "${{ fromJson(steps.host.outputs.manifest).announcement_is_prerelease && '--prerelease' || '' }}" + ANNOUNCEMENT_TITLE: "${{ fromJson(steps.host.outputs.manifest).announcement_title }}" + ANNOUNCEMENT_BODY: "${{ fromJson(steps.host.outputs.manifest).announcement_github_body }}" + RELEASE_COMMIT: "${{ github.sha }}" + run: | + # Write and read notes from a file to avoid quoting breaking things + echo "$ANNOUNCEMENT_BODY" > $RUNNER_TEMP/notes.txt + + gh release create "${{ needs.plan.outputs.tag }}" --target "$RELEASE_COMMIT" $PRERELEASE_FLAG --title "$ANNOUNCEMENT_TITLE" --notes-file "$RUNNER_TEMP/notes.txt" artifacts/* - # Create a Github Release while uploading all files to it announce: needs: - plan @@ -241,20 +279,3 @@ jobs: - uses: actions/checkout@v4 with: submodules: recursive - - name: "Download Github Artifacts" - uses: actions/download-artifact@v3 - with: - name: artifacts - path: artifacts - - name: Cleanup - run: | - # Remove the granular manifests - rm -f artifacts/*-dist-manifest.json - - name: Create Github Release - uses: ncipollo/release-action@v1 - with: - tag: ${{ needs.plan.outputs.tag }} - name: ${{ fromJson(needs.host.outputs.val).announcement_title }} - body: ${{ fromJson(needs.host.outputs.val).announcement_github_body }} - prerelease: ${{ fromJson(needs.host.outputs.val).announcement_is_prerelease }} - artifacts: "artifacts/*" diff --git a/Cargo.lock b/Cargo.lock index 06e122f1..c0ed55c8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -360,9 +360,9 @@ checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de" [[package]] name = "async-trait" -version = "0.1.80" +version = "0.1.81" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6fa2087f2753a7da8cc1c0dbfcf89579dd57458e36769de5ac750b4671737ca" +checksum = "6e0c28dcc82d7c8ead5cb13beb15405b57b8546e93215673ff8ca0349a028107" dependencies = [ "proc-macro2", "quote", @@ -488,9 +488,9 @@ dependencies = [ [[package]] name = "built" -version = "0.7.3" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6a6c0b39c38fd754ac338b00a88066436389c0f029da5d37d1e01091d9b7c17" +checksum = "236e6289eda5a812bc6b53c3b024039382a2895fbbeef2d748b2931546d392c4" dependencies = [ "git2", ] @@ -577,9 +577,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.7" +version = "4.5.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5db83dced34638ad474f39f250d7fea9598bdd239eaced1bdf45d597da0f433f" +checksum = "64acc1846d54c1fe936a78dc189c34e28d3f5afc348403f28ecf53660b9b8462" dependencies = [ "clap_builder", "clap_derive", @@ -587,9 +587,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.7" +version = "4.5.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7e204572485eb3fbf28f871612191521df159bc3e15a9f5064c66dba3a8c05f" +checksum = "6fb8393d67ba2e7bfaf28a23458e4e2b543cc73a99595511eb207fdb8aede942" dependencies = [ "anstream", "anstyle", @@ -599,18 +599,18 @@ dependencies = [ [[package]] name = "clap_complete" -version = "4.5.6" +version = "4.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbca90c87c2a04da41e95d1856e8bcd22f159bdbfa147314d2ce5218057b0e58" +checksum = "5b4be9c4c4b1f30b78d8a750e0822b6a6102d97e62061c583a6c1dea2dfb33ae" dependencies = [ "clap", ] [[package]] name = "clap_derive" -version = "4.5.5" +version = "4.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c780290ccf4fb26629baa7a1081e68ced113f1d3ec302fa5948f1c381ebf06c6" +checksum = "2bac35c6dafb060fd4d275d9a4ffae97917c13a6327903a8be2153cd964f7085" dependencies = [ "heck 0.5.0", "proc-macro2", @@ -630,6 +630,16 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b6a852b24ab71dffc585bcb46eaf7959d175cb865a7152e35b348d1b2960422" +[[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 = "compact_str" version = "0.7.1" @@ -883,7 +893,7 @@ checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10" [[package]] name = "dropshot" version = "0.10.2-dev" -source = "git+https://github.com/oxidecomputer/dropshot#84aaeb7d7aadceaa77010e550811df6701bd5b55" +source = "git+https://github.com/oxidecomputer/dropshot#fb254ad92bbbc29d249fa514486a9217d027f4a5" dependencies = [ "async-stream", "async-trait", @@ -928,8 +938,9 @@ dependencies = [ [[package]] name = "dropshot_endpoint" version = "0.10.2-dev" -source = "git+https://github.com/oxidecomputer/dropshot#84aaeb7d7aadceaa77010e550811df6701bd5b55" +source = "git+https://github.com/oxidecomputer/dropshot#fb254ad92bbbc29d249fa514486a9217d027f4a5" dependencies = [ + "heck 0.5.0", "proc-macro2", "quote", "serde", @@ -1281,9 +1292,9 @@ checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" [[package]] name = "git2" -version = "0.18.3" +version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "232e6a7bfe35766bf715e55a88b39a700596c0ccfd88cd3680b4cdb40d66ef70" +checksum = "b903b73e45dc0c6c596f2d37eccece7c1c8bb6e4407b001096387c63d0d93724" dependencies = [ "bitflags 2.5.0", "libc", @@ -1595,6 +1606,15 @@ version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3" +[[package]] +name = "ipnetwork" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf466541e9d546596ee94f9f69590f89473455f88372423e0008fc1a7daf100e" +dependencies = [ + "serde", +] + [[package]] name = "is-docker" version = "0.2.0" @@ -1760,9 +1780,9 @@ checksum = "ae743338b92ff9146ce83992f766a31066a91a8c84a45e0e9f21e7cf6de6d346" [[package]] name = "libgit2-sys" -version = "0.16.2+1.7.2" +version = "0.17.0+1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee4126d8b4ee5c9d9ea891dd875cfdc1e9d0950437179104b183d7d8a74d24e8" +checksum = "10472326a8a6477c3c20a64547b0059e4b0d086869eee31e6d7da728a8eb7224" dependencies = [ "cc", "libc", @@ -1826,9 +1846,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.21" +version = "0.4.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" +checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" dependencies = [ "value-bag", ] @@ -2090,7 +2110,7 @@ dependencies = [ [[package]] name = "oxide" -version = "0.5.0+20240502.0" +version = "0.6.0+20240710.0" dependencies = [ "base64 0.22.1", "chrono", @@ -2113,7 +2133,7 @@ dependencies = [ [[package]] name = "oxide-cli" -version = "0.5.0+20240502.0" +version = "0.6.1+20240710.0" dependencies = [ "anyhow", "assert_cmd", @@ -2123,6 +2143,7 @@ dependencies = [ "chrono", "clap", "clap_complete", + "colored", "crossterm", "dialoguer", "dirs", @@ -2131,12 +2152,14 @@ dependencies = [ "expectorate", "futures", "httpmock", + "humantime", "indicatif", "log", "oauth2", "open", "oxide", "oxide-httpmock", + "oxnet", "predicates", "pretty_assertions", "rand", @@ -2151,13 +2174,15 @@ dependencies = [ "test-common", "thouart", "tokio", + "toml", + "toml_edit", "url", "uuid", ] [[package]] name = "oxide-httpmock" -version = "0.5.0+20240502.0" +version = "0.6.0+20240710.0" dependencies = [ "chrono", "httpmock", @@ -2167,6 +2192,17 @@ dependencies = [ "uuid", ] +[[package]] +name = "oxnet" +version = "0.1.0" +source = "git+https://github.com/oxidecomputer/oxnet#f37a7aaf5ca96d87af5f8194c19b20e276b5d5e1" +dependencies = [ + "ipnetwork", + "schemars", + "serde", + "serde_json", +] + [[package]] name = "parking" version = "2.2.0" @@ -2404,7 +2440,7 @@ dependencies = [ [[package]] name = "progenitor" version = "0.7.0" -source = "git+https://github.com/oxidecomputer/progenitor#4d364419eda10f3bcd5e90228d55f3925a22b30c" +source = "git+https://github.com/oxidecomputer/progenitor#0deadb0d0935785f4845380f9f7dd1841f907333" dependencies = [ "progenitor-client 0.7.0 (git+https://github.com/oxidecomputer/progenitor)", "progenitor-impl", @@ -2429,7 +2465,7 @@ dependencies = [ [[package]] name = "progenitor-client" version = "0.7.0" -source = "git+https://github.com/oxidecomputer/progenitor#4d364419eda10f3bcd5e90228d55f3925a22b30c" +source = "git+https://github.com/oxidecomputer/progenitor#0deadb0d0935785f4845380f9f7dd1841f907333" dependencies = [ "bytes", "futures-core", @@ -2443,7 +2479,7 @@ dependencies = [ [[package]] name = "progenitor-impl" version = "0.7.0" -source = "git+https://github.com/oxidecomputer/progenitor#4d364419eda10f3bcd5e90228d55f3925a22b30c" +source = "git+https://github.com/oxidecomputer/progenitor#0deadb0d0935785f4845380f9f7dd1841f907333" dependencies = [ "heck 0.5.0", "http 0.2.12", @@ -2464,7 +2500,7 @@ dependencies = [ [[package]] name = "progenitor-macro" version = "0.7.0" -source = "git+https://github.com/oxidecomputer/progenitor#4d364419eda10f3bcd5e90228d55f3925a22b30c" +source = "git+https://github.com/oxidecomputer/progenitor#0deadb0d0935785f4845380f9f7dd1841f907333" dependencies = [ "openapiv3", "proc-macro2", @@ -2908,18 +2944,18 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.203" +version = "1.0.204" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7253ab4de971e72fb7be983802300c30b5a7f0c2e56fab8abfc6a214307c0094" +checksum = "bc76f558e0cbb2a839d37354c575f1dc3fdc6546b5be373ba43d95f231bf7c12" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.203" +version = "1.0.204" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "500cbc0ebeb6f46627f50f3f5811ccf6bf00643be300b4c3eabc0ef55dc5b5ba" +checksum = "e0cd7e117be63d3c3678776753929474f3b04a43a080c744d6b0ae2a8c28e222" dependencies = [ "proc-macro2", "quote", @@ -2939,9 +2975,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.117" +version = "1.0.120" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "455182ea6142b14f93f4bc5320a2b31c1f266b66a4a5c858b013302a5d8cbfc3" +checksum = "4e0d21c9a8cae1235ad58a00c11cb40d4b1e5c784f1ef2c537876ed6ffd8b7c5" dependencies = [ "itoa", "ryu", @@ -3262,9 +3298,9 @@ checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" [[package]] name = "syn" -version = "2.0.68" +version = "2.0.70" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "901fa70d88b9d6c98022e23b4136f9f3e54e4662c3bc1bd1d84a42a9a0f0c1e9" +checksum = "2f0209b68b3613b093e0ec905354eccaedcfe83b8cb37cbdeae64026c3064c16" dependencies = [ "proc-macro2", "quote", @@ -3374,18 +3410,18 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.61" +version = "1.0.62" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c546c80d6be4bc6a00c0f01730c08df82eaa7a7a61f11d656526506112cc1709" +checksum = "f2675633b1499176c2dff06b0856a27976a8f9d436737b4cf4f312d4d91d8bbb" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.61" +version = "1.0.62" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46c3384250002a6d5af4d114f2845d37b57521033f30d5c3f46c4d70e1197533" +checksum = "d20468752b09f49e909e55a5d338caa8bedf615594e9d80bc4c565d30faf798c" dependencies = [ "proc-macro2", "quote", @@ -3584,9 +3620,9 @@ dependencies = [ [[package]] name = "toml_edit" -version = "0.22.14" +version = "0.22.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f21c7aaf97f1bd9ca9d4f9e73b0a6c74bd5afef56f2bc931943a6e1c37e04e38" +checksum = "d59a3a72298453f564e2b111fa896f8d07fabb36f51f06d7e875fc5e0b5a3ef1" dependencies = [ "indexmap", "serde", @@ -3690,7 +3726,7 @@ checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" [[package]] name = "typify" version = "0.1.0" -source = "git+https://github.com/oxidecomputer/typify#b462bde4209a1e75b59900dea213a840f216a6b7" +source = "git+https://github.com/oxidecomputer/typify#abf38e3322ca4c03b01751b33430e2e92549f821" dependencies = [ "typify-impl", "typify-macro", @@ -3699,7 +3735,7 @@ dependencies = [ [[package]] name = "typify-impl" version = "0.1.0" -source = "git+https://github.com/oxidecomputer/typify#b462bde4209a1e75b59900dea213a840f216a6b7" +source = "git+https://github.com/oxidecomputer/typify#abf38e3322ca4c03b01751b33430e2e92549f821" dependencies = [ "heck 0.5.0", "log", @@ -3718,7 +3754,7 @@ dependencies = [ [[package]] name = "typify-macro" version = "0.1.0" -source = "git+https://github.com/oxidecomputer/typify#b462bde4209a1e75b59900dea213a840f216a6b7" +source = "git+https://github.com/oxidecomputer/typify#abf38e3322ca4c03b01751b33430e2e92549f821" dependencies = [ "proc-macro2", "quote", @@ -3824,9 +3860,9 @@ checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" [[package]] name = "uuid" -version = "1.9.1" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5de17fd2f7da591098415cff336e12965a28061ddace43b59cb3c430179c9439" +checksum = "81dfa00651efa65069b0b6b651f4aaa31ba9e3c3ce0137aaad053604ee7e0314" dependencies = [ "getrandom", "serde", diff --git a/Cargo.toml b/Cargo.toml index 7ed900f0..9ba39a39 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,12 +11,13 @@ resolver = "2" [workspace.dependencies] anyhow = "1.0.86" assert_cmd = "2.0.14" -async-trait = "0.1.80" +async-trait = "0.1.81" base64 = "0.22.1" -built = { version = "0.7.3", features = ["git2"] } +built = { version = "0.7.4", features = ["git2"] } chrono = { version = "0.4.38", features = ["serde"] } -clap = { version = "4.5.7", features = ["derive", "string", "env"] } -clap_complete = "4.5.6" +clap = { version = "4.5.9", features = ["derive", "string", "env"] } +clap_complete = "4.5.8" +colored = "2.1.0" crossterm = { version = "0.27.0", features = [ "event-stream" ] } dialoguer = "0.10.4" dirs = "4.0.0" @@ -25,13 +26,15 @@ env_logger = "0.10.2" expectorate = { version = "1.1.0", features = ["predicates"] } futures = "0.3.30" httpmock = "0.6.8" +humantime = "2" indicatif = "0.17" -log = "0.4.21" +log = "0.4.22" newline-converter = "0.3.0" oauth2 = "4.4.2" open = "4.2.0" -oxide = { path = "sdk", version = "0.5.0" } -oxide-httpmock = { path = "sdk-httpmock", version = "0.5.0" } +oxide = { path = "sdk", version = "0.6.0" } +oxide-httpmock = { path = "sdk-httpmock", version = "0.6.0" } +oxnet = { git = "https://github.com/oxidecomputer/oxnet" } predicates = "3.1.0" pretty_assertions = "1.4.0" progenitor = { git = "https://github.com/oxidecomputer/progenitor" } @@ -45,7 +48,7 @@ reqwest = "0.11.27" rustfmt-wrapper = "0.2.1" schemars = { version = "0.8.20", features = ["chrono", "uuid1"] } serde = { version = "1.0.203", features = ["derive"] } -serde_json = "1.0.116" +serde_json = "1.0.120" similar = "2.4.0" tabwriter = "1.4.0" thiserror = { version = "1", default-features = false } @@ -54,26 +57,20 @@ test-common = { path = "test-common" } thouart = { git = "https://github.com/oxidecomputer/thouart.git" } tokio = { version = "1.38.0", features = ["full"] } toml = "0.8.14" -toml_edit = "0.22.14" +toml_edit = "0.22.15" url = "2.5.2" uuid = { version = "1.9.1", features = ["serde", "v4"] } # Config for 'cargo dist' [workspace.metadata.dist] # The preferred cargo-dist version to use in CI (Cargo.toml SemVer syntax) -cargo-dist-version = "0.6.2" +cargo-dist-version = "0.19.1" # CI backends to support -ci = ["github"] +ci = "github" # The installers to generate for each app installers = [] # Target platforms to build apps for (Rust target-triple syntax) -targets = [ - "x86_64-unknown-linux-gnu", - "aarch64-apple-darwin", - "x86_64-apple-darwin", - "x86_64-unknown-linux-musl", - "x86_64-pc-windows-msvc", -] +targets = ["aarch64-apple-darwin", "x86_64-apple-darwin", "x86_64-unknown-linux-gnu", "x86_64-unknown-linux-musl", "x86_64-pc-windows-msvc"] # Publish jobs to run in CI pr-run-mode = "plan" diff --git a/README.adoc b/README.adoc index 0fd0d8ca..b9b571aa 100644 --- a/README.adoc +++ b/README.adoc @@ -6,7 +6,7 @@ published to crates.io. All are derived from the Oxide API OpenAPI spec. ## Generation -Generation of the CLI, SDK, and mocking library +Generation of the CLI, SDK, and mocking library use https://github.com/oxidecomputer/progenitor[`progenitor`] for code generation from the OpenAPI description of the Oxide API. Typically `progenitor` is used via a macro or `build.rs`, here we use an @@ -67,4 +67,4 @@ Use `cargo publish -p oxide` and `cargo publish -p oxide-httpmock`. Tag the repo with the version number (`vMAJOR.MINOR.PATCH+API_VERSION`) and push change and tags. This will kick off the `cargo-dist` workflow to generate -a release with binaries. \ No newline at end of file +a release with binaries. diff --git a/cli/Cargo.toml b/cli/Cargo.toml index 0f7fb1b7..fb192fd3 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "oxide-cli" description = "CLI for the Oxide rack" -version = "0.5.0+20240502.0" +version = "0.6.1+20240710.0" edition = "2021" license = "MPL-2.0" repository = "https://github.com/oxidecomputer/oxide.rs" @@ -24,16 +24,19 @@ base64 = { workspace = true } chrono = { workspace = true } clap = { workspace = true } clap_complete = { workspace = true } +colored = { workspace = true } crossterm = { workspace = true } dialoguer = { workspace = true } dirs = { workspace = true } env_logger = { workspace = true } futures = { workspace = true } +humantime = { workspace = true } indicatif = { workspace = true } log = { workspace = true } oauth2 = { workspace = true } open = { workspace = true } oxide = { workspace = true } +oxnet = { workspace = true } predicates = { workspace = true } ratatui = { workspace = true } reqwest = { workspace = true, features = ["native-tls-vendored"] } @@ -42,6 +45,8 @@ serde = { workspace = true } serde_json = { workspace = true } tabwriter = { workspace = true } thouart = { workspace = true } +toml = { workspace = true } +toml_edit = { workspace = true } tokio = { workspace = true } url = { workspace = true } uuid = { workspace = true } diff --git a/cli/README.md b/cli/README.md index fd43bbdc..3b7611a1 100644 --- a/cli/README.md +++ b/cli/README.md @@ -2,12 +2,23 @@ ## Installation -Build with `cargo build --bin oxide` and add the executable in `target/debug` to your `PATH`. +You can find pre-built binaries for various platforms +[here](https://github.com/oxidecomputer/oxide.rs/releases). Look for the most +recent release whose version suffix matches the version of Oxide software +currently deployed. For example, if you're running the `20240502.0` release, +use `0.5.0+20240502.0`. + +You can build the CLI yourself using `cargo build --bin oxide`; then add the +executable in `target/debug` to your `PATH` or copy it to another location. ## Authentication -There are two ways to authenticate against the Oxide rack using the CLI: +To authenticate, use `oxide auth login --host my.oxide.host`. This will add a +new profile to `$HOME/.config/oxide/credentials.toml` (and create the file if +necessary). The profile will derive its name from the name of the Oxide Silo. -- Environment variables: You can set the `OXIDE_HOST` and `OXIDE_TOKEN` environment variables. This method is useful for service accounts. +The first time you authenticate, the `default-profile` will be set in +`$HOME/.config/oxide/config.toml`. Edit that file to change the default later. -- Configuration file: When running the `oxide auth login` command, a `$HOME/.config/oxide/hosts.toml` file is generated. This file contains sensitive information like your token and user ID. +If you have a token value, you may set the `OXIDE_HOST` and `OXIDE_TOKEN` +environment variables. This method can be useful for service accounts. diff --git a/cli/docs/cli.json b/cli/docs/cli.json index 4572f881..c49d0e5d 100644 --- a/cli/docs/cli.json +++ b/cli/docs/cli.json @@ -1,6 +1,7 @@ { "name": "oxide", - "version": "0.5.0+20240502.0", + "version": "0.6.1+20240710.0", + "about": "Control an Oxide environment", "args": [ { "long": "cacert", @@ -18,6 +19,10 @@ "long": "insecure", "help": "Disable certificate validation and hostname verification" }, + { + "long": "profile", + "help": "Configuration profile to use for commands" + }, { "long": "resolve", "help": "Modify name resolution" @@ -79,8 +84,8 @@ "subcommands": [ { "name": "login", - "about": "Authenticate with an Oxide host.", - "long_about": "Authenticate with an Oxide host.\n\nAlternatively, pass in a token on standard input by using `--with-token`.\n\n # start interactive setup\n $ oxide auth login\n\n # authenticate against a specific Oxide instance by reading the token\n # from a file\n $ oxide auth login --with-token --host oxide.internal < mytoken.txt\n\n # authenticate with a specific Oxide instance\n $ oxide auth login --host oxide.internal\n\n # authenticate with an insecure Oxide instance (not recommended)\n $ oxide auth login --host http://oxide.internal", + "about": "Authenticate with an Oxide Silo", + "long_about": "Authenticate with an Oxide Silo\n\n # authenticate with a specific Oxide silo\n $ oxide auth login --host oxide.internal\n\n # authenticate with an insecure Oxide silo (not recommended)\n $ oxide auth login --host http://oxide.internal", "args": [ { "long": "browser", @@ -89,57 +94,36 @@ { "long": "host", "short": "H", - "help": "The host of the Oxide instance to authenticate with. This assumes http; specify an `http://` prefix if needed", + "help": "The host of the Oxide instance to authenticate with. This assumes https; specify an `http://` prefix if needed", "required": true }, { "long": "no-browser", "help": "Print the authentication URL rather than opening a browser window" - }, - { - "long": "with-token", - "help": "Read token from standard input" } ] }, { "name": "logout", - "about": "Removes saved authentication information.", - "long_about": "Removes saved authentication information.\n\nThis command does not invalidate any tokens from the hosts.", + "about": "Removes saved authentication information from profiles.", + "long_about": "Removes saved authentication information from profiles.\n\nThis command does not invalidate any tokens.", "args": [ { "long": "all", "short": "a", - "help": "If set, all known hosts and authentication information will be deleted" + "help": "If set, authentication information from all profiles will be deleted" }, { "long": "force", "short": "f", "help": "Skip confirmation prompt" - }, - { - "long": "host", - "short": "H", - "help": "Remove authentication information for a single host" } ] }, { "name": "status", "about": "Verifies and displays information about your authentication state.", - "long_about": "Verifies and displays information about your authentication state.\n\nThis command validates the authentication state for each Oxide environment\nin the current configuration. These hosts may be from your hosts.toml file\nand/or $OXIDE_HOST environment variable.", - "args": [ - { - "long": "host", - "short": "H", - "help": "Specific hostname to validate" - }, - { - "long": "show-token", - "short": "t", - "help": "Display the auth token" - } - ] + "long_about": "Verifies and displays information about your authentication state.\n\nThis command validates the authentication state for each profile in the\ncurrent configuration." } ] }, @@ -3120,6 +3104,10 @@ } ] }, + { + "name": "show-status", + "about": "Get the status of switch ports." + }, { "name": "status", "about": "Get switch port status", @@ -3148,6 +3136,149 @@ { "name": "networking", "subcommands": [ + { + "name": "addr", + "about": "Manage switch port addresses.", + "subcommands": [ + { + "name": "add", + "about": "Add an address to a port configuration", + "args": [ + { + "long": "addr", + "help": "Address to add", + "required": true + }, + { + "long": "lot", + "help": "Address lot to allocate from", + "required": true + }, + { + "long": "port", + "values": [ + "qsfp0", + "qsfp1", + "qsfp2", + "qsfp3", + "qsfp4", + "qsfp5", + "qsfp6", + "qsfp7", + "qsfp8", + "qsfp9", + "qsfp10", + "qsfp11", + "qsfp12", + "qsfp13", + "qsfp14", + "qsfp15", + "qsfp16", + "qsfp17", + "qsfp18", + "qsfp19", + "qsfp20", + "qsfp21", + "qsfp22", + "qsfp23", + "qsfp24", + "qsfp25", + "qsfp26", + "qsfp27", + "qsfp28", + "qsfp29", + "qsfp30", + "qsfp31" + ], + "help": "Port to add the port to", + "required": true + }, + { + "long": "rack", + "help": "Id of the rack to add the address to", + "required": true + }, + { + "long": "switch", + "values": [ + "switch0", + "switch1" + ], + "help": "Switch to add the address to", + "required": true + }, + { + "long": "vlan", + "help": "Optional VLAN to assign to the address" + } + ] + }, + { + "name": "del", + "about": "Remove an address from a port configuration", + "args": [ + { + "long": "addr", + "help": "Address to remove", + "required": true + }, + { + "long": "port", + "values": [ + "qsfp0", + "qsfp1", + "qsfp2", + "qsfp3", + "qsfp4", + "qsfp5", + "qsfp6", + "qsfp7", + "qsfp8", + "qsfp9", + "qsfp10", + "qsfp11", + "qsfp12", + "qsfp13", + "qsfp14", + "qsfp15", + "qsfp16", + "qsfp17", + "qsfp18", + "qsfp19", + "qsfp20", + "qsfp21", + "qsfp22", + "qsfp23", + "qsfp24", + "qsfp25", + "qsfp26", + "qsfp27", + "qsfp28", + "qsfp29", + "qsfp30", + "qsfp31" + ], + "help": "Port to remove the address from", + "required": true + }, + { + "long": "rack", + "help": "Id of the rack to remove the address from", + "required": true + }, + { + "long": "switch", + "values": [ + "switch0", + "switch1" + ], + "help": "Switch to remove the address from", + "required": true + } + ] + } + ] + }, { "name": "address-lot", "subcommands": [ @@ -3342,33 +3473,48 @@ { "name": "bgp", "subcommands": [ + { + "name": "announce", + "about": "Announce a prefix over BGP.", + "long_about": "Announce a prefix over BGP.\n\nThis command adds the provided prefix to the specified announce set. It is\nrequired that the prefix be available in the given address lot. The add is\nperformed as a read-modify-write on the specified address lot.", + "args": [ + { + "long": "address-lot", + "help": "The address lot to draw from", + "required": true + }, + { + "long": "announce-set", + "help": "The announce set to announce from", + "required": true + }, + { + "long": "description" + }, + { + "long": "prefix", + "help": "The prefix to announce", + "required": true + } + ] + }, { "name": "announce-set", "subcommands": [ { - "name": "create", - "about": "Create new BGP announce set", + "name": "delete", + "about": "Delete BGP announce set", "args": [ { - "long": "description" - }, - { - "long": "json-body", - "help": "Path to a file that contains the full json body.", + "long": "name-or-id", + "help": "A name or id to use when selecting BGP port settings", "required": true - }, - { - "long": "json-body-template", - "help": "XXX" - }, - { - "long": "name" } ] }, { - "name": "delete", - "about": "Delete BGP announce set", + "name": "list", + "about": "Get originated routes for a BGP configuration", "args": [ { "long": "name-or-id", @@ -3378,13 +3524,24 @@ ] }, { - "name": "list", - "about": "Get originated routes for a BGP configuration", + "name": "update", + "about": "Update BGP announce set", + "long_about": "If the announce set exists, this endpoint replaces the existing announce set with the one specified.", "args": [ { - "long": "name-or-id", - "help": "A name or id to use when selecting BGP port settings", + "long": "description" + }, + { + "long": "json-body", + "help": "Path to a file that contains the full json body.", "required": true + }, + { + "long": "json-body-template", + "help": "XXX" + }, + { + "long": "name" } ] } @@ -3459,6 +3616,88 @@ } ] }, + { + "name": "filter", + "about": "Add a filtering requirement to a BGP session.", + "long_about": "Add a filtering requirement to a BGP session.\n\nThe Oxide BGP implementation can filter prefixes received from peers\non import and filter prefixes sent to peers on export. This command\nprovides a way to specify import/export filtering. Filtering is a\nproperty of the BGP peering settings found in port settings configuration.\nThis command works by performing a read-modify-write on the port settings\nconfiguration identified by the specified rack/switch/port.", + "args": [ + { + "long": "allowed", + "help": "Prefixes to allow for the peer" + }, + { + "long": "direction", + "values": [ + "import", + "export" + ], + "help": "Whether to apply the filter to imported or exported prefixes", + "required": true + }, + { + "long": "no-filtering", + "help": "Do not filter" + }, + { + "long": "peer", + "help": "Peer to apply allow list to", + "required": true + }, + { + "long": "port", + "values": [ + "qsfp0", + "qsfp1", + "qsfp2", + "qsfp3", + "qsfp4", + "qsfp5", + "qsfp6", + "qsfp7", + "qsfp8", + "qsfp9", + "qsfp10", + "qsfp11", + "qsfp12", + "qsfp13", + "qsfp14", + "qsfp15", + "qsfp16", + "qsfp17", + "qsfp18", + "qsfp19", + "qsfp20", + "qsfp21", + "qsfp22", + "qsfp23", + "qsfp24", + "qsfp25", + "qsfp26", + "qsfp27", + "qsfp28", + "qsfp29", + "qsfp30", + "qsfp31" + ], + "help": "Port to add the port to", + "required": true + }, + { + "long": "rack", + "help": "Id of the rack to add the address to", + "required": true + }, + { + "long": "switch", + "values": [ + "switch0", + "switch1" + ], + "help": "Switch to add the address to", + "required": true + } + ] + }, { "name": "history", "about": "Get BGP router message history", @@ -3486,29 +3725,394 @@ } ] }, + { + "name": "peer", + "about": "Manage BGP peers.", + "long_about": "Manage BGP peers.\n\nThis command provides add and delete subcommands for managing BGP peers.\nBGP peer configuration is a part of a switch port settings configuration.\nThe peer add and remove subcommands perform read-modify-write operations\non switch port settings objects to manage BGP peer configurations.", + "subcommands": [ + { + "name": "add", + "about": "Add a BGP peer to a port configuration", + "args": [ + { + "long": "addr", + "help": "Address of the peer to add", + "required": true + }, + { + "long": "allowed-exports", + "help": "Prefixes that may be exported to the peer. Empty list means all prefixes allowed" + }, + { + "long": "allowed-imports", + "help": "Prefixes that may be imported form the peer. Empty list means all prefixes allowed" + }, + { + "long": "bgp-config", + "help": "BGP configuration this peer is associated with", + "required": true + }, + { + "long": "communities", + "help": "Include the provided communities in updates sent to the peer" + }, + { + "long": "connect-retry", + "help": "How long to to wait between TCP connection retries (seconds)" + }, + { + "long": "delay-open", + "help": "How long to delay sending an open request after establishing a TCP session (seconds)" + }, + { + "long": "enforce-first-as", + "help": "Enforce that the first AS in paths received from this peer is the peer's AS" + }, + { + "long": "hold-time", + "help": "How long to hold peer connections between keepalives (seconds)" + }, + { + "long": "idle-hold-time", + "help": "How long to hold a peer in idle before attempting a new session (seconds)" + }, + { + "long": "keepalive", + "help": "How often to send keepalive requests (seconds)" + }, + { + "long": "local-pref", + "help": "Apply a local preference to routes received from this peer" + }, + { + "long": "md5-auth-key", + "help": "Use the given key for TCP-MD5 authentication with the peer" + }, + { + "long": "min-ttl", + "help": "Require messages from a peer have a minimum IP time to live field" + }, + { + "long": "multi-exit-discriminator", + "help": "Apply the provided multi-exit discriminator (MED) updates sent to the peer" + }, + { + "long": "port", + "values": [ + "qsfp0", + "qsfp1", + "qsfp2", + "qsfp3", + "qsfp4", + "qsfp5", + "qsfp6", + "qsfp7", + "qsfp8", + "qsfp9", + "qsfp10", + "qsfp11", + "qsfp12", + "qsfp13", + "qsfp14", + "qsfp15", + "qsfp16", + "qsfp17", + "qsfp18", + "qsfp19", + "qsfp20", + "qsfp21", + "qsfp22", + "qsfp23", + "qsfp24", + "qsfp25", + "qsfp26", + "qsfp27", + "qsfp28", + "qsfp29", + "qsfp30", + "qsfp31" + ], + "help": "Port to add the peer to", + "required": true + }, + { + "long": "rack", + "help": "Id of the rack to add the peer to", + "required": true + }, + { + "long": "remote-asn", + "help": "Require that a peer has a specified ASN" + }, + { + "long": "switch", + "values": [ + "switch0", + "switch1" + ], + "help": "Switch to add the peer to", + "required": true + }, + { + "long": "vlan-id", + "help": "Associate a VLAN ID with a peer" + } + ] + }, + { + "name": "del", + "about": "Remove a BGP from a port configuration", + "args": [ + { + "long": "addr", + "help": "Address of the peer to remove", + "required": true + }, + { + "long": "port", + "values": [ + "qsfp0", + "qsfp1", + "qsfp2", + "qsfp3", + "qsfp4", + "qsfp5", + "qsfp6", + "qsfp7", + "qsfp8", + "qsfp9", + "qsfp10", + "qsfp11", + "qsfp12", + "qsfp13", + "qsfp14", + "qsfp15", + "qsfp16", + "qsfp17", + "qsfp18", + "qsfp19", + "qsfp20", + "qsfp21", + "qsfp22", + "qsfp23", + "qsfp24", + "qsfp25", + "qsfp26", + "qsfp27", + "qsfp28", + "qsfp29", + "qsfp30", + "qsfp31" + ], + "help": "Port to remove the peer from", + "required": true + }, + { + "long": "rack", + "help": "Id of the rack to remove the peer from", + "required": true + }, + { + "long": "switch", + "values": [ + "switch0", + "switch1" + ], + "help": "Switch to remove the peer from", + "required": true + } + ] + } + ] + }, + { + "name": "show-status", + "about": "Get the status of BGP on the rack.", + "long_about": "Get the status of BGP on the rack.\n\nThis will show the peering status for all peers on all switches." + }, { "name": "status", "about": "Get BGP peer status" + }, + { + "name": "withdraw", + "about": "Withdraw a prefix over BGP.", + "long_about": "Withdraw a prefix over BGP.\n\nThis command removes the provided prefix to the specified announce set.\nThe remove is performed as a read-modify-write on the specified address lot.", + "args": [ + { + "long": "announce-set", + "help": "The announce set to withdraw from", + "required": true + }, + { + "long": "prefix", + "help": "The prefix to withdraw", + "required": true + } + ] } ] }, { - "name": "loopback-address", + "name": "link", + "about": "Manage switch port links.", + "long_about": "Manage switch port links.\n\nLinks carry layer-2 Ethernet properties for a lane or set of lanes on a\nswitch port. Lane geometry is defined in physical port settings. At the\npresent time only single lane configurations are supported, and thus only\na single link per physical port is supported.", "subcommands": [ { - "name": "create", - "about": "Create loopback address", + "name": "add", + "about": "Add a link to a port", "args": [ { - "long": "address", - "help": "The address to create." + "long": "autoneg", + "help": "Whether or not to set auto-negotiation" }, { - "long": "address-lot", - "help": "The name or id of the address lot this loopback address will pull an address from." + "long": "fec", + "help": "The forward error correction mode of the link", + "required": true }, { - "long": "anycast", + "long": "mtu", + "help": "Maximum transmission unit for the link" + }, + { + "long": "port", + "values": [ + "qsfp0", + "qsfp1", + "qsfp2", + "qsfp3", + "qsfp4", + "qsfp5", + "qsfp6", + "qsfp7", + "qsfp8", + "qsfp9", + "qsfp10", + "qsfp11", + "qsfp12", + "qsfp13", + "qsfp14", + "qsfp15", + "qsfp16", + "qsfp17", + "qsfp18", + "qsfp19", + "qsfp20", + "qsfp21", + "qsfp22", + "qsfp23", + "qsfp24", + "qsfp25", + "qsfp26", + "qsfp27", + "qsfp28", + "qsfp29", + "qsfp30", + "qsfp31" + ], + "help": "Port to add the link to", + "required": true + }, + { + "long": "rack", + "help": "Id of the rack to add the link to", + "required": true + }, + { + "long": "speed", + "help": "The speed of the link", + "required": true + }, + { + "long": "switch", + "values": [ + "switch0", + "switch1" + ], + "help": "Switch to add the link to", + "required": true + } + ] + }, + { + "name": "del", + "about": "Remove a link from a port", + "args": [ + { + "long": "port", + "values": [ + "qsfp0", + "qsfp1", + "qsfp2", + "qsfp3", + "qsfp4", + "qsfp5", + "qsfp6", + "qsfp7", + "qsfp8", + "qsfp9", + "qsfp10", + "qsfp11", + "qsfp12", + "qsfp13", + "qsfp14", + "qsfp15", + "qsfp16", + "qsfp17", + "qsfp18", + "qsfp19", + "qsfp20", + "qsfp21", + "qsfp22", + "qsfp23", + "qsfp24", + "qsfp25", + "qsfp26", + "qsfp27", + "qsfp28", + "qsfp29", + "qsfp30", + "qsfp31" + ], + "help": "Port to remove the link from", + "required": true + }, + { + "long": "rack", + "help": "Id of the rack to remove the link from", + "required": true + }, + { + "long": "switch", + "values": [ + "switch0", + "switch1" + ], + "help": "Switch to remove the link from", + "required": true + } + ] + } + ] + }, + { + "name": "loopback-address", + "subcommands": [ + { + "name": "create", + "about": "Create loopback address", + "args": [ + { + "long": "address", + "help": "The address to create." + }, + { + "long": "address-lot", + "help": "The name or id of the address lot this loopback address will pull an address from." + }, + { + "long": "anycast", "values": [ "true", "false" @@ -3637,6 +4241,10 @@ } ] }, + { + "name": "show", + "about": "Get the configuration of switch ports." + }, { "name": "view", "about": "Get information about switch port", @@ -3827,6 +4435,294 @@ } ] }, + { + "name": "router", + "subcommands": [ + { + "name": "create", + "about": "Create VPC router", + "args": [ + { + "long": "description" + }, + { + "long": "json-body", + "help": "Path to a file that contains the full json body." + }, + { + "long": "json-body-template", + "help": "XXX" + }, + { + "long": "name" + }, + { + "long": "project", + "help": "Name or ID of the project, only required if `vpc` is provided as a `Name`" + }, + { + "long": "vpc", + "help": "Name or ID of the VPC", + "required": true + } + ] + }, + { + "name": "delete", + "about": "Delete router", + "args": [ + { + "long": "project", + "help": "Name or ID of the project, only required if `vpc` is provided as a `Name`" + }, + { + "long": "router", + "help": "Name or ID of the router", + "required": true + }, + { + "long": "vpc", + "help": "Name or ID of the VPC" + } + ] + }, + { + "name": "list", + "about": "List routers", + "args": [ + { + "long": "limit", + "help": "Maximum number of items returned by a single call" + }, + { + "long": "project", + "help": "Name or ID of the project, only required if `vpc` is provided as a `Name`" + }, + { + "long": "sort-by", + "values": [ + "name_ascending", + "name_descending", + "id_ascending" + ] + }, + { + "long": "vpc", + "help": "Name or ID of the VPC", + "required": true + } + ] + }, + { + "name": "route", + "subcommands": [ + { + "name": "create", + "about": "Create route", + "args": [ + { + "long": "description" + }, + { + "long": "json-body", + "help": "Path to a file that contains the full json body.", + "required": true + }, + { + "long": "json-body-template", + "help": "XXX" + }, + { + "long": "name" + }, + { + "long": "project", + "help": "Name or ID of the project, only required if `vpc` is provided as a `Name`" + }, + { + "long": "router", + "help": "Name or ID of the router", + "required": true + }, + { + "long": "vpc", + "help": "Name or ID of the VPC, only required if `router` is provided as a `Name`" + } + ] + }, + { + "name": "delete", + "about": "Delete route", + "args": [ + { + "long": "project", + "help": "Name or ID of the project, only required if `vpc` is provided as a `Name`" + }, + { + "long": "route", + "help": "Name or ID of the route", + "required": true + }, + { + "long": "router", + "help": "Name or ID of the router" + }, + { + "long": "vpc", + "help": "Name or ID of the VPC, only required if `router` is provided as a `Name`" + } + ] + }, + { + "name": "list", + "about": "List routes", + "long_about": "List the routes associated with a router in a particular VPC.", + "args": [ + { + "long": "limit", + "help": "Maximum number of items returned by a single call" + }, + { + "long": "project", + "help": "Name or ID of the project, only required if `vpc` is provided as a `Name`" + }, + { + "long": "router", + "help": "Name or ID of the router", + "required": true + }, + { + "long": "sort-by", + "values": [ + "name_ascending", + "name_descending", + "id_ascending" + ] + }, + { + "long": "vpc", + "help": "Name or ID of the VPC, only required if `router` is provided as a `Name`" + } + ] + }, + { + "name": "update", + "about": "Update route", + "args": [ + { + "long": "description" + }, + { + "long": "json-body", + "help": "Path to a file that contains the full json body.", + "required": true + }, + { + "long": "json-body-template", + "help": "XXX" + }, + { + "long": "name" + }, + { + "long": "project", + "help": "Name or ID of the project, only required if `vpc` is provided as a `Name`" + }, + { + "long": "route", + "help": "Name or ID of the route", + "required": true + }, + { + "long": "router", + "help": "Name or ID of the router" + }, + { + "long": "vpc", + "help": "Name or ID of the VPC, only required if `router` is provided as a `Name`" + } + ] + }, + { + "name": "view", + "about": "Fetch route", + "args": [ + { + "long": "project", + "help": "Name or ID of the project, only required if `vpc` is provided as a `Name`" + }, + { + "long": "route", + "help": "Name or ID of the route", + "required": true + }, + { + "long": "router", + "help": "Name or ID of the router", + "required": true + }, + { + "long": "vpc", + "help": "Name or ID of the VPC, only required if `router` is provided as a `Name`" + } + ] + } + ] + }, + { + "name": "update", + "about": "Update router", + "args": [ + { + "long": "description" + }, + { + "long": "json-body", + "help": "Path to a file that contains the full json body." + }, + { + "long": "json-body-template", + "help": "XXX" + }, + { + "long": "name" + }, + { + "long": "project", + "help": "Name or ID of the project, only required if `vpc` is provided as a `Name`" + }, + { + "long": "router", + "help": "Name or ID of the router", + "required": true + }, + { + "long": "vpc", + "help": "Name or ID of the VPC" + } + ] + }, + { + "name": "view", + "about": "Fetch router", + "args": [ + { + "long": "project", + "help": "Name or ID of the project, only required if `vpc` is provided as a `Name`" + }, + { + "long": "router", + "help": "Name or ID of the router", + "required": true + }, + { + "long": "vpc", + "help": "Name or ID of the VPC" + } + ] + } + ] + }, { "name": "subnet", "subcommands": [ @@ -3834,6 +4730,10 @@ "name": "create", "about": "Create subnet", "args": [ + { + "long": "custom-router", + "help": "An optional router, used to direct packets sent from hosts in this subnet to any destination address.\n\nCustom routers apply in addition to the VPC-wide *system* router, and have higher priority than the system router for an otherwise equal-prefix-length match." + }, { "long": "description" }, @@ -3953,6 +4853,10 @@ "name": "update", "about": "Update subnet", "args": [ + { + "long": "custom-router", + "help": "An optional router, used to direct packets sent from hosts in this subnet to any destination address." + }, { "long": "description" }, diff --git a/cli/src/cli_builder.rs b/cli/src/cli_builder.rs index 67fcb408..86e06cf8 100644 --- a/cli/src/cli_builder.rs +++ b/cli/src/cli_builder.rs @@ -4,7 +4,7 @@ // Copyright 2024 Oxide Computer Company -use std::{collections::BTreeMap, marker::PhantomData, path::PathBuf}; +use std::{collections::BTreeMap, marker::PhantomData, net::IpAddr, path::PathBuf}; use anyhow::{bail, Result}; use async_trait::async_trait; @@ -12,21 +12,24 @@ use clap::{ArgMatches, Command, CommandFactory, FromArgMatches}; use log::LevelFilter; use crate::{ + context::Context, generated_cli::{Cli, CliCommand}, OxideOverride, RunnableCmd, }; -use oxide::{ - config::{Config, ResolveValue}, - context::Context, -}; +use oxide::ClientConfig; +/// Control an Oxide environment #[derive(clap::Parser, Debug, Clone)] -#[command(name = "oxide")] +#[command(name = "oxide", verbatim_doc_comment)] struct OxideCli { /// Enable debug output #[clap(long)] pub debug: bool, + /// Configuration profile to use for commands + #[clap(long)] + pub profile: Option, + /// Directory to use for configuration #[clap(long, value_name = "DIR")] pub config_dir: Option, @@ -48,6 +51,41 @@ struct OxideCli { pub timeout: Option, } +#[derive(Debug, Clone, PartialEq, Eq)] +struct ResolveValue { + pub host: String, + pub port: u16, + pub addr: IpAddr, +} + +impl std::str::FromStr for ResolveValue { + type Err = String; + + fn from_str(s: &str) -> std::result::Result { + let values = s.splitn(3, ':').collect::>(); + let [host, port, addr] = values.as_slice() else { + return Err(r#"value must be "host:port:addr"#.to_string()); + }; + + let host = host.to_string(); + let port = port + .parse() + .map_err(|_| format!("error parsing port '{}'", port))?; + + // `IpAddr::parse()` does not accept enclosing brackets on IPv6 + // addresses; strip them off if they exist. + let addr = addr + .strip_prefix('[') + .and_then(|s| s.strip_suffix(']')) + .unwrap_or(addr); + let addr = addr + .parse() + .map_err(|_| format!("error parsing address '{}'", addr))?; + + Ok(Self { host, port, addr }) + } +} + #[async_trait] trait RunIt: Send + Sync { async fn run_cmd(&self, matches: &ArgMatches, ctx: &Context) -> Result<()>; @@ -182,6 +220,7 @@ impl<'a> NewCli<'a> { let matches = parser.get_matches(); let OxideCli { + profile, debug, config_dir, resolve, @@ -194,42 +233,36 @@ impl<'a> NewCli<'a> { env_logger::builder().filter_level(LevelFilter::Debug); } - let mut config = if let Some(dir) = config_dir { - Config::new_with_config_dir(dir) - } else { - Config::default() - }; + let mut client_config = ClientConfig::default(); + + if let Some(profile_name) = profile { + client_config = client_config.with_profile(profile_name); + } + if let Some(config_dir) = config_dir { + client_config = client_config.with_config_dir(config_dir); + } if let Some(resolve) = resolve { - config = config.with_resolve(resolve); + client_config = client_config.with_resolve(resolve.host, resolve.addr); } if let Some(cacert_path) = cacert { - enum CertType { - Pem, - Der, - } - let extension = cacert_path .extension() .map(std::ffi::OsStr::to_ascii_lowercase); - let ct = match extension.as_ref().and_then(|ex| ex.to_str()) { - Some("pem") => CertType::Pem, - Some("der") => CertType::Der, - _ => bail!("--cacert path must be a 'pem' or 'der' file".to_string()), - }; - let contents = std::fs::read(cacert_path)?; - - let cert = match ct { - CertType::Pem => reqwest::tls::Certificate::from_pem(&contents), - CertType::Der => reqwest::tls::Certificate::from_der(&contents), + let cert = match extension.as_ref().and_then(|ex| ex.to_str()) { + Some("pem") => reqwest::tls::Certificate::from_pem(&contents), + Some("der") => reqwest::tls::Certificate::from_der(&contents), + _ => bail!("--cacert path must be a 'pem' or 'der' file".to_string()), }?; - config = config.with_cert(cert); + + client_config = client_config.with_cert(cert); } - config = config.with_insecure(insecure); + client_config = client_config.with_insecure(insecure); if let Some(timeout) = timeout { - config = config.with_timeout(timeout); + client_config = client_config.with_timeout(timeout); } - let ctx = Context::new(config)?; + + let ctx = Context::new(client_config)?; let mut node = &runner; let mut sm = &matches; @@ -267,7 +300,8 @@ struct GeneratedCmd(CliCommand); #[async_trait] impl RunIt for GeneratedCmd { async fn run_cmd(&self, matches: &ArgMatches, ctx: &Context) -> Result<()> { - let cli = Cli::new(ctx.client()?.clone(), OxideOverride::default()); + let client = oxide::Client::new_authenticated_config(ctx.client_config())?; + let cli = Cli::new(client, OxideOverride::default()); cli.execute(self.0, matches).await } @@ -382,6 +416,17 @@ fn xxx<'a>(command: CliCommand) -> Option<&'a str> { CliCommand::VpcSubnetDelete => Some("vpc subnet delete"), CliCommand::VpcSubnetListNetworkInterfaces => Some("vpc subnet nic list"), + CliCommand::VpcRouterRouteList => Some("vpc router route list"), + CliCommand::VpcRouterRouteCreate => Some("vpc router route create"), + CliCommand::VpcRouterRouteView => Some("vpc router route view"), + CliCommand::VpcRouterRouteUpdate => Some("vpc router route update"), + CliCommand::VpcRouterRouteDelete => Some("vpc router route delete"), + CliCommand::VpcRouterList => Some("vpc router list"), + CliCommand::VpcRouterCreate => Some("vpc router create"), + CliCommand::VpcRouterView => Some("vpc router view"), + CliCommand::VpcRouterUpdate => Some("vpc router update"), + CliCommand::VpcRouterDelete => Some("vpc router delete"), + CliCommand::NetworkingAddressLotList => Some("system networking address-lot list"), CliCommand::NetworkingAddressLotCreate => Some("system networking address-lot create"), CliCommand::NetworkingAddressLotDelete => Some("system networking address-lot delete"), @@ -428,8 +473,8 @@ fn xxx<'a>(command: CliCommand) -> Option<&'a str> { CliCommand::NetworkingBgpConfigCreate => Some("system networking bgp config create"), CliCommand::NetworkingBgpConfigDelete => Some("system networking bgp config delete"), CliCommand::NetworkingBgpConfigList => Some("system networking bgp config list"), - CliCommand::NetworkingBgpAnnounceSetCreate => { - Some("system networking bgp announce-set create") + CliCommand::NetworkingBgpAnnounceSetUpdate => { + Some("system networking bgp announce-set update") } CliCommand::NetworkingBgpAnnounceSetDelete => { Some("system networking bgp announce-set delete") diff --git a/cli/src/cmd_api.rs b/cli/src/cmd_api.rs index 69ebc2de..333c62f6 100644 --- a/cli/src/cmd_api.rs +++ b/cli/src/cmd_api.rs @@ -13,9 +13,10 @@ use anyhow::{anyhow, Result}; use async_trait::async_trait; use clap::Parser; use futures::{StreamExt, TryStreamExt}; +use oxide::Client; use serde::Deserialize; -use crate::RunnableCmd; +use crate::{print_nopipe, println_nopipe}; /// Makes an authenticated HTTP request to the Oxide API and prints the response. /// @@ -97,8 +98,8 @@ pub struct PaginatedResponse { } #[async_trait] -impl RunnableCmd for CmdApi { - async fn run(&self, ctx: &oxide::context::Context) -> Result<()> { +impl crate::AuthenticatedCmd for CmdApi { + async fn run(&self, client: &Client) -> Result<()> { // Make sure the endpoint starts with a slash. let endpoint = if self.endpoint.starts_with('/') { self.endpoint.clone() @@ -107,7 +108,7 @@ impl RunnableCmd for CmdApi { }; // Parse the fields. - let params = self.parse_fields(ctx)?; + let params = self.parse_fields()?; // Set them as our body if they exist. let mut b = String::new(); @@ -164,7 +165,6 @@ impl RunnableCmd for CmdApi { } } - let client = ctx.client()?; let rclient = client.client(); let uri = format!("{}{}", client.baseurl(), endpoint_with_query); @@ -185,19 +185,19 @@ impl RunnableCmd for CmdApi { if !resp.status().is_success() { let status = resp.status(); let v = resp.json::().await?; - println!( + println_nopipe!( "error; status code: {} {}", status.as_u16(), status.canonical_reason().unwrap_or_default() ); - println!("{}", serde_json::to_string_pretty(&v).unwrap()); + println_nopipe!("{}", serde_json::to_string_pretty(&v).unwrap()); return Err(anyhow!("error")); } // Print the response headers if requested. if self.include { - println!("{:?} {}", resp.version(), resp.status()); - print_headers(ctx, resp.headers())?; + println_nopipe!("{:?} {}", resp.version(), resp.status()); + print_headers(resp.headers())?; } if resp.status() == reqwest::StatusCode::NO_CONTENT { @@ -207,7 +207,7 @@ impl RunnableCmd for CmdApi { if !self.paginate { // Print the response body. let result = resp.json::().await?; - println!("{}", serde_json::to_string_pretty(&result)?); + println_nopipe!("{}", serde_json::to_string_pretty(&result)?); Ok(()) } else { @@ -241,7 +241,7 @@ impl RunnableCmd for CmdApi { Result::Ok(Some((items, next_page))) }); - print!("["); + print_nopipe!("["); let result = first .chain(rest) @@ -254,19 +254,19 @@ impl RunnableCmd for CmdApi { let items_core = &value_out[2..len - 2]; if comma_needed { - print!(","); + print_nopipe!(","); } - println!(); - print!("{}", items_core); + println_nopipe!(); + print_nopipe!("{}", items_core); } Ok(true) }) .await; - println!(); - println!("]"); + println_nopipe!(); + println_nopipe!("]"); if let Err(e) = &result { - println!("An error occurred during a paginated query:\n{}", e); + println_nopipe!("An error occurred during a paginated query:\n{}", e); } result.map(|_| ()) } @@ -292,10 +292,7 @@ impl CmdApi { Ok(headers) } - fn parse_fields( - &self, - _ctx: &oxide::context::Context, - ) -> Result> { + fn parse_fields(&self) -> Result> { let mut params: HashMap = HashMap::new(); // Parse the raw fields. @@ -371,10 +368,7 @@ impl CmdApi { } } -fn print_headers( - _ctx: &oxide::context::Context, - headers: &reqwest::header::HeaderMap, -) -> Result<()> { +fn print_headers(headers: &reqwest::header::HeaderMap) -> Result<()> { let mut names: Vec = headers.keys().map(|k| k.as_str().to_string()).collect(); names.sort_by_key(|a| a.to_lowercase()); @@ -395,7 +389,7 @@ fn print_headers( tw.flush()?; let table = String::from_utf8(tw.into_inner()?)?; - println!("{}", table); + println_nopipe!("{}", table); Ok(()) } diff --git a/cli/src/cmd_auth.rs b/cli/src/cmd_auth.rs index cfca2d80..9c2543f6 100644 --- a/cli/src/cmd_auth.rs +++ b/cli/src/cmd_auth.rs @@ -4,7 +4,7 @@ // Copyright 2024 Oxide Computer Company -use std::{collections::HashMap, fs::File, io::Read}; +use std::fs::File; use anyhow::{anyhow, bail, Result}; use async_trait::async_trait; @@ -13,13 +13,13 @@ use oauth2::{ basic::BasicClient, devicecode::StandardDeviceAuthorizationResponse, AuthType, AuthUrl, ClientId, DeviceAuthorizationUrl, TokenResponse, TokenUrl, }; -use oxide::{ - config::{Config, Host}, - context::{make_client, make_rclient, Context}, - ClientSessionExt, -}; +use oxide::types::CurrentUser; +use oxide::{Client, ClientConfig, ClientSessionExt}; +use toml_edit::{Item, Table}; +use uuid::Uuid; -use crate::RunnableCmd; +use crate::context::Context; +use crate::{println_nopipe, AsHost, RunnableCmd}; /// Login, logout, and get the status of your authentication. /// @@ -43,9 +43,9 @@ enum SubCommand { impl RunnableCmd for CmdAuth { async fn run(&self, ctx: &Context) -> Result<()> { match &self.subcmd { - SubCommand::Login(cmd) => cmd.run(ctx).await, - SubCommand::Logout(cmd) => cmd.run(ctx).await, - SubCommand::Status(cmd) => cmd.run(ctx).await, + SubCommand::Login(cmd) => cmd.login(ctx).await, + SubCommand::Logout(cmd) => cmd.logout(ctx).await, + SubCommand::Status(cmd) => cmd.status(ctx).await, } } } @@ -93,6 +93,14 @@ pub fn parse_host(input: &str) -> Result { } } +fn yes(prompt: impl Into) -> Result { + match dialoguer::Confirm::new().with_prompt(prompt).interact() { + Ok(true) => Ok(true), + Ok(false) => Ok(false), + Err(err) => Err(anyhow!("prompt failed: {}", err)), + } +} + // fn parse_host_interactively(ctx: &mut crate::context::Context) -> Result { // loop { // match dialoguer::Input::::new() @@ -111,197 +119,161 @@ pub fn parse_host(input: &str) -> Result { // } // } -/// Authenticate with an Oxide host. -/// -/// Alternatively, pass in a token on standard input by using `--with-token`. -/// -/// # start interactive setup -/// $ oxide auth login +/// Authenticate with an Oxide Silo /// -/// # authenticate against a specific Oxide instance by reading the token -/// # from a file -/// $ oxide auth login --with-token --host oxide.internal < mytoken.txt -/// -/// # authenticate with a specific Oxide instance +/// # authenticate with a specific Oxide silo /// $ oxide auth login --host oxide.internal /// -/// # authenticate with an insecure Oxide instance (not recommended) +/// # authenticate with an insecure Oxide silo (not recommended) /// $ oxide auth login --host http://oxide.internal #[derive(Parser, Debug, Clone)] #[command(verbatim_doc_comment)] pub struct CmdAuthLogin { - /// Read token from standard input. - #[clap(long)] - with_token: bool, - /// The host of the Oxide instance to authenticate with. - /// This assumes http; specify an `http://` prefix if needed. + /// This assumes https; specify an `http://` prefix if needed. #[clap(short = 'H', long, value_parser = parse_host)] host: url::Url, /// Override the default browser when opening the authentication URL. - #[clap(long, group = "browser-options")] + #[clap(long)] browser: Option, /// Print the authentication URL rather than opening a browser window. - #[clap(long = "no-browser", group = "browser-options")] + #[clap(long)] no_browser: bool, } impl CmdAuthLogin { - pub async fn run(&self, ctx: &Context) -> Result<()> { - // if !ctx.io.can_prompt() && !self.with_token { - // return Err(anyhow!( - // "--with-token required when not running interactively" - // )); - // } - - let token = if self.with_token { - // Read the token from stdin. - let mut token = String::new(); - std::io::stdin().read_to_string(&mut token)?; - token = token.trim_end_matches('\n').to_string(); - - if token.is_empty() { - bail!("token cannot be empty"); + pub async fn login(&self, ctx: &Context) -> Result<()> { + let profile = ctx.client_config().profile(); + + if let Some(profile_name) = profile { + // If the profile already has a token, alert the user. + if ctx.cred_file().profile.contains_key(profile_name) + && !yes(format!( + "The profile \"{}\" is already authenticated. {}", + profile_name, "Do you want to re-authenticate?", + ))? + { + return Ok(()); } - - token - } else { - let existing_token = ctx - .config() - .hosts - .get(&self.host) - .map(|host_entry| &host_entry.token); - - if existing_token.is_some() { - match dialoguer::Confirm::new() - .with_prompt(format!( - "You're already logged into {}\nDo you want to re-authenticate?", - &self.host, - )) - .interact() - { - Ok(true) => {} - Ok(false) => { - return Ok(()); - } - Err(err) => { - return Err(anyhow!("prompt failed: {}", err)); - } - } + } else if let Some(existing_profile) = ctx + .cred_file() + .profile + .iter() + .filter_map(|(name, info)| (info.host == self.host.as_str()).then_some(name)) + .next() + { + // If the host is already present in a profile, alert the user. + if !yes(format!( + "The profile \"{}\" {} {}. {}", + existing_profile, + "is already authenticated with host", + &self.host, + "Do you want to proceed?" + ))? { + return Ok(()); } + } - let config = ctx.config(); - - // Copied from oauth2::async_http_client to customize the - // reqwest::Client with the top-level command-line options. - let async_http_client_custom = |request: oauth2::HttpRequest| async move { - let client = make_rclient(None, config) - // Following redirects opens the client up to SSRF - // vulnerabilities. - .redirect(reqwest::redirect::Policy::none()) - .build()?; - - let mut request_builder = client - .request(request.method, request.url.as_str()) - .body(request.body); - for (name, value) in &request.headers { - request_builder = request_builder.header(name.as_str(), value.as_bytes()); - } - let request = request_builder.build()?; - - let response = client.execute(request).await?; - - let status_code = response.status(); - let headers = response.headers().to_owned(); - let chunks = response.bytes().await?; - std::result::Result::<_, reqwest::Error>::Ok(oauth2::HttpResponse { - status_code, - headers, - body: chunks.to_vec(), - }) - }; - - // Do an OAuth 2.0 Device Authorization Grant dance to get a token. - let device_auth_url = - DeviceAuthorizationUrl::new(format!("{}device/auth", &self.host))?; - let client_id = &ctx.config().client_id; - let auth_client = BasicClient::new( - ClientId::new(client_id.to_string()), - None, - AuthUrl::new(format!("{}authorize", &self.host))?, - Some(TokenUrl::new(format!("{}device/token", &self.host))?), - ) - .set_auth_type(AuthType::RequestBody) - .set_device_authorization_url(device_auth_url); - - let details: StandardDeviceAuthorizationResponse = auth_client - .exchange_device_code()? - .request_async(async_http_client_custom) - .await?; - - let uri = details.verification_uri().to_string(); + // Make the client for use by oauth2 + let client = &ctx + .client_config() + .make_unauthenticated_client_builder() + .redirect(reqwest::redirect::Policy::none()) + .build()?; + // Copied from oauth2::async_http_client to customize the + // reqwest::Client with the top-level command-line options. + let async_http_client_custom = |request: oauth2::HttpRequest| async move { + let mut request_builder = client + .request(request.method, request.url.as_str()) + .body(request.body); + for (name, value) in &request.headers { + request_builder = request_builder.header(name.as_str(), value.as_bytes()); + } + let request = request_builder.build()?; + + let response = client.execute(request).await?; + + let status_code = response.status(); + let headers = response.headers().to_owned(); + let chunks = response.bytes().await?; + std::result::Result::<_, reqwest::Error>::Ok(oauth2::HttpResponse { + status_code, + headers, + body: chunks.to_vec(), + }) + }; - let opened = match (&self.browser, self.no_browser) { - (None, false) => open::that(&uri).is_ok(), - (Some(app), false) => open::with(&uri, app).is_ok(), - (None, true) => false, - (Some(_), true) => unreachable!(), - }; + // Do an OAuth 2.0 Device Authorization Grant dance to get a token. + let device_auth_url = DeviceAuthorizationUrl::new(format!("{}device/auth", &self.host))?; + // The client ID is intended to be an identifier issued to clients; + // since we're not doing that and this ID would be public if it were + // static, we just generate a random one each time we authenticate. + let client_id = Uuid::new_v4(); + let auth_client = BasicClient::new( + ClientId::new(client_id.to_string()), + None, + AuthUrl::new(format!("{}authorize", &self.host))?, + Some(TokenUrl::new(format!("{}device/token", &self.host))?), + ) + .set_auth_type(AuthType::RequestBody) + .set_device_authorization_url(device_auth_url); + + let details: StandardDeviceAuthorizationResponse = auth_client + .exchange_device_code()? + .request_async(async_http_client_custom) + .await?; + + let uri = details.verification_uri().to_string(); + + let opened = match (&self.browser, self.no_browser) { + (None, false) => open::that(&uri).is_ok(), + (Some(app), false) => open::with(&uri, app).is_ok(), + (None, true) => false, + (Some(_), true) => unreachable!(), + }; - if opened { - println!("Opened this URL in your browser:\n {}", uri); - } else { - println!("Open this URL in your browser:\n {}", uri); - } + if opened { + println!("Opened this URL in your browser:\n {}", uri); + } else { + println!("Open this URL in your browser:\n {}", uri); + } - println!("\nEnter the code: {}\n", details.user_code().secret()); + println!("\nEnter the code: {}\n", details.user_code().secret()); - auth_client - .exchange_device_access_token(&details) - .request_async(async_http_client_custom, tokio::time::sleep, None) - .await? - .access_token() - .secret() - .to_string() - }; + let token = auth_client + .exchange_device_access_token(&details) + .request_async(async_http_client_custom, tokio::time::sleep, None) + .await? + .access_token() + .secret() + .to_string(); - // if let Err(err) = ctx.config.check_writable(host, "token") { - // if let Some(crate::config_from_env::ReadOnlyEnvVarError::Variable(var)) = - // err.downcast_ref() - // { - // writeln!( - // ctx.io.err_out, - // "The value of the {} environment variable is being used for authentication.", - // var - // )?; - // writeln!( - // ctx.io.err_out, - // "To have Oxide CLI store credentials instead, first clear the value from the environment." - // )?; - // return Err(anyhow!("")); - // } - - // return Err(err); - // } - - // Set the token in the config file. - // ctx.config.set(host, "token", &token)?; - - // let client = ctx.api_client(host)?; - - // Get the session for the token. - // let session = client.hidden().session_me().await?; + let host = self.host.as_host(); // Construct a one-off API client, authenticated with the token // returned in the previous step, so that we can extract and save the // identity of the authenticated user. - let client = make_client(self.host.as_ref(), token.clone(), ctx.config()); - - let user = client.current_user_view().send().await?; - - println!("{:#?}", user); + let client = Client::new_authenticated_config( + &ClientConfig::default().with_host_and_token(host, &token), + ) + .unwrap(); + + let user = client.current_user_view().send().await?.into_inner(); + + // If there's no specified profile, we'll use the name of the silo as + // the profile name... and deal with conflicting names. + let profile_name = profile.map(String::from).unwrap_or_else(|| { + let silo_name = user.silo_name.to_string(); + let mut name = silo_name.clone(); + let mut ii = 2; + while ctx.cred_file().profile.contains_key(&name) { + name = format!("{}{}", silo_name, ii); + ii += 1; + } + name + }); // Set the user. // TODO: This should instead store the email, or some username or something @@ -309,42 +281,93 @@ impl CmdAuthLogin { // TODO ahl: not sure what even we're shooting for here. Maybe just a // way to understand the config files? let uid = user.id; - // ctx.config.set(host, "user", &email)?; - // Save the config. - // ctx.config.write()?; + // Read / modify / write the credentials file. + let credentials_path = ctx.client_config().config_dir().join("credentials.toml"); + let mut credentials = + if let Ok(contents) = std::fs::read_to_string(credentials_path.clone()) { + contents.parse::().unwrap() + } else { + Default::default() + }; - println!("Logged in as {}", uid); + let profile_table = credentials + .entry("profile") + .or_insert_with(|| { + let mut table = Table::default(); + // Avoid a bare [profile] table. + table.set_implicit(true); + Item::Table(table) + }) + .as_table_mut() + .unwrap_or_else(|| { + panic!( + "\"profile\" in {} is not a table", + credentials_path.to_string_lossy() + ) + }); + + let profile = profile_table + .entry(&profile_name) + .or_insert_with(|| Item::Table(Table::default())) + .as_table_mut() + .unwrap_or_else(|| { + panic!( + "\"profile.{}\" in {} is not a table", + profile_name, + credentials_path.to_string_lossy() + ) + }); + + profile.insert("host", toml_edit::value(self.host.as_host().to_string())); + profile.insert("token", toml_edit::value(token)); + profile.insert("user", toml_edit::value(uid.to_string())); + + std::fs::write(credentials_path, credentials.to_string()) + .expect("unable to write credentials.toml"); + + // If there is no default profile then we'll set this new profile to be + // the default. + if ctx.config_file().basics.default_profile.is_none() { + let config_path = ctx.client_config().config_dir().join("config.toml"); + let mut config = if let Ok(contents) = std::fs::read_to_string(config_path.clone()) { + contents.parse::().unwrap() + } else { + Default::default() + }; - let host_entry = Host { - token, - user: uid.to_string(), - default: false, - }; + config.insert("default-profile", Item::Value(profile_name.clone().into())); + + std::fs::write(config_path, config.to_string()).expect("unable to write config.toml"); + } - ctx.config() - .update_host(self.host.to_string(), host_entry)?; + let CurrentUser { + display_name, + id, + silo_id, + silo_name, + } = &user; + + println!("Login successful"); + println!(" silo: {} ({})", **silo_name, silo_id); + println!(" user: {} ({})", display_name, id); + if ctx.config_file().basics.default_profile.is_none() { + println!("Profile '{}' set as the default", profile_name); + } else { + println!("Use --profile '{}'", profile_name); + } Ok(()) } } -/// Removes saved authentication information. +/// Removes saved authentication information from profiles. /// -/// This command does not invalidate any tokens from the hosts. +/// This command does not invalidate any tokens. #[derive(Parser, Debug, Clone)] #[command(verbatim_doc_comment)] pub struct CmdAuthLogout { - /// Remove authentication information for a single host. - #[clap( - short = 'H', - long, - value_parser = parse_host, - required_unless_present = "all", - conflicts_with = "all", - )] - pub host: Option, - /// If set, all known hosts and authentication information will be deleted. + /// If set, authentication information from all profiles will be deleted. #[clap(short = 'a', long)] pub all: bool, /// Skip confirmation prompt. @@ -353,46 +376,42 @@ pub struct CmdAuthLogout { } impl CmdAuthLogout { - pub async fn run(&self, ctx: &Context) -> Result<()> { - if !self.force { - match dialoguer::Confirm::new() - .with_prompt("Confirm authentication information deletion:") - .interact() - { - Ok(true) => {} - Ok(false) => { - println!("Action aborted. No changes have been made."); - return Ok(()); - } - Err(err) => { - return Err(anyhow!("prompt failed: {}", err)); - } - } + pub async fn logout(&self, ctx: &Context) -> Result<()> { + if !self.force && !yes("Confirm authentication information deletion:")? { + return Ok(()); } - match &self.host { - Some(host) => { - // Setting the host with empty parameters so it will now be listed as - // "unauthenticated" when running `$ oxide auth status`. - let host_entry = Host { - token: String::from(""), - user: String::from(""), - default: false, - }; - ctx.config().update_host(host.to_string(), host_entry)?; + let credentials_path = ctx.client_config().config_dir().join("credentials.toml"); - println!("Removed authentication information for: {}", host); - } - None => { - let mut dir = dirs::home_dir().unwrap(); - dir.push(".config"); - dir.push("oxide"); - let hosts_path = dir.join("hosts.toml"); - - // Clear the entire file for users who want to reset their known hosts. - let _ = File::create(hosts_path)?; - println!("Removed all authentication information"); + if self.all { + // Clear the entire file for users who want to reset their known hosts. + let _ = File::create(credentials_path)?; + println!("Removed all authentication information"); + } else { + let profile = ctx + .client_config() + .profile() + .or_else(|| ctx.config_file().basics.default_profile.as_deref()); + let Some(profile_name) = profile else { + bail!("No profile specified and no default profile"); + }; + let Ok(credentials_contents) = std::fs::read_to_string(credentials_path.clone()) else { + // No file; no credentials. + return Ok(()); + }; + let mut credentials = credentials_contents + .parse::() + .unwrap(); + if let Some(profiles) = credentials.get_mut("profile") { + let profiles = profiles.as_table_mut().unwrap(); + profiles.remove(profile_name); } + std::fs::write(credentials_path, credentials.to_string()) + .expect("unable to write credentials.toml"); + println!( + "Removed authentication information for profile \"{}\"", + profile_name, + ); } Ok(()) @@ -401,242 +420,94 @@ impl CmdAuthLogout { /// Verifies and displays information about your authentication state. /// -/// This command validates the authentication state for each Oxide environment -/// in the current configuration. These hosts may be from your hosts.toml file -/// and/or $OXIDE_HOST environment variable. +/// This command validates the authentication state for each profile in the +/// current configuration. #[derive(Parser, Debug, Clone)] #[command(verbatim_doc_comment)] -pub struct CmdAuthStatus { - /// Display the auth token. - #[clap(short = 't', long)] - pub show_token: bool, - - /// Specific hostname to validate. - #[clap(short = 'H', long, value_parser = parse_host)] - pub host: Option, -} +pub struct CmdAuthStatus {} impl CmdAuthStatus { - pub async fn run(&self, ctx: &Context) -> Result<()> { - let mut status_info: HashMap> = HashMap::new(); - - // Initializing a new Config here instead of taking ctx (&Context) - // because ctx already has an initialized oxide::Client. This would - // give the CLI a chance to return an error before the status checks - // have even started. - // - // For example: the user has the OXIDE_HOST env var set but hasn't set - // OXIDE_TOKEN nor do they have a corresponding token for that host on - // the hosts.toml file, the CLI would return an error even if other - // host/token combinations on the hosts.toml file are valid. - let config = Config::default(); - let mut host_list = config.hosts.hosts; - - // Include login information from environment variables if set - if let Some(h) = std::env::var_os("OXIDE_HOST") { - let env_token = match std::env::var_os("OXIDE_TOKEN") { - Some(t) => t.into_string().unwrap(), - None => String::from(""), - }; - let info = Host { - token: env_token, - user: String::from(""), - default: false, - }; - - host_list.insert(h.into_string().unwrap(), info); - } - - if host_list.is_empty() { - return Err(anyhow!("You are not logged into any Oxide hosts.")); - } - - // Return a single result if the --host flag has been set - if let Some(url) = &self.host { - if host_list.contains_key(url.as_ref()) { - host_list.retain(|k, _| k == url.as_str()) - } else { - return Err(anyhow!( - "Host {} Not found in hosts.toml file or environment variables.", - url.as_str() - )); - } - } + pub async fn status(&self, ctx: &Context) -> Result<()> { + // For backward compatibility, we'll check OXIDE_HOST and OXIDE_TOKEN + // first. + if let (Ok(host_env), Ok(token_env)) = + (std::env::var("OXIDE_HOST"), std::env::var("OXIDE_TOKEN")) + { + let client = Client::new_authenticated_config( + &ClientConfig::default().with_host_and_token(&host_env, &token_env), + ) + .expect("client authentication from host/token failed"); - for (host, info) in host_list.iter() { - // Construct a client with each host/token combination - let client = make_client(host, info.token.clone(), ctx.config()); - - let result = client.current_user_view().send().await; - let user = match result { - Ok(usr) => usr, - Err(_) => { - // TODO we need to examine the error - status_info.insert( - host.to_string(), - vec![String::from( - "Not authenticated. Host/token combination invalid", - )], - ); - continue; + match client.current_user_view().send().await { + Ok(user) => { + log::debug!("success response for {} (env): {:?}", host_env, user); + println_nopipe!("Logged in to {} as {}", host_env, user.id) + } + Err(e) => { + log::debug!("error response for {} (env): {}", host_env, e); + println_nopipe!("{}: {}", host_env, Self::error_msg(&e)) } }; + } else { + for (profile_name, profile_info) in &ctx.cred_file().profile { + let client = Client::new_authenticated_config( + &ClientConfig::default() + .with_host_and_token(&profile_info.host, &profile_info.token), + ) + .expect("client authentication from host/token failed"); + + let status = match client.current_user_view().send().await { + Ok(v) => { + log::debug!("success response for {}: {:?}", profile_info.host, v); + "Authenticated".to_string() + } + Err(e) => { + log::debug!("error response for {}: {}", profile_info.host, e); + Self::error_msg(&e) + } + }; - // TODO: this should be the users email or something consistent with login - // and logout. - let email = user.id.to_string(); - - // TODO: Once tokens have expiry dates, report expired tokens. - let token_display = if self.show_token { - info.token.to_string() - } else { - "*******************".to_string() - }; - - status_info.insert( - host.to_string(), - vec![ - format!("Logged in to {} as {}", host, &email), - format!("Token: {}", token_display), - ], - ); + println_nopipe!( + "Profile \"{}\" ({}) status: {}", + profile_name, + profile_info.host, + status + ); + } } + Ok(()) + } - for (key, value) in &status_info { - println!("{}: {:?}", key, value); + fn error_msg(e: &oxide::Error) -> String { + match e { + oxide::Error::ErrorResponse(ee) => format!("Error Response: {}", ee.message), + ee => ee.to_string(), } - - Ok(()) } } #[cfg(test)] mod tests { - // use pretty_assertions::assert_eq; - - // use crate::cmd::Command; - - // pub struct TestItem { - // name: String, - // cmd: crate::cmd_auth::SubCommand, - // stdin: String, - // want_out: String, - // want_err: String, - // } - - // TODO: Auth is shaky with current docker container CI implementation. - // remove ignore tag once tests run against mock API server - // #[ignore] - // #[tokio::test(flavor = "multi_thread", worker_threads = 1)] - // #[serial_test::serial] - // async fn test_cmd_auth() { - // let test_host = std::env::var("OXIDE_TEST_HOST") - // .expect("you need to set OXIDE_TEST_HOST to where the api is running"); - // let test_host = crate::cmd_auth::parse_host(&test_host).expect("invalid OXIDE_TEST_HOST"); - - // let test_token = std::env::var("OXIDE_TEST_TOKEN").expect("OXIDE_TEST_TOKEN is required"); - - // let tests: Vec = vec![ - // TestItem { - // name: "status".to_string(), - // cmd: crate::cmd_auth::SubCommand::Status(crate::cmd_auth::CmdAuthStatus { - // show_token: false, - // host: None, - // }), - // stdin: "".to_string(), - // want_out: "You are not logged into any Oxide hosts. Run oxide auth login to authenticate.\n" - // .to_string(), - // want_err: "".to_string(), - // }, - // TestItem { - // name: "login --with-token=false".to_string(), - // cmd: crate::cmd_auth::SubCommand::Login(crate::cmd_auth::CmdAuthLogin { - // host: Some(test_host.clone()), - // with_token: false, - // }), - // stdin: test_token.to_string(), - // want_out: "".to_string(), - // want_err: "--with-token required when not running interactively".to_string(), - // }, - // TestItem { - // name: "login --with-token=true".to_string(), - // cmd: crate::cmd_auth::SubCommand::Login(crate::cmd_auth::CmdAuthLogin { - // host: Some(test_host.clone()), - // with_token: true, - // }), - // stdin: test_token.to_string(), - // want_out: "✔ Logged in as ".to_string(), - // want_err: "".to_string(), - // }, - // TestItem { - // name: "status".to_string(), - // cmd: crate::cmd_auth::SubCommand::Status(crate::cmd_auth::CmdAuthStatus { - // show_token: false, - // host: Some(test_host.clone()), - // }), - // stdin: "".to_string(), - // want_out: format!("{}\n✔ Logged in to {} as", test_host, test_host), - // want_err: "".to_string(), - // }, - // TestItem { - // name: "logout no prompt no host".to_string(), - // cmd: crate::cmd_auth::SubCommand::Logout(crate::cmd_auth::CmdAuthLogout { host: None }), - // stdin: "".to_string(), - // want_out: "".to_string(), - // want_err: "--host required when not running interactively".to_string(), - // }, - // TestItem { - // name: "logout no prompt with host".to_string(), - // cmd: crate::cmd_auth::SubCommand::Logout(crate::cmd_auth::CmdAuthLogout { - // host: Some(test_host.clone()), - // }), - // stdin: "".to_string(), - // want_out: format!("✔ Logged out of {}", test_host), - // want_err: "".to_string(), - // }, - // ]; - - // let mut config = crate::config::new_blank_config().unwrap(); - // let mut c = crate::config_from_env::EnvConfig::inherit_env(&mut config); - - // for t in tests { - // let (mut io, stdout_path, stderr_path) = crate::iostreams::IoStreams::test(); - // if !t.stdin.is_empty() { - // io.stdin = Box::new(std::io::Cursor::new(t.stdin)); - // } - // // We need to also turn off the fancy terminal colors. - // // This ensures it also works in GitHub actions/any CI. - // io.set_color_enabled(false); - // // TODO: we should figure out how to test the prompts. - // io.set_never_prompt(true); - // let mut ctx = crate::context::Context { - // config: &mut c, - // io, - // debug: false, - // }; - - // let cmd_auth = crate::cmd_auth::CmdAuth { subcmd: t.cmd }; - // match cmd_auth.run(&mut ctx).await { - // Ok(()) => { - // let stdout = std::fs::read_to_string(stdout_path).unwrap(); - // let stderr = std::fs::read_to_string(stderr_path).unwrap(); - // assert!(stderr.is_empty(), "test {}: {}", t.name, stderr); - // if !stdout.contains(&t.want_out) { - // assert_eq!(stdout, t.want_out, "test {}: stdout mismatch", t.name); - // } - // } - // Err(err) => { - // let stdout = std::fs::read_to_string(stdout_path).unwrap(); - // let stderr = std::fs::read_to_string(stderr_path).unwrap(); - // assert_eq!(stdout, t.want_out, "test {}", t.name); - // if !err.to_string().contains(&t.want_err) { - // assert_eq!(err.to_string(), t.want_err, "test {}: err mismatch", t.name); - // } - // assert!(stderr.is_empty(), "test {}: {}", t.name, stderr); - // } - // } - // } - // } + #[test] + fn test_cmd_auth_login() { + use assert_cmd::Command; + use predicates::str; + + let bad_url = "sys.oxide.invalid"; + + // Validate connection error details are printed + Command::cargo_bin("oxide") + .unwrap() + .arg("auth") + .arg("login") + .arg("--host") + .arg(bad_url) + .assert() + .failure() + .stderr(str::starts_with(format!( + "Request failed: error sending request for url (https://{bad_url}/device/auth):" + ))); + } #[test] fn test_parse_host() { @@ -700,106 +571,3 @@ mod tests { )); } } - -#[test] -fn test_cmd_auth_status() { - use assert_cmd::Command; - use httpmock::{Method::GET, MockServer}; - use predicates::str; - - let server = MockServer::start(); - let oxide_mock = server.mock(|when, then| { - when.method(GET) - .path("/v1/me") - .header("authorization", "Bearer oxide-token-1111"); - then.status(200).json_body_obj(&oxide::types::CurrentUser { - display_name: "privileged".to_string(), - id: "001de000-05e4-4000-8000-000000004007".parse().unwrap(), - silo_id: "d1bb398f-872c-438c-a4c6-2211e2042526".parse().unwrap(), - silo_name: "funky-town".parse().unwrap(), - }); - }); - - // Validate authenticated credentials. - Command::cargo_bin("oxide") - .unwrap() - .arg("auth") - .arg("status") - .env("OXIDE_HOST", server.url("")) - .env("OXIDE_TOKEN", "oxide-token-1111") - .assert() - .success() - .stdout(str::contains(format!( - "{}: [{}, {}]", - server.url(""), - format_args!( - "\"Logged in to {} as 001de000-05e4-4000-8000-000000004007\"", - server.url("") - ), - "\"Token: *******************\"", - ))); - - // Validate authenticated credentials with the token displayed in plain text. - Command::cargo_bin("oxide") - .unwrap() - .arg("auth") - .arg("status") - .env("OXIDE_HOST", server.url("")) - .env("OXIDE_TOKEN", "oxide-token-1111") - .arg("-t") - .assert() - .success() - .stdout(str::contains(format!( - "{}: [{}, {}]", - server.url(""), - format_args!( - "\"Logged in to {} as 001de000-05e4-4000-8000-000000004007\"", - server.url("") - ), - "\"Token: oxide-token-1111\"", - ))); - - // Assert that both commands hit the mock. - oxide_mock.assert_hits(2); - - let oxide_mock = server.mock(|when, then| { - when.header("authorization", "Bearer oxide-token-1112"); - then.status(401).json_body_obj(&oxide::types::Error { - error_code: None, - message: "oops".to_string(), - request_id: "42".to_string(), - }); - }); - - // Try invalid credentials. - Command::cargo_bin("oxide") - .unwrap() - .arg("auth") - .arg("status") - .env("OXIDE_HOST", server.url("")) - .env("OXIDE_TOKEN", "oxide-token-1112") - .assert() - .success() - .stdout(str::contains(format!( - "{}: [\"Not authenticated. Host/token combination invalid\"]", - server.url("") - ))); - - // Make sure the command only returns information about the specified host. - Command::cargo_bin("oxide") - .unwrap() - .arg("auth") - .arg("status") - .env("OXIDE_HOST", server.url("/")) - .env("OXIDE_TOKEN", "oxide-token-1112") - .arg("-H") - .arg(server.url("")) - .assert() - .success() - .stdout(format!( - "{}: [\"Not authenticated. Host/token combination invalid\"]\n", - server.url("/") - )); - // Assert that both commands hit the mock. - oxide_mock.assert_hits(2); -} diff --git a/cli/src/cmd_completion.rs b/cli/src/cmd_completion.rs index acebc877..68db117c 100644 --- a/cli/src/cmd_completion.rs +++ b/cli/src/cmd_completion.rs @@ -4,7 +4,6 @@ // Copyright 2024 Oxide Computer Company -use crate::RunnableCmd; use anyhow::Result; use async_trait::async_trait; use clap::Parser; @@ -89,8 +88,8 @@ pub struct CmdCompletion { } #[async_trait] -impl RunnableCmd for CmdCompletion { - async fn run(&self, _ctx: &oxide::context::Context) -> Result<()> { +impl crate::RunnableCmd for CmdCompletion { + async fn run(&self, _: &crate::Context) -> Result<()> { let cli = crate::make_cli(); let mut cmd = cli.command_take(); let name = cmd.get_name().to_string(); diff --git a/cli/src/cmd_disk.rs b/cli/src/cmd_disk.rs index 92b643b5..0708a53f 100644 --- a/cli/src/cmd_disk.rs +++ b/cli/src/cmd_disk.rs @@ -4,7 +4,8 @@ // Copyright 2024 Oxide Computer Company -use crate::RunnableCmd; +use crate::{eprintln_nopipe, println_nopipe}; + use anyhow::bail; use anyhow::Result; use async_trait::async_trait; @@ -191,9 +192,10 @@ impl CmdDiskImport { .await; if let Err(e) = response { - eprintln!( + eprintln_nopipe!( "trying to unwind, deleting {:?} failed with {:?}", - self.disk, e + self.disk, + e ); return Err(e.into()); } @@ -211,9 +213,10 @@ impl CmdDiskImport { // If this fails, then the disk will remain in state "import-ready" if let Err(e) = response { - eprintln!( + eprintln_nopipe!( "trying to unwind, finalizing {:?} failed with {:?}", - self.disk, e + self.disk, + e ); return Err(e.into()); } @@ -232,9 +235,10 @@ impl CmdDiskImport { // If this fails, then the disk will remain in state // "importing-from-bulk-writes" if let Err(e) = response { - eprintln!( + eprintln_nopipe!( "trying to unwind, stopping the bulk write process for {:?} failed with {:?}", - self.disk, e + self.disk, + e ); return Err(e.into()); } @@ -244,13 +248,11 @@ impl CmdDiskImport { } #[async_trait] -impl RunnableCmd for CmdDiskImport { +impl crate::AuthenticatedCmd for CmdDiskImport { fn is_subtree() -> bool { false } - async fn run(&self, ctx: &oxide::context::Context) -> Result<()> { - let client = ctx.client()?; - + async fn run(&self, client: &Client) -> Result<()> { if !Path::new(&self.path).exists() { bail!("path {} does not exist", self.path.to_string_lossy()); } @@ -323,7 +325,7 @@ impl RunnableCmd for CmdDiskImport { disk_source: DiskSource::ImportingBlocks { block_size: disk_block_size.clone(), }, - size: disk_size.try_into()?, + size: disk_size.into(), }) .send() .await?; @@ -337,7 +339,7 @@ impl RunnableCmd for CmdDiskImport { .await; if let Err(e) = start_bulk_write_response { - eprintln!("starting the bulk write process failed with {:?}", e); + eprintln_nopipe!("starting the bulk write process failed with {:?}", e); // If this fails, the disk is in state import-ready. Finalize it so // it can be deleted. @@ -405,7 +407,7 @@ impl RunnableCmd for CmdDiskImport { let n = match file.by_ref().take(CHUNK_SIZE).read_to_end(&mut chunk) { Ok(n) => n, Err(e) => { - eprintln!( + eprintln_nopipe!( "reading from {} failed with {:?}", self.path.to_string_lossy(), e, @@ -423,7 +425,7 @@ impl RunnableCmd for CmdDiskImport { let encoded = base64::engine::general_purpose::STANDARD.encode(&chunk[0..n]); if let Err(e) = senders[i % UPLOAD_TASKS].send((offset, encoded, n)).await { - eprintln!("sending chunk to thread failed with {:?}", e); + eprintln_nopipe!("sending chunk to thread failed with {:?}", e); break Err(e.into()); } } else { @@ -459,7 +461,7 @@ impl RunnableCmd for CmdDiskImport { if results.iter().any(|x| x.is_err()) { // If any of the upload threads returned an error, unwind the disk. - eprintln!("one of the upload threads failed"); + eprintln_nopipe!("one of the upload threads failed"); self.unwind_disk_bulk_write_stop(client).await?; self.unwind_disk_finalize(client).await?; self.unwind_disk_delete(client).await?; @@ -478,7 +480,7 @@ impl RunnableCmd for CmdDiskImport { .await; if let Err(e) = stop_bulk_write_response { - eprintln!("stopping the bulk write process failed with {:?}", e); + eprintln_nopipe!("stopping the bulk write process failed with {:?}", e); // Attempt to unwind the disk, although it will probably fail - the // first step is to stop the bulk write process! @@ -501,7 +503,7 @@ impl RunnableCmd for CmdDiskImport { let finalize_response = request.send().await; if let Err(e) = finalize_response { - eprintln!("finalizing the disk failed with {:?}", e); + eprintln_nopipe!("finalizing the disk failed with {:?}", e); // Attempt to unwind the disk, although it will probably fail - the // first step is to finalize the disk! @@ -544,7 +546,7 @@ impl RunnableCmd for CmdDiskImport { .await?; } - println!("done!"); + println_nopipe!("done!"); Ok(()) } diff --git a/cli/src/cmd_docs.rs b/cli/src/cmd_docs.rs index f76a4dff..89118538 100644 --- a/cli/src/cmd_docs.rs +++ b/cli/src/cmd_docs.rs @@ -4,8 +4,8 @@ // Copyright 2024 Oxide Computer Company +use crate::context::Context; use crate::RunnableCmd; -use oxide::context::Context; use super::cmd_version::built_info; use anyhow::Result; diff --git a/cli/src/cmd_instance.rs b/cli/src/cmd_instance.rs index 2733b0d7..21fe018c 100644 --- a/cli/src/cmd_instance.rs +++ b/cli/src/cmd_instance.rs @@ -4,7 +4,6 @@ // Copyright 2024 Oxide Computer Company -use crate::RunnableCmd; use anyhow::Result; use async_trait::async_trait; use clap::Parser; @@ -13,8 +12,8 @@ use oxide::types::{ NameOrId, }; -use oxide::ClientImagesExt; use oxide::ClientInstancesExt; +use oxide::{Client, ClientImagesExt}; use std::path::PathBuf; /// Connect to or retrieve data from the instance's serial console. @@ -27,11 +26,11 @@ pub struct CmdInstanceSerial { } #[async_trait] -impl RunnableCmd for CmdInstanceSerial { - async fn run(&self, ctx: &oxide::context::Context) -> Result<()> { +impl crate::AuthenticatedCmd for CmdInstanceSerial { + async fn run(&self, client: &Client) -> Result<()> { match &self.subcmd { - SerialSubCommand::Console(cmd) => cmd.run(ctx).await, - SerialSubCommand::History(cmd) => cmd.run(ctx).await, + SerialSubCommand::Console(cmd) => cmd.run(client).await, + SerialSubCommand::History(cmd) => cmd.run(client).await, } } } @@ -98,12 +97,10 @@ by typing { Ctrl+V, Ctrl+], Ctrl+V, Ctrl+C } at the command line." tty: Option, } -#[async_trait] -impl RunnableCmd for CmdInstanceSerialConsole { +impl CmdInstanceSerialConsole { // cli process becomes an interactive remote shell. - async fn run(&self, ctx: &oxide::context::Context) -> Result<()> { - let mut req = ctx - .client()? + async fn run(&self, client: &Client) -> Result<()> { + let mut req = client .instance_serial_console_stream() .instance(self.instance.clone()) .most_recent(self.most_recent); @@ -175,12 +172,10 @@ pub struct CmdInstanceSerialHistory { json: bool, } -#[async_trait] -impl RunnableCmd for CmdInstanceSerialHistory { +impl CmdInstanceSerialHistory { // cli process becomes an interactive remote shell. - async fn run(&self, ctx: &oxide::context::Context) -> Result<()> { - let mut req = ctx - .client()? + async fn run(&self, client: &Client) -> Result<()> { + let mut req = client .instance_serial_console() .instance(self.instance.clone()); @@ -253,18 +248,17 @@ pub struct CmdInstanceFromImage { } #[async_trait] -impl RunnableCmd for CmdInstanceFromImage { - async fn run(&self, ctx: &oxide::context::Context) -> Result<()> { +impl crate::AuthenticatedCmd for CmdInstanceFromImage { + async fn run(&self, client: &Client) -> Result<()> { // Validate the image and get its ID (if specified by name). - let mut image_request = ctx.client()?.image_view().image(&self.image); + let mut image_request = client.image_view().image(&self.image); // We only need the project if the image is specified by name. if let NameOrId::Name(_) = &self.image { image_request = image_request.project(&self.project); }; let image_view = image_request.send().await?; - let instance = ctx - .client()? + let instance = client .instance_create() .project(&self.project) .body_map(|body| { diff --git a/cli/src/cmd_net.rs b/cli/src/cmd_net.rs new file mode 100644 index 00000000..0864da3c --- /dev/null +++ b/cli/src/cmd_net.rs @@ -0,0 +1,1509 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +// Copyright 2024 Oxide Computer Company + +use std::{collections::HashMap, net::IpAddr}; + +use crate::AuthenticatedCmd; +use anyhow::Result; +use async_trait::async_trait; +use clap::Parser; +use colored::*; +use futures::TryStreamExt; +use oxide::{ + types::{ + Address, AddressConfig, BgpAnnounceSetCreate, BgpAnnouncementCreate, BgpPeer, + BgpPeerConfig, BgpPeerStatus, ImportExportPolicy, IpNet, LinkConfigCreate, LinkFec, + LinkSpeed, LldpServiceConfigCreate, Name, NameOrId, Route, RouteConfig, + SwitchInterfaceConfigCreate, SwitchInterfaceKind, SwitchInterfaceKind2, SwitchLocation, + SwitchPort, SwitchPortConfigCreate, SwitchPortGeometry, SwitchPortGeometry2, + SwitchPortSettingsCreate, + }, + Client, ClientSystemHardwareExt, ClientSystemNetworkingExt, +}; +use serde::{Deserialize, Serialize}; +use std::io::Write; +use tabwriter::TabWriter; +use uuid::Uuid; + +use crate::println_nopipe; + +// We do not yet support port breakouts, but the API is phrased in terms of +// ports that can be broken out. The constant phy0 represents the first port +// in a breakout. +const PHY0: &str = "phy0"; + +/// Manage switch port links. +/// +/// Links carry layer-2 Ethernet properties for a lane or set of lanes on a +/// switch port. Lane geometry is defined in physical port settings. At the +/// present time only single lane configurations are supported, and thus only +/// a single link per physical port is supported. +#[derive(Parser, Debug, Clone)] +#[command(verbatim_doc_comment)] +#[command(name = "link")] +pub struct CmdLink { + #[clap(subcommand)] + subcmd: LinkSubCommand, +} + +#[async_trait] +impl AuthenticatedCmd for CmdLink { + async fn run(&self, client: &Client) -> Result<()> { + match &self.subcmd { + LinkSubCommand::Add(cmd) => cmd.run(client).await, + LinkSubCommand::Del(cmd) => cmd.run(client).await, + } + } +} + +#[derive(Parser, Debug, Clone)] +enum LinkSubCommand { + /// Add a link to a port. + Add(CmdLinkAdd), + + /// Remove a link from a port. + Del(CmdLinkDel), +} + +/// Add a link to a switch port. +/// +/// This operation performs a read-modify write on the port settings object +/// identified by the rack/switch/port parameters, adding a link to the +/// corresponding port settings. +#[derive(Parser, Debug, Clone)] +#[command(verbatim_doc_comment)] +#[command(name = "add")] +pub struct CmdLinkAdd { + /// Id of the rack to add the link to. + #[arg(long)] + rack: Uuid, + + /// Switch to add the link to. + #[arg(long, value_enum)] + switch: Switch, + + /// Port to add the link to. + #[arg(long, value_enum)] + port: Port, + + /// Whether or not to set auto-negotiation + #[arg(long)] + pub autoneg: bool, + + /// The forward error correction mode of the link. + #[arg(long, value_enum)] + pub fec: LinkFec, + + /// Maximum transmission unit for the link. + #[arg(long, default_value_t = 1500u16)] + pub mtu: u16, + + /// The speed of the link. + #[arg(long, value_enum)] + pub speed: LinkSpeed, +} + +#[async_trait] +impl AuthenticatedCmd for CmdLinkAdd { + async fn run(&self, client: &Client) -> Result<()> { + let mut settings = + current_port_settings(client, &self.rack, &self.switch, &self.port).await?; + let link = LinkConfigCreate { + autoneg: self.autoneg, + fec: self.fec, + mtu: self.mtu, + speed: self.speed, + //TODO not fully plumbed on the back end yet. + lldp: LldpServiceConfigCreate { + enabled: false, + lldp_config: None, + }, + }; + match settings.links.get(PHY0) { + Some(_) => { + return Err(anyhow::anyhow!("only one link per port supported")); + } + None => { + settings.links.insert(String::from(PHY0), link); + } + } + client + .networking_switch_port_settings_create() + .body(settings) + .send() + .await?; + Ok(()) + } +} + +/// Remove a link from a switch port. +/// +/// This operation performs a read-modify write on the port settings object +/// identified by the rack/switch/port parameters, removing a link from the +/// corresponding port settings. +#[derive(Parser, Debug, Clone)] +#[command(verbatim_doc_comment)] +#[command(name = "del")] +pub struct CmdLinkDel { + /// Id of the rack to remove the link from. + #[arg(long)] + rack: Uuid, + + /// Switch to remove the link from. + #[arg(long, value_enum)] + switch: Switch, + + /// Port to remove the link from. + #[arg(long, value_enum)] + port: Port, +} + +#[async_trait] +impl AuthenticatedCmd for CmdLinkDel { + async fn run(&self, client: &Client) -> Result<()> { + let mut settings = + current_port_settings(client, &self.rack, &self.switch, &self.port).await?; + settings.links.clear(); + client + .networking_switch_port_settings_create() + .body(settings) + .send() + .await?; + Ok(()) + } +} + +/// Manage and query rack Border Gateway Protocol (BGP) configuration and +/// status. +#[derive(Parser, Debug, Clone)] +#[command(verbatim_doc_comment)] +#[command(name = "bgp")] +pub struct CmdBgp { + #[clap(subcommand)] + subcmd: BgpSubCommand, +} + +#[async_trait] +impl AuthenticatedCmd for CmdBgp { + async fn run(&self, client: &Client) -> Result<()> { + match &self.subcmd { + BgpSubCommand::Status(cmd) => cmd.run(client).await, + BgpSubCommand::Config(cmd) => cmd.run(client).await, + BgpSubCommand::Announce(cmd) => cmd.run(client).await, + BgpSubCommand::Withdraw(cmd) => cmd.run(client).await, + BgpSubCommand::Filter(cmd) => cmd.run(client).await, + } + } +} + +#[allow(clippy::large_enum_variant)] +#[derive(Parser, Debug, Clone)] +enum BgpSubCommand { + /// Observe BGP status. + Status(CmdBgpStatus), + + /// Manage BGP configuration. + Config(CmdBgpConfig), + + /// Make a BGP announcement. + Announce(CmdBgpAnnounce), + + /// Make a BGP announcement. + Withdraw(CmdBgpWithdraw), + + /// Set a filtering specification for a peer. + Filter(CmdBgpFilter), +} + +/// Announce a prefix over BGP. +/// +/// This command adds the provided prefix to the specified announce set. It is +/// required that the prefix be available in the given address lot. The add is +/// performed as a read-modify-write on the specified address lot. +#[derive(Parser, Debug, Clone)] +#[command(verbatim_doc_comment)] +#[command(name = "announce")] +pub struct CmdBgpAnnounce { + /// The announce set to announce from. + #[arg(long)] + announce_set: Name, + + /// The address lot to draw from. + #[arg(long)] + address_lot: Name, + + /// The prefix to announce. + #[arg(long)] + prefix: oxnet::IpNet, + + #[arg(long, default_value = "")] + description: String, +} + +#[async_trait] +impl AuthenticatedCmd for CmdBgpAnnounce { + async fn run(&self, client: &Client) -> Result<()> { + let mut current: Vec = client + .networking_bgp_announce_set_list() + .name_or_id(NameOrId::Name(self.announce_set.clone())) + .send() + .await? + .into_inner() + .into_iter() + .map(|x| BgpAnnouncementCreate { + address_lot_block: NameOrId::Id(x.address_lot_block_id), + network: x.network, + }) + .collect(); + + current.push(BgpAnnouncementCreate { + address_lot_block: NameOrId::Name(self.address_lot.clone()), + network: self.prefix.to_string().parse().unwrap(), + }); + + client + .networking_bgp_announce_set_update() + .body(BgpAnnounceSetCreate { + announcement: current, + name: self.announce_set.clone(), + description: self.description.clone(), + }) + .send() + .await?; + + Ok(()) + } +} + +/// Withdraw a prefix over BGP. +/// +/// This command removes the provided prefix to the specified announce set. +/// The remove is performed as a read-modify-write on the specified address lot. +#[derive(Parser, Debug, Clone)] +#[command(verbatim_doc_comment)] +#[command(name = "withdraw")] +pub struct CmdBgpWithdraw { + /// The announce set to withdraw from. + #[arg(long)] + announce_set: Name, + + /// The prefix to withdraw. + #[arg(long)] + prefix: oxnet::IpNet, +} + +#[async_trait] +impl AuthenticatedCmd for CmdBgpWithdraw { + async fn run(&self, client: &Client) -> Result<()> { + let mut current: Vec = client + .networking_bgp_announce_set_list() + .name_or_id(NameOrId::Name(self.announce_set.clone())) + .send() + .await? + .into_inner() + .into_iter() + .map(|x| BgpAnnouncementCreate { + address_lot_block: NameOrId::Id(x.address_lot_block_id), + network: x.network, + }) + .collect(); + + current.retain(|x| x.network.to_string() != self.prefix.to_string()); + + client + .networking_bgp_announce_set_update() + .body(BgpAnnounceSetCreate { + announcement: current, + name: self.announce_set.clone(), + description: self.announce_set.to_string(), //TODO? + }) + .send() + .await?; + + Ok(()) + } +} + +#[derive(clap::ValueEnum, Clone, Debug)] +enum FilterDirection { + Import, + Export, +} + +/// Add a filtering requirement to a BGP session. +/// +/// The Oxide BGP implementation can filter prefixes received from peers +/// on import and filter prefixes sent to peers on export. This command +/// provides a way to specify import/export filtering. Filtering is a +/// property of the BGP peering settings found in port settings configuration. +/// This command works by performing a read-modify-write on the port settings +/// configuration identified by the specified rack/switch/port. +#[derive(Parser, Debug, Clone)] +#[command(verbatim_doc_comment)] +#[command(name = "filter")] +pub struct CmdBgpFilter { + /// Id of the rack to add the address to. + #[arg(long)] + rack: Uuid, + + /// Switch to add the address to. + #[arg(long, value_enum)] + switch: Switch, + + /// Port to add the port to. + #[arg(long, value_enum)] + port: Port, + + /// Peer to apply allow list to. + #[arg(long)] + peer: IpAddr, + + /// Whether to apply the filter to imported or exported prefixes. + #[arg(long, value_enum)] + direction: FilterDirection, + + /// Prefixes to allow for the peer. + #[clap(long, conflicts_with = "no_filtering")] + allowed: Vec, + + /// Do not filter. + #[clap(long, conflicts_with = "allowed")] + no_filtering: bool, +} + +#[async_trait] +impl AuthenticatedCmd for CmdBgpFilter { + async fn run(&self, client: &Client) -> Result<()> { + let mut settings = + current_port_settings(client, &self.rack, &self.switch, &self.port).await?; + match settings.bgp_peers.get_mut(PHY0) { + None => return Err(anyhow::anyhow!("no BGP peers configured")), + Some(config) => { + let peer = config + .peers + .iter_mut() + .find(|x| x.addr == self.peer) + .ok_or(anyhow::anyhow!("specified peer does not exist"))?; + + let list: Vec = self + .allowed + .iter() + .map(|x| x.to_string().parse().unwrap()) + .collect(); + match self.direction { + FilterDirection::Import => { + if self.no_filtering { + peer.allowed_import = ImportExportPolicy::NoFiltering; + } else { + peer.allowed_import = ImportExportPolicy::Allow(list); + } + } + FilterDirection::Export => { + if self.no_filtering { + peer.allowed_export = ImportExportPolicy::NoFiltering; + } else { + peer.allowed_export = ImportExportPolicy::Allow(list); + } + } + } + } + } + client + .networking_switch_port_settings_create() + .body(settings) + .send() + .await?; + Ok(()) + } +} + +/// Manage switch port addresses. +#[derive(Parser, Debug, Clone)] +#[command(verbatim_doc_comment)] +#[command(name = "addr")] +pub struct CmdAddr { + #[clap(subcommand)] + subcmd: AddrSubCommand, +} + +#[async_trait] +impl AuthenticatedCmd for CmdAddr { + async fn run(&self, client: &Client) -> Result<()> { + match &self.subcmd { + AddrSubCommand::Add(cmd) => cmd.run(client).await, + AddrSubCommand::Del(cmd) => cmd.run(client).await, + } + } +} + +#[derive(Parser, Debug, Clone)] +enum AddrSubCommand { + /// Add an address to a port configuration. + Add(CmdAddrAdd), + + /// Remove an address from a port configuration. + Del(CmdAddrDel), +} + +/// Add an address to a switch port. +/// +/// Addresses are a part of switch port settings configuration. This command +/// works by performing a read-modify-write on the switch port settings +/// configuration identified by the specified rack/switch/port. +#[derive(Parser, Debug, Clone)] +#[command(verbatim_doc_comment)] +#[command(name = "add")] +pub struct CmdAddrAdd { + /// Id of the rack to add the address to. + #[arg(long)] + rack: Uuid, + + /// Switch to add the address to. + #[arg(long, value_enum)] + switch: Switch, + + /// Port to add the port to. + #[arg(long, value_enum)] + port: Port, + + /// Address to add. + #[arg(long)] + addr: oxnet::Ipv4Net, + + /// Address lot to allocate from. + #[arg(long)] + lot: NameOrId, + + /// Optional VLAN to assign to the address. + #[arg(long)] + vlan: Option, +} + +#[async_trait] +impl AuthenticatedCmd for CmdAddrAdd { + async fn run(&self, client: &Client) -> Result<()> { + let mut settings = + current_port_settings(client, &self.rack, &self.switch, &self.port).await?; + let addr = Address { + address: IpNet::V4(self.addr.to_string().parse().unwrap()), + address_lot: self.lot.clone(), + vlan_id: self.vlan, + }; + match settings.addresses.get_mut(PHY0) { + Some(ac) => { + ac.addresses.push(addr); + } + None => { + settings.addresses.insert( + String::from(PHY0), + AddressConfig { + addresses: vec![addr], + }, + ); + } + } + client + .networking_switch_port_settings_create() + .body(settings) + .send() + .await?; + Ok(()) + } +} + +/// Remove an address from a switch port. +/// +/// Addresses are a part of switch port settings configuration. This command +/// works by performing a read-modify-write on the switch port settings +/// configuration identified by the specified rack/switch/port. +#[derive(Parser, Debug, Clone)] +#[command(verbatim_doc_comment)] +#[command(name = "del")] +pub struct CmdAddrDel { + /// Id of the rack to remove the address from. + #[arg(long)] + rack: Uuid, + + /// Switch to remove the address from. + #[arg(long, value_enum)] + switch: Switch, + + /// Port to remove the address from. + #[arg(long, value_enum)] + port: Port, + + /// Address to remove. + #[arg(long)] + addr: oxnet::Ipv4Net, +} + +#[async_trait] +impl AuthenticatedCmd for CmdAddrDel { + async fn run(&self, client: &Client) -> Result<()> { + let mut settings = + current_port_settings(client, &self.rack, &self.switch, &self.port).await?; + if let Some(addrs) = settings.addresses.get_mut(PHY0) { + addrs + .addresses + .retain(|x| x.address.to_string() != self.addr.to_string()); + } + client + .networking_switch_port_settings_create() + .body(settings) + .send() + .await?; + Ok(()) + } +} + +/// Manage a rack's BGP configuration. +/// +/// Rack BGP configuration is centered around peers. The subcommands available +/// within this command allow for managing BGP peer sessions. +#[derive(Parser, Debug, Clone)] +#[command(verbatim_doc_comment)] +#[command(name = "config")] +pub struct CmdBgpConfig { + #[clap(subcommand)] + subcmd: BgpConfigSubCommand, +} + +#[async_trait] +impl AuthenticatedCmd for CmdBgpConfig { + async fn run(&self, client: &Client) -> Result<()> { + match &self.subcmd { + BgpConfigSubCommand::Peer(cmd) => cmd.run(client).await, + } + } +} + +#[derive(Parser, Debug, Clone)] +enum BgpConfigSubCommand { + /// Manage BGP peer configuration. + Peer(CmdBgpPeer), +} + +/// Manage BGP peers. +/// +/// This command provides add and delete subcommands for managing BGP peers. +/// BGP peer configuration is a part of a switch port settings configuration. +/// The peer add and remove subcommands perform read-modify-write operations +/// on switch port settings objects to manage BGP peer configurations. +#[derive(Parser, Debug, Clone)] +#[command(verbatim_doc_comment)] +#[command(name = "peer")] +pub struct CmdBgpPeer { + #[clap(subcommand)] + subcmd: BgpConfigPeerSubCommand, +} + +#[async_trait] +impl AuthenticatedCmd for CmdBgpPeer { + async fn run(&self, client: &Client) -> Result<()> { + match &self.subcmd { + BgpConfigPeerSubCommand::Add(cmd) => cmd.run(client).await, + BgpConfigPeerSubCommand::Del(cmd) => cmd.run(client).await, + } + } +} + +#[derive(Parser, Debug, Clone)] +enum BgpConfigPeerSubCommand { + /// Add a BGP peer to a port configuration. + Add(CmdBgpPeerAdd), + + /// Remove a BGP from a port configuration. + Del(CmdBgpPeerDel), +} + +/// Add a BGP peer to a given switch port configuration. +/// +/// This performs a read-modify-write on the specified switch port +/// configuration. +#[derive(Parser, Debug, Clone)] +#[command(verbatim_doc_comment)] +#[command(name = "add")] +pub struct CmdBgpPeerAdd { + /// Id of the rack to add the peer to. + #[arg(long)] + rack: Uuid, + + /// Switch to add the peer to. + #[arg(long, value_enum)] + switch: Switch, + + /// Port to add the peer to. + #[arg(long, value_enum)] + port: Port, + + /// Address of the peer to add. + #[arg(long)] + addr: IpAddr, + + /// BGP configuration this peer is associated with. + #[arg(long)] + bgp_config: NameOrId, + + /// Prefixes that may be imported form the peer. Empty list means all prefixes + /// allowed. + #[arg(long)] + allowed_imports: Vec, + + /// Prefixes that may be exported to the peer. Empty list means all prefixes + /// allowed. + #[arg(long)] + allowed_exports: Vec, + + /// Include the provided communities in updates sent to the peer. + #[arg(long)] + communities: Vec, + + /// How long to to wait between TCP connection retries (seconds). + #[clap(long, default_value_t = 0u32)] + pub connect_retry: u32, + + /// How long to delay sending an open request after establishing a TCP + /// session (seconds). + #[clap(long, default_value_t = 0u32)] + pub delay_open: u32, + + /// Enforce that the first AS in paths received from this peer is the + /// peer's AS. + #[clap(long, default_value_t = false)] + pub enforce_first_as: bool, + + /// How long to hold peer connections between keepalives (seconds). + #[clap(long, default_value_t = 6u32)] + pub hold_time: u32, + + /// How often to send keepalive requests (seconds). + #[clap(long, default_value_t = 2u32)] + pub keepalive: u32, + + /// How long to hold a peer in idle before attempting a new session + /// (seconds). + #[clap(long, default_value_t = 0u32)] + pub idle_hold_time: u32, + + /// Apply a local preference to routes received from this peer. + #[arg(long)] + pub local_pref: Option, + + /// Use the given key for TCP-MD5 authentication with the peer. + #[arg(long)] + pub md5_auth_key: Option, + + /// Require messages from a peer have a minimum IP time to live field. + #[arg(long)] + pub min_ttl: Option, + + /// Apply the provided multi-exit discriminator (MED) updates sent to + /// the peer. + #[arg(long)] + pub multi_exit_discriminator: Option, + + /// Require that a peer has a specified ASN. + #[arg(long)] + pub remote_asn: Option, + + /// Associate a VLAN ID with a peer. + #[arg(long)] + pub vlan_id: Option, +} + +#[async_trait] +impl AuthenticatedCmd for CmdBgpPeerAdd { + async fn run(&self, client: &Client) -> Result<()> { + let mut settings = + current_port_settings(client, &self.rack, &self.switch, &self.port).await?; + let peer = BgpPeer { + addr: self.addr, + allowed_import: if self.allowed_imports.is_empty() { + ImportExportPolicy::NoFiltering + } else { + ImportExportPolicy::Allow( + self.allowed_imports + .clone() + .into_iter() + .map(|x| x.to_string().parse().unwrap()) + .collect(), + ) + }, + allowed_export: if self.allowed_exports.is_empty() { + ImportExportPolicy::NoFiltering + } else { + ImportExportPolicy::Allow( + self.allowed_exports + .clone() + .into_iter() + .map(|x| x.to_string().parse().unwrap()) + .collect(), + ) + }, + bgp_config: self.bgp_config.clone(), + communities: self.communities.clone(), + connect_retry: self.connect_retry, + delay_open: self.delay_open, + enforce_first_as: self.enforce_first_as, + hold_time: self.hold_time, + idle_hold_time: self.idle_hold_time, + interface_name: PHY0.to_owned(), + keepalive: self.keepalive, + local_pref: self.local_pref, + md5_auth_key: self.md5_auth_key.clone(), + min_ttl: self.min_ttl, + multi_exit_discriminator: self.multi_exit_discriminator, + remote_asn: self.remote_asn, + vlan_id: self.vlan_id, + }; + match settings.bgp_peers.get_mut(PHY0) { + Some(conf) => { + conf.peers.push(peer); + } + None => { + settings + .bgp_peers + .insert(String::from(PHY0), BgpPeerConfig { peers: vec![peer] }); + } + } + client + .networking_switch_port_settings_create() + .body(settings) + .send() + .await?; + Ok(()) + } +} + +/// Remove a BGP peer from a given switch port configuration. +/// +/// This performs a read-modify-write on the specified switch port +/// configuration. +#[derive(Parser, Debug, Clone)] +#[command(verbatim_doc_comment)] +#[command(name = "del")] +pub struct CmdBgpPeerDel { + /// Id of the rack to remove the peer from. + #[arg(long)] + rack: Uuid, + + /// Switch to remove the peer from. + #[arg(long, value_enum)] + switch: Switch, + + /// Port to remove the peer from. + #[arg(long, value_enum)] + port: Port, + + /// Address of the peer to remove. + #[arg(long)] + addr: IpAddr, +} + +#[async_trait] +impl AuthenticatedCmd for CmdBgpPeerDel { + async fn run(&self, client: &Client) -> Result<()> { + let mut settings = + current_port_settings(client, &self.rack, &self.switch, &self.port).await?; + if let Some(config) = settings.bgp_peers.get_mut(PHY0) { + config.peers.retain(|x| x.addr != self.addr); + } + client + .networking_switch_port_settings_create() + .body(settings) + .send() + .await?; + Ok(()) + } +} + +/// Get the configuration of switch ports. +#[derive(Parser, Debug, Clone)] +#[command(verbatim_doc_comment)] +#[command(name = "net port config")] +pub struct CmdPortConfig {} + +#[async_trait] +impl AuthenticatedCmd for CmdPortConfig { + async fn run(&self, client: &Client) -> Result<()> { + let ports = client + .networking_switch_port_list() + .stream() + .try_collect::>() + .await?; + + let mut tw = TabWriter::new(std::io::stdout()).ansi(true); + + // TODO bad API, having to pull all address lots to work backwards from + // address reference to address lot block is terribad + let addr_lots = client + .networking_address_lot_list() + .stream() + .try_collect::>() + .await?; + + let mut addr_lot_blocks = Vec::new(); + for a in addr_lots.iter() { + let blocks = client + .networking_address_lot_block_list() + .address_lot(a.id) + .stream() + .try_collect::>() + .await?; + + for b in blocks.iter() { + addr_lot_blocks.push((a.clone(), b.clone())); + } + } + + for p in &ports { + if let Some(id) = p.port_settings_id { + let config = client + .networking_switch_port_settings_view() + .port(id) + .send() + .await? + .into_inner(); + + let bgp_configs: HashMap = client + .networking_bgp_config_list() + .stream() + .try_collect::>() + .await? + .into_iter() + .map(|x| (x.id, x.name)) + .collect(); + + println_nopipe!( + "{}{}{}", + p.switch_location.to_string().blue(), + "/".dimmed(), + p.port_name.blue(), + ); + + println_nopipe!( + "{}", + "=".repeat(p.port_name.len() + p.switch_location.to_string().len() + 1) + .dimmed() + ); + + writeln!( + &mut tw, + "{}\t{}\t{}", + "Autoneg".dimmed(), + "Fec".dimmed(), + "Speed".dimmed(), + )?; + + for l in &config.links { + writeln!(&mut tw, "{:?}\t{:?}\t{:?}", l.autoneg, l.fec, l.speed,)?; + } + tw.flush()?; + println_nopipe!(); + + writeln!( + &mut tw, + "{}\t{}\t{}", + "Address".dimmed(), + "Lot".dimmed(), + "VLAN".dimmed() + )?; + for a in &config.addresses { + let addr = match &a.address { + oxide::types::IpNet::V4(a) => a.to_string(), + oxide::types::IpNet::V6(a) => a.to_string(), + }; + + let alb = addr_lot_blocks + .iter() + .find(|x| x.1.id == a.address_lot_block_id) + .unwrap(); + + writeln!(&mut tw, "{}\t{}\t{:?}", addr, *alb.0.name, a.vlan_id)?; + } + tw.flush()?; + println_nopipe!(); + + writeln!( + &mut tw, + "{}\t{}\t{}\t{}\t{}\t{}\t{}\t{}\t{}\t{}\t{}\t{}\t{}\t{}\t{}\t{}\t{}", + "BGP Peer".dimmed(), + "Config".dimmed(), + "Export".dimmed(), + "Import".dimmed(), + "Communities".dimmed(), + "Connect Retry".dimmed(), + "Delay Open".dimmed(), + "Enforce First AS".dimmed(), + "Hold Time".dimmed(), + "Idle Hold Time".dimmed(), + "Keepalive".dimmed(), + "Local Pref".dimmed(), + "Md5 Auth".dimmed(), + "Min TTL".dimmed(), + "MED".dimmed(), + "Remote ASN".dimmed(), + "VLAN".dimmed(), + )?; + for p in &config.bgp_peers { + writeln!( + &mut tw, + "{}\t{}\t[{}]\t[{}]\t{:?}\t{}\t{}\t{}\t{}\t{}\t{}\t{:?}\t{:?}\t{:?}\t{:?}\t{:?}\t{:?}", + p.addr, + match &p.bgp_config { + NameOrId::Id(id) => bgp_configs[id].to_string(), + NameOrId::Name(name) => name.to_string(), + }, + match &p.allowed_export { + ImportExportPolicy::NoFiltering => String::from("no filtering"), + ImportExportPolicy::Allow(list) => list + .iter() + .map(|x| x.to_string()) + .collect::>() + .join(" "), + }, + match &p.allowed_import { + ImportExportPolicy::NoFiltering => String::from("no filtering"), + ImportExportPolicy::Allow(list) => list + .iter() + .map(|x| x.to_string()) + .collect::>() + .join(" "), + }, + p.communities, + p.connect_retry, + p.delay_open, + p.enforce_first_as, + p.hold_time, + p.idle_hold_time, + p.keepalive, + p.local_pref, + p.md5_auth_key, + p.min_ttl, + p.multi_exit_discriminator, + p.remote_asn, + p.vlan_id, + )?; + } + tw.flush()?; + println_nopipe!(); + + // Uncomment to see full payload + //println!(""); + //println!("{:#?}", config); + //println!(""); + } + } + + Ok(()) + } +} + +/// Get the status of BGP on the rack. +/// +/// This will show the peering status for all peers on all switches. +#[derive(Parser, Debug, Clone)] +#[command(verbatim_doc_comment)] +#[command(name = "net bgp status")] +pub struct CmdBgpStatus {} + +#[async_trait] +impl AuthenticatedCmd for CmdBgpStatus { + async fn run(&self, client: &Client) -> Result<()> { + let status = client.networking_bgp_status().send().await?.into_inner(); + + let (sw0, sw1) = status + .iter() + .partition(|x| x.switch == SwitchLocation::Switch0); + + println_nopipe!("{}", "switch0".dimmed()); + println_nopipe!("{}", "=======".dimmed()); + show_status(&sw0)?; + println_nopipe!(); + + println_nopipe!("{}", "switch1".dimmed()); + println_nopipe!("{}", "=======".dimmed()); + show_status(&sw1)?; + + Ok(()) + } +} + +fn show_status(st: &Vec<&BgpPeerStatus>) -> Result<()> { + let mut tw = TabWriter::new(std::io::stdout()).ansi(true); + writeln!( + &mut tw, + "{}\t{}\t{}\t{}\t{}", + "Peer Address".dimmed(), + "Local ASN".dimmed(), + "Remote ASN".dimmed(), + "Session State".dimmed(), + "State Duration".dimmed(), + )?; + for s in st { + writeln!( + tw, + "{}\t{}\t{}\t{:?}\t{}", + s.addr, + s.local_asn, + s.remote_asn, + s.state, + humantime::Duration::from(std::time::Duration::from_millis(s.state_duration_millis)), + )?; + } + tw.flush()?; + Ok(()) +} + +/// Get the status of switch ports. +#[derive(Parser, Debug, Clone)] +#[command(verbatim_doc_comment)] +#[command(name = "net port status")] +pub struct CmdPortStatus {} + +#[async_trait] +impl AuthenticatedCmd for CmdPortStatus { + async fn run(&self, client: &Client) -> Result<()> { + let ports = client + .networking_switch_port_list() + .stream() + .try_collect::>() + .await?; + + let (mut sw0, mut sw1): (Vec<&SwitchPort>, Vec<&SwitchPort>) = ports + .iter() + .partition(|x| x.switch_location.as_str() == "switch0"); + + sw0.sort_by_key(|x| x.port_name.as_str()); + sw1.sort_by_key(|x| x.port_name.as_str()); + + println_nopipe!("{}", "switch0".dimmed()); + println_nopipe!("{}", "=======".dimmed()); + self.show_switch(client, "switch0", &sw0).await?; + + println_nopipe!("{}", "switch1".dimmed()); + println_nopipe!("{}", "=======".dimmed()); + self.show_switch(client, "switch1", &sw1).await?; + + Ok(()) + } +} + +#[derive(Serialize, Deserialize, Clone, Debug)] +pub struct MacAddr { + a: [u8; 6], +} + +#[derive(Serialize, Deserialize, Clone, Debug)] +struct LinkStatus { + address: MacAddr, + enabled: bool, + autoneg: bool, + fec: String, + link_state: String, + fsm_state: String, + media: String, + speed: String, +} + +#[derive(Serialize, Deserialize, Clone, Debug)] +#[serde(rename_all = "snake_case")] +pub enum ReceiverPower { + /// The measurement is represents average optical power, in mW. + Average(f32), + + /// The measurement represents a peak-to-peak, in mW. + PeakToPeak(f32), +} + +#[derive(Serialize, Deserialize, Clone, Debug)] +struct Monitors { + receiver_power: Vec, + transmitter_bias_current: Vec, + transmitter_power: Vec, +} + +impl CmdPortStatus { + async fn show_switch(&self, c: &Client, sw: &str, ports: &Vec<&SwitchPort>) -> Result<()> { + let mut ltw = TabWriter::new(std::io::stdout()).ansi(true); + let mut mtw = TabWriter::new(std::io::stdout()).ansi(true); + + writeln!( + &mut ltw, + "{}\t{}\t{}\t{}\t{}\t{}\t{}\t{}\t{}", + "Port".dimmed(), + "Configured".dimmed(), + "Enabled".dimmed(), + "MAC".dimmed(), + "Autoneg".dimmed(), + "FEC".dimmed(), + "Link/FSM State".dimmed(), + "Media".dimmed(), + "Speed".dimmed(), + )?; + + writeln!( + &mut mtw, + "{}\t{}\t{}", + "Receiver Power".dimmed(), + "Transmitter Bias Current".dimmed(), + "Transmitter Power".dimmed(), + )?; + + for p in ports { + let status = c + .networking_switch_port_status() + .port(&p.port_name) + .rack_id(p.rack_id) + .switch_location(sw) + .send() + .await + .ok() + .map(|x| x.into_inner().0); + + let link = status.as_ref().map(|x| { + let ls: LinkStatus = + serde_json::from_value(x.get("link").unwrap().clone()).unwrap(); + ls + }); + + writeln!( + &mut ltw, + "{}\t{}\t{}\t{}\t{}\t{}\t{}\t{}\t{}", + p.port_name, + p.port_settings_id.is_some(), + link.as_ref() + .map(|x| x.enabled.to_string()) + .unwrap_or("-".to_string()), + link.as_ref() + .map(|x| format!( + "{:02x}:{:02x}:{:02x}:{:02x}:{:02x}:{:02x}", + x.address.a[0], + x.address.a[1], + x.address.a[2], + x.address.a[3], + x.address.a[4], + x.address.a[5] + )) + .unwrap_or("-".to_string()), + link.as_ref() + .map(|x| x.autoneg.to_string()) + .unwrap_or("-".to_string()), + link.as_ref() + .map(|x| x.fec.clone()) + .unwrap_or("-".to_string()), + link.as_ref() + .map(|x| format!("{}/{}", x.link_state, x.fsm_state)) + .unwrap_or("-".to_string()), + link.as_ref() + .map(|x| x.media.clone()) + .unwrap_or("-".to_string()), + link.as_ref() + .map(|x| x.speed.clone()) + .unwrap_or("-".to_string()), + )?; + + let monitors = status + .as_ref() + .and_then(|value| value.get("monitors")) + .and_then(|value| Monitors::deserialize(value).ok()); + + writeln!( + &mut mtw, + "{}\t{}\t{}", + monitors + .as_ref() + .map(|x| format!("{:?}", x.receiver_power)) + .unwrap_or("-".to_string()), + monitors + .as_ref() + .map(|x| format!("{:?}", x.transmitter_bias_current)) + .unwrap_or("-".to_string()), + monitors + .as_ref() + .map(|x| format!("{:?}", x.transmitter_power)) + .unwrap_or("-".to_string()), + )?; + } + + ltw.flush()?; + println_nopipe!(); + mtw.flush()?; + println_nopipe!(); + + Ok(()) + } +} + +// NOTE: This bonanza of befuckerry is needed to translate the current +// switch port settings view into a corresponding switch port +// settings create request. It's the preliminary step for a read- +// modify-write operation. +async fn create_current(settings_id: Uuid, client: &Client) -> Result { + let list = client + .networking_switch_port_settings_list() + .stream() + .try_collect::>() + .await?; + + let name = list + .iter() + .find(|x| x.id == settings_id) + .ok_or(anyhow::anyhow!("settings not found for {}", settings_id))? + .name + .clone(); + + let current = client + .networking_switch_port_settings_view() + .port(settings_id) + .send() + .await + .unwrap() + .into_inner(); + + let mut block_to_lot = HashMap::new(); + let lots = client + .networking_address_lot_list() + .stream() + .try_collect::>() + .await?; + + for lot in lots.iter() { + let lot_blocks = client + .networking_address_lot_block_list() + .address_lot(lot.id) + .stream() + .try_collect::>() + .await?; + for block in lot_blocks.iter() { + block_to_lot.insert(block.id, lot.id); + } + } + + let addrs: Vec
= current + .addresses + .clone() + .into_iter() + .map(|x| Address { + address: x.address, + address_lot: NameOrId::Id(block_to_lot[&x.address_lot_block_id]), + vlan_id: x.vlan_id, + }) + .collect(); + + let mut addresses = HashMap::new(); + addresses.insert(String::from(PHY0), AddressConfig { addresses: addrs }); + + let mut bgp_peers = HashMap::new(); + bgp_peers.insert( + String::from(PHY0), + BgpPeerConfig { + peers: current.bgp_peers, + }, + ); + + let groups: Vec = current + .groups + .iter() + .map(|x| NameOrId::Id(x.port_settings_group_id)) + .collect(); + + let mut interfaces: HashMap = current + .interfaces + .iter() + .map(|x| { + ( + x.interface_name.clone(), + SwitchInterfaceConfigCreate { + kind: match x.kind { + SwitchInterfaceKind2::Primary => SwitchInterfaceKind::Primary, + SwitchInterfaceKind2::Loopback => SwitchInterfaceKind::Loopback, + SwitchInterfaceKind2::Vlan => { + todo!("vlan interface outside vlan interfaces?") + } + }, + v6_enabled: x.v6_enabled, + }, + ) + }) + .collect(); + + for v in current.vlan_interfaces.iter() { + interfaces.insert( + format!("vlan-{}", v.vlan_id), + SwitchInterfaceConfigCreate { + kind: SwitchInterfaceKind::Vlan(v.vlan_id), + v6_enabled: false, + }, + ); + } + + let links: HashMap = current + .links + .iter() + .enumerate() + .map(|(i, x)| { + ( + format!("phy{}", i), + LinkConfigCreate { + autoneg: x.autoneg, + fec: x.fec, + lldp: LldpServiceConfigCreate { + //TODO + enabled: false, + lldp_config: None, + }, + mtu: x.mtu, + speed: x.speed, + }, + ) + }) + .collect(); + + let port_config = SwitchPortConfigCreate { + geometry: match current.port.geometry { + SwitchPortGeometry2::Qsfp28x1 => SwitchPortGeometry::Qsfp28x1, + SwitchPortGeometry2::Qsfp28x2 => SwitchPortGeometry::Qsfp28x2, + SwitchPortGeometry2::Sfp28x4 => SwitchPortGeometry::Sfp28x4, + }, + }; + + let route_config = RouteConfig { + routes: current + .routes + .iter() + .map(|x| Route { + dst: x.dst.clone(), + gw: x.gw.to_string().parse().unwrap(), + vid: x.vlan_id, + }) + .collect(), + }; + + let mut routes = HashMap::new(); + routes.insert(String::from(PHY0), route_config); + + let create = SwitchPortSettingsCreate { + addresses, + bgp_peers, + description: String::from("switch port settings"), + groups, + interfaces, + links, + name: name.parse().unwrap(), + port_config, + routes, + }; + + Ok(create) +} + +#[derive(clap::ValueEnum, Clone, Debug)] +enum Switch { + Switch0, + Switch1, +} + +impl std::fmt::Display for Switch { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", format!("{self:?}").to_lowercase()) + } +} + +#[derive(clap::ValueEnum, Clone, Debug)] +enum Port { + Qsfp0, + Qsfp1, + Qsfp2, + Qsfp3, + Qsfp4, + Qsfp5, + Qsfp6, + Qsfp7, + Qsfp8, + Qsfp9, + Qsfp10, + Qsfp11, + Qsfp12, + Qsfp13, + Qsfp14, + Qsfp15, + Qsfp16, + Qsfp17, + Qsfp18, + Qsfp19, + Qsfp20, + Qsfp21, + Qsfp22, + Qsfp23, + Qsfp24, + Qsfp25, + Qsfp26, + Qsfp27, + Qsfp28, + Qsfp29, + Qsfp30, + Qsfp31, +} + +impl std::fmt::Display for Port { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", format!("{self:?}").to_lowercase()) + } +} + +async fn get_port( + client: &Client, + rack_id: &Uuid, + switch: &Switch, + port: &Port, +) -> Result { + let ports = client + .networking_switch_port_list() + .stream() + .try_collect::>() + .await?; + + let port = ports + .into_iter() + .find(|x| { + x.rack_id == *rack_id + && x.switch_location == switch.to_string() + && x.port_name == port.to_string() + }) + .ok_or(anyhow::anyhow!( + "port {} not found for rack {} switch {}", + port, + switch, + rack_id + ))?; + + Ok(port) +} + +async fn get_port_settings_id( + client: &Client, + rack_id: &Uuid, + switch: &Switch, + port: &Port, +) -> Result { + let port = get_port(client, rack_id, switch, port).await?; + let id = port.port_settings_id.ok_or(anyhow::anyhow!( + "Port settings uninitialized. Initialize by creating a link." + ))?; + Ok(id) +} + +async fn current_port_settings( + client: &Client, + rack_id: &Uuid, + switch: &Switch, + port: &Port, +) -> Result { + let id = get_port_settings_id(client, rack_id, switch, port).await?; + let settings = create_current(id, client).await?; + Ok(settings) +} diff --git a/cli/src/cmd_timeseries/mod.rs b/cli/src/cmd_timeseries/mod.rs index 756a17f7..d2198065 100644 --- a/cli/src/cmd_timeseries/mod.rs +++ b/cli/src/cmd_timeseries/mod.rs @@ -18,11 +18,10 @@ use crossterm::{ execute, terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen}, }; -use oxide::context::Context; +use oxide::Client; use ratatui::{prelude::CrosstermBackend, Terminal}; use self::dashboard::Dashboard; -use crate::RunnableCmd; /// Graph the results of an OxQL timeseries query. #[derive(Parser, Debug, Clone)] @@ -40,14 +39,14 @@ pub struct CmdTimeseriesDashboard { type Tui = Terminal>; #[async_trait] -impl RunnableCmd for CmdTimeseriesDashboard { - async fn run(&self, ctx: &Context) -> Result<()> { +impl crate::AuthenticatedCmd for CmdTimeseriesDashboard { + async fn run(&self, client: &Client) -> Result<()> { anyhow::ensure!( self.interval > 0 && self.interval < 1_000, "Please provide a reasonable update interval" ); let interval = Duration::from_secs(self.interval); - let client = ctx.client()?.clone(); + let client = client.clone(); let mut terminal = init_tui()?; let res = Dashboard::new(&self.query, interval) .run(&mut terminal, client) diff --git a/cli/src/cmd_version.rs b/cli/src/cmd_version.rs index 9c6b256c..6e143ab9 100644 --- a/cli/src/cmd_version.rs +++ b/cli/src/cmd_version.rs @@ -7,9 +7,9 @@ use anyhow::Result; use async_trait::async_trait; use clap::Parser; -use oxide::{context::Context, Client}; +use oxide::Client; -use crate::RunnableCmd; +use crate::{context::Context, RunnableCmd}; pub mod built_info { include!(concat!(env!("OUT_DIR"), "/built.rs")); diff --git a/cli/src/context.rs b/cli/src/context.rs new file mode 100644 index 00000000..5b5e514f --- /dev/null +++ b/cli/src/context.rs @@ -0,0 +1,62 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +// Copyright 2024 Oxide Computer Company + +use std::path::PathBuf; + +use anyhow::Result; +use oxide::{BasicConfigFile, ClientConfig, CredentialsFile}; +use serde::{de::DeserializeOwned, Deserialize}; + +/// The Context is what we use to carry globally relevant information around +/// to subcommands. This includes configuration information and top-level +/// command-line options. This may be used to construct an authenticated +/// client if the subcommand requires it. +pub struct Context { + client_config: ClientConfig, + cred_file: CredentialsFile, + config_file: ConfigFile, +} + +#[derive(Deserialize, Debug, Default)] +#[serde(rename_all = "kebab-case")] +pub struct ConfigFile { + #[serde(flatten)] + pub basics: BasicConfigFile, +} + +fn read_or_default(path: PathBuf) -> Result { + match std::fs::read_to_string(path) { + Ok(contents) => Ok(toml::from_str(&contents)?), + Err(e) if e.kind() == std::io::ErrorKind::NotFound => Ok(T::default()), + Err(e) => Err(e.into()), + } +} + +impl Context { + pub fn new(client_config: ClientConfig) -> Result { + let config_dir = client_config.config_dir(); + let cred_file = read_or_default(config_dir.join("credentials.toml"))?; + let config_file = read_or_default(config_dir.join("config.toml"))?; + + Ok(Self { + client_config, + cred_file, + config_file, + }) + } + + pub fn client_config(&self) -> &ClientConfig { + &self.client_config + } + + pub fn cred_file(&self) -> &CredentialsFile { + &self.cred_file + } + + pub fn config_file(&self) -> &ConfigFile { + &self.config_file + } +} diff --git a/cli/src/generated_cli.rs b/cli/src/generated_cli.rs index 6e1b3ee9..b40f2691 100644 --- a/cli/src/generated_cli.rs +++ b/cli/src/generated_cli.rs @@ -164,8 +164,8 @@ impl Cli { CliCommand::NetworkingBgpAnnounceSetList => { Self::cli_networking_bgp_announce_set_list() } - CliCommand::NetworkingBgpAnnounceSetCreate => { - Self::cli_networking_bgp_announce_set_create() + CliCommand::NetworkingBgpAnnounceSetUpdate => { + Self::cli_networking_bgp_announce_set_update() } CliCommand::NetworkingBgpAnnounceSetDelete => { Self::cli_networking_bgp_announce_set_delete() @@ -222,6 +222,16 @@ impl Cli { CliCommand::UtilizationView => Self::cli_utilization_view(), CliCommand::VpcFirewallRulesView => Self::cli_vpc_firewall_rules_view(), CliCommand::VpcFirewallRulesUpdate => Self::cli_vpc_firewall_rules_update(), + CliCommand::VpcRouterRouteList => Self::cli_vpc_router_route_list(), + CliCommand::VpcRouterRouteCreate => Self::cli_vpc_router_route_create(), + CliCommand::VpcRouterRouteView => Self::cli_vpc_router_route_view(), + CliCommand::VpcRouterRouteUpdate => Self::cli_vpc_router_route_update(), + CliCommand::VpcRouterRouteDelete => Self::cli_vpc_router_route_delete(), + CliCommand::VpcRouterList => Self::cli_vpc_router_list(), + CliCommand::VpcRouterCreate => Self::cli_vpc_router_create(), + CliCommand::VpcRouterView => Self::cli_vpc_router_view(), + CliCommand::VpcRouterUpdate => Self::cli_vpc_router_update(), + CliCommand::VpcRouterDelete => Self::cli_vpc_router_delete(), CliCommand::VpcSubnetList => Self::cli_vpc_subnet_list(), CliCommand::VpcSubnetCreate => Self::cli_vpc_subnet_create(), CliCommand::VpcSubnetView => Self::cli_vpc_subnet_view(), @@ -4202,7 +4212,7 @@ impl Cli { .about("Get originated routes for a BGP configuration") } - pub fn cli_networking_bgp_announce_set_create() -> clap::Command { + pub fn cli_networking_bgp_announce_set_update() -> clap::Command { clap::Command::new("") .arg( clap::Arg::new("description") @@ -4230,7 +4240,11 @@ impl Cli { .action(clap::ArgAction::SetTrue) .help("XXX"), ) - .about("Create new BGP announce set") + .about("Update BGP announce set") + .long_about( + "If the announce set exists, this endpoint replaces the existing announce set \ + with the one specified.", + ) } pub fn cli_networking_bgp_announce_set_delete() -> clap::Command { @@ -5062,7 +5076,7 @@ impl Cli { .about("Replace firewall rules") } - pub fn cli_vpc_subnet_list() -> clap::Command { + pub fn cli_vpc_router_route_list() -> clap::Command { clap::Command::new("") .arg( clap::Arg::new("limit") @@ -5080,6 +5094,13 @@ impl Cli { "Name or ID of the project, only required if `vpc` is provided as a `Name`", ), ) + .arg( + clap::Arg::new("router") + .long("router") + .value_parser(clap::value_parser!(types::NameOrId)) + .required(true) + .help("Name or ID of the router"), + ) .arg( clap::Arg::new("sort-by") .long("sort-by") @@ -5097,13 +5118,16 @@ impl Cli { clap::Arg::new("vpc") .long("vpc") .value_parser(clap::value_parser!(types::NameOrId)) - .required(true) - .help("Name or ID of the VPC"), + .required(false) + .help( + "Name or ID of the VPC, only required if `router` is provided as a `Name`", + ), ) - .about("List subnets") + .about("List routes") + .long_about("List the routes associated with a router in a particular VPC.") } - pub fn cli_vpc_subnet_create() -> clap::Command { + pub fn cli_vpc_router_route_create() -> clap::Command { clap::Command::new("") .arg( clap::Arg::new("description") @@ -5111,29 +5135,6 @@ impl Cli { .value_parser(clap::value_parser!(String)) .required_unless_present("json-body"), ) - .arg( - clap::Arg::new("ipv4-block") - .long("ipv4-block") - .value_parser(clap::value_parser!(types::Ipv4Net)) - .required_unless_present("json-body") - .help( - "The IPv4 address range for this subnet.\n\nIt must be allocated from an \ - RFC 1918 private address range, and must not overlap with any other \ - existing subnet in the VPC.", - ), - ) - .arg( - clap::Arg::new("ipv6-block") - .long("ipv6-block") - .value_parser(clap::value_parser!(types::Ipv6Net)) - .required(false) - .help( - "The IPv6 address range for this subnet.\n\nIt must be allocated from the \ - RFC 4193 Unique Local Address range, with the prefix equal to the parent \ - VPC's prefix. A random `/64` block will be assigned if one is not \ - provided. It must not overlap with any existing subnet in the VPC.", - ), - ) .arg( clap::Arg::new("name") .long("name") @@ -5149,18 +5150,27 @@ impl Cli { "Name or ID of the project, only required if `vpc` is provided as a `Name`", ), ) + .arg( + clap::Arg::new("router") + .long("router") + .value_parser(clap::value_parser!(types::NameOrId)) + .required(true) + .help("Name or ID of the router"), + ) .arg( clap::Arg::new("vpc") .long("vpc") .value_parser(clap::value_parser!(types::NameOrId)) - .required(true) - .help("Name or ID of the VPC"), + .required(false) + .help( + "Name or ID of the VPC, only required if `router` is provided as a `Name`", + ), ) .arg( clap::Arg::new("json-body") .long("json-body") .value_name("JSON-FILE") - .required(false) + .required(true) .value_parser(clap::value_parser!(std::path::PathBuf)) .help("Path to a file that contains the full json body."), ) @@ -5170,10 +5180,10 @@ impl Cli { .action(clap::ArgAction::SetTrue) .help("XXX"), ) - .about("Create subnet") + .about("Create route") } - pub fn cli_vpc_subnet_view() -> clap::Command { + pub fn cli_vpc_router_route_view() -> clap::Command { clap::Command::new("") .arg( clap::Arg::new("project") @@ -5185,23 +5195,32 @@ impl Cli { ), ) .arg( - clap::Arg::new("subnet") - .long("subnet") + clap::Arg::new("route") + .long("route") .value_parser(clap::value_parser!(types::NameOrId)) .required(true) - .help("Name or ID of the subnet"), + .help("Name or ID of the route"), + ) + .arg( + clap::Arg::new("router") + .long("router") + .value_parser(clap::value_parser!(types::NameOrId)) + .required(true) + .help("Name or ID of the router"), ) .arg( clap::Arg::new("vpc") .long("vpc") .value_parser(clap::value_parser!(types::NameOrId)) .required(false) - .help("Name or ID of the VPC"), + .help( + "Name or ID of the VPC, only required if `router` is provided as a `Name`", + ), ) - .about("Fetch subnet") + .about("Fetch route") } - pub fn cli_vpc_subnet_update() -> clap::Command { + pub fn cli_vpc_router_route_update() -> clap::Command { clap::Command::new("") .arg( clap::Arg::new("description") @@ -5225,24 +5244,33 @@ impl Cli { ), ) .arg( - clap::Arg::new("subnet") - .long("subnet") + clap::Arg::new("route") + .long("route") .value_parser(clap::value_parser!(types::NameOrId)) .required(true) - .help("Name or ID of the subnet"), + .help("Name or ID of the route"), + ) + .arg( + clap::Arg::new("router") + .long("router") + .value_parser(clap::value_parser!(types::NameOrId)) + .required(false) + .help("Name or ID of the router"), ) .arg( clap::Arg::new("vpc") .long("vpc") .value_parser(clap::value_parser!(types::NameOrId)) .required(false) - .help("Name or ID of the VPC"), + .help( + "Name or ID of the VPC, only required if `router` is provided as a `Name`", + ), ) .arg( clap::Arg::new("json-body") .long("json-body") .value_name("JSON-FILE") - .required(false) + .required(true) .value_parser(clap::value_parser!(std::path::PathBuf)) .help("Path to a file that contains the full json body."), ) @@ -5252,10 +5280,10 @@ impl Cli { .action(clap::ArgAction::SetTrue) .help("XXX"), ) - .about("Update subnet") + .about("Update route") } - pub fn cli_vpc_subnet_delete() -> clap::Command { + pub fn cli_vpc_router_route_delete() -> clap::Command { clap::Command::new("") .arg( clap::Arg::new("project") @@ -5267,23 +5295,32 @@ impl Cli { ), ) .arg( - clap::Arg::new("subnet") - .long("subnet") + clap::Arg::new("route") + .long("route") .value_parser(clap::value_parser!(types::NameOrId)) .required(true) - .help("Name or ID of the subnet"), + .help("Name or ID of the route"), + ) + .arg( + clap::Arg::new("router") + .long("router") + .value_parser(clap::value_parser!(types::NameOrId)) + .required(false) + .help("Name or ID of the router"), ) .arg( clap::Arg::new("vpc") .long("vpc") .value_parser(clap::value_parser!(types::NameOrId)) .required(false) - .help("Name or ID of the VPC"), + .help( + "Name or ID of the VPC, only required if `router` is provided as a `Name`", + ), ) - .about("Delete subnet") + .about("Delete route") } - pub fn cli_vpc_subnet_list_network_interfaces() -> clap::Command { + pub fn cli_vpc_router_list() -> clap::Command { clap::Command::new("") .arg( clap::Arg::new("limit") @@ -5314,56 +5351,17 @@ impl Cli { )) .required(false), ) - .arg( - clap::Arg::new("subnet") - .long("subnet") - .value_parser(clap::value_parser!(types::NameOrId)) - .required(true) - .help("Name or ID of the subnet"), - ) .arg( clap::Arg::new("vpc") .long("vpc") .value_parser(clap::value_parser!(types::NameOrId)) - .required(false) - .help("Name or ID of the VPC"), - ) - .about("List network interfaces") - } - - pub fn cli_vpc_list() -> clap::Command { - clap::Command::new("") - .arg( - clap::Arg::new("limit") - .long("limit") - .value_parser(clap::value_parser!(std::num::NonZeroU32)) - .required(false) - .help("Maximum number of items returned by a single call"), - ) - .arg( - clap::Arg::new("project") - .long("project") - .value_parser(clap::value_parser!(types::NameOrId)) .required(true) - .help("Name or ID of the project"), - ) - .arg( - clap::Arg::new("sort-by") - .long("sort-by") - .value_parser(clap::builder::TypedValueParser::map( - clap::builder::PossibleValuesParser::new([ - types::NameOrIdSortMode::NameAscending.to_string(), - types::NameOrIdSortMode::NameDescending.to_string(), - types::NameOrIdSortMode::IdAscending.to_string(), - ]), - |s| types::NameOrIdSortMode::try_from(s).unwrap(), - )) - .required(false), + .help("Name or ID of the VPC"), ) - .about("List VPCs") + .about("List routers") } - pub fn cli_vpc_create() -> clap::Command { + pub fn cli_vpc_router_create() -> clap::Command { clap::Command::new("") .arg( clap::Arg::new("description") @@ -5372,35 +5370,26 @@ impl Cli { .required_unless_present("json-body"), ) .arg( - clap::Arg::new("dns-name") - .long("dns-name") + clap::Arg::new("name") + .long("name") .value_parser(clap::value_parser!(types::Name)) .required_unless_present("json-body"), ) .arg( - clap::Arg::new("ipv6-prefix") - .long("ipv6-prefix") - .value_parser(clap::value_parser!(types::Ipv6Net)) + clap::Arg::new("project") + .long("project") + .value_parser(clap::value_parser!(types::NameOrId)) .required(false) .help( - "The IPv6 prefix for this VPC\n\nAll IPv6 subnets created from this VPC \ - must be taken from this range, which should be a Unique Local Address in \ - the range `fd00::/48`. The default VPC Subnet will have the first `/64` \ - range from this prefix.", + "Name or ID of the project, only required if `vpc` is provided as a `Name`", ), ) .arg( - clap::Arg::new("name") - .long("name") - .value_parser(clap::value_parser!(types::Name)) - .required_unless_present("json-body"), - ) - .arg( - clap::Arg::new("project") - .long("project") + clap::Arg::new("vpc") + .long("vpc") .value_parser(clap::value_parser!(types::NameOrId)) .required(true) - .help("Name or ID of the project"), + .help("Name or ID of the VPC"), ) .arg( clap::Arg::new("json-body") @@ -5416,29 +5405,38 @@ impl Cli { .action(clap::ArgAction::SetTrue) .help("XXX"), ) - .about("Create VPC") + .about("Create VPC router") } - pub fn cli_vpc_view() -> clap::Command { + pub fn cli_vpc_router_view() -> clap::Command { clap::Command::new("") .arg( clap::Arg::new("project") .long("project") .value_parser(clap::value_parser!(types::NameOrId)) .required(false) - .help("Name or ID of the project"), + .help( + "Name or ID of the project, only required if `vpc` is provided as a `Name`", + ), + ) + .arg( + clap::Arg::new("router") + .long("router") + .value_parser(clap::value_parser!(types::NameOrId)) + .required(true) + .help("Name or ID of the router"), ) .arg( clap::Arg::new("vpc") .long("vpc") .value_parser(clap::value_parser!(types::NameOrId)) - .required(true) + .required(false) .help("Name or ID of the VPC"), ) - .about("Fetch VPC") + .about("Fetch router") } - pub fn cli_vpc_update() -> clap::Command { + pub fn cli_vpc_router_update() -> clap::Command { clap::Command::new("") .arg( clap::Arg::new("description") @@ -5446,12 +5444,6 @@ impl Cli { .value_parser(clap::value_parser!(String)) .required(false), ) - .arg( - clap::Arg::new("dns-name") - .long("dns-name") - .value_parser(clap::value_parser!(types::Name)) - .required(false), - ) .arg( clap::Arg::new("name") .long("name") @@ -5463,13 +5455,22 @@ impl Cli { .long("project") .value_parser(clap::value_parser!(types::NameOrId)) .required(false) - .help("Name or ID of the project"), + .help( + "Name or ID of the project, only required if `vpc` is provided as a `Name`", + ), + ) + .arg( + clap::Arg::new("router") + .long("router") + .value_parser(clap::value_parser!(types::NameOrId)) + .required(true) + .help("Name or ID of the router"), ) .arg( clap::Arg::new("vpc") .long("vpc") .value_parser(clap::value_parser!(types::NameOrId)) - .required(true) + .required(false) .help("Name or ID of the VPC"), ) .arg( @@ -5486,17 +5487,67 @@ impl Cli { .action(clap::ArgAction::SetTrue) .help("XXX"), ) - .about("Update a VPC") + .about("Update router") } - pub fn cli_vpc_delete() -> clap::Command { + pub fn cli_vpc_router_delete() -> clap::Command { clap::Command::new("") .arg( clap::Arg::new("project") .long("project") .value_parser(clap::value_parser!(types::NameOrId)) .required(false) - .help("Name or ID of the project"), + .help( + "Name or ID of the project, only required if `vpc` is provided as a `Name`", + ), + ) + .arg( + clap::Arg::new("router") + .long("router") + .value_parser(clap::value_parser!(types::NameOrId)) + .required(true) + .help("Name or ID of the router"), + ) + .arg( + clap::Arg::new("vpc") + .long("vpc") + .value_parser(clap::value_parser!(types::NameOrId)) + .required(false) + .help("Name or ID of the VPC"), + ) + .about("Delete router") + } + + pub fn cli_vpc_subnet_list() -> clap::Command { + clap::Command::new("") + .arg( + clap::Arg::new("limit") + .long("limit") + .value_parser(clap::value_parser!(std::num::NonZeroU32)) + .required(false) + .help("Maximum number of items returned by a single call"), + ) + .arg( + clap::Arg::new("project") + .long("project") + .value_parser(clap::value_parser!(types::NameOrId)) + .required(false) + .help( + "Name or ID of the project, only required if `vpc` is provided as a `Name`", + ), + ) + .arg( + clap::Arg::new("sort-by") + .long("sort-by") + .value_parser(clap::builder::TypedValueParser::map( + clap::builder::PossibleValuesParser::new([ + types::NameOrIdSortMode::NameAscending.to_string(), + types::NameOrIdSortMode::NameDescending.to_string(), + types::NameOrIdSortMode::IdAscending.to_string(), + ]), + |s| types::NameOrIdSortMode::try_from(s).unwrap(), + )) + .required(false), ) .arg( clap::Arg::new("vpc") @@ -5505,25 +5556,452 @@ impl Cli { .required(true) .help("Name or ID of the VPC"), ) - .about("Delete VPC") + .about("List subnets") } - pub async fn execute(&self, cmd: CliCommand, matches: &clap::ArgMatches) -> anyhow::Result<()> { - match cmd { - CliCommand::DeviceAuthRequest => self.execute_device_auth_request(matches).await, - CliCommand::DeviceAuthConfirm => self.execute_device_auth_confirm(matches).await, - CliCommand::DeviceAccessToken => self.execute_device_access_token(matches).await, - CliCommand::ProbeList => self.execute_probe_list(matches).await, - CliCommand::ProbeCreate => self.execute_probe_create(matches).await, - CliCommand::ProbeView => self.execute_probe_view(matches).await, - CliCommand::ProbeDelete => self.execute_probe_delete(matches).await, - CliCommand::LoginSaml => self.execute_login_saml(matches).await, - CliCommand::CertificateList => self.execute_certificate_list(matches).await, - CliCommand::CertificateCreate => self.execute_certificate_create(matches).await, - CliCommand::CertificateView => self.execute_certificate_view(matches).await, - CliCommand::CertificateDelete => self.execute_certificate_delete(matches).await, - CliCommand::DiskList => self.execute_disk_list(matches).await, - CliCommand::DiskCreate => self.execute_disk_create(matches).await, + pub fn cli_vpc_subnet_create() -> clap::Command { + clap::Command::new("") + .arg( + clap::Arg::new("custom-router") + .long("custom-router") + .value_parser(clap::value_parser!(types::NameOrId)) + .required(false) + .help( + "An optional router, used to direct packets sent from hosts in this \ + subnet to any destination address.\n\nCustom routers apply in addition \ + to the VPC-wide *system* router, and have higher priority than the \ + system router for an otherwise equal-prefix-length match.", + ), + ) + .arg( + clap::Arg::new("description") + .long("description") + .value_parser(clap::value_parser!(String)) + .required_unless_present("json-body"), + ) + .arg( + clap::Arg::new("ipv4-block") + .long("ipv4-block") + .value_parser(clap::value_parser!(types::Ipv4Net)) + .required_unless_present("json-body") + .help( + "The IPv4 address range for this subnet.\n\nIt must be allocated from an \ + RFC 1918 private address range, and must not overlap with any other \ + existing subnet in the VPC.", + ), + ) + .arg( + clap::Arg::new("ipv6-block") + .long("ipv6-block") + .value_parser(clap::value_parser!(types::Ipv6Net)) + .required(false) + .help( + "The IPv6 address range for this subnet.\n\nIt must be allocated from the \ + RFC 4193 Unique Local Address range, with the prefix equal to the parent \ + VPC's prefix. A random `/64` block will be assigned if one is not \ + provided. It must not overlap with any existing subnet in the VPC.", + ), + ) + .arg( + clap::Arg::new("name") + .long("name") + .value_parser(clap::value_parser!(types::Name)) + .required_unless_present("json-body"), + ) + .arg( + clap::Arg::new("project") + .long("project") + .value_parser(clap::value_parser!(types::NameOrId)) + .required(false) + .help( + "Name or ID of the project, only required if `vpc` is provided as a `Name`", + ), + ) + .arg( + clap::Arg::new("vpc") + .long("vpc") + .value_parser(clap::value_parser!(types::NameOrId)) + .required(true) + .help("Name or ID of the VPC"), + ) + .arg( + clap::Arg::new("json-body") + .long("json-body") + .value_name("JSON-FILE") + .required(false) + .value_parser(clap::value_parser!(std::path::PathBuf)) + .help("Path to a file that contains the full json body."), + ) + .arg( + clap::Arg::new("json-body-template") + .long("json-body-template") + .action(clap::ArgAction::SetTrue) + .help("XXX"), + ) + .about("Create subnet") + } + + pub fn cli_vpc_subnet_view() -> clap::Command { + clap::Command::new("") + .arg( + clap::Arg::new("project") + .long("project") + .value_parser(clap::value_parser!(types::NameOrId)) + .required(false) + .help( + "Name or ID of the project, only required if `vpc` is provided as a `Name`", + ), + ) + .arg( + clap::Arg::new("subnet") + .long("subnet") + .value_parser(clap::value_parser!(types::NameOrId)) + .required(true) + .help("Name or ID of the subnet"), + ) + .arg( + clap::Arg::new("vpc") + .long("vpc") + .value_parser(clap::value_parser!(types::NameOrId)) + .required(false) + .help("Name or ID of the VPC"), + ) + .about("Fetch subnet") + } + + pub fn cli_vpc_subnet_update() -> clap::Command { + clap::Command::new("") + .arg( + clap::Arg::new("custom-router") + .long("custom-router") + .value_parser(clap::value_parser!(types::NameOrId)) + .required(false) + .help( + "An optional router, used to direct packets sent from hosts in this \ + subnet to any destination address.", + ), + ) + .arg( + clap::Arg::new("description") + .long("description") + .value_parser(clap::value_parser!(String)) + .required(false), + ) + .arg( + clap::Arg::new("name") + .long("name") + .value_parser(clap::value_parser!(types::Name)) + .required(false), + ) + .arg( + clap::Arg::new("project") + .long("project") + .value_parser(clap::value_parser!(types::NameOrId)) + .required(false) + .help( + "Name or ID of the project, only required if `vpc` is provided as a `Name`", + ), + ) + .arg( + clap::Arg::new("subnet") + .long("subnet") + .value_parser(clap::value_parser!(types::NameOrId)) + .required(true) + .help("Name or ID of the subnet"), + ) + .arg( + clap::Arg::new("vpc") + .long("vpc") + .value_parser(clap::value_parser!(types::NameOrId)) + .required(false) + .help("Name or ID of the VPC"), + ) + .arg( + clap::Arg::new("json-body") + .long("json-body") + .value_name("JSON-FILE") + .required(false) + .value_parser(clap::value_parser!(std::path::PathBuf)) + .help("Path to a file that contains the full json body."), + ) + .arg( + clap::Arg::new("json-body-template") + .long("json-body-template") + .action(clap::ArgAction::SetTrue) + .help("XXX"), + ) + .about("Update subnet") + } + + pub fn cli_vpc_subnet_delete() -> clap::Command { + clap::Command::new("") + .arg( + clap::Arg::new("project") + .long("project") + .value_parser(clap::value_parser!(types::NameOrId)) + .required(false) + .help( + "Name or ID of the project, only required if `vpc` is provided as a `Name`", + ), + ) + .arg( + clap::Arg::new("subnet") + .long("subnet") + .value_parser(clap::value_parser!(types::NameOrId)) + .required(true) + .help("Name or ID of the subnet"), + ) + .arg( + clap::Arg::new("vpc") + .long("vpc") + .value_parser(clap::value_parser!(types::NameOrId)) + .required(false) + .help("Name or ID of the VPC"), + ) + .about("Delete subnet") + } + + pub fn cli_vpc_subnet_list_network_interfaces() -> clap::Command { + clap::Command::new("") + .arg( + clap::Arg::new("limit") + .long("limit") + .value_parser(clap::value_parser!(std::num::NonZeroU32)) + .required(false) + .help("Maximum number of items returned by a single call"), + ) + .arg( + clap::Arg::new("project") + .long("project") + .value_parser(clap::value_parser!(types::NameOrId)) + .required(false) + .help( + "Name or ID of the project, only required if `vpc` is provided as a `Name`", + ), + ) + .arg( + clap::Arg::new("sort-by") + .long("sort-by") + .value_parser(clap::builder::TypedValueParser::map( + clap::builder::PossibleValuesParser::new([ + types::NameOrIdSortMode::NameAscending.to_string(), + types::NameOrIdSortMode::NameDescending.to_string(), + types::NameOrIdSortMode::IdAscending.to_string(), + ]), + |s| types::NameOrIdSortMode::try_from(s).unwrap(), + )) + .required(false), + ) + .arg( + clap::Arg::new("subnet") + .long("subnet") + .value_parser(clap::value_parser!(types::NameOrId)) + .required(true) + .help("Name or ID of the subnet"), + ) + .arg( + clap::Arg::new("vpc") + .long("vpc") + .value_parser(clap::value_parser!(types::NameOrId)) + .required(false) + .help("Name or ID of the VPC"), + ) + .about("List network interfaces") + } + + pub fn cli_vpc_list() -> clap::Command { + clap::Command::new("") + .arg( + clap::Arg::new("limit") + .long("limit") + .value_parser(clap::value_parser!(std::num::NonZeroU32)) + .required(false) + .help("Maximum number of items returned by a single call"), + ) + .arg( + clap::Arg::new("project") + .long("project") + .value_parser(clap::value_parser!(types::NameOrId)) + .required(true) + .help("Name or ID of the project"), + ) + .arg( + clap::Arg::new("sort-by") + .long("sort-by") + .value_parser(clap::builder::TypedValueParser::map( + clap::builder::PossibleValuesParser::new([ + types::NameOrIdSortMode::NameAscending.to_string(), + types::NameOrIdSortMode::NameDescending.to_string(), + types::NameOrIdSortMode::IdAscending.to_string(), + ]), + |s| types::NameOrIdSortMode::try_from(s).unwrap(), + )) + .required(false), + ) + .about("List VPCs") + } + + pub fn cli_vpc_create() -> clap::Command { + clap::Command::new("") + .arg( + clap::Arg::new("description") + .long("description") + .value_parser(clap::value_parser!(String)) + .required_unless_present("json-body"), + ) + .arg( + clap::Arg::new("dns-name") + .long("dns-name") + .value_parser(clap::value_parser!(types::Name)) + .required_unless_present("json-body"), + ) + .arg( + clap::Arg::new("ipv6-prefix") + .long("ipv6-prefix") + .value_parser(clap::value_parser!(types::Ipv6Net)) + .required(false) + .help( + "The IPv6 prefix for this VPC\n\nAll IPv6 subnets created from this VPC \ + must be taken from this range, which should be a Unique Local Address in \ + the range `fd00::/48`. The default VPC Subnet will have the first `/64` \ + range from this prefix.", + ), + ) + .arg( + clap::Arg::new("name") + .long("name") + .value_parser(clap::value_parser!(types::Name)) + .required_unless_present("json-body"), + ) + .arg( + clap::Arg::new("project") + .long("project") + .value_parser(clap::value_parser!(types::NameOrId)) + .required(true) + .help("Name or ID of the project"), + ) + .arg( + clap::Arg::new("json-body") + .long("json-body") + .value_name("JSON-FILE") + .required(false) + .value_parser(clap::value_parser!(std::path::PathBuf)) + .help("Path to a file that contains the full json body."), + ) + .arg( + clap::Arg::new("json-body-template") + .long("json-body-template") + .action(clap::ArgAction::SetTrue) + .help("XXX"), + ) + .about("Create VPC") + } + + pub fn cli_vpc_view() -> clap::Command { + clap::Command::new("") + .arg( + clap::Arg::new("project") + .long("project") + .value_parser(clap::value_parser!(types::NameOrId)) + .required(false) + .help("Name or ID of the project"), + ) + .arg( + clap::Arg::new("vpc") + .long("vpc") + .value_parser(clap::value_parser!(types::NameOrId)) + .required(true) + .help("Name or ID of the VPC"), + ) + .about("Fetch VPC") + } + + pub fn cli_vpc_update() -> clap::Command { + clap::Command::new("") + .arg( + clap::Arg::new("description") + .long("description") + .value_parser(clap::value_parser!(String)) + .required(false), + ) + .arg( + clap::Arg::new("dns-name") + .long("dns-name") + .value_parser(clap::value_parser!(types::Name)) + .required(false), + ) + .arg( + clap::Arg::new("name") + .long("name") + .value_parser(clap::value_parser!(types::Name)) + .required(false), + ) + .arg( + clap::Arg::new("project") + .long("project") + .value_parser(clap::value_parser!(types::NameOrId)) + .required(false) + .help("Name or ID of the project"), + ) + .arg( + clap::Arg::new("vpc") + .long("vpc") + .value_parser(clap::value_parser!(types::NameOrId)) + .required(true) + .help("Name or ID of the VPC"), + ) + .arg( + clap::Arg::new("json-body") + .long("json-body") + .value_name("JSON-FILE") + .required(false) + .value_parser(clap::value_parser!(std::path::PathBuf)) + .help("Path to a file that contains the full json body."), + ) + .arg( + clap::Arg::new("json-body-template") + .long("json-body-template") + .action(clap::ArgAction::SetTrue) + .help("XXX"), + ) + .about("Update a VPC") + } + + pub fn cli_vpc_delete() -> clap::Command { + clap::Command::new("") + .arg( + clap::Arg::new("project") + .long("project") + .value_parser(clap::value_parser!(types::NameOrId)) + .required(false) + .help("Name or ID of the project"), + ) + .arg( + clap::Arg::new("vpc") + .long("vpc") + .value_parser(clap::value_parser!(types::NameOrId)) + .required(true) + .help("Name or ID of the VPC"), + ) + .about("Delete VPC") + } + + pub async fn execute(&self, cmd: CliCommand, matches: &clap::ArgMatches) -> anyhow::Result<()> { + match cmd { + CliCommand::DeviceAuthRequest => self.execute_device_auth_request(matches).await, + CliCommand::DeviceAuthConfirm => self.execute_device_auth_confirm(matches).await, + CliCommand::DeviceAccessToken => self.execute_device_access_token(matches).await, + CliCommand::ProbeList => self.execute_probe_list(matches).await, + CliCommand::ProbeCreate => self.execute_probe_create(matches).await, + CliCommand::ProbeView => self.execute_probe_view(matches).await, + CliCommand::ProbeDelete => self.execute_probe_delete(matches).await, + CliCommand::LoginSaml => self.execute_login_saml(matches).await, + CliCommand::CertificateList => self.execute_certificate_list(matches).await, + CliCommand::CertificateCreate => self.execute_certificate_create(matches).await, + CliCommand::CertificateView => self.execute_certificate_view(matches).await, + CliCommand::CertificateDelete => self.execute_certificate_delete(matches).await, + CliCommand::DiskList => self.execute_disk_list(matches).await, + CliCommand::DiskCreate => self.execute_disk_create(matches).await, CliCommand::DiskView => self.execute_disk_view(matches).await, CliCommand::DiskDelete => self.execute_disk_delete(matches).await, CliCommand::DiskBulkWriteImport => self.execute_disk_bulk_write_import(matches).await, @@ -5648,226 +6126,816 @@ impl Cli { CliCommand::NetworkingSwitchPortList => { self.execute_networking_switch_port_list(matches).await } - CliCommand::NetworkingSwitchPortApplySettings => { - self.execute_networking_switch_port_apply_settings(matches) + CliCommand::NetworkingSwitchPortApplySettings => { + self.execute_networking_switch_port_apply_settings(matches) + .await + } + CliCommand::NetworkingSwitchPortClearSettings => { + self.execute_networking_switch_port_clear_settings(matches) + .await + } + CliCommand::NetworkingSwitchPortStatus => { + self.execute_networking_switch_port_status(matches).await + } + CliCommand::SwitchList => self.execute_switch_list(matches).await, + CliCommand::SwitchView => self.execute_switch_view(matches).await, + CliCommand::SiloIdentityProviderList => { + self.execute_silo_identity_provider_list(matches).await + } + CliCommand::LocalIdpUserCreate => self.execute_local_idp_user_create(matches).await, + CliCommand::LocalIdpUserDelete => self.execute_local_idp_user_delete(matches).await, + CliCommand::LocalIdpUserSetPassword => { + self.execute_local_idp_user_set_password(matches).await + } + CliCommand::SamlIdentityProviderCreate => { + self.execute_saml_identity_provider_create(matches).await + } + CliCommand::SamlIdentityProviderView => { + self.execute_saml_identity_provider_view(matches).await + } + CliCommand::IpPoolList => self.execute_ip_pool_list(matches).await, + CliCommand::IpPoolCreate => self.execute_ip_pool_create(matches).await, + CliCommand::IpPoolView => self.execute_ip_pool_view(matches).await, + CliCommand::IpPoolUpdate => self.execute_ip_pool_update(matches).await, + CliCommand::IpPoolDelete => self.execute_ip_pool_delete(matches).await, + CliCommand::IpPoolRangeList => self.execute_ip_pool_range_list(matches).await, + CliCommand::IpPoolRangeAdd => self.execute_ip_pool_range_add(matches).await, + CliCommand::IpPoolRangeRemove => self.execute_ip_pool_range_remove(matches).await, + CliCommand::IpPoolSiloList => self.execute_ip_pool_silo_list(matches).await, + CliCommand::IpPoolSiloLink => self.execute_ip_pool_silo_link(matches).await, + CliCommand::IpPoolSiloUpdate => self.execute_ip_pool_silo_update(matches).await, + CliCommand::IpPoolSiloUnlink => self.execute_ip_pool_silo_unlink(matches).await, + CliCommand::IpPoolUtilizationView => { + self.execute_ip_pool_utilization_view(matches).await + } + CliCommand::IpPoolServiceView => self.execute_ip_pool_service_view(matches).await, + CliCommand::IpPoolServiceRangeList => { + self.execute_ip_pool_service_range_list(matches).await + } + CliCommand::IpPoolServiceRangeAdd => { + self.execute_ip_pool_service_range_add(matches).await + } + CliCommand::IpPoolServiceRangeRemove => { + self.execute_ip_pool_service_range_remove(matches).await + } + CliCommand::SystemMetric => self.execute_system_metric(matches).await, + CliCommand::NetworkingAddressLotList => { + self.execute_networking_address_lot_list(matches).await + } + CliCommand::NetworkingAddressLotCreate => { + self.execute_networking_address_lot_create(matches).await + } + CliCommand::NetworkingAddressLotDelete => { + self.execute_networking_address_lot_delete(matches).await + } + CliCommand::NetworkingAddressLotBlockList => { + self.execute_networking_address_lot_block_list(matches) + .await + } + CliCommand::NetworkingAllowListView => { + self.execute_networking_allow_list_view(matches).await + } + CliCommand::NetworkingAllowListUpdate => { + self.execute_networking_allow_list_update(matches).await + } + CliCommand::NetworkingBfdDisable => self.execute_networking_bfd_disable(matches).await, + CliCommand::NetworkingBfdEnable => self.execute_networking_bfd_enable(matches).await, + CliCommand::NetworkingBfdStatus => self.execute_networking_bfd_status(matches).await, + CliCommand::NetworkingBgpConfigList => { + self.execute_networking_bgp_config_list(matches).await + } + CliCommand::NetworkingBgpConfigCreate => { + self.execute_networking_bgp_config_create(matches).await + } + CliCommand::NetworkingBgpConfigDelete => { + self.execute_networking_bgp_config_delete(matches).await + } + CliCommand::NetworkingBgpAnnounceSetList => { + self.execute_networking_bgp_announce_set_list(matches).await + } + CliCommand::NetworkingBgpAnnounceSetUpdate => { + self.execute_networking_bgp_announce_set_update(matches) + .await + } + CliCommand::NetworkingBgpAnnounceSetDelete => { + self.execute_networking_bgp_announce_set_delete(matches) + .await + } + CliCommand::NetworkingBgpMessageHistory => { + self.execute_networking_bgp_message_history(matches).await + } + CliCommand::NetworkingBgpImportedRoutesIpv4 => { + self.execute_networking_bgp_imported_routes_ipv4(matches) + .await + } + CliCommand::NetworkingBgpStatus => self.execute_networking_bgp_status(matches).await, + CliCommand::NetworkingLoopbackAddressList => { + self.execute_networking_loopback_address_list(matches).await + } + CliCommand::NetworkingLoopbackAddressCreate => { + self.execute_networking_loopback_address_create(matches) + .await + } + CliCommand::NetworkingLoopbackAddressDelete => { + self.execute_networking_loopback_address_delete(matches) + .await + } + CliCommand::NetworkingSwitchPortSettingsList => { + self.execute_networking_switch_port_settings_list(matches) + .await + } + CliCommand::NetworkingSwitchPortSettingsCreate => { + self.execute_networking_switch_port_settings_create(matches) + .await + } + CliCommand::NetworkingSwitchPortSettingsDelete => { + self.execute_networking_switch_port_settings_delete(matches) + .await + } + CliCommand::NetworkingSwitchPortSettingsView => { + self.execute_networking_switch_port_settings_view(matches) + .await + } + CliCommand::SystemPolicyView => self.execute_system_policy_view(matches).await, + CliCommand::SystemPolicyUpdate => self.execute_system_policy_update(matches).await, + CliCommand::RoleList => self.execute_role_list(matches).await, + CliCommand::RoleView => self.execute_role_view(matches).await, + CliCommand::SystemQuotasList => self.execute_system_quotas_list(matches).await, + CliCommand::SiloList => self.execute_silo_list(matches).await, + CliCommand::SiloCreate => self.execute_silo_create(matches).await, + CliCommand::SiloView => self.execute_silo_view(matches).await, + CliCommand::SiloDelete => self.execute_silo_delete(matches).await, + CliCommand::SiloIpPoolList => self.execute_silo_ip_pool_list(matches).await, + CliCommand::SiloPolicyView => self.execute_silo_policy_view(matches).await, + CliCommand::SiloPolicyUpdate => self.execute_silo_policy_update(matches).await, + CliCommand::SiloQuotasView => self.execute_silo_quotas_view(matches).await, + CliCommand::SiloQuotasUpdate => self.execute_silo_quotas_update(matches).await, + CliCommand::SiloUserList => self.execute_silo_user_list(matches).await, + CliCommand::SiloUserView => self.execute_silo_user_view(matches).await, + CliCommand::UserBuiltinList => self.execute_user_builtin_list(matches).await, + CliCommand::UserBuiltinView => self.execute_user_builtin_view(matches).await, + CliCommand::SiloUtilizationList => self.execute_silo_utilization_list(matches).await, + CliCommand::SiloUtilizationView => self.execute_silo_utilization_view(matches).await, + CliCommand::TimeseriesQuery => self.execute_timeseries_query(matches).await, + CliCommand::TimeseriesSchemaList => self.execute_timeseries_schema_list(matches).await, + CliCommand::UserList => self.execute_user_list(matches).await, + CliCommand::UtilizationView => self.execute_utilization_view(matches).await, + CliCommand::VpcFirewallRulesView => self.execute_vpc_firewall_rules_view(matches).await, + CliCommand::VpcFirewallRulesUpdate => { + self.execute_vpc_firewall_rules_update(matches).await + } + CliCommand::VpcRouterRouteList => self.execute_vpc_router_route_list(matches).await, + CliCommand::VpcRouterRouteCreate => self.execute_vpc_router_route_create(matches).await, + CliCommand::VpcRouterRouteView => self.execute_vpc_router_route_view(matches).await, + CliCommand::VpcRouterRouteUpdate => self.execute_vpc_router_route_update(matches).await, + CliCommand::VpcRouterRouteDelete => self.execute_vpc_router_route_delete(matches).await, + CliCommand::VpcRouterList => self.execute_vpc_router_list(matches).await, + CliCommand::VpcRouterCreate => self.execute_vpc_router_create(matches).await, + CliCommand::VpcRouterView => self.execute_vpc_router_view(matches).await, + CliCommand::VpcRouterUpdate => self.execute_vpc_router_update(matches).await, + CliCommand::VpcRouterDelete => self.execute_vpc_router_delete(matches).await, + CliCommand::VpcSubnetList => self.execute_vpc_subnet_list(matches).await, + CliCommand::VpcSubnetCreate => self.execute_vpc_subnet_create(matches).await, + CliCommand::VpcSubnetView => self.execute_vpc_subnet_view(matches).await, + CliCommand::VpcSubnetUpdate => self.execute_vpc_subnet_update(matches).await, + CliCommand::VpcSubnetDelete => self.execute_vpc_subnet_delete(matches).await, + CliCommand::VpcSubnetListNetworkInterfaces => { + self.execute_vpc_subnet_list_network_interfaces(matches) .await } - CliCommand::NetworkingSwitchPortClearSettings => { - self.execute_networking_switch_port_clear_settings(matches) - .await + CliCommand::VpcList => self.execute_vpc_list(matches).await, + CliCommand::VpcCreate => self.execute_vpc_create(matches).await, + CliCommand::VpcView => self.execute_vpc_view(matches).await, + CliCommand::VpcUpdate => self.execute_vpc_update(matches).await, + CliCommand::VpcDelete => self.execute_vpc_delete(matches).await, + } + } + + pub async fn execute_device_auth_request( + &self, + matches: &clap::ArgMatches, + ) -> anyhow::Result<()> { + let mut request = self.client.device_auth_request(); + if let Some(value) = matches.get_one::("client-id") { + request = request.body_map(|body| body.client_id(value.clone())) + } + + if let Some(value) = matches.get_one::("json-body") { + let body_txt = std::fs::read_to_string(value).unwrap(); + let body_value = serde_json::from_str::(&body_txt).unwrap(); + request = request.body(body_value); + } + + self.config + .execute_device_auth_request(matches, &mut request)?; + let result = request.send().await; + match result { + Ok(r) => { + todo!() } - CliCommand::NetworkingSwitchPortStatus => { - self.execute_networking_switch_port_status(matches).await + Err(r) => { + todo!() } - CliCommand::SwitchList => self.execute_switch_list(matches).await, - CliCommand::SwitchView => self.execute_switch_view(matches).await, - CliCommand::SiloIdentityProviderList => { - self.execute_silo_identity_provider_list(matches).await + } + } + + pub async fn execute_device_auth_confirm( + &self, + matches: &clap::ArgMatches, + ) -> anyhow::Result<()> { + let mut request = self.client.device_auth_confirm(); + if let Some(value) = matches.get_one::("user-code") { + request = request.body_map(|body| body.user_code(value.clone())) + } + + if let Some(value) = matches.get_one::("json-body") { + let body_txt = std::fs::read_to_string(value).unwrap(); + let body_value = serde_json::from_str::(&body_txt).unwrap(); + request = request.body(body_value); + } + + self.config + .execute_device_auth_confirm(matches, &mut request)?; + let result = request.send().await; + match result { + Ok(r) => { + self.config.success_no_item(&r); + Ok(()) } - CliCommand::LocalIdpUserCreate => self.execute_local_idp_user_create(matches).await, - CliCommand::LocalIdpUserDelete => self.execute_local_idp_user_delete(matches).await, - CliCommand::LocalIdpUserSetPassword => { - self.execute_local_idp_user_set_password(matches).await + Err(r) => { + self.config.error(&r); + Err(anyhow::Error::new(r)) } - CliCommand::SamlIdentityProviderCreate => { - self.execute_saml_identity_provider_create(matches).await + } + } + + pub async fn execute_device_access_token( + &self, + matches: &clap::ArgMatches, + ) -> anyhow::Result<()> { + let mut request = self.client.device_access_token(); + if let Some(value) = matches.get_one::("client-id") { + request = request.body_map(|body| body.client_id(value.clone())) + } + + if let Some(value) = matches.get_one::("device-code") { + request = request.body_map(|body| body.device_code(value.clone())) + } + + if let Some(value) = matches.get_one::("grant-type") { + request = request.body_map(|body| body.grant_type(value.clone())) + } + + if let Some(value) = matches.get_one::("json-body") { + let body_txt = std::fs::read_to_string(value).unwrap(); + let body_value = + serde_json::from_str::(&body_txt).unwrap(); + request = request.body(body_value); + } + + self.config + .execute_device_access_token(matches, &mut request)?; + let result = request.send().await; + match result { + Ok(r) => { + todo!() } - CliCommand::SamlIdentityProviderView => { - self.execute_saml_identity_provider_view(matches).await + Err(r) => { + todo!() } - CliCommand::IpPoolList => self.execute_ip_pool_list(matches).await, - CliCommand::IpPoolCreate => self.execute_ip_pool_create(matches).await, - CliCommand::IpPoolView => self.execute_ip_pool_view(matches).await, - CliCommand::IpPoolUpdate => self.execute_ip_pool_update(matches).await, - CliCommand::IpPoolDelete => self.execute_ip_pool_delete(matches).await, - CliCommand::IpPoolRangeList => self.execute_ip_pool_range_list(matches).await, - CliCommand::IpPoolRangeAdd => self.execute_ip_pool_range_add(matches).await, - CliCommand::IpPoolRangeRemove => self.execute_ip_pool_range_remove(matches).await, - CliCommand::IpPoolSiloList => self.execute_ip_pool_silo_list(matches).await, - CliCommand::IpPoolSiloLink => self.execute_ip_pool_silo_link(matches).await, - CliCommand::IpPoolSiloUpdate => self.execute_ip_pool_silo_update(matches).await, - CliCommand::IpPoolSiloUnlink => self.execute_ip_pool_silo_unlink(matches).await, - CliCommand::IpPoolUtilizationView => { - self.execute_ip_pool_utilization_view(matches).await + } + } + + pub async fn execute_probe_list(&self, matches: &clap::ArgMatches) -> anyhow::Result<()> { + let mut request = self.client.probe_list(); + if let Some(value) = matches.get_one::("limit") { + request = request.limit(value.clone()); + } + + if let Some(value) = matches.get_one::("project") { + request = request.project(value.clone()); + } + + if let Some(value) = matches.get_one::("sort-by") { + request = request.sort_by(value.clone()); + } + + self.config.execute_probe_list(matches, &mut request)?; + self.config.list_start::(); + let mut stream = futures::StreamExt::take( + request.stream(), + matches + .get_one::("limit") + .map_or(usize::MAX, |x| x.get() as usize), + ); + loop { + match futures::TryStreamExt::try_next(&mut stream).await { + Err(r) => { + self.config.list_end_error(&r); + return Err(anyhow::Error::new(r)); + } + Ok(None) => { + self.config + .list_end_success::(); + return Ok(()); + } + Ok(Some(value)) => { + self.config.list_item(&value); + } } - CliCommand::IpPoolServiceView => self.execute_ip_pool_service_view(matches).await, - CliCommand::IpPoolServiceRangeList => { - self.execute_ip_pool_service_range_list(matches).await + } + } + + pub async fn execute_probe_create(&self, matches: &clap::ArgMatches) -> anyhow::Result<()> { + let mut request = self.client.probe_create(); + if let Some(value) = matches.get_one::("description") { + request = request.body_map(|body| body.description(value.clone())) + } + + if let Some(value) = matches.get_one::("ip-pool") { + request = request.body_map(|body| body.ip_pool(value.clone())) + } + + if let Some(value) = matches.get_one::("name") { + request = request.body_map(|body| body.name(value.clone())) + } + + if let Some(value) = matches.get_one::("project") { + request = request.project(value.clone()); + } + + if let Some(value) = matches.get_one::("sled") { + request = request.body_map(|body| body.sled(value.clone())) + } + + if let Some(value) = matches.get_one::("json-body") { + let body_txt = std::fs::read_to_string(value).unwrap(); + let body_value = serde_json::from_str::(&body_txt).unwrap(); + request = request.body(body_value); + } + + self.config.execute_probe_create(matches, &mut request)?; + let result = request.send().await; + match result { + Ok(r) => { + self.config.success_item(&r); + Ok(()) } - CliCommand::IpPoolServiceRangeAdd => { - self.execute_ip_pool_service_range_add(matches).await + Err(r) => { + self.config.error(&r); + Err(anyhow::Error::new(r)) } - CliCommand::IpPoolServiceRangeRemove => { - self.execute_ip_pool_service_range_remove(matches).await + } + } + + pub async fn execute_probe_view(&self, matches: &clap::ArgMatches) -> anyhow::Result<()> { + let mut request = self.client.probe_view(); + if let Some(value) = matches.get_one::("probe") { + request = request.probe(value.clone()); + } + + if let Some(value) = matches.get_one::("project") { + request = request.project(value.clone()); + } + + self.config.execute_probe_view(matches, &mut request)?; + let result = request.send().await; + match result { + Ok(r) => { + self.config.success_item(&r); + Ok(()) } - CliCommand::SystemMetric => self.execute_system_metric(matches).await, - CliCommand::NetworkingAddressLotList => { - self.execute_networking_address_lot_list(matches).await + Err(r) => { + self.config.error(&r); + Err(anyhow::Error::new(r)) } - CliCommand::NetworkingAddressLotCreate => { - self.execute_networking_address_lot_create(matches).await + } + } + + pub async fn execute_probe_delete(&self, matches: &clap::ArgMatches) -> anyhow::Result<()> { + let mut request = self.client.probe_delete(); + if let Some(value) = matches.get_one::("probe") { + request = request.probe(value.clone()); + } + + if let Some(value) = matches.get_one::("project") { + request = request.project(value.clone()); + } + + self.config.execute_probe_delete(matches, &mut request)?; + let result = request.send().await; + match result { + Ok(r) => { + self.config.success_no_item(&r); + Ok(()) } - CliCommand::NetworkingAddressLotDelete => { - self.execute_networking_address_lot_delete(matches).await + Err(r) => { + self.config.error(&r); + Err(anyhow::Error::new(r)) } - CliCommand::NetworkingAddressLotBlockList => { - self.execute_networking_address_lot_block_list(matches) - .await + } + } + + pub async fn execute_login_saml(&self, matches: &clap::ArgMatches) -> anyhow::Result<()> { + let mut request = self.client.login_saml(); + if let Some(value) = matches.get_one::("provider-name") { + request = request.provider_name(value.clone()); + } + + if let Some(value) = matches.get_one::("silo-name") { + request = request.silo_name(value.clone()); + } + + self.config.execute_login_saml(matches, &mut request)?; + let result = request.send().await; + match result { + Ok(r) => { + todo!() } - CliCommand::NetworkingAllowListView => { - self.execute_networking_allow_list_view(matches).await + Err(r) => { + self.config.error(&r); + Err(anyhow::Error::new(r)) } - CliCommand::NetworkingAllowListUpdate => { - self.execute_networking_allow_list_update(matches).await + } + } + + pub async fn execute_certificate_list(&self, matches: &clap::ArgMatches) -> anyhow::Result<()> { + let mut request = self.client.certificate_list(); + if let Some(value) = matches.get_one::("limit") { + request = request.limit(value.clone()); + } + + if let Some(value) = matches.get_one::("sort-by") { + request = request.sort_by(value.clone()); + } + + self.config + .execute_certificate_list(matches, &mut request)?; + self.config.list_start::(); + let mut stream = futures::StreamExt::take( + request.stream(), + matches + .get_one::("limit") + .map_or(usize::MAX, |x| x.get() as usize), + ); + loop { + match futures::TryStreamExt::try_next(&mut stream).await { + Err(r) => { + self.config.list_end_error(&r); + return Err(anyhow::Error::new(r)); + } + Ok(None) => { + self.config + .list_end_success::(); + return Ok(()); + } + Ok(Some(value)) => { + self.config.list_item(&value); + } } - CliCommand::NetworkingBfdDisable => self.execute_networking_bfd_disable(matches).await, - CliCommand::NetworkingBfdEnable => self.execute_networking_bfd_enable(matches).await, - CliCommand::NetworkingBfdStatus => self.execute_networking_bfd_status(matches).await, - CliCommand::NetworkingBgpConfigList => { - self.execute_networking_bgp_config_list(matches).await + } + } + + pub async fn execute_certificate_create( + &self, + matches: &clap::ArgMatches, + ) -> anyhow::Result<()> { + let mut request = self.client.certificate_create(); + if let Some(value) = matches.get_one::("cert") { + request = request.body_map(|body| body.cert(value.clone())) + } + + if let Some(value) = matches.get_one::("description") { + request = request.body_map(|body| body.description(value.clone())) + } + + if let Some(value) = matches.get_one::("key") { + request = request.body_map(|body| body.key(value.clone())) + } + + if let Some(value) = matches.get_one::("name") { + request = request.body_map(|body| body.name(value.clone())) + } + + if let Some(value) = matches.get_one::("service") { + request = request.body_map(|body| body.service(value.clone())) + } + + if let Some(value) = matches.get_one::("json-body") { + let body_txt = std::fs::read_to_string(value).unwrap(); + let body_value = serde_json::from_str::(&body_txt).unwrap(); + request = request.body(body_value); + } + + self.config + .execute_certificate_create(matches, &mut request)?; + let result = request.send().await; + match result { + Ok(r) => { + self.config.success_item(&r); + Ok(()) } - CliCommand::NetworkingBgpConfigCreate => { - self.execute_networking_bgp_config_create(matches).await + Err(r) => { + self.config.error(&r); + Err(anyhow::Error::new(r)) } - CliCommand::NetworkingBgpConfigDelete => { - self.execute_networking_bgp_config_delete(matches).await + } + } + + pub async fn execute_certificate_view(&self, matches: &clap::ArgMatches) -> anyhow::Result<()> { + let mut request = self.client.certificate_view(); + if let Some(value) = matches.get_one::("certificate") { + request = request.certificate(value.clone()); + } + + self.config + .execute_certificate_view(matches, &mut request)?; + let result = request.send().await; + match result { + Ok(r) => { + self.config.success_item(&r); + Ok(()) } - CliCommand::NetworkingBgpAnnounceSetList => { - self.execute_networking_bgp_announce_set_list(matches).await + Err(r) => { + self.config.error(&r); + Err(anyhow::Error::new(r)) } - CliCommand::NetworkingBgpAnnounceSetCreate => { - self.execute_networking_bgp_announce_set_create(matches) - .await + } + } + + pub async fn execute_certificate_delete( + &self, + matches: &clap::ArgMatches, + ) -> anyhow::Result<()> { + let mut request = self.client.certificate_delete(); + if let Some(value) = matches.get_one::("certificate") { + request = request.certificate(value.clone()); + } + + self.config + .execute_certificate_delete(matches, &mut request)?; + let result = request.send().await; + match result { + Ok(r) => { + self.config.success_no_item(&r); + Ok(()) } - CliCommand::NetworkingBgpAnnounceSetDelete => { - self.execute_networking_bgp_announce_set_delete(matches) - .await + Err(r) => { + self.config.error(&r); + Err(anyhow::Error::new(r)) } - CliCommand::NetworkingBgpMessageHistory => { - self.execute_networking_bgp_message_history(matches).await + } + } + + pub async fn execute_disk_list(&self, matches: &clap::ArgMatches) -> anyhow::Result<()> { + let mut request = self.client.disk_list(); + if let Some(value) = matches.get_one::("limit") { + request = request.limit(value.clone()); + } + + if let Some(value) = matches.get_one::("project") { + request = request.project(value.clone()); + } + + if let Some(value) = matches.get_one::("sort-by") { + request = request.sort_by(value.clone()); + } + + self.config.execute_disk_list(matches, &mut request)?; + self.config.list_start::(); + let mut stream = futures::StreamExt::take( + request.stream(), + matches + .get_one::("limit") + .map_or(usize::MAX, |x| x.get() as usize), + ); + loop { + match futures::TryStreamExt::try_next(&mut stream).await { + Err(r) => { + self.config.list_end_error(&r); + return Err(anyhow::Error::new(r)); + } + Ok(None) => { + self.config.list_end_success::(); + return Ok(()); + } + Ok(Some(value)) => { + self.config.list_item(&value); + } } - CliCommand::NetworkingBgpImportedRoutesIpv4 => { - self.execute_networking_bgp_imported_routes_ipv4(matches) - .await + } + } + + pub async fn execute_disk_create(&self, matches: &clap::ArgMatches) -> anyhow::Result<()> { + let mut request = self.client.disk_create(); + if let Some(value) = matches.get_one::("description") { + request = request.body_map(|body| body.description(value.clone())) + } + + if let Some(value) = matches.get_one::("name") { + request = request.body_map(|body| body.name(value.clone())) + } + + if let Some(value) = matches.get_one::("project") { + request = request.project(value.clone()); + } + + if let Some(value) = matches.get_one::("size") { + request = request.body_map(|body| body.size(value.clone())) + } + + if let Some(value) = matches.get_one::("json-body") { + let body_txt = std::fs::read_to_string(value).unwrap(); + let body_value = serde_json::from_str::(&body_txt).unwrap(); + request = request.body(body_value); + } + + self.config.execute_disk_create(matches, &mut request)?; + let result = request.send().await; + match result { + Ok(r) => { + self.config.success_item(&r); + Ok(()) } - CliCommand::NetworkingBgpStatus => self.execute_networking_bgp_status(matches).await, - CliCommand::NetworkingLoopbackAddressList => { - self.execute_networking_loopback_address_list(matches).await + Err(r) => { + self.config.error(&r); + Err(anyhow::Error::new(r)) } - CliCommand::NetworkingLoopbackAddressCreate => { - self.execute_networking_loopback_address_create(matches) - .await + } + } + + pub async fn execute_disk_view(&self, matches: &clap::ArgMatches) -> anyhow::Result<()> { + let mut request = self.client.disk_view(); + if let Some(value) = matches.get_one::("disk") { + request = request.disk(value.clone()); + } + + if let Some(value) = matches.get_one::("project") { + request = request.project(value.clone()); + } + + self.config.execute_disk_view(matches, &mut request)?; + let result = request.send().await; + match result { + Ok(r) => { + self.config.success_item(&r); + Ok(()) } - CliCommand::NetworkingLoopbackAddressDelete => { - self.execute_networking_loopback_address_delete(matches) - .await + Err(r) => { + self.config.error(&r); + Err(anyhow::Error::new(r)) } - CliCommand::NetworkingSwitchPortSettingsList => { - self.execute_networking_switch_port_settings_list(matches) - .await + } + } + + pub async fn execute_disk_delete(&self, matches: &clap::ArgMatches) -> anyhow::Result<()> { + let mut request = self.client.disk_delete(); + if let Some(value) = matches.get_one::("disk") { + request = request.disk(value.clone()); + } + + if let Some(value) = matches.get_one::("project") { + request = request.project(value.clone()); + } + + self.config.execute_disk_delete(matches, &mut request)?; + let result = request.send().await; + match result { + Ok(r) => { + self.config.success_no_item(&r); + Ok(()) } - CliCommand::NetworkingSwitchPortSettingsCreate => { - self.execute_networking_switch_port_settings_create(matches) - .await + Err(r) => { + self.config.error(&r); + Err(anyhow::Error::new(r)) } - CliCommand::NetworkingSwitchPortSettingsDelete => { - self.execute_networking_switch_port_settings_delete(matches) - .await + } + } + + pub async fn execute_disk_bulk_write_import( + &self, + matches: &clap::ArgMatches, + ) -> anyhow::Result<()> { + let mut request = self.client.disk_bulk_write_import(); + if let Some(value) = matches.get_one::("base64-encoded-data") { + request = request.body_map(|body| body.base64_encoded_data(value.clone())) + } + + if let Some(value) = matches.get_one::("disk") { + request = request.disk(value.clone()); + } + + if let Some(value) = matches.get_one::("offset") { + request = request.body_map(|body| body.offset(value.clone())) + } + + if let Some(value) = matches.get_one::("project") { + request = request.project(value.clone()); + } + + if let Some(value) = matches.get_one::("json-body") { + let body_txt = std::fs::read_to_string(value).unwrap(); + let body_value = + serde_json::from_str::(&body_txt).unwrap(); + request = request.body(body_value); + } + + self.config + .execute_disk_bulk_write_import(matches, &mut request)?; + let result = request.send().await; + match result { + Ok(r) => { + self.config.success_no_item(&r); + Ok(()) } - CliCommand::NetworkingSwitchPortSettingsView => { - self.execute_networking_switch_port_settings_view(matches) - .await + Err(r) => { + self.config.error(&r); + Err(anyhow::Error::new(r)) } - CliCommand::SystemPolicyView => self.execute_system_policy_view(matches).await, - CliCommand::SystemPolicyUpdate => self.execute_system_policy_update(matches).await, - CliCommand::RoleList => self.execute_role_list(matches).await, - CliCommand::RoleView => self.execute_role_view(matches).await, - CliCommand::SystemQuotasList => self.execute_system_quotas_list(matches).await, - CliCommand::SiloList => self.execute_silo_list(matches).await, - CliCommand::SiloCreate => self.execute_silo_create(matches).await, - CliCommand::SiloView => self.execute_silo_view(matches).await, - CliCommand::SiloDelete => self.execute_silo_delete(matches).await, - CliCommand::SiloIpPoolList => self.execute_silo_ip_pool_list(matches).await, - CliCommand::SiloPolicyView => self.execute_silo_policy_view(matches).await, - CliCommand::SiloPolicyUpdate => self.execute_silo_policy_update(matches).await, - CliCommand::SiloQuotasView => self.execute_silo_quotas_view(matches).await, - CliCommand::SiloQuotasUpdate => self.execute_silo_quotas_update(matches).await, - CliCommand::SiloUserList => self.execute_silo_user_list(matches).await, - CliCommand::SiloUserView => self.execute_silo_user_view(matches).await, - CliCommand::UserBuiltinList => self.execute_user_builtin_list(matches).await, - CliCommand::UserBuiltinView => self.execute_user_builtin_view(matches).await, - CliCommand::SiloUtilizationList => self.execute_silo_utilization_list(matches).await, - CliCommand::SiloUtilizationView => self.execute_silo_utilization_view(matches).await, - CliCommand::TimeseriesQuery => self.execute_timeseries_query(matches).await, - CliCommand::TimeseriesSchemaList => self.execute_timeseries_schema_list(matches).await, - CliCommand::UserList => self.execute_user_list(matches).await, - CliCommand::UtilizationView => self.execute_utilization_view(matches).await, - CliCommand::VpcFirewallRulesView => self.execute_vpc_firewall_rules_view(matches).await, - CliCommand::VpcFirewallRulesUpdate => { - self.execute_vpc_firewall_rules_update(matches).await + } + } + + pub async fn execute_disk_bulk_write_import_start( + &self, + matches: &clap::ArgMatches, + ) -> anyhow::Result<()> { + let mut request = self.client.disk_bulk_write_import_start(); + if let Some(value) = matches.get_one::("disk") { + request = request.disk(value.clone()); + } + + if let Some(value) = matches.get_one::("project") { + request = request.project(value.clone()); + } + + self.config + .execute_disk_bulk_write_import_start(matches, &mut request)?; + let result = request.send().await; + match result { + Ok(r) => { + self.config.success_no_item(&r); + Ok(()) } - CliCommand::VpcSubnetList => self.execute_vpc_subnet_list(matches).await, - CliCommand::VpcSubnetCreate => self.execute_vpc_subnet_create(matches).await, - CliCommand::VpcSubnetView => self.execute_vpc_subnet_view(matches).await, - CliCommand::VpcSubnetUpdate => self.execute_vpc_subnet_update(matches).await, - CliCommand::VpcSubnetDelete => self.execute_vpc_subnet_delete(matches).await, - CliCommand::VpcSubnetListNetworkInterfaces => { - self.execute_vpc_subnet_list_network_interfaces(matches) - .await + Err(r) => { + self.config.error(&r); + Err(anyhow::Error::new(r)) } - CliCommand::VpcList => self.execute_vpc_list(matches).await, - CliCommand::VpcCreate => self.execute_vpc_create(matches).await, - CliCommand::VpcView => self.execute_vpc_view(matches).await, - CliCommand::VpcUpdate => self.execute_vpc_update(matches).await, - CliCommand::VpcDelete => self.execute_vpc_delete(matches).await, } } - pub async fn execute_device_auth_request( + pub async fn execute_disk_bulk_write_import_stop( &self, matches: &clap::ArgMatches, ) -> anyhow::Result<()> { - let mut request = self.client.device_auth_request(); - if let Some(value) = matches.get_one::("client-id") { - request = request.body_map(|body| body.client_id(value.clone())) + let mut request = self.client.disk_bulk_write_import_stop(); + if let Some(value) = matches.get_one::("disk") { + request = request.disk(value.clone()); } - if let Some(value) = matches.get_one::("json-body") { - let body_txt = std::fs::read_to_string(value).unwrap(); - let body_value = serde_json::from_str::(&body_txt).unwrap(); - request = request.body(body_value); + if let Some(value) = matches.get_one::("project") { + request = request.project(value.clone()); } self.config - .execute_device_auth_request(matches, &mut request)?; + .execute_disk_bulk_write_import_stop(matches, &mut request)?; let result = request.send().await; match result { Ok(r) => { - todo!() + self.config.success_no_item(&r); + Ok(()) } Err(r) => { - todo!() + self.config.error(&r); + Err(anyhow::Error::new(r)) } } } - pub async fn execute_device_auth_confirm( + pub async fn execute_disk_finalize_import( &self, matches: &clap::ArgMatches, ) -> anyhow::Result<()> { - let mut request = self.client.device_auth_confirm(); - if let Some(value) = matches.get_one::("user-code") { - request = request.body_map(|body| body.user_code(value.clone())) + let mut request = self.client.disk_finalize_import(); + if let Some(value) = matches.get_one::("disk") { + request = request.disk(value.clone()); + } + + if let Some(value) = matches.get_one::("project") { + request = request.project(value.clone()); + } + + if let Some(value) = matches.get_one::("snapshot-name") { + request = request.body_map(|body| body.snapshot_name(value.clone())) } if let Some(value) = matches.get_one::("json-body") { let body_txt = std::fs::read_to_string(value).unwrap(); - let body_value = serde_json::from_str::(&body_txt).unwrap(); + let body_value = serde_json::from_str::(&body_txt).unwrap(); request = request.body(body_value); } self.config - .execute_device_auth_confirm(matches, &mut request)?; + .execute_disk_finalize_import(matches, &mut request)?; let result = request.send().await; match result { Ok(r) => { @@ -5881,45 +6949,69 @@ impl Cli { } } - pub async fn execute_device_access_token( + pub async fn execute_disk_metrics_list( &self, matches: &clap::ArgMatches, ) -> anyhow::Result<()> { - let mut request = self.client.device_access_token(); - if let Some(value) = matches.get_one::("client-id") { - request = request.body_map(|body| body.client_id(value.clone())) + let mut request = self.client.disk_metrics_list(); + if let Some(value) = matches.get_one::("disk") { + request = request.disk(value.clone()); } - if let Some(value) = matches.get_one::("device-code") { - request = request.body_map(|body| body.device_code(value.clone())) + if let Some(value) = matches.get_one::>("end-time") { + request = request.end_time(value.clone()); } - if let Some(value) = matches.get_one::("grant-type") { - request = request.body_map(|body| body.grant_type(value.clone())) + if let Some(value) = matches.get_one::("limit") { + request = request.limit(value.clone()); + } + + if let Some(value) = matches.get_one::("metric") { + request = request.metric(value.clone()); + } + + if let Some(value) = matches.get_one::("order") { + request = request.order(value.clone()); + } + + if let Some(value) = matches.get_one::("project") { + request = request.project(value.clone()); } - if let Some(value) = matches.get_one::("json-body") { - let body_txt = std::fs::read_to_string(value).unwrap(); - let body_value = - serde_json::from_str::(&body_txt).unwrap(); - request = request.body(body_value); + if let Some(value) = matches.get_one::>("start-time") + { + request = request.start_time(value.clone()); } self.config - .execute_device_access_token(matches, &mut request)?; - let result = request.send().await; - match result { - Ok(r) => { - todo!() - } - Err(r) => { - todo!() + .execute_disk_metrics_list(matches, &mut request)?; + self.config.list_start::(); + let mut stream = futures::StreamExt::take( + request.stream(), + matches + .get_one::("limit") + .map_or(usize::MAX, |x| x.get() as usize), + ); + loop { + match futures::TryStreamExt::try_next(&mut stream).await { + Err(r) => { + self.config.list_end_error(&r); + return Err(anyhow::Error::new(r)); + } + Ok(None) => { + self.config + .list_end_success::(); + return Ok(()); + } + Ok(Some(value)) => { + self.config.list_item(&value); + } } } } - pub async fn execute_probe_list(&self, matches: &clap::ArgMatches) -> anyhow::Result<()> { - let mut request = self.client.probe_list(); + pub async fn execute_floating_ip_list(&self, matches: &clap::ArgMatches) -> anyhow::Result<()> { + let mut request = self.client.floating_ip_list(); if let Some(value) = matches.get_one::("limit") { request = request.limit(value.clone()); } @@ -5932,8 +7024,9 @@ impl Cli { request = request.sort_by(value.clone()); } - self.config.execute_probe_list(matches, &mut request)?; - self.config.list_start::(); + self.config + .execute_floating_ip_list(matches, &mut request)?; + self.config.list_start::(); let mut stream = futures::StreamExt::take( request.stream(), matches @@ -5948,7 +7041,7 @@ impl Cli { } Ok(None) => { self.config - .list_end_success::(); + .list_end_success::(); return Ok(()); } Ok(Some(value)) => { @@ -5958,35 +7051,39 @@ impl Cli { } } - pub async fn execute_probe_create(&self, matches: &clap::ArgMatches) -> anyhow::Result<()> { - let mut request = self.client.probe_create(); + pub async fn execute_floating_ip_create( + &self, + matches: &clap::ArgMatches, + ) -> anyhow::Result<()> { + let mut request = self.client.floating_ip_create(); if let Some(value) = matches.get_one::("description") { request = request.body_map(|body| body.description(value.clone())) } - if let Some(value) = matches.get_one::("ip-pool") { - request = request.body_map(|body| body.ip_pool(value.clone())) + if let Some(value) = matches.get_one::("ip") { + request = request.body_map(|body| body.ip(value.clone())) } if let Some(value) = matches.get_one::("name") { request = request.body_map(|body| body.name(value.clone())) } - if let Some(value) = matches.get_one::("project") { - request = request.project(value.clone()); + if let Some(value) = matches.get_one::("pool") { + request = request.body_map(|body| body.pool(value.clone())) } - if let Some(value) = matches.get_one::("sled") { - request = request.body_map(|body| body.sled(value.clone())) + if let Some(value) = matches.get_one::("project") { + request = request.project(value.clone()); } if let Some(value) = matches.get_one::("json-body") { let body_txt = std::fs::read_to_string(value).unwrap(); - let body_value = serde_json::from_str::(&body_txt).unwrap(); + let body_value = serde_json::from_str::(&body_txt).unwrap(); request = request.body(body_value); } - self.config.execute_probe_create(matches, &mut request)?; + self.config + .execute_floating_ip_create(matches, &mut request)?; let result = request.send().await; match result { Ok(r) => { @@ -6000,17 +7097,18 @@ impl Cli { } } - pub async fn execute_probe_view(&self, matches: &clap::ArgMatches) -> anyhow::Result<()> { - let mut request = self.client.probe_view(); - if let Some(value) = matches.get_one::("probe") { - request = request.probe(value.clone()); + pub async fn execute_floating_ip_view(&self, matches: &clap::ArgMatches) -> anyhow::Result<()> { + let mut request = self.client.floating_ip_view(); + if let Some(value) = matches.get_one::("floating-ip") { + request = request.floating_ip(value.clone()); } if let Some(value) = matches.get_one::("project") { request = request.project(value.clone()); } - self.config.execute_probe_view(matches, &mut request)?; + self.config + .execute_floating_ip_view(matches, &mut request)?; let result = request.send().await; match result { Ok(r) => { @@ -6024,21 +7122,39 @@ impl Cli { } } - pub async fn execute_probe_delete(&self, matches: &clap::ArgMatches) -> anyhow::Result<()> { - let mut request = self.client.probe_delete(); - if let Some(value) = matches.get_one::("probe") { - request = request.probe(value.clone()); + pub async fn execute_floating_ip_update( + &self, + matches: &clap::ArgMatches, + ) -> anyhow::Result<()> { + let mut request = self.client.floating_ip_update(); + if let Some(value) = matches.get_one::("description") { + request = request.body_map(|body| body.description(value.clone())) + } + + if let Some(value) = matches.get_one::("floating-ip") { + request = request.floating_ip(value.clone()); + } + + if let Some(value) = matches.get_one::("name") { + request = request.body_map(|body| body.name(value.clone())) } if let Some(value) = matches.get_one::("project") { request = request.project(value.clone()); } - self.config.execute_probe_delete(matches, &mut request)?; + if let Some(value) = matches.get_one::("json-body") { + let body_txt = std::fs::read_to_string(value).unwrap(); + let body_value = serde_json::from_str::(&body_txt).unwrap(); + request = request.body(body_value); + } + + self.config + .execute_floating_ip_update(matches, &mut request)?; let result = request.send().await; match result { Ok(r) => { - self.config.success_no_item(&r); + self.config.success_item(&r); Ok(()) } Err(r) => { @@ -6048,21 +7164,26 @@ impl Cli { } } - pub async fn execute_login_saml(&self, matches: &clap::ArgMatches) -> anyhow::Result<()> { - let mut request = self.client.login_saml(); - if let Some(value) = matches.get_one::("provider-name") { - request = request.provider_name(value.clone()); + pub async fn execute_floating_ip_delete( + &self, + matches: &clap::ArgMatches, + ) -> anyhow::Result<()> { + let mut request = self.client.floating_ip_delete(); + if let Some(value) = matches.get_one::("floating-ip") { + request = request.floating_ip(value.clone()); } - if let Some(value) = matches.get_one::("silo-name") { - request = request.silo_name(value.clone()); + if let Some(value) = matches.get_one::("project") { + request = request.project(value.clone()); } - self.config.execute_login_saml(matches, &mut request)?; + self.config + .execute_floating_ip_delete(matches, &mut request)?; let result = request.send().await; match result { Ok(r) => { - todo!() + self.config.success_no_item(&r); + Ok(()) } Err(r) => { self.config.error(&r); @@ -6071,76 +7192,35 @@ impl Cli { } } - pub async fn execute_certificate_list(&self, matches: &clap::ArgMatches) -> anyhow::Result<()> { - let mut request = self.client.certificate_list(); - if let Some(value) = matches.get_one::("limit") { - request = request.limit(value.clone()); - } - - if let Some(value) = matches.get_one::("sort-by") { - request = request.sort_by(value.clone()); - } - - self.config - .execute_certificate_list(matches, &mut request)?; - self.config.list_start::(); - let mut stream = futures::StreamExt::take( - request.stream(), - matches - .get_one::("limit") - .map_or(usize::MAX, |x| x.get() as usize), - ); - loop { - match futures::TryStreamExt::try_next(&mut stream).await { - Err(r) => { - self.config.list_end_error(&r); - return Err(anyhow::Error::new(r)); - } - Ok(None) => { - self.config - .list_end_success::(); - return Ok(()); - } - Ok(Some(value)) => { - self.config.list_item(&value); - } - } - } - } - - pub async fn execute_certificate_create( + pub async fn execute_floating_ip_attach( &self, matches: &clap::ArgMatches, ) -> anyhow::Result<()> { - let mut request = self.client.certificate_create(); - if let Some(value) = matches.get_one::("cert") { - request = request.body_map(|body| body.cert(value.clone())) - } - - if let Some(value) = matches.get_one::("description") { - request = request.body_map(|body| body.description(value.clone())) + let mut request = self.client.floating_ip_attach(); + if let Some(value) = matches.get_one::("floating-ip") { + request = request.floating_ip(value.clone()); } - if let Some(value) = matches.get_one::("key") { - request = request.body_map(|body| body.key(value.clone())) + if let Some(value) = matches.get_one::("kind") { + request = request.body_map(|body| body.kind(value.clone())) } - if let Some(value) = matches.get_one::("name") { - request = request.body_map(|body| body.name(value.clone())) + if let Some(value) = matches.get_one::("parent") { + request = request.body_map(|body| body.parent(value.clone())) } - if let Some(value) = matches.get_one::("service") { - request = request.body_map(|body| body.service(value.clone())) + if let Some(value) = matches.get_one::("project") { + request = request.project(value.clone()); } if let Some(value) = matches.get_one::("json-body") { let body_txt = std::fs::read_to_string(value).unwrap(); - let body_value = serde_json::from_str::(&body_txt).unwrap(); + let body_value = serde_json::from_str::(&body_txt).unwrap(); request = request.body(body_value); } self.config - .execute_certificate_create(matches, &mut request)?; + .execute_floating_ip_attach(matches, &mut request)?; let result = request.send().await; match result { Ok(r) => { @@ -6154,14 +7234,21 @@ impl Cli { } } - pub async fn execute_certificate_view(&self, matches: &clap::ArgMatches) -> anyhow::Result<()> { - let mut request = self.client.certificate_view(); - if let Some(value) = matches.get_one::("certificate") { - request = request.certificate(value.clone()); + pub async fn execute_floating_ip_detach( + &self, + matches: &clap::ArgMatches, + ) -> anyhow::Result<()> { + let mut request = self.client.floating_ip_detach(); + if let Some(value) = matches.get_one::("floating-ip") { + request = request.floating_ip(value.clone()); + } + + if let Some(value) = matches.get_one::("project") { + request = request.project(value.clone()); } self.config - .execute_certificate_view(matches, &mut request)?; + .execute_floating_ip_detach(matches, &mut request)?; let result = request.send().await; match result { Ok(r) => { @@ -6175,21 +7262,52 @@ impl Cli { } } - pub async fn execute_certificate_delete( - &self, - matches: &clap::ArgMatches, - ) -> anyhow::Result<()> { - let mut request = self.client.certificate_delete(); - if let Some(value) = matches.get_one::("certificate") { - request = request.certificate(value.clone()); + pub async fn execute_group_list(&self, matches: &clap::ArgMatches) -> anyhow::Result<()> { + let mut request = self.client.group_list(); + if let Some(value) = matches.get_one::("limit") { + request = request.limit(value.clone()); + } + + if let Some(value) = matches.get_one::("sort-by") { + request = request.sort_by(value.clone()); + } + + self.config.execute_group_list(matches, &mut request)?; + self.config.list_start::(); + let mut stream = futures::StreamExt::take( + request.stream(), + matches + .get_one::("limit") + .map_or(usize::MAX, |x| x.get() as usize), + ); + loop { + match futures::TryStreamExt::try_next(&mut stream).await { + Err(r) => { + self.config.list_end_error(&r); + return Err(anyhow::Error::new(r)); + } + Ok(None) => { + self.config.list_end_success::(); + return Ok(()); + } + Ok(Some(value)) => { + self.config.list_item(&value); + } + } } + } - self.config - .execute_certificate_delete(matches, &mut request)?; + pub async fn execute_group_view(&self, matches: &clap::ArgMatches) -> anyhow::Result<()> { + let mut request = self.client.group_view(); + if let Some(value) = matches.get_one::("group-id") { + request = request.group_id(value.clone()); + } + + self.config.execute_group_view(matches, &mut request)?; let result = request.send().await; match result { Ok(r) => { - self.config.success_no_item(&r); + self.config.success_item(&r); Ok(()) } Err(r) => { @@ -6199,8 +7317,8 @@ impl Cli { } } - pub async fn execute_disk_list(&self, matches: &clap::ArgMatches) -> anyhow::Result<()> { - let mut request = self.client.disk_list(); + pub async fn execute_image_list(&self, matches: &clap::ArgMatches) -> anyhow::Result<()> { + let mut request = self.client.image_list(); if let Some(value) = matches.get_one::("limit") { request = request.limit(value.clone()); } @@ -6213,8 +7331,8 @@ impl Cli { request = request.sort_by(value.clone()); } - self.config.execute_disk_list(matches, &mut request)?; - self.config.list_start::(); + self.config.execute_image_list(matches, &mut request)?; + self.config.list_start::(); let mut stream = futures::StreamExt::take( request.stream(), matches @@ -6228,7 +7346,7 @@ impl Cli { return Err(anyhow::Error::new(r)); } Ok(None) => { - self.config.list_end_success::(); + self.config.list_end_success::(); return Ok(()); } Ok(Some(value)) => { @@ -6238,8 +7356,8 @@ impl Cli { } } - pub async fn execute_disk_create(&self, matches: &clap::ArgMatches) -> anyhow::Result<()> { - let mut request = self.client.disk_create(); + pub async fn execute_image_create(&self, matches: &clap::ArgMatches) -> anyhow::Result<()> { + let mut request = self.client.image_create(); if let Some(value) = matches.get_one::("description") { request = request.body_map(|body| body.description(value.clone())) } @@ -6248,21 +7366,25 @@ impl Cli { request = request.body_map(|body| body.name(value.clone())) } + if let Some(value) = matches.get_one::("os") { + request = request.body_map(|body| body.os(value.clone())) + } + if let Some(value) = matches.get_one::("project") { request = request.project(value.clone()); } - if let Some(value) = matches.get_one::("size") { - request = request.body_map(|body| body.size(value.clone())) + if let Some(value) = matches.get_one::("version") { + request = request.body_map(|body| body.version(value.clone())) } if let Some(value) = matches.get_one::("json-body") { let body_txt = std::fs::read_to_string(value).unwrap(); - let body_value = serde_json::from_str::(&body_txt).unwrap(); + let body_value = serde_json::from_str::(&body_txt).unwrap(); request = request.body(body_value); } - self.config.execute_disk_create(matches, &mut request)?; + self.config.execute_image_create(matches, &mut request)?; let result = request.send().await; match result { Ok(r) => { @@ -6276,17 +7398,17 @@ impl Cli { } } - pub async fn execute_disk_view(&self, matches: &clap::ArgMatches) -> anyhow::Result<()> { - let mut request = self.client.disk_view(); - if let Some(value) = matches.get_one::("disk") { - request = request.disk(value.clone()); + pub async fn execute_image_view(&self, matches: &clap::ArgMatches) -> anyhow::Result<()> { + let mut request = self.client.image_view(); + if let Some(value) = matches.get_one::("image") { + request = request.image(value.clone()); } if let Some(value) = matches.get_one::("project") { request = request.project(value.clone()); } - self.config.execute_disk_view(matches, &mut request)?; + self.config.execute_image_view(matches, &mut request)?; let result = request.send().await; match result { Ok(r) => { @@ -6300,88 +7422,17 @@ impl Cli { } } - pub async fn execute_disk_delete(&self, matches: &clap::ArgMatches) -> anyhow::Result<()> { - let mut request = self.client.disk_delete(); - if let Some(value) = matches.get_one::("disk") { - request = request.disk(value.clone()); - } - - if let Some(value) = matches.get_one::("project") { - request = request.project(value.clone()); - } - - self.config.execute_disk_delete(matches, &mut request)?; - let result = request.send().await; - match result { - Ok(r) => { - self.config.success_no_item(&r); - Ok(()) - } - Err(r) => { - self.config.error(&r); - Err(anyhow::Error::new(r)) - } - } - } - - pub async fn execute_disk_bulk_write_import( - &self, - matches: &clap::ArgMatches, - ) -> anyhow::Result<()> { - let mut request = self.client.disk_bulk_write_import(); - if let Some(value) = matches.get_one::("base64-encoded-data") { - request = request.body_map(|body| body.base64_encoded_data(value.clone())) - } - - if let Some(value) = matches.get_one::("disk") { - request = request.disk(value.clone()); - } - - if let Some(value) = matches.get_one::("offset") { - request = request.body_map(|body| body.offset(value.clone())) - } - - if let Some(value) = matches.get_one::("project") { - request = request.project(value.clone()); - } - - if let Some(value) = matches.get_one::("json-body") { - let body_txt = std::fs::read_to_string(value).unwrap(); - let body_value = - serde_json::from_str::(&body_txt).unwrap(); - request = request.body(body_value); - } - - self.config - .execute_disk_bulk_write_import(matches, &mut request)?; - let result = request.send().await; - match result { - Ok(r) => { - self.config.success_no_item(&r); - Ok(()) - } - Err(r) => { - self.config.error(&r); - Err(anyhow::Error::new(r)) - } - } - } - - pub async fn execute_disk_bulk_write_import_start( - &self, - matches: &clap::ArgMatches, - ) -> anyhow::Result<()> { - let mut request = self.client.disk_bulk_write_import_start(); - if let Some(value) = matches.get_one::("disk") { - request = request.disk(value.clone()); + pub async fn execute_image_delete(&self, matches: &clap::ArgMatches) -> anyhow::Result<()> { + let mut request = self.client.image_delete(); + if let Some(value) = matches.get_one::("image") { + request = request.image(value.clone()); } if let Some(value) = matches.get_one::("project") { request = request.project(value.clone()); } - self.config - .execute_disk_bulk_write_import_start(matches, &mut request)?; + self.config.execute_image_delete(matches, &mut request)?; let result = request.send().await; match result { Ok(r) => { @@ -6395,25 +7446,21 @@ impl Cli { } } - pub async fn execute_disk_bulk_write_import_stop( - &self, - matches: &clap::ArgMatches, - ) -> anyhow::Result<()> { - let mut request = self.client.disk_bulk_write_import_stop(); - if let Some(value) = matches.get_one::("disk") { - request = request.disk(value.clone()); + pub async fn execute_image_demote(&self, matches: &clap::ArgMatches) -> anyhow::Result<()> { + let mut request = self.client.image_demote(); + if let Some(value) = matches.get_one::("image") { + request = request.image(value.clone()); } if let Some(value) = matches.get_one::("project") { request = request.project(value.clone()); } - self.config - .execute_disk_bulk_write_import_stop(matches, &mut request)?; + self.config.execute_image_demote(matches, &mut request)?; let result = request.send().await; match result { Ok(r) => { - self.config.success_no_item(&r); + self.config.success_item(&r); Ok(()) } Err(r) => { @@ -6423,35 +7470,21 @@ impl Cli { } } - pub async fn execute_disk_finalize_import( - &self, - matches: &clap::ArgMatches, - ) -> anyhow::Result<()> { - let mut request = self.client.disk_finalize_import(); - if let Some(value) = matches.get_one::("disk") { - request = request.disk(value.clone()); + pub async fn execute_image_promote(&self, matches: &clap::ArgMatches) -> anyhow::Result<()> { + let mut request = self.client.image_promote(); + if let Some(value) = matches.get_one::("image") { + request = request.image(value.clone()); } if let Some(value) = matches.get_one::("project") { request = request.project(value.clone()); } - if let Some(value) = matches.get_one::("snapshot-name") { - request = request.body_map(|body| body.snapshot_name(value.clone())) - } - - if let Some(value) = matches.get_one::("json-body") { - let body_txt = std::fs::read_to_string(value).unwrap(); - let body_value = serde_json::from_str::(&body_txt).unwrap(); - request = request.body(body_value); - } - - self.config - .execute_disk_finalize_import(matches, &mut request)?; + self.config.execute_image_promote(matches, &mut request)?; let result = request.send().await; match result { Ok(r) => { - self.config.success_no_item(&r); + self.config.success_item(&r); Ok(()) } Err(r) => { @@ -6461,69 +7494,8 @@ impl Cli { } } - pub async fn execute_disk_metrics_list( - &self, - matches: &clap::ArgMatches, - ) -> anyhow::Result<()> { - let mut request = self.client.disk_metrics_list(); - if let Some(value) = matches.get_one::("disk") { - request = request.disk(value.clone()); - } - - if let Some(value) = matches.get_one::>("end-time") { - request = request.end_time(value.clone()); - } - - if let Some(value) = matches.get_one::("limit") { - request = request.limit(value.clone()); - } - - if let Some(value) = matches.get_one::("metric") { - request = request.metric(value.clone()); - } - - if let Some(value) = matches.get_one::("order") { - request = request.order(value.clone()); - } - - if let Some(value) = matches.get_one::("project") { - request = request.project(value.clone()); - } - - if let Some(value) = matches.get_one::>("start-time") - { - request = request.start_time(value.clone()); - } - - self.config - .execute_disk_metrics_list(matches, &mut request)?; - self.config.list_start::(); - let mut stream = futures::StreamExt::take( - request.stream(), - matches - .get_one::("limit") - .map_or(usize::MAX, |x| x.get() as usize), - ); - loop { - match futures::TryStreamExt::try_next(&mut stream).await { - Err(r) => { - self.config.list_end_error(&r); - return Err(anyhow::Error::new(r)); - } - Ok(None) => { - self.config - .list_end_success::(); - return Ok(()); - } - Ok(Some(value)) => { - self.config.list_item(&value); - } - } - } - } - - pub async fn execute_floating_ip_list(&self, matches: &clap::ArgMatches) -> anyhow::Result<()> { - let mut request = self.client.floating_ip_list(); + pub async fn execute_instance_list(&self, matches: &clap::ArgMatches) -> anyhow::Result<()> { + let mut request = self.client.instance_list(); if let Some(value) = matches.get_one::("limit") { request = request.limit(value.clone()); } @@ -6536,9 +7508,8 @@ impl Cli { request = request.sort_by(value.clone()); } - self.config - .execute_floating_ip_list(matches, &mut request)?; - self.config.list_start::(); + self.config.execute_instance_list(matches, &mut request)?; + self.config.list_start::(); let mut stream = futures::StreamExt::take( request.stream(), matches @@ -6552,8 +7523,7 @@ impl Cli { return Err(anyhow::Error::new(r)); } Ok(None) => { - self.config - .list_end_success::(); + self.config.list_end_success::(); return Ok(()); } Ok(Some(value)) => { @@ -6563,39 +7533,47 @@ impl Cli { } } - pub async fn execute_floating_ip_create( - &self, - matches: &clap::ArgMatches, - ) -> anyhow::Result<()> { - let mut request = self.client.floating_ip_create(); + pub async fn execute_instance_create(&self, matches: &clap::ArgMatches) -> anyhow::Result<()> { + let mut request = self.client.instance_create(); if let Some(value) = matches.get_one::("description") { request = request.body_map(|body| body.description(value.clone())) } - if let Some(value) = matches.get_one::("ip") { - request = request.body_map(|body| body.ip(value.clone())) + if let Some(value) = matches.get_one::("hostname") { + request = request.body_map(|body| body.hostname(value.clone())) + } + + if let Some(value) = matches.get_one::("memory") { + request = request.body_map(|body| body.memory(value.clone())) } if let Some(value) = matches.get_one::("name") { request = request.body_map(|body| body.name(value.clone())) } - if let Some(value) = matches.get_one::("pool") { - request = request.body_map(|body| body.pool(value.clone())) + if let Some(value) = matches.get_one::("ncpus") { + request = request.body_map(|body| body.ncpus(value.clone())) } if let Some(value) = matches.get_one::("project") { request = request.project(value.clone()); } + if let Some(value) = matches.get_one::("start") { + request = request.body_map(|body| body.start(value.clone())) + } + + if let Some(value) = matches.get_one::("user-data") { + request = request.body_map(|body| body.user_data(value.clone())) + } + if let Some(value) = matches.get_one::("json-body") { let body_txt = std::fs::read_to_string(value).unwrap(); - let body_value = serde_json::from_str::(&body_txt).unwrap(); + let body_value = serde_json::from_str::(&body_txt).unwrap(); request = request.body(body_value); } - self.config - .execute_floating_ip_create(matches, &mut request)?; + self.config.execute_instance_create(matches, &mut request)?; let result = request.send().await; match result { Ok(r) => { @@ -6609,18 +7587,17 @@ impl Cli { } } - pub async fn execute_floating_ip_view(&self, matches: &clap::ArgMatches) -> anyhow::Result<()> { - let mut request = self.client.floating_ip_view(); - if let Some(value) = matches.get_one::("floating-ip") { - request = request.floating_ip(value.clone()); + pub async fn execute_instance_view(&self, matches: &clap::ArgMatches) -> anyhow::Result<()> { + let mut request = self.client.instance_view(); + if let Some(value) = matches.get_one::("instance") { + request = request.instance(value.clone()); } if let Some(value) = matches.get_one::("project") { request = request.project(value.clone()); } - self.config - .execute_floating_ip_view(matches, &mut request)?; + self.config.execute_instance_view(matches, &mut request)?; let result = request.send().await; match result { Ok(r) => { @@ -6634,39 +7611,21 @@ impl Cli { } } - pub async fn execute_floating_ip_update( - &self, - matches: &clap::ArgMatches, - ) -> anyhow::Result<()> { - let mut request = self.client.floating_ip_update(); - if let Some(value) = matches.get_one::("description") { - request = request.body_map(|body| body.description(value.clone())) - } - - if let Some(value) = matches.get_one::("floating-ip") { - request = request.floating_ip(value.clone()); - } - - if let Some(value) = matches.get_one::("name") { - request = request.body_map(|body| body.name(value.clone())) + pub async fn execute_instance_delete(&self, matches: &clap::ArgMatches) -> anyhow::Result<()> { + let mut request = self.client.instance_delete(); + if let Some(value) = matches.get_one::("instance") { + request = request.instance(value.clone()); } if let Some(value) = matches.get_one::("project") { request = request.project(value.clone()); } - if let Some(value) = matches.get_one::("json-body") { - let body_txt = std::fs::read_to_string(value).unwrap(); - let body_value = serde_json::from_str::(&body_txt).unwrap(); - request = request.body(body_value); - } - - self.config - .execute_floating_ip_update(matches, &mut request)?; + self.config.execute_instance_delete(matches, &mut request)?; let result = request.send().await; match result { Ok(r) => { - self.config.success_item(&r); + self.config.success_no_item(&r); Ok(()) } Err(r) => { @@ -6676,49 +7635,64 @@ impl Cli { } } - pub async fn execute_floating_ip_delete( + pub async fn execute_instance_disk_list( &self, matches: &clap::ArgMatches, ) -> anyhow::Result<()> { - let mut request = self.client.floating_ip_delete(); - if let Some(value) = matches.get_one::("floating-ip") { - request = request.floating_ip(value.clone()); + let mut request = self.client.instance_disk_list(); + if let Some(value) = matches.get_one::("instance") { + request = request.instance(value.clone()); + } + + if let Some(value) = matches.get_one::("limit") { + request = request.limit(value.clone()); } if let Some(value) = matches.get_one::("project") { request = request.project(value.clone()); } + if let Some(value) = matches.get_one::("sort-by") { + request = request.sort_by(value.clone()); + } + self.config - .execute_floating_ip_delete(matches, &mut request)?; - let result = request.send().await; - match result { - Ok(r) => { - self.config.success_no_item(&r); - Ok(()) - } - Err(r) => { - self.config.error(&r); - Err(anyhow::Error::new(r)) + .execute_instance_disk_list(matches, &mut request)?; + self.config.list_start::(); + let mut stream = futures::StreamExt::take( + request.stream(), + matches + .get_one::("limit") + .map_or(usize::MAX, |x| x.get() as usize), + ); + loop { + match futures::TryStreamExt::try_next(&mut stream).await { + Err(r) => { + self.config.list_end_error(&r); + return Err(anyhow::Error::new(r)); + } + Ok(None) => { + self.config.list_end_success::(); + return Ok(()); + } + Ok(Some(value)) => { + self.config.list_item(&value); + } } } } - pub async fn execute_floating_ip_attach( + pub async fn execute_instance_disk_attach( &self, matches: &clap::ArgMatches, ) -> anyhow::Result<()> { - let mut request = self.client.floating_ip_attach(); - if let Some(value) = matches.get_one::("floating-ip") { - request = request.floating_ip(value.clone()); - } - - if let Some(value) = matches.get_one::("kind") { - request = request.body_map(|body| body.kind(value.clone())) + let mut request = self.client.instance_disk_attach(); + if let Some(value) = matches.get_one::("disk") { + request = request.body_map(|body| body.disk(value.clone())) } - if let Some(value) = matches.get_one::("parent") { - request = request.body_map(|body| body.parent(value.clone())) + if let Some(value) = matches.get_one::("instance") { + request = request.instance(value.clone()); } if let Some(value) = matches.get_one::("project") { @@ -6727,12 +7701,12 @@ impl Cli { if let Some(value) = matches.get_one::("json-body") { let body_txt = std::fs::read_to_string(value).unwrap(); - let body_value = serde_json::from_str::(&body_txt).unwrap(); + let body_value = serde_json::from_str::(&body_txt).unwrap(); request = request.body(body_value); } self.config - .execute_floating_ip_attach(matches, &mut request)?; + .execute_instance_disk_attach(matches, &mut request)?; let result = request.send().await; match result { Ok(r) => { @@ -6746,21 +7720,31 @@ impl Cli { } } - pub async fn execute_floating_ip_detach( + pub async fn execute_instance_disk_detach( &self, matches: &clap::ArgMatches, ) -> anyhow::Result<()> { - let mut request = self.client.floating_ip_detach(); - if let Some(value) = matches.get_one::("floating-ip") { - request = request.floating_ip(value.clone()); + let mut request = self.client.instance_disk_detach(); + if let Some(value) = matches.get_one::("disk") { + request = request.body_map(|body| body.disk(value.clone())) + } + + if let Some(value) = matches.get_one::("instance") { + request = request.instance(value.clone()); } if let Some(value) = matches.get_one::("project") { request = request.project(value.clone()); } + if let Some(value) = matches.get_one::("json-body") { + let body_txt = std::fs::read_to_string(value).unwrap(); + let body_value = serde_json::from_str::(&body_txt).unwrap(); + request = request.body(body_value); + } + self.config - .execute_floating_ip_detach(matches, &mut request)?; + .execute_instance_disk_detach(matches, &mut request)?; let result = request.send().await; match result { Ok(r) => { @@ -6774,48 +7758,21 @@ impl Cli { } } - pub async fn execute_group_list(&self, matches: &clap::ArgMatches) -> anyhow::Result<()> { - let mut request = self.client.group_list(); - if let Some(value) = matches.get_one::("limit") { - request = request.limit(value.clone()); - } - - if let Some(value) = matches.get_one::("sort-by") { - request = request.sort_by(value.clone()); - } - - self.config.execute_group_list(matches, &mut request)?; - self.config.list_start::(); - let mut stream = futures::StreamExt::take( - request.stream(), - matches - .get_one::("limit") - .map_or(usize::MAX, |x| x.get() as usize), - ); - loop { - match futures::TryStreamExt::try_next(&mut stream).await { - Err(r) => { - self.config.list_end_error(&r); - return Err(anyhow::Error::new(r)); - } - Ok(None) => { - self.config.list_end_success::(); - return Ok(()); - } - Ok(Some(value)) => { - self.config.list_item(&value); - } - } + pub async fn execute_instance_external_ip_list( + &self, + matches: &clap::ArgMatches, + ) -> anyhow::Result<()> { + let mut request = self.client.instance_external_ip_list(); + if let Some(value) = matches.get_one::("instance") { + request = request.instance(value.clone()); } - } - pub async fn execute_group_view(&self, matches: &clap::ArgMatches) -> anyhow::Result<()> { - let mut request = self.client.group_view(); - if let Some(value) = matches.get_one::("group-id") { - request = request.group_id(value.clone()); + if let Some(value) = matches.get_one::("project") { + request = request.project(value.clone()); } - self.config.execute_group_view(matches, &mut request)?; + self.config + .execute_instance_external_ip_list(matches, &mut request)?; let result = request.send().await; match result { Ok(r) => { @@ -6829,74 +7786,31 @@ impl Cli { } } - pub async fn execute_image_list(&self, matches: &clap::ArgMatches) -> anyhow::Result<()> { - let mut request = self.client.image_list(); - if let Some(value) = matches.get_one::("limit") { - request = request.limit(value.clone()); - } - - if let Some(value) = matches.get_one::("project") { - request = request.project(value.clone()); - } - - if let Some(value) = matches.get_one::("sort-by") { - request = request.sort_by(value.clone()); - } - - self.config.execute_image_list(matches, &mut request)?; - self.config.list_start::(); - let mut stream = futures::StreamExt::take( - request.stream(), - matches - .get_one::("limit") - .map_or(usize::MAX, |x| x.get() as usize), - ); - loop { - match futures::TryStreamExt::try_next(&mut stream).await { - Err(r) => { - self.config.list_end_error(&r); - return Err(anyhow::Error::new(r)); - } - Ok(None) => { - self.config.list_end_success::(); - return Ok(()); - } - Ok(Some(value)) => { - self.config.list_item(&value); - } - } - } - } - - pub async fn execute_image_create(&self, matches: &clap::ArgMatches) -> anyhow::Result<()> { - let mut request = self.client.image_create(); - if let Some(value) = matches.get_one::("description") { - request = request.body_map(|body| body.description(value.clone())) - } - - if let Some(value) = matches.get_one::("name") { - request = request.body_map(|body| body.name(value.clone())) + pub async fn execute_instance_ephemeral_ip_attach( + &self, + matches: &clap::ArgMatches, + ) -> anyhow::Result<()> { + let mut request = self.client.instance_ephemeral_ip_attach(); + if let Some(value) = matches.get_one::("instance") { + request = request.instance(value.clone()); } - - if let Some(value) = matches.get_one::("os") { - request = request.body_map(|body| body.os(value.clone())) + + if let Some(value) = matches.get_one::("pool") { + request = request.body_map(|body| body.pool(value.clone())) } if let Some(value) = matches.get_one::("project") { request = request.project(value.clone()); } - if let Some(value) = matches.get_one::("version") { - request = request.body_map(|body| body.version(value.clone())) - } - if let Some(value) = matches.get_one::("json-body") { let body_txt = std::fs::read_to_string(value).unwrap(); - let body_value = serde_json::from_str::(&body_txt).unwrap(); + let body_value = serde_json::from_str::(&body_txt).unwrap(); request = request.body(body_value); } - self.config.execute_image_create(matches, &mut request)?; + self.config + .execute_instance_ephemeral_ip_attach(matches, &mut request)?; let result = request.send().await; match result { Ok(r) => { @@ -6910,21 +7824,25 @@ impl Cli { } } - pub async fn execute_image_view(&self, matches: &clap::ArgMatches) -> anyhow::Result<()> { - let mut request = self.client.image_view(); - if let Some(value) = matches.get_one::("image") { - request = request.image(value.clone()); + pub async fn execute_instance_ephemeral_ip_detach( + &self, + matches: &clap::ArgMatches, + ) -> anyhow::Result<()> { + let mut request = self.client.instance_ephemeral_ip_detach(); + if let Some(value) = matches.get_one::("instance") { + request = request.instance(value.clone()); } if let Some(value) = matches.get_one::("project") { request = request.project(value.clone()); } - self.config.execute_image_view(matches, &mut request)?; + self.config + .execute_instance_ephemeral_ip_detach(matches, &mut request)?; let result = request.send().await; match result { Ok(r) => { - self.config.success_item(&r); + self.config.success_no_item(&r); Ok(()) } Err(r) => { @@ -6934,21 +7852,32 @@ impl Cli { } } - pub async fn execute_image_delete(&self, matches: &clap::ArgMatches) -> anyhow::Result<()> { - let mut request = self.client.image_delete(); - if let Some(value) = matches.get_one::("image") { - request = request.image(value.clone()); + pub async fn execute_instance_migrate(&self, matches: &clap::ArgMatches) -> anyhow::Result<()> { + let mut request = self.client.instance_migrate(); + if let Some(value) = matches.get_one::("dst-sled-id") { + request = request.body_map(|body| body.dst_sled_id(value.clone())) + } + + if let Some(value) = matches.get_one::("instance") { + request = request.instance(value.clone()); } if let Some(value) = matches.get_one::("project") { request = request.project(value.clone()); } - self.config.execute_image_delete(matches, &mut request)?; + if let Some(value) = matches.get_one::("json-body") { + let body_txt = std::fs::read_to_string(value).unwrap(); + let body_value = serde_json::from_str::(&body_txt).unwrap(); + request = request.body(body_value); + } + + self.config + .execute_instance_migrate(matches, &mut request)?; let result = request.send().await; match result { Ok(r) => { - self.config.success_no_item(&r); + self.config.success_item(&r); Ok(()) } Err(r) => { @@ -6958,17 +7887,17 @@ impl Cli { } } - pub async fn execute_image_demote(&self, matches: &clap::ArgMatches) -> anyhow::Result<()> { - let mut request = self.client.image_demote(); - if let Some(value) = matches.get_one::("image") { - request = request.image(value.clone()); + pub async fn execute_instance_reboot(&self, matches: &clap::ArgMatches) -> anyhow::Result<()> { + let mut request = self.client.instance_reboot(); + if let Some(value) = matches.get_one::("instance") { + request = request.instance(value.clone()); } if let Some(value) = matches.get_one::("project") { request = request.project(value.clone()); } - self.config.execute_image_demote(matches, &mut request)?; + self.config.execute_instance_reboot(matches, &mut request)?; let result = request.send().await; match result { Ok(r) => { @@ -6982,17 +7911,33 @@ impl Cli { } } - pub async fn execute_image_promote(&self, matches: &clap::ArgMatches) -> anyhow::Result<()> { - let mut request = self.client.image_promote(); - if let Some(value) = matches.get_one::("image") { - request = request.image(value.clone()); + pub async fn execute_instance_serial_console( + &self, + matches: &clap::ArgMatches, + ) -> anyhow::Result<()> { + let mut request = self.client.instance_serial_console(); + if let Some(value) = matches.get_one::("from-start") { + request = request.from_start(value.clone()); + } + + if let Some(value) = matches.get_one::("instance") { + request = request.instance(value.clone()); + } + + if let Some(value) = matches.get_one::("max-bytes") { + request = request.max_bytes(value.clone()); + } + + if let Some(value) = matches.get_one::("most-recent") { + request = request.most_recent(value.clone()); } if let Some(value) = matches.get_one::("project") { request = request.project(value.clone()); } - self.config.execute_image_promote(matches, &mut request)?; + self.config + .execute_instance_serial_console(matches, &mut request)?; let result = request.send().await; match result { Ok(r) => { @@ -7006,8 +7951,45 @@ impl Cli { } } - pub async fn execute_instance_list(&self, matches: &clap::ArgMatches) -> anyhow::Result<()> { - let mut request = self.client.instance_list(); + pub async fn execute_instance_serial_console_stream( + &self, + matches: &clap::ArgMatches, + ) -> anyhow::Result<()> { + let mut request = self.client.instance_serial_console_stream(); + if let Some(value) = matches.get_one::("instance") { + request = request.instance(value.clone()); + } + + if let Some(value) = matches.get_one::("most-recent") { + request = request.most_recent(value.clone()); + } + + if let Some(value) = matches.get_one::("project") { + request = request.project(value.clone()); + } + + self.config + .execute_instance_serial_console_stream(matches, &mut request)?; + let result = request.send().await; + match result { + Ok(r) => { + todo!() + } + Err(r) => { + todo!() + } + } + } + + pub async fn execute_instance_ssh_public_key_list( + &self, + matches: &clap::ArgMatches, + ) -> anyhow::Result<()> { + let mut request = self.client.instance_ssh_public_key_list(); + if let Some(value) = matches.get_one::("instance") { + request = request.instance(value.clone()); + } + if let Some(value) = matches.get_one::("limit") { request = request.limit(value.clone()); } @@ -7020,8 +8002,9 @@ impl Cli { request = request.sort_by(value.clone()); } - self.config.execute_instance_list(matches, &mut request)?; - self.config.list_start::(); + self.config + .execute_instance_ssh_public_key_list(matches, &mut request)?; + self.config.list_start::(); let mut stream = futures::StreamExt::take( request.stream(), matches @@ -7035,7 +8018,7 @@ impl Cli { return Err(anyhow::Error::new(r)); } Ok(None) => { - self.config.list_end_success::(); + self.config.list_end_success::(); return Ok(()); } Ok(Some(value)) => { @@ -7045,62 +8028,8 @@ impl Cli { } } - pub async fn execute_instance_create(&self, matches: &clap::ArgMatches) -> anyhow::Result<()> { - let mut request = self.client.instance_create(); - if let Some(value) = matches.get_one::("description") { - request = request.body_map(|body| body.description(value.clone())) - } - - if let Some(value) = matches.get_one::("hostname") { - request = request.body_map(|body| body.hostname(value.clone())) - } - - if let Some(value) = matches.get_one::("memory") { - request = request.body_map(|body| body.memory(value.clone())) - } - - if let Some(value) = matches.get_one::("name") { - request = request.body_map(|body| body.name(value.clone())) - } - - if let Some(value) = matches.get_one::("ncpus") { - request = request.body_map(|body| body.ncpus(value.clone())) - } - - if let Some(value) = matches.get_one::("project") { - request = request.project(value.clone()); - } - - if let Some(value) = matches.get_one::("start") { - request = request.body_map(|body| body.start(value.clone())) - } - - if let Some(value) = matches.get_one::("user-data") { - request = request.body_map(|body| body.user_data(value.clone())) - } - - if let Some(value) = matches.get_one::("json-body") { - let body_txt = std::fs::read_to_string(value).unwrap(); - let body_value = serde_json::from_str::(&body_txt).unwrap(); - request = request.body(body_value); - } - - self.config.execute_instance_create(matches, &mut request)?; - let result = request.send().await; - match result { - Ok(r) => { - self.config.success_item(&r); - Ok(()) - } - Err(r) => { - self.config.error(&r); - Err(anyhow::Error::new(r)) - } - } - } - - pub async fn execute_instance_view(&self, matches: &clap::ArgMatches) -> anyhow::Result<()> { - let mut request = self.client.instance_view(); + pub async fn execute_instance_start(&self, matches: &clap::ArgMatches) -> anyhow::Result<()> { + let mut request = self.client.instance_start(); if let Some(value) = matches.get_one::("instance") { request = request.instance(value.clone()); } @@ -7109,7 +8038,7 @@ impl Cli { request = request.project(value.clone()); } - self.config.execute_instance_view(matches, &mut request)?; + self.config.execute_instance_start(matches, &mut request)?; let result = request.send().await; match result { Ok(r) => { @@ -7123,8 +8052,8 @@ impl Cli { } } - pub async fn execute_instance_delete(&self, matches: &clap::ArgMatches) -> anyhow::Result<()> { - let mut request = self.client.instance_delete(); + pub async fn execute_instance_stop(&self, matches: &clap::ArgMatches) -> anyhow::Result<()> { + let mut request = self.client.instance_stop(); if let Some(value) = matches.get_one::("instance") { request = request.instance(value.clone()); } @@ -7133,11 +8062,11 @@ impl Cli { request = request.project(value.clone()); } - self.config.execute_instance_delete(matches, &mut request)?; + self.config.execute_instance_stop(matches, &mut request)?; let result = request.send().await; match result { Ok(r) => { - self.config.success_no_item(&r); + self.config.success_item(&r); Ok(()) } Err(r) => { @@ -7147,30 +8076,22 @@ impl Cli { } } - pub async fn execute_instance_disk_list( + pub async fn execute_project_ip_pool_list( &self, matches: &clap::ArgMatches, ) -> anyhow::Result<()> { - let mut request = self.client.instance_disk_list(); - if let Some(value) = matches.get_one::("instance") { - request = request.instance(value.clone()); - } - + let mut request = self.client.project_ip_pool_list(); if let Some(value) = matches.get_one::("limit") { request = request.limit(value.clone()); } - if let Some(value) = matches.get_one::("project") { - request = request.project(value.clone()); - } - if let Some(value) = matches.get_one::("sort-by") { request = request.sort_by(value.clone()); } self.config - .execute_instance_disk_list(matches, &mut request)?; - self.config.list_start::(); + .execute_project_ip_pool_list(matches, &mut request)?; + self.config.list_start::(); let mut stream = futures::StreamExt::take( request.stream(), matches @@ -7184,7 +8105,8 @@ impl Cli { return Err(anyhow::Error::new(r)); } Ok(None) => { - self.config.list_end_success::(); + self.config + .list_end_success::(); return Ok(()); } Ok(Some(value)) => { @@ -7194,31 +8116,17 @@ impl Cli { } } - pub async fn execute_instance_disk_attach( + pub async fn execute_project_ip_pool_view( &self, matches: &clap::ArgMatches, ) -> anyhow::Result<()> { - let mut request = self.client.instance_disk_attach(); - if let Some(value) = matches.get_one::("disk") { - request = request.body_map(|body| body.disk(value.clone())) - } - - if let Some(value) = matches.get_one::("instance") { - request = request.instance(value.clone()); - } - - if let Some(value) = matches.get_one::("project") { - request = request.project(value.clone()); - } - - if let Some(value) = matches.get_one::("json-body") { - let body_txt = std::fs::read_to_string(value).unwrap(); - let body_value = serde_json::from_str::(&body_txt).unwrap(); - request = request.body(body_value); + let mut request = self.client.project_ip_pool_view(); + if let Some(value) = matches.get_one::("pool") { + request = request.pool(value.clone()); } self.config - .execute_instance_disk_attach(matches, &mut request)?; + .execute_project_ip_pool_view(matches, &mut request)?; let result = request.send().await; match result { Ok(r) => { @@ -7232,35 +8140,32 @@ impl Cli { } } - pub async fn execute_instance_disk_detach( - &self, - matches: &clap::ArgMatches, - ) -> anyhow::Result<()> { - let mut request = self.client.instance_disk_detach(); - if let Some(value) = matches.get_one::("disk") { - request = request.body_map(|body| body.disk(value.clone())) + pub async fn execute_login_local(&self, matches: &clap::ArgMatches) -> anyhow::Result<()> { + let mut request = self.client.login_local(); + if let Some(value) = matches.get_one::("password") { + request = request.body_map(|body| body.password(value.clone())) } - if let Some(value) = matches.get_one::("instance") { - request = request.instance(value.clone()); + if let Some(value) = matches.get_one::("silo-name") { + request = request.silo_name(value.clone()); } - if let Some(value) = matches.get_one::("project") { - request = request.project(value.clone()); + if let Some(value) = matches.get_one::("username") { + request = request.body_map(|body| body.username(value.clone())) } if let Some(value) = matches.get_one::("json-body") { let body_txt = std::fs::read_to_string(value).unwrap(); - let body_value = serde_json::from_str::(&body_txt).unwrap(); + let body_value = + serde_json::from_str::(&body_txt).unwrap(); request = request.body(body_value); } - self.config - .execute_instance_disk_detach(matches, &mut request)?; + self.config.execute_login_local(matches, &mut request)?; let result = request.send().await; match result { Ok(r) => { - self.config.success_item(&r); + self.config.success_no_item(&r); Ok(()) } Err(r) => { @@ -7270,25 +8175,13 @@ impl Cli { } } - pub async fn execute_instance_external_ip_list( - &self, - matches: &clap::ArgMatches, - ) -> anyhow::Result<()> { - let mut request = self.client.instance_external_ip_list(); - if let Some(value) = matches.get_one::("instance") { - request = request.instance(value.clone()); - } - - if let Some(value) = matches.get_one::("project") { - request = request.project(value.clone()); - } - - self.config - .execute_instance_external_ip_list(matches, &mut request)?; + pub async fn execute_logout(&self, matches: &clap::ArgMatches) -> anyhow::Result<()> { + let mut request = self.client.logout(); + self.config.execute_logout(matches, &mut request)?; let result = request.send().await; match result { Ok(r) => { - self.config.success_item(&r); + self.config.success_no_item(&r); Ok(()) } Err(r) => { @@ -7298,31 +8191,13 @@ impl Cli { } } - pub async fn execute_instance_ephemeral_ip_attach( + pub async fn execute_current_user_view( &self, matches: &clap::ArgMatches, ) -> anyhow::Result<()> { - let mut request = self.client.instance_ephemeral_ip_attach(); - if let Some(value) = matches.get_one::("instance") { - request = request.instance(value.clone()); - } - - if let Some(value) = matches.get_one::("pool") { - request = request.body_map(|body| body.pool(value.clone())) - } - - if let Some(value) = matches.get_one::("project") { - request = request.project(value.clone()); - } - - if let Some(value) = matches.get_one::("json-body") { - let body_txt = std::fs::read_to_string(value).unwrap(); - let body_value = serde_json::from_str::(&body_txt).unwrap(); - request = request.body(body_value); - } - + let mut request = self.client.current_user_view(); self.config - .execute_instance_ephemeral_ip_attach(matches, &mut request)?; + .execute_current_user_view(matches, &mut request)?; let result = request.send().await; match result { Ok(r) => { @@ -7336,56 +8211,109 @@ impl Cli { } } - pub async fn execute_instance_ephemeral_ip_detach( + pub async fn execute_current_user_groups( &self, matches: &clap::ArgMatches, ) -> anyhow::Result<()> { - let mut request = self.client.instance_ephemeral_ip_detach(); - if let Some(value) = matches.get_one::("instance") { - request = request.instance(value.clone()); + let mut request = self.client.current_user_groups(); + if let Some(value) = matches.get_one::("limit") { + request = request.limit(value.clone()); } - if let Some(value) = matches.get_one::("project") { - request = request.project(value.clone()); + if let Some(value) = matches.get_one::("sort-by") { + request = request.sort_by(value.clone()); } self.config - .execute_instance_ephemeral_ip_detach(matches, &mut request)?; - let result = request.send().await; - match result { - Ok(r) => { - self.config.success_no_item(&r); - Ok(()) + .execute_current_user_groups(matches, &mut request)?; + self.config.list_start::(); + let mut stream = futures::StreamExt::take( + request.stream(), + matches + .get_one::("limit") + .map_or(usize::MAX, |x| x.get() as usize), + ); + loop { + match futures::TryStreamExt::try_next(&mut stream).await { + Err(r) => { + self.config.list_end_error(&r); + return Err(anyhow::Error::new(r)); + } + Ok(None) => { + self.config.list_end_success::(); + return Ok(()); + } + Ok(Some(value)) => { + self.config.list_item(&value); + } } - Err(r) => { - self.config.error(&r); - Err(anyhow::Error::new(r)) + } + } + + pub async fn execute_current_user_ssh_key_list( + &self, + matches: &clap::ArgMatches, + ) -> anyhow::Result<()> { + let mut request = self.client.current_user_ssh_key_list(); + if let Some(value) = matches.get_one::("limit") { + request = request.limit(value.clone()); + } + + if let Some(value) = matches.get_one::("sort-by") { + request = request.sort_by(value.clone()); + } + + self.config + .execute_current_user_ssh_key_list(matches, &mut request)?; + self.config.list_start::(); + let mut stream = futures::StreamExt::take( + request.stream(), + matches + .get_one::("limit") + .map_or(usize::MAX, |x| x.get() as usize), + ); + loop { + match futures::TryStreamExt::try_next(&mut stream).await { + Err(r) => { + self.config.list_end_error(&r); + return Err(anyhow::Error::new(r)); + } + Ok(None) => { + self.config.list_end_success::(); + return Ok(()); + } + Ok(Some(value)) => { + self.config.list_item(&value); + } } } } - pub async fn execute_instance_migrate(&self, matches: &clap::ArgMatches) -> anyhow::Result<()> { - let mut request = self.client.instance_migrate(); - if let Some(value) = matches.get_one::("dst-sled-id") { - request = request.body_map(|body| body.dst_sled_id(value.clone())) + pub async fn execute_current_user_ssh_key_create( + &self, + matches: &clap::ArgMatches, + ) -> anyhow::Result<()> { + let mut request = self.client.current_user_ssh_key_create(); + if let Some(value) = matches.get_one::("description") { + request = request.body_map(|body| body.description(value.clone())) } - if let Some(value) = matches.get_one::("instance") { - request = request.instance(value.clone()); + if let Some(value) = matches.get_one::("name") { + request = request.body_map(|body| body.name(value.clone())) } - if let Some(value) = matches.get_one::("project") { - request = request.project(value.clone()); + if let Some(value) = matches.get_one::("public-key") { + request = request.body_map(|body| body.public_key(value.clone())) } if let Some(value) = matches.get_one::("json-body") { let body_txt = std::fs::read_to_string(value).unwrap(); - let body_value = serde_json::from_str::(&body_txt).unwrap(); + let body_value = serde_json::from_str::(&body_txt).unwrap(); request = request.body(body_value); } self.config - .execute_instance_migrate(matches, &mut request)?; + .execute_current_user_ssh_key_create(matches, &mut request)?; let result = request.send().await; match result { Ok(r) => { @@ -7399,17 +8327,17 @@ impl Cli { } } - pub async fn execute_instance_reboot(&self, matches: &clap::ArgMatches) -> anyhow::Result<()> { - let mut request = self.client.instance_reboot(); - if let Some(value) = matches.get_one::("instance") { - request = request.instance(value.clone()); - } - - if let Some(value) = matches.get_one::("project") { - request = request.project(value.clone()); + pub async fn execute_current_user_ssh_key_view( + &self, + matches: &clap::ArgMatches, + ) -> anyhow::Result<()> { + let mut request = self.client.current_user_ssh_key_view(); + if let Some(value) = matches.get_one::("ssh-key") { + request = request.ssh_key(value.clone()); } - self.config.execute_instance_reboot(matches, &mut request)?; + self.config + .execute_current_user_ssh_key_view(matches, &mut request)?; let result = request.send().await; match result { Ok(r) => { @@ -7423,37 +8351,21 @@ impl Cli { } } - pub async fn execute_instance_serial_console( + pub async fn execute_current_user_ssh_key_delete( &self, matches: &clap::ArgMatches, ) -> anyhow::Result<()> { - let mut request = self.client.instance_serial_console(); - if let Some(value) = matches.get_one::("from-start") { - request = request.from_start(value.clone()); - } - - if let Some(value) = matches.get_one::("instance") { - request = request.instance(value.clone()); - } - - if let Some(value) = matches.get_one::("max-bytes") { - request = request.max_bytes(value.clone()); - } - - if let Some(value) = matches.get_one::("most-recent") { - request = request.most_recent(value.clone()); - } - - if let Some(value) = matches.get_one::("project") { - request = request.project(value.clone()); + let mut request = self.client.current_user_ssh_key_delete(); + if let Some(value) = matches.get_one::("ssh-key") { + request = request.ssh_key(value.clone()); } self.config - .execute_instance_serial_console(matches, &mut request)?; + .execute_current_user_ssh_key_delete(matches, &mut request)?; let result = request.send().await; match result { Ok(r) => { - self.config.success_item(&r); + self.config.success_no_item(&r); Ok(()) } Err(r) => { @@ -7463,41 +8375,64 @@ impl Cli { } } - pub async fn execute_instance_serial_console_stream( - &self, - matches: &clap::ArgMatches, - ) -> anyhow::Result<()> { - let mut request = self.client.instance_serial_console_stream(); - if let Some(value) = matches.get_one::("instance") { - request = request.instance(value.clone()); + pub async fn execute_silo_metric(&self, matches: &clap::ArgMatches) -> anyhow::Result<()> { + let mut request = self.client.silo_metric(); + if let Some(value) = matches.get_one::>("end-time") { + request = request.end_time(value.clone()); } - if let Some(value) = matches.get_one::("most-recent") { - request = request.most_recent(value.clone()); + if let Some(value) = matches.get_one::("limit") { + request = request.limit(value.clone()); + } + + if let Some(value) = matches.get_one::("metric-name") { + request = request.metric_name(value.clone()); + } + + if let Some(value) = matches.get_one::("order") { + request = request.order(value.clone()); } if let Some(value) = matches.get_one::("project") { request = request.project(value.clone()); } - self.config - .execute_instance_serial_console_stream(matches, &mut request)?; - let result = request.send().await; - match result { - Ok(r) => { - todo!() - } - Err(r) => { - todo!() + if let Some(value) = matches.get_one::>("start-time") + { + request = request.start_time(value.clone()); + } + + self.config.execute_silo_metric(matches, &mut request)?; + self.config.list_start::(); + let mut stream = futures::StreamExt::take( + request.stream(), + matches + .get_one::("limit") + .map_or(usize::MAX, |x| x.get() as usize), + ); + loop { + match futures::TryStreamExt::try_next(&mut stream).await { + Err(r) => { + self.config.list_end_error(&r); + return Err(anyhow::Error::new(r)); + } + Ok(None) => { + self.config + .list_end_success::(); + return Ok(()); + } + Ok(Some(value)) => { + self.config.list_item(&value); + } } } } - pub async fn execute_instance_ssh_public_key_list( + pub async fn execute_instance_network_interface_list( &self, matches: &clap::ArgMatches, ) -> anyhow::Result<()> { - let mut request = self.client.instance_ssh_public_key_list(); + let mut request = self.client.instance_network_interface_list(); if let Some(value) = matches.get_one::("instance") { request = request.instance(value.clone()); } @@ -7515,8 +8450,9 @@ impl Cli { } self.config - .execute_instance_ssh_public_key_list(matches, &mut request)?; - self.config.list_start::(); + .execute_instance_network_interface_list(matches, &mut request)?; + self.config + .list_start::(); let mut stream = futures::StreamExt::take( request.stream(), matches @@ -7530,7 +8466,8 @@ impl Cli { return Err(anyhow::Error::new(r)); } Ok(None) => { - self.config.list_end_success::(); + self.config + .list_end_success::(); return Ok(()); } Ok(Some(value)) => { @@ -7540,17 +8477,48 @@ impl Cli { } } - pub async fn execute_instance_start(&self, matches: &clap::ArgMatches) -> anyhow::Result<()> { - let mut request = self.client.instance_start(); + pub async fn execute_instance_network_interface_create( + &self, + matches: &clap::ArgMatches, + ) -> anyhow::Result<()> { + let mut request = self.client.instance_network_interface_create(); + if let Some(value) = matches.get_one::("description") { + request = request.body_map(|body| body.description(value.clone())) + } + if let Some(value) = matches.get_one::("instance") { request = request.instance(value.clone()); } + if let Some(value) = matches.get_one::("ip") { + request = request.body_map(|body| body.ip(value.clone())) + } + + if let Some(value) = matches.get_one::("name") { + request = request.body_map(|body| body.name(value.clone())) + } + if let Some(value) = matches.get_one::("project") { request = request.project(value.clone()); } - self.config.execute_instance_start(matches, &mut request)?; + if let Some(value) = matches.get_one::("subnet-name") { + request = request.body_map(|body| body.subnet_name(value.clone())) + } + + if let Some(value) = matches.get_one::("vpc-name") { + request = request.body_map(|body| body.vpc_name(value.clone())) + } + + if let Some(value) = matches.get_one::("json-body") { + let body_txt = std::fs::read_to_string(value).unwrap(); + let body_value = + serde_json::from_str::(&body_txt).unwrap(); + request = request.body(body_value); + } + + self.config + .execute_instance_network_interface_create(matches, &mut request)?; let result = request.send().await; match result { Ok(r) => { @@ -7564,17 +8532,25 @@ impl Cli { } } - pub async fn execute_instance_stop(&self, matches: &clap::ArgMatches) -> anyhow::Result<()> { - let mut request = self.client.instance_stop(); + pub async fn execute_instance_network_interface_view( + &self, + matches: &clap::ArgMatches, + ) -> anyhow::Result<()> { + let mut request = self.client.instance_network_interface_view(); if let Some(value) = matches.get_one::("instance") { request = request.instance(value.clone()); } + if let Some(value) = matches.get_one::("interface") { + request = request.interface(value.clone()); + } + if let Some(value) = matches.get_one::("project") { request = request.project(value.clone()); } - self.config.execute_instance_stop(matches, &mut request)?; + self.config + .execute_instance_network_interface_view(matches, &mut request)?; let result = request.send().await; match result { Ok(r) => { @@ -7588,57 +8564,44 @@ impl Cli { } } - pub async fn execute_project_ip_pool_list( + pub async fn execute_instance_network_interface_update( &self, matches: &clap::ArgMatches, ) -> anyhow::Result<()> { - let mut request = self.client.project_ip_pool_list(); - if let Some(value) = matches.get_one::("limit") { - request = request.limit(value.clone()); + let mut request = self.client.instance_network_interface_update(); + if let Some(value) = matches.get_one::("description") { + request = request.body_map(|body| body.description(value.clone())) } - if let Some(value) = matches.get_one::("sort-by") { - request = request.sort_by(value.clone()); + if let Some(value) = matches.get_one::("instance") { + request = request.instance(value.clone()); } - self.config - .execute_project_ip_pool_list(matches, &mut request)?; - self.config.list_start::(); - let mut stream = futures::StreamExt::take( - request.stream(), - matches - .get_one::("limit") - .map_or(usize::MAX, |x| x.get() as usize), - ); - loop { - match futures::TryStreamExt::try_next(&mut stream).await { - Err(r) => { - self.config.list_end_error(&r); - return Err(anyhow::Error::new(r)); - } - Ok(None) => { - self.config - .list_end_success::(); - return Ok(()); - } - Ok(Some(value)) => { - self.config.list_item(&value); - } - } + if let Some(value) = matches.get_one::("interface") { + request = request.interface(value.clone()); } - } - pub async fn execute_project_ip_pool_view( - &self, - matches: &clap::ArgMatches, - ) -> anyhow::Result<()> { - let mut request = self.client.project_ip_pool_view(); - if let Some(value) = matches.get_one::("pool") { - request = request.pool(value.clone()); + if let Some(value) = matches.get_one::("name") { + request = request.body_map(|body| body.name(value.clone())) + } + + if let Some(value) = matches.get_one::("primary") { + request = request.body_map(|body| body.primary(value.clone())) + } + + if let Some(value) = matches.get_one::("project") { + request = request.project(value.clone()); + } + + if let Some(value) = matches.get_one::("json-body") { + let body_txt = std::fs::read_to_string(value).unwrap(); + let body_value = + serde_json::from_str::(&body_txt).unwrap(); + request = request.body(body_value); } self.config - .execute_project_ip_pool_view(matches, &mut request)?; + .execute_instance_network_interface_update(matches, &mut request)?; let result = request.send().await; match result { Ok(r) => { @@ -7652,32 +8615,45 @@ impl Cli { } } - pub async fn execute_login_local(&self, matches: &clap::ArgMatches) -> anyhow::Result<()> { - let mut request = self.client.login_local(); - if let Some(value) = matches.get_one::("password") { - request = request.body_map(|body| body.password(value.clone())) + pub async fn execute_instance_network_interface_delete( + &self, + matches: &clap::ArgMatches, + ) -> anyhow::Result<()> { + let mut request = self.client.instance_network_interface_delete(); + if let Some(value) = matches.get_one::("instance") { + request = request.instance(value.clone()); } - if let Some(value) = matches.get_one::("silo-name") { - request = request.silo_name(value.clone()); + if let Some(value) = matches.get_one::("interface") { + request = request.interface(value.clone()); } - if let Some(value) = matches.get_one::("username") { - request = request.body_map(|body| body.username(value.clone())) + if let Some(value) = matches.get_one::("project") { + request = request.project(value.clone()); } - if let Some(value) = matches.get_one::("json-body") { - let body_txt = std::fs::read_to_string(value).unwrap(); - let body_value = - serde_json::from_str::(&body_txt).unwrap(); - request = request.body(body_value); + self.config + .execute_instance_network_interface_delete(matches, &mut request)?; + let result = request.send().await; + match result { + Ok(r) => { + self.config.success_no_item(&r); + Ok(()) + } + Err(r) => { + self.config.error(&r); + Err(anyhow::Error::new(r)) + } } + } - self.config.execute_login_local(matches, &mut request)?; + pub async fn execute_ping(&self, matches: &clap::ArgMatches) -> anyhow::Result<()> { + let mut request = self.client.ping(); + self.config.execute_ping(matches, &mut request)?; let result = request.send().await; match result { Ok(r) => { - self.config.success_no_item(&r); + self.config.success_item(&r); Ok(()) } Err(r) => { @@ -7687,13 +8663,13 @@ impl Cli { } } - pub async fn execute_logout(&self, matches: &clap::ArgMatches) -> anyhow::Result<()> { - let mut request = self.client.logout(); - self.config.execute_logout(matches, &mut request)?; + pub async fn execute_policy_view(&self, matches: &clap::ArgMatches) -> anyhow::Result<()> { + let mut request = self.client.policy_view(); + self.config.execute_policy_view(matches, &mut request)?; let result = request.send().await; match result { Ok(r) => { - self.config.success_no_item(&r); + self.config.success_item(&r); Ok(()) } Err(r) => { @@ -7703,13 +8679,15 @@ impl Cli { } } - pub async fn execute_current_user_view( - &self, - matches: &clap::ArgMatches, - ) -> anyhow::Result<()> { - let mut request = self.client.current_user_view(); - self.config - .execute_current_user_view(matches, &mut request)?; + pub async fn execute_policy_update(&self, matches: &clap::ArgMatches) -> anyhow::Result<()> { + let mut request = self.client.policy_update(); + if let Some(value) = matches.get_one::("json-body") { + let body_txt = std::fs::read_to_string(value).unwrap(); + let body_value = serde_json::from_str::(&body_txt).unwrap(); + request = request.body(body_value); + } + + self.config.execute_policy_update(matches, &mut request)?; let result = request.send().await; match result { Ok(r) => { @@ -7723,22 +8701,18 @@ impl Cli { } } - pub async fn execute_current_user_groups( - &self, - matches: &clap::ArgMatches, - ) -> anyhow::Result<()> { - let mut request = self.client.current_user_groups(); + pub async fn execute_project_list(&self, matches: &clap::ArgMatches) -> anyhow::Result<()> { + let mut request = self.client.project_list(); if let Some(value) = matches.get_one::("limit") { request = request.limit(value.clone()); } - if let Some(value) = matches.get_one::("sort-by") { + if let Some(value) = matches.get_one::("sort-by") { request = request.sort_by(value.clone()); } - self.config - .execute_current_user_groups(matches, &mut request)?; - self.config.list_start::(); + self.config.execute_project_list(matches, &mut request)?; + self.config.list_start::(); let mut stream = futures::StreamExt::take( request.stream(), matches @@ -7752,7 +8726,7 @@ impl Cli { return Err(anyhow::Error::new(r)); } Ok(None) => { - self.config.list_end_success::(); + self.config.list_end_success::(); return Ok(()); } Ok(Some(value)) => { @@ -7762,50 +8736,58 @@ impl Cli { } } - pub async fn execute_current_user_ssh_key_list( - &self, - matches: &clap::ArgMatches, - ) -> anyhow::Result<()> { - let mut request = self.client.current_user_ssh_key_list(); - if let Some(value) = matches.get_one::("limit") { - request = request.limit(value.clone()); + pub async fn execute_project_create(&self, matches: &clap::ArgMatches) -> anyhow::Result<()> { + let mut request = self.client.project_create(); + if let Some(value) = matches.get_one::("description") { + request = request.body_map(|body| body.description(value.clone())) + } + + if let Some(value) = matches.get_one::("name") { + request = request.body_map(|body| body.name(value.clone())) + } + + if let Some(value) = matches.get_one::("json-body") { + let body_txt = std::fs::read_to_string(value).unwrap(); + let body_value = serde_json::from_str::(&body_txt).unwrap(); + request = request.body(body_value); + } + + self.config.execute_project_create(matches, &mut request)?; + let result = request.send().await; + match result { + Ok(r) => { + self.config.success_item(&r); + Ok(()) + } + Err(r) => { + self.config.error(&r); + Err(anyhow::Error::new(r)) + } } + } - if let Some(value) = matches.get_one::("sort-by") { - request = request.sort_by(value.clone()); + pub async fn execute_project_view(&self, matches: &clap::ArgMatches) -> anyhow::Result<()> { + let mut request = self.client.project_view(); + if let Some(value) = matches.get_one::("project") { + request = request.project(value.clone()); } - self.config - .execute_current_user_ssh_key_list(matches, &mut request)?; - self.config.list_start::(); - let mut stream = futures::StreamExt::take( - request.stream(), - matches - .get_one::("limit") - .map_or(usize::MAX, |x| x.get() as usize), - ); - loop { - match futures::TryStreamExt::try_next(&mut stream).await { - Err(r) => { - self.config.list_end_error(&r); - return Err(anyhow::Error::new(r)); - } - Ok(None) => { - self.config.list_end_success::(); - return Ok(()); - } - Ok(Some(value)) => { - self.config.list_item(&value); - } + self.config.execute_project_view(matches, &mut request)?; + let result = request.send().await; + match result { + Ok(r) => { + self.config.success_item(&r); + Ok(()) + } + Err(r) => { + self.config.error(&r); + Err(anyhow::Error::new(r)) } } } - pub async fn execute_current_user_ssh_key_create( - &self, - matches: &clap::ArgMatches, - ) -> anyhow::Result<()> { - let mut request = self.client.current_user_ssh_key_create(); + pub async fn execute_project_update(&self, matches: &clap::ArgMatches) -> anyhow::Result<()> { + let mut request = self.client.project_update(); if let Some(value) = matches.get_one::("description") { request = request.body_map(|body| body.description(value.clone())) } @@ -7814,18 +8796,17 @@ impl Cli { request = request.body_map(|body| body.name(value.clone())) } - if let Some(value) = matches.get_one::("public-key") { - request = request.body_map(|body| body.public_key(value.clone())) + if let Some(value) = matches.get_one::("project") { + request = request.project(value.clone()); } if let Some(value) = matches.get_one::("json-body") { let body_txt = std::fs::read_to_string(value).unwrap(); - let body_value = serde_json::from_str::(&body_txt).unwrap(); + let body_value = serde_json::from_str::(&body_txt).unwrap(); request = request.body(body_value); } - self.config - .execute_current_user_ssh_key_create(matches, &mut request)?; + self.config.execute_project_update(matches, &mut request)?; let result = request.send().await; match result { Ok(r) => { @@ -7839,21 +8820,17 @@ impl Cli { } } - pub async fn execute_current_user_ssh_key_view( - &self, - matches: &clap::ArgMatches, - ) -> anyhow::Result<()> { - let mut request = self.client.current_user_ssh_key_view(); - if let Some(value) = matches.get_one::("ssh-key") { - request = request.ssh_key(value.clone()); + pub async fn execute_project_delete(&self, matches: &clap::ArgMatches) -> anyhow::Result<()> { + let mut request = self.client.project_delete(); + if let Some(value) = matches.get_one::("project") { + request = request.project(value.clone()); } - self.config - .execute_current_user_ssh_key_view(matches, &mut request)?; + self.config.execute_project_delete(matches, &mut request)?; let result = request.send().await; match result { Ok(r) => { - self.config.success_item(&r); + self.config.success_no_item(&r); Ok(()) } Err(r) => { @@ -7863,21 +8840,21 @@ impl Cli { } } - pub async fn execute_current_user_ssh_key_delete( + pub async fn execute_project_policy_view( &self, matches: &clap::ArgMatches, ) -> anyhow::Result<()> { - let mut request = self.client.current_user_ssh_key_delete(); - if let Some(value) = matches.get_one::("ssh-key") { - request = request.ssh_key(value.clone()); + let mut request = self.client.project_policy_view(); + if let Some(value) = matches.get_one::("project") { + request = request.project(value.clone()); } self.config - .execute_current_user_ssh_key_delete(matches, &mut request)?; + .execute_project_policy_view(matches, &mut request)?; let result = request.send().await; match result { Ok(r) => { - self.config.success_no_item(&r); + self.config.success_item(&r); Ok(()) } Err(r) => { @@ -7887,68 +8864,38 @@ impl Cli { } } - pub async fn execute_silo_metric(&self, matches: &clap::ArgMatches) -> anyhow::Result<()> { - let mut request = self.client.silo_metric(); - if let Some(value) = matches.get_one::>("end-time") { - request = request.end_time(value.clone()); - } - - if let Some(value) = matches.get_one::("limit") { - request = request.limit(value.clone()); - } - - if let Some(value) = matches.get_one::("metric-name") { - request = request.metric_name(value.clone()); - } - - if let Some(value) = matches.get_one::("order") { - request = request.order(value.clone()); - } - + pub async fn execute_project_policy_update( + &self, + matches: &clap::ArgMatches, + ) -> anyhow::Result<()> { + let mut request = self.client.project_policy_update(); if let Some(value) = matches.get_one::("project") { request = request.project(value.clone()); } - if let Some(value) = matches.get_one::>("start-time") - { - request = request.start_time(value.clone()); + if let Some(value) = matches.get_one::("json-body") { + let body_txt = std::fs::read_to_string(value).unwrap(); + let body_value = serde_json::from_str::(&body_txt).unwrap(); + request = request.body(body_value); } - self.config.execute_silo_metric(matches, &mut request)?; - self.config.list_start::(); - let mut stream = futures::StreamExt::take( - request.stream(), - matches - .get_one::("limit") - .map_or(usize::MAX, |x| x.get() as usize), - ); - loop { - match futures::TryStreamExt::try_next(&mut stream).await { - Err(r) => { - self.config.list_end_error(&r); - return Err(anyhow::Error::new(r)); - } - Ok(None) => { - self.config - .list_end_success::(); - return Ok(()); - } - Ok(Some(value)) => { - self.config.list_item(&value); - } + self.config + .execute_project_policy_update(matches, &mut request)?; + let result = request.send().await; + match result { + Ok(r) => { + self.config.success_item(&r); + Ok(()) + } + Err(r) => { + self.config.error(&r); + Err(anyhow::Error::new(r)) } } } - pub async fn execute_instance_network_interface_list( - &self, - matches: &clap::ArgMatches, - ) -> anyhow::Result<()> { - let mut request = self.client.instance_network_interface_list(); - if let Some(value) = matches.get_one::("instance") { - request = request.instance(value.clone()); - } - + pub async fn execute_snapshot_list(&self, matches: &clap::ArgMatches) -> anyhow::Result<()> { + let mut request = self.client.snapshot_list(); if let Some(value) = matches.get_one::("limit") { request = request.limit(value.clone()); } @@ -7961,10 +8908,8 @@ impl Cli { request = request.sort_by(value.clone()); } - self.config - .execute_instance_network_interface_list(matches, &mut request)?; - self.config - .list_start::(); + self.config.execute_snapshot_list(matches, &mut request)?; + self.config.list_start::(); let mut stream = futures::StreamExt::take( request.stream(), matches @@ -7978,8 +8923,7 @@ impl Cli { return Err(anyhow::Error::new(r)); } Ok(None) => { - self.config - .list_end_success::(); + self.config.list_end_success::(); return Ok(()); } Ok(Some(value)) => { @@ -7989,21 +8933,14 @@ impl Cli { } } - pub async fn execute_instance_network_interface_create( - &self, - matches: &clap::ArgMatches, - ) -> anyhow::Result<()> { - let mut request = self.client.instance_network_interface_create(); + pub async fn execute_snapshot_create(&self, matches: &clap::ArgMatches) -> anyhow::Result<()> { + let mut request = self.client.snapshot_create(); if let Some(value) = matches.get_one::("description") { request = request.body_map(|body| body.description(value.clone())) } - if let Some(value) = matches.get_one::("instance") { - request = request.instance(value.clone()); - } - - if let Some(value) = matches.get_one::("ip") { - request = request.body_map(|body| body.ip(value.clone())) + if let Some(value) = matches.get_one::("disk") { + request = request.body_map(|body| body.disk(value.clone())) } if let Some(value) = matches.get_one::("name") { @@ -8014,23 +8951,13 @@ impl Cli { request = request.project(value.clone()); } - if let Some(value) = matches.get_one::("subnet-name") { - request = request.body_map(|body| body.subnet_name(value.clone())) - } - - if let Some(value) = matches.get_one::("vpc-name") { - request = request.body_map(|body| body.vpc_name(value.clone())) - } - if let Some(value) = matches.get_one::("json-body") { let body_txt = std::fs::read_to_string(value).unwrap(); - let body_value = - serde_json::from_str::(&body_txt).unwrap(); + let body_value = serde_json::from_str::(&body_txt).unwrap(); request = request.body(body_value); } - self.config - .execute_instance_network_interface_create(matches, &mut request)?; + self.config.execute_snapshot_create(matches, &mut request)?; let result = request.send().await; match result { Ok(r) => { @@ -8044,25 +8971,17 @@ impl Cli { } } - pub async fn execute_instance_network_interface_view( - &self, - matches: &clap::ArgMatches, - ) -> anyhow::Result<()> { - let mut request = self.client.instance_network_interface_view(); - if let Some(value) = matches.get_one::("instance") { - request = request.instance(value.clone()); - } - - if let Some(value) = matches.get_one::("interface") { - request = request.interface(value.clone()); - } - + pub async fn execute_snapshot_view(&self, matches: &clap::ArgMatches) -> anyhow::Result<()> { + let mut request = self.client.snapshot_view(); if let Some(value) = matches.get_one::("project") { request = request.project(value.clone()); } - self.config - .execute_instance_network_interface_view(matches, &mut request)?; + if let Some(value) = matches.get_one::("snapshot") { + request = request.snapshot(value.clone()); + } + + self.config.execute_snapshot_view(matches, &mut request)?; let result = request.send().await; match result { Ok(r) => { @@ -8076,48 +8995,21 @@ impl Cli { } } - pub async fn execute_instance_network_interface_update( - &self, - matches: &clap::ArgMatches, - ) -> anyhow::Result<()> { - let mut request = self.client.instance_network_interface_update(); - if let Some(value) = matches.get_one::("description") { - request = request.body_map(|body| body.description(value.clone())) - } - - if let Some(value) = matches.get_one::("instance") { - request = request.instance(value.clone()); - } - - if let Some(value) = matches.get_one::("interface") { - request = request.interface(value.clone()); - } - - if let Some(value) = matches.get_one::("name") { - request = request.body_map(|body| body.name(value.clone())) - } - - if let Some(value) = matches.get_one::("primary") { - request = request.body_map(|body| body.primary(value.clone())) - } - + pub async fn execute_snapshot_delete(&self, matches: &clap::ArgMatches) -> anyhow::Result<()> { + let mut request = self.client.snapshot_delete(); if let Some(value) = matches.get_one::("project") { request = request.project(value.clone()); } - if let Some(value) = matches.get_one::("json-body") { - let body_txt = std::fs::read_to_string(value).unwrap(); - let body_value = - serde_json::from_str::(&body_txt).unwrap(); - request = request.body(body_value); + if let Some(value) = matches.get_one::("snapshot") { + request = request.snapshot(value.clone()); } - - self.config - .execute_instance_network_interface_update(matches, &mut request)?; + + self.config.execute_snapshot_delete(matches, &mut request)?; let result = request.send().await; match result { Ok(r) => { - self.config.success_item(&r); + self.config.success_no_item(&r); Ok(()) } Err(r) => { @@ -8127,41 +9019,57 @@ impl Cli { } } - pub async fn execute_instance_network_interface_delete( + pub async fn execute_physical_disk_list( &self, matches: &clap::ArgMatches, ) -> anyhow::Result<()> { - let mut request = self.client.instance_network_interface_delete(); - if let Some(value) = matches.get_one::("instance") { - request = request.instance(value.clone()); - } - - if let Some(value) = matches.get_one::("interface") { - request = request.interface(value.clone()); + let mut request = self.client.physical_disk_list(); + if let Some(value) = matches.get_one::("limit") { + request = request.limit(value.clone()); } - if let Some(value) = matches.get_one::("project") { - request = request.project(value.clone()); + if let Some(value) = matches.get_one::("sort-by") { + request = request.sort_by(value.clone()); } self.config - .execute_instance_network_interface_delete(matches, &mut request)?; - let result = request.send().await; - match result { - Ok(r) => { - self.config.success_no_item(&r); - Ok(()) - } - Err(r) => { - self.config.error(&r); - Err(anyhow::Error::new(r)) + .execute_physical_disk_list(matches, &mut request)?; + self.config.list_start::(); + let mut stream = futures::StreamExt::take( + request.stream(), + matches + .get_one::("limit") + .map_or(usize::MAX, |x| x.get() as usize), + ); + loop { + match futures::TryStreamExt::try_next(&mut stream).await { + Err(r) => { + self.config.list_end_error(&r); + return Err(anyhow::Error::new(r)); + } + Ok(None) => { + self.config + .list_end_success::(); + return Ok(()); + } + Ok(Some(value)) => { + self.config.list_item(&value); + } } } } - pub async fn execute_ping(&self, matches: &clap::ArgMatches) -> anyhow::Result<()> { - let mut request = self.client.ping(); - self.config.execute_ping(matches, &mut request)?; + pub async fn execute_physical_disk_view( + &self, + matches: &clap::ArgMatches, + ) -> anyhow::Result<()> { + let mut request = self.client.physical_disk_view(); + if let Some(value) = matches.get_one::("disk-id") { + request = request.disk_id(value.clone()); + } + + self.config + .execute_physical_disk_view(matches, &mut request)?; let result = request.send().await; match result { Ok(r) => { @@ -8175,31 +9083,48 @@ impl Cli { } } - pub async fn execute_policy_view(&self, matches: &clap::ArgMatches) -> anyhow::Result<()> { - let mut request = self.client.policy_view(); - self.config.execute_policy_view(matches, &mut request)?; - let result = request.send().await; - match result { - Ok(r) => { - self.config.success_item(&r); - Ok(()) - } - Err(r) => { - self.config.error(&r); - Err(anyhow::Error::new(r)) + pub async fn execute_rack_list(&self, matches: &clap::ArgMatches) -> anyhow::Result<()> { + let mut request = self.client.rack_list(); + if let Some(value) = matches.get_one::("limit") { + request = request.limit(value.clone()); + } + + if let Some(value) = matches.get_one::("sort-by") { + request = request.sort_by(value.clone()); + } + + self.config.execute_rack_list(matches, &mut request)?; + self.config.list_start::(); + let mut stream = futures::StreamExt::take( + request.stream(), + matches + .get_one::("limit") + .map_or(usize::MAX, |x| x.get() as usize), + ); + loop { + match futures::TryStreamExt::try_next(&mut stream).await { + Err(r) => { + self.config.list_end_error(&r); + return Err(anyhow::Error::new(r)); + } + Ok(None) => { + self.config.list_end_success::(); + return Ok(()); + } + Ok(Some(value)) => { + self.config.list_item(&value); + } } } } - pub async fn execute_policy_update(&self, matches: &clap::ArgMatches) -> anyhow::Result<()> { - let mut request = self.client.policy_update(); - if let Some(value) = matches.get_one::("json-body") { - let body_txt = std::fs::read_to_string(value).unwrap(); - let body_value = serde_json::from_str::(&body_txt).unwrap(); - request = request.body(body_value); + pub async fn execute_rack_view(&self, matches: &clap::ArgMatches) -> anyhow::Result<()> { + let mut request = self.client.rack_view(); + if let Some(value) = matches.get_one::("rack-id") { + request = request.rack_id(value.clone()); } - self.config.execute_policy_update(matches, &mut request)?; + self.config.execute_rack_view(matches, &mut request)?; let result = request.send().await; match result { Ok(r) => { @@ -8213,18 +9138,18 @@ impl Cli { } } - pub async fn execute_project_list(&self, matches: &clap::ArgMatches) -> anyhow::Result<()> { - let mut request = self.client.project_list(); + pub async fn execute_sled_list(&self, matches: &clap::ArgMatches) -> anyhow::Result<()> { + let mut request = self.client.sled_list(); if let Some(value) = matches.get_one::("limit") { request = request.limit(value.clone()); } - if let Some(value) = matches.get_one::("sort-by") { + if let Some(value) = matches.get_one::("sort-by") { request = request.sort_by(value.clone()); } - self.config.execute_project_list(matches, &mut request)?; - self.config.list_start::(); + self.config.execute_sled_list(matches, &mut request)?; + self.config.list_start::(); let mut stream = futures::StreamExt::take( request.stream(), matches @@ -8238,7 +9163,7 @@ impl Cli { return Err(anyhow::Error::new(r)); } Ok(None) => { - self.config.list_end_success::(); + self.config.list_end_success::(); return Ok(()); } Ok(Some(value)) => { @@ -8248,23 +9173,23 @@ impl Cli { } } - pub async fn execute_project_create(&self, matches: &clap::ArgMatches) -> anyhow::Result<()> { - let mut request = self.client.project_create(); - if let Some(value) = matches.get_one::("description") { - request = request.body_map(|body| body.description(value.clone())) + pub async fn execute_sled_add(&self, matches: &clap::ArgMatches) -> anyhow::Result<()> { + let mut request = self.client.sled_add(); + if let Some(value) = matches.get_one::("part") { + request = request.body_map(|body| body.part(value.clone())) } - if let Some(value) = matches.get_one::("name") { - request = request.body_map(|body| body.name(value.clone())) + if let Some(value) = matches.get_one::("serial") { + request = request.body_map(|body| body.serial(value.clone())) } if let Some(value) = matches.get_one::("json-body") { let body_txt = std::fs::read_to_string(value).unwrap(); - let body_value = serde_json::from_str::(&body_txt).unwrap(); + let body_value = serde_json::from_str::(&body_txt).unwrap(); request = request.body(body_value); } - self.config.execute_project_create(matches, &mut request)?; + self.config.execute_sled_add(matches, &mut request)?; let result = request.send().await; match result { Ok(r) => { @@ -8278,13 +9203,13 @@ impl Cli { } } - pub async fn execute_project_view(&self, matches: &clap::ArgMatches) -> anyhow::Result<()> { - let mut request = self.client.project_view(); - if let Some(value) = matches.get_one::("project") { - request = request.project(value.clone()); + pub async fn execute_sled_view(&self, matches: &clap::ArgMatches) -> anyhow::Result<()> { + let mut request = self.client.sled_view(); + if let Some(value) = matches.get_one::("sled-id") { + request = request.sled_id(value.clone()); } - self.config.execute_project_view(matches, &mut request)?; + self.config.execute_sled_view(matches, &mut request)?; let result = request.send().await; match result { Ok(r) => { @@ -8298,71 +9223,116 @@ impl Cli { } } - pub async fn execute_project_update(&self, matches: &clap::ArgMatches) -> anyhow::Result<()> { - let mut request = self.client.project_update(); - if let Some(value) = matches.get_one::("description") { - request = request.body_map(|body| body.description(value.clone())) - } - - if let Some(value) = matches.get_one::("name") { - request = request.body_map(|body| body.name(value.clone())) + pub async fn execute_sled_physical_disk_list( + &self, + matches: &clap::ArgMatches, + ) -> anyhow::Result<()> { + let mut request = self.client.sled_physical_disk_list(); + if let Some(value) = matches.get_one::("limit") { + request = request.limit(value.clone()); } - if let Some(value) = matches.get_one::("project") { - request = request.project(value.clone()); + if let Some(value) = matches.get_one::("sled-id") { + request = request.sled_id(value.clone()); } - if let Some(value) = matches.get_one::("json-body") { - let body_txt = std::fs::read_to_string(value).unwrap(); - let body_value = serde_json::from_str::(&body_txt).unwrap(); - request = request.body(body_value); + if let Some(value) = matches.get_one::("sort-by") { + request = request.sort_by(value.clone()); } - self.config.execute_project_update(matches, &mut request)?; - let result = request.send().await; - match result { - Ok(r) => { - self.config.success_item(&r); - Ok(()) - } - Err(r) => { - self.config.error(&r); - Err(anyhow::Error::new(r)) + self.config + .execute_sled_physical_disk_list(matches, &mut request)?; + self.config.list_start::(); + let mut stream = futures::StreamExt::take( + request.stream(), + matches + .get_one::("limit") + .map_or(usize::MAX, |x| x.get() as usize), + ); + loop { + match futures::TryStreamExt::try_next(&mut stream).await { + Err(r) => { + self.config.list_end_error(&r); + return Err(anyhow::Error::new(r)); + } + Ok(None) => { + self.config + .list_end_success::(); + return Ok(()); + } + Ok(Some(value)) => { + self.config.list_item(&value); + } } } } - pub async fn execute_project_delete(&self, matches: &clap::ArgMatches) -> anyhow::Result<()> { - let mut request = self.client.project_delete(); - if let Some(value) = matches.get_one::("project") { - request = request.project(value.clone()); + pub async fn execute_sled_instance_list( + &self, + matches: &clap::ArgMatches, + ) -> anyhow::Result<()> { + let mut request = self.client.sled_instance_list(); + if let Some(value) = matches.get_one::("limit") { + request = request.limit(value.clone()); } - self.config.execute_project_delete(matches, &mut request)?; - let result = request.send().await; - match result { - Ok(r) => { - self.config.success_no_item(&r); - Ok(()) - } - Err(r) => { - self.config.error(&r); - Err(anyhow::Error::new(r)) + if let Some(value) = matches.get_one::("sled-id") { + request = request.sled_id(value.clone()); + } + + if let Some(value) = matches.get_one::("sort-by") { + request = request.sort_by(value.clone()); + } + + self.config + .execute_sled_instance_list(matches, &mut request)?; + self.config.list_start::(); + let mut stream = futures::StreamExt::take( + request.stream(), + matches + .get_one::("limit") + .map_or(usize::MAX, |x| x.get() as usize), + ); + loop { + match futures::TryStreamExt::try_next(&mut stream).await { + Err(r) => { + self.config.list_end_error(&r); + return Err(anyhow::Error::new(r)); + } + Ok(None) => { + self.config + .list_end_success::(); + return Ok(()); + } + Ok(Some(value)) => { + self.config.list_item(&value); + } } } } - pub async fn execute_project_policy_view( + pub async fn execute_sled_set_provision_policy( &self, matches: &clap::ArgMatches, ) -> anyhow::Result<()> { - let mut request = self.client.project_policy_view(); - if let Some(value) = matches.get_one::("project") { - request = request.project(value.clone()); + let mut request = self.client.sled_set_provision_policy(); + if let Some(value) = matches.get_one::("sled-id") { + request = request.sled_id(value.clone()); + } + + if let Some(value) = matches.get_one::("state") { + request = request.body_map(|body| body.state(value.clone())) + } + + if let Some(value) = matches.get_one::("json-body") { + let body_txt = std::fs::read_to_string(value).unwrap(); + let body_value = + serde_json::from_str::(&body_txt).unwrap(); + request = request.body(body_value); } self.config - .execute_project_policy_view(matches, &mut request)?; + .execute_sled_set_provision_policy(matches, &mut request)?; let result = request.send().await; match result { Ok(r) => { @@ -8376,52 +9346,63 @@ impl Cli { } } - pub async fn execute_project_policy_update( + pub async fn execute_sled_list_uninitialized( &self, matches: &clap::ArgMatches, ) -> anyhow::Result<()> { - let mut request = self.client.project_policy_update(); - if let Some(value) = matches.get_one::("project") { - request = request.project(value.clone()); - } - - if let Some(value) = matches.get_one::("json-body") { - let body_txt = std::fs::read_to_string(value).unwrap(); - let body_value = serde_json::from_str::(&body_txt).unwrap(); - request = request.body(body_value); + let mut request = self.client.sled_list_uninitialized(); + if let Some(value) = matches.get_one::("limit") { + request = request.limit(value.clone()); } self.config - .execute_project_policy_update(matches, &mut request)?; - let result = request.send().await; - match result { - Ok(r) => { - self.config.success_item(&r); - Ok(()) - } - Err(r) => { - self.config.error(&r); - Err(anyhow::Error::new(r)) + .execute_sled_list_uninitialized(matches, &mut request)?; + self.config + .list_start::(); + let mut stream = futures::StreamExt::take( + request.stream(), + matches + .get_one::("limit") + .map_or(usize::MAX, |x| x.get() as usize), + ); + loop { + match futures::TryStreamExt::try_next(&mut stream).await { + Err(r) => { + self.config.list_end_error(&r); + return Err(anyhow::Error::new(r)); + } + Ok(None) => { + self.config + .list_end_success::(); + return Ok(()); + } + Ok(Some(value)) => { + self.config.list_item(&value); + } } } } - pub async fn execute_snapshot_list(&self, matches: &clap::ArgMatches) -> anyhow::Result<()> { - let mut request = self.client.snapshot_list(); + pub async fn execute_networking_switch_port_list( + &self, + matches: &clap::ArgMatches, + ) -> anyhow::Result<()> { + let mut request = self.client.networking_switch_port_list(); if let Some(value) = matches.get_one::("limit") { request = request.limit(value.clone()); } - if let Some(value) = matches.get_one::("project") { - request = request.project(value.clone()); + if let Some(value) = matches.get_one::("sort-by") { + request = request.sort_by(value.clone()); } - if let Some(value) = matches.get_one::("sort-by") { - request = request.sort_by(value.clone()); + if let Some(value) = matches.get_one::("switch-port-id") { + request = request.switch_port_id(value.clone()); } - self.config.execute_snapshot_list(matches, &mut request)?; - self.config.list_start::(); + self.config + .execute_networking_switch_port_list(matches, &mut request)?; + self.config.list_start::(); let mut stream = futures::StreamExt::take( request.stream(), matches @@ -8435,7 +9416,8 @@ impl Cli { return Err(anyhow::Error::new(r)); } Ok(None) => { - self.config.list_end_success::(); + self.config + .list_end_success::(); return Ok(()); } Ok(Some(value)) => { @@ -8445,35 +9427,40 @@ impl Cli { } } - pub async fn execute_snapshot_create(&self, matches: &clap::ArgMatches) -> anyhow::Result<()> { - let mut request = self.client.snapshot_create(); - if let Some(value) = matches.get_one::("description") { - request = request.body_map(|body| body.description(value.clone())) + pub async fn execute_networking_switch_port_apply_settings( + &self, + matches: &clap::ArgMatches, + ) -> anyhow::Result<()> { + let mut request = self.client.networking_switch_port_apply_settings(); + if let Some(value) = matches.get_one::("port") { + request = request.port(value.clone()); } - if let Some(value) = matches.get_one::("disk") { - request = request.body_map(|body| body.disk(value.clone())) + if let Some(value) = matches.get_one::("port-settings") { + request = request.body_map(|body| body.port_settings(value.clone())) } - if let Some(value) = matches.get_one::("name") { - request = request.body_map(|body| body.name(value.clone())) + if let Some(value) = matches.get_one::("rack-id") { + request = request.rack_id(value.clone()); } - if let Some(value) = matches.get_one::("project") { - request = request.project(value.clone()); + if let Some(value) = matches.get_one::("switch-location") { + request = request.switch_location(value.clone()); } if let Some(value) = matches.get_one::("json-body") { let body_txt = std::fs::read_to_string(value).unwrap(); - let body_value = serde_json::from_str::(&body_txt).unwrap(); + let body_value = + serde_json::from_str::(&body_txt).unwrap(); request = request.body(body_value); } - self.config.execute_snapshot_create(matches, &mut request)?; + self.config + .execute_networking_switch_port_apply_settings(matches, &mut request)?; let result = request.send().await; match result { Ok(r) => { - self.config.success_item(&r); + self.config.success_no_item(&r); Ok(()) } Err(r) => { @@ -8483,41 +9470,25 @@ impl Cli { } } - pub async fn execute_snapshot_view(&self, matches: &clap::ArgMatches) -> anyhow::Result<()> { - let mut request = self.client.snapshot_view(); - if let Some(value) = matches.get_one::("project") { - request = request.project(value.clone()); - } - - if let Some(value) = matches.get_one::("snapshot") { - request = request.snapshot(value.clone()); - } - - self.config.execute_snapshot_view(matches, &mut request)?; - let result = request.send().await; - match result { - Ok(r) => { - self.config.success_item(&r); - Ok(()) - } - Err(r) => { - self.config.error(&r); - Err(anyhow::Error::new(r)) - } + pub async fn execute_networking_switch_port_clear_settings( + &self, + matches: &clap::ArgMatches, + ) -> anyhow::Result<()> { + let mut request = self.client.networking_switch_port_clear_settings(); + if let Some(value) = matches.get_one::("port") { + request = request.port(value.clone()); } - } - pub async fn execute_snapshot_delete(&self, matches: &clap::ArgMatches) -> anyhow::Result<()> { - let mut request = self.client.snapshot_delete(); - if let Some(value) = matches.get_one::("project") { - request = request.project(value.clone()); + if let Some(value) = matches.get_one::("rack-id") { + request = request.rack_id(value.clone()); } - if let Some(value) = matches.get_one::("snapshot") { - request = request.snapshot(value.clone()); + if let Some(value) = matches.get_one::("switch-location") { + request = request.switch_location(value.clone()); } - self.config.execute_snapshot_delete(matches, &mut request)?; + self.config + .execute_networking_switch_port_clear_settings(matches, &mut request)?; let result = request.send().await; match result { Ok(r) => { @@ -8531,57 +9502,25 @@ impl Cli { } } - pub async fn execute_physical_disk_list( + pub async fn execute_networking_switch_port_status( &self, matches: &clap::ArgMatches, ) -> anyhow::Result<()> { - let mut request = self.client.physical_disk_list(); - if let Some(value) = matches.get_one::("limit") { - request = request.limit(value.clone()); - } - - if let Some(value) = matches.get_one::("sort-by") { - request = request.sort_by(value.clone()); + let mut request = self.client.networking_switch_port_status(); + if let Some(value) = matches.get_one::("port") { + request = request.port(value.clone()); } - self.config - .execute_physical_disk_list(matches, &mut request)?; - self.config.list_start::(); - let mut stream = futures::StreamExt::take( - request.stream(), - matches - .get_one::("limit") - .map_or(usize::MAX, |x| x.get() as usize), - ); - loop { - match futures::TryStreamExt::try_next(&mut stream).await { - Err(r) => { - self.config.list_end_error(&r); - return Err(anyhow::Error::new(r)); - } - Ok(None) => { - self.config - .list_end_success::(); - return Ok(()); - } - Ok(Some(value)) => { - self.config.list_item(&value); - } - } + if let Some(value) = matches.get_one::("rack-id") { + request = request.rack_id(value.clone()); } - } - pub async fn execute_physical_disk_view( - &self, - matches: &clap::ArgMatches, - ) -> anyhow::Result<()> { - let mut request = self.client.physical_disk_view(); - if let Some(value) = matches.get_one::("disk-id") { - request = request.disk_id(value.clone()); + if let Some(value) = matches.get_one::("switch-location") { + request = request.switch_location(value.clone()); } self.config - .execute_physical_disk_view(matches, &mut request)?; + .execute_networking_switch_port_status(matches, &mut request)?; let result = request.send().await; match result { Ok(r) => { @@ -8595,8 +9534,8 @@ impl Cli { } } - pub async fn execute_rack_list(&self, matches: &clap::ArgMatches) -> anyhow::Result<()> { - let mut request = self.client.rack_list(); + pub async fn execute_switch_list(&self, matches: &clap::ArgMatches) -> anyhow::Result<()> { + let mut request = self.client.switch_list(); if let Some(value) = matches.get_one::("limit") { request = request.limit(value.clone()); } @@ -8605,8 +9544,8 @@ impl Cli { request = request.sort_by(value.clone()); } - self.config.execute_rack_list(matches, &mut request)?; - self.config.list_start::(); + self.config.execute_switch_list(matches, &mut request)?; + self.config.list_start::(); let mut stream = futures::StreamExt::take( request.stream(), matches @@ -8620,7 +9559,7 @@ impl Cli { return Err(anyhow::Error::new(r)); } Ok(None) => { - self.config.list_end_success::(); + self.config.list_end_success::(); return Ok(()); } Ok(Some(value)) => { @@ -8630,13 +9569,13 @@ impl Cli { } } - pub async fn execute_rack_view(&self, matches: &clap::ArgMatches) -> anyhow::Result<()> { - let mut request = self.client.rack_view(); - if let Some(value) = matches.get_one::("rack-id") { - request = request.rack_id(value.clone()); + pub async fn execute_switch_view(&self, matches: &clap::ArgMatches) -> anyhow::Result<()> { + let mut request = self.client.switch_view(); + if let Some(value) = matches.get_one::("switch-id") { + request = request.switch_id(value.clone()); } - self.config.execute_rack_view(matches, &mut request)?; + self.config.execute_switch_view(matches, &mut request)?; let result = request.send().await; match result { Ok(r) => { @@ -8650,18 +9589,27 @@ impl Cli { } } - pub async fn execute_sled_list(&self, matches: &clap::ArgMatches) -> anyhow::Result<()> { - let mut request = self.client.sled_list(); + pub async fn execute_silo_identity_provider_list( + &self, + matches: &clap::ArgMatches, + ) -> anyhow::Result<()> { + let mut request = self.client.silo_identity_provider_list(); if let Some(value) = matches.get_one::("limit") { request = request.limit(value.clone()); } - if let Some(value) = matches.get_one::("sort-by") { + if let Some(value) = matches.get_one::("silo") { + request = request.silo(value.clone()); + } + + if let Some(value) = matches.get_one::("sort-by") { request = request.sort_by(value.clone()); } - self.config.execute_sled_list(matches, &mut request)?; - self.config.list_start::(); + self.config + .execute_silo_identity_provider_list(matches, &mut request)?; + self.config + .list_start::(); let mut stream = futures::StreamExt::take( request.stream(), matches @@ -8675,7 +9623,8 @@ impl Cli { return Err(anyhow::Error::new(r)); } Ok(None) => { - self.config.list_end_success::(); + self.config + .list_end_success::(); return Ok(()); } Ok(Some(value)) => { @@ -8685,23 +9634,27 @@ impl Cli { } } - pub async fn execute_sled_add(&self, matches: &clap::ArgMatches) -> anyhow::Result<()> { - let mut request = self.client.sled_add(); - if let Some(value) = matches.get_one::("part") { - request = request.body_map(|body| body.part(value.clone())) + pub async fn execute_local_idp_user_create( + &self, + matches: &clap::ArgMatches, + ) -> anyhow::Result<()> { + let mut request = self.client.local_idp_user_create(); + if let Some(value) = matches.get_one::("external-id") { + request = request.body_map(|body| body.external_id(value.clone())) } - if let Some(value) = matches.get_one::("serial") { - request = request.body_map(|body| body.serial(value.clone())) + if let Some(value) = matches.get_one::("silo") { + request = request.silo(value.clone()); } if let Some(value) = matches.get_one::("json-body") { let body_txt = std::fs::read_to_string(value).unwrap(); - let body_value = serde_json::from_str::(&body_txt).unwrap(); + let body_value = serde_json::from_str::(&body_txt).unwrap(); request = request.body(body_value); } - self.config.execute_sled_add(matches, &mut request)?; + self.config + .execute_local_idp_user_create(matches, &mut request)?; let result = request.send().await; match result { Ok(r) => { @@ -8715,17 +9668,25 @@ impl Cli { } } - pub async fn execute_sled_view(&self, matches: &clap::ArgMatches) -> anyhow::Result<()> { - let mut request = self.client.sled_view(); - if let Some(value) = matches.get_one::("sled-id") { - request = request.sled_id(value.clone()); + pub async fn execute_local_idp_user_delete( + &self, + matches: &clap::ArgMatches, + ) -> anyhow::Result<()> { + let mut request = self.client.local_idp_user_delete(); + if let Some(value) = matches.get_one::("silo") { + request = request.silo(value.clone()); } - self.config.execute_sled_view(matches, &mut request)?; + if let Some(value) = matches.get_one::("user-id") { + request = request.user_id(value.clone()); + } + + self.config + .execute_local_idp_user_delete(matches, &mut request)?; let result = request.send().await; match result { Ok(r) => { - self.config.success_item(&r); + self.config.success_no_item(&r); Ok(()) } Err(r) => { @@ -8735,116 +9696,90 @@ impl Cli { } } - pub async fn execute_sled_physical_disk_list( + pub async fn execute_local_idp_user_set_password( &self, matches: &clap::ArgMatches, ) -> anyhow::Result<()> { - let mut request = self.client.sled_physical_disk_list(); - if let Some(value) = matches.get_one::("limit") { - request = request.limit(value.clone()); + let mut request = self.client.local_idp_user_set_password(); + if let Some(value) = matches.get_one::("silo") { + request = request.silo(value.clone()); } - if let Some(value) = matches.get_one::("sled-id") { - request = request.sled_id(value.clone()); + if let Some(value) = matches.get_one::("user-id") { + request = request.user_id(value.clone()); } - if let Some(value) = matches.get_one::("sort-by") { - request = request.sort_by(value.clone()); + if let Some(value) = matches.get_one::("json-body") { + let body_txt = std::fs::read_to_string(value).unwrap(); + let body_value = serde_json::from_str::(&body_txt).unwrap(); + request = request.body(body_value); } self.config - .execute_sled_physical_disk_list(matches, &mut request)?; - self.config.list_start::(); - let mut stream = futures::StreamExt::take( - request.stream(), - matches - .get_one::("limit") - .map_or(usize::MAX, |x| x.get() as usize), - ); - loop { - match futures::TryStreamExt::try_next(&mut stream).await { - Err(r) => { - self.config.list_end_error(&r); - return Err(anyhow::Error::new(r)); - } - Ok(None) => { - self.config - .list_end_success::(); - return Ok(()); - } - Ok(Some(value)) => { - self.config.list_item(&value); - } + .execute_local_idp_user_set_password(matches, &mut request)?; + let result = request.send().await; + match result { + Ok(r) => { + self.config.success_no_item(&r); + Ok(()) + } + Err(r) => { + self.config.error(&r); + Err(anyhow::Error::new(r)) } } } - pub async fn execute_sled_instance_list( + pub async fn execute_saml_identity_provider_create( &self, matches: &clap::ArgMatches, ) -> anyhow::Result<()> { - let mut request = self.client.sled_instance_list(); - if let Some(value) = matches.get_one::("limit") { - request = request.limit(value.clone()); + let mut request = self.client.saml_identity_provider_create(); + if let Some(value) = matches.get_one::("acs-url") { + request = request.body_map(|body| body.acs_url(value.clone())) } - if let Some(value) = matches.get_one::("sled-id") { - request = request.sled_id(value.clone()); + if let Some(value) = matches.get_one::("description") { + request = request.body_map(|body| body.description(value.clone())) } - if let Some(value) = matches.get_one::("sort-by") { - request = request.sort_by(value.clone()); + if let Some(value) = matches.get_one::("group-attribute-name") { + request = request.body_map(|body| body.group_attribute_name(value.clone())) } - self.config - .execute_sled_instance_list(matches, &mut request)?; - self.config.list_start::(); - let mut stream = futures::StreamExt::take( - request.stream(), - matches - .get_one::("limit") - .map_or(usize::MAX, |x| x.get() as usize), - ); - loop { - match futures::TryStreamExt::try_next(&mut stream).await { - Err(r) => { - self.config.list_end_error(&r); - return Err(anyhow::Error::new(r)); - } - Ok(None) => { - self.config - .list_end_success::(); - return Ok(()); - } - Ok(Some(value)) => { - self.config.list_item(&value); - } - } + if let Some(value) = matches.get_one::("idp-entity-id") { + request = request.body_map(|body| body.idp_entity_id(value.clone())) } - } - pub async fn execute_sled_set_provision_policy( - &self, - matches: &clap::ArgMatches, - ) -> anyhow::Result<()> { - let mut request = self.client.sled_set_provision_policy(); - if let Some(value) = matches.get_one::("sled-id") { - request = request.sled_id(value.clone()); + if let Some(value) = matches.get_one::("name") { + request = request.body_map(|body| body.name(value.clone())) } - if let Some(value) = matches.get_one::("state") { - request = request.body_map(|body| body.state(value.clone())) + if let Some(value) = matches.get_one::("silo") { + request = request.silo(value.clone()); + } + + if let Some(value) = matches.get_one::("slo-url") { + request = request.body_map(|body| body.slo_url(value.clone())) + } + + if let Some(value) = matches.get_one::("sp-client-id") { + request = request.body_map(|body| body.sp_client_id(value.clone())) + } + + if let Some(value) = matches.get_one::("technical-contact-email") { + request = request.body_map(|body| body.technical_contact_email(value.clone())) } if let Some(value) = matches.get_one::("json-body") { let body_txt = std::fs::read_to_string(value).unwrap(); let body_value = - serde_json::from_str::(&body_txt).unwrap(); + serde_json::from_str::(&body_txt).unwrap(); request = request.body(body_value); } self.config - .execute_sled_set_provision_policy(matches, &mut request)?; + .execute_saml_identity_provider_create(matches, &mut request)?; let result = request.send().await; match result { Ok(r) => { @@ -8858,63 +9793,46 @@ impl Cli { } } - pub async fn execute_sled_list_uninitialized( + pub async fn execute_saml_identity_provider_view( &self, matches: &clap::ArgMatches, ) -> anyhow::Result<()> { - let mut request = self.client.sled_list_uninitialized(); - if let Some(value) = matches.get_one::("limit") { - request = request.limit(value.clone()); + let mut request = self.client.saml_identity_provider_view(); + if let Some(value) = matches.get_one::("provider") { + request = request.provider(value.clone()); + } + + if let Some(value) = matches.get_one::("silo") { + request = request.silo(value.clone()); } self.config - .execute_sled_list_uninitialized(matches, &mut request)?; - self.config - .list_start::(); - let mut stream = futures::StreamExt::take( - request.stream(), - matches - .get_one::("limit") - .map_or(usize::MAX, |x| x.get() as usize), - ); - loop { - match futures::TryStreamExt::try_next(&mut stream).await { - Err(r) => { - self.config.list_end_error(&r); - return Err(anyhow::Error::new(r)); - } - Ok(None) => { - self.config - .list_end_success::(); - return Ok(()); - } - Ok(Some(value)) => { - self.config.list_item(&value); - } + .execute_saml_identity_provider_view(matches, &mut request)?; + let result = request.send().await; + match result { + Ok(r) => { + self.config.success_item(&r); + Ok(()) + } + Err(r) => { + self.config.error(&r); + Err(anyhow::Error::new(r)) } } } - pub async fn execute_networking_switch_port_list( - &self, - matches: &clap::ArgMatches, - ) -> anyhow::Result<()> { - let mut request = self.client.networking_switch_port_list(); + pub async fn execute_ip_pool_list(&self, matches: &clap::ArgMatches) -> anyhow::Result<()> { + let mut request = self.client.ip_pool_list(); if let Some(value) = matches.get_one::("limit") { request = request.limit(value.clone()); } - if let Some(value) = matches.get_one::("sort-by") { + if let Some(value) = matches.get_one::("sort-by") { request = request.sort_by(value.clone()); } - if let Some(value) = matches.get_one::("switch-port-id") { - request = request.switch_port_id(value.clone()); - } - - self.config - .execute_networking_switch_port_list(matches, &mut request)?; - self.config.list_start::(); + self.config.execute_ip_pool_list(matches, &mut request)?; + self.config.list_start::(); let mut stream = futures::StreamExt::take( request.stream(), matches @@ -8928,8 +9846,7 @@ impl Cli { return Err(anyhow::Error::new(r)); } Ok(None) => { - self.config - .list_end_success::(); + self.config.list_end_success::(); return Ok(()); } Ok(Some(value)) => { @@ -8939,40 +9856,27 @@ impl Cli { } } - pub async fn execute_networking_switch_port_apply_settings( - &self, - matches: &clap::ArgMatches, - ) -> anyhow::Result<()> { - let mut request = self.client.networking_switch_port_apply_settings(); - if let Some(value) = matches.get_one::("port") { - request = request.port(value.clone()); - } - - if let Some(value) = matches.get_one::("port-settings") { - request = request.body_map(|body| body.port_settings(value.clone())) - } - - if let Some(value) = matches.get_one::("rack-id") { - request = request.rack_id(value.clone()); + pub async fn execute_ip_pool_create(&self, matches: &clap::ArgMatches) -> anyhow::Result<()> { + let mut request = self.client.ip_pool_create(); + if let Some(value) = matches.get_one::("description") { + request = request.body_map(|body| body.description(value.clone())) } - if let Some(value) = matches.get_one::("switch-location") { - request = request.switch_location(value.clone()); + if let Some(value) = matches.get_one::("name") { + request = request.body_map(|body| body.name(value.clone())) } if let Some(value) = matches.get_one::("json-body") { let body_txt = std::fs::read_to_string(value).unwrap(); - let body_value = - serde_json::from_str::(&body_txt).unwrap(); + let body_value = serde_json::from_str::(&body_txt).unwrap(); request = request.body(body_value); } - self.config - .execute_networking_switch_port_apply_settings(matches, &mut request)?; + self.config.execute_ip_pool_create(matches, &mut request)?; let result = request.send().await; match result { Ok(r) => { - self.config.success_no_item(&r); + self.config.success_item(&r); Ok(()) } Err(r) => { @@ -8982,29 +9886,17 @@ impl Cli { } } - pub async fn execute_networking_switch_port_clear_settings( - &self, - matches: &clap::ArgMatches, - ) -> anyhow::Result<()> { - let mut request = self.client.networking_switch_port_clear_settings(); - if let Some(value) = matches.get_one::("port") { - request = request.port(value.clone()); - } - - if let Some(value) = matches.get_one::("rack-id") { - request = request.rack_id(value.clone()); - } - - if let Some(value) = matches.get_one::("switch-location") { - request = request.switch_location(value.clone()); + pub async fn execute_ip_pool_view(&self, matches: &clap::ArgMatches) -> anyhow::Result<()> { + let mut request = self.client.ip_pool_view(); + if let Some(value) = matches.get_one::("pool") { + request = request.pool(value.clone()); } - self.config - .execute_networking_switch_port_clear_settings(matches, &mut request)?; + self.config.execute_ip_pool_view(matches, &mut request)?; let result = request.send().await; match result { Ok(r) => { - self.config.success_no_item(&r); + self.config.success_item(&r); Ok(()) } Err(r) => { @@ -9014,25 +9906,27 @@ impl Cli { } } - pub async fn execute_networking_switch_port_status( - &self, - matches: &clap::ArgMatches, - ) -> anyhow::Result<()> { - let mut request = self.client.networking_switch_port_status(); - if let Some(value) = matches.get_one::("port") { - request = request.port(value.clone()); + pub async fn execute_ip_pool_update(&self, matches: &clap::ArgMatches) -> anyhow::Result<()> { + let mut request = self.client.ip_pool_update(); + if let Some(value) = matches.get_one::("description") { + request = request.body_map(|body| body.description(value.clone())) } - if let Some(value) = matches.get_one::("rack-id") { - request = request.rack_id(value.clone()); + if let Some(value) = matches.get_one::("name") { + request = request.body_map(|body| body.name(value.clone())) } - if let Some(value) = matches.get_one::("switch-location") { - request = request.switch_location(value.clone()); + if let Some(value) = matches.get_one::("pool") { + request = request.pool(value.clone()); } - self.config - .execute_networking_switch_port_status(matches, &mut request)?; + if let Some(value) = matches.get_one::("json-body") { + let body_txt = std::fs::read_to_string(value).unwrap(); + let body_value = serde_json::from_str::(&body_txt).unwrap(); + request = request.body(body_value); + } + + self.config.execute_ip_pool_update(matches, &mut request)?; let result = request.send().await; match result { Ok(r) => { @@ -9046,18 +9940,42 @@ impl Cli { } } - pub async fn execute_switch_list(&self, matches: &clap::ArgMatches) -> anyhow::Result<()> { - let mut request = self.client.switch_list(); + pub async fn execute_ip_pool_delete(&self, matches: &clap::ArgMatches) -> anyhow::Result<()> { + let mut request = self.client.ip_pool_delete(); + if let Some(value) = matches.get_one::("pool") { + request = request.pool(value.clone()); + } + + self.config.execute_ip_pool_delete(matches, &mut request)?; + let result = request.send().await; + match result { + Ok(r) => { + self.config.success_no_item(&r); + Ok(()) + } + Err(r) => { + self.config.error(&r); + Err(anyhow::Error::new(r)) + } + } + } + + pub async fn execute_ip_pool_range_list( + &self, + matches: &clap::ArgMatches, + ) -> anyhow::Result<()> { + let mut request = self.client.ip_pool_range_list(); if let Some(value) = matches.get_one::("limit") { request = request.limit(value.clone()); } - if let Some(value) = matches.get_one::("sort-by") { - request = request.sort_by(value.clone()); + if let Some(value) = matches.get_one::("pool") { + request = request.pool(value.clone()); } - self.config.execute_switch_list(matches, &mut request)?; - self.config.list_start::(); + self.config + .execute_ip_pool_range_list(matches, &mut request)?; + self.config.list_start::(); let mut stream = futures::StreamExt::take( request.stream(), matches @@ -9071,7 +9989,8 @@ impl Cli { return Err(anyhow::Error::new(r)); } Ok(None) => { - self.config.list_end_success::(); + self.config + .list_end_success::(); return Ok(()); } Ok(Some(value)) => { @@ -9081,13 +10000,23 @@ impl Cli { } } - pub async fn execute_switch_view(&self, matches: &clap::ArgMatches) -> anyhow::Result<()> { - let mut request = self.client.switch_view(); - if let Some(value) = matches.get_one::("switch-id") { - request = request.switch_id(value.clone()); + pub async fn execute_ip_pool_range_add( + &self, + matches: &clap::ArgMatches, + ) -> anyhow::Result<()> { + let mut request = self.client.ip_pool_range_add(); + if let Some(value) = matches.get_one::("pool") { + request = request.pool(value.clone()); } - self.config.execute_switch_view(matches, &mut request)?; + if let Some(value) = matches.get_one::("json-body") { + let body_txt = std::fs::read_to_string(value).unwrap(); + let body_value = serde_json::from_str::(&body_txt).unwrap(); + request = request.body(body_value); + } + + self.config + .execute_ip_pool_range_add(matches, &mut request)?; let result = request.send().await; match result { Ok(r) => { @@ -9101,27 +10030,56 @@ impl Cli { } } - pub async fn execute_silo_identity_provider_list( + pub async fn execute_ip_pool_range_remove( &self, matches: &clap::ArgMatches, ) -> anyhow::Result<()> { - let mut request = self.client.silo_identity_provider_list(); + let mut request = self.client.ip_pool_range_remove(); + if let Some(value) = matches.get_one::("pool") { + request = request.pool(value.clone()); + } + + if let Some(value) = matches.get_one::("json-body") { + let body_txt = std::fs::read_to_string(value).unwrap(); + let body_value = serde_json::from_str::(&body_txt).unwrap(); + request = request.body(body_value); + } + + self.config + .execute_ip_pool_range_remove(matches, &mut request)?; + let result = request.send().await; + match result { + Ok(r) => { + self.config.success_no_item(&r); + Ok(()) + } + Err(r) => { + self.config.error(&r); + Err(anyhow::Error::new(r)) + } + } + } + + pub async fn execute_ip_pool_silo_list( + &self, + matches: &clap::ArgMatches, + ) -> anyhow::Result<()> { + let mut request = self.client.ip_pool_silo_list(); if let Some(value) = matches.get_one::("limit") { request = request.limit(value.clone()); } - if let Some(value) = matches.get_one::("silo") { - request = request.silo(value.clone()); + if let Some(value) = matches.get_one::("pool") { + request = request.pool(value.clone()); } - if let Some(value) = matches.get_one::("sort-by") { + if let Some(value) = matches.get_one::("sort-by") { request = request.sort_by(value.clone()); } self.config - .execute_silo_identity_provider_list(matches, &mut request)?; - self.config - .list_start::(); + .execute_ip_pool_silo_list(matches, &mut request)?; + self.config.list_start::(); let mut stream = futures::StreamExt::take( request.stream(), matches @@ -9136,7 +10094,7 @@ impl Cli { } Ok(None) => { self.config - .list_end_success::(); + .list_end_success::(); return Ok(()); } Ok(Some(value)) => { @@ -9146,27 +10104,31 @@ impl Cli { } } - pub async fn execute_local_idp_user_create( + pub async fn execute_ip_pool_silo_link( &self, matches: &clap::ArgMatches, ) -> anyhow::Result<()> { - let mut request = self.client.local_idp_user_create(); - if let Some(value) = matches.get_one::("external-id") { - request = request.body_map(|body| body.external_id(value.clone())) + let mut request = self.client.ip_pool_silo_link(); + if let Some(value) = matches.get_one::("is-default") { + request = request.body_map(|body| body.is_default(value.clone())) + } + + if let Some(value) = matches.get_one::("pool") { + request = request.pool(value.clone()); } if let Some(value) = matches.get_one::("silo") { - request = request.silo(value.clone()); + request = request.body_map(|body| body.silo(value.clone())) } if let Some(value) = matches.get_one::("json-body") { let body_txt = std::fs::read_to_string(value).unwrap(); - let body_value = serde_json::from_str::(&body_txt).unwrap(); + let body_value = serde_json::from_str::(&body_txt).unwrap(); request = request.body(body_value); } self.config - .execute_local_idp_user_create(matches, &mut request)?; + .execute_ip_pool_silo_link(matches, &mut request)?; let result = request.send().await; match result { Ok(r) => { @@ -9180,59 +10142,35 @@ impl Cli { } } - pub async fn execute_local_idp_user_delete( + pub async fn execute_ip_pool_silo_update( &self, matches: &clap::ArgMatches, ) -> anyhow::Result<()> { - let mut request = self.client.local_idp_user_delete(); - if let Some(value) = matches.get_one::("silo") { - request = request.silo(value.clone()); - } - - if let Some(value) = matches.get_one::("user-id") { - request = request.user_id(value.clone()); + let mut request = self.client.ip_pool_silo_update(); + if let Some(value) = matches.get_one::("is-default") { + request = request.body_map(|body| body.is_default(value.clone())) } - self.config - .execute_local_idp_user_delete(matches, &mut request)?; - let result = request.send().await; - match result { - Ok(r) => { - self.config.success_no_item(&r); - Ok(()) - } - Err(r) => { - self.config.error(&r); - Err(anyhow::Error::new(r)) - } + if let Some(value) = matches.get_one::("pool") { + request = request.pool(value.clone()); } - } - pub async fn execute_local_idp_user_set_password( - &self, - matches: &clap::ArgMatches, - ) -> anyhow::Result<()> { - let mut request = self.client.local_idp_user_set_password(); if let Some(value) = matches.get_one::("silo") { request = request.silo(value.clone()); } - if let Some(value) = matches.get_one::("user-id") { - request = request.user_id(value.clone()); - } - if let Some(value) = matches.get_one::("json-body") { let body_txt = std::fs::read_to_string(value).unwrap(); - let body_value = serde_json::from_str::(&body_txt).unwrap(); + let body_value = serde_json::from_str::(&body_txt).unwrap(); request = request.body(body_value); } self.config - .execute_local_idp_user_set_password(matches, &mut request)?; + .execute_ip_pool_silo_update(matches, &mut request)?; let result = request.send().await; match result { Ok(r) => { - self.config.success_no_item(&r); + self.config.success_item(&r); Ok(()) } Err(r) => { @@ -9242,56 +10180,45 @@ impl Cli { } } - pub async fn execute_saml_identity_provider_create( + pub async fn execute_ip_pool_silo_unlink( &self, matches: &clap::ArgMatches, - ) -> anyhow::Result<()> { - let mut request = self.client.saml_identity_provider_create(); - if let Some(value) = matches.get_one::("acs-url") { - request = request.body_map(|body| body.acs_url(value.clone())) - } - - if let Some(value) = matches.get_one::("description") { - request = request.body_map(|body| body.description(value.clone())) - } - - if let Some(value) = matches.get_one::("group-attribute-name") { - request = request.body_map(|body| body.group_attribute_name(value.clone())) - } - - if let Some(value) = matches.get_one::("idp-entity-id") { - request = request.body_map(|body| body.idp_entity_id(value.clone())) - } - - if let Some(value) = matches.get_one::("name") { - request = request.body_map(|body| body.name(value.clone())) + ) -> anyhow::Result<()> { + let mut request = self.client.ip_pool_silo_unlink(); + if let Some(value) = matches.get_one::("pool") { + request = request.pool(value.clone()); } if let Some(value) = matches.get_one::("silo") { request = request.silo(value.clone()); } - if let Some(value) = matches.get_one::("slo-url") { - request = request.body_map(|body| body.slo_url(value.clone())) - } - - if let Some(value) = matches.get_one::("sp-client-id") { - request = request.body_map(|body| body.sp_client_id(value.clone())) - } - - if let Some(value) = matches.get_one::("technical-contact-email") { - request = request.body_map(|body| body.technical_contact_email(value.clone())) + self.config + .execute_ip_pool_silo_unlink(matches, &mut request)?; + let result = request.send().await; + match result { + Ok(r) => { + self.config.success_no_item(&r); + Ok(()) + } + Err(r) => { + self.config.error(&r); + Err(anyhow::Error::new(r)) + } } + } - if let Some(value) = matches.get_one::("json-body") { - let body_txt = std::fs::read_to_string(value).unwrap(); - let body_value = - serde_json::from_str::(&body_txt).unwrap(); - request = request.body(body_value); + pub async fn execute_ip_pool_utilization_view( + &self, + matches: &clap::ArgMatches, + ) -> anyhow::Result<()> { + let mut request = self.client.ip_pool_utilization_view(); + if let Some(value) = matches.get_one::("pool") { + request = request.pool(value.clone()); } self.config - .execute_saml_identity_provider_create(matches, &mut request)?; + .execute_ip_pool_utilization_view(matches, &mut request)?; let result = request.send().await; match result { Ok(r) => { @@ -9305,21 +10232,13 @@ impl Cli { } } - pub async fn execute_saml_identity_provider_view( + pub async fn execute_ip_pool_service_view( &self, matches: &clap::ArgMatches, ) -> anyhow::Result<()> { - let mut request = self.client.saml_identity_provider_view(); - if let Some(value) = matches.get_one::("provider") { - request = request.provider(value.clone()); - } - - if let Some(value) = matches.get_one::("silo") { - request = request.silo(value.clone()); - } - + let mut request = self.client.ip_pool_service_view(); self.config - .execute_saml_identity_provider_view(matches, &mut request)?; + .execute_ip_pool_service_view(matches, &mut request)?; let result = request.send().await; match result { Ok(r) => { @@ -9333,18 +10252,18 @@ impl Cli { } } - pub async fn execute_ip_pool_list(&self, matches: &clap::ArgMatches) -> anyhow::Result<()> { - let mut request = self.client.ip_pool_list(); + pub async fn execute_ip_pool_service_range_list( + &self, + matches: &clap::ArgMatches, + ) -> anyhow::Result<()> { + let mut request = self.client.ip_pool_service_range_list(); if let Some(value) = matches.get_one::("limit") { request = request.limit(value.clone()); } - if let Some(value) = matches.get_one::("sort-by") { - request = request.sort_by(value.clone()); - } - - self.config.execute_ip_pool_list(matches, &mut request)?; - self.config.list_start::(); + self.config + .execute_ip_pool_service_range_list(matches, &mut request)?; + self.config.list_start::(); let mut stream = futures::StreamExt::take( request.stream(), matches @@ -9358,7 +10277,8 @@ impl Cli { return Err(anyhow::Error::new(r)); } Ok(None) => { - self.config.list_end_success::(); + self.config + .list_end_success::(); return Ok(()); } Ok(Some(value)) => { @@ -9368,23 +10288,19 @@ impl Cli { } } - pub async fn execute_ip_pool_create(&self, matches: &clap::ArgMatches) -> anyhow::Result<()> { - let mut request = self.client.ip_pool_create(); - if let Some(value) = matches.get_one::("description") { - request = request.body_map(|body| body.description(value.clone())) - } - - if let Some(value) = matches.get_one::("name") { - request = request.body_map(|body| body.name(value.clone())) - } - + pub async fn execute_ip_pool_service_range_add( + &self, + matches: &clap::ArgMatches, + ) -> anyhow::Result<()> { + let mut request = self.client.ip_pool_service_range_add(); if let Some(value) = matches.get_one::("json-body") { let body_txt = std::fs::read_to_string(value).unwrap(); - let body_value = serde_json::from_str::(&body_txt).unwrap(); + let body_value = serde_json::from_str::(&body_txt).unwrap(); request = request.body(body_value); } - self.config.execute_ip_pool_create(matches, &mut request)?; + self.config + .execute_ip_pool_service_range_add(matches, &mut request)?; let result = request.send().await; match result { Ok(r) => { @@ -9398,17 +10314,23 @@ impl Cli { } } - pub async fn execute_ip_pool_view(&self, matches: &clap::ArgMatches) -> anyhow::Result<()> { - let mut request = self.client.ip_pool_view(); - if let Some(value) = matches.get_one::("pool") { - request = request.pool(value.clone()); + pub async fn execute_ip_pool_service_range_remove( + &self, + matches: &clap::ArgMatches, + ) -> anyhow::Result<()> { + let mut request = self.client.ip_pool_service_range_remove(); + if let Some(value) = matches.get_one::("json-body") { + let body_txt = std::fs::read_to_string(value).unwrap(); + let body_value = serde_json::from_str::(&body_txt).unwrap(); + request = request.body(body_value); } - self.config.execute_ip_pool_view(matches, &mut request)?; + self.config + .execute_ip_pool_service_range_remove(matches, &mut request)?; let result = request.send().await; match result { Ok(r) => { - self.config.success_item(&r); + self.config.success_no_item(&r); Ok(()) } Err(r) => { @@ -9418,76 +10340,75 @@ impl Cli { } } - pub async fn execute_ip_pool_update(&self, matches: &clap::ArgMatches) -> anyhow::Result<()> { - let mut request = self.client.ip_pool_update(); - if let Some(value) = matches.get_one::("description") { - request = request.body_map(|body| body.description(value.clone())) + pub async fn execute_system_metric(&self, matches: &clap::ArgMatches) -> anyhow::Result<()> { + let mut request = self.client.system_metric(); + if let Some(value) = matches.get_one::>("end-time") { + request = request.end_time(value.clone()); } - if let Some(value) = matches.get_one::("name") { - request = request.body_map(|body| body.name(value.clone())) + if let Some(value) = matches.get_one::("limit") { + request = request.limit(value.clone()); } - if let Some(value) = matches.get_one::("pool") { - request = request.pool(value.clone()); + if let Some(value) = matches.get_one::("metric-name") { + request = request.metric_name(value.clone()); } - if let Some(value) = matches.get_one::("json-body") { - let body_txt = std::fs::read_to_string(value).unwrap(); - let body_value = serde_json::from_str::(&body_txt).unwrap(); - request = request.body(body_value); + if let Some(value) = matches.get_one::("order") { + request = request.order(value.clone()); } - self.config.execute_ip_pool_update(matches, &mut request)?; - let result = request.send().await; - match result { - Ok(r) => { - self.config.success_item(&r); - Ok(()) - } - Err(r) => { - self.config.error(&r); - Err(anyhow::Error::new(r)) - } + if let Some(value) = matches.get_one::("silo") { + request = request.silo(value.clone()); } - } - pub async fn execute_ip_pool_delete(&self, matches: &clap::ArgMatches) -> anyhow::Result<()> { - let mut request = self.client.ip_pool_delete(); - if let Some(value) = matches.get_one::("pool") { - request = request.pool(value.clone()); + if let Some(value) = matches.get_one::>("start-time") + { + request = request.start_time(value.clone()); } - self.config.execute_ip_pool_delete(matches, &mut request)?; - let result = request.send().await; - match result { - Ok(r) => { - self.config.success_no_item(&r); - Ok(()) - } - Err(r) => { - self.config.error(&r); - Err(anyhow::Error::new(r)) + self.config.execute_system_metric(matches, &mut request)?; + self.config.list_start::(); + let mut stream = futures::StreamExt::take( + request.stream(), + matches + .get_one::("limit") + .map_or(usize::MAX, |x| x.get() as usize), + ); + loop { + match futures::TryStreamExt::try_next(&mut stream).await { + Err(r) => { + self.config.list_end_error(&r); + return Err(anyhow::Error::new(r)); + } + Ok(None) => { + self.config + .list_end_success::(); + return Ok(()); + } + Ok(Some(value)) => { + self.config.list_item(&value); + } } } } - pub async fn execute_ip_pool_range_list( + pub async fn execute_networking_address_lot_list( &self, matches: &clap::ArgMatches, ) -> anyhow::Result<()> { - let mut request = self.client.ip_pool_range_list(); + let mut request = self.client.networking_address_lot_list(); if let Some(value) = matches.get_one::("limit") { request = request.limit(value.clone()); } - if let Some(value) = matches.get_one::("pool") { - request = request.pool(value.clone()); + if let Some(value) = matches.get_one::("sort-by") { + request = request.sort_by(value.clone()); } self.config - .execute_ip_pool_range_list(matches, &mut request)?; - self.config.list_start::(); + .execute_networking_address_lot_list(matches, &mut request)?; + self.config.list_start::(); let mut stream = futures::StreamExt::take( request.stream(), matches @@ -9502,7 +10423,7 @@ impl Cli { } Ok(None) => { self.config - .list_end_success::(); + .list_end_success::(); return Ok(()); } Ok(Some(value)) => { @@ -9512,23 +10433,31 @@ impl Cli { } } - pub async fn execute_ip_pool_range_add( + pub async fn execute_networking_address_lot_create( &self, matches: &clap::ArgMatches, ) -> anyhow::Result<()> { - let mut request = self.client.ip_pool_range_add(); - if let Some(value) = matches.get_one::("pool") { - request = request.pool(value.clone()); + let mut request = self.client.networking_address_lot_create(); + if let Some(value) = matches.get_one::("description") { + request = request.body_map(|body| body.description(value.clone())) + } + + if let Some(value) = matches.get_one::("kind") { + request = request.body_map(|body| body.kind(value.clone())) + } + + if let Some(value) = matches.get_one::("name") { + request = request.body_map(|body| body.name(value.clone())) } if let Some(value) = matches.get_one::("json-body") { let body_txt = std::fs::read_to_string(value).unwrap(); - let body_value = serde_json::from_str::(&body_txt).unwrap(); + let body_value = serde_json::from_str::(&body_txt).unwrap(); request = request.body(body_value); } self.config - .execute_ip_pool_range_add(matches, &mut request)?; + .execute_networking_address_lot_create(matches, &mut request)?; let result = request.send().await; match result { Ok(r) => { @@ -9542,23 +10471,17 @@ impl Cli { } } - pub async fn execute_ip_pool_range_remove( + pub async fn execute_networking_address_lot_delete( &self, matches: &clap::ArgMatches, ) -> anyhow::Result<()> { - let mut request = self.client.ip_pool_range_remove(); - if let Some(value) = matches.get_one::("pool") { - request = request.pool(value.clone()); - } - - if let Some(value) = matches.get_one::("json-body") { - let body_txt = std::fs::read_to_string(value).unwrap(); - let body_value = serde_json::from_str::(&body_txt).unwrap(); - request = request.body(body_value); + let mut request = self.client.networking_address_lot_delete(); + if let Some(value) = matches.get_one::("address-lot") { + request = request.address_lot(value.clone()); } self.config - .execute_ip_pool_range_remove(matches, &mut request)?; + .execute_networking_address_lot_delete(matches, &mut request)?; let result = request.send().await; match result { Ok(r) => { @@ -9572,17 +10495,17 @@ impl Cli { } } - pub async fn execute_ip_pool_silo_list( + pub async fn execute_networking_address_lot_block_list( &self, matches: &clap::ArgMatches, ) -> anyhow::Result<()> { - let mut request = self.client.ip_pool_silo_list(); - if let Some(value) = matches.get_one::("limit") { - request = request.limit(value.clone()); + let mut request = self.client.networking_address_lot_block_list(); + if let Some(value) = matches.get_one::("address-lot") { + request = request.address_lot(value.clone()); } - if let Some(value) = matches.get_one::("pool") { - request = request.pool(value.clone()); + if let Some(value) = matches.get_one::("limit") { + request = request.limit(value.clone()); } if let Some(value) = matches.get_one::("sort-by") { @@ -9590,8 +10513,9 @@ impl Cli { } self.config - .execute_ip_pool_silo_list(matches, &mut request)?; - self.config.list_start::(); + .execute_networking_address_lot_block_list(matches, &mut request)?; + self.config + .list_start::(); let mut stream = futures::StreamExt::take( request.stream(), matches @@ -9606,7 +10530,7 @@ impl Cli { } Ok(None) => { self.config - .list_end_success::(); + .list_end_success::(); return Ok(()); } Ok(Some(value)) => { @@ -9616,31 +10540,13 @@ impl Cli { } } - pub async fn execute_ip_pool_silo_link( + pub async fn execute_networking_allow_list_view( &self, matches: &clap::ArgMatches, ) -> anyhow::Result<()> { - let mut request = self.client.ip_pool_silo_link(); - if let Some(value) = matches.get_one::("is-default") { - request = request.body_map(|body| body.is_default(value.clone())) - } - - if let Some(value) = matches.get_one::("pool") { - request = request.pool(value.clone()); - } - - if let Some(value) = matches.get_one::("silo") { - request = request.body_map(|body| body.silo(value.clone())) - } - - if let Some(value) = matches.get_one::("json-body") { - let body_txt = std::fs::read_to_string(value).unwrap(); - let body_value = serde_json::from_str::(&body_txt).unwrap(); - request = request.body(body_value); - } - + let mut request = self.client.networking_allow_list_view(); self.config - .execute_ip_pool_silo_link(matches, &mut request)?; + .execute_networking_allow_list_view(matches, &mut request)?; let result = request.send().await; match result { Ok(r) => { @@ -9654,31 +10560,19 @@ impl Cli { } } - pub async fn execute_ip_pool_silo_update( + pub async fn execute_networking_allow_list_update( &self, matches: &clap::ArgMatches, ) -> anyhow::Result<()> { - let mut request = self.client.ip_pool_silo_update(); - if let Some(value) = matches.get_one::("is-default") { - request = request.body_map(|body| body.is_default(value.clone())) - } - - if let Some(value) = matches.get_one::("pool") { - request = request.pool(value.clone()); - } - - if let Some(value) = matches.get_one::("silo") { - request = request.silo(value.clone()); - } - + let mut request = self.client.networking_allow_list_update(); if let Some(value) = matches.get_one::("json-body") { let body_txt = std::fs::read_to_string(value).unwrap(); - let body_value = serde_json::from_str::(&body_txt).unwrap(); + let body_value = serde_json::from_str::(&body_txt).unwrap(); request = request.body(body_value); } self.config - .execute_ip_pool_silo_update(matches, &mut request)?; + .execute_networking_allow_list_update(matches, &mut request)?; let result = request.send().await; match result { Ok(r) => { @@ -9692,21 +10586,27 @@ impl Cli { } } - pub async fn execute_ip_pool_silo_unlink( + pub async fn execute_networking_bfd_disable( &self, matches: &clap::ArgMatches, ) -> anyhow::Result<()> { - let mut request = self.client.ip_pool_silo_unlink(); - if let Some(value) = matches.get_one::("pool") { - request = request.pool(value.clone()); + let mut request = self.client.networking_bfd_disable(); + if let Some(value) = matches.get_one::("remote") { + request = request.body_map(|body| body.remote(value.clone())) } - if let Some(value) = matches.get_one::("silo") { - request = request.silo(value.clone()); + if let Some(value) = matches.get_one::("switch") { + request = request.body_map(|body| body.switch(value.clone())) + } + + if let Some(value) = matches.get_one::("json-body") { + let body_txt = std::fs::read_to_string(value).unwrap(); + let body_value = serde_json::from_str::(&body_txt).unwrap(); + request = request.body(body_value); } self.config - .execute_ip_pool_silo_unlink(matches, &mut request)?; + .execute_networking_bfd_disable(matches, &mut request)?; let result = request.send().await; match result { Ok(r) => { @@ -9720,21 +10620,47 @@ impl Cli { } } - pub async fn execute_ip_pool_utilization_view( + pub async fn execute_networking_bfd_enable( &self, matches: &clap::ArgMatches, ) -> anyhow::Result<()> { - let mut request = self.client.ip_pool_utilization_view(); - if let Some(value) = matches.get_one::("pool") { - request = request.pool(value.clone()); + let mut request = self.client.networking_bfd_enable(); + if let Some(value) = matches.get_one::("detection-threshold") { + request = request.body_map(|body| body.detection_threshold(value.clone())) + } + + if let Some(value) = matches.get_one::("local") { + request = request.body_map(|body| body.local(value.clone())) + } + + if let Some(value) = matches.get_one::("mode") { + request = request.body_map(|body| body.mode(value.clone())) + } + + if let Some(value) = matches.get_one::("remote") { + request = request.body_map(|body| body.remote(value.clone())) + } + + if let Some(value) = matches.get_one::("required-rx") { + request = request.body_map(|body| body.required_rx(value.clone())) + } + + if let Some(value) = matches.get_one::("switch") { + request = request.body_map(|body| body.switch(value.clone())) + } + + if let Some(value) = matches.get_one::("json-body") { + let body_txt = std::fs::read_to_string(value).unwrap(); + let body_value = serde_json::from_str::(&body_txt).unwrap(); + request = request.body(body_value); } self.config - .execute_ip_pool_utilization_view(matches, &mut request)?; + .execute_networking_bfd_enable(matches, &mut request)?; let result = request.send().await; match result { Ok(r) => { - self.config.success_item(&r); + self.config.success_no_item(&r); Ok(()) } Err(r) => { @@ -9744,13 +10670,13 @@ impl Cli { } } - pub async fn execute_ip_pool_service_view( + pub async fn execute_networking_bfd_status( &self, matches: &clap::ArgMatches, ) -> anyhow::Result<()> { - let mut request = self.client.ip_pool_service_view(); + let mut request = self.client.networking_bfd_status(); self.config - .execute_ip_pool_service_view(matches, &mut request)?; + .execute_networking_bfd_status(matches, &mut request)?; let result = request.send().await; match result { Ok(r) => { @@ -9764,18 +10690,26 @@ impl Cli { } } - pub async fn execute_ip_pool_service_range_list( + pub async fn execute_networking_bgp_config_list( &self, matches: &clap::ArgMatches, ) -> anyhow::Result<()> { - let mut request = self.client.ip_pool_service_range_list(); + let mut request = self.client.networking_bgp_config_list(); if let Some(value) = matches.get_one::("limit") { request = request.limit(value.clone()); } + if let Some(value) = matches.get_one::("name-or-id") { + request = request.name_or_id(value.clone()); + } + + if let Some(value) = matches.get_one::("sort-by") { + request = request.sort_by(value.clone()); + } + self.config - .execute_ip_pool_service_range_list(matches, &mut request)?; - self.config.list_start::(); + .execute_networking_bgp_config_list(matches, &mut request)?; + self.config.list_start::(); let mut stream = futures::StreamExt::take( request.stream(), matches @@ -9790,7 +10724,7 @@ impl Cli { } Ok(None) => { self.config - .list_end_success::(); + .list_end_success::(); return Ok(()); } Ok(Some(value)) => { @@ -9800,19 +10734,39 @@ impl Cli { } } - pub async fn execute_ip_pool_service_range_add( + pub async fn execute_networking_bgp_config_create( &self, matches: &clap::ArgMatches, ) -> anyhow::Result<()> { - let mut request = self.client.ip_pool_service_range_add(); + let mut request = self.client.networking_bgp_config_create(); + if let Some(value) = matches.get_one::("asn") { + request = request.body_map(|body| body.asn(value.clone())) + } + + if let Some(value) = matches.get_one::("bgp-announce-set-id") { + request = request.body_map(|body| body.bgp_announce_set_id(value.clone())) + } + + if let Some(value) = matches.get_one::("description") { + request = request.body_map(|body| body.description(value.clone())) + } + + if let Some(value) = matches.get_one::("name") { + request = request.body_map(|body| body.name(value.clone())) + } + + if let Some(value) = matches.get_one::("vrf") { + request = request.body_map(|body| body.vrf(value.clone())) + } + if let Some(value) = matches.get_one::("json-body") { let body_txt = std::fs::read_to_string(value).unwrap(); - let body_value = serde_json::from_str::(&body_txt).unwrap(); + let body_value = serde_json::from_str::(&body_txt).unwrap(); request = request.body(body_value); } self.config - .execute_ip_pool_service_range_add(matches, &mut request)?; + .execute_networking_bgp_config_create(matches, &mut request)?; let result = request.send().await; match result { Ok(r) => { @@ -9826,19 +10780,17 @@ impl Cli { } } - pub async fn execute_ip_pool_service_range_remove( + pub async fn execute_networking_bgp_config_delete( &self, matches: &clap::ArgMatches, ) -> anyhow::Result<()> { - let mut request = self.client.ip_pool_service_range_remove(); - if let Some(value) = matches.get_one::("json-body") { - let body_txt = std::fs::read_to_string(value).unwrap(); - let body_value = serde_json::from_str::(&body_txt).unwrap(); - request = request.body(body_value); + let mut request = self.client.networking_bgp_config_delete(); + if let Some(value) = matches.get_one::("name-or-id") { + request = request.name_or_id(value.clone()); } self.config - .execute_ip_pool_service_range_remove(matches, &mut request)?; + .execute_networking_bgp_config_delete(matches, &mut request)?; let result = request.send().await; match result { Ok(r) => { @@ -9851,125 +10803,53 @@ impl Cli { } } } - - pub async fn execute_system_metric(&self, matches: &clap::ArgMatches) -> anyhow::Result<()> { - let mut request = self.client.system_metric(); - if let Some(value) = matches.get_one::>("end-time") { - request = request.end_time(value.clone()); - } - - if let Some(value) = matches.get_one::("limit") { - request = request.limit(value.clone()); - } - - if let Some(value) = matches.get_one::("metric-name") { - request = request.metric_name(value.clone()); - } - - if let Some(value) = matches.get_one::("order") { - request = request.order(value.clone()); - } - - if let Some(value) = matches.get_one::("silo") { - request = request.silo(value.clone()); - } - - if let Some(value) = matches.get_one::>("start-time") - { - request = request.start_time(value.clone()); - } - - self.config.execute_system_metric(matches, &mut request)?; - self.config.list_start::(); - let mut stream = futures::StreamExt::take( - request.stream(), - matches - .get_one::("limit") - .map_or(usize::MAX, |x| x.get() as usize), - ); - loop { - match futures::TryStreamExt::try_next(&mut stream).await { - Err(r) => { - self.config.list_end_error(&r); - return Err(anyhow::Error::new(r)); - } - Ok(None) => { - self.config - .list_end_success::(); - return Ok(()); - } - Ok(Some(value)) => { - self.config.list_item(&value); - } - } - } - } - - pub async fn execute_networking_address_lot_list( - &self, - matches: &clap::ArgMatches, - ) -> anyhow::Result<()> { - let mut request = self.client.networking_address_lot_list(); - if let Some(value) = matches.get_one::("limit") { - request = request.limit(value.clone()); - } - - if let Some(value) = matches.get_one::("sort-by") { - request = request.sort_by(value.clone()); - } - - self.config - .execute_networking_address_lot_list(matches, &mut request)?; - self.config.list_start::(); - let mut stream = futures::StreamExt::take( - request.stream(), - matches - .get_one::("limit") - .map_or(usize::MAX, |x| x.get() as usize), - ); - loop { - match futures::TryStreamExt::try_next(&mut stream).await { - Err(r) => { - self.config.list_end_error(&r); - return Err(anyhow::Error::new(r)); - } - Ok(None) => { - self.config - .list_end_success::(); - return Ok(()); - } - Ok(Some(value)) => { - self.config.list_item(&value); - } + + pub async fn execute_networking_bgp_announce_set_list( + &self, + matches: &clap::ArgMatches, + ) -> anyhow::Result<()> { + let mut request = self.client.networking_bgp_announce_set_list(); + if let Some(value) = matches.get_one::("name-or-id") { + request = request.name_or_id(value.clone()); + } + + self.config + .execute_networking_bgp_announce_set_list(matches, &mut request)?; + let result = request.send().await; + match result { + Ok(r) => { + self.config.success_item(&r); + Ok(()) + } + Err(r) => { + self.config.error(&r); + Err(anyhow::Error::new(r)) } } } - pub async fn execute_networking_address_lot_create( + pub async fn execute_networking_bgp_announce_set_update( &self, matches: &clap::ArgMatches, ) -> anyhow::Result<()> { - let mut request = self.client.networking_address_lot_create(); + let mut request = self.client.networking_bgp_announce_set_update(); if let Some(value) = matches.get_one::("description") { request = request.body_map(|body| body.description(value.clone())) } - if let Some(value) = matches.get_one::("kind") { - request = request.body_map(|body| body.kind(value.clone())) - } - if let Some(value) = matches.get_one::("name") { request = request.body_map(|body| body.name(value.clone())) } if let Some(value) = matches.get_one::("json-body") { let body_txt = std::fs::read_to_string(value).unwrap(); - let body_value = serde_json::from_str::(&body_txt).unwrap(); + let body_value = + serde_json::from_str::(&body_txt).unwrap(); request = request.body(body_value); } self.config - .execute_networking_address_lot_create(matches, &mut request)?; + .execute_networking_bgp_announce_set_update(matches, &mut request)?; let result = request.send().await; match result { Ok(r) => { @@ -9983,17 +10863,17 @@ impl Cli { } } - pub async fn execute_networking_address_lot_delete( + pub async fn execute_networking_bgp_announce_set_delete( &self, matches: &clap::ArgMatches, ) -> anyhow::Result<()> { - let mut request = self.client.networking_address_lot_delete(); - if let Some(value) = matches.get_one::("address-lot") { - request = request.address_lot(value.clone()); + let mut request = self.client.networking_bgp_announce_set_delete(); + if let Some(value) = matches.get_one::("name-or-id") { + request = request.name_or_id(value.clone()); } self.config - .execute_networking_address_lot_delete(matches, &mut request)?; + .execute_networking_bgp_announce_set_delete(matches, &mut request)?; let result = request.send().await; match result { Ok(r) => { @@ -10007,58 +10887,41 @@ impl Cli { } } - pub async fn execute_networking_address_lot_block_list( + pub async fn execute_networking_bgp_message_history( &self, matches: &clap::ArgMatches, ) -> anyhow::Result<()> { - let mut request = self.client.networking_address_lot_block_list(); - if let Some(value) = matches.get_one::("address-lot") { - request = request.address_lot(value.clone()); - } - - if let Some(value) = matches.get_one::("limit") { - request = request.limit(value.clone()); - } - - if let Some(value) = matches.get_one::("sort-by") { - request = request.sort_by(value.clone()); + let mut request = self.client.networking_bgp_message_history(); + if let Some(value) = matches.get_one::("asn") { + request = request.asn(value.clone()); } self.config - .execute_networking_address_lot_block_list(matches, &mut request)?; - self.config - .list_start::(); - let mut stream = futures::StreamExt::take( - request.stream(), - matches - .get_one::("limit") - .map_or(usize::MAX, |x| x.get() as usize), - ); - loop { - match futures::TryStreamExt::try_next(&mut stream).await { - Err(r) => { - self.config.list_end_error(&r); - return Err(anyhow::Error::new(r)); - } - Ok(None) => { - self.config - .list_end_success::(); - return Ok(()); - } - Ok(Some(value)) => { - self.config.list_item(&value); - } + .execute_networking_bgp_message_history(matches, &mut request)?; + let result = request.send().await; + match result { + Ok(r) => { + self.config.success_item(&r); + Ok(()) + } + Err(r) => { + self.config.error(&r); + Err(anyhow::Error::new(r)) } } } - pub async fn execute_networking_allow_list_view( + pub async fn execute_networking_bgp_imported_routes_ipv4( &self, matches: &clap::ArgMatches, ) -> anyhow::Result<()> { - let mut request = self.client.networking_allow_list_view(); + let mut request = self.client.networking_bgp_imported_routes_ipv4(); + if let Some(value) = matches.get_one::("asn") { + request = request.asn(value.clone()); + } + self.config - .execute_networking_allow_list_view(matches, &mut request)?; + .execute_networking_bgp_imported_routes_ipv4(matches, &mut request)?; let result = request.send().await; match result { Ok(r) => { @@ -10072,19 +10935,13 @@ impl Cli { } } - pub async fn execute_networking_allow_list_update( + pub async fn execute_networking_bgp_status( &self, matches: &clap::ArgMatches, ) -> anyhow::Result<()> { - let mut request = self.client.networking_allow_list_update(); - if let Some(value) = matches.get_one::("json-body") { - let body_txt = std::fs::read_to_string(value).unwrap(); - let body_value = serde_json::from_str::(&body_txt).unwrap(); - request = request.body(body_value); - } - + let mut request = self.client.networking_bgp_status(); self.config - .execute_networking_allow_list_update(matches, &mut request)?; + .execute_networking_bgp_status(matches, &mut request)?; let result = request.send().await; match result { Ok(r) => { @@ -10098,81 +10955,89 @@ impl Cli { } } - pub async fn execute_networking_bfd_disable( + pub async fn execute_networking_loopback_address_list( &self, matches: &clap::ArgMatches, ) -> anyhow::Result<()> { - let mut request = self.client.networking_bfd_disable(); - if let Some(value) = matches.get_one::("remote") { - request = request.body_map(|body| body.remote(value.clone())) - } - - if let Some(value) = matches.get_one::("switch") { - request = request.body_map(|body| body.switch(value.clone())) + let mut request = self.client.networking_loopback_address_list(); + if let Some(value) = matches.get_one::("limit") { + request = request.limit(value.clone()); } - if let Some(value) = matches.get_one::("json-body") { - let body_txt = std::fs::read_to_string(value).unwrap(); - let body_value = serde_json::from_str::(&body_txt).unwrap(); - request = request.body(body_value); + if let Some(value) = matches.get_one::("sort-by") { + request = request.sort_by(value.clone()); } self.config - .execute_networking_bfd_disable(matches, &mut request)?; - let result = request.send().await; - match result { - Ok(r) => { - self.config.success_no_item(&r); - Ok(()) - } - Err(r) => { - self.config.error(&r); - Err(anyhow::Error::new(r)) + .execute_networking_loopback_address_list(matches, &mut request)?; + self.config + .list_start::(); + let mut stream = futures::StreamExt::take( + request.stream(), + matches + .get_one::("limit") + .map_or(usize::MAX, |x| x.get() as usize), + ); + loop { + match futures::TryStreamExt::try_next(&mut stream).await { + Err(r) => { + self.config.list_end_error(&r); + return Err(anyhow::Error::new(r)); + } + Ok(None) => { + self.config + .list_end_success::(); + return Ok(()); + } + Ok(Some(value)) => { + self.config.list_item(&value); + } } } } - pub async fn execute_networking_bfd_enable( + pub async fn execute_networking_loopback_address_create( &self, matches: &clap::ArgMatches, ) -> anyhow::Result<()> { - let mut request = self.client.networking_bfd_enable(); - if let Some(value) = matches.get_one::("detection-threshold") { - request = request.body_map(|body| body.detection_threshold(value.clone())) + let mut request = self.client.networking_loopback_address_create(); + if let Some(value) = matches.get_one::("address") { + request = request.body_map(|body| body.address(value.clone())) } - if let Some(value) = matches.get_one::("local") { - request = request.body_map(|body| body.local(value.clone())) + if let Some(value) = matches.get_one::("address-lot") { + request = request.body_map(|body| body.address_lot(value.clone())) } - if let Some(value) = matches.get_one::("mode") { - request = request.body_map(|body| body.mode(value.clone())) + if let Some(value) = matches.get_one::("anycast") { + request = request.body_map(|body| body.anycast(value.clone())) } - if let Some(value) = matches.get_one::("remote") { - request = request.body_map(|body| body.remote(value.clone())) + if let Some(value) = matches.get_one::("mask") { + request = request.body_map(|body| body.mask(value.clone())) } - if let Some(value) = matches.get_one::("required-rx") { - request = request.body_map(|body| body.required_rx(value.clone())) + if let Some(value) = matches.get_one::("rack-id") { + request = request.body_map(|body| body.rack_id(value.clone())) } - if let Some(value) = matches.get_one::("switch") { - request = request.body_map(|body| body.switch(value.clone())) + if let Some(value) = matches.get_one::("switch-location") { + request = request.body_map(|body| body.switch_location(value.clone())) } if let Some(value) = matches.get_one::("json-body") { let body_txt = std::fs::read_to_string(value).unwrap(); - let body_value = serde_json::from_str::(&body_txt).unwrap(); + let body_value = + serde_json::from_str::(&body_txt).unwrap(); request = request.body(body_value); } self.config - .execute_networking_bfd_enable(matches, &mut request)?; + .execute_networking_loopback_address_create(matches, &mut request)?; let result = request.send().await; match result { Ok(r) => { - self.config.success_no_item(&r); + self.config.success_item(&r); Ok(()) } Err(r) => { @@ -10182,17 +11047,33 @@ impl Cli { } } - pub async fn execute_networking_bfd_status( + pub async fn execute_networking_loopback_address_delete( &self, matches: &clap::ArgMatches, ) -> anyhow::Result<()> { - let mut request = self.client.networking_bfd_status(); + let mut request = self.client.networking_loopback_address_delete(); + if let Some(value) = matches.get_one::("address") { + request = request.address(value.clone()); + } + + if let Some(value) = matches.get_one::("rack-id") { + request = request.rack_id(value.clone()); + } + + if let Some(value) = matches.get_one::("subnet-mask") { + request = request.subnet_mask(value.clone()); + } + + if let Some(value) = matches.get_one::("switch-location") { + request = request.switch_location(value.clone()); + } + self.config - .execute_networking_bfd_status(matches, &mut request)?; + .execute_networking_loopback_address_delete(matches, &mut request)?; let result = request.send().await; match result { Ok(r) => { - self.config.success_item(&r); + self.config.success_no_item(&r); Ok(()) } Err(r) => { @@ -10202,17 +11083,17 @@ impl Cli { } } - pub async fn execute_networking_bgp_config_list( + pub async fn execute_networking_switch_port_settings_list( &self, matches: &clap::ArgMatches, ) -> anyhow::Result<()> { - let mut request = self.client.networking_bgp_config_list(); + let mut request = self.client.networking_switch_port_settings_list(); if let Some(value) = matches.get_one::("limit") { request = request.limit(value.clone()); } - if let Some(value) = matches.get_one::("name-or-id") { - request = request.name_or_id(value.clone()); + if let Some(value) = matches.get_one::("port-settings") { + request = request.port_settings(value.clone()); } if let Some(value) = matches.get_one::("sort-by") { @@ -10220,8 +11101,9 @@ impl Cli { } self.config - .execute_networking_bgp_config_list(matches, &mut request)?; - self.config.list_start::(); + .execute_networking_switch_port_settings_list(matches, &mut request)?; + self.config + .list_start::(); let mut stream = futures::StreamExt::take( request.stream(), matches @@ -10236,7 +11118,7 @@ impl Cli { } Ok(None) => { self.config - .list_end_success::(); + .list_end_success::(); return Ok(()); } Ok(Some(value)) => { @@ -10246,39 +11128,28 @@ impl Cli { } } - pub async fn execute_networking_bgp_config_create( + pub async fn execute_networking_switch_port_settings_create( &self, matches: &clap::ArgMatches, ) -> anyhow::Result<()> { - let mut request = self.client.networking_bgp_config_create(); - if let Some(value) = matches.get_one::("asn") { - request = request.body_map(|body| body.asn(value.clone())) - } - - if let Some(value) = matches.get_one::("bgp-announce-set-id") { - request = request.body_map(|body| body.bgp_announce_set_id(value.clone())) - } - + let mut request = self.client.networking_switch_port_settings_create(); if let Some(value) = matches.get_one::("description") { request = request.body_map(|body| body.description(value.clone())) } - - if let Some(value) = matches.get_one::("name") { - request = request.body_map(|body| body.name(value.clone())) - } - - if let Some(value) = matches.get_one::("vrf") { - request = request.body_map(|body| body.vrf(value.clone())) + + if let Some(value) = matches.get_one::("name") { + request = request.body_map(|body| body.name(value.clone())) } if let Some(value) = matches.get_one::("json-body") { let body_txt = std::fs::read_to_string(value).unwrap(); - let body_value = serde_json::from_str::(&body_txt).unwrap(); + let body_value = + serde_json::from_str::(&body_txt).unwrap(); request = request.body(body_value); } self.config - .execute_networking_bgp_config_create(matches, &mut request)?; + .execute_networking_switch_port_settings_create(matches, &mut request)?; let result = request.send().await; match result { Ok(r) => { @@ -10292,17 +11163,17 @@ impl Cli { } } - pub async fn execute_networking_bgp_config_delete( + pub async fn execute_networking_switch_port_settings_delete( &self, matches: &clap::ArgMatches, ) -> anyhow::Result<()> { - let mut request = self.client.networking_bgp_config_delete(); - if let Some(value) = matches.get_one::("name-or-id") { - request = request.name_or_id(value.clone()); + let mut request = self.client.networking_switch_port_settings_delete(); + if let Some(value) = matches.get_one::("port-settings") { + request = request.port_settings(value.clone()); } self.config - .execute_networking_bgp_config_delete(matches, &mut request)?; + .execute_networking_switch_port_settings_delete(matches, &mut request)?; let result = request.send().await; match result { Ok(r) => { @@ -10316,17 +11187,17 @@ impl Cli { } } - pub async fn execute_networking_bgp_announce_set_list( + pub async fn execute_networking_switch_port_settings_view( &self, matches: &clap::ArgMatches, ) -> anyhow::Result<()> { - let mut request = self.client.networking_bgp_announce_set_list(); - if let Some(value) = matches.get_one::("name-or-id") { - request = request.name_or_id(value.clone()); + let mut request = self.client.networking_switch_port_settings_view(); + if let Some(value) = matches.get_one::("port") { + request = request.port(value.clone()); } self.config - .execute_networking_bgp_announce_set_list(matches, &mut request)?; + .execute_networking_switch_port_settings_view(matches, &mut request)?; let result = request.send().await; match result { Ok(r) => { @@ -10340,28 +11211,13 @@ impl Cli { } } - pub async fn execute_networking_bgp_announce_set_create( + pub async fn execute_system_policy_view( &self, matches: &clap::ArgMatches, ) -> anyhow::Result<()> { - let mut request = self.client.networking_bgp_announce_set_create(); - if let Some(value) = matches.get_one::("description") { - request = request.body_map(|body| body.description(value.clone())) - } - - if let Some(value) = matches.get_one::("name") { - request = request.body_map(|body| body.name(value.clone())) - } - - if let Some(value) = matches.get_one::("json-body") { - let body_txt = std::fs::read_to_string(value).unwrap(); - let body_value = - serde_json::from_str::(&body_txt).unwrap(); - request = request.body(body_value); - } - + let mut request = self.client.system_policy_view(); self.config - .execute_networking_bgp_announce_set_create(matches, &mut request)?; + .execute_system_policy_view(matches, &mut request)?; let result = request.send().await; match result { Ok(r) => { @@ -10375,21 +11231,23 @@ impl Cli { } } - pub async fn execute_networking_bgp_announce_set_delete( + pub async fn execute_system_policy_update( &self, matches: &clap::ArgMatches, ) -> anyhow::Result<()> { - let mut request = self.client.networking_bgp_announce_set_delete(); - if let Some(value) = matches.get_one::("name-or-id") { - request = request.name_or_id(value.clone()); + let mut request = self.client.system_policy_update(); + if let Some(value) = matches.get_one::("json-body") { + let body_txt = std::fs::read_to_string(value).unwrap(); + let body_value = serde_json::from_str::(&body_txt).unwrap(); + request = request.body(body_value); } self.config - .execute_networking_bgp_announce_set_delete(matches, &mut request)?; + .execute_system_policy_update(matches, &mut request)?; let result = request.send().await; match result { Ok(r) => { - self.config.success_no_item(&r); + self.config.success_item(&r); Ok(()) } Err(r) => { @@ -10399,61 +11257,44 @@ impl Cli { } } - pub async fn execute_networking_bgp_message_history( - &self, - matches: &clap::ArgMatches, - ) -> anyhow::Result<()> { - let mut request = self.client.networking_bgp_message_history(); - if let Some(value) = matches.get_one::("asn") { - request = request.asn(value.clone()); + pub async fn execute_role_list(&self, matches: &clap::ArgMatches) -> anyhow::Result<()> { + let mut request = self.client.role_list(); + if let Some(value) = matches.get_one::("limit") { + request = request.limit(value.clone()); } - self.config - .execute_networking_bgp_message_history(matches, &mut request)?; - let result = request.send().await; - match result { - Ok(r) => { - self.config.success_item(&r); - Ok(()) - } - Err(r) => { - self.config.error(&r); - Err(anyhow::Error::new(r)) + self.config.execute_role_list(matches, &mut request)?; + self.config.list_start::(); + let mut stream = futures::StreamExt::take( + request.stream(), + matches + .get_one::("limit") + .map_or(usize::MAX, |x| x.get() as usize), + ); + loop { + match futures::TryStreamExt::try_next(&mut stream).await { + Err(r) => { + self.config.list_end_error(&r); + return Err(anyhow::Error::new(r)); + } + Ok(None) => { + self.config.list_end_success::(); + return Ok(()); + } + Ok(Some(value)) => { + self.config.list_item(&value); + } } } } - pub async fn execute_networking_bgp_imported_routes_ipv4( - &self, - matches: &clap::ArgMatches, - ) -> anyhow::Result<()> { - let mut request = self.client.networking_bgp_imported_routes_ipv4(); - if let Some(value) = matches.get_one::("asn") { - request = request.asn(value.clone()); - } - - self.config - .execute_networking_bgp_imported_routes_ipv4(matches, &mut request)?; - let result = request.send().await; - match result { - Ok(r) => { - self.config.success_item(&r); - Ok(()) - } - Err(r) => { - self.config.error(&r); - Err(anyhow::Error::new(r)) - } + pub async fn execute_role_view(&self, matches: &clap::ArgMatches) -> anyhow::Result<()> { + let mut request = self.client.role_view(); + if let Some(value) = matches.get_one::("role-name") { + request = request.role_name(value.clone()); } - } - pub async fn execute_networking_bgp_status( - &self, - matches: &clap::ArgMatches, - ) -> anyhow::Result<()> { - let mut request = self.client.networking_bgp_status(); - self.config - .execute_networking_bgp_status(matches, &mut request)?; + self.config.execute_role_view(matches, &mut request)?; let result = request.send().await; match result { Ok(r) => { @@ -10467,11 +11308,11 @@ impl Cli { } } - pub async fn execute_networking_loopback_address_list( + pub async fn execute_system_quotas_list( &self, matches: &clap::ArgMatches, ) -> anyhow::Result<()> { - let mut request = self.client.networking_loopback_address_list(); + let mut request = self.client.system_quotas_list(); if let Some(value) = matches.get_one::("limit") { request = request.limit(value.clone()); } @@ -10481,9 +11322,8 @@ impl Cli { } self.config - .execute_networking_loopback_address_list(matches, &mut request)?; - self.config - .list_start::(); + .execute_system_quotas_list(matches, &mut request)?; + self.config.list_start::(); let mut stream = futures::StreamExt::take( request.stream(), matches @@ -10498,7 +11338,7 @@ impl Cli { } Ok(None) => { self.config - .list_end_success::(); + .list_end_success::(); return Ok(()); } Ok(Some(value)) => { @@ -10508,44 +11348,70 @@ impl Cli { } } - pub async fn execute_networking_loopback_address_create( - &self, - matches: &clap::ArgMatches, - ) -> anyhow::Result<()> { - let mut request = self.client.networking_loopback_address_create(); - if let Some(value) = matches.get_one::("address") { - request = request.body_map(|body| body.address(value.clone())) + pub async fn execute_silo_list(&self, matches: &clap::ArgMatches) -> anyhow::Result<()> { + let mut request = self.client.silo_list(); + if let Some(value) = matches.get_one::("limit") { + request = request.limit(value.clone()); } - if let Some(value) = matches.get_one::("address-lot") { - request = request.body_map(|body| body.address_lot(value.clone())) + if let Some(value) = matches.get_one::("sort-by") { + request = request.sort_by(value.clone()); } - if let Some(value) = matches.get_one::("anycast") { - request = request.body_map(|body| body.anycast(value.clone())) + self.config.execute_silo_list(matches, &mut request)?; + self.config.list_start::(); + let mut stream = futures::StreamExt::take( + request.stream(), + matches + .get_one::("limit") + .map_or(usize::MAX, |x| x.get() as usize), + ); + loop { + match futures::TryStreamExt::try_next(&mut stream).await { + Err(r) => { + self.config.list_end_error(&r); + return Err(anyhow::Error::new(r)); + } + Ok(None) => { + self.config.list_end_success::(); + return Ok(()); + } + Ok(Some(value)) => { + self.config.list_item(&value); + } + } } + } - if let Some(value) = matches.get_one::("mask") { - request = request.body_map(|body| body.mask(value.clone())) + pub async fn execute_silo_create(&self, matches: &clap::ArgMatches) -> anyhow::Result<()> { + let mut request = self.client.silo_create(); + if let Some(value) = matches.get_one::("admin-group-name") { + request = request.body_map(|body| body.admin_group_name(value.clone())) } - if let Some(value) = matches.get_one::("rack-id") { - request = request.body_map(|body| body.rack_id(value.clone())) + if let Some(value) = matches.get_one::("description") { + request = request.body_map(|body| body.description(value.clone())) } - if let Some(value) = matches.get_one::("switch-location") { - request = request.body_map(|body| body.switch_location(value.clone())) + if let Some(value) = matches.get_one::("discoverable") { + request = request.body_map(|body| body.discoverable(value.clone())) + } + + if let Some(value) = matches.get_one::("identity-mode") { + request = request.body_map(|body| body.identity_mode(value.clone())) + } + + if let Some(value) = matches.get_one::("name") { + request = request.body_map(|body| body.name(value.clone())) } if let Some(value) = matches.get_one::("json-body") { let body_txt = std::fs::read_to_string(value).unwrap(); - let body_value = - serde_json::from_str::(&body_txt).unwrap(); + let body_value = serde_json::from_str::(&body_txt).unwrap(); request = request.body(body_value); } - self.config - .execute_networking_loopback_address_create(matches, &mut request)?; + self.config.execute_silo_create(matches, &mut request)?; let result = request.send().await; match result { Ok(r) => { @@ -10559,29 +11425,33 @@ impl Cli { } } - pub async fn execute_networking_loopback_address_delete( - &self, - matches: &clap::ArgMatches, - ) -> anyhow::Result<()> { - let mut request = self.client.networking_loopback_address_delete(); - if let Some(value) = matches.get_one::("address") { - request = request.address(value.clone()); - } - - if let Some(value) = matches.get_one::("rack-id") { - request = request.rack_id(value.clone()); + pub async fn execute_silo_view(&self, matches: &clap::ArgMatches) -> anyhow::Result<()> { + let mut request = self.client.silo_view(); + if let Some(value) = matches.get_one::("silo") { + request = request.silo(value.clone()); } - if let Some(value) = matches.get_one::("subnet-mask") { - request = request.subnet_mask(value.clone()); + self.config.execute_silo_view(matches, &mut request)?; + let result = request.send().await; + match result { + Ok(r) => { + self.config.success_item(&r); + Ok(()) + } + Err(r) => { + self.config.error(&r); + Err(anyhow::Error::new(r)) + } } + } - if let Some(value) = matches.get_one::("switch-location") { - request = request.switch_location(value.clone()); + pub async fn execute_silo_delete(&self, matches: &clap::ArgMatches) -> anyhow::Result<()> { + let mut request = self.client.silo_delete(); + if let Some(value) = matches.get_one::("silo") { + request = request.silo(value.clone()); } - self.config - .execute_networking_loopback_address_delete(matches, &mut request)?; + self.config.execute_silo_delete(matches, &mut request)?; let result = request.send().await; match result { Ok(r) => { @@ -10595,17 +11465,17 @@ impl Cli { } } - pub async fn execute_networking_switch_port_settings_list( + pub async fn execute_silo_ip_pool_list( &self, matches: &clap::ArgMatches, ) -> anyhow::Result<()> { - let mut request = self.client.networking_switch_port_settings_list(); + let mut request = self.client.silo_ip_pool_list(); if let Some(value) = matches.get_one::("limit") { request = request.limit(value.clone()); } - if let Some(value) = matches.get_one::("port-settings") { - request = request.port_settings(value.clone()); + if let Some(value) = matches.get_one::("silo") { + request = request.silo(value.clone()); } if let Some(value) = matches.get_one::("sort-by") { @@ -10613,9 +11483,8 @@ impl Cli { } self.config - .execute_networking_switch_port_settings_list(matches, &mut request)?; - self.config - .list_start::(); + .execute_silo_ip_pool_list(matches, &mut request)?; + self.config.list_start::(); let mut stream = futures::StreamExt::take( request.stream(), matches @@ -10630,7 +11499,7 @@ impl Cli { } Ok(None) => { self.config - .list_end_success::(); + .list_end_success::(); return Ok(()); } Ok(Some(value)) => { @@ -10640,28 +11509,14 @@ impl Cli { } } - pub async fn execute_networking_switch_port_settings_create( - &self, - matches: &clap::ArgMatches, - ) -> anyhow::Result<()> { - let mut request = self.client.networking_switch_port_settings_create(); - if let Some(value) = matches.get_one::("description") { - request = request.body_map(|body| body.description(value.clone())) - } - - if let Some(value) = matches.get_one::("name") { - request = request.body_map(|body| body.name(value.clone())) - } - - if let Some(value) = matches.get_one::("json-body") { - let body_txt = std::fs::read_to_string(value).unwrap(); - let body_value = - serde_json::from_str::(&body_txt).unwrap(); - request = request.body(body_value); + pub async fn execute_silo_policy_view(&self, matches: &clap::ArgMatches) -> anyhow::Result<()> { + let mut request = self.client.silo_policy_view(); + if let Some(value) = matches.get_one::("silo") { + request = request.silo(value.clone()); } self.config - .execute_networking_switch_port_settings_create(matches, &mut request)?; + .execute_silo_policy_view(matches, &mut request)?; let result = request.send().await; match result { Ok(r) => { @@ -10675,21 +11530,27 @@ impl Cli { } } - pub async fn execute_networking_switch_port_settings_delete( + pub async fn execute_silo_policy_update( &self, matches: &clap::ArgMatches, ) -> anyhow::Result<()> { - let mut request = self.client.networking_switch_port_settings_delete(); - if let Some(value) = matches.get_one::("port-settings") { - request = request.port_settings(value.clone()); + let mut request = self.client.silo_policy_update(); + if let Some(value) = matches.get_one::("silo") { + request = request.silo(value.clone()); + } + + if let Some(value) = matches.get_one::("json-body") { + let body_txt = std::fs::read_to_string(value).unwrap(); + let body_value = serde_json::from_str::(&body_txt).unwrap(); + request = request.body(body_value); } self.config - .execute_networking_switch_port_settings_delete(matches, &mut request)?; + .execute_silo_policy_update(matches, &mut request)?; let result = request.send().await; match result { Ok(r) => { - self.config.success_no_item(&r); + self.config.success_item(&r); Ok(()) } Err(r) => { @@ -10699,17 +11560,14 @@ impl Cli { } } - pub async fn execute_networking_switch_port_settings_view( - &self, - matches: &clap::ArgMatches, - ) -> anyhow::Result<()> { - let mut request = self.client.networking_switch_port_settings_view(); - if let Some(value) = matches.get_one::("port") { - request = request.port(value.clone()); + pub async fn execute_silo_quotas_view(&self, matches: &clap::ArgMatches) -> anyhow::Result<()> { + let mut request = self.client.silo_quotas_view(); + if let Some(value) = matches.get_one::("silo") { + request = request.silo(value.clone()); } self.config - .execute_networking_switch_port_settings_view(matches, &mut request)?; + .execute_silo_quotas_view(matches, &mut request)?; let result = request.send().await; match result { Ok(r) => { @@ -10723,39 +11581,35 @@ impl Cli { } } - pub async fn execute_system_policy_view( + pub async fn execute_silo_quotas_update( &self, matches: &clap::ArgMatches, ) -> anyhow::Result<()> { - let mut request = self.client.system_policy_view(); - self.config - .execute_system_policy_view(matches, &mut request)?; - let result = request.send().await; - match result { - Ok(r) => { - self.config.success_item(&r); - Ok(()) - } - Err(r) => { - self.config.error(&r); - Err(anyhow::Error::new(r)) - } + let mut request = self.client.silo_quotas_update(); + if let Some(value) = matches.get_one::("cpus") { + request = request.body_map(|body| body.cpus(value.clone())) + } + + if let Some(value) = matches.get_one::("memory") { + request = request.body_map(|body| body.memory(value.clone())) + } + + if let Some(value) = matches.get_one::("silo") { + request = request.silo(value.clone()); + } + + if let Some(value) = matches.get_one::("storage") { + request = request.body_map(|body| body.storage(value.clone())) } - } - pub async fn execute_system_policy_update( - &self, - matches: &clap::ArgMatches, - ) -> anyhow::Result<()> { - let mut request = self.client.system_policy_update(); if let Some(value) = matches.get_one::("json-body") { let body_txt = std::fs::read_to_string(value).unwrap(); - let body_value = serde_json::from_str::(&body_txt).unwrap(); + let body_value = serde_json::from_str::(&body_txt).unwrap(); request = request.body(body_value); } self.config - .execute_system_policy_update(matches, &mut request)?; + .execute_silo_quotas_update(matches, &mut request)?; let result = request.send().await; match result { Ok(r) => { @@ -10769,14 +11623,22 @@ impl Cli { } } - pub async fn execute_role_list(&self, matches: &clap::ArgMatches) -> anyhow::Result<()> { - let mut request = self.client.role_list(); + pub async fn execute_silo_user_list(&self, matches: &clap::ArgMatches) -> anyhow::Result<()> { + let mut request = self.client.silo_user_list(); if let Some(value) = matches.get_one::("limit") { request = request.limit(value.clone()); } - self.config.execute_role_list(matches, &mut request)?; - self.config.list_start::(); + if let Some(value) = matches.get_one::("silo") { + request = request.silo(value.clone()); + } + + if let Some(value) = matches.get_one::("sort-by") { + request = request.sort_by(value.clone()); + } + + self.config.execute_silo_user_list(matches, &mut request)?; + self.config.list_start::(); let mut stream = futures::StreamExt::take( request.stream(), matches @@ -10790,7 +11652,7 @@ impl Cli { return Err(anyhow::Error::new(r)); } Ok(None) => { - self.config.list_end_success::(); + self.config.list_end_success::(); return Ok(()); } Ok(Some(value)) => { @@ -10800,13 +11662,17 @@ impl Cli { } } - pub async fn execute_role_view(&self, matches: &clap::ArgMatches) -> anyhow::Result<()> { - let mut request = self.client.role_view(); - if let Some(value) = matches.get_one::("role-name") { - request = request.role_name(value.clone()); + pub async fn execute_silo_user_view(&self, matches: &clap::ArgMatches) -> anyhow::Result<()> { + let mut request = self.client.silo_user_view(); + if let Some(value) = matches.get_one::("silo") { + request = request.silo(value.clone()); } - self.config.execute_role_view(matches, &mut request)?; + if let Some(value) = matches.get_one::("user-id") { + request = request.user_id(value.clone()); + } + + self.config.execute_silo_user_view(matches, &mut request)?; let result = request.send().await; match result { Ok(r) => { @@ -10820,22 +11686,22 @@ impl Cli { } } - pub async fn execute_system_quotas_list( + pub async fn execute_user_builtin_list( &self, matches: &clap::ArgMatches, ) -> anyhow::Result<()> { - let mut request = self.client.system_quotas_list(); + let mut request = self.client.user_builtin_list(); if let Some(value) = matches.get_one::("limit") { request = request.limit(value.clone()); } - if let Some(value) = matches.get_one::("sort-by") { + if let Some(value) = matches.get_one::("sort-by") { request = request.sort_by(value.clone()); } self.config - .execute_system_quotas_list(matches, &mut request)?; - self.config.list_start::(); + .execute_user_builtin_list(matches, &mut request)?; + self.config.list_start::(); let mut stream = futures::StreamExt::take( request.stream(), matches @@ -10850,7 +11716,7 @@ impl Cli { } Ok(None) => { self.config - .list_end_success::(); + .list_end_success::(); return Ok(()); } Ok(Some(value)) => { @@ -10860,8 +11726,35 @@ impl Cli { } } - pub async fn execute_silo_list(&self, matches: &clap::ArgMatches) -> anyhow::Result<()> { - let mut request = self.client.silo_list(); + pub async fn execute_user_builtin_view( + &self, + matches: &clap::ArgMatches, + ) -> anyhow::Result<()> { + let mut request = self.client.user_builtin_view(); + if let Some(value) = matches.get_one::("user") { + request = request.user(value.clone()); + } + + self.config + .execute_user_builtin_view(matches, &mut request)?; + let result = request.send().await; + match result { + Ok(r) => { + self.config.success_item(&r); + Ok(()) + } + Err(r) => { + self.config.error(&r); + Err(anyhow::Error::new(r)) + } + } + } + + pub async fn execute_silo_utilization_list( + &self, + matches: &clap::ArgMatches, + ) -> anyhow::Result<()> { + let mut request = self.client.silo_utilization_list(); if let Some(value) = matches.get_one::("limit") { request = request.limit(value.clone()); } @@ -10870,8 +11763,10 @@ impl Cli { request = request.sort_by(value.clone()); } - self.config.execute_silo_list(matches, &mut request)?; - self.config.list_start::(); + self.config + .execute_silo_utilization_list(matches, &mut request)?; + self.config + .list_start::(); let mut stream = futures::StreamExt::take( request.stream(), matches @@ -10885,7 +11780,8 @@ impl Cli { return Err(anyhow::Error::new(r)); } Ok(None) => { - self.config.list_end_success::(); + self.config + .list_end_success::(); return Ok(()); } Ok(Some(value)) => { @@ -10895,55 +11791,17 @@ impl Cli { } } - pub async fn execute_silo_create(&self, matches: &clap::ArgMatches) -> anyhow::Result<()> { - let mut request = self.client.silo_create(); - if let Some(value) = matches.get_one::("admin-group-name") { - request = request.body_map(|body| body.admin_group_name(value.clone())) - } - - if let Some(value) = matches.get_one::("description") { - request = request.body_map(|body| body.description(value.clone())) - } - - if let Some(value) = matches.get_one::("discoverable") { - request = request.body_map(|body| body.discoverable(value.clone())) - } - - if let Some(value) = matches.get_one::("identity-mode") { - request = request.body_map(|body| body.identity_mode(value.clone())) - } - - if let Some(value) = matches.get_one::("name") { - request = request.body_map(|body| body.name(value.clone())) - } - - if let Some(value) = matches.get_one::("json-body") { - let body_txt = std::fs::read_to_string(value).unwrap(); - let body_value = serde_json::from_str::(&body_txt).unwrap(); - request = request.body(body_value); - } - - self.config.execute_silo_create(matches, &mut request)?; - let result = request.send().await; - match result { - Ok(r) => { - self.config.success_item(&r); - Ok(()) - } - Err(r) => { - self.config.error(&r); - Err(anyhow::Error::new(r)) - } - } - } - - pub async fn execute_silo_view(&self, matches: &clap::ArgMatches) -> anyhow::Result<()> { - let mut request = self.client.silo_view(); + pub async fn execute_silo_utilization_view( + &self, + matches: &clap::ArgMatches, + ) -> anyhow::Result<()> { + let mut request = self.client.silo_utilization_view(); if let Some(value) = matches.get_one::("silo") { request = request.silo(value.clone()); } - self.config.execute_silo_view(matches, &mut request)?; + self.config + .execute_silo_utilization_view(matches, &mut request)?; let result = request.send().await; match result { Ok(r) => { @@ -10957,17 +11815,24 @@ impl Cli { } } - pub async fn execute_silo_delete(&self, matches: &clap::ArgMatches) -> anyhow::Result<()> { - let mut request = self.client.silo_delete(); - if let Some(value) = matches.get_one::("silo") { - request = request.silo(value.clone()); + pub async fn execute_timeseries_query(&self, matches: &clap::ArgMatches) -> anyhow::Result<()> { + let mut request = self.client.timeseries_query(); + if let Some(value) = matches.get_one::("query") { + request = request.body_map(|body| body.query(value.clone())) + } + + if let Some(value) = matches.get_one::("json-body") { + let body_txt = std::fs::read_to_string(value).unwrap(); + let body_value = serde_json::from_str::(&body_txt).unwrap(); + request = request.body(body_value); } - self.config.execute_silo_delete(matches, &mut request)?; + self.config + .execute_timeseries_query(matches, &mut request)?; let result = request.send().await; match result { Ok(r) => { - self.config.success_no_item(&r); + self.config.success_item(&r); Ok(()) } Err(r) => { @@ -10977,26 +11842,19 @@ impl Cli { } } - pub async fn execute_silo_ip_pool_list( + pub async fn execute_timeseries_schema_list( &self, matches: &clap::ArgMatches, ) -> anyhow::Result<()> { - let mut request = self.client.silo_ip_pool_list(); + let mut request = self.client.timeseries_schema_list(); if let Some(value) = matches.get_one::("limit") { request = request.limit(value.clone()); } - if let Some(value) = matches.get_one::("silo") { - request = request.silo(value.clone()); - } - - if let Some(value) = matches.get_one::("sort-by") { - request = request.sort_by(value.clone()); - } - self.config - .execute_silo_ip_pool_list(matches, &mut request)?; - self.config.list_start::(); + .execute_timeseries_schema_list(matches, &mut request)?; + self.config + .list_start::(); let mut stream = futures::StreamExt::take( request.stream(), matches @@ -11011,7 +11869,7 @@ impl Cli { } Ok(None) => { self.config - .list_end_success::(); + .list_end_success::(); return Ok(()); } Ok(Some(value)) => { @@ -11021,44 +11879,49 @@ impl Cli { } } - pub async fn execute_silo_policy_view(&self, matches: &clap::ArgMatches) -> anyhow::Result<()> { - let mut request = self.client.silo_policy_view(); - if let Some(value) = matches.get_one::("silo") { - request = request.silo(value.clone()); + pub async fn execute_user_list(&self, matches: &clap::ArgMatches) -> anyhow::Result<()> { + let mut request = self.client.user_list(); + if let Some(value) = matches.get_one::("group") { + request = request.group(value.clone()); } - self.config - .execute_silo_policy_view(matches, &mut request)?; - let result = request.send().await; - match result { - Ok(r) => { - self.config.success_item(&r); - Ok(()) - } - Err(r) => { - self.config.error(&r); - Err(anyhow::Error::new(r)) - } + if let Some(value) = matches.get_one::("limit") { + request = request.limit(value.clone()); } - } - pub async fn execute_silo_policy_update( - &self, - matches: &clap::ArgMatches, - ) -> anyhow::Result<()> { - let mut request = self.client.silo_policy_update(); - if let Some(value) = matches.get_one::("silo") { - request = request.silo(value.clone()); + if let Some(value) = matches.get_one::("sort-by") { + request = request.sort_by(value.clone()); } - if let Some(value) = matches.get_one::("json-body") { - let body_txt = std::fs::read_to_string(value).unwrap(); - let body_value = serde_json::from_str::(&body_txt).unwrap(); - request = request.body(body_value); + self.config.execute_user_list(matches, &mut request)?; + self.config.list_start::(); + let mut stream = futures::StreamExt::take( + request.stream(), + matches + .get_one::("limit") + .map_or(usize::MAX, |x| x.get() as usize), + ); + loop { + match futures::TryStreamExt::try_next(&mut stream).await { + Err(r) => { + self.config.list_end_error(&r); + return Err(anyhow::Error::new(r)); + } + Ok(None) => { + self.config.list_end_success::(); + return Ok(()); + } + Ok(Some(value)) => { + self.config.list_item(&value); + } + } } + } + pub async fn execute_utilization_view(&self, matches: &clap::ArgMatches) -> anyhow::Result<()> { + let mut request = self.client.utilization_view(); self.config - .execute_silo_policy_update(matches, &mut request)?; + .execute_utilization_view(matches, &mut request)?; let result = request.send().await; match result { Ok(r) => { @@ -11072,14 +11935,21 @@ impl Cli { } } - pub async fn execute_silo_quotas_view(&self, matches: &clap::ArgMatches) -> anyhow::Result<()> { - let mut request = self.client.silo_quotas_view(); - if let Some(value) = matches.get_one::("silo") { - request = request.silo(value.clone()); + pub async fn execute_vpc_firewall_rules_view( + &self, + matches: &clap::ArgMatches, + ) -> anyhow::Result<()> { + let mut request = self.client.vpc_firewall_rules_view(); + if let Some(value) = matches.get_one::("project") { + request = request.project(value.clone()); + } + + if let Some(value) = matches.get_one::("vpc") { + request = request.vpc(value.clone()); } self.config - .execute_silo_quotas_view(matches, &mut request)?; + .execute_vpc_firewall_rules_view(matches, &mut request)?; let result = request.send().await; match result { Ok(r) => { @@ -11093,35 +11963,28 @@ impl Cli { } } - pub async fn execute_silo_quotas_update( + pub async fn execute_vpc_firewall_rules_update( &self, matches: &clap::ArgMatches, ) -> anyhow::Result<()> { - let mut request = self.client.silo_quotas_update(); - if let Some(value) = matches.get_one::("cpus") { - request = request.body_map(|body| body.cpus(value.clone())) - } - - if let Some(value) = matches.get_one::("memory") { - request = request.body_map(|body| body.memory(value.clone())) - } - - if let Some(value) = matches.get_one::("silo") { - request = request.silo(value.clone()); + let mut request = self.client.vpc_firewall_rules_update(); + if let Some(value) = matches.get_one::("project") { + request = request.project(value.clone()); } - if let Some(value) = matches.get_one::("storage") { - request = request.body_map(|body| body.storage(value.clone())) + if let Some(value) = matches.get_one::("vpc") { + request = request.vpc(value.clone()); } if let Some(value) = matches.get_one::("json-body") { let body_txt = std::fs::read_to_string(value).unwrap(); - let body_value = serde_json::from_str::(&body_txt).unwrap(); + let body_value = + serde_json::from_str::(&body_txt).unwrap(); request = request.body(body_value); } self.config - .execute_silo_quotas_update(matches, &mut request)?; + .execute_vpc_firewall_rules_update(matches, &mut request)?; let result = request.send().await; match result { Ok(r) => { @@ -11135,22 +11998,34 @@ impl Cli { } } - pub async fn execute_silo_user_list(&self, matches: &clap::ArgMatches) -> anyhow::Result<()> { - let mut request = self.client.silo_user_list(); + pub async fn execute_vpc_router_route_list( + &self, + matches: &clap::ArgMatches, + ) -> anyhow::Result<()> { + let mut request = self.client.vpc_router_route_list(); if let Some(value) = matches.get_one::("limit") { request = request.limit(value.clone()); } - if let Some(value) = matches.get_one::("silo") { - request = request.silo(value.clone()); + if let Some(value) = matches.get_one::("project") { + request = request.project(value.clone()); } - if let Some(value) = matches.get_one::("sort-by") { + if let Some(value) = matches.get_one::("router") { + request = request.router(value.clone()); + } + + if let Some(value) = matches.get_one::("sort-by") { request = request.sort_by(value.clone()); } - self.config.execute_silo_user_list(matches, &mut request)?; - self.config.list_start::(); + if let Some(value) = matches.get_one::("vpc") { + request = request.vpc(value.clone()); + } + + self.config + .execute_vpc_router_route_list(matches, &mut request)?; + self.config.list_start::(); let mut stream = futures::StreamExt::take( request.stream(), matches @@ -11164,7 +12039,8 @@ impl Cli { return Err(anyhow::Error::new(r)); } Ok(None) => { - self.config.list_end_success::(); + self.config + .list_end_success::(); return Ok(()); } Ok(Some(value)) => { @@ -11174,17 +12050,39 @@ impl Cli { } } - pub async fn execute_silo_user_view(&self, matches: &clap::ArgMatches) -> anyhow::Result<()> { - let mut request = self.client.silo_user_view(); - if let Some(value) = matches.get_one::("silo") { - request = request.silo(value.clone()); + pub async fn execute_vpc_router_route_create( + &self, + matches: &clap::ArgMatches, + ) -> anyhow::Result<()> { + let mut request = self.client.vpc_router_route_create(); + if let Some(value) = matches.get_one::("description") { + request = request.body_map(|body| body.description(value.clone())) } - if let Some(value) = matches.get_one::("user-id") { - request = request.user_id(value.clone()); + if let Some(value) = matches.get_one::("name") { + request = request.body_map(|body| body.name(value.clone())) } - self.config.execute_silo_user_view(matches, &mut request)?; + if let Some(value) = matches.get_one::("project") { + request = request.project(value.clone()); + } + + if let Some(value) = matches.get_one::("router") { + request = request.router(value.clone()); + } + + if let Some(value) = matches.get_one::("vpc") { + request = request.vpc(value.clone()); + } + + if let Some(value) = matches.get_one::("json-body") { + let body_txt = std::fs::read_to_string(value).unwrap(); + let body_value = serde_json::from_str::(&body_txt).unwrap(); + request = request.body(body_value); + } + + self.config + .execute_vpc_router_route_create(matches, &mut request)?; let result = request.send().await; match result { Ok(r) => { @@ -11198,61 +12096,119 @@ impl Cli { } } - pub async fn execute_user_builtin_list( + pub async fn execute_vpc_router_route_view( &self, matches: &clap::ArgMatches, ) -> anyhow::Result<()> { - let mut request = self.client.user_builtin_list(); - if let Some(value) = matches.get_one::("limit") { - request = request.limit(value.clone()); + let mut request = self.client.vpc_router_route_view(); + if let Some(value) = matches.get_one::("project") { + request = request.project(value.clone()); } - if let Some(value) = matches.get_one::("sort-by") { - request = request.sort_by(value.clone()); + if let Some(value) = matches.get_one::("route") { + request = request.route(value.clone()); + } + + if let Some(value) = matches.get_one::("router") { + request = request.router(value.clone()); + } + + if let Some(value) = matches.get_one::("vpc") { + request = request.vpc(value.clone()); } self.config - .execute_user_builtin_list(matches, &mut request)?; - self.config.list_start::(); - let mut stream = futures::StreamExt::take( - request.stream(), - matches - .get_one::("limit") - .map_or(usize::MAX, |x| x.get() as usize), - ); - loop { - match futures::TryStreamExt::try_next(&mut stream).await { - Err(r) => { - self.config.list_end_error(&r); - return Err(anyhow::Error::new(r)); - } - Ok(None) => { - self.config - .list_end_success::(); - return Ok(()); - } - Ok(Some(value)) => { - self.config.list_item(&value); - } + .execute_vpc_router_route_view(matches, &mut request)?; + let result = request.send().await; + match result { + Ok(r) => { + self.config.success_item(&r); + Ok(()) + } + Err(r) => { + self.config.error(&r); + Err(anyhow::Error::new(r)) + } + } + } + + pub async fn execute_vpc_router_route_update( + &self, + matches: &clap::ArgMatches, + ) -> anyhow::Result<()> { + let mut request = self.client.vpc_router_route_update(); + if let Some(value) = matches.get_one::("description") { + request = request.body_map(|body| body.description(value.clone())) + } + + if let Some(value) = matches.get_one::("name") { + request = request.body_map(|body| body.name(value.clone())) + } + + if let Some(value) = matches.get_one::("project") { + request = request.project(value.clone()); + } + + if let Some(value) = matches.get_one::("route") { + request = request.route(value.clone()); + } + + if let Some(value) = matches.get_one::("router") { + request = request.router(value.clone()); + } + + if let Some(value) = matches.get_one::("vpc") { + request = request.vpc(value.clone()); + } + + if let Some(value) = matches.get_one::("json-body") { + let body_txt = std::fs::read_to_string(value).unwrap(); + let body_value = serde_json::from_str::(&body_txt).unwrap(); + request = request.body(body_value); + } + + self.config + .execute_vpc_router_route_update(matches, &mut request)?; + let result = request.send().await; + match result { + Ok(r) => { + self.config.success_item(&r); + Ok(()) + } + Err(r) => { + self.config.error(&r); + Err(anyhow::Error::new(r)) } } } - pub async fn execute_user_builtin_view( + pub async fn execute_vpc_router_route_delete( &self, matches: &clap::ArgMatches, ) -> anyhow::Result<()> { - let mut request = self.client.user_builtin_view(); - if let Some(value) = matches.get_one::("user") { - request = request.user(value.clone()); + let mut request = self.client.vpc_router_route_delete(); + if let Some(value) = matches.get_one::("project") { + request = request.project(value.clone()); + } + + if let Some(value) = matches.get_one::("route") { + request = request.route(value.clone()); + } + + if let Some(value) = matches.get_one::("router") { + request = request.router(value.clone()); + } + + if let Some(value) = matches.get_one::("vpc") { + request = request.vpc(value.clone()); } self.config - .execute_user_builtin_view(matches, &mut request)?; + .execute_vpc_router_route_delete(matches, &mut request)?; let result = request.send().await; match result { Ok(r) => { - self.config.success_item(&r); + self.config.success_no_item(&r); Ok(()) } Err(r) => { @@ -11262,23 +12218,26 @@ impl Cli { } } - pub async fn execute_silo_utilization_list( - &self, - matches: &clap::ArgMatches, - ) -> anyhow::Result<()> { - let mut request = self.client.silo_utilization_list(); + pub async fn execute_vpc_router_list(&self, matches: &clap::ArgMatches) -> anyhow::Result<()> { + let mut request = self.client.vpc_router_list(); if let Some(value) = matches.get_one::("limit") { request = request.limit(value.clone()); } + if let Some(value) = matches.get_one::("project") { + request = request.project(value.clone()); + } + if let Some(value) = matches.get_one::("sort-by") { request = request.sort_by(value.clone()); } - self.config - .execute_silo_utilization_list(matches, &mut request)?; - self.config - .list_start::(); + if let Some(value) = matches.get_one::("vpc") { + request = request.vpc(value.clone()); + } + + self.config.execute_vpc_router_list(matches, &mut request)?; + self.config.list_start::(); let mut stream = futures::StreamExt::take( request.stream(), matches @@ -11293,7 +12252,7 @@ impl Cli { } Ok(None) => { self.config - .list_end_success::(); + .list_end_success::(); return Ok(()); } Ok(Some(value)) => { @@ -11303,44 +12262,35 @@ impl Cli { } } - pub async fn execute_silo_utilization_view( + pub async fn execute_vpc_router_create( &self, matches: &clap::ArgMatches, ) -> anyhow::Result<()> { - let mut request = self.client.silo_utilization_view(); - if let Some(value) = matches.get_one::("silo") { - request = request.silo(value.clone()); + let mut request = self.client.vpc_router_create(); + if let Some(value) = matches.get_one::("description") { + request = request.body_map(|body| body.description(value.clone())) } - self.config - .execute_silo_utilization_view(matches, &mut request)?; - let result = request.send().await; - match result { - Ok(r) => { - self.config.success_item(&r); - Ok(()) - } - Err(r) => { - self.config.error(&r); - Err(anyhow::Error::new(r)) - } + if let Some(value) = matches.get_one::("name") { + request = request.body_map(|body| body.name(value.clone())) } - } - pub async fn execute_timeseries_query(&self, matches: &clap::ArgMatches) -> anyhow::Result<()> { - let mut request = self.client.timeseries_query(); - if let Some(value) = matches.get_one::("query") { - request = request.body_map(|body| body.query(value.clone())) + if let Some(value) = matches.get_one::("project") { + request = request.project(value.clone()); + } + + if let Some(value) = matches.get_one::("vpc") { + request = request.vpc(value.clone()); } if let Some(value) = matches.get_one::("json-body") { let body_txt = std::fs::read_to_string(value).unwrap(); - let body_value = serde_json::from_str::(&body_txt).unwrap(); + let body_value = serde_json::from_str::(&body_txt).unwrap(); request = request.body(body_value); } self.config - .execute_timeseries_query(matches, &mut request)?; + .execute_vpc_router_create(matches, &mut request)?; let result = request.send().await; match result { Ok(r) => { @@ -11354,86 +12304,21 @@ impl Cli { } } - pub async fn execute_timeseries_schema_list( - &self, - matches: &clap::ArgMatches, - ) -> anyhow::Result<()> { - let mut request = self.client.timeseries_schema_list(); - if let Some(value) = matches.get_one::("limit") { - request = request.limit(value.clone()); - } - - self.config - .execute_timeseries_schema_list(matches, &mut request)?; - self.config - .list_start::(); - let mut stream = futures::StreamExt::take( - request.stream(), - matches - .get_one::("limit") - .map_or(usize::MAX, |x| x.get() as usize), - ); - loop { - match futures::TryStreamExt::try_next(&mut stream).await { - Err(r) => { - self.config.list_end_error(&r); - return Err(anyhow::Error::new(r)); - } - Ok(None) => { - self.config - .list_end_success::(); - return Ok(()); - } - Ok(Some(value)) => { - self.config.list_item(&value); - } - } - } - } - - pub async fn execute_user_list(&self, matches: &clap::ArgMatches) -> anyhow::Result<()> { - let mut request = self.client.user_list(); - if let Some(value) = matches.get_one::("group") { - request = request.group(value.clone()); - } - - if let Some(value) = matches.get_one::("limit") { - request = request.limit(value.clone()); + pub async fn execute_vpc_router_view(&self, matches: &clap::ArgMatches) -> anyhow::Result<()> { + let mut request = self.client.vpc_router_view(); + if let Some(value) = matches.get_one::("project") { + request = request.project(value.clone()); } - if let Some(value) = matches.get_one::("sort-by") { - request = request.sort_by(value.clone()); + if let Some(value) = matches.get_one::("router") { + request = request.router(value.clone()); } - self.config.execute_user_list(matches, &mut request)?; - self.config.list_start::(); - let mut stream = futures::StreamExt::take( - request.stream(), - matches - .get_one::("limit") - .map_or(usize::MAX, |x| x.get() as usize), - ); - loop { - match futures::TryStreamExt::try_next(&mut stream).await { - Err(r) => { - self.config.list_end_error(&r); - return Err(anyhow::Error::new(r)); - } - Ok(None) => { - self.config.list_end_success::(); - return Ok(()); - } - Ok(Some(value)) => { - self.config.list_item(&value); - } - } + if let Some(value) = matches.get_one::("vpc") { + request = request.vpc(value.clone()); } - } - pub async fn execute_utilization_view(&self, matches: &clap::ArgMatches) -> anyhow::Result<()> { - let mut request = self.client.utilization_view(); - self.config - .execute_utilization_view(matches, &mut request)?; + self.config.execute_vpc_router_view(matches, &mut request)?; let result = request.send().await; match result { Ok(r) => { @@ -11447,21 +12332,39 @@ impl Cli { } } - pub async fn execute_vpc_firewall_rules_view( + pub async fn execute_vpc_router_update( &self, matches: &clap::ArgMatches, ) -> anyhow::Result<()> { - let mut request = self.client.vpc_firewall_rules_view(); + let mut request = self.client.vpc_router_update(); + if let Some(value) = matches.get_one::("description") { + request = request.body_map(|body| body.description(value.clone())) + } + + if let Some(value) = matches.get_one::("name") { + request = request.body_map(|body| body.name(value.clone())) + } + if let Some(value) = matches.get_one::("project") { request = request.project(value.clone()); } + if let Some(value) = matches.get_one::("router") { + request = request.router(value.clone()); + } + if let Some(value) = matches.get_one::("vpc") { request = request.vpc(value.clone()); } + if let Some(value) = matches.get_one::("json-body") { + let body_txt = std::fs::read_to_string(value).unwrap(); + let body_value = serde_json::from_str::(&body_txt).unwrap(); + request = request.body(body_value); + } + self.config - .execute_vpc_firewall_rules_view(matches, &mut request)?; + .execute_vpc_router_update(matches, &mut request)?; let result = request.send().await; match result { Ok(r) => { @@ -11475,32 +12378,29 @@ impl Cli { } } - pub async fn execute_vpc_firewall_rules_update( + pub async fn execute_vpc_router_delete( &self, matches: &clap::ArgMatches, ) -> anyhow::Result<()> { - let mut request = self.client.vpc_firewall_rules_update(); + let mut request = self.client.vpc_router_delete(); if let Some(value) = matches.get_one::("project") { request = request.project(value.clone()); } - if let Some(value) = matches.get_one::("vpc") { - request = request.vpc(value.clone()); + if let Some(value) = matches.get_one::("router") { + request = request.router(value.clone()); } - if let Some(value) = matches.get_one::("json-body") { - let body_txt = std::fs::read_to_string(value).unwrap(); - let body_value = - serde_json::from_str::(&body_txt).unwrap(); - request = request.body(body_value); + if let Some(value) = matches.get_one::("vpc") { + request = request.vpc(value.clone()); } self.config - .execute_vpc_firewall_rules_update(matches, &mut request)?; + .execute_vpc_router_delete(matches, &mut request)?; let result = request.send().await; match result { Ok(r) => { - self.config.success_item(&r); + self.config.success_no_item(&r); Ok(()) } Err(r) => { @@ -11559,6 +12459,10 @@ impl Cli { matches: &clap::ArgMatches, ) -> anyhow::Result<()> { let mut request = self.client.vpc_subnet_create(); + if let Some(value) = matches.get_one::("custom-router") { + request = request.body_map(|body| body.custom_router(value.clone())) + } + if let Some(value) = matches.get_one::("description") { request = request.body_map(|body| body.description(value.clone())) } @@ -11637,6 +12541,10 @@ impl Cli { matches: &clap::ArgMatches, ) -> anyhow::Result<()> { let mut request = self.client.vpc_subnet_update(); + if let Some(value) = matches.get_one::("custom-router") { + request = request.body_map(|body| body.custom_router(value.clone())) + } + if let Some(value) = matches.get_one::("description") { request = request.body_map(|body| body.description(value.clone())) } @@ -13051,10 +13959,10 @@ pub trait CliConfig { Ok(()) } - fn execute_networking_bgp_announce_set_create( + fn execute_networking_bgp_announce_set_update( &self, matches: &clap::ArgMatches, - request: &mut builder::NetworkingBgpAnnounceSetCreate, + request: &mut builder::NetworkingBgpAnnounceSetUpdate, ) -> anyhow::Result<()> { Ok(()) } @@ -13355,6 +14263,86 @@ pub trait CliConfig { Ok(()) } + fn execute_vpc_router_route_list( + &self, + matches: &clap::ArgMatches, + request: &mut builder::VpcRouterRouteList, + ) -> anyhow::Result<()> { + Ok(()) + } + + fn execute_vpc_router_route_create( + &self, + matches: &clap::ArgMatches, + request: &mut builder::VpcRouterRouteCreate, + ) -> anyhow::Result<()> { + Ok(()) + } + + fn execute_vpc_router_route_view( + &self, + matches: &clap::ArgMatches, + request: &mut builder::VpcRouterRouteView, + ) -> anyhow::Result<()> { + Ok(()) + } + + fn execute_vpc_router_route_update( + &self, + matches: &clap::ArgMatches, + request: &mut builder::VpcRouterRouteUpdate, + ) -> anyhow::Result<()> { + Ok(()) + } + + fn execute_vpc_router_route_delete( + &self, + matches: &clap::ArgMatches, + request: &mut builder::VpcRouterRouteDelete, + ) -> anyhow::Result<()> { + Ok(()) + } + + fn execute_vpc_router_list( + &self, + matches: &clap::ArgMatches, + request: &mut builder::VpcRouterList, + ) -> anyhow::Result<()> { + Ok(()) + } + + fn execute_vpc_router_create( + &self, + matches: &clap::ArgMatches, + request: &mut builder::VpcRouterCreate, + ) -> anyhow::Result<()> { + Ok(()) + } + + fn execute_vpc_router_view( + &self, + matches: &clap::ArgMatches, + request: &mut builder::VpcRouterView, + ) -> anyhow::Result<()> { + Ok(()) + } + + fn execute_vpc_router_update( + &self, + matches: &clap::ArgMatches, + request: &mut builder::VpcRouterUpdate, + ) -> anyhow::Result<()> { + Ok(()) + } + + fn execute_vpc_router_delete( + &self, + matches: &clap::ArgMatches, + request: &mut builder::VpcRouterDelete, + ) -> anyhow::Result<()> { + Ok(()) + } + fn execute_vpc_subnet_list( &self, matches: &clap::ArgMatches, @@ -13583,7 +14571,7 @@ pub enum CliCommand { NetworkingBgpConfigCreate, NetworkingBgpConfigDelete, NetworkingBgpAnnounceSetList, - NetworkingBgpAnnounceSetCreate, + NetworkingBgpAnnounceSetUpdate, NetworkingBgpAnnounceSetDelete, NetworkingBgpMessageHistory, NetworkingBgpImportedRoutesIpv4, @@ -13621,6 +14609,16 @@ pub enum CliCommand { UtilizationView, VpcFirewallRulesView, VpcFirewallRulesUpdate, + VpcRouterRouteList, + VpcRouterRouteCreate, + VpcRouterRouteView, + VpcRouterRouteUpdate, + VpcRouterRouteDelete, + VpcRouterList, + VpcRouterCreate, + VpcRouterView, + VpcRouterUpdate, + VpcRouterDelete, VpcSubnetList, VpcSubnetCreate, VpcSubnetView, @@ -13774,7 +14772,7 @@ impl CliCommand { CliCommand::NetworkingBgpConfigCreate, CliCommand::NetworkingBgpConfigDelete, CliCommand::NetworkingBgpAnnounceSetList, - CliCommand::NetworkingBgpAnnounceSetCreate, + CliCommand::NetworkingBgpAnnounceSetUpdate, CliCommand::NetworkingBgpAnnounceSetDelete, CliCommand::NetworkingBgpMessageHistory, CliCommand::NetworkingBgpImportedRoutesIpv4, @@ -13812,6 +14810,16 @@ impl CliCommand { CliCommand::UtilizationView, CliCommand::VpcFirewallRulesView, CliCommand::VpcFirewallRulesUpdate, + CliCommand::VpcRouterRouteList, + CliCommand::VpcRouterRouteCreate, + CliCommand::VpcRouterRouteView, + CliCommand::VpcRouterRouteUpdate, + CliCommand::VpcRouterRouteDelete, + CliCommand::VpcRouterList, + CliCommand::VpcRouterCreate, + CliCommand::VpcRouterView, + CliCommand::VpcRouterUpdate, + CliCommand::VpcRouterDelete, CliCommand::VpcSubnetList, CliCommand::VpcSubnetCreate, CliCommand::VpcSubnetView, diff --git a/cli/src/main.rs b/cli/src/main.rs index 38694e52..35c88578 100644 --- a/cli/src/main.rs +++ b/cli/src/main.rs @@ -6,15 +6,20 @@ #![forbid(unsafe_code)] +use std::io; use std::net::IpAddr; use std::sync::atomic::AtomicBool; use anyhow::Result; use async_trait::async_trait; use cli_builder::NewCli; +use context::Context; use generated_cli::CliConfig; -use oxide::context::Context; -use oxide::types::{AllowedSourceIps, IdpMetadataSource, IpRange, Ipv4Range, Ipv6Range}; +use oxide::{ + types::{AllowedSourceIps, IdpMetadataSource, IpRange, Ipv4Range, Ipv6Range}, + Client, +}; +use url::Url; mod cli_builder; mod cmd_api; @@ -23,7 +28,9 @@ mod cmd_completion; mod cmd_disk; mod cmd_docs; mod cmd_instance; +mod cmd_net; mod cmd_timeseries; +mod context; mod cmd_version; #[allow(unused_mut)] @@ -31,6 +38,8 @@ mod cmd_version; #[allow(unused_must_use)] // TODO #[allow(clippy::clone_on_copy)] mod generated_cli; +#[macro_use] +mod print; #[async_trait] pub trait RunnableCmd: Send + Sync { @@ -40,17 +49,47 @@ pub trait RunnableCmd: Send + Sync { } } +#[async_trait] +pub trait AuthenticatedCmd: Send + Sync { + async fn run(&self, client: &Client) -> Result<()>; + fn is_subtree() -> bool { + true + } +} + +#[async_trait] +impl RunnableCmd for T { + async fn run(&self, ctx: &Context) -> Result<()> { + let client = Client::new_authenticated_config(ctx.client_config())?; + self.run(&client).await + } + fn is_subtree() -> bool { + ::is_subtree() + } +} + pub fn make_cli() -> NewCli<'static> { NewCli::default() .add_custom::("auth") - .add_custom::("api") + // Informational commands that don't require server access .add_custom::("docs") + .add_custom::("completion") .add_custom::("version") + // Custom--often compound--client commands + .add_custom::("api") .add_custom::("disk import") .add_custom::("instance serial") .add_custom::("instance from-image") - .add_custom::("completion") .add_custom::("experimental timeseries dashboard") + .add_custom::("system networking addr") + .add_custom::("system networking link") + .add_custom::("system networking switch-port-settings show") + .add_custom::("system hardware switch-port show-status") + .add_custom::("system networking bgp show-status") + .add_custom::("system networking bgp peer") + .add_custom::("system networking bgp announce") + .add_custom::("system networking bgp withdraw") + .add_custom::("system networking bgp filter") } #[tokio::main] @@ -65,7 +104,16 @@ async fn main() { .await .unwrap(); if let Err(e) = result { - eprintln!("{e}"); + if let Some(io_err) = e.downcast_ref::() { + if io_err.kind() == io::ErrorKind::BrokenPipe { + return; + } + } + + let src = e.source().map(|s| format!(": {s}")).unwrap_or_default(); + eprintln_nopipe!("{e}{src}"); + + eprintln_nopipe!("{e}"); std::process::exit(1) } } @@ -103,7 +151,7 @@ impl CliConfig for OxideOverride { { let s = serde_json::to_string_pretty(std::ops::Deref::deref(value)) .expect("failed to serialize return to json"); - println!("{}", s); + println_nopipe!("{}", s); } fn success_no_item(&self, _: &oxide::ResponseValue<()>) {} @@ -112,7 +160,7 @@ impl CliConfig for OxideOverride { where T: schemars::JsonSchema + serde::Serialize + std::fmt::Debug, { - eprintln!("error"); + eprintln_nopipe!("error"); } fn list_start(&self) @@ -121,7 +169,7 @@ impl CliConfig for OxideOverride { { self.needs_comma .store(false, std::sync::atomic::Ordering::Relaxed); - print!("["); + print_nopipe!("["); } fn list_item(&self, value: &T) @@ -130,9 +178,9 @@ impl CliConfig for OxideOverride { { let s = serde_json::to_string_pretty(&[value]).expect("failed to serialize result to json"); if self.needs_comma.load(std::sync::atomic::Ordering::Relaxed) { - print!(", {}", &s[4..s.len() - 2]); + print_nopipe!(", {}", &s[4..s.len() - 2]); } else { - print!("\n{}", &s[2..s.len() - 2]); + print_nopipe!("\n{}", &s[2..s.len() - 2]); }; self.needs_comma .store(true, std::sync::atomic::Ordering::Relaxed); @@ -143,9 +191,9 @@ impl CliConfig for OxideOverride { T: schemars::JsonSchema + serde::Serialize + std::fmt::Debug, { if self.needs_comma.load(std::sync::atomic::Ordering::Relaxed) { - println!("\n]"); + println_nopipe!("\n]"); } else { - println!("]"); + println_nopipe!("]"); } } @@ -394,3 +442,15 @@ impl IpOrNet { } } } + +pub(crate) trait AsHost { + fn as_host(&self) -> &str; +} + +impl AsHost for Url { + fn as_host(&self) -> &str { + assert_eq!(self.path(), "/"); + let s = self.as_str(); + &s[..s.len() - 1] + } +} diff --git a/cli/src/print.rs b/cli/src/print.rs new file mode 100644 index 00000000..75b144a8 --- /dev/null +++ b/cli/src/print.rs @@ -0,0 +1,107 @@ +/// A wrapper around print! that will not panic on EPIPE. +/// Useful for avoiding spurious panics when piping to head(1). +#[macro_export] +macro_rules! print_nopipe { + // Ignore failure when printing an empty line. + () => { + { + use std::io::Write; + match write!(std::io::stdout()) { + Ok(()) => (), + Err(e) if e.kind() == std::io::ErrorKind::BrokenPipe => (), + Err(e) => panic!("{e}"), + } + } + }; + ($($arg:tt)*) => { + { + use std::io::Write; + match write!(std::io::stdout(), $($arg)*) { + Ok(()) => (), + Err(e) if e.kind() == std::io::ErrorKind::BrokenPipe => (), + Err(e) => panic!("{e}"), + } + } + }; +} + +/// A wrapper around println! that will not panic on EPIPE. +/// Useful for avoiding spurious panics when piping to head(1). +#[macro_export] +macro_rules! println_nopipe { + // Ignore failure when printing an empty line. + () => { + { + use std::io::Write; + match writeln!(std::io::stdout()) { + Ok(()) => (), + Err(e) if e.kind() == std::io::ErrorKind::BrokenPipe => (), + Err(e) => panic!("{e}"), + } + } + }; + ($($arg:tt)*) => { + { + use std::io::Write; + match writeln!(std::io::stdout(), $($arg)*) { + Ok(()) => (), + Err(e) if e.kind() == std::io::ErrorKind::BrokenPipe => (), + Err(e) => panic!("{e}"), + } + } + }; +} + +/// A wrapper around eprint! that will not panic on EPIPE. +/// Useful for avoiding spurious panics when piping to head(1). +#[macro_export] +macro_rules! eprint_nopipe { + // Ignore failure when printing an empty line. + () => { + { + use std::io::Write; + match write!(std::io::stderr()) { + Ok(()) => (), + Err(e) if e.kind() == std::io::ErrorKind::BrokenPipe => (), + Err(e) => panic!("{e}"), + } + } + }; + ($($arg:tt)*) => { + { + use std::io::Write; + match write!(std::io::stderr(), $($arg)*) { + Ok(()) => (), + Err(e) if e.kind() == std::io::ErrorKind::BrokenPipe => (), + Err(e) => panic!("{e}"), + } + } + }; +} + +/// A wrapper around eprintln! that will not panic on EPIPE. +/// Useful for avoiding spurious panics when piping to head(1). +#[macro_export] +macro_rules! eprintln_nopipe { + // Ignore failure when printing an empty line. + () => { + { + use std::io::Write; + match writeln!(std::io::stderr()) { + Ok(()) => (), + Err(e) if e.kind() == std::io::ErrorKind::BrokenPipe => (), + Err(e) => panic!("{e}"), + } + } + }; + ($($arg:tt)*) => { + { + use std::io::Write; + match writeln!(std::io::stderr(), $($arg)*) { + Ok(()) => (), + Err(e) if e.kind() == std::io::ErrorKind::BrokenPipe => (), + Err(e) => panic!("{e}"), + } + } + }; +} diff --git a/cli/tests/data/json-body-required.txt b/cli/tests/data/json-body-required.txt index 08d49673..8b8950cc 100644 --- a/cli/tests/data/json-body-required.txt +++ b/cli/tests/data/json-body-required.txt @@ -1,13 +1,15 @@ oxide image create oxide policy update oxide project policy update -oxide system networking address-lot create -oxide system networking bgp announce-set create -oxide system networking switch-port-settings create -oxide system policy update oxide silo idp local user create oxide silo idp local user set-password oxide silo create oxide silo policy update oxide vpc firewall-rules update -oxide disk create \ No newline at end of file +oxide vpc router route create +oxide vpc router route update +oxide disk create +oxide system policy update +oxide system networking address-lot create +oxide system networking switch-port-settings create +oxide system networking bgp announce-set update \ No newline at end of file diff --git a/cli/tests/data/test_auth_double.stdout b/cli/tests/data/test_auth_double.stdout new file mode 100644 index 00000000..c89a2870 --- /dev/null +++ b/cli/tests/data/test_auth_double.stdout @@ -0,0 +1,9 @@ +Open this URL in your browser: + http://go.here.to/verify + +Enter the code: 0X1-D3C + +Login successful + silo: crystal-palace (12e8c7a4-399f-41e2-985e-7b120ecbcc1a) + user: falken (831dedf4-0a66-4b04-a232-b610f9f8924c) +Use --profile 'crystal-palace2' diff --git a/cli/tests/data/test_auth_double_config.toml b/cli/tests/data/test_auth_double_config.toml new file mode 100644 index 00000000..70a0df50 --- /dev/null +++ b/cli/tests/data/test_auth_double_config.toml @@ -0,0 +1 @@ +default-profile = "crystal-palace" diff --git a/cli/tests/data/test_auth_double_credentials.toml b/cli/tests/data/test_auth_double_credentials.toml new file mode 100644 index 00000000..362417e8 --- /dev/null +++ b/cli/tests/data/test_auth_double_credentials.toml @@ -0,0 +1,9 @@ +[profile.crystal-palace] +host = "" +token = "123-456-789" +user = "831dedf4-0a66-4b04-a232-b610f9f8924c" + +[profile.crystal-palace2] +host = "" +token = "123-456-789" +user = "831dedf4-0a66-4b04-a232-b610f9f8924c" diff --git a/cli/tests/data/test_auth_existing_default.stdout b/cli/tests/data/test_auth_existing_default.stdout new file mode 100644 index 00000000..1c20e5e2 --- /dev/null +++ b/cli/tests/data/test_auth_existing_default.stdout @@ -0,0 +1,9 @@ +Open this URL in your browser: + http://go.here.to/verify + +Enter the code: 0X1-D3C + +Login successful + silo: crystal-palace (12e8c7a4-399f-41e2-985e-7b120ecbcc1a) + user: falken (831dedf4-0a66-4b04-a232-b610f9f8924c) +Use --profile 'crystal-palace' diff --git a/cli/tests/data/test_auth_existing_default_config.toml b/cli/tests/data/test_auth_existing_default_config.toml new file mode 100644 index 00000000..15acd58f --- /dev/null +++ b/cli/tests/data/test_auth_existing_default_config.toml @@ -0,0 +1,2 @@ +default-profile = "first" + \ No newline at end of file diff --git a/cli/tests/data/test_auth_existing_default_credentials.toml b/cli/tests/data/test_auth_existing_default_credentials.toml new file mode 100644 index 00000000..7f88c523 --- /dev/null +++ b/cli/tests/data/test_auth_existing_default_credentials.toml @@ -0,0 +1,9 @@ +[profile.first] +host = "https://oxide.internal" +token = "***-***-***" +user = "00000000-0000-0000-0000-000000000000" + +[profile.crystal-palace] +host = "" +token = "123-456-789" +user = "831dedf4-0a66-4b04-a232-b610f9f8924c" diff --git a/cli/tests/data/test_auth_existing_no_default.stdout b/cli/tests/data/test_auth_existing_no_default.stdout new file mode 100644 index 00000000..86916f08 --- /dev/null +++ b/cli/tests/data/test_auth_existing_no_default.stdout @@ -0,0 +1,9 @@ +Open this URL in your browser: + http://go.here.to/verify + +Enter the code: 0X1-D3C + +Login successful + silo: crystal-palace (12e8c7a4-399f-41e2-985e-7b120ecbcc1a) + user: falken (831dedf4-0a66-4b04-a232-b610f9f8924c) +Profile 'crystal-palace' set as the default diff --git a/cli/tests/data/test_auth_existing_no_default_config.toml b/cli/tests/data/test_auth_existing_no_default_config.toml new file mode 100644 index 00000000..70a0df50 --- /dev/null +++ b/cli/tests/data/test_auth_existing_no_default_config.toml @@ -0,0 +1 @@ +default-profile = "crystal-palace" diff --git a/cli/tests/data/test_auth_existing_no_default_credentials.toml b/cli/tests/data/test_auth_existing_no_default_credentials.toml new file mode 100644 index 00000000..7f88c523 --- /dev/null +++ b/cli/tests/data/test_auth_existing_no_default_credentials.toml @@ -0,0 +1,9 @@ +[profile.first] +host = "https://oxide.internal" +token = "***-***-***" +user = "00000000-0000-0000-0000-000000000000" + +[profile.crystal-palace] +host = "" +token = "123-456-789" +user = "831dedf4-0a66-4b04-a232-b610f9f8924c" diff --git a/cli/tests/data/test_auth_login_first.stdout b/cli/tests/data/test_auth_login_first.stdout new file mode 100644 index 00000000..86916f08 --- /dev/null +++ b/cli/tests/data/test_auth_login_first.stdout @@ -0,0 +1,9 @@ +Open this URL in your browser: + http://go.here.to/verify + +Enter the code: 0X1-D3C + +Login successful + silo: crystal-palace (12e8c7a4-399f-41e2-985e-7b120ecbcc1a) + user: falken (831dedf4-0a66-4b04-a232-b610f9f8924c) +Profile 'crystal-palace' set as the default diff --git a/cli/tests/data/test_auth_login_first_config.toml b/cli/tests/data/test_auth_login_first_config.toml new file mode 100644 index 00000000..70a0df50 --- /dev/null +++ b/cli/tests/data/test_auth_login_first_config.toml @@ -0,0 +1 @@ +default-profile = "crystal-palace" diff --git a/cli/tests/data/test_auth_login_first_credentials.toml b/cli/tests/data/test_auth_login_first_credentials.toml new file mode 100644 index 00000000..4f334790 --- /dev/null +++ b/cli/tests/data/test_auth_login_first_credentials.toml @@ -0,0 +1,4 @@ +[profile.crystal-palace] +host = "" +token = "123-456-789" +user = "831dedf4-0a66-4b04-a232-b610f9f8924c" diff --git a/cli/tests/data/test_auth_status.stdout b/cli/tests/data/test_auth_status.stdout new file mode 100644 index 00000000..f499c75a --- /dev/null +++ b/cli/tests/data/test_auth_status.stdout @@ -0,0 +1,4 @@ +Profile "jennifer" () status: Authenticated +Profile "lightman" () status: Authenticated +Profile "malvin" () status: Error Response: ** IMPROPER REQUEST ** +Profile "sting" (https://unresolvabledomainnameihope) status: Communication Error: error sending request for url (https://unresolvabledomainnameihope/v1/me): error trying to connect: \ No newline at end of file diff --git a/cli/tests/data/test_switch_port_settings_show.stdout b/cli/tests/data/test_switch_port_settings_show.stdout new file mode 100644 index 00000000..a8aa20b4 --- /dev/null +++ b/cli/tests/data/test_switch_port_settings_show.stdout @@ -0,0 +1,26 @@ +switch0/qsfp0 +============= +Autoneg Fec Speed +false None Speed100G + +Address Lot VLAN +169.254.10.2/30 initial-infra None +169.254.30.2/30 initial-infra Some(300) + +BGP Peer Config Export Import Communities Connect Retry Delay Open Enforce First AS Hold Time Idle Hold Time Keepalive Local Pref Md5 Auth Min TTL MED Remote ASN VLAN +169.254.10.1 as65547 [198.51.100.0/24] [no filtering] [] 3 3 false 6 3 2 None None None None None None +169.254.30.1 as65547 [203.0.113.0/24] [no filtering] [] 0 0 false 6 0 2 None None None None None Some(300) + +switch1/qsfp0 +============= +Autoneg Fec Speed +false None Speed100G + +Address Lot VLAN +169.254.20.2/30 initial-infra None +169.254.40.2/30 initial-infra Some(400) + +BGP Peer Config Export Import Communities Connect Retry Delay Open Enforce First AS Hold Time Idle Hold Time Keepalive Local Pref Md5 Auth Min TTL MED Remote ASN VLAN +169.254.20.1 as65547 [198.51.100.0/24] [no filtering] [] 3 3 false 6 3 2 None None None None None None +169.254.40.1 as65547 [203.0.113.0/24] [no filtering] [] 0 0 false 6 0 2 None None None None None Some(400) + diff --git a/cli/tests/test_auth.rs b/cli/tests/test_auth.rs new file mode 100644 index 00000000..c36313b2 --- /dev/null +++ b/cli/tests/test_auth.rs @@ -0,0 +1,432 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +// Copyright 2024 Oxide Computer Company + +use std::{ + fs::{read_to_string, write}, + path::Path, +}; + +use assert_cmd::Command; +use expectorate::assert_contents; +use httpmock::{Method::POST, Mock, MockServer}; +use oxide::types::CurrentUser; +use oxide_httpmock::MockServerExt; +use serde_json::json; + +fn scrub_server(raw: String, server: String) -> String { + raw.replace(&server, "") +} +struct MockOAuth<'a> { + device_auth: Mock<'a>, + device_token: Mock<'a>, + me: Mock<'a>, +} + +impl<'a> MockOAuth<'a> { + fn new(server: &'a MockServer) -> Self { + let device_auth = server.mock(|when, then| { + let body = json!({ + "device_code": "DEV-CODE", + "user_code": "0X1-D3C", + "verification_uri": "http://go.here.to/verify", + "expires_in": 10, + }); + when.method(POST).path("/device/auth"); + then.status(200) + .json_body(body) + .header("content-type", "application/json"); + }); + + // This is where we'd poll, but let's just wave them through. + let device_token = server.mock(|when, then| { + let body = json!({ + "access_token": "123-456-789", + "token_type": "Bearer", + }); + when.method(POST).path("/device/token"); + then.delay(std::time::Duration::from_secs(1)) + .status(200) + .json_body(body) + .header("content-type", "application/json"); + }); + + // User and silo identity now that we're "authenticated". + let me = server.current_user_view(|when, then| { + when.into_inner().any_request(); + then.ok(&CurrentUser { + display_name: "falken".to_string(), + id: "831dedf4-0a66-4b04-a232-b610f9f8924c".parse().unwrap(), + silo_id: "12e8c7a4-399f-41e2-985e-7b120ecbcc1a".parse().unwrap(), + silo_name: "crystal-palace".try_into().unwrap(), + }); + }); + Self { + device_auth, + device_token, + me, + } + } + + fn assert(&self) { + self.device_auth.assert(); + self.device_token.assert(); + self.me.assert(); + } + + fn assert_hits(&self, hits: usize) { + self.device_auth.assert_hits(hits); + self.device_token.assert_hits(hits); + self.me.assert_hits(hits); + } +} + +// Test the first login where no config files exist yet. +#[test] +fn test_auth_login_first() { + let server = MockServer::start(); + let mock = MockOAuth::new(&server); + + let temp_dir = tempfile::tempdir().unwrap().into_path(); + + let cmd = Command::cargo_bin("oxide") + .unwrap() + .env("RUST_BACKTRACE", "1") + .arg("--config-dir") + .arg(temp_dir.as_os_str()) + .arg("auth") + .arg("login") + .arg("--no-browser") + .arg("--host") + .arg(server.url("")) + .assert() + .success(); + let stdout = String::from_utf8_lossy(&cmd.get_output().stdout); + + assert_contents( + "tests/data/test_auth_login_first.stdout", + &scrub_server(stdout.to_string(), server.url("")), + ); + + mock.assert(); + + assert_contents( + "tests/data/test_auth_login_first_credentials.toml", + &scrub_server( + read_to_string(temp_dir.join("credentials.toml")).unwrap(), + server.url(""), + ), + ); + + assert_contents( + "tests/data/test_auth_login_first_config.toml", + &read_to_string(temp_dir.join("config.toml")).unwrap(), + ); +} + +fn write_first_creds(dir: &Path) { + let cred_path = dir.join("credentials.toml"); + let creds = "\ + [profile.first]\n\ + host = \"https://oxide.internal\"\n\ + token = \"***-***-***\"\n\ + user = \"00000000-0000-0000-0000-000000000000\"\n\ + "; + write(cred_path, creds).unwrap(); +} +fn write_first_config(dir: &Path) { + let config_path = dir.join("config.toml"); + let config = "\ + default-profile = \"first\" + "; + write(config_path, config).unwrap(); +} + +#[test] +fn test_auth_login_existing_default() { + let server = MockServer::start(); + let mock = MockOAuth::new(&server); + + let temp_dir = tempfile::tempdir().unwrap().into_path(); + write_first_creds(&temp_dir); + write_first_config(&temp_dir); + + let cmd = Command::cargo_bin("oxide") + .unwrap() + .env("RUST_BACKTRACE", "1") + .arg("--profile") + .arg("crystal-palace") + .arg("--config-dir") + .arg(temp_dir.as_os_str()) + .arg("auth") + .arg("login") + .arg("--no-browser") + .arg("--host") + .arg(server.url("")) + .assert() + .success(); + let stdout = String::from_utf8_lossy(&cmd.get_output().stdout); + + assert_contents( + "tests/data/test_auth_existing_default.stdout", + &scrub_server(stdout.to_string(), server.url("")), + ); + + mock.assert(); + + assert_contents( + "tests/data/test_auth_existing_default_credentials.toml", + &scrub_server( + read_to_string(temp_dir.join("credentials.toml")).unwrap(), + server.url(""), + ), + ); + + assert_contents( + "tests/data/test_auth_existing_default_config.toml", + &read_to_string(temp_dir.join("config.toml")).unwrap(), + ); +} + +#[test] +fn test_auth_login_existing_no_default() { + let server = MockServer::start(); + let mock = MockOAuth::new(&server); + + let temp_dir = tempfile::tempdir().unwrap().into_path(); + write_first_creds(&temp_dir); + + let cmd = Command::cargo_bin("oxide") + .unwrap() + .env("RUST_BACKTRACE", "1") + .arg("--config-dir") + .arg(temp_dir.as_os_str()) + .arg("auth") + .arg("login") + .arg("--no-browser") + .arg("--host") + .arg(server.url("")) + .assert() + .success(); + let stdout = String::from_utf8_lossy(&cmd.get_output().stdout); + + assert_contents( + "tests/data/test_auth_existing_no_default.stdout", + &scrub_server(stdout.to_string(), server.url("")), + ); + + mock.assert(); + + assert_contents( + "tests/data/test_auth_existing_no_default_credentials.toml", + &scrub_server( + read_to_string(temp_dir.join("credentials.toml")).unwrap(), + server.url(""), + ), + ); + + assert_contents( + "tests/data/test_auth_existing_no_default_config.toml", + &read_to_string(temp_dir.join("config.toml")).unwrap(), + ); +} + +#[test] +fn test_auth_login_double() { + let server = MockServer::start(); + let mock = MockOAuth::new(&server); + + let temp_dir = tempfile::tempdir().unwrap().into_path(); + + Command::cargo_bin("oxide") + .unwrap() + .env("RUST_BACKTRACE", "1") + .arg("--config-dir") + .arg(temp_dir.as_os_str()) + .arg("auth") + .arg("login") + .arg("--no-browser") + .arg("--host") + .arg(server.url("")) + .assert() + .success(); + let cmd = Command::cargo_bin("oxide") + .unwrap() + .env("RUST_BACKTRACE", "1") + .arg("--config-dir") + .arg(temp_dir.as_os_str()) + .arg("auth") + .arg("login") + .arg("--no-browser") + .arg("--host") + .arg(server.url("")) + .assert() + .success(); + let stdout = String::from_utf8_lossy(&cmd.get_output().stdout); + + assert_contents( + "tests/data/test_auth_double.stdout", + &scrub_server(stdout.to_string(), server.url("")), + ); + + mock.assert_hits(2); + + assert_contents( + "tests/data/test_auth_double_credentials.toml", + &scrub_server( + read_to_string(temp_dir.join("credentials.toml")).unwrap(), + server.url(""), + ), + ); + + assert_contents( + "tests/data/test_auth_double_config.toml", + &read_to_string(temp_dir.join("config.toml")).unwrap(), + ); +} + +#[test] +fn test_cmd_auth_status() { + let server = MockServer::start(); + + let temp_dir = tempfile::tempdir().unwrap().into_path(); + let cred_path = temp_dir.join("credentials.toml"); + let creds = format!( + "\ + [profile.lightman]\n\ + host = \"{}\"\n\ + token = \"***-***-*ok\"\n\ + user = \"00000000-0000-0000-0000-000000000000\"\n\ + \n\ + [profile.jennifer]\n\ + host = \"{}\"\n\ + token = \"***-***-*ok\"\n\ + user = \"00000000-0000-0000-0000-000000000001\"\n\ + \n\ + [profile.malvin]\n\ + host = \"{}\"\n\ + token = \"***-***-bad\"\n\ + user = \"00000000-0000-0000-0000-000000000002\"\n\ + \n\ + [profile.sting]\n\ + host = \"https://unresolvabledomainnameihope\"\n\ + token = \"***-***-***\"\n\ + user = \"00000000-0000-0000-0000-000000000002\"\n\ + \n\ + ", + server.url(""), + server.url(""), + server.url(""), + ); + write(cred_path, creds).unwrap(); + + let ok = server.current_user_view(|when, then| { + when.into_inner() + .header("authorization", "Bearer ***-***-*ok"); + + then.ok(&oxide::types::CurrentUser { + display_name: "privileged".to_string(), + id: "001de000-05e4-4000-8000-000000004007".parse().unwrap(), + silo_id: "d1bb398f-872c-438c-a4c6-2211e2042526".parse().unwrap(), + silo_name: "funky-town".parse().unwrap(), + }); + }); + let bad = server.current_user_view(|when, then| { + when.into_inner() + .header("authorization", "Bearer ***-***-bad"); + + then.client_error( + 401, + &oxide::types::Error { + error_code: None, + message: "** IMPROPER REQUEST **".to_string(), + request_id: "42".to_string(), + }, + ); + }); + + // Validate authenticated credentials + let cmd = Command::cargo_bin("oxide") + .unwrap() + .arg("--config-dir") + .arg(temp_dir.as_os_str()) + .arg("auth") + .arg("status") + .assert() + .success(); + let stdout = String::from_utf8_lossy(&cmd.get_output().stdout); + + // DNS failure output can vary by platform + let stdout = match stdout.find("dns error:") { + Some(ii) => &stdout[..ii], + None => &stdout, + }; + + assert_contents( + "tests/data/test_auth_status.stdout", + &scrub_server(stdout.to_string(), server.url("")), + ); + + ok.assert_hits(2); + bad.assert(); +} + +#[test] +fn test_cmd_auth_status_env() { + let server = MockServer::start(); + + let oxide_mock = server.current_user_view(|when, then| { + when.into_inner() + .header("authorization", "Bearer oxide-token-good"); + + then.ok(&oxide::types::CurrentUser { + display_name: "privileged".to_string(), + id: "001de000-05e4-4000-8000-000000004007".parse().unwrap(), + silo_id: "d1bb398f-872c-438c-a4c6-2211e2042526".parse().unwrap(), + silo_name: "funky-town".parse().unwrap(), + }); + }); + + // Validate authenticated credentials + Command::cargo_bin("oxide") + .unwrap() + .arg("auth") + .arg("status") + .env("OXIDE_HOST", server.url("")) + .env("OXIDE_TOKEN", "oxide-token-good") + .assert() + .success() + .stdout(format!( + "Logged in to {} as 001de000-05e4-4000-8000-000000004007\n", + server.url("") + )); + + oxide_mock.assert(); + + let oxide_mock = server.current_user_view(|when, then| { + when.into_inner() + .header("authorization", "Bearer oxide-token-bad"); + then.server_error( + 500, + &oxide::types::Error { + error_code: None, + message: "oops".to_string(), + request_id: "42".to_string(), + }, + ); + }); + + // Try invalid credentials. + Command::cargo_bin("oxide") + .unwrap() + .arg("auth") + .arg("status") + .env("OXIDE_HOST", server.url("")) + .env("OXIDE_TOKEN", "oxide-token-bad") + .assert() + .success() + .stdout(format!("{}: Error Response: oops\n", server.url(""))); + oxide_mock.assert(); +} diff --git a/cli/tests/test_net.rs b/cli/tests/test_net.rs new file mode 100644 index 00000000..ee725cb2 --- /dev/null +++ b/cli/tests/test_net.rs @@ -0,0 +1,309 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +// Copyright 2024 Oxide Computer Company + +use assert_cmd::Command; +use chrono::prelude::*; +use httpmock::MockServer; +use oxide::types::{ + AddressLot, AddressLotBlock, AddressLotBlockResultsPage, AddressLotKind, AddressLotResultsPage, + BgpConfig, BgpConfigResultsPage, BgpPeer, ImportExportPolicy, LinkFec, LinkSpeed, NameOrId, + SwitchPort, SwitchPortAddressConfig, SwitchPortConfig, SwitchPortGeometry2, + SwitchPortLinkConfig, SwitchPortResultsPage, SwitchPortSettings, SwitchPortSettingsView, +}; +use oxide_httpmock::MockServerExt; +use uuid::Uuid; + +#[test] +fn test_port_config() { + let server = MockServer::start(); + + let rack_id = Uuid::new_v4(); + let switch0_qsfp0_settings_id = Uuid::new_v4(); + let switch1_qsfp0_settings_id = Uuid::new_v4(); + let lot_id = Uuid::new_v4(); + let lot_block_id = Uuid::new_v4(); + + let ports = SwitchPortResultsPage { + items: vec![ + SwitchPort { + id: Uuid::new_v4(), + port_name: String::from("qsfp0"), + port_settings_id: Some(switch0_qsfp0_settings_id), + rack_id, + switch_location: String::from("switch0"), + }, + SwitchPort { + id: Uuid::new_v4(), + port_name: String::from("qsfp0"), + port_settings_id: Some(switch1_qsfp0_settings_id), + rack_id, + switch_location: String::from("switch1"), + }, + ], + next_page: None, + }; + + let mock_ports = server.networking_switch_port_list(|when, then| { + when.into_inner().any_request(); + then.ok(&ports); + }); + + let lots = AddressLotResultsPage { + items: vec![AddressLot { + description: String::from("Initial infrastructure address lot"), + id: lot_id, + kind: AddressLotKind::Infra, + name: "initial-infra".parse().unwrap(), + time_created: Utc.with_ymd_and_hms(2024, 7, 8, 9, 10, 11).unwrap(), + time_modified: Utc.with_ymd_and_hms(2024, 7, 8, 9, 10, 11).unwrap(), + }], + next_page: None, + }; + + let mock_lots = server.networking_address_lot_list(|when, then| { + when.into_inner().any_request(); + then.ok(&lots); + }); + + let lot_blocks = AddressLotBlockResultsPage { + items: vec![AddressLotBlock { + first_address: "198.51.100.0".parse().unwrap(), + last_address: "198.51.100.254".parse().unwrap(), + id: lot_block_id, + }], + next_page: None, + }; + + let mock_lot_blocks = server.networking_address_lot_block_list(|when, then| { + when.into_inner().any_request(); + then.ok(&lot_blocks); + }); + + let bgp_configs = BgpConfigResultsPage { + items: vec![BgpConfig { + asn: 65547, + description: String::from("as65547"), + id: Uuid::new_v4(), + name: "as65547".parse().unwrap(), + vrf: None, + time_created: Utc.with_ymd_and_hms(2024, 7, 8, 9, 10, 11).unwrap(), + time_modified: Utc.with_ymd_and_hms(2024, 7, 8, 9, 10, 11).unwrap(), + }], + next_page: None, + }; + + let mock_bgp_configs = server.networking_bgp_config_list(|when, then| { + when.into_inner().any_request(); + then.ok(&bgp_configs); + }); + + let switch0_qsfp0_view = SwitchPortSettingsView { + addresses: vec![ + SwitchPortAddressConfig { + address: "169.254.10.2/30".parse().unwrap(), + address_lot_block_id: lot_block_id, + interface_name: String::from("phy0"), + port_settings_id: switch0_qsfp0_settings_id, + vlan_id: None, + }, + SwitchPortAddressConfig { + address: "169.254.30.2/30".parse().unwrap(), + address_lot_block_id: lot_block_id, + interface_name: String::from("phy0"), + port_settings_id: switch0_qsfp0_settings_id, + vlan_id: Some(300), + }, + ], + bgp_peers: vec![ + BgpPeer { + interface_name: String::from("phy0"), + addr: "169.254.10.1".parse().unwrap(), + bgp_config: NameOrId::Id(bgp_configs.items[0].id), + allowed_export: ImportExportPolicy::Allow(vec!["198.51.100.0/24".parse().unwrap()]), + allowed_import: ImportExportPolicy::NoFiltering, + communities: Vec::new(), + connect_retry: 3, + delay_open: 3, + enforce_first_as: false, + hold_time: 6, + idle_hold_time: 3, + keepalive: 2, + local_pref: None, + md5_auth_key: None, + min_ttl: None, + multi_exit_discriminator: None, + remote_asn: None, + vlan_id: None, + }, + BgpPeer { + interface_name: String::from("phy0"), + addr: "169.254.30.1".parse().unwrap(), + bgp_config: NameOrId::Id(bgp_configs.items[0].id), + allowed_export: ImportExportPolicy::Allow(vec!["203.0.113.0/24".parse().unwrap()]), + allowed_import: ImportExportPolicy::NoFiltering, + communities: Vec::new(), + connect_retry: 0, + delay_open: 0, + enforce_first_as: false, + hold_time: 6, + idle_hold_time: 0, + keepalive: 2, + local_pref: None, + md5_auth_key: None, + min_ttl: None, + multi_exit_discriminator: None, + remote_asn: None, + vlan_id: Some(300), + }, + ], + groups: Vec::new(), + interfaces: Vec::new(), + link_lldp: Vec::new(), + links: vec![SwitchPortLinkConfig { + autoneg: false, + fec: LinkFec::None, + link_name: String::from("phy0"), + lldp_service_config_id: Uuid::new_v4(), + mtu: 1500, + port_settings_id: switch1_qsfp0_settings_id, + speed: LinkSpeed::Speed100G, + }], + port: SwitchPortConfig { + geometry: SwitchPortGeometry2::Qsfp28x1, + port_settings_id: switch1_qsfp0_settings_id, + }, + routes: Vec::new(), + settings: SwitchPortSettings { + description: String::from("default uplink 0 switch port settings"), + id: switch1_qsfp0_settings_id, + name: "default-uplink0".parse().unwrap(), + time_created: Utc.with_ymd_and_hms(2024, 7, 8, 9, 10, 11).unwrap(), + time_modified: Utc.with_ymd_and_hms(2024, 7, 8, 9, 10, 11).unwrap(), + }, + vlan_interfaces: Vec::new(), + }; + let switch1_qsfp0_view = SwitchPortSettingsView { + addresses: vec![ + SwitchPortAddressConfig { + address: "169.254.20.2/30".parse().unwrap(), + address_lot_block_id: lot_block_id, + interface_name: String::from("phy0"), + port_settings_id: switch0_qsfp0_settings_id, + vlan_id: None, + }, + SwitchPortAddressConfig { + address: "169.254.40.2/30".parse().unwrap(), + address_lot_block_id: lot_block_id, + interface_name: String::from("phy0"), + port_settings_id: switch0_qsfp0_settings_id, + vlan_id: Some(400), + }, + ], + bgp_peers: vec![ + BgpPeer { + interface_name: String::from("phy0"), + addr: "169.254.20.1".parse().unwrap(), + bgp_config: NameOrId::Id(bgp_configs.items[0].id), + allowed_export: ImportExportPolicy::Allow(vec!["198.51.100.0/24".parse().unwrap()]), + allowed_import: ImportExportPolicy::NoFiltering, + communities: Vec::new(), + connect_retry: 3, + delay_open: 3, + enforce_first_as: false, + hold_time: 6, + idle_hold_time: 3, + keepalive: 2, + local_pref: None, + md5_auth_key: None, + min_ttl: None, + multi_exit_discriminator: None, + remote_asn: None, + vlan_id: None, + }, + BgpPeer { + interface_name: String::from("phy0"), + addr: "169.254.40.1".parse().unwrap(), + bgp_config: NameOrId::Id(bgp_configs.items[0].id), + allowed_export: ImportExportPolicy::Allow(vec!["203.0.113.0/24".parse().unwrap()]), + allowed_import: ImportExportPolicy::NoFiltering, + communities: Vec::new(), + connect_retry: 0, + delay_open: 0, + enforce_first_as: false, + hold_time: 6, + idle_hold_time: 0, + keepalive: 2, + local_pref: None, + md5_auth_key: None, + min_ttl: None, + multi_exit_discriminator: None, + remote_asn: None, + vlan_id: Some(400), + }, + ], + groups: Vec::new(), + interfaces: Vec::new(), + link_lldp: Vec::new(), + links: vec![SwitchPortLinkConfig { + autoneg: false, + fec: LinkFec::None, + link_name: String::from("phy0"), + lldp_service_config_id: Uuid::new_v4(), + mtu: 1500, + port_settings_id: switch1_qsfp0_settings_id, + speed: LinkSpeed::Speed100G, + }], + port: SwitchPortConfig { + geometry: SwitchPortGeometry2::Qsfp28x1, + port_settings_id: switch1_qsfp0_settings_id, + }, + routes: Vec::new(), + settings: SwitchPortSettings { + description: String::from("default uplink 1 switch port settings"), + id: switch1_qsfp0_settings_id, + name: "default-uplink1".parse().unwrap(), + time_created: Utc.with_ymd_and_hms(2024, 7, 8, 9, 10, 11).unwrap(), + time_modified: Utc.with_ymd_and_hms(2024, 7, 8, 9, 10, 11).unwrap(), + }, + vlan_interfaces: Vec::new(), + }; + + let mock_switch0_qsfp0_settings_view = + server.networking_switch_port_settings_view(|when, then| { + when.port(&NameOrId::Id(ports.items[0].port_settings_id.unwrap())); + then.ok(&switch0_qsfp0_view); + }); + + let mock_switch1_qsfp0_settings_view = + server.networking_switch_port_settings_view(|when, then| { + when.port(&NameOrId::Id(ports.items[1].port_settings_id.unwrap())); + then.ok(&switch1_qsfp0_view); + }); + + env_logger::init(); + + Command::cargo_bin("oxide") + .unwrap() + .env("RUST_BACKTRACE", "1") + .env("OXIDE_HOST", server.url("")) + .env("OXIDE_TOKEN", "fake-token") + .arg("system") + .arg("networking") + .arg("switch-port-settings") + .arg("show") + .assert() + .success() + .stdout(expectorate::eq_file_or_panic( + "tests/data/test_switch_port_settings_show.stdout", + )); + + mock_ports.assert(); + mock_lots.assert(); + mock_lot_blocks.assert(); + mock_bgp_configs.assert_hits(2); + mock_switch0_qsfp0_settings_view.assert(); + mock_switch1_qsfp0_settings_view.assert(); +} diff --git a/oxide.json b/oxide.json index a985a3e4..c9d85a8e 100644 --- a/oxide.json +++ b/oxide.json @@ -7,7 +7,7 @@ "url": "https://oxide.computer", "email": "api@oxide.computer" }, - "version": "20240502.0" + "version": "20240710.0" }, "paths": { "/device/auth": { @@ -6630,12 +6630,13 @@ } } }, - "post": { + "put": { "tags": [ "system/networking" ], - "summary": "Create new BGP announce set", - "operationId": "networking_bgp_announce_set_create", + "summary": "Update BGP announce set", + "description": "If the announce set exists, this endpoint replaces the existing announce set with the one specified.", + "operationId": "networking_bgp_announce_set_update", "requestBody": { "content": { "application/json": { @@ -8346,13 +8347,14 @@ } } }, - "/v1/vpc-subnets": { + "/v1/vpc-router-routes": { "get": { "tags": [ "vpcs" ], - "summary": "List subnets", - "operationId": "vpc_subnet_list", + "summary": "List routes", + "description": "List the routes associated with a router in a particular VPC.", + "operationId": "vpc_router_route_list", "parameters": [ { "in": "query", @@ -8382,6 +8384,14 @@ "$ref": "#/components/schemas/NameOrId" } }, + { + "in": "query", + "name": "router", + "description": "Name or ID of the router", + "schema": { + "$ref": "#/components/schemas/NameOrId" + } + }, { "in": "query", "name": "sort_by", @@ -8392,7 +8402,7 @@ { "in": "query", "name": "vpc", - "description": "Name or ID of the VPC", + "description": "Name or ID of the VPC, only required if `router` is provided as a `Name`", "schema": { "$ref": "#/components/schemas/NameOrId" } @@ -8404,7 +8414,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/VpcSubnetResultsPage" + "$ref": "#/components/schemas/RouterRouteResultsPage" } } } @@ -8418,7 +8428,7 @@ }, "x-dropshot-pagination": { "required": [ - "vpc" + "router" ] } }, @@ -8426,8 +8436,8 @@ "tags": [ "vpcs" ], - "summary": "Create subnet", - "operationId": "vpc_subnet_create", + "summary": "Create route", + "operationId": "vpc_router_route_create", "parameters": [ { "in": "query", @@ -8439,19 +8449,27 @@ }, { "in": "query", - "name": "vpc", - "description": "Name or ID of the VPC", + "name": "router", + "description": "Name or ID of the router", "required": true, "schema": { "$ref": "#/components/schemas/NameOrId" } + }, + { + "in": "query", + "name": "vpc", + "description": "Name or ID of the VPC, only required if `router` is provided as a `Name`", + "schema": { + "$ref": "#/components/schemas/NameOrId" + } } ], "requestBody": { "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/VpcSubnetCreate" + "$ref": "#/components/schemas/RouterRouteCreate" } } }, @@ -8463,7 +8481,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/VpcSubnet" + "$ref": "#/components/schemas/RouterRoute" } } } @@ -8477,18 +8495,18 @@ } } }, - "/v1/vpc-subnets/{subnet}": { + "/v1/vpc-router-routes/{route}": { "get": { "tags": [ "vpcs" ], - "summary": "Fetch subnet", - "operationId": "vpc_subnet_view", + "summary": "Fetch route", + "operationId": "vpc_router_route_view", "parameters": [ { "in": "path", - "name": "subnet", - "description": "Name or ID of the subnet", + "name": "route", + "description": "Name or ID of the route", "required": true, "schema": { "$ref": "#/components/schemas/NameOrId" @@ -8502,10 +8520,19 @@ "$ref": "#/components/schemas/NameOrId" } }, + { + "in": "query", + "name": "router", + "description": "Name or ID of the router", + "required": true, + "schema": { + "$ref": "#/components/schemas/NameOrId" + } + }, { "in": "query", "name": "vpc", - "description": "Name or ID of the VPC", + "description": "Name or ID of the VPC, only required if `router` is provided as a `Name`", "schema": { "$ref": "#/components/schemas/NameOrId" } @@ -8517,7 +8544,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/VpcSubnet" + "$ref": "#/components/schemas/RouterRoute" } } } @@ -8534,13 +8561,13 @@ "tags": [ "vpcs" ], - "summary": "Update subnet", - "operationId": "vpc_subnet_update", + "summary": "Update route", + "operationId": "vpc_router_route_update", "parameters": [ { "in": "path", - "name": "subnet", - "description": "Name or ID of the subnet", + "name": "route", + "description": "Name or ID of the route", "required": true, "schema": { "$ref": "#/components/schemas/NameOrId" @@ -8554,10 +8581,18 @@ "$ref": "#/components/schemas/NameOrId" } }, + { + "in": "query", + "name": "router", + "description": "Name or ID of the router", + "schema": { + "$ref": "#/components/schemas/NameOrId" + } + }, { "in": "query", "name": "vpc", - "description": "Name or ID of the VPC", + "description": "Name or ID of the VPC, only required if `router` is provided as a `Name`", "schema": { "$ref": "#/components/schemas/NameOrId" } @@ -8567,7 +8602,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/VpcSubnetUpdate" + "$ref": "#/components/schemas/RouterRouteUpdate" } } }, @@ -8579,7 +8614,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/VpcSubnet" + "$ref": "#/components/schemas/RouterRoute" } } } @@ -8596,13 +8631,13 @@ "tags": [ "vpcs" ], - "summary": "Delete subnet", - "operationId": "vpc_subnet_delete", + "summary": "Delete route", + "operationId": "vpc_router_route_delete", "parameters": [ { "in": "path", - "name": "subnet", - "description": "Name or ID of the subnet", + "name": "route", + "description": "Name or ID of the route", "required": true, "schema": { "$ref": "#/components/schemas/NameOrId" @@ -8616,10 +8651,18 @@ "$ref": "#/components/schemas/NameOrId" } }, + { + "in": "query", + "name": "router", + "description": "Name or ID of the router", + "schema": { + "$ref": "#/components/schemas/NameOrId" + } + }, { "in": "query", "name": "vpc", - "description": "Name or ID of the VPC", + "description": "Name or ID of the VPC, only required if `router` is provided as a `Name`", "schema": { "$ref": "#/components/schemas/NameOrId" } @@ -8638,23 +8681,14 @@ } } }, - "/v1/vpc-subnets/{subnet}/network-interfaces": { + "/v1/vpc-routers": { "get": { "tags": [ "vpcs" ], - "summary": "List network interfaces", - "operationId": "vpc_subnet_list_network_interfaces", + "summary": "List routers", + "operationId": "vpc_router_list", "parameters": [ - { - "in": "path", - "name": "subnet", - "description": "Name or ID of the subnet", - "required": true, - "schema": { - "$ref": "#/components/schemas/NameOrId" - } - }, { "in": "query", "name": "limit", @@ -8705,7 +8739,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/InstanceNetworkInterfaceResultsPage" + "$ref": "#/components/schemas/VpcRouterResultsPage" } } } @@ -8718,89 +8752,30 @@ } }, "x-dropshot-pagination": { - "required": [] + "required": [ + "vpc" + ] } - } - }, - "/v1/vpcs": { - "get": { + }, + "post": { "tags": [ "vpcs" ], - "summary": "List VPCs", - "operationId": "vpc_list", + "summary": "Create VPC router", + "operationId": "vpc_router_create", "parameters": [ - { - "in": "query", - "name": "limit", - "description": "Maximum number of items returned by a single call", - "schema": { - "nullable": true, - "type": "integer", - "format": "uint32", - "minimum": 1 - } - }, - { - "in": "query", - "name": "page_token", - "description": "Token returned by previous call to retrieve the subsequent page", - "schema": { - "nullable": true, - "type": "string" - } - }, { "in": "query", "name": "project", - "description": "Name or ID of the project", + "description": "Name or ID of the project, only required if `vpc` is provided as a `Name`", "schema": { "$ref": "#/components/schemas/NameOrId" } }, { "in": "query", - "name": "sort_by", - "schema": { - "$ref": "#/components/schemas/NameOrIdSortMode" - } - } - ], - "responses": { - "200": { - "description": "successful operation", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/VpcResultsPage" - } - } - } - }, - "4XX": { - "$ref": "#/components/responses/Error" - }, - "5XX": { - "$ref": "#/components/responses/Error" - } - }, - "x-dropshot-pagination": { - "required": [ - "project" - ] - } - }, - "post": { - "tags": [ - "vpcs" - ], - "summary": "Create VPC", - "operationId": "vpc_create", - "parameters": [ - { - "in": "query", - "name": "project", - "description": "Name or ID of the project", + "name": "vpc", + "description": "Name or ID of the VPC", "required": true, "schema": { "$ref": "#/components/schemas/NameOrId" @@ -8811,7 +8786,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/VpcCreate" + "$ref": "#/components/schemas/VpcRouterCreate" } } }, @@ -8823,7 +8798,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/Vpc" + "$ref": "#/components/schemas/VpcRouter" } } } @@ -8837,18 +8812,18 @@ } } }, - "/v1/vpcs/{vpc}": { + "/v1/vpc-routers/{router}": { "get": { "tags": [ "vpcs" ], - "summary": "Fetch VPC", - "operationId": "vpc_view", + "summary": "Fetch router", + "operationId": "vpc_router_view", "parameters": [ { "in": "path", - "name": "vpc", - "description": "Name or ID of the VPC", + "name": "router", + "description": "Name or ID of the router", "required": true, "schema": { "$ref": "#/components/schemas/NameOrId" @@ -8857,7 +8832,15 @@ { "in": "query", "name": "project", - "description": "Name or ID of the project", + "description": "Name or ID of the project, only required if `vpc` is provided as a `Name`", + "schema": { + "$ref": "#/components/schemas/NameOrId" + } + }, + { + "in": "query", + "name": "vpc", + "description": "Name or ID of the VPC", "schema": { "$ref": "#/components/schemas/NameOrId" } @@ -8869,7 +8852,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/Vpc" + "$ref": "#/components/schemas/VpcRouter" } } } @@ -8886,13 +8869,13 @@ "tags": [ "vpcs" ], - "summary": "Update a VPC", - "operationId": "vpc_update", + "summary": "Update router", + "operationId": "vpc_router_update", "parameters": [ { "in": "path", - "name": "vpc", - "description": "Name or ID of the VPC", + "name": "router", + "description": "Name or ID of the router", "required": true, "schema": { "$ref": "#/components/schemas/NameOrId" @@ -8901,7 +8884,15 @@ { "in": "query", "name": "project", - "description": "Name or ID of the project", + "description": "Name or ID of the project, only required if `vpc` is provided as a `Name`", + "schema": { + "$ref": "#/components/schemas/NameOrId" + } + }, + { + "in": "query", + "name": "vpc", + "description": "Name or ID of the VPC", "schema": { "$ref": "#/components/schemas/NameOrId" } @@ -8911,7 +8902,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/VpcUpdate" + "$ref": "#/components/schemas/VpcRouterUpdate" } } }, @@ -8923,7 +8914,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/Vpc" + "$ref": "#/components/schemas/VpcRouter" } } } @@ -8940,13 +8931,13 @@ "tags": [ "vpcs" ], - "summary": "Delete VPC", - "operationId": "vpc_delete", + "summary": "Delete router", + "operationId": "vpc_router_delete", "parameters": [ { "in": "path", - "name": "vpc", - "description": "Name or ID of the VPC", + "name": "router", + "description": "Name or ID of the router", "required": true, "schema": { "$ref": "#/components/schemas/NameOrId" @@ -8955,7 +8946,15 @@ { "in": "query", "name": "project", - "description": "Name or ID of the project", + "description": "Name or ID of the project, only required if `vpc` is provided as a `Name`", + "schema": { + "$ref": "#/components/schemas/NameOrId" + } + }, + { + "in": "query", + "name": "vpc", + "description": "Name or ID of the VPC", "schema": { "$ref": "#/components/schemas/NameOrId" } @@ -8973,34 +8972,662 @@ } } } - } - }, - "components": { - "schemas": { - "Address": { - "description": "An address tied to an address lot.", - "type": "object", - "properties": { - "address": { - "description": "The address and prefix length of this address.", - "allOf": [ - { - "$ref": "#/components/schemas/IpNet" - } - ] + }, + "/v1/vpc-subnets": { + "get": { + "tags": [ + "vpcs" + ], + "summary": "List subnets", + "operationId": "vpc_subnet_list", + "parameters": [ + { + "in": "query", + "name": "limit", + "description": "Maximum number of items returned by a single call", + "schema": { + "nullable": true, + "type": "integer", + "format": "uint32", + "minimum": 1 + } }, - "address_lot": { - "description": "The address lot this address is drawn from.", - "allOf": [ - { - "$ref": "#/components/schemas/NameOrId" - } - ] + { + "in": "query", + "name": "page_token", + "description": "Token returned by previous call to retrieve the subsequent page", + "schema": { + "nullable": true, + "type": "string" + } }, - "vlan_id": { - "nullable": true, - "description": "Optional VLAN ID for this address", - "type": "integer", + { + "in": "query", + "name": "project", + "description": "Name or ID of the project, only required if `vpc` is provided as a `Name`", + "schema": { + "$ref": "#/components/schemas/NameOrId" + } + }, + { + "in": "query", + "name": "sort_by", + "schema": { + "$ref": "#/components/schemas/NameOrIdSortMode" + } + }, + { + "in": "query", + "name": "vpc", + "description": "Name or ID of the VPC", + "schema": { + "$ref": "#/components/schemas/NameOrId" + } + } + ], + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/VpcSubnetResultsPage" + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + }, + "x-dropshot-pagination": { + "required": [ + "vpc" + ] + } + }, + "post": { + "tags": [ + "vpcs" + ], + "summary": "Create subnet", + "operationId": "vpc_subnet_create", + "parameters": [ + { + "in": "query", + "name": "project", + "description": "Name or ID of the project, only required if `vpc` is provided as a `Name`", + "schema": { + "$ref": "#/components/schemas/NameOrId" + } + }, + { + "in": "query", + "name": "vpc", + "description": "Name or ID of the VPC", + "required": true, + "schema": { + "$ref": "#/components/schemas/NameOrId" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/VpcSubnetCreate" + } + } + }, + "required": true + }, + "responses": { + "201": { + "description": "successful creation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/VpcSubnet" + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, + "/v1/vpc-subnets/{subnet}": { + "get": { + "tags": [ + "vpcs" + ], + "summary": "Fetch subnet", + "operationId": "vpc_subnet_view", + "parameters": [ + { + "in": "path", + "name": "subnet", + "description": "Name or ID of the subnet", + "required": true, + "schema": { + "$ref": "#/components/schemas/NameOrId" + } + }, + { + "in": "query", + "name": "project", + "description": "Name or ID of the project, only required if `vpc` is provided as a `Name`", + "schema": { + "$ref": "#/components/schemas/NameOrId" + } + }, + { + "in": "query", + "name": "vpc", + "description": "Name or ID of the VPC", + "schema": { + "$ref": "#/components/schemas/NameOrId" + } + } + ], + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/VpcSubnet" + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + }, + "put": { + "tags": [ + "vpcs" + ], + "summary": "Update subnet", + "operationId": "vpc_subnet_update", + "parameters": [ + { + "in": "path", + "name": "subnet", + "description": "Name or ID of the subnet", + "required": true, + "schema": { + "$ref": "#/components/schemas/NameOrId" + } + }, + { + "in": "query", + "name": "project", + "description": "Name or ID of the project, only required if `vpc` is provided as a `Name`", + "schema": { + "$ref": "#/components/schemas/NameOrId" + } + }, + { + "in": "query", + "name": "vpc", + "description": "Name or ID of the VPC", + "schema": { + "$ref": "#/components/schemas/NameOrId" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/VpcSubnetUpdate" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/VpcSubnet" + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + }, + "delete": { + "tags": [ + "vpcs" + ], + "summary": "Delete subnet", + "operationId": "vpc_subnet_delete", + "parameters": [ + { + "in": "path", + "name": "subnet", + "description": "Name or ID of the subnet", + "required": true, + "schema": { + "$ref": "#/components/schemas/NameOrId" + } + }, + { + "in": "query", + "name": "project", + "description": "Name or ID of the project, only required if `vpc` is provided as a `Name`", + "schema": { + "$ref": "#/components/schemas/NameOrId" + } + }, + { + "in": "query", + "name": "vpc", + "description": "Name or ID of the VPC", + "schema": { + "$ref": "#/components/schemas/NameOrId" + } + } + ], + "responses": { + "204": { + "description": "successful deletion" + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, + "/v1/vpc-subnets/{subnet}/network-interfaces": { + "get": { + "tags": [ + "vpcs" + ], + "summary": "List network interfaces", + "operationId": "vpc_subnet_list_network_interfaces", + "parameters": [ + { + "in": "path", + "name": "subnet", + "description": "Name or ID of the subnet", + "required": true, + "schema": { + "$ref": "#/components/schemas/NameOrId" + } + }, + { + "in": "query", + "name": "limit", + "description": "Maximum number of items returned by a single call", + "schema": { + "nullable": true, + "type": "integer", + "format": "uint32", + "minimum": 1 + } + }, + { + "in": "query", + "name": "page_token", + "description": "Token returned by previous call to retrieve the subsequent page", + "schema": { + "nullable": true, + "type": "string" + } + }, + { + "in": "query", + "name": "project", + "description": "Name or ID of the project, only required if `vpc` is provided as a `Name`", + "schema": { + "$ref": "#/components/schemas/NameOrId" + } + }, + { + "in": "query", + "name": "sort_by", + "schema": { + "$ref": "#/components/schemas/NameOrIdSortMode" + } + }, + { + "in": "query", + "name": "vpc", + "description": "Name or ID of the VPC", + "schema": { + "$ref": "#/components/schemas/NameOrId" + } + } + ], + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InstanceNetworkInterfaceResultsPage" + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + }, + "x-dropshot-pagination": { + "required": [] + } + } + }, + "/v1/vpcs": { + "get": { + "tags": [ + "vpcs" + ], + "summary": "List VPCs", + "operationId": "vpc_list", + "parameters": [ + { + "in": "query", + "name": "limit", + "description": "Maximum number of items returned by a single call", + "schema": { + "nullable": true, + "type": "integer", + "format": "uint32", + "minimum": 1 + } + }, + { + "in": "query", + "name": "page_token", + "description": "Token returned by previous call to retrieve the subsequent page", + "schema": { + "nullable": true, + "type": "string" + } + }, + { + "in": "query", + "name": "project", + "description": "Name or ID of the project", + "schema": { + "$ref": "#/components/schemas/NameOrId" + } + }, + { + "in": "query", + "name": "sort_by", + "schema": { + "$ref": "#/components/schemas/NameOrIdSortMode" + } + } + ], + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/VpcResultsPage" + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + }, + "x-dropshot-pagination": { + "required": [ + "project" + ] + } + }, + "post": { + "tags": [ + "vpcs" + ], + "summary": "Create VPC", + "operationId": "vpc_create", + "parameters": [ + { + "in": "query", + "name": "project", + "description": "Name or ID of the project", + "required": true, + "schema": { + "$ref": "#/components/schemas/NameOrId" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/VpcCreate" + } + } + }, + "required": true + }, + "responses": { + "201": { + "description": "successful creation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Vpc" + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + }, + "/v1/vpcs/{vpc}": { + "get": { + "tags": [ + "vpcs" + ], + "summary": "Fetch VPC", + "operationId": "vpc_view", + "parameters": [ + { + "in": "path", + "name": "vpc", + "description": "Name or ID of the VPC", + "required": true, + "schema": { + "$ref": "#/components/schemas/NameOrId" + } + }, + { + "in": "query", + "name": "project", + "description": "Name or ID of the project", + "schema": { + "$ref": "#/components/schemas/NameOrId" + } + } + ], + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Vpc" + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + }, + "put": { + "tags": [ + "vpcs" + ], + "summary": "Update a VPC", + "operationId": "vpc_update", + "parameters": [ + { + "in": "path", + "name": "vpc", + "description": "Name or ID of the VPC", + "required": true, + "schema": { + "$ref": "#/components/schemas/NameOrId" + } + }, + { + "in": "query", + "name": "project", + "description": "Name or ID of the project", + "schema": { + "$ref": "#/components/schemas/NameOrId" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/VpcUpdate" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Vpc" + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + }, + "delete": { + "tags": [ + "vpcs" + ], + "summary": "Delete VPC", + "operationId": "vpc_delete", + "parameters": [ + { + "in": "path", + "name": "vpc", + "description": "Name or ID of the VPC", + "required": true, + "schema": { + "$ref": "#/components/schemas/NameOrId" + } + }, + { + "in": "query", + "name": "project", + "description": "Name or ID of the project", + "schema": { + "$ref": "#/components/schemas/NameOrId" + } + } + ], + "responses": { + "204": { + "description": "successful deletion" + }, + "4XX": { + "$ref": "#/components/responses/Error" + }, + "5XX": { + "$ref": "#/components/responses/Error" + } + } + } + } + }, + "components": { + "schemas": { + "Address": { + "description": "An address tied to an address lot.", + "type": "object", + "properties": { + "address": { + "description": "The address and prefix length of this address.", + "allOf": [ + { + "$ref": "#/components/schemas/IpNet" + } + ] + }, + "address_lot": { + "description": "The address lot this address is drawn from.", + "allOf": [ + { + "$ref": "#/components/schemas/NameOrId" + } + ] + }, + "vlan_id": { + "nullable": true, + "description": "Optional VLAN ID for this address", + "type": "integer", "format": "uint16", "minimum": 0 } @@ -14713,6 +15340,14 @@ "type": "string", "format": "date-time" }, + "transit_ips": { + "description": "A set of additional networks that this interface may send and receive traffic on.", + "default": [], + "type": "array", + "items": { + "$ref": "#/components/schemas/IpNet" + } + }, "vpc_id": { "description": "The VPC to which the interface belongs.", "type": "string", @@ -14871,6 +15506,14 @@ "description": "Make a secondary interface the instance's primary interface.\n\nIf applied to a secondary interface, that interface will become the primary on the next reboot of the instance. Note that this may have implications for routing between instances, as the new primary interface will be on a distinct subnet from the previous primary interface.\n\nNote that this can only be used to select a new primary interface for an instance. Requests to change the primary interface into a secondary will return an error.", "default": false, "type": "boolean" + }, + "transit_ips": { + "description": "A set of additional networks that this interface may send and receive traffic on.", + "default": [], + "type": "array", + "items": { + "$ref": "#/components/schemas/IpNet" + } } } }, @@ -14995,14 +15638,6 @@ } ] }, - "IpKind": { - "type": "string", - "enum": [ - "snat", - "floating", - "ephemeral" - ] - }, "IpNet": { "x-rust-type": { "crate": "oxnet", @@ -15786,7 +16421,7 @@ }, "Name": { "title": "A name unique within the parent collection", - "description": "Names must begin with a lower case ASCII letter, be composed exclusively of lowercase ASCII, uppercase ASCII, numbers, and '-', and may not end with a '-'. Names cannot be a UUID though they may contain a UUID.", + "description": "Names must begin with a lower case ASCII letter, be composed exclusively of lowercase ASCII, uppercase ASCII, numbers, and '-', and may not end with a '-'. Names cannot be a UUID, but they may contain a UUID. They can be at most 63 characters long.", "type": "string", "pattern": "^(?![0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$)^[a-z]([a-zA-Z0-9-]*[a-zA-Z0-9]+)?$", "minLength": 1, @@ -15845,6 +16480,13 @@ "subnet": { "$ref": "#/components/schemas/IpNet" }, + "transit_ips": { + "default": [], + "type": "array", + "items": { + "$ref": "#/components/schemas/IpNet" + } + }, "vni": { "$ref": "#/components/schemas/Vni" } @@ -16221,7 +16863,7 @@ "format": "ip" }, "kind": { - "$ref": "#/components/schemas/IpKind" + "$ref": "#/components/schemas/ProbeExternalIpKind" }, "last_port": { "type": "integer", @@ -16236,6 +16878,14 @@ "last_port" ] }, + "ProbeExternalIpKind": { + "type": "string", + "enum": [ + "snat", + "floating", + "ephemeral" + ] + }, "ProbeInfo": { "type": "object", "properties": { @@ -16387,105 +17037,476 @@ } }, "required": [ - "role_assignments" + "role_assignments" + ] + }, + "ProjectRoleRoleAssignment": { + "description": "Describes the assignment of a particular role on a particular resource to a particular identity (user, group, etc.)\n\nThe resource is not part of this structure. Rather, `RoleAssignment`s are put into a `Policy` and that Policy is applied to a particular resource.", + "type": "object", + "properties": { + "identity_id": { + "type": "string", + "format": "uuid" + }, + "identity_type": { + "$ref": "#/components/schemas/IdentityType" + }, + "role_name": { + "$ref": "#/components/schemas/ProjectRole" + } + }, + "required": [ + "identity_id", + "identity_type", + "role_name" + ] + }, + "ProjectUpdate": { + "description": "Updateable properties of a `Project`", + "type": "object", + "properties": { + "description": { + "nullable": true, + "type": "string" + }, + "name": { + "nullable": true, + "allOf": [ + { + "$ref": "#/components/schemas/Name" + } + ] + } + } + }, + "Quantile": { + "description": "Structure for estimating the p-quantile of a population.\n\nThis is based on the P² algorithm for estimating quantiles using constant space.\n\nThe algorithm consists of maintaining five markers: the minimum, the p/2-, p-, and (1 + p)/2 quantiles, and the maximum.", + "type": "object", + "properties": { + "desired_marker_positions": { + "description": "The desired marker positions.", + "type": "array", + "items": { + "type": "number", + "format": "double" + }, + "minItems": 5, + "maxItems": 5 + }, + "marker_heights": { + "description": "The heights of the markers.", + "type": "array", + "items": { + "type": "number", + "format": "double" + }, + "minItems": 5, + "maxItems": 5 + }, + "marker_positions": { + "description": "The positions of the markers.\n\nWe track sample size in the 5th position, as useful observations won't start until we've filled the heights at the 6th sample anyway This does deviate from the paper, but it's a more useful representation that works according to the paper's algorithm.", + "type": "array", + "items": { + "type": "integer", + "format": "uint64", + "minimum": 0 + }, + "minItems": 5, + "maxItems": 5 + }, + "p": { + "description": "The p value for the quantile.", + "type": "number", + "format": "double" + } + }, + "required": [ + "desired_marker_positions", + "marker_heights", + "marker_positions", + "p" + ] + }, + "Rack": { + "description": "View of an Rack", + "type": "object", + "properties": { + "id": { + "description": "unique, immutable, system-controlled identifier for each resource", + "type": "string", + "format": "uuid" + }, + "time_created": { + "description": "timestamp when this resource was created", + "type": "string", + "format": "date-time" + }, + "time_modified": { + "description": "timestamp when this resource was last modified", + "type": "string", + "format": "date-time" + } + }, + "required": [ + "id", + "time_created", + "time_modified" + ] + }, + "RackResultsPage": { + "description": "A single page of results", + "type": "object", + "properties": { + "items": { + "description": "list of items on this page of results", + "type": "array", + "items": { + "$ref": "#/components/schemas/Rack" + } + }, + "next_page": { + "nullable": true, + "description": "token used to fetch the next page of results (if any)", + "type": "string" + } + }, + "required": [ + "items" + ] + }, + "Role": { + "description": "View of a Role", + "type": "object", + "properties": { + "description": { + "type": "string" + }, + "name": { + "$ref": "#/components/schemas/RoleName" + } + }, + "required": [ + "description", + "name" + ] + }, + "RoleName": { + "title": "A name for a built-in role", + "description": "Role names consist of two string components separated by dot (\".\").", + "type": "string", + "pattern": "[a-z-]+\\.[a-z-]+", + "maxLength": 63 + }, + "RoleResultsPage": { + "description": "A single page of results", + "type": "object", + "properties": { + "items": { + "description": "list of items on this page of results", + "type": "array", + "items": { + "$ref": "#/components/schemas/Role" + } + }, + "next_page": { + "nullable": true, + "description": "token used to fetch the next page of results (if any)", + "type": "string" + } + }, + "required": [ + "items" + ] + }, + "Route": { + "description": "A route to a destination network through a gateway address.", + "type": "object", + "properties": { + "dst": { + "description": "The route destination.", + "allOf": [ + { + "$ref": "#/components/schemas/IpNet" + } + ] + }, + "gw": { + "description": "The route gateway.", + "type": "string", + "format": "ip" + }, + "vid": { + "nullable": true, + "description": "VLAN id the gateway is reachable over.", + "type": "integer", + "format": "uint16", + "minimum": 0 + } + }, + "required": [ + "dst", + "gw" + ] + }, + "RouteConfig": { + "description": "Route configuration data associated with a switch port configuration.", + "type": "object", + "properties": { + "routes": { + "description": "The set of routes assigned to a switch port.", + "type": "array", + "items": { + "$ref": "#/components/schemas/Route" + } + } + }, + "required": [ + "routes" ] }, - "ProjectRoleRoleAssignment": { - "description": "Describes the assignment of a particular role on a particular resource to a particular identity (user, group, etc.)\n\nThe resource is not part of this structure. Rather, `RoleAssignment`s are put into a `Policy` and that Policy is applied to a particular resource.", - "type": "object", - "properties": { - "identity_id": { - "type": "string", - "format": "uuid" + "RouteDestination": { + "description": "A `RouteDestination` is used to match traffic with a routing rule, on the destination of that traffic.\n\nWhen traffic is to be sent to a destination that is within a given `RouteDestination`, the corresponding `RouterRoute` applies, and traffic will be forward to the `RouteTarget` for that rule.", + "oneOf": [ + { + "description": "Route applies to traffic destined for a specific IP address", + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "ip" + ] + }, + "value": { + "type": "string", + "format": "ip" + } + }, + "required": [ + "type", + "value" + ] }, - "identity_type": { - "$ref": "#/components/schemas/IdentityType" + { + "description": "Route applies to traffic destined for a specific IP subnet", + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "ip_net" + ] + }, + "value": { + "$ref": "#/components/schemas/IpNet" + } + }, + "required": [ + "type", + "value" + ] }, - "role_name": { - "$ref": "#/components/schemas/ProjectRole" + { + "description": "Route applies to traffic destined for the given VPC.", + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "vpc" + ] + }, + "value": { + "$ref": "#/components/schemas/Name" + } + }, + "required": [ + "type", + "value" + ] + }, + { + "description": "Route applies to traffic", + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "subnet" + ] + }, + "value": { + "$ref": "#/components/schemas/Name" + } + }, + "required": [ + "type", + "value" + ] } - }, - "required": [ - "identity_id", - "identity_type", - "role_name" ] }, - "ProjectUpdate": { - "description": "Updateable properties of a `Project`", - "type": "object", - "properties": { - "description": { - "nullable": true, - "type": "string" + "RouteTarget": { + "description": "A `RouteTarget` describes the possible locations that traffic matching a route destination can be sent.", + "oneOf": [ + { + "description": "Forward traffic to a particular IP address.", + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "ip" + ] + }, + "value": { + "type": "string", + "format": "ip" + } + }, + "required": [ + "type", + "value" + ] }, - "name": { - "nullable": true, - "allOf": [ - { + { + "description": "Forward traffic to a VPC", + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "vpc" + ] + }, + "value": { "$ref": "#/components/schemas/Name" } + }, + "required": [ + "type", + "value" ] - } - } - }, - "Quantile": { - "description": "Structure for estimating the p-quantile of a population.\n\nThis is based on the P² algorithm for estimating quantiles using constant space.\n\nThe algorithm consists of maintaining five markers: the minimum, the p/2-, p-, and (1 + p)/2 quantiles, and the maximum.", - "type": "object", - "properties": { - "desired_marker_positions": { - "description": "The desired marker positions.", - "type": "array", - "items": { - "type": "number", - "format": "double" + }, + { + "description": "Forward traffic to a VPC Subnet", + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "subnet" + ] + }, + "value": { + "$ref": "#/components/schemas/Name" + } }, - "minItems": 5, - "maxItems": 5 + "required": [ + "type", + "value" + ] }, - "marker_heights": { - "description": "The heights of the markers.", - "type": "array", - "items": { - "type": "number", - "format": "double" + { + "description": "Forward traffic to a specific instance", + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "instance" + ] + }, + "value": { + "$ref": "#/components/schemas/Name" + } }, - "minItems": 5, - "maxItems": 5 + "required": [ + "type", + "value" + ] }, - "marker_positions": { - "description": "The positions of the markers.\n\nWe track sample size in the 5th position, as useful observations won't start until we've filled the heights at the 6th sample anyway This does deviate from the paper, but it's a more useful representation that works according to the paper's algorithm.", - "type": "array", - "items": { - "type": "integer", - "format": "uint64", - "minimum": 0 + { + "description": "Forward traffic to an internet gateway", + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "internet_gateway" + ] + }, + "value": { + "$ref": "#/components/schemas/Name" + } }, - "minItems": 5, - "maxItems": 5 + "required": [ + "type", + "value" + ] }, - "p": { - "description": "The p value for the quantile.", - "type": "number", - "format": "double" + { + "description": "Drop matching traffic", + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "drop" + ] + } + }, + "required": [ + "type" + ] } - }, - "required": [ - "desired_marker_positions", - "marker_heights", - "marker_positions", - "p" ] }, - "Rack": { - "description": "View of an Rack", + "RouterRoute": { + "description": "A route defines a rule that governs where traffic should be sent based on its destination.", "type": "object", "properties": { + "description": { + "description": "human-readable free-form text about a resource", + "type": "string" + }, + "destination": { + "description": "Selects which traffic this routing rule will apply to.", + "allOf": [ + { + "$ref": "#/components/schemas/RouteDestination" + } + ] + }, "id": { "description": "unique, immutable, system-controlled identifier for each resource", "type": "string", "format": "uuid" }, + "kind": { + "description": "Describes the kind of router. Set at creation. `read-only`", + "allOf": [ + { + "$ref": "#/components/schemas/RouterRouteKind" + } + ] + }, + "name": { + "description": "unique, mutable, user-controlled identifier for each resource", + "allOf": [ + { + "$ref": "#/components/schemas/Name" + } + ] + }, + "target": { + "description": "The location that matched packets should be forwarded to.", + "allOf": [ + { + "$ref": "#/components/schemas/RouteTarget" + } + ] + }, "time_created": { "description": "timestamp when this resource was created", "type": "string", @@ -16495,59 +17516,93 @@ "description": "timestamp when this resource was last modified", "type": "string", "format": "date-time" + }, + "vpc_router_id": { + "description": "The ID of the VPC Router to which the route belongs", + "type": "string", + "format": "uuid" } }, "required": [ + "description", + "destination", "id", + "kind", + "name", + "target", "time_created", - "time_modified" - ] - }, - "RackResultsPage": { - "description": "A single page of results", - "type": "object", - "properties": { - "items": { - "description": "list of items on this page of results", - "type": "array", - "items": { - "$ref": "#/components/schemas/Rack" - } - }, - "next_page": { - "nullable": true, - "description": "token used to fetch the next page of results (if any)", - "type": "string" - } - }, - "required": [ - "items" + "time_modified", + "vpc_router_id" ] }, - "Role": { - "description": "View of a Role", + "RouterRouteCreate": { + "description": "Create-time parameters for a `RouterRoute`", "type": "object", "properties": { "description": { "type": "string" }, + "destination": { + "description": "Selects which traffic this routing rule will apply to.", + "allOf": [ + { + "$ref": "#/components/schemas/RouteDestination" + } + ] + }, "name": { - "$ref": "#/components/schemas/RoleName" + "$ref": "#/components/schemas/Name" + }, + "target": { + "description": "The location that matched packets should be forwarded to.", + "allOf": [ + { + "$ref": "#/components/schemas/RouteTarget" + } + ] } }, "required": [ "description", - "name" + "destination", + "name", + "target" ] }, - "RoleName": { - "title": "A name for a built-in role", - "description": "Role names consist of two string components separated by dot (\".\").", - "type": "string", - "pattern": "[a-z-]+\\.[a-z-]+", - "maxLength": 63 + "RouterRouteKind": { + "description": "The kind of a `RouterRoute`\n\nThe kind determines certain attributes such as if the route is modifiable and describes how or where the route was created.", + "oneOf": [ + { + "description": "Determines the default destination of traffic, such as whether it goes to the internet or not.\n\n`Destination: An Internet Gateway` `Modifiable: true`", + "type": "string", + "enum": [ + "default" + ] + }, + { + "description": "Automatically added for each VPC Subnet in the VPC\n\n`Destination: A VPC Subnet` `Modifiable: false`", + "type": "string", + "enum": [ + "vpc_subnet" + ] + }, + { + "description": "Automatically added when VPC peering is established\n\n`Destination: A different VPC` `Modifiable: false`", + "type": "string", + "enum": [ + "vpc_peering" + ] + }, + { + "description": "Created by a user; see `RouteTarget`\n\n`Destination: User defined` `Modifiable: true`", + "type": "string", + "enum": [ + "custom" + ] + } + ] }, - "RoleResultsPage": { + "RouterRouteResultsPage": { "description": "A single page of results", "type": "object", "properties": { @@ -16555,7 +17610,7 @@ "description": "list of items on this page of results", "type": "array", "items": { - "$ref": "#/components/schemas/Role" + "$ref": "#/components/schemas/RouterRoute" } }, "next_page": { @@ -16568,50 +17623,42 @@ "items" ] }, - "Route": { - "description": "A route to a destination network through a gateway address.", + "RouterRouteUpdate": { + "description": "Updateable properties of a `RouterRoute`", "type": "object", "properties": { - "dst": { - "description": "The route destination.", + "description": { + "nullable": true, + "type": "string" + }, + "destination": { + "description": "Selects which traffic this routing rule will apply to.", "allOf": [ { - "$ref": "#/components/schemas/IpNet" + "$ref": "#/components/schemas/RouteDestination" } ] }, - "gw": { - "description": "The route gateway.", - "type": "string", - "format": "ip" - }, - "vid": { + "name": { "nullable": true, - "description": "VLAN id the gateway is reachable over.", - "type": "integer", - "format": "uint16", - "minimum": 0 - } - }, - "required": [ - "dst", - "gw" - ] - }, - "RouteConfig": { - "description": "Route configuration data associated with a switch port configuration.", - "type": "object", - "properties": { - "routes": { - "description": "The set of routes assigned to a switch port.", - "type": "array", - "items": { - "$ref": "#/components/schemas/Route" - } + "allOf": [ + { + "$ref": "#/components/schemas/Name" + } + ] + }, + "target": { + "description": "The location that matched packets should be forwarded to.", + "allOf": [ + { + "$ref": "#/components/schemas/RouteTarget" + } + ] } }, "required": [ - "routes" + "destination", + "target" ] }, "SamlIdentityProvider": { @@ -18752,7 +19799,9 @@ "type": "string", "enum": [ "count", - "bytes" + "bytes", + "seconds", + "nanoseconds" ] }, "User": { @@ -18780,7 +19829,7 @@ ] }, "UserBuiltin": { - "description": "View of a Built-in User\n\nA Built-in User is explicitly created as opposed to being derived from an Identify Provider.", + "description": "View of a Built-in User\n\nBuilt-in users are identities internal to the system, used when the control plane performs actions autonomously", "type": "object", "properties": { "description": { @@ -18868,7 +19917,7 @@ }, "UserId": { "title": "A name unique within the parent collection", - "description": "Names must begin with a lower case ASCII letter, be composed exclusively of lowercase ASCII, uppercase ASCII, numbers, and '-', and may not end with a '-'. Names cannot be a UUID though they may contain a UUID.", + "description": "Names must begin with a lower case ASCII letter, be composed exclusively of lowercase ASCII, uppercase ASCII, numbers, and '-', and may not end with a '-'. Names cannot be a UUID, but they may contain a UUID. They can be at most 63 characters long.", "type": "string", "pattern": "^(?![0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$)^[a-z]([a-zA-Z0-9-]*[a-zA-Z0-9]+)?$", "minLength": 1, @@ -19759,10 +20808,128 @@ "items" ] }, + "VpcRouter": { + "description": "A VPC router defines a series of rules that indicate where traffic should be sent depending on its destination.", + "type": "object", + "properties": { + "description": { + "description": "human-readable free-form text about a resource", + "type": "string" + }, + "id": { + "description": "unique, immutable, system-controlled identifier for each resource", + "type": "string", + "format": "uuid" + }, + "kind": { + "$ref": "#/components/schemas/VpcRouterKind" + }, + "name": { + "description": "unique, mutable, user-controlled identifier for each resource", + "allOf": [ + { + "$ref": "#/components/schemas/Name" + } + ] + }, + "time_created": { + "description": "timestamp when this resource was created", + "type": "string", + "format": "date-time" + }, + "time_modified": { + "description": "timestamp when this resource was last modified", + "type": "string", + "format": "date-time" + }, + "vpc_id": { + "description": "The VPC to which the router belongs.", + "type": "string", + "format": "uuid" + } + }, + "required": [ + "description", + "id", + "kind", + "name", + "time_created", + "time_modified", + "vpc_id" + ] + }, + "VpcRouterCreate": { + "description": "Create-time parameters for a `VpcRouter`", + "type": "object", + "properties": { + "description": { + "type": "string" + }, + "name": { + "$ref": "#/components/schemas/Name" + } + }, + "required": [ + "description", + "name" + ] + }, + "VpcRouterKind": { + "type": "string", + "enum": [ + "system", + "custom" + ] + }, + "VpcRouterResultsPage": { + "description": "A single page of results", + "type": "object", + "properties": { + "items": { + "description": "list of items on this page of results", + "type": "array", + "items": { + "$ref": "#/components/schemas/VpcRouter" + } + }, + "next_page": { + "nullable": true, + "description": "token used to fetch the next page of results (if any)", + "type": "string" + } + }, + "required": [ + "items" + ] + }, + "VpcRouterUpdate": { + "description": "Updateable properties of a `VpcRouter`", + "type": "object", + "properties": { + "description": { + "nullable": true, + "type": "string" + }, + "name": { + "nullable": true, + "allOf": [ + { + "$ref": "#/components/schemas/Name" + } + ] + } + } + }, "VpcSubnet": { - "description": "A VPC subnet represents a logical grouping for instances that allows network traffic between them, within a IPv4 subnetwork or optionall an IPv6 subnetwork.", + "description": "A VPC subnet represents a logical grouping for instances that allows network traffic between them, within a IPv4 subnetwork or optionally an IPv6 subnetwork.", "type": "object", "properties": { + "custom_router_id": { + "nullable": true, + "description": "ID for an attached custom router.", + "type": "string", + "format": "uuid" + }, "description": { "description": "human-readable free-form text about a resource", "type": "string" @@ -19827,6 +20994,15 @@ "description": "Create-time parameters for a `VpcSubnet`", "type": "object", "properties": { + "custom_router": { + "nullable": true, + "description": "An optional router, used to direct packets sent from hosts in this subnet to any destination address.\n\nCustom routers apply in addition to the VPC-wide *system* router, and have higher priority than the system router for an otherwise equal-prefix-length match.", + "allOf": [ + { + "$ref": "#/components/schemas/NameOrId" + } + ] + }, "description": { "type": "string" }, @@ -19882,6 +21058,15 @@ "description": "Updateable properties of a `VpcSubnet`", "type": "object", "properties": { + "custom_router": { + "nullable": true, + "description": "An optional router, used to direct packets sent from hosts in this subnet to any destination address.", + "allOf": [ + { + "$ref": "#/components/schemas/NameOrId" + } + ] + }, "description": { "nullable": true, "type": "string" diff --git a/sdk-httpmock/Cargo.toml b/sdk-httpmock/Cargo.toml index 9db7bf61..d30a9ca2 100644 --- a/sdk-httpmock/Cargo.toml +++ b/sdk-httpmock/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "oxide-httpmock" description = "httpmock for the Oxide rack API" -version = "0.5.0+20240502.0" +version = "0.6.0+20240710.0" edition = "2021" license = "MPL-2.0" repository = "https://github.com/oxidecomputer/oxide.rs" @@ -13,4 +13,4 @@ httpmock = { workspace = true } oxide = { workspace = true } regex = { workspace = true } serde_json = { workspace = true } -uuid = { workspace = true } \ No newline at end of file +uuid = { workspace = true } diff --git a/sdk-httpmock/src/generated_httpmock.rs b/sdk-httpmock/src/generated_httpmock.rs index 5f0e243f..f674a2ee 100644 --- a/sdk-httpmock/src/generated_httpmock.rs +++ b/sdk-httpmock/src/generated_httpmock.rs @@ -10747,11 +10747,11 @@ pub mod operations { } } - pub struct NetworkingBgpAnnounceSetCreateWhen(httpmock::When); - impl NetworkingBgpAnnounceSetCreateWhen { + pub struct NetworkingBgpAnnounceSetUpdateWhen(httpmock::When); + impl NetworkingBgpAnnounceSetUpdateWhen { pub fn new(inner: httpmock::When) -> Self { Self( - inner.method(httpmock::Method::POST).path_matches( + inner.method(httpmock::Method::PUT).path_matches( regex::Regex::new("^/v1/system/networking/bgp-announce$").unwrap(), ), ) @@ -10766,8 +10766,8 @@ pub mod operations { } } - pub struct NetworkingBgpAnnounceSetCreateThen(httpmock::Then); - impl NetworkingBgpAnnounceSetCreateThen { + pub struct NetworkingBgpAnnounceSetUpdateThen(httpmock::Then); + impl NetworkingBgpAnnounceSetUpdateThen { pub fn new(inner: httpmock::Then) -> Self { Self(inner) } @@ -13276,57 +13276,1040 @@ pub mod operations { } } - pub fn limit(self, value: T) -> Self - where - T: Into>, - { - if let Some(value) = value.into() { - Self(self.0.query_param("limit", value.to_string())) - } else { - Self(self.0.matches(|req| { - req.query_params - .as_ref() - .and_then(|qs| qs.iter().find(|(key, _)| key == "limit")) - .is_none() - })) - } + pub fn limit(self, value: T) -> Self + where + T: Into>, + { + if let Some(value) = value.into() { + Self(self.0.query_param("limit", value.to_string())) + } else { + Self(self.0.matches(|req| { + req.query_params + .as_ref() + .and_then(|qs| qs.iter().find(|(key, _)| key == "limit")) + .is_none() + })) + } + } + + pub fn page_token<'a, T>(self, value: T) -> Self + where + T: Into>, + { + if let Some(value) = value.into() { + Self(self.0.query_param("page_token", value.to_string())) + } else { + Self(self.0.matches(|req| { + req.query_params + .as_ref() + .and_then(|qs| qs.iter().find(|(key, _)| key == "page_token")) + .is_none() + })) + } + } + + pub fn sort_by(self, value: T) -> Self + where + T: Into>, + { + if let Some(value) = value.into() { + Self(self.0.query_param("sort_by", value.to_string())) + } else { + Self(self.0.matches(|req| { + req.query_params + .as_ref() + .and_then(|qs| qs.iter().find(|(key, _)| key == "sort_by")) + .is_none() + })) + } + } + } + + pub struct UserListThen(httpmock::Then); + impl UserListThen { + pub fn new(inner: httpmock::Then) -> Self { + Self(inner) + } + + pub fn into_inner(self) -> httpmock::Then { + self.0 + } + + pub fn ok(self, value: &types::UserResultsPage) -> Self { + Self( + self.0 + .status(200u16) + .header("content-type", "application/json") + .json_body_obj(value), + ) + } + + pub fn client_error(self, status: u16, value: &types::Error) -> Self { + assert_eq!(status / 100u16, 4u16); + Self( + self.0 + .status(status) + .header("content-type", "application/json") + .json_body_obj(value), + ) + } + + pub fn server_error(self, status: u16, value: &types::Error) -> Self { + assert_eq!(status / 100u16, 5u16); + Self( + self.0 + .status(status) + .header("content-type", "application/json") + .json_body_obj(value), + ) + } + } + + pub struct UtilizationViewWhen(httpmock::When); + impl UtilizationViewWhen { + pub fn new(inner: httpmock::When) -> Self { + Self( + inner + .method(httpmock::Method::GET) + .path_matches(regex::Regex::new("^/v1/utilization$").unwrap()), + ) + } + + pub fn into_inner(self) -> httpmock::When { + self.0 + } + } + + pub struct UtilizationViewThen(httpmock::Then); + impl UtilizationViewThen { + pub fn new(inner: httpmock::Then) -> Self { + Self(inner) + } + + pub fn into_inner(self) -> httpmock::Then { + self.0 + } + + pub fn ok(self, value: &types::Utilization) -> Self { + Self( + self.0 + .status(200u16) + .header("content-type", "application/json") + .json_body_obj(value), + ) + } + + pub fn client_error(self, status: u16, value: &types::Error) -> Self { + assert_eq!(status / 100u16, 4u16); + Self( + self.0 + .status(status) + .header("content-type", "application/json") + .json_body_obj(value), + ) + } + + pub fn server_error(self, status: u16, value: &types::Error) -> Self { + assert_eq!(status / 100u16, 5u16); + Self( + self.0 + .status(status) + .header("content-type", "application/json") + .json_body_obj(value), + ) + } + } + + pub struct VpcFirewallRulesViewWhen(httpmock::When); + impl VpcFirewallRulesViewWhen { + pub fn new(inner: httpmock::When) -> Self { + Self( + inner + .method(httpmock::Method::GET) + .path_matches(regex::Regex::new("^/v1/vpc-firewall-rules$").unwrap()), + ) + } + + pub fn into_inner(self) -> httpmock::When { + self.0 + } + + pub fn project<'a, T>(self, value: T) -> Self + where + T: Into>, + { + if let Some(value) = value.into() { + Self(self.0.query_param("project", value.to_string())) + } else { + Self(self.0.matches(|req| { + req.query_params + .as_ref() + .and_then(|qs| qs.iter().find(|(key, _)| key == "project")) + .is_none() + })) + } + } + + pub fn vpc(self, value: &types::NameOrId) -> Self { + Self(self.0.query_param("vpc", value.to_string())) + } + } + + pub struct VpcFirewallRulesViewThen(httpmock::Then); + impl VpcFirewallRulesViewThen { + pub fn new(inner: httpmock::Then) -> Self { + Self(inner) + } + + pub fn into_inner(self) -> httpmock::Then { + self.0 + } + + pub fn ok(self, value: &types::VpcFirewallRules) -> Self { + Self( + self.0 + .status(200u16) + .header("content-type", "application/json") + .json_body_obj(value), + ) + } + + pub fn client_error(self, status: u16, value: &types::Error) -> Self { + assert_eq!(status / 100u16, 4u16); + Self( + self.0 + .status(status) + .header("content-type", "application/json") + .json_body_obj(value), + ) + } + + pub fn server_error(self, status: u16, value: &types::Error) -> Self { + assert_eq!(status / 100u16, 5u16); + Self( + self.0 + .status(status) + .header("content-type", "application/json") + .json_body_obj(value), + ) + } + } + + pub struct VpcFirewallRulesUpdateWhen(httpmock::When); + impl VpcFirewallRulesUpdateWhen { + pub fn new(inner: httpmock::When) -> Self { + Self( + inner + .method(httpmock::Method::PUT) + .path_matches(regex::Regex::new("^/v1/vpc-firewall-rules$").unwrap()), + ) + } + + pub fn into_inner(self) -> httpmock::When { + self.0 + } + + pub fn project<'a, T>(self, value: T) -> Self + where + T: Into>, + { + if let Some(value) = value.into() { + Self(self.0.query_param("project", value.to_string())) + } else { + Self(self.0.matches(|req| { + req.query_params + .as_ref() + .and_then(|qs| qs.iter().find(|(key, _)| key == "project")) + .is_none() + })) + } + } + + pub fn vpc(self, value: &types::NameOrId) -> Self { + Self(self.0.query_param("vpc", value.to_string())) + } + + pub fn body(self, value: &types::VpcFirewallRuleUpdateParams) -> Self { + Self(self.0.json_body_obj(value)) + } + } + + pub struct VpcFirewallRulesUpdateThen(httpmock::Then); + impl VpcFirewallRulesUpdateThen { + pub fn new(inner: httpmock::Then) -> Self { + Self(inner) + } + + pub fn into_inner(self) -> httpmock::Then { + self.0 + } + + pub fn ok(self, value: &types::VpcFirewallRules) -> Self { + Self( + self.0 + .status(200u16) + .header("content-type", "application/json") + .json_body_obj(value), + ) + } + + pub fn client_error(self, status: u16, value: &types::Error) -> Self { + assert_eq!(status / 100u16, 4u16); + Self( + self.0 + .status(status) + .header("content-type", "application/json") + .json_body_obj(value), + ) + } + + pub fn server_error(self, status: u16, value: &types::Error) -> Self { + assert_eq!(status / 100u16, 5u16); + Self( + self.0 + .status(status) + .header("content-type", "application/json") + .json_body_obj(value), + ) + } + } + + pub struct VpcRouterRouteListWhen(httpmock::When); + impl VpcRouterRouteListWhen { + pub fn new(inner: httpmock::When) -> Self { + Self( + inner + .method(httpmock::Method::GET) + .path_matches(regex::Regex::new("^/v1/vpc-router-routes$").unwrap()), + ) + } + + pub fn into_inner(self) -> httpmock::When { + self.0 + } + + pub fn limit(self, value: T) -> Self + where + T: Into>, + { + if let Some(value) = value.into() { + Self(self.0.query_param("limit", value.to_string())) + } else { + Self(self.0.matches(|req| { + req.query_params + .as_ref() + .and_then(|qs| qs.iter().find(|(key, _)| key == "limit")) + .is_none() + })) + } + } + + pub fn page_token<'a, T>(self, value: T) -> Self + where + T: Into>, + { + if let Some(value) = value.into() { + Self(self.0.query_param("page_token", value.to_string())) + } else { + Self(self.0.matches(|req| { + req.query_params + .as_ref() + .and_then(|qs| qs.iter().find(|(key, _)| key == "page_token")) + .is_none() + })) + } + } + + pub fn project<'a, T>(self, value: T) -> Self + where + T: Into>, + { + if let Some(value) = value.into() { + Self(self.0.query_param("project", value.to_string())) + } else { + Self(self.0.matches(|req| { + req.query_params + .as_ref() + .and_then(|qs| qs.iter().find(|(key, _)| key == "project")) + .is_none() + })) + } + } + + pub fn router<'a, T>(self, value: T) -> Self + where + T: Into>, + { + if let Some(value) = value.into() { + Self(self.0.query_param("router", value.to_string())) + } else { + Self(self.0.matches(|req| { + req.query_params + .as_ref() + .and_then(|qs| qs.iter().find(|(key, _)| key == "router")) + .is_none() + })) + } + } + + pub fn sort_by(self, value: T) -> Self + where + T: Into>, + { + if let Some(value) = value.into() { + Self(self.0.query_param("sort_by", value.to_string())) + } else { + Self(self.0.matches(|req| { + req.query_params + .as_ref() + .and_then(|qs| qs.iter().find(|(key, _)| key == "sort_by")) + .is_none() + })) + } + } + + pub fn vpc<'a, T>(self, value: T) -> Self + where + T: Into>, + { + if let Some(value) = value.into() { + Self(self.0.query_param("vpc", value.to_string())) + } else { + Self(self.0.matches(|req| { + req.query_params + .as_ref() + .and_then(|qs| qs.iter().find(|(key, _)| key == "vpc")) + .is_none() + })) + } + } + } + + pub struct VpcRouterRouteListThen(httpmock::Then); + impl VpcRouterRouteListThen { + pub fn new(inner: httpmock::Then) -> Self { + Self(inner) + } + + pub fn into_inner(self) -> httpmock::Then { + self.0 + } + + pub fn ok(self, value: &types::RouterRouteResultsPage) -> Self { + Self( + self.0 + .status(200u16) + .header("content-type", "application/json") + .json_body_obj(value), + ) + } + + pub fn client_error(self, status: u16, value: &types::Error) -> Self { + assert_eq!(status / 100u16, 4u16); + Self( + self.0 + .status(status) + .header("content-type", "application/json") + .json_body_obj(value), + ) + } + + pub fn server_error(self, status: u16, value: &types::Error) -> Self { + assert_eq!(status / 100u16, 5u16); + Self( + self.0 + .status(status) + .header("content-type", "application/json") + .json_body_obj(value), + ) + } + } + + pub struct VpcRouterRouteCreateWhen(httpmock::When); + impl VpcRouterRouteCreateWhen { + pub fn new(inner: httpmock::When) -> Self { + Self( + inner + .method(httpmock::Method::POST) + .path_matches(regex::Regex::new("^/v1/vpc-router-routes$").unwrap()), + ) + } + + pub fn into_inner(self) -> httpmock::When { + self.0 + } + + pub fn project<'a, T>(self, value: T) -> Self + where + T: Into>, + { + if let Some(value) = value.into() { + Self(self.0.query_param("project", value.to_string())) + } else { + Self(self.0.matches(|req| { + req.query_params + .as_ref() + .and_then(|qs| qs.iter().find(|(key, _)| key == "project")) + .is_none() + })) + } + } + + pub fn router(self, value: &types::NameOrId) -> Self { + Self(self.0.query_param("router", value.to_string())) + } + + pub fn vpc<'a, T>(self, value: T) -> Self + where + T: Into>, + { + if let Some(value) = value.into() { + Self(self.0.query_param("vpc", value.to_string())) + } else { + Self(self.0.matches(|req| { + req.query_params + .as_ref() + .and_then(|qs| qs.iter().find(|(key, _)| key == "vpc")) + .is_none() + })) + } + } + + pub fn body(self, value: &types::RouterRouteCreate) -> Self { + Self(self.0.json_body_obj(value)) + } + } + + pub struct VpcRouterRouteCreateThen(httpmock::Then); + impl VpcRouterRouteCreateThen { + pub fn new(inner: httpmock::Then) -> Self { + Self(inner) + } + + pub fn into_inner(self) -> httpmock::Then { + self.0 + } + + pub fn created(self, value: &types::RouterRoute) -> Self { + Self( + self.0 + .status(201u16) + .header("content-type", "application/json") + .json_body_obj(value), + ) + } + + pub fn client_error(self, status: u16, value: &types::Error) -> Self { + assert_eq!(status / 100u16, 4u16); + Self( + self.0 + .status(status) + .header("content-type", "application/json") + .json_body_obj(value), + ) + } + + pub fn server_error(self, status: u16, value: &types::Error) -> Self { + assert_eq!(status / 100u16, 5u16); + Self( + self.0 + .status(status) + .header("content-type", "application/json") + .json_body_obj(value), + ) + } + } + + pub struct VpcRouterRouteViewWhen(httpmock::When); + impl VpcRouterRouteViewWhen { + pub fn new(inner: httpmock::When) -> Self { + Self( + inner + .method(httpmock::Method::GET) + .path_matches(regex::Regex::new("^/v1/vpc-router-routes/[^/]*$").unwrap()), + ) + } + + pub fn into_inner(self) -> httpmock::When { + self.0 + } + + pub fn route(self, value: &types::NameOrId) -> Self { + let re = regex::Regex::new(&format!("^/v1/vpc-router-routes/{}$", value.to_string())) + .unwrap(); + Self(self.0.path_matches(re)) + } + + pub fn project<'a, T>(self, value: T) -> Self + where + T: Into>, + { + if let Some(value) = value.into() { + Self(self.0.query_param("project", value.to_string())) + } else { + Self(self.0.matches(|req| { + req.query_params + .as_ref() + .and_then(|qs| qs.iter().find(|(key, _)| key == "project")) + .is_none() + })) + } + } + + pub fn router(self, value: &types::NameOrId) -> Self { + Self(self.0.query_param("router", value.to_string())) + } + + pub fn vpc<'a, T>(self, value: T) -> Self + where + T: Into>, + { + if let Some(value) = value.into() { + Self(self.0.query_param("vpc", value.to_string())) + } else { + Self(self.0.matches(|req| { + req.query_params + .as_ref() + .and_then(|qs| qs.iter().find(|(key, _)| key == "vpc")) + .is_none() + })) + } + } + } + + pub struct VpcRouterRouteViewThen(httpmock::Then); + impl VpcRouterRouteViewThen { + pub fn new(inner: httpmock::Then) -> Self { + Self(inner) + } + + pub fn into_inner(self) -> httpmock::Then { + self.0 + } + + pub fn ok(self, value: &types::RouterRoute) -> Self { + Self( + self.0 + .status(200u16) + .header("content-type", "application/json") + .json_body_obj(value), + ) + } + + pub fn client_error(self, status: u16, value: &types::Error) -> Self { + assert_eq!(status / 100u16, 4u16); + Self( + self.0 + .status(status) + .header("content-type", "application/json") + .json_body_obj(value), + ) + } + + pub fn server_error(self, status: u16, value: &types::Error) -> Self { + assert_eq!(status / 100u16, 5u16); + Self( + self.0 + .status(status) + .header("content-type", "application/json") + .json_body_obj(value), + ) + } + } + + pub struct VpcRouterRouteUpdateWhen(httpmock::When); + impl VpcRouterRouteUpdateWhen { + pub fn new(inner: httpmock::When) -> Self { + Self( + inner + .method(httpmock::Method::PUT) + .path_matches(regex::Regex::new("^/v1/vpc-router-routes/[^/]*$").unwrap()), + ) + } + + pub fn into_inner(self) -> httpmock::When { + self.0 + } + + pub fn route(self, value: &types::NameOrId) -> Self { + let re = regex::Regex::new(&format!("^/v1/vpc-router-routes/{}$", value.to_string())) + .unwrap(); + Self(self.0.path_matches(re)) + } + + pub fn project<'a, T>(self, value: T) -> Self + where + T: Into>, + { + if let Some(value) = value.into() { + Self(self.0.query_param("project", value.to_string())) + } else { + Self(self.0.matches(|req| { + req.query_params + .as_ref() + .and_then(|qs| qs.iter().find(|(key, _)| key == "project")) + .is_none() + })) + } + } + + pub fn router<'a, T>(self, value: T) -> Self + where + T: Into>, + { + if let Some(value) = value.into() { + Self(self.0.query_param("router", value.to_string())) + } else { + Self(self.0.matches(|req| { + req.query_params + .as_ref() + .and_then(|qs| qs.iter().find(|(key, _)| key == "router")) + .is_none() + })) + } + } + + pub fn vpc<'a, T>(self, value: T) -> Self + where + T: Into>, + { + if let Some(value) = value.into() { + Self(self.0.query_param("vpc", value.to_string())) + } else { + Self(self.0.matches(|req| { + req.query_params + .as_ref() + .and_then(|qs| qs.iter().find(|(key, _)| key == "vpc")) + .is_none() + })) + } + } + + pub fn body(self, value: &types::RouterRouteUpdate) -> Self { + Self(self.0.json_body_obj(value)) + } + } + + pub struct VpcRouterRouteUpdateThen(httpmock::Then); + impl VpcRouterRouteUpdateThen { + pub fn new(inner: httpmock::Then) -> Self { + Self(inner) + } + + pub fn into_inner(self) -> httpmock::Then { + self.0 + } + + pub fn ok(self, value: &types::RouterRoute) -> Self { + Self( + self.0 + .status(200u16) + .header("content-type", "application/json") + .json_body_obj(value), + ) + } + + pub fn client_error(self, status: u16, value: &types::Error) -> Self { + assert_eq!(status / 100u16, 4u16); + Self( + self.0 + .status(status) + .header("content-type", "application/json") + .json_body_obj(value), + ) + } + + pub fn server_error(self, status: u16, value: &types::Error) -> Self { + assert_eq!(status / 100u16, 5u16); + Self( + self.0 + .status(status) + .header("content-type", "application/json") + .json_body_obj(value), + ) + } + } + + pub struct VpcRouterRouteDeleteWhen(httpmock::When); + impl VpcRouterRouteDeleteWhen { + pub fn new(inner: httpmock::When) -> Self { + Self( + inner + .method(httpmock::Method::DELETE) + .path_matches(regex::Regex::new("^/v1/vpc-router-routes/[^/]*$").unwrap()), + ) + } + + pub fn into_inner(self) -> httpmock::When { + self.0 + } + + pub fn route(self, value: &types::NameOrId) -> Self { + let re = regex::Regex::new(&format!("^/v1/vpc-router-routes/{}$", value.to_string())) + .unwrap(); + Self(self.0.path_matches(re)) + } + + pub fn project<'a, T>(self, value: T) -> Self + where + T: Into>, + { + if let Some(value) = value.into() { + Self(self.0.query_param("project", value.to_string())) + } else { + Self(self.0.matches(|req| { + req.query_params + .as_ref() + .and_then(|qs| qs.iter().find(|(key, _)| key == "project")) + .is_none() + })) + } + } + + pub fn router<'a, T>(self, value: T) -> Self + where + T: Into>, + { + if let Some(value) = value.into() { + Self(self.0.query_param("router", value.to_string())) + } else { + Self(self.0.matches(|req| { + req.query_params + .as_ref() + .and_then(|qs| qs.iter().find(|(key, _)| key == "router")) + .is_none() + })) + } + } + + pub fn vpc<'a, T>(self, value: T) -> Self + where + T: Into>, + { + if let Some(value) = value.into() { + Self(self.0.query_param("vpc", value.to_string())) + } else { + Self(self.0.matches(|req| { + req.query_params + .as_ref() + .and_then(|qs| qs.iter().find(|(key, _)| key == "vpc")) + .is_none() + })) + } + } + } + + pub struct VpcRouterRouteDeleteThen(httpmock::Then); + impl VpcRouterRouteDeleteThen { + pub fn new(inner: httpmock::Then) -> Self { + Self(inner) + } + + pub fn into_inner(self) -> httpmock::Then { + self.0 + } + + pub fn no_content(self) -> Self { + Self(self.0.status(204u16)) + } + + pub fn client_error(self, status: u16, value: &types::Error) -> Self { + assert_eq!(status / 100u16, 4u16); + Self( + self.0 + .status(status) + .header("content-type", "application/json") + .json_body_obj(value), + ) + } + + pub fn server_error(self, status: u16, value: &types::Error) -> Self { + assert_eq!(status / 100u16, 5u16); + Self( + self.0 + .status(status) + .header("content-type", "application/json") + .json_body_obj(value), + ) + } + } + + pub struct VpcRouterListWhen(httpmock::When); + impl VpcRouterListWhen { + pub fn new(inner: httpmock::When) -> Self { + Self( + inner + .method(httpmock::Method::GET) + .path_matches(regex::Regex::new("^/v1/vpc-routers$").unwrap()), + ) + } + + pub fn into_inner(self) -> httpmock::When { + self.0 + } + + pub fn limit(self, value: T) -> Self + where + T: Into>, + { + if let Some(value) = value.into() { + Self(self.0.query_param("limit", value.to_string())) + } else { + Self(self.0.matches(|req| { + req.query_params + .as_ref() + .and_then(|qs| qs.iter().find(|(key, _)| key == "limit")) + .is_none() + })) + } + } + + pub fn page_token<'a, T>(self, value: T) -> Self + where + T: Into>, + { + if let Some(value) = value.into() { + Self(self.0.query_param("page_token", value.to_string())) + } else { + Self(self.0.matches(|req| { + req.query_params + .as_ref() + .and_then(|qs| qs.iter().find(|(key, _)| key == "page_token")) + .is_none() + })) + } + } + + pub fn project<'a, T>(self, value: T) -> Self + where + T: Into>, + { + if let Some(value) = value.into() { + Self(self.0.query_param("project", value.to_string())) + } else { + Self(self.0.matches(|req| { + req.query_params + .as_ref() + .and_then(|qs| qs.iter().find(|(key, _)| key == "project")) + .is_none() + })) + } + } + + pub fn sort_by(self, value: T) -> Self + where + T: Into>, + { + if let Some(value) = value.into() { + Self(self.0.query_param("sort_by", value.to_string())) + } else { + Self(self.0.matches(|req| { + req.query_params + .as_ref() + .and_then(|qs| qs.iter().find(|(key, _)| key == "sort_by")) + .is_none() + })) + } + } + + pub fn vpc<'a, T>(self, value: T) -> Self + where + T: Into>, + { + if let Some(value) = value.into() { + Self(self.0.query_param("vpc", value.to_string())) + } else { + Self(self.0.matches(|req| { + req.query_params + .as_ref() + .and_then(|qs| qs.iter().find(|(key, _)| key == "vpc")) + .is_none() + })) + } + } + } + + pub struct VpcRouterListThen(httpmock::Then); + impl VpcRouterListThen { + pub fn new(inner: httpmock::Then) -> Self { + Self(inner) + } + + pub fn into_inner(self) -> httpmock::Then { + self.0 + } + + pub fn ok(self, value: &types::VpcRouterResultsPage) -> Self { + Self( + self.0 + .status(200u16) + .header("content-type", "application/json") + .json_body_obj(value), + ) + } + + pub fn client_error(self, status: u16, value: &types::Error) -> Self { + assert_eq!(status / 100u16, 4u16); + Self( + self.0 + .status(status) + .header("content-type", "application/json") + .json_body_obj(value), + ) + } + + pub fn server_error(self, status: u16, value: &types::Error) -> Self { + assert_eq!(status / 100u16, 5u16); + Self( + self.0 + .status(status) + .header("content-type", "application/json") + .json_body_obj(value), + ) + } + } + + pub struct VpcRouterCreateWhen(httpmock::When); + impl VpcRouterCreateWhen { + pub fn new(inner: httpmock::When) -> Self { + Self( + inner + .method(httpmock::Method::POST) + .path_matches(regex::Regex::new("^/v1/vpc-routers$").unwrap()), + ) + } + + pub fn into_inner(self) -> httpmock::When { + self.0 } - pub fn page_token<'a, T>(self, value: T) -> Self + pub fn project<'a, T>(self, value: T) -> Self where - T: Into>, + T: Into>, { if let Some(value) = value.into() { - Self(self.0.query_param("page_token", value.to_string())) + Self(self.0.query_param("project", value.to_string())) } else { Self(self.0.matches(|req| { req.query_params .as_ref() - .and_then(|qs| qs.iter().find(|(key, _)| key == "page_token")) + .and_then(|qs| qs.iter().find(|(key, _)| key == "project")) .is_none() })) } } - pub fn sort_by(self, value: T) -> Self - where - T: Into>, - { - if let Some(value) = value.into() { - Self(self.0.query_param("sort_by", value.to_string())) - } else { - Self(self.0.matches(|req| { - req.query_params - .as_ref() - .and_then(|qs| qs.iter().find(|(key, _)| key == "sort_by")) - .is_none() - })) - } + pub fn vpc(self, value: &types::NameOrId) -> Self { + Self(self.0.query_param("vpc", value.to_string())) + } + + pub fn body(self, value: &types::VpcRouterCreate) -> Self { + Self(self.0.json_body_obj(value)) } } - pub struct UserListThen(httpmock::Then); - impl UserListThen { + pub struct VpcRouterCreateThen(httpmock::Then); + impl VpcRouterCreateThen { pub fn new(inner: httpmock::Then) -> Self { Self(inner) } @@ -13335,10 +14318,10 @@ pub mod operations { self.0 } - pub fn ok(self, value: &types::UserResultsPage) -> Self { + pub fn created(self, value: &types::VpcRouter) -> Self { Self( self.0 - .status(200u16) + .status(201u16) .header("content-type", "application/json") .json_body_obj(value), ) @@ -13365,23 +14348,61 @@ pub mod operations { } } - pub struct UtilizationViewWhen(httpmock::When); - impl UtilizationViewWhen { + pub struct VpcRouterViewWhen(httpmock::When); + impl VpcRouterViewWhen { pub fn new(inner: httpmock::When) -> Self { Self( inner .method(httpmock::Method::GET) - .path_matches(regex::Regex::new("^/v1/utilization$").unwrap()), + .path_matches(regex::Regex::new("^/v1/vpc-routers/[^/]*$").unwrap()), ) } pub fn into_inner(self) -> httpmock::When { self.0 } + + pub fn router(self, value: &types::NameOrId) -> Self { + let re = + regex::Regex::new(&format!("^/v1/vpc-routers/{}$", value.to_string())).unwrap(); + Self(self.0.path_matches(re)) + } + + pub fn project<'a, T>(self, value: T) -> Self + where + T: Into>, + { + if let Some(value) = value.into() { + Self(self.0.query_param("project", value.to_string())) + } else { + Self(self.0.matches(|req| { + req.query_params + .as_ref() + .and_then(|qs| qs.iter().find(|(key, _)| key == "project")) + .is_none() + })) + } + } + + pub fn vpc<'a, T>(self, value: T) -> Self + where + T: Into>, + { + if let Some(value) = value.into() { + Self(self.0.query_param("vpc", value.to_string())) + } else { + Self(self.0.matches(|req| { + req.query_params + .as_ref() + .and_then(|qs| qs.iter().find(|(key, _)| key == "vpc")) + .is_none() + })) + } + } } - pub struct UtilizationViewThen(httpmock::Then); - impl UtilizationViewThen { + pub struct VpcRouterViewThen(httpmock::Then); + impl VpcRouterViewThen { pub fn new(inner: httpmock::Then) -> Self { Self(inner) } @@ -13390,7 +14411,7 @@ pub mod operations { self.0 } - pub fn ok(self, value: &types::Utilization) -> Self { + pub fn ok(self, value: &types::VpcRouter) -> Self { Self( self.0 .status(200u16) @@ -13420,13 +14441,13 @@ pub mod operations { } } - pub struct VpcFirewallRulesViewWhen(httpmock::When); - impl VpcFirewallRulesViewWhen { + pub struct VpcRouterUpdateWhen(httpmock::When); + impl VpcRouterUpdateWhen { pub fn new(inner: httpmock::When) -> Self { Self( inner - .method(httpmock::Method::GET) - .path_matches(regex::Regex::new("^/v1/vpc-firewall-rules$").unwrap()), + .method(httpmock::Method::PUT) + .path_matches(regex::Regex::new("^/v1/vpc-routers/[^/]*$").unwrap()), ) } @@ -13434,6 +14455,12 @@ pub mod operations { self.0 } + pub fn router(self, value: &types::NameOrId) -> Self { + let re = + regex::Regex::new(&format!("^/v1/vpc-routers/{}$", value.to_string())).unwrap(); + Self(self.0.path_matches(re)) + } + pub fn project<'a, T>(self, value: T) -> Self where T: Into>, @@ -13450,13 +14477,29 @@ pub mod operations { } } - pub fn vpc(self, value: &types::NameOrId) -> Self { - Self(self.0.query_param("vpc", value.to_string())) + pub fn vpc<'a, T>(self, value: T) -> Self + where + T: Into>, + { + if let Some(value) = value.into() { + Self(self.0.query_param("vpc", value.to_string())) + } else { + Self(self.0.matches(|req| { + req.query_params + .as_ref() + .and_then(|qs| qs.iter().find(|(key, _)| key == "vpc")) + .is_none() + })) + } + } + + pub fn body(self, value: &types::VpcRouterUpdate) -> Self { + Self(self.0.json_body_obj(value)) } } - pub struct VpcFirewallRulesViewThen(httpmock::Then); - impl VpcFirewallRulesViewThen { + pub struct VpcRouterUpdateThen(httpmock::Then); + impl VpcRouterUpdateThen { pub fn new(inner: httpmock::Then) -> Self { Self(inner) } @@ -13465,7 +14508,7 @@ pub mod operations { self.0 } - pub fn ok(self, value: &types::VpcFirewallRules) -> Self { + pub fn ok(self, value: &types::VpcRouter) -> Self { Self( self.0 .status(200u16) @@ -13495,13 +14538,13 @@ pub mod operations { } } - pub struct VpcFirewallRulesUpdateWhen(httpmock::When); - impl VpcFirewallRulesUpdateWhen { + pub struct VpcRouterDeleteWhen(httpmock::When); + impl VpcRouterDeleteWhen { pub fn new(inner: httpmock::When) -> Self { Self( inner - .method(httpmock::Method::PUT) - .path_matches(regex::Regex::new("^/v1/vpc-firewall-rules$").unwrap()), + .method(httpmock::Method::DELETE) + .path_matches(regex::Regex::new("^/v1/vpc-routers/[^/]*$").unwrap()), ) } @@ -13509,6 +14552,12 @@ pub mod operations { self.0 } + pub fn router(self, value: &types::NameOrId) -> Self { + let re = + regex::Regex::new(&format!("^/v1/vpc-routers/{}$", value.to_string())).unwrap(); + Self(self.0.path_matches(re)) + } + pub fn project<'a, T>(self, value: T) -> Self where T: Into>, @@ -13525,17 +14574,25 @@ pub mod operations { } } - pub fn vpc(self, value: &types::NameOrId) -> Self { - Self(self.0.query_param("vpc", value.to_string())) - } - - pub fn body(self, value: &types::VpcFirewallRuleUpdateParams) -> Self { - Self(self.0.json_body_obj(value)) + pub fn vpc<'a, T>(self, value: T) -> Self + where + T: Into>, + { + if let Some(value) = value.into() { + Self(self.0.query_param("vpc", value.to_string())) + } else { + Self(self.0.matches(|req| { + req.query_params + .as_ref() + .and_then(|qs| qs.iter().find(|(key, _)| key == "vpc")) + .is_none() + })) + } } } - pub struct VpcFirewallRulesUpdateThen(httpmock::Then); - impl VpcFirewallRulesUpdateThen { + pub struct VpcRouterDeleteThen(httpmock::Then); + impl VpcRouterDeleteThen { pub fn new(inner: httpmock::Then) -> Self { Self(inner) } @@ -13544,13 +14601,8 @@ pub mod operations { self.0 } - pub fn ok(self, value: &types::VpcFirewallRules) -> Self { - Self( - self.0 - .status(200u16) - .header("content-type", "application/json") - .json_body_obj(value), - ) + pub fn no_content(self) -> Self { + Self(self.0.status(204u16)) } pub fn client_error(self, status: u16, value: &types::Error) -> Self { @@ -15111,11 +16163,11 @@ pub trait MockServerExt { operations::NetworkingBgpAnnounceSetListWhen, operations::NetworkingBgpAnnounceSetListThen, ); - fn networking_bgp_announce_set_create(&self, config_fn: F) -> httpmock::Mock + fn networking_bgp_announce_set_update(&self, config_fn: F) -> httpmock::Mock where F: FnOnce( - operations::NetworkingBgpAnnounceSetCreateWhen, - operations::NetworkingBgpAnnounceSetCreateThen, + operations::NetworkingBgpAnnounceSetUpdateWhen, + operations::NetworkingBgpAnnounceSetUpdateThen, ); fn networking_bgp_announce_set_delete(&self, config_fn: F) -> httpmock::Mock where @@ -15258,6 +16310,36 @@ pub trait MockServerExt { fn vpc_firewall_rules_update(&self, config_fn: F) -> httpmock::Mock where F: FnOnce(operations::VpcFirewallRulesUpdateWhen, operations::VpcFirewallRulesUpdateThen); + fn vpc_router_route_list(&self, config_fn: F) -> httpmock::Mock + where + F: FnOnce(operations::VpcRouterRouteListWhen, operations::VpcRouterRouteListThen); + fn vpc_router_route_create(&self, config_fn: F) -> httpmock::Mock + where + F: FnOnce(operations::VpcRouterRouteCreateWhen, operations::VpcRouterRouteCreateThen); + fn vpc_router_route_view(&self, config_fn: F) -> httpmock::Mock + where + F: FnOnce(operations::VpcRouterRouteViewWhen, operations::VpcRouterRouteViewThen); + fn vpc_router_route_update(&self, config_fn: F) -> httpmock::Mock + where + F: FnOnce(operations::VpcRouterRouteUpdateWhen, operations::VpcRouterRouteUpdateThen); + fn vpc_router_route_delete(&self, config_fn: F) -> httpmock::Mock + where + F: FnOnce(operations::VpcRouterRouteDeleteWhen, operations::VpcRouterRouteDeleteThen); + fn vpc_router_list(&self, config_fn: F) -> httpmock::Mock + where + F: FnOnce(operations::VpcRouterListWhen, operations::VpcRouterListThen); + fn vpc_router_create(&self, config_fn: F) -> httpmock::Mock + where + F: FnOnce(operations::VpcRouterCreateWhen, operations::VpcRouterCreateThen); + fn vpc_router_view(&self, config_fn: F) -> httpmock::Mock + where + F: FnOnce(operations::VpcRouterViewWhen, operations::VpcRouterViewThen); + fn vpc_router_update(&self, config_fn: F) -> httpmock::Mock + where + F: FnOnce(operations::VpcRouterUpdateWhen, operations::VpcRouterUpdateThen); + fn vpc_router_delete(&self, config_fn: F) -> httpmock::Mock + where + F: FnOnce(operations::VpcRouterDeleteWhen, operations::VpcRouterDeleteThen); fn vpc_subnet_list(&self, config_fn: F) -> httpmock::Mock where F: FnOnce(operations::VpcSubnetListWhen, operations::VpcSubnetListThen); @@ -17019,17 +18101,17 @@ impl MockServerExt for httpmock::MockServer { }) } - fn networking_bgp_announce_set_create(&self, config_fn: F) -> httpmock::Mock + fn networking_bgp_announce_set_update(&self, config_fn: F) -> httpmock::Mock where F: FnOnce( - operations::NetworkingBgpAnnounceSetCreateWhen, - operations::NetworkingBgpAnnounceSetCreateThen, + operations::NetworkingBgpAnnounceSetUpdateWhen, + operations::NetworkingBgpAnnounceSetUpdateThen, ), { self.mock(|when, then| { config_fn( - operations::NetworkingBgpAnnounceSetCreateWhen::new(when), - operations::NetworkingBgpAnnounceSetCreateThen::new(then), + operations::NetworkingBgpAnnounceSetUpdateWhen::new(when), + operations::NetworkingBgpAnnounceSetUpdateThen::new(then), ) }) } @@ -17508,6 +18590,126 @@ impl MockServerExt for httpmock::MockServer { }) } + fn vpc_router_route_list(&self, config_fn: F) -> httpmock::Mock + where + F: FnOnce(operations::VpcRouterRouteListWhen, operations::VpcRouterRouteListThen), + { + self.mock(|when, then| { + config_fn( + operations::VpcRouterRouteListWhen::new(when), + operations::VpcRouterRouteListThen::new(then), + ) + }) + } + + fn vpc_router_route_create(&self, config_fn: F) -> httpmock::Mock + where + F: FnOnce(operations::VpcRouterRouteCreateWhen, operations::VpcRouterRouteCreateThen), + { + self.mock(|when, then| { + config_fn( + operations::VpcRouterRouteCreateWhen::new(when), + operations::VpcRouterRouteCreateThen::new(then), + ) + }) + } + + fn vpc_router_route_view(&self, config_fn: F) -> httpmock::Mock + where + F: FnOnce(operations::VpcRouterRouteViewWhen, operations::VpcRouterRouteViewThen), + { + self.mock(|when, then| { + config_fn( + operations::VpcRouterRouteViewWhen::new(when), + operations::VpcRouterRouteViewThen::new(then), + ) + }) + } + + fn vpc_router_route_update(&self, config_fn: F) -> httpmock::Mock + where + F: FnOnce(operations::VpcRouterRouteUpdateWhen, operations::VpcRouterRouteUpdateThen), + { + self.mock(|when, then| { + config_fn( + operations::VpcRouterRouteUpdateWhen::new(when), + operations::VpcRouterRouteUpdateThen::new(then), + ) + }) + } + + fn vpc_router_route_delete(&self, config_fn: F) -> httpmock::Mock + where + F: FnOnce(operations::VpcRouterRouteDeleteWhen, operations::VpcRouterRouteDeleteThen), + { + self.mock(|when, then| { + config_fn( + operations::VpcRouterRouteDeleteWhen::new(when), + operations::VpcRouterRouteDeleteThen::new(then), + ) + }) + } + + fn vpc_router_list(&self, config_fn: F) -> httpmock::Mock + where + F: FnOnce(operations::VpcRouterListWhen, operations::VpcRouterListThen), + { + self.mock(|when, then| { + config_fn( + operations::VpcRouterListWhen::new(when), + operations::VpcRouterListThen::new(then), + ) + }) + } + + fn vpc_router_create(&self, config_fn: F) -> httpmock::Mock + where + F: FnOnce(operations::VpcRouterCreateWhen, operations::VpcRouterCreateThen), + { + self.mock(|when, then| { + config_fn( + operations::VpcRouterCreateWhen::new(when), + operations::VpcRouterCreateThen::new(then), + ) + }) + } + + fn vpc_router_view(&self, config_fn: F) -> httpmock::Mock + where + F: FnOnce(operations::VpcRouterViewWhen, operations::VpcRouterViewThen), + { + self.mock(|when, then| { + config_fn( + operations::VpcRouterViewWhen::new(when), + operations::VpcRouterViewThen::new(then), + ) + }) + } + + fn vpc_router_update(&self, config_fn: F) -> httpmock::Mock + where + F: FnOnce(operations::VpcRouterUpdateWhen, operations::VpcRouterUpdateThen), + { + self.mock(|when, then| { + config_fn( + operations::VpcRouterUpdateWhen::new(when), + operations::VpcRouterUpdateThen::new(then), + ) + }) + } + + fn vpc_router_delete(&self, config_fn: F) -> httpmock::Mock + where + F: FnOnce(operations::VpcRouterDeleteWhen, operations::VpcRouterDeleteThen), + { + self.mock(|when, then| { + config_fn( + operations::VpcRouterDeleteWhen::new(when), + operations::VpcRouterDeleteThen::new(then), + ) + }) + } + fn vpc_subnet_list(&self, config_fn: F) -> httpmock::Mock where F: FnOnce(operations::VpcSubnetListWhen, operations::VpcSubnetListThen), diff --git a/sdk/Cargo.toml b/sdk/Cargo.toml index 562330b0..6f068708 100644 --- a/sdk/Cargo.toml +++ b/sdk/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "oxide" description = "SDK for the Oxide rack" -version = "0.5.0+20240502.0" +version = "0.6.0+20240710.0" edition = "2021" license = "MPL-2.0" repository = "https://github.com/oxidecomputer/oxide.rs" diff --git a/sdk/README.md b/sdk/README.md index 4607cd9d..234a7fc9 100644 --- a/sdk/README.md +++ b/sdk/README.md @@ -1,11 +1,11 @@ # The Oxide Rust SDK -Generated bindings for the Oxide API. +SDK for the Oxide API. ## Installation -The `oxide` crate is available on crates.io. You'll probably want to use `tokio` as well. Add them to your `Cargo.toml` file -or use `cargo add`: +The `oxide` crate is available on crates.io. You'll probably want to use +`tokio` as well. Add them to your `Cargo.toml` file or use `cargo add`: ```console $ cargo add oxide @@ -15,16 +15,19 @@ $ cargo add tokio ## Authentication To connect to the Oxide API, the SDK needs a host URL and a token. There are -two ways to specify these: +several ways to specify these: -* Environment variables: You can set the OXIDE_HOST and OXIDE_TOKEN environment - variables. -* Configuration file: When you run oxide auth login in the CLI, a - $HOME/.config/oxide/hosts.toml file is generated. This file contains - sensitive information like your token and user ID. +* Configuration files: When you use the CLI's `oxide auth login`, `config.toml` + and `credentials.toml` files are generated in `$HOME/.config/oxide/`. The + credentials file contains sensitive information such as your token and user + ID. +* Environment variables: You can set the `OXIDE_HOST` and `OXIDE_TOKEN` + environment variables. +* Explicit host URL and token. -The SDK will first look for the environment variables, and if they are not -defined, it will look for the config file. +The simplest way to create an authenticated client is to use +`oxide::Client::new_authenticated()` which uses the same credentials and +authentication logic as the CLI. ## Example @@ -32,13 +35,13 @@ Create a new `oxide::Client` like this: ```rust ,no_run use futures::StreamExt; -use oxide::{config::Config, context::Context, prelude::*}; +use oxide::{Client, prelude::*}; #[tokio::main] async fn main() { - // Get a client from the on-disk configuration. - let context = Context::new(Config::default()).expect("unabled to create context"); - let client: &Client = context.client().expect("unable to get client"); + // Make a client from the on-disk configuration. + let client = Client::new_authenticated() + .expect("unable to create an authenticated client"); // Start using the client! diff --git a/sdk/src/auth.rs b/sdk/src/auth.rs new file mode 100644 index 00000000..dfb13e80 --- /dev/null +++ b/sdk/src/auth.rs @@ -0,0 +1,265 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +// Copyright 2024 Oxide Computer Company + +use std::{ + collections::BTreeMap, + net::{IpAddr, SocketAddr}, + path::{Path, PathBuf}, +}; + +use crate::{Client, OxideAuthError}; +use reqwest::ClientBuilder; +use serde::Deserialize; + +#[derive(Deserialize, Debug)] +pub struct ProfileCredentials { + pub token: String, + pub host: String, + pub user: String, +} + +// TODO: do we want a way to easily change the port number? It would need to be +// shoved into the baseurl string +struct ResolveValue { + pub domain: String, + pub addr: IpAddr, +} + +/// Configuration for creating an authenticated [Client] +pub struct ClientConfig { + config_dir: PathBuf, + auth_method: AuthMethod, + resolve: Option, + cert: Option, + insecure: bool, + timeout: Option, +} + +enum AuthMethod { + DefaultProfile, + Profile(String), + HostToken { host: String, token: String }, +} + +impl Default for ClientConfig { + fn default() -> Self { + let mut config_dir = dirs::home_dir().expect("unable to determint the home directory"); + config_dir.push(".config"); + config_dir.push("oxide"); + Self { + config_dir, + auth_method: AuthMethod::DefaultProfile, + resolve: None, + cert: None, + insecure: false, + timeout: None, + } + } +} + +impl ClientConfig { + /// Specify the configuration directory where [Client] authentication will + /// look for the `credentials.toml` and `config.toml` files. + pub fn with_config_dir(mut self, config_dir: impl Into) -> Self { + self.config_dir = config_dir.into(); + self + } + + /// Specify the profile name that will be looked up in the + /// `credentials.toml` file. + pub fn with_profile(mut self, profile: impl ToString) -> Self { + self.auth_method = AuthMethod::Profile(profile.to_string()); + self + } + + /// Authenticate with an explicit host and token. + pub fn with_host_and_token(mut self, host: impl AsRef, token: impl AsRef) -> Self { + self.auth_method = AuthMethod::HostToken { + host: host.as_ref().to_string(), + token: token.as_ref().to_string(), + }; + self + } + + /// Override hostname resolution with a particular address. + pub fn with_resolve(mut self, domain: impl ToString, addr: IpAddr) -> Self { + self.resolve = Some(ResolveValue { + domain: domain.to_string(), + addr, + }); + self + } + + /// Use the specified certificate when establishing a secure connection + /// with the host. + pub fn with_cert(mut self, cert: reqwest::Certificate) -> Self { + self.cert = Some(cert); + self + } + + /// Allow insecure connections. + pub fn with_insecure(mut self, insecure: bool) -> Self { + self.insecure = insecure; + self + } + + /// Specify the desired client timeout. + pub fn with_timeout(mut self, timeout: u64) -> Self { + self.timeout = Some(timeout); + self + } + + /// Retrieve the configuration directory. + pub fn config_dir(&self) -> &PathBuf { + &self.config_dir + } + + /// Retrieve the specified profile (if any). + pub fn profile(&self) -> Option<&str> { + match &self.auth_method { + AuthMethod::Profile(profile) => Some(profile.as_ref()), + _ => None, + } + } +} + +#[derive(Deserialize, Debug, Default)] +pub struct CredentialsFile { + pub profile: BTreeMap, +} + +/// Clients such as the CLI may specify additional configuration information; +/// authentication only relies on the value of `default-profile`. +#[derive(Deserialize, Debug, Default)] +#[serde(rename_all = "kebab-case")] +pub struct BasicConfigFile { + pub default_profile: Option, +} + +impl Client { + pub fn new_authenticated() -> Result { + Self::new_authenticated_config(&ClientConfig::default()) + } + + pub fn new_authenticated_config(config: &ClientConfig) -> Result { + let ClientConfig { + config_dir, + auth_method, + .. + } = config; + + let (host, token) = match auth_method { + AuthMethod::DefaultProfile => get_profile_auth(config_dir, None)?, + AuthMethod::Profile(profile) => get_profile_auth(config_dir, Some(profile))?, + AuthMethod::HostToken { host, token } => (host.clone(), token.clone()), + }; + + let mut client_builder = config.make_unauthenticated_client_builder(); + + let mut bearer = + reqwest::header::HeaderValue::from_str(format!("Bearer {}", &token).as_str()) + .expect("failed to construct the auth header"); + bearer.set_sensitive(true); + client_builder = client_builder.default_headers( + [(reqwest::header::AUTHORIZATION, bearer)] + .into_iter() + .collect(), + ); + + Ok(Self::new_with_client( + &host, + client_builder + .build() + .expect("failure to construct underlying client object"), + )) + } +} + +impl ClientConfig { + pub fn make_unauthenticated_client_builder(&self) -> ClientBuilder { + let ClientConfig { + resolve, + cert, + insecure, + timeout, + .. + } = self; + let dur = std::time::Duration::from_secs(timeout.unwrap_or(15)); + let mut client_builder = ClientBuilder::new().connect_timeout(dur).timeout(dur); + + if let Some(ResolveValue { domain, addr }) = resolve { + client_builder = client_builder.resolve(domain, SocketAddr::new(*addr, 0)); + } + if let Some(cert) = cert { + client_builder = client_builder.add_root_certificate(cert.clone()); + } + + if *insecure { + client_builder = client_builder + .danger_accept_invalid_hostnames(true) + .danger_accept_invalid_certs(true); + } + + client_builder + } +} + +fn get_profile_auth( + config_dir: &Path, + profile: Option<&String>, +) -> Result<(String, String), OxideAuthError> { + if let (None, Ok(env_token)) = (profile, std::env::var("OXIDE_TOKEN")) { + let env_host = std::env::var("OXIDE_HOST").map_err(|_| OxideAuthError::MissingHost)?; + Ok((env_host, env_token)) + } else { + let credentials_path = config_dir.join("credentials.toml"); + let contents = std::fs::read_to_string(&credentials_path).map_err(|e| { + if e.kind() == std::io::ErrorKind::NotFound { + OxideAuthError::NoAuthenticatedHosts + } else { + OxideAuthError::IoError(e) + } + })?; + let creds = toml::from_str::(&contents) + .map_err(|e| OxideAuthError::TomlError(credentials_path.clone(), e))?; + + let profile_name = if let Some(profile_name) = profile { + profile_name.clone() + } else if let Ok(env_host) = std::env::var("OXIDE_HOST") { + // For backward compatibility, allow users to specify a profile by + // naming its host in OXIDE_HOST + creds + .profile + .iter() + .filter_map(|(profile_name, profile_info)| { + (profile_info.host == env_host).then_some(profile_name) + }) + .next() + .ok_or_else(|| OxideAuthError::MissingToken(env_host))? + .clone() + } else { + let config_path = config_dir.join("config.toml"); + let contents = std::fs::read_to_string(&config_path).map_err(|e| { + if e.kind() == std::io::ErrorKind::NotFound { + OxideAuthError::NoDefaultProfile + } else { + OxideAuthError::IoError(e) + } + })?; + let config = toml::from_str::(&contents) + .map_err(|e| OxideAuthError::TomlError(config_path, e))?; + match config.default_profile { + Some(p) => p, + None => return Err(OxideAuthError::NoDefaultProfile), + } + }; + let profile = creds + .profile + .get(&profile_name) + .ok_or_else(|| OxideAuthError::NoProfile(credentials_path, profile_name))?; + Ok((profile.host.clone(), profile.token.clone())) + } +} diff --git a/sdk/src/config.rs b/sdk/src/config.rs deleted file mode 100644 index c98ca5b9..00000000 --- a/sdk/src/config.rs +++ /dev/null @@ -1,173 +0,0 @@ -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at https://mozilla.org/MPL/2.0/. - -// Copyright 2024 Oxide Computer Company - -use core::str::FromStr; -use std::collections::BTreeMap; -use std::fs::create_dir_all; -use std::net::IpAddr; -use std::path::PathBuf; - -use crate::OxideError; -use serde::{Deserialize, Serialize}; -use toml_edit::{Item, Table}; -use uuid::Uuid; - -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct ResolveValue { - pub host: String, - pub port: u16, - pub addr: IpAddr, -} - -impl FromStr for ResolveValue { - type Err = String; - - fn from_str(s: &str) -> std::result::Result { - let values = s.splitn(3, ':').collect::>(); - let [host, port, addr] = values.as_slice() else { - return Err(r#"value must be "host:port:addr"#.to_string()); - }; - - let host = host.to_string(); - let port = port - .parse() - .map_err(|_| format!("error parsing port '{}'", port))?; - - // `IpAddr::parse()` does not accept enclosing brackets on IPv6 - // addresses; strip them off if they exist. - let addr = addr - .strip_prefix('[') - .and_then(|s| s.strip_suffix(']')) - .unwrap_or(addr); - let addr = addr - .parse() - .map_err(|_| format!("error parsing address '{}'", addr))?; - - Ok(Self { host, port, addr }) - } -} - -pub struct Config { - pub client_id: Uuid, - pub hosts: Hosts, - pub resolve: Option, - pub cert: Option, - pub insecure: bool, - pub timeout: Option, -} - -#[derive(Default, Debug, Deserialize, Serialize)] -pub struct Hosts { - #[serde(flatten)] - pub hosts: BTreeMap, -} - -impl Hosts { - pub fn get>(&self, hostname: S) -> Option<&Host> { - self.hosts.get(hostname.as_ref()) - } -} - -#[derive(Debug, Deserialize, Serialize)] -pub struct Host { - pub token: String, - pub user: String, - #[serde(default, skip_serializing_if = "is_default")] - pub default: bool, -} - -fn is_default(value: &T) -> bool { - value == &T::default() -} - -impl Default for Config { - fn default() -> Self { - let mut dir = dirs::home_dir().unwrap(); - dir.push(".config"); - dir.push("oxide"); - create_dir_all(&dir).unwrap(); - Self::new_with_config_dir(dir) - } -} - -impl Config { - pub fn new_with_config_dir(dir: PathBuf) -> Self { - let hosts_path = dir.join("hosts.toml"); - let hosts = if let Ok(contents) = std::fs::read_to_string(hosts_path) { - toml::from_str(&contents).unwrap() - } else { - Default::default() - }; - - Self { - client_id: Default::default(), - hosts, - resolve: None, - cert: None, - insecure: false, - timeout: None, - } - } - - pub fn update_host(&self, hostname: String, host_entry: Host) -> Result<(), OxideError> { - let mut dir = dirs::home_dir().unwrap(); - dir.push(".config"); - dir.push("oxide"); - create_dir_all(&dir).unwrap(); - - let hosts_path = dir.join("hosts.toml"); - let mut hosts = if let Ok(contents) = std::fs::read_to_string(hosts_path.clone()) { - contents - .parse::() - .map_err(OxideError::TomlError)? - } else { - Default::default() - }; - - let table = hosts - .entry(&hostname) - .or_insert_with(|| Item::Table(Table::default())) - .as_table_mut() - .unwrap(); // TODO - - let Host { - token, - user, - default, - } = host_entry; - - table.insert("token", toml_edit::value(token)); - table.insert("user", toml_edit::value(user)); - - if default || table.contains_key("default") { - table.insert("default", toml_edit::value(default)); - } - - std::fs::write(hosts_path, hosts.to_string()).map_err(OxideError::IoError)?; - - Ok(()) - } - - pub fn with_resolve(mut self, resolve: ResolveValue) -> Self { - self.resolve = Some(resolve); - self - } - - pub fn with_cert(mut self, cert: reqwest::Certificate) -> Self { - self.cert = Some(cert); - self - } - - pub fn with_insecure(mut self, insecure: bool) -> Self { - self.insecure = insecure; - self - } - - pub fn with_timeout(mut self, timeout: u64) -> Self { - self.timeout = Some(timeout); - self - } -} diff --git a/sdk/src/context.rs b/sdk/src/context.rs deleted file mode 100644 index 6508e181..00000000 --- a/sdk/src/context.rs +++ /dev/null @@ -1,99 +0,0 @@ -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at https://mozilla.org/MPL/2.0/. - -// Copyright 2024 Oxide Computer Company - -use std::{net::SocketAddr, time::Duration}; - -use reqwest::ClientBuilder; - -use crate::{ - config::{Config, ResolveValue}, - Client, OxideError, -}; - -pub struct Context { - client: Option, - config: Config, -} - -impl Context { - pub fn new(config: Config) -> Result { - let client = get_client(&config)?; - Ok(Self { client, config }) - } - - pub fn client(&self) -> Result<&Client, OxideError> { - self.client - .as_ref() - .ok_or_else(|| OxideError::NoAuthenticatedHosts) - } - - pub fn config(&self) -> &Config { - &self.config - } -} - -fn get_client(config: &Config) -> Result, OxideError> { - let (host, token) = match (std::env::var("OXIDE_HOST"), std::env::var("OXIDE_TOKEN")) { - (Ok(host), Ok(token)) => (host, token), - (Ok(host), Err(_)) => { - let Some(host_entry) = config.hosts.get(&host) else { - return Err(OxideError::MissingToken(host)); - }; - (host, host_entry.token.clone()) - } - (Err(_), Ok(token)) => { - let Some((host, _)) = config.hosts.hosts.iter().next() else { - return Ok(None); - }; - (host.clone(), token) - } - (Err(_), Err(_)) => { - let Some((host, host_entry)) = config.hosts.hosts.iter().next() else { - return Ok(None); - }; - (host.clone(), host_entry.token.clone()) - } - }; - - Ok(Some(make_client(&host, token, config))) -} - -pub fn make_client(host: &str, token: String, config: &Config) -> Client { - Client::new_with_client(host, make_rclient(Some(token), config).build().unwrap()) -} - -pub fn make_rclient(token: Option, config: &Config) -> reqwest::ClientBuilder { - let mut client_builder = ClientBuilder::new().connect_timeout(Duration::from_secs(15)); - - if let Some(token) = token { - let mut bearer = - reqwest::header::HeaderValue::from_str(format!("Bearer {}", token).as_str()).unwrap(); - bearer.set_sensitive(true); - client_builder = client_builder.default_headers( - [(reqwest::header::AUTHORIZATION, bearer)] - .into_iter() - .collect(), - ); - } - - if let Some(ResolveValue { host, port, addr }) = &config.resolve { - client_builder = client_builder.resolve(host, SocketAddr::new(*addr, *port)); - } - if let Some(cert) = &config.cert { - client_builder = client_builder.add_root_certificate(cert.clone()); - } - if let Some(timeout) = &config.timeout { - client_builder = client_builder.timeout(Duration::from_secs(*timeout)); - } - - if config.insecure { - client_builder = client_builder - .danger_accept_invalid_hostnames(true) - .danger_accept_invalid_certs(true); - } - - client_builder -} diff --git a/sdk/src/generated_sdk.rs b/sdk/src/generated_sdk.rs index 880f6f01..ffc8d68f 100644 --- a/sdk/src/generated_sdk.rs +++ b/sdk/src/generated_sdk.rs @@ -11654,6 +11654,15 @@ pub mod types { /// "type": "string", /// "format": "date-time" /// }, + /// "transit_ips": { + /// "description": "A set of additional networks that this interface + /// may send and receive traffic on.", + /// "default": [], + /// "type": "array", + /// "items": { + /// "$ref": "#/components/schemas/IpNet" + /// } + /// }, /// "vpc_id": { /// "description": "The VPC to which the interface belongs.", /// "type": "string", @@ -11686,6 +11695,10 @@ pub mod types { pub time_created: chrono::DateTime, /// timestamp when this resource was last modified pub time_modified: chrono::DateTime, + /// A set of additional networks that this interface may send and + /// receive traffic on. + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub transit_ips: Vec, /// The VPC to which the interface belongs. pub vpc_id: uuid::Uuid, } @@ -11970,6 +11983,15 @@ pub mod types { /// a secondary will return an error.", /// "default": false, /// "type": "boolean" + /// }, + /// "transit_ips": { + /// "description": "A set of additional networks that this interface + /// may send and receive traffic on.", + /// "default": [], + /// "type": "array", + /// "items": { + /// "$ref": "#/components/schemas/IpNet" + /// } /// } /// } /// } @@ -11994,6 +12016,10 @@ pub mod types { /// secondary will return an error. #[serde(default)] pub primary: bool, + /// A set of additional networks that this interface may send and + /// receive traffic on. + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub transit_ips: Vec, } impl From<&InstanceNetworkInterfaceUpdate> for InstanceNetworkInterfaceUpdate { @@ -12324,92 +12350,6 @@ pub mod types { } } - /// IpKind - /// - ///
JSON schema - /// - /// ```json - /// { - /// "type": "string", - /// "enum": [ - /// "snat", - /// "floating", - /// "ephemeral" - /// ] - /// } - /// ``` - ///
- #[derive( - Clone, - Copy, - Debug, - Deserialize, - Eq, - Hash, - Ord, - PartialEq, - PartialOrd, - Serialize, - schemars :: JsonSchema, - )] - pub enum IpKind { - #[serde(rename = "snat")] - Snat, - #[serde(rename = "floating")] - Floating, - #[serde(rename = "ephemeral")] - Ephemeral, - } - - impl From<&IpKind> for IpKind { - fn from(value: &IpKind) -> Self { - value.clone() - } - } - - impl ToString for IpKind { - fn to_string(&self) -> String { - match *self { - Self::Snat => "snat".to_string(), - Self::Floating => "floating".to_string(), - Self::Ephemeral => "ephemeral".to_string(), - } - } - } - - impl std::str::FromStr for IpKind { - type Err = self::error::ConversionError; - fn from_str(value: &str) -> Result { - match value { - "snat" => Ok(Self::Snat), - "floating" => Ok(Self::Floating), - "ephemeral" => Ok(Self::Ephemeral), - _ => Err("invalid value".into()), - } - } - } - - impl std::convert::TryFrom<&str> for IpKind { - type Error = self::error::ConversionError; - fn try_from(value: &str) -> Result { - value.parse() - } - } - - impl std::convert::TryFrom<&String> for IpKind { - type Error = self::error::ConversionError; - fn try_from(value: &String) -> Result { - value.parse() - } - } - - impl std::convert::TryFrom for IpKind { - type Error = self::error::ConversionError; - fn try_from(value: String) -> Result { - value.parse() - } - } - /// IpNet /// ///
JSON schema @@ -14715,7 +14655,8 @@ pub mod types { /// Names must begin with a lower case ASCII letter, be composed exclusively /// of lowercase ASCII, uppercase ASCII, numbers, and '-', and may not end - /// with a '-'. Names cannot be a UUID though they may contain a UUID. + /// with a '-'. Names cannot be a UUID, but they may contain a UUID. They + /// can be at most 63 characters long. /// ///
JSON schema /// @@ -14724,8 +14665,8 @@ pub mod types { /// "title": "A name unique within the parent collection", /// "description": "Names must begin with a lower case ASCII letter, be /// composed exclusively of lowercase ASCII, uppercase ASCII, numbers, and - /// '-', and may not end with a '-'. Names cannot be a UUID though they may - /// contain a UUID.", + /// '-', and may not end with a '-'. Names cannot be a UUID, but they may + /// contain a UUID. They can be at most 63 characters long.", /// "type": "string", /// "maxLength": 63, /// "minLength": 1, @@ -15145,6 +15086,13 @@ pub mod types { /// "subnet": { /// "$ref": "#/components/schemas/IpNet" /// }, + /// "transit_ips": { + /// "default": [], + /// "type": "array", + /// "items": { + /// "$ref": "#/components/schemas/IpNet" + /// } + /// }, /// "vni": { /// "$ref": "#/components/schemas/Vni" /// } @@ -15162,6 +15110,8 @@ pub mod types { pub primary: bool, pub slot: u8, pub subnet: IpNet, + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub transit_ips: Vec, pub vni: Vni, } @@ -16243,7 +16193,7 @@ pub mod types { /// "format": "ip" /// }, /// "kind": { - /// "$ref": "#/components/schemas/IpKind" + /// "$ref": "#/components/schemas/ProbeExternalIpKind" /// }, /// "last_port": { /// "type": "integer", @@ -16258,7 +16208,7 @@ pub mod types { pub struct ProbeExternalIp { pub first_port: u16, pub ip: std::net::IpAddr, - pub kind: IpKind, + pub kind: ProbeExternalIpKind, pub last_port: u16, } @@ -16274,6 +16224,92 @@ pub mod types { } } + /// ProbeExternalIpKind + /// + ///
JSON schema + /// + /// ```json + /// { + /// "type": "string", + /// "enum": [ + /// "snat", + /// "floating", + /// "ephemeral" + /// ] + /// } + /// ``` + ///
+ #[derive( + Clone, + Copy, + Debug, + Deserialize, + Eq, + Hash, + Ord, + PartialEq, + PartialOrd, + Serialize, + schemars :: JsonSchema, + )] + pub enum ProbeExternalIpKind { + #[serde(rename = "snat")] + Snat, + #[serde(rename = "floating")] + Floating, + #[serde(rename = "ephemeral")] + Ephemeral, + } + + impl From<&ProbeExternalIpKind> for ProbeExternalIpKind { + fn from(value: &ProbeExternalIpKind) -> Self { + value.clone() + } + } + + impl ToString for ProbeExternalIpKind { + fn to_string(&self) -> String { + match *self { + Self::Snat => "snat".to_string(), + Self::Floating => "floating".to_string(), + Self::Ephemeral => "ephemeral".to_string(), + } + } + } + + impl std::str::FromStr for ProbeExternalIpKind { + type Err = self::error::ConversionError; + fn from_str(value: &str) -> Result { + match value { + "snat" => Ok(Self::Snat), + "floating" => Ok(Self::Floating), + "ephemeral" => Ok(Self::Ephemeral), + _ => Err("invalid value".into()), + } + } + } + + impl std::convert::TryFrom<&str> for ProbeExternalIpKind { + type Error = self::error::ConversionError; + fn try_from(value: &str) -> Result { + value.parse() + } + } + + impl std::convert::TryFrom<&String> for ProbeExternalIpKind { + type Error = self::error::ConversionError; + fn try_from(value: &String) -> Result { + value.parse() + } + } + + impl std::convert::TryFrom for ProbeExternalIpKind { + type Error = self::error::ConversionError; + fn try_from(value: String) -> Result { + value.parse() + } + } + /// ProbeInfo /// ///
JSON schema @@ -17282,45 +17318,334 @@ pub mod types { } } - /// Identity-related metadata that's included in nearly all public API - /// objects + /// A `RouteDestination` is used to match traffic with a routing rule, on + /// the destination of that traffic. + /// + /// When traffic is to be sent to a destination that is within a given + /// `RouteDestination`, the corresponding `RouterRoute` applies, and traffic + /// will be forward to the `RouteTarget` for that rule. /// ///
JSON schema /// /// ```json /// { - /// "description": "Identity-related metadata that's included in nearly all - /// public API objects", + /// "description": "A `RouteDestination` is used to match traffic with a + /// routing rule, on the destination of that traffic.\n\nWhen traffic is to + /// be sent to a destination that is within a given `RouteDestination`, the + /// corresponding `RouterRoute` applies, and traffic will be forward to the + /// `RouteTarget` for that rule.", + /// "oneOf": [ + /// { + /// "description": "Route applies to traffic destined for a specific IP + /// address", + /// "type": "object", + /// "required": [ + /// "type", + /// "value" + /// ], + /// "properties": { + /// "type": { + /// "type": "string", + /// "enum": [ + /// "ip" + /// ] + /// }, + /// "value": { + /// "type": "string", + /// "format": "ip" + /// } + /// } + /// }, + /// { + /// "description": "Route applies to traffic destined for a specific IP + /// subnet", + /// "type": "object", + /// "required": [ + /// "type", + /// "value" + /// ], + /// "properties": { + /// "type": { + /// "type": "string", + /// "enum": [ + /// "ip_net" + /// ] + /// }, + /// "value": { + /// "$ref": "#/components/schemas/IpNet" + /// } + /// } + /// }, + /// { + /// "description": "Route applies to traffic destined for the given + /// VPC.", + /// "type": "object", + /// "required": [ + /// "type", + /// "value" + /// ], + /// "properties": { + /// "type": { + /// "type": "string", + /// "enum": [ + /// "vpc" + /// ] + /// }, + /// "value": { + /// "$ref": "#/components/schemas/Name" + /// } + /// } + /// }, + /// { + /// "description": "Route applies to traffic", + /// "type": "object", + /// "required": [ + /// "type", + /// "value" + /// ], + /// "properties": { + /// "type": { + /// "type": "string", + /// "enum": [ + /// "subnet" + /// ] + /// }, + /// "value": { + /// "$ref": "#/components/schemas/Name" + /// } + /// } + /// } + /// ] + /// } + /// ``` + ///
+ #[derive(Clone, Debug, Deserialize, Serialize, schemars :: JsonSchema)] + #[serde(tag = "type", content = "value")] + pub enum RouteDestination { + /// Route applies to traffic destined for a specific IP address + #[serde(rename = "ip")] + Ip(std::net::IpAddr), + /// Route applies to traffic destined for a specific IP subnet + #[serde(rename = "ip_net")] + IpNet(IpNet), + /// Route applies to traffic destined for the given VPC. + #[serde(rename = "vpc")] + Vpc(Name), + /// Route applies to traffic + #[serde(rename = "subnet")] + Subnet(Name), + } + + impl From<&RouteDestination> for RouteDestination { + fn from(value: &RouteDestination) -> Self { + value.clone() + } + } + + impl From for RouteDestination { + fn from(value: std::net::IpAddr) -> Self { + Self::Ip(value) + } + } + + impl From for RouteDestination { + fn from(value: IpNet) -> Self { + Self::IpNet(value) + } + } + + /// A `RouteTarget` describes the possible locations that traffic matching a + /// route destination can be sent. + /// + ///
JSON schema + /// + /// ```json + /// { + /// "description": "A `RouteTarget` describes the possible locations that + /// traffic matching a route destination can be sent.", + /// "oneOf": [ + /// { + /// "description": "Forward traffic to a particular IP address.", + /// "type": "object", + /// "required": [ + /// "type", + /// "value" + /// ], + /// "properties": { + /// "type": { + /// "type": "string", + /// "enum": [ + /// "ip" + /// ] + /// }, + /// "value": { + /// "type": "string", + /// "format": "ip" + /// } + /// } + /// }, + /// { + /// "description": "Forward traffic to a VPC", + /// "type": "object", + /// "required": [ + /// "type", + /// "value" + /// ], + /// "properties": { + /// "type": { + /// "type": "string", + /// "enum": [ + /// "vpc" + /// ] + /// }, + /// "value": { + /// "$ref": "#/components/schemas/Name" + /// } + /// } + /// }, + /// { + /// "description": "Forward traffic to a VPC Subnet", + /// "type": "object", + /// "required": [ + /// "type", + /// "value" + /// ], + /// "properties": { + /// "type": { + /// "type": "string", + /// "enum": [ + /// "subnet" + /// ] + /// }, + /// "value": { + /// "$ref": "#/components/schemas/Name" + /// } + /// } + /// }, + /// { + /// "description": "Forward traffic to a specific instance", + /// "type": "object", + /// "required": [ + /// "type", + /// "value" + /// ], + /// "properties": { + /// "type": { + /// "type": "string", + /// "enum": [ + /// "instance" + /// ] + /// }, + /// "value": { + /// "$ref": "#/components/schemas/Name" + /// } + /// } + /// }, + /// { + /// "description": "Forward traffic to an internet gateway", + /// "type": "object", + /// "required": [ + /// "type", + /// "value" + /// ], + /// "properties": { + /// "type": { + /// "type": "string", + /// "enum": [ + /// "internet_gateway" + /// ] + /// }, + /// "value": { + /// "$ref": "#/components/schemas/Name" + /// } + /// } + /// }, + /// { + /// "description": "Drop matching traffic", + /// "type": "object", + /// "required": [ + /// "type" + /// ], + /// "properties": { + /// "type": { + /// "type": "string", + /// "enum": [ + /// "drop" + /// ] + /// } + /// } + /// } + /// ] + /// } + /// ``` + ///
+ #[derive(Clone, Debug, Deserialize, Serialize, schemars :: JsonSchema)] + #[serde(tag = "type", content = "value")] + pub enum RouteTarget { + /// Forward traffic to a particular IP address. + #[serde(rename = "ip")] + Ip(std::net::IpAddr), + /// Forward traffic to a VPC + #[serde(rename = "vpc")] + Vpc(Name), + /// Forward traffic to a VPC Subnet + #[serde(rename = "subnet")] + Subnet(Name), + /// Forward traffic to a specific instance + #[serde(rename = "instance")] + Instance(Name), + /// Forward traffic to an internet gateway + #[serde(rename = "internet_gateway")] + InternetGateway(Name), + #[serde(rename = "drop")] + Drop, + } + + impl From<&RouteTarget> for RouteTarget { + fn from(value: &RouteTarget) -> Self { + value.clone() + } + } + + impl From for RouteTarget { + fn from(value: std::net::IpAddr) -> Self { + Self::Ip(value) + } + } + + /// A route defines a rule that governs where traffic should be sent based + /// on its destination. + /// + ///
JSON schema + /// + /// ```json + /// { + /// "description": "A route defines a rule that governs where traffic + /// should be sent based on its destination.", /// "type": "object", /// "required": [ - /// "acs_url", /// "description", + /// "destination", /// "id", - /// "idp_entity_id", + /// "kind", /// "name", - /// "slo_url", - /// "sp_client_id", - /// "technical_contact_email", + /// "target", /// "time_created", - /// "time_modified" + /// "time_modified", + /// "vpc_router_id" /// ], /// "properties": { - /// "acs_url": { - /// "description": "Service provider endpoint where the response will - /// be sent", - /// "type": "string" - /// }, /// "description": { /// "description": "human-readable free-form text about a resource", /// "type": "string" /// }, - /// "group_attribute_name": { - /// "description": "If set, attributes with this name will be - /// considered to denote a user's group membership, where the values will be - /// the group names.", - /// "type": [ - /// "string", - /// "null" + /// "destination": { + /// "description": "Selects which traffic this routing rule will apply + /// to.", + /// "allOf": [ + /// { + /// "$ref": "#/components/schemas/RouteDestination" + /// } /// ] /// }, /// "id": { @@ -17329,9 +17654,14 @@ pub mod types { /// "type": "string", /// "format": "uuid" /// }, - /// "idp_entity_id": { - /// "description": "IdP's entity id", - /// "type": "string" + /// "kind": { + /// "description": "Describes the kind of router. Set at creation. + /// `read-only`", + /// "allOf": [ + /// { + /// "$ref": "#/components/schemas/RouterRouteKind" + /// } + /// ] /// }, /// "name": { /// "description": "unique, mutable, user-controlled identifier for @@ -17342,28 +17672,15 @@ pub mod types { /// } /// ] /// }, - /// "public_cert": { - /// "description": "Optional request signing public certificate (base64 - /// encoded der file)", - /// "type": [ - /// "string", - /// "null" + /// "target": { + /// "description": "The location that matched packets should be + /// forwarded to.", + /// "allOf": [ + /// { + /// "$ref": "#/components/schemas/RouteTarget" + /// } /// ] /// }, - /// "slo_url": { - /// "description": "Service provider endpoint where the idp should send - /// log out requests", - /// "type": "string" - /// }, - /// "sp_client_id": { - /// "description": "SP's client id", - /// "type": "string" - /// }, - /// "technical_contact_email": { - /// "description": "Customer's technical contact for saml - /// configuration", - /// "type": "string" - /// }, /// "time_created": { /// "description": "timestamp when this resource was created", /// "type": "string", @@ -17373,71 +17690,534 @@ pub mod types { /// "description": "timestamp when this resource was last modified", /// "type": "string", /// "format": "date-time" + /// }, + /// "vpc_router_id": { + /// "description": "The ID of the VPC Router to which the route + /// belongs", + /// "type": "string", + /// "format": "uuid" /// } /// } /// } /// ``` ///
#[derive(Clone, Debug, Deserialize, Serialize, schemars :: JsonSchema)] - pub struct SamlIdentityProvider { - /// Service provider endpoint where the response will be sent - pub acs_url: String, + pub struct RouterRoute { /// human-readable free-form text about a resource pub description: String, - /// If set, attributes with this name will be considered to denote a - /// user's group membership, where the values will be the group names. - #[serde(default, skip_serializing_if = "Option::is_none")] - pub group_attribute_name: Option, + /// Selects which traffic this routing rule will apply to. + pub destination: RouteDestination, /// unique, immutable, system-controlled identifier for each resource pub id: uuid::Uuid, - /// IdP's entity id - pub idp_entity_id: String, + /// Describes the kind of router. Set at creation. `read-only` + pub kind: RouterRouteKind, /// unique, mutable, user-controlled identifier for each resource pub name: Name, - /// Optional request signing public certificate (base64 encoded der - /// file) - #[serde(default, skip_serializing_if = "Option::is_none")] - pub public_cert: Option, - /// Service provider endpoint where the idp should send log out requests - pub slo_url: String, - /// SP's client id - pub sp_client_id: String, - /// Customer's technical contact for saml configuration - pub technical_contact_email: String, + /// The location that matched packets should be forwarded to. + pub target: RouteTarget, /// timestamp when this resource was created pub time_created: chrono::DateTime, /// timestamp when this resource was last modified pub time_modified: chrono::DateTime, + /// The ID of the VPC Router to which the route belongs + pub vpc_router_id: uuid::Uuid, } - impl From<&SamlIdentityProvider> for SamlIdentityProvider { - fn from(value: &SamlIdentityProvider) -> Self { + impl From<&RouterRoute> for RouterRoute { + fn from(value: &RouterRoute) -> Self { value.clone() } } - impl SamlIdentityProvider { - pub fn builder() -> builder::SamlIdentityProvider { + impl RouterRoute { + pub fn builder() -> builder::RouterRoute { Default::default() } } - /// Create-time identity-related parameters + /// Create-time parameters for a `RouterRoute` /// ///
JSON schema /// /// ```json /// { - /// "description": "Create-time identity-related parameters", + /// "description": "Create-time parameters for a `RouterRoute`", /// "type": "object", /// "required": [ - /// "acs_url", /// "description", - /// "idp_entity_id", - /// "idp_metadata_source", + /// "destination", /// "name", - /// "slo_url", - /// "sp_client_id", + /// "target" + /// ], + /// "properties": { + /// "description": { + /// "type": "string" + /// }, + /// "destination": { + /// "description": "Selects which traffic this routing rule will apply + /// to.", + /// "allOf": [ + /// { + /// "$ref": "#/components/schemas/RouteDestination" + /// } + /// ] + /// }, + /// "name": { + /// "$ref": "#/components/schemas/Name" + /// }, + /// "target": { + /// "description": "The location that matched packets should be + /// forwarded to.", + /// "allOf": [ + /// { + /// "$ref": "#/components/schemas/RouteTarget" + /// } + /// ] + /// } + /// } + /// } + /// ``` + ///
+ #[derive(Clone, Debug, Deserialize, Serialize, schemars :: JsonSchema)] + pub struct RouterRouteCreate { + pub description: String, + /// Selects which traffic this routing rule will apply to. + pub destination: RouteDestination, + pub name: Name, + /// The location that matched packets should be forwarded to. + pub target: RouteTarget, + } + + impl From<&RouterRouteCreate> for RouterRouteCreate { + fn from(value: &RouterRouteCreate) -> Self { + value.clone() + } + } + + impl RouterRouteCreate { + pub fn builder() -> builder::RouterRouteCreate { + Default::default() + } + } + + /// The kind of a `RouterRoute` + /// + /// The kind determines certain attributes such as if the route is + /// modifiable and describes how or where the route was created. + /// + ///
JSON schema + /// + /// ```json + /// { + /// "description": "The kind of a `RouterRoute`\n\nThe kind determines + /// certain attributes such as if the route is modifiable and describes how + /// or where the route was created.", + /// "oneOf": [ + /// { + /// "description": "Determines the default destination of traffic, such + /// as whether it goes to the internet or not.\n\n`Destination: An Internet + /// Gateway` `Modifiable: true`", + /// "type": "string", + /// "enum": [ + /// "default" + /// ] + /// }, + /// { + /// "description": "Automatically added for each VPC Subnet in the + /// VPC\n\n`Destination: A VPC Subnet` `Modifiable: false`", + /// "type": "string", + /// "enum": [ + /// "vpc_subnet" + /// ] + /// }, + /// { + /// "description": "Automatically added when VPC peering is + /// established\n\n`Destination: A different VPC` `Modifiable: false`", + /// "type": "string", + /// "enum": [ + /// "vpc_peering" + /// ] + /// }, + /// { + /// "description": "Created by a user; see + /// `RouteTarget`\n\n`Destination: User defined` `Modifiable: true`", + /// "type": "string", + /// "enum": [ + /// "custom" + /// ] + /// } + /// ] + /// } + /// ``` + ///
+ #[derive( + Clone, + Copy, + Debug, + Deserialize, + Eq, + Hash, + Ord, + PartialEq, + PartialOrd, + Serialize, + schemars :: JsonSchema, + )] + pub enum RouterRouteKind { + /// Determines the default destination of traffic, such as whether it + /// goes to the internet or not. + /// + /// `Destination: An Internet Gateway` `Modifiable: true` + #[serde(rename = "default")] + Default, + /// Automatically added for each VPC Subnet in the VPC + /// + /// `Destination: A VPC Subnet` `Modifiable: false` + #[serde(rename = "vpc_subnet")] + VpcSubnet, + /// Automatically added when VPC peering is established + /// + /// `Destination: A different VPC` `Modifiable: false` + #[serde(rename = "vpc_peering")] + VpcPeering, + /// Created by a user; see `RouteTarget` + /// + /// `Destination: User defined` `Modifiable: true` + #[serde(rename = "custom")] + Custom, + } + + impl From<&RouterRouteKind> for RouterRouteKind { + fn from(value: &RouterRouteKind) -> Self { + value.clone() + } + } + + impl ToString for RouterRouteKind { + fn to_string(&self) -> String { + match *self { + Self::Default => "default".to_string(), + Self::VpcSubnet => "vpc_subnet".to_string(), + Self::VpcPeering => "vpc_peering".to_string(), + Self::Custom => "custom".to_string(), + } + } + } + + impl std::str::FromStr for RouterRouteKind { + type Err = self::error::ConversionError; + fn from_str(value: &str) -> Result { + match value { + "default" => Ok(Self::Default), + "vpc_subnet" => Ok(Self::VpcSubnet), + "vpc_peering" => Ok(Self::VpcPeering), + "custom" => Ok(Self::Custom), + _ => Err("invalid value".into()), + } + } + } + + impl std::convert::TryFrom<&str> for RouterRouteKind { + type Error = self::error::ConversionError; + fn try_from(value: &str) -> Result { + value.parse() + } + } + + impl std::convert::TryFrom<&String> for RouterRouteKind { + type Error = self::error::ConversionError; + fn try_from(value: &String) -> Result { + value.parse() + } + } + + impl std::convert::TryFrom for RouterRouteKind { + type Error = self::error::ConversionError; + fn try_from(value: String) -> Result { + value.parse() + } + } + + /// A single page of results + /// + ///
JSON schema + /// + /// ```json + /// { + /// "description": "A single page of results", + /// "type": "object", + /// "required": [ + /// "items" + /// ], + /// "properties": { + /// "items": { + /// "description": "list of items on this page of results", + /// "type": "array", + /// "items": { + /// "$ref": "#/components/schemas/RouterRoute" + /// } + /// }, + /// "next_page": { + /// "description": "token used to fetch the next page of results (if + /// any)", + /// "type": [ + /// "string", + /// "null" + /// ] + /// } + /// } + /// } + /// ``` + ///
+ #[derive(Clone, Debug, Deserialize, Serialize, schemars :: JsonSchema)] + pub struct RouterRouteResultsPage { + /// list of items on this page of results + pub items: Vec, + /// token used to fetch the next page of results (if any) + #[serde(default, skip_serializing_if = "Option::is_none")] + pub next_page: Option, + } + + impl From<&RouterRouteResultsPage> for RouterRouteResultsPage { + fn from(value: &RouterRouteResultsPage) -> Self { + value.clone() + } + } + + impl RouterRouteResultsPage { + pub fn builder() -> builder::RouterRouteResultsPage { + Default::default() + } + } + + /// Updateable properties of a `RouterRoute` + /// + ///
JSON schema + /// + /// ```json + /// { + /// "description": "Updateable properties of a `RouterRoute`", + /// "type": "object", + /// "required": [ + /// "destination", + /// "target" + /// ], + /// "properties": { + /// "description": { + /// "type": [ + /// "string", + /// "null" + /// ] + /// }, + /// "destination": { + /// "description": "Selects which traffic this routing rule will apply + /// to.", + /// "allOf": [ + /// { + /// "$ref": "#/components/schemas/RouteDestination" + /// } + /// ] + /// }, + /// "name": { + /// "allOf": [ + /// { + /// "$ref": "#/components/schemas/Name" + /// } + /// ] + /// }, + /// "target": { + /// "description": "The location that matched packets should be + /// forwarded to.", + /// "allOf": [ + /// { + /// "$ref": "#/components/schemas/RouteTarget" + /// } + /// ] + /// } + /// } + /// } + /// ``` + ///
+ #[derive(Clone, Debug, Deserialize, Serialize, schemars :: JsonSchema)] + pub struct RouterRouteUpdate { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub description: Option, + /// Selects which traffic this routing rule will apply to. + pub destination: RouteDestination, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub name: Option, + /// The location that matched packets should be forwarded to. + pub target: RouteTarget, + } + + impl From<&RouterRouteUpdate> for RouterRouteUpdate { + fn from(value: &RouterRouteUpdate) -> Self { + value.clone() + } + } + + impl RouterRouteUpdate { + pub fn builder() -> builder::RouterRouteUpdate { + Default::default() + } + } + + /// Identity-related metadata that's included in nearly all public API + /// objects + /// + ///
JSON schema + /// + /// ```json + /// { + /// "description": "Identity-related metadata that's included in nearly all + /// public API objects", + /// "type": "object", + /// "required": [ + /// "acs_url", + /// "description", + /// "id", + /// "idp_entity_id", + /// "name", + /// "slo_url", + /// "sp_client_id", + /// "technical_contact_email", + /// "time_created", + /// "time_modified" + /// ], + /// "properties": { + /// "acs_url": { + /// "description": "Service provider endpoint where the response will + /// be sent", + /// "type": "string" + /// }, + /// "description": { + /// "description": "human-readable free-form text about a resource", + /// "type": "string" + /// }, + /// "group_attribute_name": { + /// "description": "If set, attributes with this name will be + /// considered to denote a user's group membership, where the values will be + /// the group names.", + /// "type": [ + /// "string", + /// "null" + /// ] + /// }, + /// "id": { + /// "description": "unique, immutable, system-controlled identifier for + /// each resource", + /// "type": "string", + /// "format": "uuid" + /// }, + /// "idp_entity_id": { + /// "description": "IdP's entity id", + /// "type": "string" + /// }, + /// "name": { + /// "description": "unique, mutable, user-controlled identifier for + /// each resource", + /// "allOf": [ + /// { + /// "$ref": "#/components/schemas/Name" + /// } + /// ] + /// }, + /// "public_cert": { + /// "description": "Optional request signing public certificate (base64 + /// encoded der file)", + /// "type": [ + /// "string", + /// "null" + /// ] + /// }, + /// "slo_url": { + /// "description": "Service provider endpoint where the idp should send + /// log out requests", + /// "type": "string" + /// }, + /// "sp_client_id": { + /// "description": "SP's client id", + /// "type": "string" + /// }, + /// "technical_contact_email": { + /// "description": "Customer's technical contact for saml + /// configuration", + /// "type": "string" + /// }, + /// "time_created": { + /// "description": "timestamp when this resource was created", + /// "type": "string", + /// "format": "date-time" + /// }, + /// "time_modified": { + /// "description": "timestamp when this resource was last modified", + /// "type": "string", + /// "format": "date-time" + /// } + /// } + /// } + /// ``` + ///
+ #[derive(Clone, Debug, Deserialize, Serialize, schemars :: JsonSchema)] + pub struct SamlIdentityProvider { + /// Service provider endpoint where the response will be sent + pub acs_url: String, + /// human-readable free-form text about a resource + pub description: String, + /// If set, attributes with this name will be considered to denote a + /// user's group membership, where the values will be the group names. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub group_attribute_name: Option, + /// unique, immutable, system-controlled identifier for each resource + pub id: uuid::Uuid, + /// IdP's entity id + pub idp_entity_id: String, + /// unique, mutable, user-controlled identifier for each resource + pub name: Name, + /// Optional request signing public certificate (base64 encoded der + /// file) + #[serde(default, skip_serializing_if = "Option::is_none")] + pub public_cert: Option, + /// Service provider endpoint where the idp should send log out requests + pub slo_url: String, + /// SP's client id + pub sp_client_id: String, + /// Customer's technical contact for saml configuration + pub technical_contact_email: String, + /// timestamp when this resource was created + pub time_created: chrono::DateTime, + /// timestamp when this resource was last modified + pub time_modified: chrono::DateTime, + } + + impl From<&SamlIdentityProvider> for SamlIdentityProvider { + fn from(value: &SamlIdentityProvider) -> Self { + value.clone() + } + } + + impl SamlIdentityProvider { + pub fn builder() -> builder::SamlIdentityProvider { + Default::default() + } + } + + /// Create-time identity-related parameters + /// + ///
JSON schema + /// + /// ```json + /// { + /// "description": "Create-time identity-related parameters", + /// "type": "object", + /// "required": [ + /// "acs_url", + /// "description", + /// "idp_entity_id", + /// "idp_metadata_source", + /// "name", + /// "slo_url", + /// "sp_client_id", /// "technical_contact_email" /// ], /// "properties": { @@ -22480,7 +23260,9 @@ pub mod types { /// "type": "string", /// "enum": [ /// "count", - /// "bytes" + /// "bytes", + /// "seconds", + /// "nanoseconds" /// ] /// } /// ``` @@ -22503,6 +23285,10 @@ pub mod types { Count, #[serde(rename = "bytes")] Bytes, + #[serde(rename = "seconds")] + Seconds, + #[serde(rename = "nanoseconds")] + Nanoseconds, } impl From<&Units> for Units { @@ -22516,6 +23302,8 @@ pub mod types { match *self { Self::Count => "count".to_string(), Self::Bytes => "bytes".to_string(), + Self::Seconds => "seconds".to_string(), + Self::Nanoseconds => "nanoseconds".to_string(), } } } @@ -22526,6 +23314,8 @@ pub mod types { match value { "count" => Ok(Self::Count), "bytes" => Ok(Self::Bytes), + "seconds" => Ok(Self::Seconds), + "nanoseconds" => Ok(Self::Nanoseconds), _ => Err("invalid value".into()), } } @@ -22606,16 +23396,16 @@ pub mod types { /// View of a Built-in User /// - /// A Built-in User is explicitly created as opposed to being derived from - /// an Identify Provider. + /// Built-in users are identities internal to the system, used when the + /// control plane performs actions autonomously /// ///
JSON schema /// /// ```json /// { - /// "description": "View of a Built-in User\n\nA Built-in User is - /// explicitly created as opposed to being derived from an Identify - /// Provider.", + /// "description": "View of a Built-in User\n\nBuilt-in users are + /// identities internal to the system, used when the control plane performs + /// actions autonomously", /// "type": "object", /// "required": [ /// "description", @@ -22791,7 +23581,8 @@ pub mod types { /// Names must begin with a lower case ASCII letter, be composed exclusively /// of lowercase ASCII, uppercase ASCII, numbers, and '-', and may not end - /// with a '-'. Names cannot be a UUID though they may contain a UUID. + /// with a '-'. Names cannot be a UUID, but they may contain a UUID. They + /// can be at most 63 characters long. /// ///
JSON schema /// @@ -22800,8 +23591,8 @@ pub mod types { /// "title": "A name unique within the parent collection", /// "description": "Names must begin with a lower case ASCII letter, be /// composed exclusively of lowercase ASCII, uppercase ASCII, numbers, and - /// '-', and may not end with a '-'. Names cannot be a UUID though they may - /// contain a UUID.", + /// '-', and may not end with a '-'. Names cannot be a UUID, but they may + /// contain a UUID. They can be at most 63 characters long.", /// "type": "string", /// "maxLength": 63, /// "minLength": 1, @@ -24795,8 +25586,318 @@ pub mod types { } } + /// A VPC router defines a series of rules that indicate where traffic + /// should be sent depending on its destination. + /// + ///
JSON schema + /// + /// ```json + /// { + /// "description": "A VPC router defines a series of rules that indicate + /// where traffic should be sent depending on its destination.", + /// "type": "object", + /// "required": [ + /// "description", + /// "id", + /// "kind", + /// "name", + /// "time_created", + /// "time_modified", + /// "vpc_id" + /// ], + /// "properties": { + /// "description": { + /// "description": "human-readable free-form text about a resource", + /// "type": "string" + /// }, + /// "id": { + /// "description": "unique, immutable, system-controlled identifier for + /// each resource", + /// "type": "string", + /// "format": "uuid" + /// }, + /// "kind": { + /// "$ref": "#/components/schemas/VpcRouterKind" + /// }, + /// "name": { + /// "description": "unique, mutable, user-controlled identifier for + /// each resource", + /// "allOf": [ + /// { + /// "$ref": "#/components/schemas/Name" + /// } + /// ] + /// }, + /// "time_created": { + /// "description": "timestamp when this resource was created", + /// "type": "string", + /// "format": "date-time" + /// }, + /// "time_modified": { + /// "description": "timestamp when this resource was last modified", + /// "type": "string", + /// "format": "date-time" + /// }, + /// "vpc_id": { + /// "description": "The VPC to which the router belongs.", + /// "type": "string", + /// "format": "uuid" + /// } + /// } + /// } + /// ``` + ///
+ #[derive(Clone, Debug, Deserialize, Serialize, schemars :: JsonSchema)] + pub struct VpcRouter { + /// human-readable free-form text about a resource + pub description: String, + /// unique, immutable, system-controlled identifier for each resource + pub id: uuid::Uuid, + pub kind: VpcRouterKind, + /// unique, mutable, user-controlled identifier for each resource + pub name: Name, + /// timestamp when this resource was created + pub time_created: chrono::DateTime, + /// timestamp when this resource was last modified + pub time_modified: chrono::DateTime, + /// The VPC to which the router belongs. + pub vpc_id: uuid::Uuid, + } + + impl From<&VpcRouter> for VpcRouter { + fn from(value: &VpcRouter) -> Self { + value.clone() + } + } + + impl VpcRouter { + pub fn builder() -> builder::VpcRouter { + Default::default() + } + } + + /// Create-time parameters for a `VpcRouter` + /// + ///
JSON schema + /// + /// ```json + /// { + /// "description": "Create-time parameters for a `VpcRouter`", + /// "type": "object", + /// "required": [ + /// "description", + /// "name" + /// ], + /// "properties": { + /// "description": { + /// "type": "string" + /// }, + /// "name": { + /// "$ref": "#/components/schemas/Name" + /// } + /// } + /// } + /// ``` + ///
+ #[derive(Clone, Debug, Deserialize, Serialize, schemars :: JsonSchema)] + pub struct VpcRouterCreate { + pub description: String, + pub name: Name, + } + + impl From<&VpcRouterCreate> for VpcRouterCreate { + fn from(value: &VpcRouterCreate) -> Self { + value.clone() + } + } + + impl VpcRouterCreate { + pub fn builder() -> builder::VpcRouterCreate { + Default::default() + } + } + + /// VpcRouterKind + /// + ///
JSON schema + /// + /// ```json + /// { + /// "type": "string", + /// "enum": [ + /// "system", + /// "custom" + /// ] + /// } + /// ``` + ///
+ #[derive( + Clone, + Copy, + Debug, + Deserialize, + Eq, + Hash, + Ord, + PartialEq, + PartialOrd, + Serialize, + schemars :: JsonSchema, + )] + pub enum VpcRouterKind { + #[serde(rename = "system")] + System, + #[serde(rename = "custom")] + Custom, + } + + impl From<&VpcRouterKind> for VpcRouterKind { + fn from(value: &VpcRouterKind) -> Self { + value.clone() + } + } + + impl ToString for VpcRouterKind { + fn to_string(&self) -> String { + match *self { + Self::System => "system".to_string(), + Self::Custom => "custom".to_string(), + } + } + } + + impl std::str::FromStr for VpcRouterKind { + type Err = self::error::ConversionError; + fn from_str(value: &str) -> Result { + match value { + "system" => Ok(Self::System), + "custom" => Ok(Self::Custom), + _ => Err("invalid value".into()), + } + } + } + + impl std::convert::TryFrom<&str> for VpcRouterKind { + type Error = self::error::ConversionError; + fn try_from(value: &str) -> Result { + value.parse() + } + } + + impl std::convert::TryFrom<&String> for VpcRouterKind { + type Error = self::error::ConversionError; + fn try_from(value: &String) -> Result { + value.parse() + } + } + + impl std::convert::TryFrom for VpcRouterKind { + type Error = self::error::ConversionError; + fn try_from(value: String) -> Result { + value.parse() + } + } + + /// A single page of results + /// + ///
JSON schema + /// + /// ```json + /// { + /// "description": "A single page of results", + /// "type": "object", + /// "required": [ + /// "items" + /// ], + /// "properties": { + /// "items": { + /// "description": "list of items on this page of results", + /// "type": "array", + /// "items": { + /// "$ref": "#/components/schemas/VpcRouter" + /// } + /// }, + /// "next_page": { + /// "description": "token used to fetch the next page of results (if + /// any)", + /// "type": [ + /// "string", + /// "null" + /// ] + /// } + /// } + /// } + /// ``` + ///
+ #[derive(Clone, Debug, Deserialize, Serialize, schemars :: JsonSchema)] + pub struct VpcRouterResultsPage { + /// list of items on this page of results + pub items: Vec, + /// token used to fetch the next page of results (if any) + #[serde(default, skip_serializing_if = "Option::is_none")] + pub next_page: Option, + } + + impl From<&VpcRouterResultsPage> for VpcRouterResultsPage { + fn from(value: &VpcRouterResultsPage) -> Self { + value.clone() + } + } + + impl VpcRouterResultsPage { + pub fn builder() -> builder::VpcRouterResultsPage { + Default::default() + } + } + + /// Updateable properties of a `VpcRouter` + /// + ///
JSON schema + /// + /// ```json + /// { + /// "description": "Updateable properties of a `VpcRouter`", + /// "type": "object", + /// "properties": { + /// "description": { + /// "type": [ + /// "string", + /// "null" + /// ] + /// }, + /// "name": { + /// "allOf": [ + /// { + /// "$ref": "#/components/schemas/Name" + /// } + /// ] + /// } + /// } + /// } + /// ``` + ///
+ #[derive(Clone, Debug, Deserialize, Serialize, schemars :: JsonSchema)] + pub struct VpcRouterUpdate { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub description: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub name: Option, + } + + impl From<&VpcRouterUpdate> for VpcRouterUpdate { + fn from(value: &VpcRouterUpdate) -> Self { + value.clone() + } + } + + impl VpcRouterUpdate { + pub fn builder() -> builder::VpcRouterUpdate { + Default::default() + } + } + /// A VPC subnet represents a logical grouping for instances that allows - /// network traffic between them, within a IPv4 subnetwork or optionall an + /// network traffic between them, within a IPv4 subnetwork or optionally an /// IPv6 subnetwork. /// ///
JSON schema @@ -24805,7 +25906,7 @@ pub mod types { /// { /// "description": "A VPC subnet represents a logical grouping for /// instances that allows network traffic between them, within a IPv4 - /// subnetwork or optionall an IPv6 subnetwork.", + /// subnetwork or optionally an IPv6 subnetwork.", /// "type": "object", /// "required": [ /// "description", @@ -24818,6 +25919,14 @@ pub mod types { /// "vpc_id" /// ], /// "properties": { + /// "custom_router_id": { + /// "description": "ID for an attached custom router.", + /// "type": [ + /// "string", + /// "null" + /// ], + /// "format": "uuid" + /// }, /// "description": { /// "description": "human-readable free-form text about a resource", /// "type": "string" @@ -24874,6 +25983,9 @@ pub mod types { ///
#[derive(Clone, Debug, Deserialize, Serialize, schemars :: JsonSchema)] pub struct VpcSubnet { + /// ID for an attached custom router. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub custom_router_id: Option, /// human-readable free-form text about a resource pub description: String, /// unique, immutable, system-controlled identifier for each resource @@ -24918,6 +26030,18 @@ pub mod types { /// "name" /// ], /// "properties": { + /// "custom_router": { + /// "description": "An optional router, used to direct packets sent + /// from hosts in this subnet to any destination address.\n\nCustom routers + /// apply in addition to the VPC-wide *system* router, and have higher + /// priority than the system router for an otherwise equal-prefix-length + /// match.", + /// "allOf": [ + /// { + /// "$ref": "#/components/schemas/NameOrId" + /// } + /// ] + /// }, /// "description": { /// "type": "string" /// }, @@ -24952,6 +26076,14 @@ pub mod types { ///
#[derive(Clone, Debug, Deserialize, Serialize, schemars :: JsonSchema)] pub struct VpcSubnetCreate { + /// An optional router, used to direct packets sent from hosts in this + /// subnet to any destination address. + /// + /// Custom routers apply in addition to the VPC-wide *system* router, + /// and have higher priority than the system router for an otherwise + /// equal-prefix-length match. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub custom_router: Option, pub description: String, /// The IPv4 address range for this subnet. /// @@ -25042,6 +26174,15 @@ pub mod types { /// "description": "Updateable properties of a `VpcSubnet`", /// "type": "object", /// "properties": { + /// "custom_router": { + /// "description": "An optional router, used to direct packets sent + /// from hosts in this subnet to any destination address.", + /// "allOf": [ + /// { + /// "$ref": "#/components/schemas/NameOrId" + /// } + /// ] + /// }, /// "description": { /// "type": [ /// "string", @@ -25061,6 +26202,10 @@ pub mod types { ///
#[derive(Clone, Debug, Deserialize, Serialize, schemars :: JsonSchema)] pub struct VpcSubnetUpdate { + /// An optional router, used to direct packets sent from hosts in this + /// subnet to any destination address. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub custom_router: Option, #[serde(default, skip_serializing_if = "Option::is_none")] pub description: Option, #[serde(default, skip_serializing_if = "Option::is_none")] @@ -33157,6 +34302,7 @@ pub mod types { subnet_id: Result, time_created: Result, String>, time_modified: Result, String>, + transit_ips: Result, String>, vpc_id: Result, } @@ -33173,6 +34319,7 @@ pub mod types { subnet_id: Err("no value supplied for subnet_id".to_string()), time_created: Err("no value supplied for time_created".to_string()), time_modified: Err("no value supplied for time_modified".to_string()), + transit_ips: Ok(Default::default()), vpc_id: Err("no value supplied for vpc_id".to_string()), } } @@ -33279,6 +34426,16 @@ pub mod types { }); self } + pub fn transit_ips(mut self, value: T) -> Self + where + T: std::convert::TryInto>, + T::Error: std::fmt::Display, + { + self.transit_ips = value + .try_into() + .map_err(|e| format!("error converting supplied value for transit_ips: {}", e)); + self + } pub fn vpc_id(mut self, value: T) -> Self where T: std::convert::TryInto, @@ -33307,6 +34464,7 @@ pub mod types { subnet_id: value.subnet_id?, time_created: value.time_created?, time_modified: value.time_modified?, + transit_ips: value.transit_ips?, vpc_id: value.vpc_id?, }) } @@ -33325,6 +34483,7 @@ pub mod types { subnet_id: Ok(value.subnet_id), time_created: Ok(value.time_created), time_modified: Ok(value.time_modified), + transit_ips: Ok(value.transit_ips), vpc_id: Ok(value.vpc_id), } } @@ -33499,6 +34658,7 @@ pub mod types { description: Result, String>, name: Result, String>, primary: Result, + transit_ips: Result, String>, } impl Default for InstanceNetworkInterfaceUpdate { @@ -33507,6 +34667,7 @@ pub mod types { description: Ok(Default::default()), name: Ok(Default::default()), primary: Ok(Default::default()), + transit_ips: Ok(Default::default()), } } } @@ -33542,6 +34703,16 @@ pub mod types { .map_err(|e| format!("error converting supplied value for primary: {}", e)); self } + pub fn transit_ips(mut self, value: T) -> Self + where + T: std::convert::TryInto>, + T::Error: std::fmt::Display, + { + self.transit_ips = value + .try_into() + .map_err(|e| format!("error converting supplied value for transit_ips: {}", e)); + self + } } impl std::convert::TryFrom @@ -33555,6 +34726,7 @@ pub mod types { description: value.description?, name: value.name?, primary: value.primary?, + transit_ips: value.transit_ips?, }) } } @@ -33565,6 +34737,7 @@ pub mod types { description: Ok(value.description), name: Ok(value.name), primary: Ok(value.primary), + transit_ips: Ok(value.transit_ips), } } } @@ -35307,6 +36480,7 @@ pub mod types { primary: Result, slot: Result, subnet: Result, + transit_ips: Result, String>, vni: Result, } @@ -35321,6 +36495,7 @@ pub mod types { primary: Err("no value supplied for primary".to_string()), slot: Err("no value supplied for slot".to_string()), subnet: Err("no value supplied for subnet".to_string()), + transit_ips: Ok(Default::default()), vni: Err("no value supplied for vni".to_string()), } } @@ -35407,6 +36582,16 @@ pub mod types { .map_err(|e| format!("error converting supplied value for subnet: {}", e)); self } + pub fn transit_ips(mut self, value: T) -> Self + where + T: std::convert::TryInto>, + T::Error: std::fmt::Display, + { + self.transit_ips = value + .try_into() + .map_err(|e| format!("error converting supplied value for transit_ips: {}", e)); + self + } pub fn vni(mut self, value: T) -> Self where T: std::convert::TryInto, @@ -35431,6 +36616,7 @@ pub mod types { primary: value.primary?, slot: value.slot?, subnet: value.subnet?, + transit_ips: value.transit_ips?, vni: value.vni?, }) } @@ -35447,6 +36633,7 @@ pub mod types { primary: Ok(value.primary), slot: Ok(value.slot), subnet: Ok(value.subnet), + transit_ips: Ok(value.transit_ips), vni: Ok(value.vni), } } @@ -35996,7 +37183,7 @@ pub mod types { pub struct ProbeExternalIp { first_port: Result, ip: Result, - kind: Result, + kind: Result, last_port: Result, } @@ -36034,7 +37221,7 @@ pub mod types { } pub fn kind(mut self, value: T) -> Self where - T: std::convert::TryInto, + T: std::convert::TryInto, T::Error: std::fmt::Display, { self.kind = value @@ -37073,6 +38260,390 @@ pub mod types { } } + #[derive(Clone, Debug)] + pub struct RouterRoute { + description: Result, + destination: Result, + id: Result, + kind: Result, + name: Result, + target: Result, + time_created: Result, String>, + time_modified: Result, String>, + vpc_router_id: Result, + } + + impl Default for RouterRoute { + fn default() -> Self { + Self { + description: Err("no value supplied for description".to_string()), + destination: Err("no value supplied for destination".to_string()), + id: Err("no value supplied for id".to_string()), + kind: Err("no value supplied for kind".to_string()), + name: Err("no value supplied for name".to_string()), + target: Err("no value supplied for target".to_string()), + time_created: Err("no value supplied for time_created".to_string()), + time_modified: Err("no value supplied for time_modified".to_string()), + vpc_router_id: Err("no value supplied for vpc_router_id".to_string()), + } + } + } + + impl RouterRoute { + pub fn description(mut self, value: T) -> Self + where + T: std::convert::TryInto, + T::Error: std::fmt::Display, + { + self.description = value + .try_into() + .map_err(|e| format!("error converting supplied value for description: {}", e)); + self + } + pub fn destination(mut self, value: T) -> Self + where + T: std::convert::TryInto, + T::Error: std::fmt::Display, + { + self.destination = value + .try_into() + .map_err(|e| format!("error converting supplied value for destination: {}", e)); + self + } + pub fn id(mut self, value: T) -> Self + where + T: std::convert::TryInto, + T::Error: std::fmt::Display, + { + self.id = value + .try_into() + .map_err(|e| format!("error converting supplied value for id: {}", e)); + self + } + pub fn kind(mut self, value: T) -> Self + where + T: std::convert::TryInto, + T::Error: std::fmt::Display, + { + self.kind = value + .try_into() + .map_err(|e| format!("error converting supplied value for kind: {}", e)); + self + } + pub fn name(mut self, value: T) -> Self + where + T: std::convert::TryInto, + T::Error: std::fmt::Display, + { + self.name = value + .try_into() + .map_err(|e| format!("error converting supplied value for name: {}", e)); + self + } + pub fn target(mut self, value: T) -> Self + where + T: std::convert::TryInto, + T::Error: std::fmt::Display, + { + self.target = value + .try_into() + .map_err(|e| format!("error converting supplied value for target: {}", e)); + self + } + pub fn time_created(mut self, value: T) -> Self + where + T: std::convert::TryInto>, + T::Error: std::fmt::Display, + { + self.time_created = value.try_into().map_err(|e| { + format!("error converting supplied value for time_created: {}", e) + }); + self + } + pub fn time_modified(mut self, value: T) -> Self + where + T: std::convert::TryInto>, + T::Error: std::fmt::Display, + { + self.time_modified = value.try_into().map_err(|e| { + format!("error converting supplied value for time_modified: {}", e) + }); + self + } + pub fn vpc_router_id(mut self, value: T) -> Self + where + T: std::convert::TryInto, + T::Error: std::fmt::Display, + { + self.vpc_router_id = value.try_into().map_err(|e| { + format!("error converting supplied value for vpc_router_id: {}", e) + }); + self + } + } + + impl std::convert::TryFrom for super::RouterRoute { + type Error = super::error::ConversionError; + fn try_from(value: RouterRoute) -> Result { + Ok(Self { + description: value.description?, + destination: value.destination?, + id: value.id?, + kind: value.kind?, + name: value.name?, + target: value.target?, + time_created: value.time_created?, + time_modified: value.time_modified?, + vpc_router_id: value.vpc_router_id?, + }) + } + } + + impl From for RouterRoute { + fn from(value: super::RouterRoute) -> Self { + Self { + description: Ok(value.description), + destination: Ok(value.destination), + id: Ok(value.id), + kind: Ok(value.kind), + name: Ok(value.name), + target: Ok(value.target), + time_created: Ok(value.time_created), + time_modified: Ok(value.time_modified), + vpc_router_id: Ok(value.vpc_router_id), + } + } + } + + #[derive(Clone, Debug)] + pub struct RouterRouteCreate { + description: Result, + destination: Result, + name: Result, + target: Result, + } + + impl Default for RouterRouteCreate { + fn default() -> Self { + Self { + description: Err("no value supplied for description".to_string()), + destination: Err("no value supplied for destination".to_string()), + name: Err("no value supplied for name".to_string()), + target: Err("no value supplied for target".to_string()), + } + } + } + + impl RouterRouteCreate { + pub fn description(mut self, value: T) -> Self + where + T: std::convert::TryInto, + T::Error: std::fmt::Display, + { + self.description = value + .try_into() + .map_err(|e| format!("error converting supplied value for description: {}", e)); + self + } + pub fn destination(mut self, value: T) -> Self + where + T: std::convert::TryInto, + T::Error: std::fmt::Display, + { + self.destination = value + .try_into() + .map_err(|e| format!("error converting supplied value for destination: {}", e)); + self + } + pub fn name(mut self, value: T) -> Self + where + T: std::convert::TryInto, + T::Error: std::fmt::Display, + { + self.name = value + .try_into() + .map_err(|e| format!("error converting supplied value for name: {}", e)); + self + } + pub fn target(mut self, value: T) -> Self + where + T: std::convert::TryInto, + T::Error: std::fmt::Display, + { + self.target = value + .try_into() + .map_err(|e| format!("error converting supplied value for target: {}", e)); + self + } + } + + impl std::convert::TryFrom for super::RouterRouteCreate { + type Error = super::error::ConversionError; + fn try_from(value: RouterRouteCreate) -> Result { + Ok(Self { + description: value.description?, + destination: value.destination?, + name: value.name?, + target: value.target?, + }) + } + } + + impl From for RouterRouteCreate { + fn from(value: super::RouterRouteCreate) -> Self { + Self { + description: Ok(value.description), + destination: Ok(value.destination), + name: Ok(value.name), + target: Ok(value.target), + } + } + } + + #[derive(Clone, Debug)] + pub struct RouterRouteResultsPage { + items: Result, String>, + next_page: Result, String>, + } + + impl Default for RouterRouteResultsPage { + fn default() -> Self { + Self { + items: Err("no value supplied for items".to_string()), + next_page: Ok(Default::default()), + } + } + } + + impl RouterRouteResultsPage { + pub fn items(mut self, value: T) -> Self + where + T: std::convert::TryInto>, + T::Error: std::fmt::Display, + { + self.items = value + .try_into() + .map_err(|e| format!("error converting supplied value for items: {}", e)); + self + } + pub fn next_page(mut self, value: T) -> Self + where + T: std::convert::TryInto>, + T::Error: std::fmt::Display, + { + self.next_page = value + .try_into() + .map_err(|e| format!("error converting supplied value for next_page: {}", e)); + self + } + } + + impl std::convert::TryFrom for super::RouterRouteResultsPage { + type Error = super::error::ConversionError; + fn try_from( + value: RouterRouteResultsPage, + ) -> Result { + Ok(Self { + items: value.items?, + next_page: value.next_page?, + }) + } + } + + impl From for RouterRouteResultsPage { + fn from(value: super::RouterRouteResultsPage) -> Self { + Self { + items: Ok(value.items), + next_page: Ok(value.next_page), + } + } + } + + #[derive(Clone, Debug)] + pub struct RouterRouteUpdate { + description: Result, String>, + destination: Result, + name: Result, String>, + target: Result, + } + + impl Default for RouterRouteUpdate { + fn default() -> Self { + Self { + description: Ok(Default::default()), + destination: Err("no value supplied for destination".to_string()), + name: Ok(Default::default()), + target: Err("no value supplied for target".to_string()), + } + } + } + + impl RouterRouteUpdate { + pub fn description(mut self, value: T) -> Self + where + T: std::convert::TryInto>, + T::Error: std::fmt::Display, + { + self.description = value + .try_into() + .map_err(|e| format!("error converting supplied value for description: {}", e)); + self + } + pub fn destination(mut self, value: T) -> Self + where + T: std::convert::TryInto, + T::Error: std::fmt::Display, + { + self.destination = value + .try_into() + .map_err(|e| format!("error converting supplied value for destination: {}", e)); + self + } + pub fn name(mut self, value: T) -> Self + where + T: std::convert::TryInto>, + T::Error: std::fmt::Display, + { + self.name = value + .try_into() + .map_err(|e| format!("error converting supplied value for name: {}", e)); + self + } + pub fn target(mut self, value: T) -> Self + where + T: std::convert::TryInto, + T::Error: std::fmt::Display, + { + self.target = value + .try_into() + .map_err(|e| format!("error converting supplied value for target: {}", e)); + self + } + } + + impl std::convert::TryFrom for super::RouterRouteUpdate { + type Error = super::error::ConversionError; + fn try_from(value: RouterRouteUpdate) -> Result { + Ok(Self { + description: value.description?, + destination: value.destination?, + name: value.name?, + target: value.target?, + }) + } + } + + impl From for RouterRouteUpdate { + fn from(value: super::RouterRouteUpdate) -> Self { + Self { + description: Ok(value.description), + destination: Ok(value.destination), + name: Ok(value.name), + target: Ok(value.target), + } + } + } + #[derive(Clone, Debug)] pub struct SamlIdentityProvider { acs_url: Result, @@ -43339,8 +44910,309 @@ pub mod types { } } + #[derive(Clone, Debug)] + pub struct VpcRouter { + description: Result, + id: Result, + kind: Result, + name: Result, + time_created: Result, String>, + time_modified: Result, String>, + vpc_id: Result, + } + + impl Default for VpcRouter { + fn default() -> Self { + Self { + description: Err("no value supplied for description".to_string()), + id: Err("no value supplied for id".to_string()), + kind: Err("no value supplied for kind".to_string()), + name: Err("no value supplied for name".to_string()), + time_created: Err("no value supplied for time_created".to_string()), + time_modified: Err("no value supplied for time_modified".to_string()), + vpc_id: Err("no value supplied for vpc_id".to_string()), + } + } + } + + impl VpcRouter { + pub fn description(mut self, value: T) -> Self + where + T: std::convert::TryInto, + T::Error: std::fmt::Display, + { + self.description = value + .try_into() + .map_err(|e| format!("error converting supplied value for description: {}", e)); + self + } + pub fn id(mut self, value: T) -> Self + where + T: std::convert::TryInto, + T::Error: std::fmt::Display, + { + self.id = value + .try_into() + .map_err(|e| format!("error converting supplied value for id: {}", e)); + self + } + pub fn kind(mut self, value: T) -> Self + where + T: std::convert::TryInto, + T::Error: std::fmt::Display, + { + self.kind = value + .try_into() + .map_err(|e| format!("error converting supplied value for kind: {}", e)); + self + } + pub fn name(mut self, value: T) -> Self + where + T: std::convert::TryInto, + T::Error: std::fmt::Display, + { + self.name = value + .try_into() + .map_err(|e| format!("error converting supplied value for name: {}", e)); + self + } + pub fn time_created(mut self, value: T) -> Self + where + T: std::convert::TryInto>, + T::Error: std::fmt::Display, + { + self.time_created = value.try_into().map_err(|e| { + format!("error converting supplied value for time_created: {}", e) + }); + self + } + pub fn time_modified(mut self, value: T) -> Self + where + T: std::convert::TryInto>, + T::Error: std::fmt::Display, + { + self.time_modified = value.try_into().map_err(|e| { + format!("error converting supplied value for time_modified: {}", e) + }); + self + } + pub fn vpc_id(mut self, value: T) -> Self + where + T: std::convert::TryInto, + T::Error: std::fmt::Display, + { + self.vpc_id = value + .try_into() + .map_err(|e| format!("error converting supplied value for vpc_id: {}", e)); + self + } + } + + impl std::convert::TryFrom for super::VpcRouter { + type Error = super::error::ConversionError; + fn try_from(value: VpcRouter) -> Result { + Ok(Self { + description: value.description?, + id: value.id?, + kind: value.kind?, + name: value.name?, + time_created: value.time_created?, + time_modified: value.time_modified?, + vpc_id: value.vpc_id?, + }) + } + } + + impl From for VpcRouter { + fn from(value: super::VpcRouter) -> Self { + Self { + description: Ok(value.description), + id: Ok(value.id), + kind: Ok(value.kind), + name: Ok(value.name), + time_created: Ok(value.time_created), + time_modified: Ok(value.time_modified), + vpc_id: Ok(value.vpc_id), + } + } + } + + #[derive(Clone, Debug)] + pub struct VpcRouterCreate { + description: Result, + name: Result, + } + + impl Default for VpcRouterCreate { + fn default() -> Self { + Self { + description: Err("no value supplied for description".to_string()), + name: Err("no value supplied for name".to_string()), + } + } + } + + impl VpcRouterCreate { + pub fn description(mut self, value: T) -> Self + where + T: std::convert::TryInto, + T::Error: std::fmt::Display, + { + self.description = value + .try_into() + .map_err(|e| format!("error converting supplied value for description: {}", e)); + self + } + pub fn name(mut self, value: T) -> Self + where + T: std::convert::TryInto, + T::Error: std::fmt::Display, + { + self.name = value + .try_into() + .map_err(|e| format!("error converting supplied value for name: {}", e)); + self + } + } + + impl std::convert::TryFrom for super::VpcRouterCreate { + type Error = super::error::ConversionError; + fn try_from(value: VpcRouterCreate) -> Result { + Ok(Self { + description: value.description?, + name: value.name?, + }) + } + } + + impl From for VpcRouterCreate { + fn from(value: super::VpcRouterCreate) -> Self { + Self { + description: Ok(value.description), + name: Ok(value.name), + } + } + } + + #[derive(Clone, Debug)] + pub struct VpcRouterResultsPage { + items: Result, String>, + next_page: Result, String>, + } + + impl Default for VpcRouterResultsPage { + fn default() -> Self { + Self { + items: Err("no value supplied for items".to_string()), + next_page: Ok(Default::default()), + } + } + } + + impl VpcRouterResultsPage { + pub fn items(mut self, value: T) -> Self + where + T: std::convert::TryInto>, + T::Error: std::fmt::Display, + { + self.items = value + .try_into() + .map_err(|e| format!("error converting supplied value for items: {}", e)); + self + } + pub fn next_page(mut self, value: T) -> Self + where + T: std::convert::TryInto>, + T::Error: std::fmt::Display, + { + self.next_page = value + .try_into() + .map_err(|e| format!("error converting supplied value for next_page: {}", e)); + self + } + } + + impl std::convert::TryFrom for super::VpcRouterResultsPage { + type Error = super::error::ConversionError; + fn try_from( + value: VpcRouterResultsPage, + ) -> Result { + Ok(Self { + items: value.items?, + next_page: value.next_page?, + }) + } + } + + impl From for VpcRouterResultsPage { + fn from(value: super::VpcRouterResultsPage) -> Self { + Self { + items: Ok(value.items), + next_page: Ok(value.next_page), + } + } + } + + #[derive(Clone, Debug)] + pub struct VpcRouterUpdate { + description: Result, String>, + name: Result, String>, + } + + impl Default for VpcRouterUpdate { + fn default() -> Self { + Self { + description: Ok(Default::default()), + name: Ok(Default::default()), + } + } + } + + impl VpcRouterUpdate { + pub fn description(mut self, value: T) -> Self + where + T: std::convert::TryInto>, + T::Error: std::fmt::Display, + { + self.description = value + .try_into() + .map_err(|e| format!("error converting supplied value for description: {}", e)); + self + } + pub fn name(mut self, value: T) -> Self + where + T: std::convert::TryInto>, + T::Error: std::fmt::Display, + { + self.name = value + .try_into() + .map_err(|e| format!("error converting supplied value for name: {}", e)); + self + } + } + + impl std::convert::TryFrom for super::VpcRouterUpdate { + type Error = super::error::ConversionError; + fn try_from(value: VpcRouterUpdate) -> Result { + Ok(Self { + description: value.description?, + name: value.name?, + }) + } + } + + impl From for VpcRouterUpdate { + fn from(value: super::VpcRouterUpdate) -> Self { + Self { + description: Ok(value.description), + name: Ok(value.name), + } + } + } + #[derive(Clone, Debug)] pub struct VpcSubnet { + custom_router_id: Result, String>, description: Result, id: Result, ipv4_block: Result, @@ -43354,6 +45226,7 @@ pub mod types { impl Default for VpcSubnet { fn default() -> Self { Self { + custom_router_id: Ok(Default::default()), description: Err("no value supplied for description".to_string()), id: Err("no value supplied for id".to_string()), ipv4_block: Err("no value supplied for ipv4_block".to_string()), @@ -43367,6 +45240,19 @@ pub mod types { } impl VpcSubnet { + pub fn custom_router_id(mut self, value: T) -> Self + where + T: std::convert::TryInto>, + T::Error: std::fmt::Display, + { + self.custom_router_id = value.try_into().map_err(|e| { + format!( + "error converting supplied value for custom_router_id: {}", + e + ) + }); + self + } pub fn description(mut self, value: T) -> Self where T: std::convert::TryInto, @@ -43453,6 +45339,7 @@ pub mod types { type Error = super::error::ConversionError; fn try_from(value: VpcSubnet) -> Result { Ok(Self { + custom_router_id: value.custom_router_id?, description: value.description?, id: value.id?, ipv4_block: value.ipv4_block?, @@ -43468,6 +45355,7 @@ pub mod types { impl From for VpcSubnet { fn from(value: super::VpcSubnet) -> Self { Self { + custom_router_id: Ok(value.custom_router_id), description: Ok(value.description), id: Ok(value.id), ipv4_block: Ok(value.ipv4_block), @@ -43482,6 +45370,7 @@ pub mod types { #[derive(Clone, Debug)] pub struct VpcSubnetCreate { + custom_router: Result, String>, description: Result, ipv4_block: Result, ipv6_block: Result, String>, @@ -43491,6 +45380,7 @@ pub mod types { impl Default for VpcSubnetCreate { fn default() -> Self { Self { + custom_router: Ok(Default::default()), description: Err("no value supplied for description".to_string()), ipv4_block: Err("no value supplied for ipv4_block".to_string()), ipv6_block: Ok(Default::default()), @@ -43500,6 +45390,16 @@ pub mod types { } impl VpcSubnetCreate { + pub fn custom_router(mut self, value: T) -> Self + where + T: std::convert::TryInto>, + T::Error: std::fmt::Display, + { + self.custom_router = value.try_into().map_err(|e| { + format!("error converting supplied value for custom_router: {}", e) + }); + self + } pub fn description(mut self, value: T) -> Self where T: std::convert::TryInto, @@ -43546,6 +45446,7 @@ pub mod types { type Error = super::error::ConversionError; fn try_from(value: VpcSubnetCreate) -> Result { Ok(Self { + custom_router: value.custom_router?, description: value.description?, ipv4_block: value.ipv4_block?, ipv6_block: value.ipv6_block?, @@ -43557,6 +45458,7 @@ pub mod types { impl From for VpcSubnetCreate { fn from(value: super::VpcSubnetCreate) -> Self { Self { + custom_router: Ok(value.custom_router), description: Ok(value.description), ipv4_block: Ok(value.ipv4_block), ipv6_block: Ok(value.ipv6_block), @@ -43626,6 +45528,7 @@ pub mod types { #[derive(Clone, Debug)] pub struct VpcSubnetUpdate { + custom_router: Result, String>, description: Result, String>, name: Result, String>, } @@ -43633,6 +45536,7 @@ pub mod types { impl Default for VpcSubnetUpdate { fn default() -> Self { Self { + custom_router: Ok(Default::default()), description: Ok(Default::default()), name: Ok(Default::default()), } @@ -43640,6 +45544,16 @@ pub mod types { } impl VpcSubnetUpdate { + pub fn custom_router(mut self, value: T) -> Self + where + T: std::convert::TryInto>, + T::Error: std::fmt::Display, + { + self.custom_router = value.try_into().map_err(|e| { + format!("error converting supplied value for custom_router: {}", e) + }); + self + } pub fn description(mut self, value: T) -> Self where T: std::convert::TryInto>, @@ -43666,6 +45580,7 @@ pub mod types { type Error = super::error::ConversionError; fn try_from(value: VpcSubnetUpdate) -> Result { Ok(Self { + custom_router: value.custom_router?, description: value.description?, name: value.name?, }) @@ -43675,6 +45590,7 @@ pub mod types { impl From for VpcSubnetUpdate { fn from(value: super::VpcSubnetUpdate) -> Self { Self { + custom_router: Ok(value.custom_router), description: Ok(value.description), name: Ok(value.name), } @@ -43771,7 +45687,7 @@ pub mod types { /// /// API for interacting with the Oxide control plane /// -/// Version: 20240502.0 +/// Version: 20240710.0 pub struct Client { pub(crate) baseurl: String, pub(crate) client: reqwest::Client, @@ -43824,7 +45740,7 @@ impl Client { /// This string is pulled directly from the source OpenAPI /// document and may be in any format the API selects. pub fn api_version(&self) -> &'static str { - "20240502.0" + "20240710.0" } } @@ -46593,17 +48509,20 @@ pub trait ClientSystemNetworkingExt { /// .await; /// ``` fn networking_bgp_announce_set_list(&self) -> builder::NetworkingBgpAnnounceSetList; - /// Create new BGP announce set + /// Update BGP announce set /// - /// Sends a `POST` request to `/v1/system/networking/bgp-announce` + /// If the announce set exists, this endpoint replaces the existing announce + /// set with the one specified. + /// + /// Sends a `PUT` request to `/v1/system/networking/bgp-announce` /// /// ```ignore - /// let response = client.networking_bgp_announce_set_create() + /// let response = client.networking_bgp_announce_set_update() /// .body(body) /// .send() /// .await; /// ``` - fn networking_bgp_announce_set_create(&self) -> builder::NetworkingBgpAnnounceSetCreate; + fn networking_bgp_announce_set_update(&self) -> builder::NetworkingBgpAnnounceSetUpdate; /// Delete BGP announce set /// /// Sends a `DELETE` request to `/v1/system/networking/bgp-announce` @@ -46892,8 +48811,8 @@ impl ClientSystemNetworkingExt for Client { builder::NetworkingBgpAnnounceSetList::new(self) } - fn networking_bgp_announce_set_create(&self) -> builder::NetworkingBgpAnnounceSetCreate { - builder::NetworkingBgpAnnounceSetCreate::new(self) + fn networking_bgp_announce_set_update(&self) -> builder::NetworkingBgpAnnounceSetUpdate { + builder::NetworkingBgpAnnounceSetUpdate::new(self) } fn networking_bgp_announce_set_delete(&self) -> builder::NetworkingBgpAnnounceSetDelete { @@ -47460,6 +49379,217 @@ pub trait ClientVpcsExt { /// .await; /// ``` fn vpc_firewall_rules_update(&self) -> builder::VpcFirewallRulesUpdate; + /// List routes + /// + /// List the routes associated with a router in a particular VPC. + /// + /// Sends a `GET` request to `/v1/vpc-router-routes` + /// + /// Arguments: + /// - `limit`: Maximum number of items returned by a single call + /// - `page_token`: Token returned by previous call to retrieve the + /// subsequent page + /// - `project`: Name or ID of the project, only required if `vpc` is + /// provided as a `Name` + /// - `router`: Name or ID of the router + /// - `sort_by` + /// - `vpc`: Name or ID of the VPC, only required if `router` is provided as + /// a `Name` + /// ```ignore + /// let response = client.vpc_router_route_list() + /// .limit(limit) + /// .page_token(page_token) + /// .project(project) + /// .router(router) + /// .sort_by(sort_by) + /// .vpc(vpc) + /// .send() + /// .await; + /// ``` + fn vpc_router_route_list(&self) -> builder::VpcRouterRouteList; + /// Create route + /// + /// Sends a `POST` request to `/v1/vpc-router-routes` + /// + /// Arguments: + /// - `project`: Name or ID of the project, only required if `vpc` is + /// provided as a `Name` + /// - `router`: Name or ID of the router + /// - `vpc`: Name or ID of the VPC, only required if `router` is provided as + /// a `Name` + /// - `body` + /// ```ignore + /// let response = client.vpc_router_route_create() + /// .project(project) + /// .router(router) + /// .vpc(vpc) + /// .body(body) + /// .send() + /// .await; + /// ``` + fn vpc_router_route_create(&self) -> builder::VpcRouterRouteCreate; + /// Fetch route + /// + /// Sends a `GET` request to `/v1/vpc-router-routes/{route}` + /// + /// Arguments: + /// - `route`: Name or ID of the route + /// - `project`: Name or ID of the project, only required if `vpc` is + /// provided as a `Name` + /// - `router`: Name or ID of the router + /// - `vpc`: Name or ID of the VPC, only required if `router` is provided as + /// a `Name` + /// ```ignore + /// let response = client.vpc_router_route_view() + /// .route(route) + /// .project(project) + /// .router(router) + /// .vpc(vpc) + /// .send() + /// .await; + /// ``` + fn vpc_router_route_view(&self) -> builder::VpcRouterRouteView; + /// Update route + /// + /// Sends a `PUT` request to `/v1/vpc-router-routes/{route}` + /// + /// Arguments: + /// - `route`: Name or ID of the route + /// - `project`: Name or ID of the project, only required if `vpc` is + /// provided as a `Name` + /// - `router`: Name or ID of the router + /// - `vpc`: Name or ID of the VPC, only required if `router` is provided as + /// a `Name` + /// - `body` + /// ```ignore + /// let response = client.vpc_router_route_update() + /// .route(route) + /// .project(project) + /// .router(router) + /// .vpc(vpc) + /// .body(body) + /// .send() + /// .await; + /// ``` + fn vpc_router_route_update(&self) -> builder::VpcRouterRouteUpdate; + /// Delete route + /// + /// Sends a `DELETE` request to `/v1/vpc-router-routes/{route}` + /// + /// Arguments: + /// - `route`: Name or ID of the route + /// - `project`: Name or ID of the project, only required if `vpc` is + /// provided as a `Name` + /// - `router`: Name or ID of the router + /// - `vpc`: Name or ID of the VPC, only required if `router` is provided as + /// a `Name` + /// ```ignore + /// let response = client.vpc_router_route_delete() + /// .route(route) + /// .project(project) + /// .router(router) + /// .vpc(vpc) + /// .send() + /// .await; + /// ``` + fn vpc_router_route_delete(&self) -> builder::VpcRouterRouteDelete; + /// List routers + /// + /// Sends a `GET` request to `/v1/vpc-routers` + /// + /// Arguments: + /// - `limit`: Maximum number of items returned by a single call + /// - `page_token`: Token returned by previous call to retrieve the + /// subsequent page + /// - `project`: Name or ID of the project, only required if `vpc` is + /// provided as a `Name` + /// - `sort_by` + /// - `vpc`: Name or ID of the VPC + /// ```ignore + /// let response = client.vpc_router_list() + /// .limit(limit) + /// .page_token(page_token) + /// .project(project) + /// .sort_by(sort_by) + /// .vpc(vpc) + /// .send() + /// .await; + /// ``` + fn vpc_router_list(&self) -> builder::VpcRouterList; + /// Create VPC router + /// + /// Sends a `POST` request to `/v1/vpc-routers` + /// + /// Arguments: + /// - `project`: Name or ID of the project, only required if `vpc` is + /// provided as a `Name` + /// - `vpc`: Name or ID of the VPC + /// - `body` + /// ```ignore + /// let response = client.vpc_router_create() + /// .project(project) + /// .vpc(vpc) + /// .body(body) + /// .send() + /// .await; + /// ``` + fn vpc_router_create(&self) -> builder::VpcRouterCreate; + /// Fetch router + /// + /// Sends a `GET` request to `/v1/vpc-routers/{router}` + /// + /// Arguments: + /// - `router`: Name or ID of the router + /// - `project`: Name or ID of the project, only required if `vpc` is + /// provided as a `Name` + /// - `vpc`: Name or ID of the VPC + /// ```ignore + /// let response = client.vpc_router_view() + /// .router(router) + /// .project(project) + /// .vpc(vpc) + /// .send() + /// .await; + /// ``` + fn vpc_router_view(&self) -> builder::VpcRouterView; + /// Update router + /// + /// Sends a `PUT` request to `/v1/vpc-routers/{router}` + /// + /// Arguments: + /// - `router`: Name or ID of the router + /// - `project`: Name or ID of the project, only required if `vpc` is + /// provided as a `Name` + /// - `vpc`: Name or ID of the VPC + /// - `body` + /// ```ignore + /// let response = client.vpc_router_update() + /// .router(router) + /// .project(project) + /// .vpc(vpc) + /// .body(body) + /// .send() + /// .await; + /// ``` + fn vpc_router_update(&self) -> builder::VpcRouterUpdate; + /// Delete router + /// + /// Sends a `DELETE` request to `/v1/vpc-routers/{router}` + /// + /// Arguments: + /// - `router`: Name or ID of the router + /// - `project`: Name or ID of the project, only required if `vpc` is + /// provided as a `Name` + /// - `vpc`: Name or ID of the VPC + /// ```ignore + /// let response = client.vpc_router_delete() + /// .router(router) + /// .project(project) + /// .vpc(vpc) + /// .send() + /// .await; + /// ``` + fn vpc_router_delete(&self) -> builder::VpcRouterDelete; /// List subnets /// /// Sends a `GET` request to `/v1/vpc-subnets` @@ -47675,6 +49805,46 @@ impl ClientVpcsExt for Client { builder::VpcFirewallRulesUpdate::new(self) } + fn vpc_router_route_list(&self) -> builder::VpcRouterRouteList { + builder::VpcRouterRouteList::new(self) + } + + fn vpc_router_route_create(&self) -> builder::VpcRouterRouteCreate { + builder::VpcRouterRouteCreate::new(self) + } + + fn vpc_router_route_view(&self) -> builder::VpcRouterRouteView { + builder::VpcRouterRouteView::new(self) + } + + fn vpc_router_route_update(&self) -> builder::VpcRouterRouteUpdate { + builder::VpcRouterRouteUpdate::new(self) + } + + fn vpc_router_route_delete(&self) -> builder::VpcRouterRouteDelete { + builder::VpcRouterRouteDelete::new(self) + } + + fn vpc_router_list(&self) -> builder::VpcRouterList { + builder::VpcRouterList::new(self) + } + + fn vpc_router_create(&self) -> builder::VpcRouterCreate { + builder::VpcRouterCreate::new(self) + } + + fn vpc_router_view(&self) -> builder::VpcRouterView { + builder::VpcRouterView::new(self) + } + + fn vpc_router_update(&self) -> builder::VpcRouterUpdate { + builder::VpcRouterUpdate::new(self) + } + + fn vpc_router_delete(&self) -> builder::VpcRouterDelete { + builder::VpcRouterDelete::new(self) + } + fn vpc_subnet_list(&self) -> builder::VpcSubnetList { builder::VpcSubnetList::new(self) } @@ -61557,16 +63727,16 @@ pub mod builder { } /// Builder for - /// [`ClientSystemNetworkingExt::networking_bgp_announce_set_create`] + /// [`ClientSystemNetworkingExt::networking_bgp_announce_set_update`] /// - /// [`ClientSystemNetworkingExt::networking_bgp_announce_set_create`]: super::ClientSystemNetworkingExt::networking_bgp_announce_set_create + /// [`ClientSystemNetworkingExt::networking_bgp_announce_set_update`]: super::ClientSystemNetworkingExt::networking_bgp_announce_set_update #[derive(Debug, Clone)] - pub struct NetworkingBgpAnnounceSetCreate<'a> { + pub struct NetworkingBgpAnnounceSetUpdate<'a> { client: &'a super::Client, body: Result, } - impl<'a> NetworkingBgpAnnounceSetCreate<'a> { + impl<'a> NetworkingBgpAnnounceSetUpdate<'a> { pub fn new(client: &'a super::Client) -> Self { Self { client: client, @@ -61598,7 +63768,7 @@ pub mod builder { self } - /// Sends a `POST` request to `/v1/system/networking/bgp-announce` + /// Sends a `PUT` request to `/v1/system/networking/bgp-announce` pub async fn send( self, ) -> Result, Error> { @@ -61610,7 +63780,7 @@ pub mod builder { #[allow(unused_mut)] let mut request = client .client - .post(url) + .put(url) .header( reqwest::header::ACCEPT, reqwest::header::HeaderValue::from_static("application/json"), @@ -65111,6 +67281,1344 @@ pub mod builder { } } + /// Builder for [`ClientVpcsExt::vpc_router_route_list`] + /// + /// [`ClientVpcsExt::vpc_router_route_list`]: super::ClientVpcsExt::vpc_router_route_list + #[derive(Debug, Clone)] + pub struct VpcRouterRouteList<'a> { + client: &'a super::Client, + limit: Result, String>, + page_token: Result, String>, + project: Result, String>, + router: Result, String>, + sort_by: Result, String>, + vpc: Result, String>, + } + + impl<'a> VpcRouterRouteList<'a> { + pub fn new(client: &'a super::Client) -> Self { + Self { + client: client, + limit: Ok(None), + page_token: Ok(None), + project: Ok(None), + router: Ok(None), + sort_by: Ok(None), + vpc: Ok(None), + } + } + + pub fn limit(mut self, value: V) -> Self + where + V: std::convert::TryInto, + { + self.limit = value.try_into().map(Some).map_err(|_| { + "conversion to `std :: num :: NonZeroU32` for limit failed".to_string() + }); + self + } + + pub fn page_token(mut self, value: V) -> Self + where + V: std::convert::TryInto, + { + self.page_token = value + .try_into() + .map(Some) + .map_err(|_| "conversion to `String` for page_token failed".to_string()); + self + } + + pub fn project(mut self, value: V) -> Self + where + V: std::convert::TryInto, + { + self.project = value + .try_into() + .map(Some) + .map_err(|_| "conversion to `NameOrId` for project failed".to_string()); + self + } + + pub fn router(mut self, value: V) -> Self + where + V: std::convert::TryInto, + { + self.router = value + .try_into() + .map(Some) + .map_err(|_| "conversion to `NameOrId` for router failed".to_string()); + self + } + + pub fn sort_by(mut self, value: V) -> Self + where + V: std::convert::TryInto, + { + self.sort_by = value + .try_into() + .map(Some) + .map_err(|_| "conversion to `NameOrIdSortMode` for sort_by failed".to_string()); + self + } + + pub fn vpc(mut self, value: V) -> Self + where + V: std::convert::TryInto, + { + self.vpc = value + .try_into() + .map(Some) + .map_err(|_| "conversion to `NameOrId` for vpc failed".to_string()); + self + } + + /// Sends a `GET` request to `/v1/vpc-router-routes` + pub async fn send( + self, + ) -> Result, Error> { + let Self { + client, + limit, + page_token, + project, + router, + sort_by, + vpc, + } = self; + let limit = limit.map_err(Error::InvalidRequest)?; + let page_token = page_token.map_err(Error::InvalidRequest)?; + let project = project.map_err(Error::InvalidRequest)?; + let router = router.map_err(Error::InvalidRequest)?; + let sort_by = sort_by.map_err(Error::InvalidRequest)?; + let vpc = vpc.map_err(Error::InvalidRequest)?; + let url = format!("{}/v1/vpc-router-routes", client.baseurl,); + let mut query = Vec::with_capacity(6usize); + if let Some(v) = &limit { + query.push(("limit", v.to_string())); + } + if let Some(v) = &page_token { + query.push(("page_token", v.to_string())); + } + if let Some(v) = &project { + query.push(("project", v.to_string())); + } + if let Some(v) = &router { + query.push(("router", v.to_string())); + } + if let Some(v) = &sort_by { + query.push(("sort_by", v.to_string())); + } + if let Some(v) = &vpc { + query.push(("vpc", v.to_string())); + } + #[allow(unused_mut)] + let mut request = client + .client + .get(url) + .header( + reqwest::header::ACCEPT, + reqwest::header::HeaderValue::from_static("application/json"), + ) + .query(&query) + .build()?; + let result = client.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + 400u16..=499u16 => Err(Error::ErrorResponse( + ResponseValue::from_response(response).await?, + )), + 500u16..=599u16 => Err(Error::ErrorResponse( + ResponseValue::from_response(response).await?, + )), + _ => Err(Error::UnexpectedResponse(response)), + } + } + + /// Streams `GET` requests to `/v1/vpc-router-routes` + pub fn stream( + self, + ) -> impl futures::Stream>> + Unpin + 'a + { + use futures::StreamExt; + use futures::TryFutureExt; + use futures::TryStreamExt; + let next = Self { + page_token: Ok(None), + project: Ok(None), + router: Ok(None), + sort_by: Ok(None), + vpc: Ok(None), + ..self.clone() + }; + self.send() + .map_ok(move |page| { + let page = page.into_inner(); + let first = futures::stream::iter(page.items).map(Ok); + let rest = futures::stream::try_unfold( + (page.next_page, next), + |(next_page, next)| async { + if next_page.is_none() { + Ok(None) + } else { + Self { + page_token: Ok(next_page), + ..next.clone() + } + .send() + .map_ok(|page| { + let page = page.into_inner(); + Some(( + futures::stream::iter(page.items).map(Ok), + (page.next_page, next), + )) + }) + .await + } + }, + ) + .try_flatten(); + first.chain(rest) + }) + .try_flatten_stream() + .boxed() + } + } + + /// Builder for [`ClientVpcsExt::vpc_router_route_create`] + /// + /// [`ClientVpcsExt::vpc_router_route_create`]: super::ClientVpcsExt::vpc_router_route_create + #[derive(Debug, Clone)] + pub struct VpcRouterRouteCreate<'a> { + client: &'a super::Client, + project: Result, String>, + router: Result, + vpc: Result, String>, + body: Result, + } + + impl<'a> VpcRouterRouteCreate<'a> { + pub fn new(client: &'a super::Client) -> Self { + Self { + client: client, + project: Ok(None), + router: Err("router was not initialized".to_string()), + vpc: Ok(None), + body: Ok(types::builder::RouterRouteCreate::default()), + } + } + + pub fn project(mut self, value: V) -> Self + where + V: std::convert::TryInto, + { + self.project = value + .try_into() + .map(Some) + .map_err(|_| "conversion to `NameOrId` for project failed".to_string()); + self + } + + pub fn router(mut self, value: V) -> Self + where + V: std::convert::TryInto, + { + self.router = value + .try_into() + .map_err(|_| "conversion to `NameOrId` for router failed".to_string()); + self + } + + pub fn vpc(mut self, value: V) -> Self + where + V: std::convert::TryInto, + { + self.vpc = value + .try_into() + .map(Some) + .map_err(|_| "conversion to `NameOrId` for vpc failed".to_string()); + self + } + + pub fn body(mut self, value: V) -> Self + where + V: std::convert::TryInto, + >::Error: std::fmt::Display, + { + self.body = value + .try_into() + .map(From::from) + .map_err(|s| format!("conversion to `RouterRouteCreate` for body failed: {}", s)); + self + } + + pub fn body_map(mut self, f: F) -> Self + where + F: std::ops::FnOnce( + types::builder::RouterRouteCreate, + ) -> types::builder::RouterRouteCreate, + { + self.body = self.body.map(f); + self + } + + /// Sends a `POST` request to `/v1/vpc-router-routes` + pub async fn send(self) -> Result, Error> { + let Self { + client, + project, + router, + vpc, + body, + } = self; + let project = project.map_err(Error::InvalidRequest)?; + let router = router.map_err(Error::InvalidRequest)?; + let vpc = vpc.map_err(Error::InvalidRequest)?; + let body = body + .and_then(|v| types::RouterRouteCreate::try_from(v).map_err(|e| e.to_string())) + .map_err(Error::InvalidRequest)?; + let url = format!("{}/v1/vpc-router-routes", client.baseurl,); + let mut query = Vec::with_capacity(3usize); + if let Some(v) = &project { + query.push(("project", v.to_string())); + } + query.push(("router", router.to_string())); + if let Some(v) = &vpc { + query.push(("vpc", v.to_string())); + } + #[allow(unused_mut)] + let mut request = client + .client + .post(url) + .header( + reqwest::header::ACCEPT, + reqwest::header::HeaderValue::from_static("application/json"), + ) + .json(&body) + .query(&query) + .build()?; + let result = client.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 201u16 => ResponseValue::from_response(response).await, + 400u16..=499u16 => Err(Error::ErrorResponse( + ResponseValue::from_response(response).await?, + )), + 500u16..=599u16 => Err(Error::ErrorResponse( + ResponseValue::from_response(response).await?, + )), + _ => Err(Error::UnexpectedResponse(response)), + } + } + } + + /// Builder for [`ClientVpcsExt::vpc_router_route_view`] + /// + /// [`ClientVpcsExt::vpc_router_route_view`]: super::ClientVpcsExt::vpc_router_route_view + #[derive(Debug, Clone)] + pub struct VpcRouterRouteView<'a> { + client: &'a super::Client, + route: Result, + project: Result, String>, + router: Result, + vpc: Result, String>, + } + + impl<'a> VpcRouterRouteView<'a> { + pub fn new(client: &'a super::Client) -> Self { + Self { + client: client, + route: Err("route was not initialized".to_string()), + project: Ok(None), + router: Err("router was not initialized".to_string()), + vpc: Ok(None), + } + } + + pub fn route(mut self, value: V) -> Self + where + V: std::convert::TryInto, + { + self.route = value + .try_into() + .map_err(|_| "conversion to `NameOrId` for route failed".to_string()); + self + } + + pub fn project(mut self, value: V) -> Self + where + V: std::convert::TryInto, + { + self.project = value + .try_into() + .map(Some) + .map_err(|_| "conversion to `NameOrId` for project failed".to_string()); + self + } + + pub fn router(mut self, value: V) -> Self + where + V: std::convert::TryInto, + { + self.router = value + .try_into() + .map_err(|_| "conversion to `NameOrId` for router failed".to_string()); + self + } + + pub fn vpc(mut self, value: V) -> Self + where + V: std::convert::TryInto, + { + self.vpc = value + .try_into() + .map(Some) + .map_err(|_| "conversion to `NameOrId` for vpc failed".to_string()); + self + } + + /// Sends a `GET` request to `/v1/vpc-router-routes/{route}` + pub async fn send(self) -> Result, Error> { + let Self { + client, + route, + project, + router, + vpc, + } = self; + let route = route.map_err(Error::InvalidRequest)?; + let project = project.map_err(Error::InvalidRequest)?; + let router = router.map_err(Error::InvalidRequest)?; + let vpc = vpc.map_err(Error::InvalidRequest)?; + let url = format!( + "{}/v1/vpc-router-routes/{}", + client.baseurl, + encode_path(&route.to_string()), + ); + let mut query = Vec::with_capacity(3usize); + if let Some(v) = &project { + query.push(("project", v.to_string())); + } + query.push(("router", router.to_string())); + if let Some(v) = &vpc { + query.push(("vpc", v.to_string())); + } + #[allow(unused_mut)] + let mut request = client + .client + .get(url) + .header( + reqwest::header::ACCEPT, + reqwest::header::HeaderValue::from_static("application/json"), + ) + .query(&query) + .build()?; + let result = client.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + 400u16..=499u16 => Err(Error::ErrorResponse( + ResponseValue::from_response(response).await?, + )), + 500u16..=599u16 => Err(Error::ErrorResponse( + ResponseValue::from_response(response).await?, + )), + _ => Err(Error::UnexpectedResponse(response)), + } + } + } + + /// Builder for [`ClientVpcsExt::vpc_router_route_update`] + /// + /// [`ClientVpcsExt::vpc_router_route_update`]: super::ClientVpcsExt::vpc_router_route_update + #[derive(Debug, Clone)] + pub struct VpcRouterRouteUpdate<'a> { + client: &'a super::Client, + route: Result, + project: Result, String>, + router: Result, String>, + vpc: Result, String>, + body: Result, + } + + impl<'a> VpcRouterRouteUpdate<'a> { + pub fn new(client: &'a super::Client) -> Self { + Self { + client: client, + route: Err("route was not initialized".to_string()), + project: Ok(None), + router: Ok(None), + vpc: Ok(None), + body: Ok(types::builder::RouterRouteUpdate::default()), + } + } + + pub fn route(mut self, value: V) -> Self + where + V: std::convert::TryInto, + { + self.route = value + .try_into() + .map_err(|_| "conversion to `NameOrId` for route failed".to_string()); + self + } + + pub fn project(mut self, value: V) -> Self + where + V: std::convert::TryInto, + { + self.project = value + .try_into() + .map(Some) + .map_err(|_| "conversion to `NameOrId` for project failed".to_string()); + self + } + + pub fn router(mut self, value: V) -> Self + where + V: std::convert::TryInto, + { + self.router = value + .try_into() + .map(Some) + .map_err(|_| "conversion to `NameOrId` for router failed".to_string()); + self + } + + pub fn vpc(mut self, value: V) -> Self + where + V: std::convert::TryInto, + { + self.vpc = value + .try_into() + .map(Some) + .map_err(|_| "conversion to `NameOrId` for vpc failed".to_string()); + self + } + + pub fn body(mut self, value: V) -> Self + where + V: std::convert::TryInto, + >::Error: std::fmt::Display, + { + self.body = value + .try_into() + .map(From::from) + .map_err(|s| format!("conversion to `RouterRouteUpdate` for body failed: {}", s)); + self + } + + pub fn body_map(mut self, f: F) -> Self + where + F: std::ops::FnOnce( + types::builder::RouterRouteUpdate, + ) -> types::builder::RouterRouteUpdate, + { + self.body = self.body.map(f); + self + } + + /// Sends a `PUT` request to `/v1/vpc-router-routes/{route}` + pub async fn send(self) -> Result, Error> { + let Self { + client, + route, + project, + router, + vpc, + body, + } = self; + let route = route.map_err(Error::InvalidRequest)?; + let project = project.map_err(Error::InvalidRequest)?; + let router = router.map_err(Error::InvalidRequest)?; + let vpc = vpc.map_err(Error::InvalidRequest)?; + let body = body + .and_then(|v| types::RouterRouteUpdate::try_from(v).map_err(|e| e.to_string())) + .map_err(Error::InvalidRequest)?; + let url = format!( + "{}/v1/vpc-router-routes/{}", + client.baseurl, + encode_path(&route.to_string()), + ); + let mut query = Vec::with_capacity(3usize); + if let Some(v) = &project { + query.push(("project", v.to_string())); + } + if let Some(v) = &router { + query.push(("router", v.to_string())); + } + if let Some(v) = &vpc { + query.push(("vpc", v.to_string())); + } + #[allow(unused_mut)] + let mut request = client + .client + .put(url) + .header( + reqwest::header::ACCEPT, + reqwest::header::HeaderValue::from_static("application/json"), + ) + .json(&body) + .query(&query) + .build()?; + let result = client.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + 400u16..=499u16 => Err(Error::ErrorResponse( + ResponseValue::from_response(response).await?, + )), + 500u16..=599u16 => Err(Error::ErrorResponse( + ResponseValue::from_response(response).await?, + )), + _ => Err(Error::UnexpectedResponse(response)), + } + } + } + + /// Builder for [`ClientVpcsExt::vpc_router_route_delete`] + /// + /// [`ClientVpcsExt::vpc_router_route_delete`]: super::ClientVpcsExt::vpc_router_route_delete + #[derive(Debug, Clone)] + pub struct VpcRouterRouteDelete<'a> { + client: &'a super::Client, + route: Result, + project: Result, String>, + router: Result, String>, + vpc: Result, String>, + } + + impl<'a> VpcRouterRouteDelete<'a> { + pub fn new(client: &'a super::Client) -> Self { + Self { + client: client, + route: Err("route was not initialized".to_string()), + project: Ok(None), + router: Ok(None), + vpc: Ok(None), + } + } + + pub fn route(mut self, value: V) -> Self + where + V: std::convert::TryInto, + { + self.route = value + .try_into() + .map_err(|_| "conversion to `NameOrId` for route failed".to_string()); + self + } + + pub fn project(mut self, value: V) -> Self + where + V: std::convert::TryInto, + { + self.project = value + .try_into() + .map(Some) + .map_err(|_| "conversion to `NameOrId` for project failed".to_string()); + self + } + + pub fn router(mut self, value: V) -> Self + where + V: std::convert::TryInto, + { + self.router = value + .try_into() + .map(Some) + .map_err(|_| "conversion to `NameOrId` for router failed".to_string()); + self + } + + pub fn vpc(mut self, value: V) -> Self + where + V: std::convert::TryInto, + { + self.vpc = value + .try_into() + .map(Some) + .map_err(|_| "conversion to `NameOrId` for vpc failed".to_string()); + self + } + + /// Sends a `DELETE` request to `/v1/vpc-router-routes/{route}` + pub async fn send(self) -> Result, Error> { + let Self { + client, + route, + project, + router, + vpc, + } = self; + let route = route.map_err(Error::InvalidRequest)?; + let project = project.map_err(Error::InvalidRequest)?; + let router = router.map_err(Error::InvalidRequest)?; + let vpc = vpc.map_err(Error::InvalidRequest)?; + let url = format!( + "{}/v1/vpc-router-routes/{}", + client.baseurl, + encode_path(&route.to_string()), + ); + let mut query = Vec::with_capacity(3usize); + if let Some(v) = &project { + query.push(("project", v.to_string())); + } + if let Some(v) = &router { + query.push(("router", v.to_string())); + } + if let Some(v) = &vpc { + query.push(("vpc", v.to_string())); + } + #[allow(unused_mut)] + let mut request = client + .client + .delete(url) + .header( + reqwest::header::ACCEPT, + reqwest::header::HeaderValue::from_static("application/json"), + ) + .query(&query) + .build()?; + let result = client.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 204u16 => Ok(ResponseValue::empty(response)), + 400u16..=499u16 => Err(Error::ErrorResponse( + ResponseValue::from_response(response).await?, + )), + 500u16..=599u16 => Err(Error::ErrorResponse( + ResponseValue::from_response(response).await?, + )), + _ => Err(Error::UnexpectedResponse(response)), + } + } + } + + /// Builder for [`ClientVpcsExt::vpc_router_list`] + /// + /// [`ClientVpcsExt::vpc_router_list`]: super::ClientVpcsExt::vpc_router_list + #[derive(Debug, Clone)] + pub struct VpcRouterList<'a> { + client: &'a super::Client, + limit: Result, String>, + page_token: Result, String>, + project: Result, String>, + sort_by: Result, String>, + vpc: Result, String>, + } + + impl<'a> VpcRouterList<'a> { + pub fn new(client: &'a super::Client) -> Self { + Self { + client: client, + limit: Ok(None), + page_token: Ok(None), + project: Ok(None), + sort_by: Ok(None), + vpc: Ok(None), + } + } + + pub fn limit(mut self, value: V) -> Self + where + V: std::convert::TryInto, + { + self.limit = value.try_into().map(Some).map_err(|_| { + "conversion to `std :: num :: NonZeroU32` for limit failed".to_string() + }); + self + } + + pub fn page_token(mut self, value: V) -> Self + where + V: std::convert::TryInto, + { + self.page_token = value + .try_into() + .map(Some) + .map_err(|_| "conversion to `String` for page_token failed".to_string()); + self + } + + pub fn project(mut self, value: V) -> Self + where + V: std::convert::TryInto, + { + self.project = value + .try_into() + .map(Some) + .map_err(|_| "conversion to `NameOrId` for project failed".to_string()); + self + } + + pub fn sort_by(mut self, value: V) -> Self + where + V: std::convert::TryInto, + { + self.sort_by = value + .try_into() + .map(Some) + .map_err(|_| "conversion to `NameOrIdSortMode` for sort_by failed".to_string()); + self + } + + pub fn vpc(mut self, value: V) -> Self + where + V: std::convert::TryInto, + { + self.vpc = value + .try_into() + .map(Some) + .map_err(|_| "conversion to `NameOrId` for vpc failed".to_string()); + self + } + + /// Sends a `GET` request to `/v1/vpc-routers` + pub async fn send( + self, + ) -> Result, Error> { + let Self { + client, + limit, + page_token, + project, + sort_by, + vpc, + } = self; + let limit = limit.map_err(Error::InvalidRequest)?; + let page_token = page_token.map_err(Error::InvalidRequest)?; + let project = project.map_err(Error::InvalidRequest)?; + let sort_by = sort_by.map_err(Error::InvalidRequest)?; + let vpc = vpc.map_err(Error::InvalidRequest)?; + let url = format!("{}/v1/vpc-routers", client.baseurl,); + let mut query = Vec::with_capacity(5usize); + if let Some(v) = &limit { + query.push(("limit", v.to_string())); + } + if let Some(v) = &page_token { + query.push(("page_token", v.to_string())); + } + if let Some(v) = &project { + query.push(("project", v.to_string())); + } + if let Some(v) = &sort_by { + query.push(("sort_by", v.to_string())); + } + if let Some(v) = &vpc { + query.push(("vpc", v.to_string())); + } + #[allow(unused_mut)] + let mut request = client + .client + .get(url) + .header( + reqwest::header::ACCEPT, + reqwest::header::HeaderValue::from_static("application/json"), + ) + .query(&query) + .build()?; + let result = client.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + 400u16..=499u16 => Err(Error::ErrorResponse( + ResponseValue::from_response(response).await?, + )), + 500u16..=599u16 => Err(Error::ErrorResponse( + ResponseValue::from_response(response).await?, + )), + _ => Err(Error::UnexpectedResponse(response)), + } + } + + /// Streams `GET` requests to `/v1/vpc-routers` + pub fn stream( + self, + ) -> impl futures::Stream>> + Unpin + 'a + { + use futures::StreamExt; + use futures::TryFutureExt; + use futures::TryStreamExt; + let next = Self { + page_token: Ok(None), + project: Ok(None), + sort_by: Ok(None), + vpc: Ok(None), + ..self.clone() + }; + self.send() + .map_ok(move |page| { + let page = page.into_inner(); + let first = futures::stream::iter(page.items).map(Ok); + let rest = futures::stream::try_unfold( + (page.next_page, next), + |(next_page, next)| async { + if next_page.is_none() { + Ok(None) + } else { + Self { + page_token: Ok(next_page), + ..next.clone() + } + .send() + .map_ok(|page| { + let page = page.into_inner(); + Some(( + futures::stream::iter(page.items).map(Ok), + (page.next_page, next), + )) + }) + .await + } + }, + ) + .try_flatten(); + first.chain(rest) + }) + .try_flatten_stream() + .boxed() + } + } + + /// Builder for [`ClientVpcsExt::vpc_router_create`] + /// + /// [`ClientVpcsExt::vpc_router_create`]: super::ClientVpcsExt::vpc_router_create + #[derive(Debug, Clone)] + pub struct VpcRouterCreate<'a> { + client: &'a super::Client, + project: Result, String>, + vpc: Result, + body: Result, + } + + impl<'a> VpcRouterCreate<'a> { + pub fn new(client: &'a super::Client) -> Self { + Self { + client: client, + project: Ok(None), + vpc: Err("vpc was not initialized".to_string()), + body: Ok(types::builder::VpcRouterCreate::default()), + } + } + + pub fn project(mut self, value: V) -> Self + where + V: std::convert::TryInto, + { + self.project = value + .try_into() + .map(Some) + .map_err(|_| "conversion to `NameOrId` for project failed".to_string()); + self + } + + pub fn vpc(mut self, value: V) -> Self + where + V: std::convert::TryInto, + { + self.vpc = value + .try_into() + .map_err(|_| "conversion to `NameOrId` for vpc failed".to_string()); + self + } + + pub fn body(mut self, value: V) -> Self + where + V: std::convert::TryInto, + >::Error: std::fmt::Display, + { + self.body = value + .try_into() + .map(From::from) + .map_err(|s| format!("conversion to `VpcRouterCreate` for body failed: {}", s)); + self + } + + pub fn body_map(mut self, f: F) -> Self + where + F: std::ops::FnOnce(types::builder::VpcRouterCreate) -> types::builder::VpcRouterCreate, + { + self.body = self.body.map(f); + self + } + + /// Sends a `POST` request to `/v1/vpc-routers` + pub async fn send(self) -> Result, Error> { + let Self { + client, + project, + vpc, + body, + } = self; + let project = project.map_err(Error::InvalidRequest)?; + let vpc = vpc.map_err(Error::InvalidRequest)?; + let body = body + .and_then(|v| types::VpcRouterCreate::try_from(v).map_err(|e| e.to_string())) + .map_err(Error::InvalidRequest)?; + let url = format!("{}/v1/vpc-routers", client.baseurl,); + let mut query = Vec::with_capacity(2usize); + if let Some(v) = &project { + query.push(("project", v.to_string())); + } + query.push(("vpc", vpc.to_string())); + #[allow(unused_mut)] + let mut request = client + .client + .post(url) + .header( + reqwest::header::ACCEPT, + reqwest::header::HeaderValue::from_static("application/json"), + ) + .json(&body) + .query(&query) + .build()?; + let result = client.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 201u16 => ResponseValue::from_response(response).await, + 400u16..=499u16 => Err(Error::ErrorResponse( + ResponseValue::from_response(response).await?, + )), + 500u16..=599u16 => Err(Error::ErrorResponse( + ResponseValue::from_response(response).await?, + )), + _ => Err(Error::UnexpectedResponse(response)), + } + } + } + + /// Builder for [`ClientVpcsExt::vpc_router_view`] + /// + /// [`ClientVpcsExt::vpc_router_view`]: super::ClientVpcsExt::vpc_router_view + #[derive(Debug, Clone)] + pub struct VpcRouterView<'a> { + client: &'a super::Client, + router: Result, + project: Result, String>, + vpc: Result, String>, + } + + impl<'a> VpcRouterView<'a> { + pub fn new(client: &'a super::Client) -> Self { + Self { + client: client, + router: Err("router was not initialized".to_string()), + project: Ok(None), + vpc: Ok(None), + } + } + + pub fn router(mut self, value: V) -> Self + where + V: std::convert::TryInto, + { + self.router = value + .try_into() + .map_err(|_| "conversion to `NameOrId` for router failed".to_string()); + self + } + + pub fn project(mut self, value: V) -> Self + where + V: std::convert::TryInto, + { + self.project = value + .try_into() + .map(Some) + .map_err(|_| "conversion to `NameOrId` for project failed".to_string()); + self + } + + pub fn vpc(mut self, value: V) -> Self + where + V: std::convert::TryInto, + { + self.vpc = value + .try_into() + .map(Some) + .map_err(|_| "conversion to `NameOrId` for vpc failed".to_string()); + self + } + + /// Sends a `GET` request to `/v1/vpc-routers/{router}` + pub async fn send(self) -> Result, Error> { + let Self { + client, + router, + project, + vpc, + } = self; + let router = router.map_err(Error::InvalidRequest)?; + let project = project.map_err(Error::InvalidRequest)?; + let vpc = vpc.map_err(Error::InvalidRequest)?; + let url = format!( + "{}/v1/vpc-routers/{}", + client.baseurl, + encode_path(&router.to_string()), + ); + let mut query = Vec::with_capacity(2usize); + if let Some(v) = &project { + query.push(("project", v.to_string())); + } + if let Some(v) = &vpc { + query.push(("vpc", v.to_string())); + } + #[allow(unused_mut)] + let mut request = client + .client + .get(url) + .header( + reqwest::header::ACCEPT, + reqwest::header::HeaderValue::from_static("application/json"), + ) + .query(&query) + .build()?; + let result = client.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + 400u16..=499u16 => Err(Error::ErrorResponse( + ResponseValue::from_response(response).await?, + )), + 500u16..=599u16 => Err(Error::ErrorResponse( + ResponseValue::from_response(response).await?, + )), + _ => Err(Error::UnexpectedResponse(response)), + } + } + } + + /// Builder for [`ClientVpcsExt::vpc_router_update`] + /// + /// [`ClientVpcsExt::vpc_router_update`]: super::ClientVpcsExt::vpc_router_update + #[derive(Debug, Clone)] + pub struct VpcRouterUpdate<'a> { + client: &'a super::Client, + router: Result, + project: Result, String>, + vpc: Result, String>, + body: Result, + } + + impl<'a> VpcRouterUpdate<'a> { + pub fn new(client: &'a super::Client) -> Self { + Self { + client: client, + router: Err("router was not initialized".to_string()), + project: Ok(None), + vpc: Ok(None), + body: Ok(types::builder::VpcRouterUpdate::default()), + } + } + + pub fn router(mut self, value: V) -> Self + where + V: std::convert::TryInto, + { + self.router = value + .try_into() + .map_err(|_| "conversion to `NameOrId` for router failed".to_string()); + self + } + + pub fn project(mut self, value: V) -> Self + where + V: std::convert::TryInto, + { + self.project = value + .try_into() + .map(Some) + .map_err(|_| "conversion to `NameOrId` for project failed".to_string()); + self + } + + pub fn vpc(mut self, value: V) -> Self + where + V: std::convert::TryInto, + { + self.vpc = value + .try_into() + .map(Some) + .map_err(|_| "conversion to `NameOrId` for vpc failed".to_string()); + self + } + + pub fn body(mut self, value: V) -> Self + where + V: std::convert::TryInto, + >::Error: std::fmt::Display, + { + self.body = value + .try_into() + .map(From::from) + .map_err(|s| format!("conversion to `VpcRouterUpdate` for body failed: {}", s)); + self + } + + pub fn body_map(mut self, f: F) -> Self + where + F: std::ops::FnOnce(types::builder::VpcRouterUpdate) -> types::builder::VpcRouterUpdate, + { + self.body = self.body.map(f); + self + } + + /// Sends a `PUT` request to `/v1/vpc-routers/{router}` + pub async fn send(self) -> Result, Error> { + let Self { + client, + router, + project, + vpc, + body, + } = self; + let router = router.map_err(Error::InvalidRequest)?; + let project = project.map_err(Error::InvalidRequest)?; + let vpc = vpc.map_err(Error::InvalidRequest)?; + let body = body + .and_then(|v| types::VpcRouterUpdate::try_from(v).map_err(|e| e.to_string())) + .map_err(Error::InvalidRequest)?; + let url = format!( + "{}/v1/vpc-routers/{}", + client.baseurl, + encode_path(&router.to_string()), + ); + let mut query = Vec::with_capacity(2usize); + if let Some(v) = &project { + query.push(("project", v.to_string())); + } + if let Some(v) = &vpc { + query.push(("vpc", v.to_string())); + } + #[allow(unused_mut)] + let mut request = client + .client + .put(url) + .header( + reqwest::header::ACCEPT, + reqwest::header::HeaderValue::from_static("application/json"), + ) + .json(&body) + .query(&query) + .build()?; + let result = client.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 200u16 => ResponseValue::from_response(response).await, + 400u16..=499u16 => Err(Error::ErrorResponse( + ResponseValue::from_response(response).await?, + )), + 500u16..=599u16 => Err(Error::ErrorResponse( + ResponseValue::from_response(response).await?, + )), + _ => Err(Error::UnexpectedResponse(response)), + } + } + } + + /// Builder for [`ClientVpcsExt::vpc_router_delete`] + /// + /// [`ClientVpcsExt::vpc_router_delete`]: super::ClientVpcsExt::vpc_router_delete + #[derive(Debug, Clone)] + pub struct VpcRouterDelete<'a> { + client: &'a super::Client, + router: Result, + project: Result, String>, + vpc: Result, String>, + } + + impl<'a> VpcRouterDelete<'a> { + pub fn new(client: &'a super::Client) -> Self { + Self { + client: client, + router: Err("router was not initialized".to_string()), + project: Ok(None), + vpc: Ok(None), + } + } + + pub fn router(mut self, value: V) -> Self + where + V: std::convert::TryInto, + { + self.router = value + .try_into() + .map_err(|_| "conversion to `NameOrId` for router failed".to_string()); + self + } + + pub fn project(mut self, value: V) -> Self + where + V: std::convert::TryInto, + { + self.project = value + .try_into() + .map(Some) + .map_err(|_| "conversion to `NameOrId` for project failed".to_string()); + self + } + + pub fn vpc(mut self, value: V) -> Self + where + V: std::convert::TryInto, + { + self.vpc = value + .try_into() + .map(Some) + .map_err(|_| "conversion to `NameOrId` for vpc failed".to_string()); + self + } + + /// Sends a `DELETE` request to `/v1/vpc-routers/{router}` + pub async fn send(self) -> Result, Error> { + let Self { + client, + router, + project, + vpc, + } = self; + let router = router.map_err(Error::InvalidRequest)?; + let project = project.map_err(Error::InvalidRequest)?; + let vpc = vpc.map_err(Error::InvalidRequest)?; + let url = format!( + "{}/v1/vpc-routers/{}", + client.baseurl, + encode_path(&router.to_string()), + ); + let mut query = Vec::with_capacity(2usize); + if let Some(v) = &project { + query.push(("project", v.to_string())); + } + if let Some(v) = &vpc { + query.push(("vpc", v.to_string())); + } + #[allow(unused_mut)] + let mut request = client + .client + .delete(url) + .header( + reqwest::header::ACCEPT, + reqwest::header::HeaderValue::from_static("application/json"), + ) + .query(&query) + .build()?; + let result = client.client.execute(request).await; + let response = result?; + match response.status().as_u16() { + 204u16 => Ok(ResponseValue::empty(response)), + 400u16..=499u16 => Err(Error::ErrorResponse( + ResponseValue::from_response(response).await?, + )), + 500u16..=599u16 => Err(Error::ErrorResponse( + ResponseValue::from_response(response).await?, + )), + _ => Err(Error::UnexpectedResponse(response)), + } + } + } + /// Builder for [`ClientVpcsExt::vpc_subnet_list`] /// /// [`ClientVpcsExt::vpc_subnet_list`]: super::ClientVpcsExt::vpc_subnet_list diff --git a/sdk/src/lib.rs b/sdk/src/lib.rs index 23105547..1a33e75e 100644 --- a/sdk/src/lib.rs +++ b/sdk/src/lib.rs @@ -7,29 +7,37 @@ #![forbid(unsafe_code)] #![doc = include_str!("../README.md")] +use std::path::PathBuf; + use thiserror::Error; + +mod auth; mod generated_sdk; #[cfg(feature = "clap")] mod clap_feature; -pub mod config; -pub mod context; - +pub use auth::*; pub use generated_sdk::*; +/// Errors for interfaces related to authentication #[derive(Error, Debug)] -/// Errors for interfaces related to configuration and authentication -pub enum OxideError { +pub enum OxideAuthError { + #[error(r"$OXIDE_TOKEN is set but $OXIDE_HOST is not")] + MissingHost, #[error( r"$OXIDE_HOST is set, but {0} has no corresponding token.\n Login without $OXIDE_HOST set or set $OXIDE_TOKEN." )] MissingToken(String), - #[error("no authenticated hosts")] - NoAuthenticatedHosts, - #[error("TomlError: {0}")] - TomlError(toml_edit::TomlError), + #[error("Parse error for {0}: {1}")] + TomlError(PathBuf, toml::de::Error), #[error("IO Error: {0}")] IoError(std::io::Error), + #[error("No profile specified and no default profile")] + NoDefaultProfile, + #[error("Profile information not present in {0} for {1}")] + NoProfile(PathBuf, String), + #[error("no authenticated hosts; use oxide auth login to authenticate")] + NoAuthenticatedHosts, }