├── .github ├── dependabot.yml └── workflows │ ├── release.yml │ └── test.yml ├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── README.md ├── assets └── logo.svg ├── completion ├── kubie.bash └── kubie.fish ├── rustfmt.toml └── src ├── cmd ├── context.rs ├── delete.rs ├── edit.rs ├── exec.rs ├── export.rs ├── info.rs ├── lint.rs ├── meta.rs ├── mod.rs ├── namespace.rs └── update.rs ├── ioutil.rs ├── kubeconfig.rs ├── kubectl.rs ├── main.rs ├── session.rs ├── settings.rs ├── shell ├── bash.rs ├── detect.rs ├── fish.rs ├── mod.rs ├── nu.rs ├── prompt.rs ├── xonsh.rs └── zsh.rs ├── state.rs └── vars.rs /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 5 | 6 | version: 2 7 | updates: 8 | - package-ecosystem: "cargo" # See documentation for possible values 9 | directory: "/" # Location of package manifests 10 | schedule: 11 | interval: "daily" 12 | groups: 13 | all: 14 | patterns: 15 | - "*" 16 | update-types: 17 | - "minor" 18 | - "patch" 19 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | push: 5 | tags: 6 | - '*' 7 | 8 | jobs: 9 | build-linux-amd64-static: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v3 13 | - uses: actions-rs/toolchain@v1 14 | with: 15 | toolchain: stable 16 | target: x86_64-unknown-linux-musl 17 | - run: sudo apt-get update && sudo apt-get install -y musl-tools 18 | - uses: actions-rs/cargo@v1 19 | with: 20 | command: build 21 | args: --release --target x86_64-unknown-linux-musl 22 | - uses: svenstaro/upload-release-action@v2 23 | with: 24 | repo_token: ${{ secrets.GITHUB_TOKEN }} 25 | file: target/x86_64-unknown-linux-musl/release/kubie 26 | asset_name: kubie-linux-amd64 27 | tag: ${{ github.ref }} 28 | 29 | build-linux-arm32-static: 30 | runs-on: ubuntu-latest 31 | steps: 32 | - uses: actions/checkout@v3 33 | - uses: actions-rs/toolchain@v1 34 | with: 35 | toolchain: stable 36 | target: arm-unknown-linux-musleabi 37 | override: true 38 | - uses: actions-rs/cargo@v1 39 | with: 40 | use-cross: true 41 | command: build 42 | args: --release --target arm-unknown-linux-musleabi 43 | - uses: svenstaro/upload-release-action@v2 44 | with: 45 | repo_token: ${{ secrets.GITHUB_TOKEN }} 46 | file: target/arm-unknown-linux-musleabi/release/kubie 47 | asset_name: kubie-linux-arm32 48 | tag: ${{ github.ref }} 49 | 50 | build-linux-arm64-static: 51 | runs-on: ubuntu-latest 52 | steps: 53 | - uses: actions/checkout@v3 54 | - uses: actions-rs/toolchain@v1 55 | with: 56 | toolchain: stable 57 | target: aarch64-unknown-linux-musl 58 | override: true 59 | - uses: actions-rs/cargo@v1 60 | with: 61 | use-cross: true 62 | command: build 63 | args: --release --target aarch64-unknown-linux-musl 64 | - uses: svenstaro/upload-release-action@v2 65 | with: 66 | repo_token: ${{ secrets.GITHUB_TOKEN }} 67 | file: target/aarch64-unknown-linux-musl/release/kubie 68 | asset_name: kubie-linux-arm64 69 | tag: ${{ github.ref }} 70 | 71 | build-macos-amd64: 72 | runs-on: macos-latest 73 | steps: 74 | - uses: actions/checkout@v3 75 | - uses: actions-rs/toolchain@v1 76 | with: 77 | toolchain: stable 78 | target: x86_64-apple-darwin 79 | - run: SDKROOT=$(xcrun --sdk macosx --show-sdk-path) MACOSX_DEPLOYMENT_TARGET=11.0 cargo build --release --target x86_64-apple-darwin 80 | - uses: svenstaro/upload-release-action@v2 81 | with: 82 | repo_token: ${{ secrets.GITHUB_TOKEN }} 83 | file: target/x86_64-apple-darwin/release/kubie 84 | asset_name: kubie-darwin-amd64 85 | tag: ${{ github.ref }} 86 | 87 | build-macos-arm64: 88 | runs-on: macos-latest 89 | steps: 90 | - uses: actions/checkout@v3 91 | - uses: actions-rs/toolchain@v1 92 | with: 93 | toolchain: stable 94 | target: aarch64-apple-darwin 95 | - run: SDKROOT=$(xcrun --sdk macosx --show-sdk-path) MACOSX_DEPLOYMENT_TARGET=11.0 cargo build --release --target aarch64-apple-darwin 96 | - uses: svenstaro/upload-release-action@v2 97 | with: 98 | repo_token: ${{ secrets.GITHUB_TOKEN }} 99 | file: target/aarch64-apple-darwin/release/kubie 100 | asset_name: kubie-darwin-arm64 101 | tag: ${{ github.ref }} 102 | 103 | publish-crates-io: 104 | name: Publish to crates.io 105 | runs-on: ubuntu-latest 106 | steps: 107 | - name: Checkout sources 108 | uses: actions/checkout@v3 109 | - name: Install stable toolchain 110 | uses: actions-rs/toolchain@v1 111 | with: 112 | profile: minimal 113 | toolchain: stable 114 | override: true 115 | - run: cargo publish --token ${CRATES_TOKEN} 116 | env: 117 | CRATES_TOKEN: ${{ secrets.CRATES_TOKEN }} 118 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Test 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | pull_request: 8 | 9 | jobs: 10 | test-linux-amd64: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v3 14 | - uses: actions-rs/toolchain@v1 15 | with: 16 | toolchain: stable 17 | 18 | - uses: actions-rs/cargo@v1 19 | with: 20 | command: test 21 | args: --all-features 22 | 23 | test-linux-arm32: 24 | runs-on: ubuntu-latest 25 | steps: 26 | - uses: actions/checkout@v3 27 | - uses: actions-rs/toolchain@v1 28 | with: 29 | toolchain: stable 30 | target: arm-unknown-linux-musleabi 31 | override: true 32 | 33 | - uses: actions-rs/cargo@v1 34 | with: 35 | use-cross: true 36 | command: test 37 | args: --all-features --target arm-unknown-linux-musleabi 38 | 39 | test-linux-arm64: 40 | runs-on: ubuntu-latest 41 | steps: 42 | - uses: actions/checkout@v3 43 | - uses: actions-rs/toolchain@v1 44 | with: 45 | toolchain: stable 46 | target: aarch64-unknown-linux-musl 47 | override: true 48 | 49 | - uses: actions-rs/cargo@v1 50 | with: 51 | use-cross: true 52 | command: test 53 | args: --all-features --target aarch64-unknown-linux-musl 54 | 55 | test-macos-amd64: 56 | runs-on: macos-latest 57 | steps: 58 | - uses: actions/checkout@v3 59 | - uses: actions-rs/toolchain@v1 60 | with: 61 | toolchain: stable 62 | 63 | - uses: actions-rs/cargo@v1 64 | with: 65 | command: test 66 | args: --all-features 67 | 68 | # We use an amd64 machine to cross compile for arm64. We cannot run the tests, 69 | # only verify that the project compiles well. 70 | build-macos-arm64: 71 | runs-on: macos-latest 72 | steps: 73 | - uses: actions/checkout@v3 74 | - uses: actions-rs/toolchain@v1 75 | with: 76 | toolchain: stable 77 | target: aarch64-apple-darwin 78 | 79 | - run: SDKROOT=$(xcrun --sdk macosx --show-sdk-path) MACOSX_DEPLOYMENT_TARGET=11.0 cargo build --all-features --target aarch64-apple-darwin 80 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | **/*.rs.bk 3 | /binaries 4 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 4 4 | 5 | [[package]] 6 | name = "adler2" 7 | version = "2.0.0" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" 10 | 11 | [[package]] 12 | name = "aho-corasick" 13 | version = "1.1.3" 14 | source = "registry+https://github.com/rust-lang/crates.io-index" 15 | checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" 16 | dependencies = [ 17 | "memchr", 18 | ] 19 | 20 | [[package]] 21 | name = "android-tzdata" 22 | version = "0.1.1" 23 | source = "registry+https://github.com/rust-lang/crates.io-index" 24 | checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" 25 | 26 | [[package]] 27 | name = "android_system_properties" 28 | version = "0.1.5" 29 | source = "registry+https://github.com/rust-lang/crates.io-index" 30 | checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" 31 | dependencies = [ 32 | "libc", 33 | ] 34 | 35 | [[package]] 36 | name = "anstream" 37 | version = "0.6.18" 38 | source = "registry+https://github.com/rust-lang/crates.io-index" 39 | checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b" 40 | dependencies = [ 41 | "anstyle", 42 | "anstyle-parse", 43 | "anstyle-query", 44 | "anstyle-wincon", 45 | "colorchoice", 46 | "is_terminal_polyfill", 47 | "utf8parse", 48 | ] 49 | 50 | [[package]] 51 | name = "anstyle" 52 | version = "1.0.10" 53 | source = "registry+https://github.com/rust-lang/crates.io-index" 54 | checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" 55 | 56 | [[package]] 57 | name = "anstyle-parse" 58 | version = "0.2.6" 59 | source = "registry+https://github.com/rust-lang/crates.io-index" 60 | checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9" 61 | dependencies = [ 62 | "utf8parse", 63 | ] 64 | 65 | [[package]] 66 | name = "anstyle-query" 67 | version = "1.1.2" 68 | source = "registry+https://github.com/rust-lang/crates.io-index" 69 | checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c" 70 | dependencies = [ 71 | "windows-sys 0.59.0", 72 | ] 73 | 74 | [[package]] 75 | name = "anstyle-wincon" 76 | version = "3.0.7" 77 | source = "registry+https://github.com/rust-lang/crates.io-index" 78 | checksum = "ca3534e77181a9cc07539ad51f2141fe32f6c3ffd4df76db8ad92346b003ae4e" 79 | dependencies = [ 80 | "anstyle", 81 | "once_cell", 82 | "windows-sys 0.59.0", 83 | ] 84 | 85 | [[package]] 86 | name = "anyhow" 87 | version = "1.0.98" 88 | source = "registry+https://github.com/rust-lang/crates.io-index" 89 | checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487" 90 | 91 | [[package]] 92 | name = "arrayvec" 93 | version = "0.7.6" 94 | source = "registry+https://github.com/rust-lang/crates.io-index" 95 | checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" 96 | 97 | [[package]] 98 | name = "attohttpc" 99 | version = "0.29.2" 100 | source = "registry+https://github.com/rust-lang/crates.io-index" 101 | checksum = "48404d931ab11b3a7a5267291b3b8f3590f09b86181381f8e82a7e562ed832c0" 102 | dependencies = [ 103 | "base64", 104 | "flate2", 105 | "http", 106 | "log", 107 | "rustls", 108 | "rustls-native-certs", 109 | "serde", 110 | "serde_json", 111 | "url", 112 | ] 113 | 114 | [[package]] 115 | name = "autocfg" 116 | version = "1.4.0" 117 | source = "registry+https://github.com/rust-lang/crates.io-index" 118 | checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" 119 | 120 | [[package]] 121 | name = "aws-lc-fips-sys" 122 | version = "0.13.3" 123 | source = "registry+https://github.com/rust-lang/crates.io-index" 124 | checksum = "29003a681b2b9465c1139bfb726da452a841a8b025f35953f3bce71139f10b21" 125 | dependencies = [ 126 | "bindgen", 127 | "cc", 128 | "cmake", 129 | "dunce", 130 | "fs_extra", 131 | "paste", 132 | "regex", 133 | ] 134 | 135 | [[package]] 136 | name = "aws-lc-rs" 137 | version = "1.13.0" 138 | source = "registry+https://github.com/rust-lang/crates.io-index" 139 | checksum = "19b756939cb2f8dc900aa6dcd505e6e2428e9cae7ff7b028c49e3946efa70878" 140 | dependencies = [ 141 | "aws-lc-fips-sys", 142 | "aws-lc-sys", 143 | "zeroize", 144 | ] 145 | 146 | [[package]] 147 | name = "aws-lc-sys" 148 | version = "0.28.0" 149 | source = "registry+https://github.com/rust-lang/crates.io-index" 150 | checksum = "b9f7720b74ed28ca77f90769a71fd8c637a0137f6fae4ae947e1050229cff57f" 151 | dependencies = [ 152 | "bindgen", 153 | "cc", 154 | "cmake", 155 | "dunce", 156 | "fs_extra", 157 | ] 158 | 159 | [[package]] 160 | name = "base64" 161 | version = "0.22.1" 162 | source = "registry+https://github.com/rust-lang/crates.io-index" 163 | checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" 164 | 165 | [[package]] 166 | name = "beef" 167 | version = "0.5.2" 168 | source = "registry+https://github.com/rust-lang/crates.io-index" 169 | checksum = "3a8241f3ebb85c056b509d4327ad0358fbbba6ffb340bf388f26350aeda225b1" 170 | 171 | [[package]] 172 | name = "bindgen" 173 | version = "0.69.5" 174 | source = "registry+https://github.com/rust-lang/crates.io-index" 175 | checksum = "271383c67ccabffb7381723dea0672a673f292304fcb45c01cc648c7a8d58088" 176 | dependencies = [ 177 | "bitflags 2.8.0", 178 | "cexpr", 179 | "clang-sys", 180 | "itertools", 181 | "lazy_static", 182 | "lazycell", 183 | "log", 184 | "prettyplease", 185 | "proc-macro2", 186 | "quote", 187 | "regex", 188 | "rustc-hash", 189 | "shlex", 190 | "syn", 191 | "which 4.4.2", 192 | ] 193 | 194 | [[package]] 195 | name = "bitflags" 196 | version = "1.3.2" 197 | source = "registry+https://github.com/rust-lang/crates.io-index" 198 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 199 | 200 | [[package]] 201 | name = "bitflags" 202 | version = "2.8.0" 203 | source = "registry+https://github.com/rust-lang/crates.io-index" 204 | checksum = "8f68f53c83ab957f72c32642f3868eec03eb974d1fb82e453128456482613d36" 205 | 206 | [[package]] 207 | name = "bstr" 208 | version = "1.11.3" 209 | source = "registry+https://github.com/rust-lang/crates.io-index" 210 | checksum = "531a9155a481e2ee699d4f98f43c0ca4ff8ee1bfd55c31e9e98fb29d2b176fe0" 211 | dependencies = [ 212 | "memchr", 213 | "regex-automata", 214 | "serde", 215 | ] 216 | 217 | [[package]] 218 | name = "bumpalo" 219 | version = "3.17.0" 220 | source = "registry+https://github.com/rust-lang/crates.io-index" 221 | checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf" 222 | 223 | [[package]] 224 | name = "byteorder" 225 | version = "1.5.0" 226 | source = "registry+https://github.com/rust-lang/crates.io-index" 227 | checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" 228 | 229 | [[package]] 230 | name = "bytes" 231 | version = "1.10.0" 232 | source = "registry+https://github.com/rust-lang/crates.io-index" 233 | checksum = "f61dac84819c6588b558454b194026eb1f09c293b9036ae9b159e74e73ab6cf9" 234 | 235 | [[package]] 236 | name = "cc" 237 | version = "1.2.15" 238 | source = "registry+https://github.com/rust-lang/crates.io-index" 239 | checksum = "c736e259eea577f443d5c86c304f9f4ae0295c43f3ba05c21f1d66b5f06001af" 240 | dependencies = [ 241 | "jobserver", 242 | "libc", 243 | "shlex", 244 | ] 245 | 246 | [[package]] 247 | name = "cexpr" 248 | version = "0.6.0" 249 | source = "registry+https://github.com/rust-lang/crates.io-index" 250 | checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" 251 | dependencies = [ 252 | "nom", 253 | ] 254 | 255 | [[package]] 256 | name = "cfg-if" 257 | version = "1.0.0" 258 | source = "registry+https://github.com/rust-lang/crates.io-index" 259 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 260 | 261 | [[package]] 262 | name = "cfg_aliases" 263 | version = "0.2.1" 264 | source = "registry+https://github.com/rust-lang/crates.io-index" 265 | checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" 266 | 267 | [[package]] 268 | name = "chrono" 269 | version = "0.4.40" 270 | source = "registry+https://github.com/rust-lang/crates.io-index" 271 | checksum = "1a7964611d71df112cb1730f2ee67324fcf4d0fc6606acbbe9bfe06df124637c" 272 | dependencies = [ 273 | "android-tzdata", 274 | "iana-time-zone", 275 | "js-sys", 276 | "num-traits", 277 | "wasm-bindgen", 278 | "windows-link", 279 | ] 280 | 281 | [[package]] 282 | name = "clang-sys" 283 | version = "1.8.1" 284 | source = "registry+https://github.com/rust-lang/crates.io-index" 285 | checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4" 286 | dependencies = [ 287 | "glob", 288 | "libc", 289 | "libloading", 290 | ] 291 | 292 | [[package]] 293 | name = "clap" 294 | version = "4.5.37" 295 | source = "registry+https://github.com/rust-lang/crates.io-index" 296 | checksum = "eccb054f56cbd38340b380d4a8e69ef1f02f1af43db2f0cc817a4774d80ae071" 297 | dependencies = [ 298 | "clap_builder", 299 | "clap_derive", 300 | ] 301 | 302 | [[package]] 303 | name = "clap_builder" 304 | version = "4.5.37" 305 | source = "registry+https://github.com/rust-lang/crates.io-index" 306 | checksum = "efd9466fac8543255d3b1fcad4762c5e116ffe808c8a3043d4263cd4fd4862a2" 307 | dependencies = [ 308 | "anstream", 309 | "anstyle", 310 | "clap_lex", 311 | "strsim", 312 | ] 313 | 314 | [[package]] 315 | name = "clap_derive" 316 | version = "4.5.32" 317 | source = "registry+https://github.com/rust-lang/crates.io-index" 318 | checksum = "09176aae279615badda0765c0c0b3f6ed53f4709118af73cf4655d85d1530cd7" 319 | dependencies = [ 320 | "anstyle", 321 | "heck", 322 | "proc-macro2", 323 | "pulldown-cmark", 324 | "quote", 325 | "syn", 326 | ] 327 | 328 | [[package]] 329 | name = "clap_lex" 330 | version = "0.7.4" 331 | source = "registry+https://github.com/rust-lang/crates.io-index" 332 | checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6" 333 | 334 | [[package]] 335 | name = "cmake" 336 | version = "0.1.54" 337 | source = "registry+https://github.com/rust-lang/crates.io-index" 338 | checksum = "e7caa3f9de89ddbe2c607f4101924c5abec803763ae9534e4f4d7d8f84aa81f0" 339 | dependencies = [ 340 | "cc", 341 | ] 342 | 343 | [[package]] 344 | name = "colorchoice" 345 | version = "1.0.3" 346 | source = "registry+https://github.com/rust-lang/crates.io-index" 347 | checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" 348 | 349 | [[package]] 350 | name = "core-foundation" 351 | version = "0.10.0" 352 | source = "registry+https://github.com/rust-lang/crates.io-index" 353 | checksum = "b55271e5c8c478ad3f38ad24ef34923091e0548492a266d19b3c0b4d82574c63" 354 | dependencies = [ 355 | "core-foundation-sys", 356 | "libc", 357 | ] 358 | 359 | [[package]] 360 | name = "core-foundation-sys" 361 | version = "0.8.7" 362 | source = "registry+https://github.com/rust-lang/crates.io-index" 363 | checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" 364 | 365 | [[package]] 366 | name = "crc32fast" 367 | version = "1.4.2" 368 | source = "registry+https://github.com/rust-lang/crates.io-index" 369 | checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" 370 | dependencies = [ 371 | "cfg-if", 372 | ] 373 | 374 | [[package]] 375 | name = "crossbeam" 376 | version = "0.8.4" 377 | source = "registry+https://github.com/rust-lang/crates.io-index" 378 | checksum = "1137cd7e7fc0fb5d3c5a8678be38ec56e819125d8d7907411fe24ccb943faca8" 379 | dependencies = [ 380 | "crossbeam-channel", 381 | "crossbeam-deque", 382 | "crossbeam-epoch", 383 | "crossbeam-queue", 384 | "crossbeam-utils", 385 | ] 386 | 387 | [[package]] 388 | name = "crossbeam-channel" 389 | version = "0.5.15" 390 | source = "registry+https://github.com/rust-lang/crates.io-index" 391 | checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2" 392 | dependencies = [ 393 | "crossbeam-utils", 394 | ] 395 | 396 | [[package]] 397 | name = "crossbeam-deque" 398 | version = "0.8.6" 399 | source = "registry+https://github.com/rust-lang/crates.io-index" 400 | checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" 401 | dependencies = [ 402 | "crossbeam-epoch", 403 | "crossbeam-utils", 404 | ] 405 | 406 | [[package]] 407 | name = "crossbeam-epoch" 408 | version = "0.9.18" 409 | source = "registry+https://github.com/rust-lang/crates.io-index" 410 | checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" 411 | dependencies = [ 412 | "crossbeam-utils", 413 | ] 414 | 415 | [[package]] 416 | name = "crossbeam-queue" 417 | version = "0.3.12" 418 | source = "registry+https://github.com/rust-lang/crates.io-index" 419 | checksum = "0f58bbc28f91df819d0aa2a2c00cd19754769c2fad90579b3592b1c9ba7a3115" 420 | dependencies = [ 421 | "crossbeam-utils", 422 | ] 423 | 424 | [[package]] 425 | name = "crossbeam-utils" 426 | version = "0.8.21" 427 | source = "registry+https://github.com/rust-lang/crates.io-index" 428 | checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" 429 | 430 | [[package]] 431 | name = "darling" 432 | version = "0.20.10" 433 | source = "registry+https://github.com/rust-lang/crates.io-index" 434 | checksum = "6f63b86c8a8826a49b8c21f08a2d07338eec8d900540f8630dc76284be802989" 435 | dependencies = [ 436 | "darling_core", 437 | "darling_macro", 438 | ] 439 | 440 | [[package]] 441 | name = "darling_core" 442 | version = "0.20.10" 443 | source = "registry+https://github.com/rust-lang/crates.io-index" 444 | checksum = "95133861a8032aaea082871032f5815eb9e98cef03fa916ab4500513994df9e5" 445 | dependencies = [ 446 | "fnv", 447 | "ident_case", 448 | "proc-macro2", 449 | "quote", 450 | "strsim", 451 | "syn", 452 | ] 453 | 454 | [[package]] 455 | name = "darling_macro" 456 | version = "0.20.10" 457 | source = "registry+https://github.com/rust-lang/crates.io-index" 458 | checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806" 459 | dependencies = [ 460 | "darling_core", 461 | "quote", 462 | "syn", 463 | ] 464 | 465 | [[package]] 466 | name = "defer-drop" 467 | version = "1.3.0" 468 | source = "registry+https://github.com/rust-lang/crates.io-index" 469 | checksum = "f613ec9fa66a6b28cdb1842b27f9adf24f39f9afc4dcdd9fdecee4aca7945c57" 470 | dependencies = [ 471 | "crossbeam-channel", 472 | "once_cell", 473 | ] 474 | 475 | [[package]] 476 | name = "deranged" 477 | version = "0.4.0" 478 | source = "registry+https://github.com/rust-lang/crates.io-index" 479 | checksum = "9c9e6a11ca8224451684bc0d7d5a7adbf8f2fd6887261a1cfc3c0432f9d4068e" 480 | dependencies = [ 481 | "powerfmt", 482 | ] 483 | 484 | [[package]] 485 | name = "derive_builder" 486 | version = "0.20.2" 487 | source = "registry+https://github.com/rust-lang/crates.io-index" 488 | checksum = "507dfb09ea8b7fa618fcf76e953f4f5e192547945816d5358edffe39f6f94947" 489 | dependencies = [ 490 | "derive_builder_macro", 491 | ] 492 | 493 | [[package]] 494 | name = "derive_builder_core" 495 | version = "0.20.2" 496 | source = "registry+https://github.com/rust-lang/crates.io-index" 497 | checksum = "2d5bcf7b024d6835cfb3d473887cd966994907effbe9227e8c8219824d06c4e8" 498 | dependencies = [ 499 | "darling", 500 | "proc-macro2", 501 | "quote", 502 | "syn", 503 | ] 504 | 505 | [[package]] 506 | name = "derive_builder_macro" 507 | version = "0.20.2" 508 | source = "registry+https://github.com/rust-lang/crates.io-index" 509 | checksum = "ab63b0e2bf4d5928aff72e83a7dace85d7bba5fe12dcc3c5a572d78caffd3f3c" 510 | dependencies = [ 511 | "derive_builder_core", 512 | "syn", 513 | ] 514 | 515 | [[package]] 516 | name = "dirs" 517 | version = "6.0.0" 518 | source = "registry+https://github.com/rust-lang/crates.io-index" 519 | checksum = "c3e8aa94d75141228480295a7d0e7feb620b1a5ad9f12bc40be62411e38cce4e" 520 | dependencies = [ 521 | "dirs-sys", 522 | ] 523 | 524 | [[package]] 525 | name = "dirs-next" 526 | version = "2.0.0" 527 | source = "registry+https://github.com/rust-lang/crates.io-index" 528 | checksum = "b98cf8ebf19c3d1b223e151f99a4f9f0690dca41414773390fc824184ac833e1" 529 | dependencies = [ 530 | "cfg-if", 531 | "dirs-sys-next", 532 | ] 533 | 534 | [[package]] 535 | name = "dirs-sys" 536 | version = "0.5.0" 537 | source = "registry+https://github.com/rust-lang/crates.io-index" 538 | checksum = "e01a3366d27ee9890022452ee61b2b63a67e6f13f58900b651ff5665f0bb1fab" 539 | dependencies = [ 540 | "libc", 541 | "option-ext", 542 | "redox_users 0.5.0", 543 | "windows-sys 0.59.0", 544 | ] 545 | 546 | [[package]] 547 | name = "dirs-sys-next" 548 | version = "0.1.2" 549 | source = "registry+https://github.com/rust-lang/crates.io-index" 550 | checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d" 551 | dependencies = [ 552 | "libc", 553 | "redox_users 0.4.6", 554 | "winapi", 555 | ] 556 | 557 | [[package]] 558 | name = "displaydoc" 559 | version = "0.2.5" 560 | source = "registry+https://github.com/rust-lang/crates.io-index" 561 | checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" 562 | dependencies = [ 563 | "proc-macro2", 564 | "quote", 565 | "syn", 566 | ] 567 | 568 | [[package]] 569 | name = "dunce" 570 | version = "1.0.5" 571 | source = "registry+https://github.com/rust-lang/crates.io-index" 572 | checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" 573 | 574 | [[package]] 575 | name = "either" 576 | version = "1.13.0" 577 | source = "registry+https://github.com/rust-lang/crates.io-index" 578 | checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" 579 | 580 | [[package]] 581 | name = "env_filter" 582 | version = "0.1.3" 583 | source = "registry+https://github.com/rust-lang/crates.io-index" 584 | checksum = "186e05a59d4c50738528153b83b0b0194d3a29507dfec16eccd4b342903397d0" 585 | dependencies = [ 586 | "log", 587 | "regex", 588 | ] 589 | 590 | [[package]] 591 | name = "env_home" 592 | version = "0.1.0" 593 | source = "registry+https://github.com/rust-lang/crates.io-index" 594 | checksum = "c7f84e12ccf0a7ddc17a6c41c93326024c42920d7ee630d04950e6926645c0fe" 595 | 596 | [[package]] 597 | name = "env_logger" 598 | version = "0.11.6" 599 | source = "registry+https://github.com/rust-lang/crates.io-index" 600 | checksum = "dcaee3d8e3cfc3fd92428d477bc97fc29ec8716d180c0d74c643bb26166660e0" 601 | dependencies = [ 602 | "anstream", 603 | "anstyle", 604 | "env_filter", 605 | "humantime", 606 | "log", 607 | ] 608 | 609 | [[package]] 610 | name = "equivalent" 611 | version = "1.0.2" 612 | source = "registry+https://github.com/rust-lang/crates.io-index" 613 | checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" 614 | 615 | [[package]] 616 | name = "errno" 617 | version = "0.3.10" 618 | source = "registry+https://github.com/rust-lang/crates.io-index" 619 | checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d" 620 | dependencies = [ 621 | "libc", 622 | "windows-sys 0.59.0", 623 | ] 624 | 625 | [[package]] 626 | name = "fastrand" 627 | version = "2.3.0" 628 | source = "registry+https://github.com/rust-lang/crates.io-index" 629 | checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" 630 | 631 | [[package]] 632 | name = "flate2" 633 | version = "1.0.35" 634 | source = "registry+https://github.com/rust-lang/crates.io-index" 635 | checksum = "c936bfdafb507ebbf50b8074c54fa31c5be9a1e7e5f467dd659697041407d07c" 636 | dependencies = [ 637 | "crc32fast", 638 | "miniz_oxide", 639 | ] 640 | 641 | [[package]] 642 | name = "fnv" 643 | version = "1.0.7" 644 | source = "registry+https://github.com/rust-lang/crates.io-index" 645 | checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" 646 | 647 | [[package]] 648 | name = "form_urlencoded" 649 | version = "1.2.1" 650 | source = "registry+https://github.com/rust-lang/crates.io-index" 651 | checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" 652 | dependencies = [ 653 | "percent-encoding", 654 | ] 655 | 656 | [[package]] 657 | name = "fs2" 658 | version = "0.4.3" 659 | source = "registry+https://github.com/rust-lang/crates.io-index" 660 | checksum = "9564fc758e15025b46aa6643b1b77d047d1a56a1aea6e01002ac0c7026876213" 661 | dependencies = [ 662 | "libc", 663 | "winapi", 664 | ] 665 | 666 | [[package]] 667 | name = "fs_extra" 668 | version = "1.3.0" 669 | source = "registry+https://github.com/rust-lang/crates.io-index" 670 | checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" 671 | 672 | [[package]] 673 | name = "fuzzy-matcher" 674 | version = "0.3.7" 675 | source = "registry+https://github.com/rust-lang/crates.io-index" 676 | checksum = "54614a3312934d066701a80f20f15fa3b56d67ac7722b39eea5b4c9dd1d66c94" 677 | dependencies = [ 678 | "thread_local", 679 | ] 680 | 681 | [[package]] 682 | name = "getrandom" 683 | version = "0.2.15" 684 | source = "registry+https://github.com/rust-lang/crates.io-index" 685 | checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" 686 | dependencies = [ 687 | "cfg-if", 688 | "libc", 689 | "wasi 0.11.0+wasi-snapshot-preview1", 690 | ] 691 | 692 | [[package]] 693 | name = "getrandom" 694 | version = "0.3.1" 695 | source = "registry+https://github.com/rust-lang/crates.io-index" 696 | checksum = "43a49c392881ce6d5c3b8cb70f98717b7c07aabbdff06687b9030dbfbe2725f8" 697 | dependencies = [ 698 | "cfg-if", 699 | "libc", 700 | "wasi 0.13.3+wasi-0.2.2", 701 | "windows-targets", 702 | ] 703 | 704 | [[package]] 705 | name = "glob" 706 | version = "0.3.2" 707 | source = "registry+https://github.com/rust-lang/crates.io-index" 708 | checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2" 709 | 710 | [[package]] 711 | name = "hashbrown" 712 | version = "0.15.2" 713 | source = "registry+https://github.com/rust-lang/crates.io-index" 714 | checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" 715 | 716 | [[package]] 717 | name = "heck" 718 | version = "0.5.0" 719 | source = "registry+https://github.com/rust-lang/crates.io-index" 720 | checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" 721 | 722 | [[package]] 723 | name = "home" 724 | version = "0.5.11" 725 | source = "registry+https://github.com/rust-lang/crates.io-index" 726 | checksum = "589533453244b0995c858700322199b2becb13b627df2851f64a2775d024abcf" 727 | dependencies = [ 728 | "windows-sys 0.59.0", 729 | ] 730 | 731 | [[package]] 732 | name = "http" 733 | version = "1.2.0" 734 | source = "registry+https://github.com/rust-lang/crates.io-index" 735 | checksum = "f16ca2af56261c99fba8bac40a10251ce8188205a4c448fbb745a2e4daa76fea" 736 | dependencies = [ 737 | "bytes", 738 | "fnv", 739 | "itoa", 740 | ] 741 | 742 | [[package]] 743 | name = "humantime" 744 | version = "2.1.0" 745 | source = "registry+https://github.com/rust-lang/crates.io-index" 746 | checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" 747 | 748 | [[package]] 749 | name = "iana-time-zone" 750 | version = "0.1.61" 751 | source = "registry+https://github.com/rust-lang/crates.io-index" 752 | checksum = "235e081f3925a06703c2d0117ea8b91f042756fd6e7a6e5d901e8ca1a996b220" 753 | dependencies = [ 754 | "android_system_properties", 755 | "core-foundation-sys", 756 | "iana-time-zone-haiku", 757 | "js-sys", 758 | "wasm-bindgen", 759 | "windows-core", 760 | ] 761 | 762 | [[package]] 763 | name = "iana-time-zone-haiku" 764 | version = "0.1.2" 765 | source = "registry+https://github.com/rust-lang/crates.io-index" 766 | checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" 767 | dependencies = [ 768 | "cc", 769 | ] 770 | 771 | [[package]] 772 | name = "icu_collections" 773 | version = "1.5.0" 774 | source = "registry+https://github.com/rust-lang/crates.io-index" 775 | checksum = "db2fa452206ebee18c4b5c2274dbf1de17008e874b4dc4f0aea9d01ca79e4526" 776 | dependencies = [ 777 | "displaydoc", 778 | "yoke", 779 | "zerofrom", 780 | "zerovec", 781 | ] 782 | 783 | [[package]] 784 | name = "icu_locid" 785 | version = "1.5.0" 786 | source = "registry+https://github.com/rust-lang/crates.io-index" 787 | checksum = "13acbb8371917fc971be86fc8057c41a64b521c184808a698c02acc242dbf637" 788 | dependencies = [ 789 | "displaydoc", 790 | "litemap", 791 | "tinystr", 792 | "writeable", 793 | "zerovec", 794 | ] 795 | 796 | [[package]] 797 | name = "icu_locid_transform" 798 | version = "1.5.0" 799 | source = "registry+https://github.com/rust-lang/crates.io-index" 800 | checksum = "01d11ac35de8e40fdeda00d9e1e9d92525f3f9d887cdd7aa81d727596788b54e" 801 | dependencies = [ 802 | "displaydoc", 803 | "icu_locid", 804 | "icu_locid_transform_data", 805 | "icu_provider", 806 | "tinystr", 807 | "zerovec", 808 | ] 809 | 810 | [[package]] 811 | name = "icu_locid_transform_data" 812 | version = "1.5.0" 813 | source = "registry+https://github.com/rust-lang/crates.io-index" 814 | checksum = "fdc8ff3388f852bede6b579ad4e978ab004f139284d7b28715f773507b946f6e" 815 | 816 | [[package]] 817 | name = "icu_normalizer" 818 | version = "1.5.0" 819 | source = "registry+https://github.com/rust-lang/crates.io-index" 820 | checksum = "19ce3e0da2ec68599d193c93d088142efd7f9c5d6fc9b803774855747dc6a84f" 821 | dependencies = [ 822 | "displaydoc", 823 | "icu_collections", 824 | "icu_normalizer_data", 825 | "icu_properties", 826 | "icu_provider", 827 | "smallvec", 828 | "utf16_iter", 829 | "utf8_iter", 830 | "write16", 831 | "zerovec", 832 | ] 833 | 834 | [[package]] 835 | name = "icu_normalizer_data" 836 | version = "1.5.0" 837 | source = "registry+https://github.com/rust-lang/crates.io-index" 838 | checksum = "f8cafbf7aa791e9b22bec55a167906f9e1215fd475cd22adfcf660e03e989516" 839 | 840 | [[package]] 841 | name = "icu_properties" 842 | version = "1.5.1" 843 | source = "registry+https://github.com/rust-lang/crates.io-index" 844 | checksum = "93d6020766cfc6302c15dbbc9c8778c37e62c14427cb7f6e601d849e092aeef5" 845 | dependencies = [ 846 | "displaydoc", 847 | "icu_collections", 848 | "icu_locid_transform", 849 | "icu_properties_data", 850 | "icu_provider", 851 | "tinystr", 852 | "zerovec", 853 | ] 854 | 855 | [[package]] 856 | name = "icu_properties_data" 857 | version = "1.5.0" 858 | source = "registry+https://github.com/rust-lang/crates.io-index" 859 | checksum = "67a8effbc3dd3e4ba1afa8ad918d5684b8868b3b26500753effea8d2eed19569" 860 | 861 | [[package]] 862 | name = "icu_provider" 863 | version = "1.5.0" 864 | source = "registry+https://github.com/rust-lang/crates.io-index" 865 | checksum = "6ed421c8a8ef78d3e2dbc98a973be2f3770cb42b606e3ab18d6237c4dfde68d9" 866 | dependencies = [ 867 | "displaydoc", 868 | "icu_locid", 869 | "icu_provider_macros", 870 | "stable_deref_trait", 871 | "tinystr", 872 | "writeable", 873 | "yoke", 874 | "zerofrom", 875 | "zerovec", 876 | ] 877 | 878 | [[package]] 879 | name = "icu_provider_macros" 880 | version = "1.5.0" 881 | source = "registry+https://github.com/rust-lang/crates.io-index" 882 | checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" 883 | dependencies = [ 884 | "proc-macro2", 885 | "quote", 886 | "syn", 887 | ] 888 | 889 | [[package]] 890 | name = "ident_case" 891 | version = "1.0.1" 892 | source = "registry+https://github.com/rust-lang/crates.io-index" 893 | checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" 894 | 895 | [[package]] 896 | name = "idna" 897 | version = "1.0.3" 898 | source = "registry+https://github.com/rust-lang/crates.io-index" 899 | checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e" 900 | dependencies = [ 901 | "idna_adapter", 902 | "smallvec", 903 | "utf8_iter", 904 | ] 905 | 906 | [[package]] 907 | name = "idna_adapter" 908 | version = "1.2.0" 909 | source = "registry+https://github.com/rust-lang/crates.io-index" 910 | checksum = "daca1df1c957320b2cf139ac61e7bd64fed304c5040df000a745aa1de3b4ef71" 911 | dependencies = [ 912 | "icu_normalizer", 913 | "icu_properties", 914 | ] 915 | 916 | [[package]] 917 | name = "indexmap" 918 | version = "2.9.0" 919 | source = "registry+https://github.com/rust-lang/crates.io-index" 920 | checksum = "cea70ddb795996207ad57735b50c5982d8844f38ba9ee5f1aedcfb708a2aa11e" 921 | dependencies = [ 922 | "equivalent", 923 | "hashbrown", 924 | ] 925 | 926 | [[package]] 927 | name = "is_terminal_polyfill" 928 | version = "1.70.1" 929 | source = "registry+https://github.com/rust-lang/crates.io-index" 930 | checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" 931 | 932 | [[package]] 933 | name = "itertools" 934 | version = "0.12.1" 935 | source = "registry+https://github.com/rust-lang/crates.io-index" 936 | checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" 937 | dependencies = [ 938 | "either", 939 | ] 940 | 941 | [[package]] 942 | name = "itoa" 943 | version = "1.0.14" 944 | source = "registry+https://github.com/rust-lang/crates.io-index" 945 | checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674" 946 | 947 | [[package]] 948 | name = "jobserver" 949 | version = "0.1.32" 950 | source = "registry+https://github.com/rust-lang/crates.io-index" 951 | checksum = "48d1dbcbbeb6a7fec7e059840aa538bd62aaccf972c7346c4d9d2059312853d0" 952 | dependencies = [ 953 | "libc", 954 | ] 955 | 956 | [[package]] 957 | name = "js-sys" 958 | version = "0.3.77" 959 | source = "registry+https://github.com/rust-lang/crates.io-index" 960 | checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" 961 | dependencies = [ 962 | "once_cell", 963 | "wasm-bindgen", 964 | ] 965 | 966 | [[package]] 967 | name = "kubie" 968 | version = "0.25.2" 969 | dependencies = [ 970 | "anyhow", 971 | "attohttpc", 972 | "aws-lc-rs", 973 | "cfg-if", 974 | "clap", 975 | "dirs", 976 | "fs2", 977 | "glob", 978 | "lazy_static", 979 | "libc", 980 | "serde", 981 | "serde_json", 982 | "serde_yaml", 983 | "signal-hook", 984 | "skim", 985 | "tempfile", 986 | "which 7.0.2", 987 | "wildmatch", 988 | ] 989 | 990 | [[package]] 991 | name = "lazy_static" 992 | version = "1.5.0" 993 | source = "registry+https://github.com/rust-lang/crates.io-index" 994 | checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" 995 | 996 | [[package]] 997 | name = "lazycell" 998 | version = "1.3.0" 999 | source = "registry+https://github.com/rust-lang/crates.io-index" 1000 | checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" 1001 | 1002 | [[package]] 1003 | name = "libc" 1004 | version = "0.2.172" 1005 | source = "registry+https://github.com/rust-lang/crates.io-index" 1006 | checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa" 1007 | 1008 | [[package]] 1009 | name = "libloading" 1010 | version = "0.8.6" 1011 | source = "registry+https://github.com/rust-lang/crates.io-index" 1012 | checksum = "fc2f4eb4bc735547cfed7c0a4922cbd04a4655978c09b54f1f7b228750664c34" 1013 | dependencies = [ 1014 | "cfg-if", 1015 | "windows-targets", 1016 | ] 1017 | 1018 | [[package]] 1019 | name = "libredox" 1020 | version = "0.1.3" 1021 | source = "registry+https://github.com/rust-lang/crates.io-index" 1022 | checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" 1023 | dependencies = [ 1024 | "bitflags 2.8.0", 1025 | "libc", 1026 | ] 1027 | 1028 | [[package]] 1029 | name = "linux-raw-sys" 1030 | version = "0.4.15" 1031 | source = "registry+https://github.com/rust-lang/crates.io-index" 1032 | checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" 1033 | 1034 | [[package]] 1035 | name = "linux-raw-sys" 1036 | version = "0.9.2" 1037 | source = "registry+https://github.com/rust-lang/crates.io-index" 1038 | checksum = "6db9c683daf087dc577b7506e9695b3d556a9f3849903fa28186283afd6809e9" 1039 | 1040 | [[package]] 1041 | name = "litemap" 1042 | version = "0.7.4" 1043 | source = "registry+https://github.com/rust-lang/crates.io-index" 1044 | checksum = "4ee93343901ab17bd981295f2cf0026d4ad018c7c31ba84549a4ddbb47a45104" 1045 | 1046 | [[package]] 1047 | name = "log" 1048 | version = "0.4.27" 1049 | source = "registry+https://github.com/rust-lang/crates.io-index" 1050 | checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" 1051 | 1052 | [[package]] 1053 | name = "memchr" 1054 | version = "2.7.4" 1055 | source = "registry+https://github.com/rust-lang/crates.io-index" 1056 | checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" 1057 | 1058 | [[package]] 1059 | name = "minimal-lexical" 1060 | version = "0.2.1" 1061 | source = "registry+https://github.com/rust-lang/crates.io-index" 1062 | checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" 1063 | 1064 | [[package]] 1065 | name = "miniz_oxide" 1066 | version = "0.8.5" 1067 | source = "registry+https://github.com/rust-lang/crates.io-index" 1068 | checksum = "8e3e04debbb59698c15bacbb6d93584a8c0ca9cc3213cb423d31f760d8843ce5" 1069 | dependencies = [ 1070 | "adler2", 1071 | ] 1072 | 1073 | [[package]] 1074 | name = "nix" 1075 | version = "0.24.3" 1076 | source = "registry+https://github.com/rust-lang/crates.io-index" 1077 | checksum = "fa52e972a9a719cecb6864fb88568781eb706bac2cd1d4f04a648542dbf78069" 1078 | dependencies = [ 1079 | "bitflags 1.3.2", 1080 | "cfg-if", 1081 | "libc", 1082 | ] 1083 | 1084 | [[package]] 1085 | name = "nix" 1086 | version = "0.29.0" 1087 | source = "registry+https://github.com/rust-lang/crates.io-index" 1088 | checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" 1089 | dependencies = [ 1090 | "bitflags 2.8.0", 1091 | "cfg-if", 1092 | "cfg_aliases", 1093 | "libc", 1094 | ] 1095 | 1096 | [[package]] 1097 | name = "nom" 1098 | version = "7.1.3" 1099 | source = "registry+https://github.com/rust-lang/crates.io-index" 1100 | checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" 1101 | dependencies = [ 1102 | "memchr", 1103 | "minimal-lexical", 1104 | ] 1105 | 1106 | [[package]] 1107 | name = "num-conv" 1108 | version = "0.1.0" 1109 | source = "registry+https://github.com/rust-lang/crates.io-index" 1110 | checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" 1111 | 1112 | [[package]] 1113 | name = "num-traits" 1114 | version = "0.2.19" 1115 | source = "registry+https://github.com/rust-lang/crates.io-index" 1116 | checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" 1117 | dependencies = [ 1118 | "autocfg", 1119 | ] 1120 | 1121 | [[package]] 1122 | name = "once_cell" 1123 | version = "1.20.3" 1124 | source = "registry+https://github.com/rust-lang/crates.io-index" 1125 | checksum = "945462a4b81e43c4e3ba96bd7b49d834c6f61198356aa858733bc4acf3cbe62e" 1126 | 1127 | [[package]] 1128 | name = "openssl-probe" 1129 | version = "0.1.6" 1130 | source = "registry+https://github.com/rust-lang/crates.io-index" 1131 | checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" 1132 | 1133 | [[package]] 1134 | name = "option-ext" 1135 | version = "0.2.0" 1136 | source = "registry+https://github.com/rust-lang/crates.io-index" 1137 | checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" 1138 | 1139 | [[package]] 1140 | name = "paste" 1141 | version = "1.0.15" 1142 | source = "registry+https://github.com/rust-lang/crates.io-index" 1143 | checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" 1144 | 1145 | [[package]] 1146 | name = "percent-encoding" 1147 | version = "2.3.1" 1148 | source = "registry+https://github.com/rust-lang/crates.io-index" 1149 | checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" 1150 | 1151 | [[package]] 1152 | name = "powerfmt" 1153 | version = "0.2.0" 1154 | source = "registry+https://github.com/rust-lang/crates.io-index" 1155 | checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" 1156 | 1157 | [[package]] 1158 | name = "ppv-lite86" 1159 | version = "0.2.20" 1160 | source = "registry+https://github.com/rust-lang/crates.io-index" 1161 | checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" 1162 | dependencies = [ 1163 | "zerocopy 0.7.35", 1164 | ] 1165 | 1166 | [[package]] 1167 | name = "prettyplease" 1168 | version = "0.2.29" 1169 | source = "registry+https://github.com/rust-lang/crates.io-index" 1170 | checksum = "6924ced06e1f7dfe3fa48d57b9f74f55d8915f5036121bef647ef4b204895fac" 1171 | dependencies = [ 1172 | "proc-macro2", 1173 | "syn", 1174 | ] 1175 | 1176 | [[package]] 1177 | name = "proc-macro2" 1178 | version = "1.0.93" 1179 | source = "registry+https://github.com/rust-lang/crates.io-index" 1180 | checksum = "60946a68e5f9d28b0dc1c21bb8a97ee7d018a8b322fa57838ba31cc878e22d99" 1181 | dependencies = [ 1182 | "unicode-ident", 1183 | ] 1184 | 1185 | [[package]] 1186 | name = "pulldown-cmark" 1187 | version = "0.13.0" 1188 | source = "registry+https://github.com/rust-lang/crates.io-index" 1189 | checksum = "1e8bbe1a966bd2f362681a44f6edce3c2310ac21e4d5067a6e7ec396297a6ea0" 1190 | dependencies = [ 1191 | "bitflags 2.8.0", 1192 | "memchr", 1193 | "unicase", 1194 | ] 1195 | 1196 | [[package]] 1197 | name = "quote" 1198 | version = "1.0.38" 1199 | source = "registry+https://github.com/rust-lang/crates.io-index" 1200 | checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc" 1201 | dependencies = [ 1202 | "proc-macro2", 1203 | ] 1204 | 1205 | [[package]] 1206 | name = "rand" 1207 | version = "0.9.0" 1208 | source = "registry+https://github.com/rust-lang/crates.io-index" 1209 | checksum = "3779b94aeb87e8bd4e834cee3650289ee9e0d5677f976ecdb6d219e5f4f6cd94" 1210 | dependencies = [ 1211 | "rand_chacha", 1212 | "rand_core", 1213 | "zerocopy 0.8.23", 1214 | ] 1215 | 1216 | [[package]] 1217 | name = "rand_chacha" 1218 | version = "0.9.0" 1219 | source = "registry+https://github.com/rust-lang/crates.io-index" 1220 | checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" 1221 | dependencies = [ 1222 | "ppv-lite86", 1223 | "rand_core", 1224 | ] 1225 | 1226 | [[package]] 1227 | name = "rand_core" 1228 | version = "0.9.3" 1229 | source = "registry+https://github.com/rust-lang/crates.io-index" 1230 | checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" 1231 | dependencies = [ 1232 | "getrandom 0.3.1", 1233 | ] 1234 | 1235 | [[package]] 1236 | name = "rayon" 1237 | version = "1.10.0" 1238 | source = "registry+https://github.com/rust-lang/crates.io-index" 1239 | checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa" 1240 | dependencies = [ 1241 | "either", 1242 | "rayon-core", 1243 | ] 1244 | 1245 | [[package]] 1246 | name = "rayon-core" 1247 | version = "1.12.1" 1248 | source = "registry+https://github.com/rust-lang/crates.io-index" 1249 | checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2" 1250 | dependencies = [ 1251 | "crossbeam-deque", 1252 | "crossbeam-utils", 1253 | ] 1254 | 1255 | [[package]] 1256 | name = "redox_users" 1257 | version = "0.4.6" 1258 | source = "registry+https://github.com/rust-lang/crates.io-index" 1259 | checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43" 1260 | dependencies = [ 1261 | "getrandom 0.2.15", 1262 | "libredox", 1263 | "thiserror 1.0.69", 1264 | ] 1265 | 1266 | [[package]] 1267 | name = "redox_users" 1268 | version = "0.5.0" 1269 | source = "registry+https://github.com/rust-lang/crates.io-index" 1270 | checksum = "dd6f9d3d47bdd2ad6945c5015a226ec6155d0bcdfd8f7cd29f86b71f8de99d2b" 1271 | dependencies = [ 1272 | "getrandom 0.2.15", 1273 | "libredox", 1274 | "thiserror 2.0.11", 1275 | ] 1276 | 1277 | [[package]] 1278 | name = "regex" 1279 | version = "1.11.1" 1280 | source = "registry+https://github.com/rust-lang/crates.io-index" 1281 | checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" 1282 | dependencies = [ 1283 | "aho-corasick", 1284 | "memchr", 1285 | "regex-automata", 1286 | "regex-syntax", 1287 | ] 1288 | 1289 | [[package]] 1290 | name = "regex-automata" 1291 | version = "0.4.9" 1292 | source = "registry+https://github.com/rust-lang/crates.io-index" 1293 | checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" 1294 | dependencies = [ 1295 | "aho-corasick", 1296 | "memchr", 1297 | "regex-syntax", 1298 | ] 1299 | 1300 | [[package]] 1301 | name = "regex-syntax" 1302 | version = "0.8.5" 1303 | source = "registry+https://github.com/rust-lang/crates.io-index" 1304 | checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" 1305 | 1306 | [[package]] 1307 | name = "ring" 1308 | version = "0.17.14" 1309 | source = "registry+https://github.com/rust-lang/crates.io-index" 1310 | checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" 1311 | dependencies = [ 1312 | "cc", 1313 | "cfg-if", 1314 | "getrandom 0.2.15", 1315 | "libc", 1316 | "untrusted", 1317 | "windows-sys 0.52.0", 1318 | ] 1319 | 1320 | [[package]] 1321 | name = "rustc-hash" 1322 | version = "1.1.0" 1323 | source = "registry+https://github.com/rust-lang/crates.io-index" 1324 | checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" 1325 | 1326 | [[package]] 1327 | name = "rustix" 1328 | version = "0.38.44" 1329 | source = "registry+https://github.com/rust-lang/crates.io-index" 1330 | checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" 1331 | dependencies = [ 1332 | "bitflags 2.8.0", 1333 | "errno", 1334 | "libc", 1335 | "linux-raw-sys 0.4.15", 1336 | "windows-sys 0.59.0", 1337 | ] 1338 | 1339 | [[package]] 1340 | name = "rustix" 1341 | version = "1.0.2" 1342 | source = "registry+https://github.com/rust-lang/crates.io-index" 1343 | checksum = "f7178faa4b75a30e269c71e61c353ce2748cf3d76f0c44c393f4e60abf49b825" 1344 | dependencies = [ 1345 | "bitflags 2.8.0", 1346 | "errno", 1347 | "libc", 1348 | "linux-raw-sys 0.9.2", 1349 | "windows-sys 0.59.0", 1350 | ] 1351 | 1352 | [[package]] 1353 | name = "rustls" 1354 | version = "0.23.23" 1355 | source = "registry+https://github.com/rust-lang/crates.io-index" 1356 | checksum = "47796c98c480fce5406ef69d1c76378375492c3b0a0de587be0c1d9feb12f395" 1357 | dependencies = [ 1358 | "aws-lc-rs", 1359 | "log", 1360 | "once_cell", 1361 | "rustls-pki-types", 1362 | "rustls-webpki", 1363 | "subtle", 1364 | "zeroize", 1365 | ] 1366 | 1367 | [[package]] 1368 | name = "rustls-native-certs" 1369 | version = "0.8.1" 1370 | source = "registry+https://github.com/rust-lang/crates.io-index" 1371 | checksum = "7fcff2dd52b58a8d98a70243663a0d234c4e2b79235637849d15913394a247d3" 1372 | dependencies = [ 1373 | "openssl-probe", 1374 | "rustls-pki-types", 1375 | "schannel", 1376 | "security-framework", 1377 | ] 1378 | 1379 | [[package]] 1380 | name = "rustls-pki-types" 1381 | version = "1.11.0" 1382 | source = "registry+https://github.com/rust-lang/crates.io-index" 1383 | checksum = "917ce264624a4b4db1c364dcc35bfca9ded014d0a958cd47ad3e960e988ea51c" 1384 | 1385 | [[package]] 1386 | name = "rustls-webpki" 1387 | version = "0.102.8" 1388 | source = "registry+https://github.com/rust-lang/crates.io-index" 1389 | checksum = "64ca1bc8749bd4cf37b5ce386cc146580777b4e8572c7b97baf22c83f444bee9" 1390 | dependencies = [ 1391 | "aws-lc-rs", 1392 | "ring", 1393 | "rustls-pki-types", 1394 | "untrusted", 1395 | ] 1396 | 1397 | [[package]] 1398 | name = "rustversion" 1399 | version = "1.0.19" 1400 | source = "registry+https://github.com/rust-lang/crates.io-index" 1401 | checksum = "f7c45b9784283f1b2e7fb61b42047c2fd678ef0960d4f6f1eba131594cc369d4" 1402 | 1403 | [[package]] 1404 | name = "ryu" 1405 | version = "1.0.19" 1406 | source = "registry+https://github.com/rust-lang/crates.io-index" 1407 | checksum = "6ea1a2d0a644769cc99faa24c3ad26b379b786fe7c36fd3c546254801650e6dd" 1408 | 1409 | [[package]] 1410 | name = "schannel" 1411 | version = "0.1.27" 1412 | source = "registry+https://github.com/rust-lang/crates.io-index" 1413 | checksum = "1f29ebaa345f945cec9fbbc532eb307f0fdad8161f281b6369539c8d84876b3d" 1414 | dependencies = [ 1415 | "windows-sys 0.59.0", 1416 | ] 1417 | 1418 | [[package]] 1419 | name = "security-framework" 1420 | version = "3.2.0" 1421 | source = "registry+https://github.com/rust-lang/crates.io-index" 1422 | checksum = "271720403f46ca04f7ba6f55d438f8bd878d6b8ca0a1046e8228c4145bcbb316" 1423 | dependencies = [ 1424 | "bitflags 2.8.0", 1425 | "core-foundation", 1426 | "core-foundation-sys", 1427 | "libc", 1428 | "security-framework-sys", 1429 | ] 1430 | 1431 | [[package]] 1432 | name = "security-framework-sys" 1433 | version = "2.14.0" 1434 | source = "registry+https://github.com/rust-lang/crates.io-index" 1435 | checksum = "49db231d56a190491cb4aeda9527f1ad45345af50b0851622a7adb8c03b01c32" 1436 | dependencies = [ 1437 | "core-foundation-sys", 1438 | "libc", 1439 | ] 1440 | 1441 | [[package]] 1442 | name = "serde" 1443 | version = "1.0.219" 1444 | source = "registry+https://github.com/rust-lang/crates.io-index" 1445 | checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" 1446 | dependencies = [ 1447 | "serde_derive", 1448 | ] 1449 | 1450 | [[package]] 1451 | name = "serde_derive" 1452 | version = "1.0.219" 1453 | source = "registry+https://github.com/rust-lang/crates.io-index" 1454 | checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" 1455 | dependencies = [ 1456 | "proc-macro2", 1457 | "quote", 1458 | "syn", 1459 | ] 1460 | 1461 | [[package]] 1462 | name = "serde_json" 1463 | version = "1.0.140" 1464 | source = "registry+https://github.com/rust-lang/crates.io-index" 1465 | checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373" 1466 | dependencies = [ 1467 | "itoa", 1468 | "memchr", 1469 | "ryu", 1470 | "serde", 1471 | ] 1472 | 1473 | [[package]] 1474 | name = "serde_yaml" 1475 | version = "0.9.34+deprecated" 1476 | source = "registry+https://github.com/rust-lang/crates.io-index" 1477 | checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47" 1478 | dependencies = [ 1479 | "indexmap", 1480 | "itoa", 1481 | "ryu", 1482 | "serde", 1483 | "unsafe-libyaml", 1484 | ] 1485 | 1486 | [[package]] 1487 | name = "shell-quote" 1488 | version = "0.7.2" 1489 | source = "registry+https://github.com/rust-lang/crates.io-index" 1490 | checksum = "fb502615975ae2365825521fa1529ca7648fd03ce0b0746604e0683856ecd7e4" 1491 | dependencies = [ 1492 | "bstr", 1493 | ] 1494 | 1495 | [[package]] 1496 | name = "shlex" 1497 | version = "1.3.0" 1498 | source = "registry+https://github.com/rust-lang/crates.io-index" 1499 | checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" 1500 | 1501 | [[package]] 1502 | name = "signal-hook" 1503 | version = "0.3.17" 1504 | source = "registry+https://github.com/rust-lang/crates.io-index" 1505 | checksum = "8621587d4798caf8eb44879d42e56b9a93ea5dcd315a6487c357130095b62801" 1506 | dependencies = [ 1507 | "libc", 1508 | "signal-hook-registry", 1509 | ] 1510 | 1511 | [[package]] 1512 | name = "signal-hook-registry" 1513 | version = "1.4.2" 1514 | source = "registry+https://github.com/rust-lang/crates.io-index" 1515 | checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" 1516 | dependencies = [ 1517 | "libc", 1518 | ] 1519 | 1520 | [[package]] 1521 | name = "skim" 1522 | version = "0.16.2" 1523 | source = "registry+https://github.com/rust-lang/crates.io-index" 1524 | checksum = "8e29ac781a242d0cc04f1bbf5cc3522ef2205b778fdc8c2a6f293eef34467968" 1525 | dependencies = [ 1526 | "beef", 1527 | "bitflags 1.3.2", 1528 | "chrono", 1529 | "clap", 1530 | "crossbeam", 1531 | "defer-drop", 1532 | "derive_builder", 1533 | "env_logger", 1534 | "fuzzy-matcher", 1535 | "indexmap", 1536 | "log", 1537 | "nix 0.29.0", 1538 | "rand", 1539 | "rayon", 1540 | "regex", 1541 | "shell-quote", 1542 | "shlex", 1543 | "time", 1544 | "timer", 1545 | "tuikit", 1546 | "unicode-width 0.2.0", 1547 | "vte", 1548 | "which 7.0.2", 1549 | ] 1550 | 1551 | [[package]] 1552 | name = "smallvec" 1553 | version = "1.14.0" 1554 | source = "registry+https://github.com/rust-lang/crates.io-index" 1555 | checksum = "7fcf8323ef1faaee30a44a340193b1ac6814fd9b7b4e88e9d4519a3e4abe1cfd" 1556 | 1557 | [[package]] 1558 | name = "stable_deref_trait" 1559 | version = "1.2.0" 1560 | source = "registry+https://github.com/rust-lang/crates.io-index" 1561 | checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" 1562 | 1563 | [[package]] 1564 | name = "strsim" 1565 | version = "0.11.1" 1566 | source = "registry+https://github.com/rust-lang/crates.io-index" 1567 | checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" 1568 | 1569 | [[package]] 1570 | name = "subtle" 1571 | version = "2.6.1" 1572 | source = "registry+https://github.com/rust-lang/crates.io-index" 1573 | checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" 1574 | 1575 | [[package]] 1576 | name = "syn" 1577 | version = "2.0.98" 1578 | source = "registry+https://github.com/rust-lang/crates.io-index" 1579 | checksum = "36147f1a48ae0ec2b5b3bc5b537d267457555a10dc06f3dbc8cb11ba3006d3b1" 1580 | dependencies = [ 1581 | "proc-macro2", 1582 | "quote", 1583 | "unicode-ident", 1584 | ] 1585 | 1586 | [[package]] 1587 | name = "synstructure" 1588 | version = "0.13.1" 1589 | source = "registry+https://github.com/rust-lang/crates.io-index" 1590 | checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" 1591 | dependencies = [ 1592 | "proc-macro2", 1593 | "quote", 1594 | "syn", 1595 | ] 1596 | 1597 | [[package]] 1598 | name = "tempfile" 1599 | version = "3.19.1" 1600 | source = "registry+https://github.com/rust-lang/crates.io-index" 1601 | checksum = "7437ac7763b9b123ccf33c338a5cc1bac6f69b45a136c19bdd8a65e3916435bf" 1602 | dependencies = [ 1603 | "fastrand", 1604 | "getrandom 0.3.1", 1605 | "once_cell", 1606 | "rustix 1.0.2", 1607 | "windows-sys 0.59.0", 1608 | ] 1609 | 1610 | [[package]] 1611 | name = "term" 1612 | version = "0.7.0" 1613 | source = "registry+https://github.com/rust-lang/crates.io-index" 1614 | checksum = "c59df8ac95d96ff9bede18eb7300b0fda5e5d8d90960e76f8e14ae765eedbf1f" 1615 | dependencies = [ 1616 | "dirs-next", 1617 | "rustversion", 1618 | "winapi", 1619 | ] 1620 | 1621 | [[package]] 1622 | name = "thiserror" 1623 | version = "1.0.69" 1624 | source = "registry+https://github.com/rust-lang/crates.io-index" 1625 | checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" 1626 | dependencies = [ 1627 | "thiserror-impl 1.0.69", 1628 | ] 1629 | 1630 | [[package]] 1631 | name = "thiserror" 1632 | version = "2.0.11" 1633 | source = "registry+https://github.com/rust-lang/crates.io-index" 1634 | checksum = "d452f284b73e6d76dd36758a0c8684b1d5be31f92b89d07fd5822175732206fc" 1635 | dependencies = [ 1636 | "thiserror-impl 2.0.11", 1637 | ] 1638 | 1639 | [[package]] 1640 | name = "thiserror-impl" 1641 | version = "1.0.69" 1642 | source = "registry+https://github.com/rust-lang/crates.io-index" 1643 | checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" 1644 | dependencies = [ 1645 | "proc-macro2", 1646 | "quote", 1647 | "syn", 1648 | ] 1649 | 1650 | [[package]] 1651 | name = "thiserror-impl" 1652 | version = "2.0.11" 1653 | source = "registry+https://github.com/rust-lang/crates.io-index" 1654 | checksum = "26afc1baea8a989337eeb52b6e72a039780ce45c3edfcc9c5b9d112feeb173c2" 1655 | dependencies = [ 1656 | "proc-macro2", 1657 | "quote", 1658 | "syn", 1659 | ] 1660 | 1661 | [[package]] 1662 | name = "thread_local" 1663 | version = "1.1.8" 1664 | source = "registry+https://github.com/rust-lang/crates.io-index" 1665 | checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c" 1666 | dependencies = [ 1667 | "cfg-if", 1668 | "once_cell", 1669 | ] 1670 | 1671 | [[package]] 1672 | name = "time" 1673 | version = "0.3.41" 1674 | source = "registry+https://github.com/rust-lang/crates.io-index" 1675 | checksum = "8a7619e19bc266e0f9c5e6686659d394bc57973859340060a69221e57dbc0c40" 1676 | dependencies = [ 1677 | "deranged", 1678 | "num-conv", 1679 | "powerfmt", 1680 | "serde", 1681 | "time-core", 1682 | ] 1683 | 1684 | [[package]] 1685 | name = "time-core" 1686 | version = "0.1.4" 1687 | source = "registry+https://github.com/rust-lang/crates.io-index" 1688 | checksum = "c9e9a38711f559d9e3ce1cdb06dd7c5b8ea546bc90052da6d06bb76da74bb07c" 1689 | 1690 | [[package]] 1691 | name = "timer" 1692 | version = "0.2.0" 1693 | source = "registry+https://github.com/rust-lang/crates.io-index" 1694 | checksum = "31d42176308937165701f50638db1c31586f183f1aab416268216577aec7306b" 1695 | dependencies = [ 1696 | "chrono", 1697 | ] 1698 | 1699 | [[package]] 1700 | name = "tinystr" 1701 | version = "0.7.6" 1702 | source = "registry+https://github.com/rust-lang/crates.io-index" 1703 | checksum = "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f" 1704 | dependencies = [ 1705 | "displaydoc", 1706 | "zerovec", 1707 | ] 1708 | 1709 | [[package]] 1710 | name = "tuikit" 1711 | version = "0.5.0" 1712 | source = "registry+https://github.com/rust-lang/crates.io-index" 1713 | checksum = "5e19c6ab038babee3d50c8c12ff8b910bdb2196f62278776422f50390d8e53d8" 1714 | dependencies = [ 1715 | "bitflags 1.3.2", 1716 | "lazy_static", 1717 | "log", 1718 | "nix 0.24.3", 1719 | "term", 1720 | "unicode-width 0.1.14", 1721 | ] 1722 | 1723 | [[package]] 1724 | name = "unicase" 1725 | version = "2.8.1" 1726 | source = "registry+https://github.com/rust-lang/crates.io-index" 1727 | checksum = "75b844d17643ee918803943289730bec8aac480150456169e647ed0b576ba539" 1728 | 1729 | [[package]] 1730 | name = "unicode-ident" 1731 | version = "1.0.17" 1732 | source = "registry+https://github.com/rust-lang/crates.io-index" 1733 | checksum = "00e2473a93778eb0bad35909dff6a10d28e63f792f16ed15e404fca9d5eeedbe" 1734 | 1735 | [[package]] 1736 | name = "unicode-width" 1737 | version = "0.1.14" 1738 | source = "registry+https://github.com/rust-lang/crates.io-index" 1739 | checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" 1740 | 1741 | [[package]] 1742 | name = "unicode-width" 1743 | version = "0.2.0" 1744 | source = "registry+https://github.com/rust-lang/crates.io-index" 1745 | checksum = "1fc81956842c57dac11422a97c3b8195a1ff727f06e85c84ed2e8aa277c9a0fd" 1746 | 1747 | [[package]] 1748 | name = "unsafe-libyaml" 1749 | version = "0.2.11" 1750 | source = "registry+https://github.com/rust-lang/crates.io-index" 1751 | checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861" 1752 | 1753 | [[package]] 1754 | name = "untrusted" 1755 | version = "0.9.0" 1756 | source = "registry+https://github.com/rust-lang/crates.io-index" 1757 | checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" 1758 | 1759 | [[package]] 1760 | name = "url" 1761 | version = "2.5.4" 1762 | source = "registry+https://github.com/rust-lang/crates.io-index" 1763 | checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60" 1764 | dependencies = [ 1765 | "form_urlencoded", 1766 | "idna", 1767 | "percent-encoding", 1768 | ] 1769 | 1770 | [[package]] 1771 | name = "utf16_iter" 1772 | version = "1.0.5" 1773 | source = "registry+https://github.com/rust-lang/crates.io-index" 1774 | checksum = "c8232dd3cdaed5356e0f716d285e4b40b932ac434100fe9b7e0e8e935b9e6246" 1775 | 1776 | [[package]] 1777 | name = "utf8_iter" 1778 | version = "1.0.4" 1779 | source = "registry+https://github.com/rust-lang/crates.io-index" 1780 | checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" 1781 | 1782 | [[package]] 1783 | name = "utf8parse" 1784 | version = "0.2.2" 1785 | source = "registry+https://github.com/rust-lang/crates.io-index" 1786 | checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" 1787 | 1788 | [[package]] 1789 | name = "vte" 1790 | version = "0.15.0" 1791 | source = "registry+https://github.com/rust-lang/crates.io-index" 1792 | checksum = "a5924018406ce0063cd67f8e008104968b74b563ee1b85dde3ed1f7cb87d3dbd" 1793 | dependencies = [ 1794 | "arrayvec", 1795 | "memchr", 1796 | ] 1797 | 1798 | [[package]] 1799 | name = "wasi" 1800 | version = "0.11.0+wasi-snapshot-preview1" 1801 | source = "registry+https://github.com/rust-lang/crates.io-index" 1802 | checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" 1803 | 1804 | [[package]] 1805 | name = "wasi" 1806 | version = "0.13.3+wasi-0.2.2" 1807 | source = "registry+https://github.com/rust-lang/crates.io-index" 1808 | checksum = "26816d2e1a4a36a2940b96c5296ce403917633dff8f3440e9b236ed6f6bacad2" 1809 | dependencies = [ 1810 | "wit-bindgen-rt", 1811 | ] 1812 | 1813 | [[package]] 1814 | name = "wasm-bindgen" 1815 | version = "0.2.100" 1816 | source = "registry+https://github.com/rust-lang/crates.io-index" 1817 | checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" 1818 | dependencies = [ 1819 | "cfg-if", 1820 | "once_cell", 1821 | "rustversion", 1822 | "wasm-bindgen-macro", 1823 | ] 1824 | 1825 | [[package]] 1826 | name = "wasm-bindgen-backend" 1827 | version = "0.2.100" 1828 | source = "registry+https://github.com/rust-lang/crates.io-index" 1829 | checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" 1830 | dependencies = [ 1831 | "bumpalo", 1832 | "log", 1833 | "proc-macro2", 1834 | "quote", 1835 | "syn", 1836 | "wasm-bindgen-shared", 1837 | ] 1838 | 1839 | [[package]] 1840 | name = "wasm-bindgen-macro" 1841 | version = "0.2.100" 1842 | source = "registry+https://github.com/rust-lang/crates.io-index" 1843 | checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" 1844 | dependencies = [ 1845 | "quote", 1846 | "wasm-bindgen-macro-support", 1847 | ] 1848 | 1849 | [[package]] 1850 | name = "wasm-bindgen-macro-support" 1851 | version = "0.2.100" 1852 | source = "registry+https://github.com/rust-lang/crates.io-index" 1853 | checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" 1854 | dependencies = [ 1855 | "proc-macro2", 1856 | "quote", 1857 | "syn", 1858 | "wasm-bindgen-backend", 1859 | "wasm-bindgen-shared", 1860 | ] 1861 | 1862 | [[package]] 1863 | name = "wasm-bindgen-shared" 1864 | version = "0.2.100" 1865 | source = "registry+https://github.com/rust-lang/crates.io-index" 1866 | checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" 1867 | dependencies = [ 1868 | "unicode-ident", 1869 | ] 1870 | 1871 | [[package]] 1872 | name = "which" 1873 | version = "4.4.2" 1874 | source = "registry+https://github.com/rust-lang/crates.io-index" 1875 | checksum = "87ba24419a2078cd2b0f2ede2691b6c66d8e47836da3b6db8265ebad47afbfc7" 1876 | dependencies = [ 1877 | "either", 1878 | "home", 1879 | "once_cell", 1880 | "rustix 0.38.44", 1881 | ] 1882 | 1883 | [[package]] 1884 | name = "which" 1885 | version = "7.0.2" 1886 | source = "registry+https://github.com/rust-lang/crates.io-index" 1887 | checksum = "2774c861e1f072b3aadc02f8ba886c26ad6321567ecc294c935434cad06f1283" 1888 | dependencies = [ 1889 | "either", 1890 | "env_home", 1891 | "rustix 0.38.44", 1892 | "winsafe", 1893 | ] 1894 | 1895 | [[package]] 1896 | name = "wildmatch" 1897 | version = "2.4.0" 1898 | source = "registry+https://github.com/rust-lang/crates.io-index" 1899 | checksum = "68ce1ab1f8c62655ebe1350f589c61e505cf94d385bc6a12899442d9081e71fd" 1900 | 1901 | [[package]] 1902 | name = "winapi" 1903 | version = "0.3.9" 1904 | source = "registry+https://github.com/rust-lang/crates.io-index" 1905 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 1906 | dependencies = [ 1907 | "winapi-i686-pc-windows-gnu", 1908 | "winapi-x86_64-pc-windows-gnu", 1909 | ] 1910 | 1911 | [[package]] 1912 | name = "winapi-i686-pc-windows-gnu" 1913 | version = "0.4.0" 1914 | source = "registry+https://github.com/rust-lang/crates.io-index" 1915 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 1916 | 1917 | [[package]] 1918 | name = "winapi-x86_64-pc-windows-gnu" 1919 | version = "0.4.0" 1920 | source = "registry+https://github.com/rust-lang/crates.io-index" 1921 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 1922 | 1923 | [[package]] 1924 | name = "windows-core" 1925 | version = "0.52.0" 1926 | source = "registry+https://github.com/rust-lang/crates.io-index" 1927 | checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" 1928 | dependencies = [ 1929 | "windows-targets", 1930 | ] 1931 | 1932 | [[package]] 1933 | name = "windows-link" 1934 | version = "0.1.0" 1935 | source = "registry+https://github.com/rust-lang/crates.io-index" 1936 | checksum = "6dccfd733ce2b1753b03b6d3c65edf020262ea35e20ccdf3e288043e6dd620e3" 1937 | 1938 | [[package]] 1939 | name = "windows-sys" 1940 | version = "0.52.0" 1941 | source = "registry+https://github.com/rust-lang/crates.io-index" 1942 | checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" 1943 | dependencies = [ 1944 | "windows-targets", 1945 | ] 1946 | 1947 | [[package]] 1948 | name = "windows-sys" 1949 | version = "0.59.0" 1950 | source = "registry+https://github.com/rust-lang/crates.io-index" 1951 | checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" 1952 | dependencies = [ 1953 | "windows-targets", 1954 | ] 1955 | 1956 | [[package]] 1957 | name = "windows-targets" 1958 | version = "0.52.6" 1959 | source = "registry+https://github.com/rust-lang/crates.io-index" 1960 | checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" 1961 | dependencies = [ 1962 | "windows_aarch64_gnullvm", 1963 | "windows_aarch64_msvc", 1964 | "windows_i686_gnu", 1965 | "windows_i686_gnullvm", 1966 | "windows_i686_msvc", 1967 | "windows_x86_64_gnu", 1968 | "windows_x86_64_gnullvm", 1969 | "windows_x86_64_msvc", 1970 | ] 1971 | 1972 | [[package]] 1973 | name = "windows_aarch64_gnullvm" 1974 | version = "0.52.6" 1975 | source = "registry+https://github.com/rust-lang/crates.io-index" 1976 | checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" 1977 | 1978 | [[package]] 1979 | name = "windows_aarch64_msvc" 1980 | version = "0.52.6" 1981 | source = "registry+https://github.com/rust-lang/crates.io-index" 1982 | checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" 1983 | 1984 | [[package]] 1985 | name = "windows_i686_gnu" 1986 | version = "0.52.6" 1987 | source = "registry+https://github.com/rust-lang/crates.io-index" 1988 | checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" 1989 | 1990 | [[package]] 1991 | name = "windows_i686_gnullvm" 1992 | version = "0.52.6" 1993 | source = "registry+https://github.com/rust-lang/crates.io-index" 1994 | checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" 1995 | 1996 | [[package]] 1997 | name = "windows_i686_msvc" 1998 | version = "0.52.6" 1999 | source = "registry+https://github.com/rust-lang/crates.io-index" 2000 | checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" 2001 | 2002 | [[package]] 2003 | name = "windows_x86_64_gnu" 2004 | version = "0.52.6" 2005 | source = "registry+https://github.com/rust-lang/crates.io-index" 2006 | checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" 2007 | 2008 | [[package]] 2009 | name = "windows_x86_64_gnullvm" 2010 | version = "0.52.6" 2011 | source = "registry+https://github.com/rust-lang/crates.io-index" 2012 | checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" 2013 | 2014 | [[package]] 2015 | name = "windows_x86_64_msvc" 2016 | version = "0.52.6" 2017 | source = "registry+https://github.com/rust-lang/crates.io-index" 2018 | checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" 2019 | 2020 | [[package]] 2021 | name = "winsafe" 2022 | version = "0.0.19" 2023 | source = "registry+https://github.com/rust-lang/crates.io-index" 2024 | checksum = "d135d17ab770252ad95e9a872d365cf3090e3be864a34ab46f48555993efc904" 2025 | 2026 | [[package]] 2027 | name = "wit-bindgen-rt" 2028 | version = "0.33.0" 2029 | source = "registry+https://github.com/rust-lang/crates.io-index" 2030 | checksum = "3268f3d866458b787f390cf61f4bbb563b922d091359f9608842999eaee3943c" 2031 | dependencies = [ 2032 | "bitflags 2.8.0", 2033 | ] 2034 | 2035 | [[package]] 2036 | name = "write16" 2037 | version = "1.0.0" 2038 | source = "registry+https://github.com/rust-lang/crates.io-index" 2039 | checksum = "d1890f4022759daae28ed4fe62859b1236caebfc61ede2f63ed4e695f3f6d936" 2040 | 2041 | [[package]] 2042 | name = "writeable" 2043 | version = "0.5.5" 2044 | source = "registry+https://github.com/rust-lang/crates.io-index" 2045 | checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" 2046 | 2047 | [[package]] 2048 | name = "yoke" 2049 | version = "0.7.5" 2050 | source = "registry+https://github.com/rust-lang/crates.io-index" 2051 | checksum = "120e6aef9aa629e3d4f52dc8cc43a015c7724194c97dfaf45180d2daf2b77f40" 2052 | dependencies = [ 2053 | "serde", 2054 | "stable_deref_trait", 2055 | "yoke-derive", 2056 | "zerofrom", 2057 | ] 2058 | 2059 | [[package]] 2060 | name = "yoke-derive" 2061 | version = "0.7.5" 2062 | source = "registry+https://github.com/rust-lang/crates.io-index" 2063 | checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154" 2064 | dependencies = [ 2065 | "proc-macro2", 2066 | "quote", 2067 | "syn", 2068 | "synstructure", 2069 | ] 2070 | 2071 | [[package]] 2072 | name = "zerocopy" 2073 | version = "0.7.35" 2074 | source = "registry+https://github.com/rust-lang/crates.io-index" 2075 | checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" 2076 | dependencies = [ 2077 | "byteorder", 2078 | "zerocopy-derive 0.7.35", 2079 | ] 2080 | 2081 | [[package]] 2082 | name = "zerocopy" 2083 | version = "0.8.23" 2084 | source = "registry+https://github.com/rust-lang/crates.io-index" 2085 | checksum = "fd97444d05a4328b90e75e503a34bad781f14e28a823ad3557f0750df1ebcbc6" 2086 | dependencies = [ 2087 | "zerocopy-derive 0.8.23", 2088 | ] 2089 | 2090 | [[package]] 2091 | name = "zerocopy-derive" 2092 | version = "0.7.35" 2093 | source = "registry+https://github.com/rust-lang/crates.io-index" 2094 | checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" 2095 | dependencies = [ 2096 | "proc-macro2", 2097 | "quote", 2098 | "syn", 2099 | ] 2100 | 2101 | [[package]] 2102 | name = "zerocopy-derive" 2103 | version = "0.8.23" 2104 | source = "registry+https://github.com/rust-lang/crates.io-index" 2105 | checksum = "6352c01d0edd5db859a63e2605f4ea3183ddbd15e2c4a9e7d32184df75e4f154" 2106 | dependencies = [ 2107 | "proc-macro2", 2108 | "quote", 2109 | "syn", 2110 | ] 2111 | 2112 | [[package]] 2113 | name = "zerofrom" 2114 | version = "0.1.5" 2115 | source = "registry+https://github.com/rust-lang/crates.io-index" 2116 | checksum = "cff3ee08c995dee1859d998dea82f7374f2826091dd9cd47def953cae446cd2e" 2117 | dependencies = [ 2118 | "zerofrom-derive", 2119 | ] 2120 | 2121 | [[package]] 2122 | name = "zerofrom-derive" 2123 | version = "0.1.5" 2124 | source = "registry+https://github.com/rust-lang/crates.io-index" 2125 | checksum = "595eed982f7d355beb85837f651fa22e90b3c044842dc7f2c2842c086f295808" 2126 | dependencies = [ 2127 | "proc-macro2", 2128 | "quote", 2129 | "syn", 2130 | "synstructure", 2131 | ] 2132 | 2133 | [[package]] 2134 | name = "zeroize" 2135 | version = "1.8.1" 2136 | source = "registry+https://github.com/rust-lang/crates.io-index" 2137 | checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" 2138 | 2139 | [[package]] 2140 | name = "zerovec" 2141 | version = "0.10.4" 2142 | source = "registry+https://github.com/rust-lang/crates.io-index" 2143 | checksum = "aa2b893d79df23bfb12d5461018d408ea19dfafe76c2c7ef6d4eba614f8ff079" 2144 | dependencies = [ 2145 | "yoke", 2146 | "zerofrom", 2147 | "zerovec-derive", 2148 | ] 2149 | 2150 | [[package]] 2151 | name = "zerovec-derive" 2152 | version = "0.10.3" 2153 | source = "registry+https://github.com/rust-lang/crates.io-index" 2154 | checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" 2155 | dependencies = [ 2156 | "proc-macro2", 2157 | "quote", 2158 | "syn", 2159 | ] 2160 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors = ["Simon Bernier St-Pierre "] 3 | categories = ["command-line-utilities", "development-tools"] 4 | description = "A more powerful alternative to kubectx and kubens." 5 | documentation = "https://github.com/sbstp/kubie/blob/master/README.md" 6 | edition = "2021" 7 | exclude = ["releases/**/*"] 8 | homepage = "https://github.com/sbstp/kubie" 9 | keywords = ["kubernetes", "kubectx", "kubens"] 10 | license = "Zlib" 11 | name = "kubie" 12 | readme = "README.md" 13 | repository = "https://github.com/sbstp/kubie" 14 | version = "0.25.2" 15 | 16 | [dependencies] 17 | anyhow = "1" 18 | clap = { version = "4.5.37", features = ["derive"] } 19 | cfg-if = "1" 20 | dirs = "6" 21 | fs2 = "0.4" 22 | glob = "0.3" 23 | lazy_static = "1" 24 | libc = "0.2" 25 | serde = { version = "1", features = ["derive"] } 26 | serde_json = "1" 27 | serde_yaml = "0.9" 28 | signal-hook = "0.3" 29 | tempfile = "3" 30 | which = "7" 31 | wildmatch = "2" 32 | skim = "0.16.2" 33 | 34 | [target.arm-unknown-linux-musleabi.dependencies] 35 | aws-lc-rs = { version = "1.13", default-features = false, features = [ 36 | "aws-lc-sys", 37 | "prebuilt-nasm", 38 | "bindgen", 39 | ] } 40 | 41 | [dependencies.attohttpc] 42 | default-features = false 43 | features = ["compress", "json", "tls-rustls-native-roots"] 44 | version = "0.29.2" 45 | optional = true 46 | 47 | [features] 48 | update = ["attohttpc"] 49 | default = ["update"] 50 | 51 | [profile.release] 52 | codegen-units = 1 53 | lto = true 54 | opt-level = "s" 55 | strip = true 56 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2020 Simon Bernier St-Pierre 2 | 3 | This software is provided 'as-is', without any express or implied 4 | warranty. In no event will the authors be held liable for any damages 5 | arising from the use of this software. 6 | 7 | Permission is granted to anyone to use this software for any purpose, 8 | including commercial applications, and to alter it and redistribute it 9 | freely, subject to the following restrictions: 10 | 11 | 1. The origin of this software must not be misrepresented; you must not 12 | claim that you wrote the original software. If you use this software 13 | in a product, an acknowledgment in the product documentation would be 14 | appreciated but is not required. 15 | 2. Altered source versions must be plainly marked as such, and must not be 16 | misrepresented as being the original software. 17 | 3. This notice may not be removed or altered from any source distribution. 18 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Kubie 2 | 3 | 4 | `kubie` is an alternative to `kubectx`, `kubens` and the `k on` prompt modification script. It offers context switching, 5 | namespace switching and prompt modification in a way that makes each shell independent from others. It also has 6 | support for split configuration files, meaning it can load Kubernetes contexts from multiple files. You can configure 7 | the paths where kubie will look for contexts, see the [settings](#settings) section. 8 | 9 | Kubie also has other nice features such as `kubie exec` which allows you to execute commands in a context and a 10 | namespace without having to spawn a shell and `kubie lint` which scans your k8s config files for issues and informs 11 | you of what they are. 12 | 13 | * [Installation](#installation) 14 | * [Usage](#usage) 15 | * [Settings](#settings) 16 | * [Future plans](#future-plans) 17 | 18 | Thanks to [@ahermant](https://github.com/ahermant) for the lovely logo! 19 | 20 | ## Installation 21 | 22 | ### Binary 23 | You can download a binary for Linux or OS X on the [GitHub releases page](https://github.com/sbstp/kubie/releases). You 24 | can use `curl` or `wget` to download it. Don't forget to `chmod +x` the file! 25 | 26 | ### Cargo 27 | You can build `kubie` from source using `cargo` and crates.io. If you do not have a Rust compiler installed, go to 28 | [rustup.rs](https://rustup.rs) to get one. Then you can run `cargo install kubie` and kubie will be downloaded from 29 | crates.io and then built. 30 | 31 | ### Homebrew 32 | You can install `kubie` from Homebrew by running `brew install kubie`. 33 | 34 | ### MacPorts 35 | You can also install `kubie` from [MacPorts](https://www.macports.org) by running `sudo port install kubie`. 36 | 37 | ### Nix 38 | There is a `kubie` Nix package maintained by @illiusdope that you can install. 39 | 40 | ### Arch Linux 41 | `kubie` is available in the [extra repository](https://archlinux.org/packages/extra/x86_64/kubie/) and it can be installed by running `pacman -S kubie`. 42 | 43 | ### Autocompletion 44 | 45 | #### Bash 46 | 47 | If you want autocompletion for `kubie ctx`, `kubie ns` and `kubie exec`, please install this script: 48 | ```bash 49 | sudo cp ./completion/kubie.bash /etc/bash_completion.d/kubie 50 | ``` 51 | 52 | Then spawn new shell or source copied file: 53 | ```bash 54 | . /etc/bash_completion.d/kubie 55 | ``` 56 | 57 | #### Fish 58 | 59 | Install the completions script [kubie.fish](completion/kubie.fish) by copying it, eg.: 60 | 61 | ```bash 62 | cp completion/kubie.fish ~/.config/fish/completions/ 63 | ``` 64 | 65 | Then reopen fish or source the file. 66 | 67 | ## Usage 68 | Selectable menus will be available when using `kubie ctx` and `kubie ns`. 69 | 70 | --- 71 | 72 | * `kubie ctx` display a selectable menu of contexts 73 | * `kubie ctx ` switch the current shell to the given context (spawns a shell if not a kubie shell) 74 | * `kubie ctx -` switch back to the previous context 75 | * `kubie ctx -r` spawn a recursive shell in the given context 76 | * `kubie ctx -n ` spawn a shell in the given context and namespace 77 | * `kubie ns` display a selectable menu of namespaces 78 | * `kubie ns ` switch the current shell to the given namespace 79 | * `kubie ns -` switch back to the previous namespace 80 | * `kubie ns -r` spawn a recursive shell in the given namespace 81 | * `kubie exec ...` execute a command in the given context and namespace 82 | * `kubie exec ...` execute a command in all the contexts matched by the wildcard and 83 | in the given namespace 84 | * `kubie exec -e ...` execute a command in all the contexts matched by the wildcard and 85 | in the given namespace but fail early if any of the commands executed return a non-zero exit code 86 | * `kubie export ` prints the path to an isolated config file for a context and namespace 87 | * `kubie edit` display a selectable menu of contexts to edit 88 | * `kubie edit ` edit the file that contains this context 89 | * `kubie edit-config` edit kubie's own config file 90 | * `kubie lint` lint k8s config files for issues 91 | * `kubie info ctx` print name of current context 92 | * `kubie info ns` print name of current namespace 93 | * `kubie info depth` print depth of recursive contexts 94 | * `kubie update` will check the latest kubie version and update your local installation if needed 95 | 96 | ## Settings 97 | You can customize kubie's behavior with the `~/.kube/kubie.yaml` file. The settings available and their defaults are 98 | available below. 99 | 100 | ```yaml 101 | # Force kubie to use a particular shell, if unset detect shell currently in use. 102 | # Possible values: bash, dash, fish, xonsh, zsh 103 | # Default: unset 104 | shell: bash 105 | 106 | # For the commands `kubie edit/edit-config` 107 | # Possible values: Any installed text editor 108 | # Default: unset 109 | default_editor: vim 110 | 111 | # Configure where to look for kubernetes config files. 112 | configs: 113 | 114 | # Include these globs. 115 | # Default: values listed below. 116 | include: 117 | - ~/.kube/config 118 | - ~/.kube/*.yml 119 | - ~/.kube/*.yaml 120 | - ~/.kube/configs/*.yml 121 | - ~/.kube/configs/*.yaml 122 | - ~/.kube/kubie/*.yml 123 | - ~/.kube/kubie/*.yaml 124 | 125 | # Exclude these globs. 126 | # Default: values listed below. 127 | # Note: kubie's own config file is always excluded. 128 | exclude: 129 | - ~/.kube/kubie.yaml 130 | 131 | # Prompt settings. 132 | prompt: 133 | # Disable kubie's custom prompt inside of a kubie shell. This is useful 134 | # when you already have a prompt displaying kubernetes information. 135 | # Default: false 136 | disable: false 137 | 138 | # When using recursive contexts, show depth when larger than 1. 139 | # Default: true 140 | show_depth: true 141 | 142 | # When using zsh, show context and namespace on the right-hand side using RPS1. 143 | # Default: false 144 | zsh_use_rps1: false 145 | 146 | # When using fish, show context and namespace on the right-hand side. 147 | # Default: false 148 | fish_use_rprompt: false 149 | 150 | # When using xonsh, show context and namespace on the right-hand side. 151 | # Default: false 152 | xonsh_use_right_prompt: false 153 | 154 | # Behavior 155 | behavior: 156 | # Namespace validation and switching behavior. Set to "false" if you do not have 157 | # the right to list namespaces. 158 | # Valid values: 159 | # true: Make sure the namespace exists with `kubectl get namespaces`. 160 | # false: Switch namespaces without validation. 161 | # partial: Check for partial matches when running `kubie ns ` 162 | # and no exact match is found: 163 | # - if exactly one namespace partially matches, switch to that namespace 164 | # - if multiple namespaces partially match, select from those 165 | # Default: true 166 | validate_namespaces: true 167 | 168 | # Enable or disable the printing of the 'CONTEXT => ...' headers when running 169 | # `kubie exec`. 170 | # Valid values: 171 | # auto: Prints context headers only if stdout is a TTY. Piping/redirecting 172 | # kubie output will auto-disable context headers. 173 | # always: Always prints context headers, even if stdout is not a TTY. 174 | # never: Never prints context headers. 175 | # Default: auto 176 | print_context_in_exec: auto 177 | 178 | # Optional start and stop hooks 179 | hooks: 180 | # A command hook to run when a CTX is started. 181 | # This example re-labels your terminal window 182 | # Default: none 183 | start_ctx: > 184 | echo -en "\033]1; `kubie info ctx`|`kubie info ns` \007" 185 | 186 | # A command hook to run when a CTX is stopped 187 | # This example sets the terminal back to the shell name 188 | # Default: none 189 | stop_ctx: > 190 | echo -en "\033]1; $SHELL \007" 191 | 192 | ``` 193 | 194 | ## For distro maintainers 195 | Since `0.19.0`, the self update functionality is behind a feature. You can use `cargo build --release --no-default-features` 196 | to produce a binary without the self update functionality. It's probably better if people rely on the distro's package 197 | manager for updates over this functionality. The binary produced is also quite smaller since it has fewer dependencies. 198 | 199 | ## Future plans 200 | * Integration with vault to automatically download k8s configs from a vault server 201 | * Import/edit configs 202 | -------------------------------------------------------------------------------- /assets/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 8 | 9 | 13 | 15 | 18 | 21 | 24 | 26 | 28 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 45 | 47 | 49 | 51 | 53 | 54 | 57 | 59 | 61 | 62 | 63 | 64 | -------------------------------------------------------------------------------- /completion/kubie.bash: -------------------------------------------------------------------------------- 1 | #Kubie completion script 2 | 3 | _kubiecomplete() 4 | { 5 | local cur prev 6 | 7 | cur=${COMP_WORDS[COMP_CWORD]} 8 | prev=${COMP_WORDS[COMP_CWORD-1]} 9 | 10 | { \unalias command; \unset -f command; } >/dev/null 2>&1 || true 11 | 12 | case ${COMP_CWORD} in 13 | 1) 14 | cmds="ctx edit edit-config exec help info lint ns" 15 | COMPREPLY=($(command printf "%s\n" $cmds | command grep -e "^$cur" | command xargs)) 16 | ;; 17 | 2) 18 | case ${prev} in 19 | ctx) 20 | COMPREPLY=($(command kubie ctx | command grep -e "^$cur" | command xargs)) 21 | ;; 22 | edit) 23 | COMPREPLY=($(command kubie ctx | command grep -e "^$cur" | command xargs)) 24 | ;; 25 | exec) 26 | COMPREPLY=($(command kubie ctx | command grep -e "^$cur" | command xargs)) 27 | ;; 28 | ns) 29 | COMPREPLY=($(command kubie ns | command grep -e "^$cur" | command xargs)) 30 | ;; 31 | esac 32 | ;; 33 | 3) 34 | prevprev=${COMP_WORDS[COMP_CWORD-2]} 35 | case ${prevprev} in 36 | exec) 37 | COMPREPLY=($(command kubie exec ${prev} default kubectl get namespaces|command tail -n+2|command awk '{print $1}'| command grep -e "^$cur" |command xargs)) 38 | ;; 39 | esac 40 | ;; 41 | *) 42 | COMPREPLY=() 43 | ;; 44 | esac 45 | } 46 | 47 | complete -F _kubiecomplete kubie 48 | -------------------------------------------------------------------------------- /completion/kubie.fish: -------------------------------------------------------------------------------- 1 | set -l commands ctx edit edit-config exec help info lint ns update 2 | 3 | complete -c kubie --no-files 4 | 5 | complete -c kubie \ 6 | --condition "not __fish_seen_subcommand_from $commands" \ 7 | --arguments "$commands" 8 | 9 | set -l cmd __fish_seen_subcommand_from 10 | 11 | complete -c kubie -n "not $cmd exec; or not __kubie_got_two_args" -l help -s h 12 | complete -c kubie -n "not $cmd $commands" -l version -s V 13 | 14 | complete -c kubie -n "$cmd help" -a "$commands" 15 | 16 | # FIXME: This should take --kubeconfig into account 17 | complete -c kubie -n "$cmd ctx delete edit exec; and __kubie_at_arg 1" -d 'context' \ 18 | -a '(kubie ctx 2> /dev/null)' 19 | 20 | complete -c kubie -n "$cmd ctx ns" -l recursive -s r -d 'spawn a new recursive shell' 21 | 22 | complete -c kubie -n "$cmd ctx; and __kubie_at_arg 1" -a '-' -d 'switch back' 23 | complete -c kubie -n "$cmd ctx" -l kubeconfig -s f -r -d 'load contexts from file' 24 | complete -c kubie -n "$cmd ctx" -l namespace -s n -d 'namespace' \ 25 | -xa '(kubie exec -e (__kubie_get_first_arg) default -- kubie ns 2>/dev/null)' 26 | 27 | complete -c kubie -n "$cmd exec; and __kubie_at_arg 1" -a '"*"' -d 'exec in all contexts' 28 | complete -c kubie -n "$cmd exec; and not __kubie_got_two_args" -l exit-early -e 29 | complete -c kubie -n "$cmd exec; and not __kubie_got_two_args" -l context-headers \ 30 | -xa "Auto Always Never" -d 'print context?' 31 | complete -c kubie -n "$cmd exec; and __kubie_at_arg 2" -d 'namespace' \ 32 | -a '(kubie exec -e (__kubie_get_first_arg) default -- kubie ns 2>/dev/null)' 33 | complete -c kubie -n "$cmd exec; and __kubie_got_two_args" \ 34 | -a '(__fish_complete_subcommand --commandline (__kubie_positionals)[4..-1])' 35 | 36 | complete -c kubie -n "$cmd info" -a "ctx depth help ns" 37 | 38 | complete -c kubie -n "$cmd ns" -l unset -s u 39 | complete -c kubie -n "$cmd ns" -d 'namespace' -a '(kubie ns 2>/dev/null)' 40 | complete -c kubie -n "$cmd ns" -a '-' -d 'switch back' 41 | 42 | # Strip the cmdline from options and flags, used for ctx and exec completions 43 | function __kubie_positionals 44 | set -l cmd (commandline -poc)[2..-1] (commandline -ct) 45 | argparse r/recursive f/kubeconfig= n/namespace= e/exit-early c-context-headers= -- $cmd 2>&1 46 | for x in $argv; echo $x; end 47 | end 48 | 49 | function __kubie_get_first_arg 50 | # 2 because first elem is subcmd name 51 | echo (__kubie_positionals)[2] 52 | end 53 | 54 | function __kubie_at_arg 55 | test (count (__kubie_positionals)) = (math $argv[1] + 1) 56 | end 57 | 58 | function __kubie_got_two_args 59 | test (count (__kubie_positionals)) -ge 4 60 | end 61 | -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | max_width = 120 2 | -------------------------------------------------------------------------------- /src/cmd/context.rs: -------------------------------------------------------------------------------- 1 | use anyhow::{Context, Result}; 2 | use skim::SkimOptions; 3 | 4 | use crate::cmd::{select_or_list_context, SelectResult}; 5 | use crate::kubeconfig::{self, Installed}; 6 | use crate::kubectl; 7 | use crate::session::Session; 8 | use crate::settings::Settings; 9 | use crate::shell::spawn_shell; 10 | use crate::state::State; 11 | use crate::vars; 12 | 13 | fn enter_context( 14 | settings: &Settings, 15 | installed: Installed, 16 | context_name: &str, 17 | namespace_name: Option<&str>, 18 | recursive: bool, 19 | ) -> Result<()> { 20 | let state = State::load()?; 21 | let mut session = Session::load()?; 22 | 23 | let namespace_name = 24 | namespace_name.or_else(|| state.namespace_history.get(context_name).and_then(|s| s.as_deref())); 25 | 26 | let kubeconfig = if context_name == "-" { 27 | let previous_ctx = session 28 | .get_last_context() 29 | .context("There is no previous context to switch to.")?; 30 | installed.make_kubeconfig_for_context(&previous_ctx.context, previous_ctx.namespace.as_deref())? 31 | } else { 32 | installed.make_kubeconfig_for_context(context_name, namespace_name)? 33 | }; 34 | 35 | session.add_history_entry( 36 | &kubeconfig.contexts[0].name, 37 | kubeconfig.contexts[0].context.namespace.as_deref(), 38 | ); 39 | 40 | if settings.behavior.validate_namespaces.can_list_namespaces() { 41 | if let Some(namespace_name) = namespace_name { 42 | let namespaces = kubectl::get_namespaces(Some(&kubeconfig))?; 43 | if !namespaces.iter().any(|x| x == namespace_name) { 44 | eprintln!("Warning: namespace {namespace_name} does not exist."); 45 | } 46 | } 47 | } 48 | 49 | if vars::is_kubie_active() && !recursive { 50 | let path = kubeconfig::get_kubeconfig_path()?; 51 | kubeconfig.write_to_file(path.as_path())?; 52 | session.save(None)?; 53 | } else { 54 | spawn_shell(settings, kubeconfig, &session)?; 55 | } 56 | 57 | Ok(()) 58 | } 59 | 60 | pub fn context( 61 | settings: &Settings, 62 | skim_options: &SkimOptions, 63 | context_name: Option, 64 | namespace_name: Option, 65 | kubeconfigs: Vec, 66 | recursive: bool, 67 | ) -> Result<()> { 68 | let mut installed = if kubeconfigs.is_empty() { 69 | kubeconfig::get_installed_contexts(settings)? 70 | } else { 71 | kubeconfig::get_kubeconfigs_contexts(&kubeconfigs)? 72 | }; 73 | 74 | let context_name = match context_name { 75 | Some(context_name) => context_name, 76 | None => match select_or_list_context(skim_options, &mut installed)? { 77 | SelectResult::Selected(x) => x, 78 | _ => return Ok(()), 79 | }, 80 | }; 81 | 82 | enter_context(settings, installed, &context_name, namespace_name.as_deref(), recursive) 83 | } 84 | -------------------------------------------------------------------------------- /src/cmd/delete.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Result; 2 | use skim::SkimOptions; 3 | 4 | use crate::cmd::{select_or_list_context, SelectResult}; 5 | use crate::kubeconfig; 6 | use crate::settings::Settings; 7 | 8 | pub fn delete_context(settings: &Settings, skim_options: &SkimOptions, context_name: Option) -> Result<()> { 9 | let mut installed = kubeconfig::get_installed_contexts(settings)?; 10 | 11 | let context_name = match context_name { 12 | Some(context_name) => context_name, 13 | None => match select_or_list_context(skim_options, &mut installed)? { 14 | SelectResult::Selected(x) => x, 15 | _ => return Ok(()), 16 | }, 17 | }; 18 | 19 | installed.delete_context(&context_name) 20 | } 21 | -------------------------------------------------------------------------------- /src/cmd/edit.rs: -------------------------------------------------------------------------------- 1 | use std::env; 2 | use std::path::PathBuf; 3 | use std::process::Command; 4 | 5 | use anyhow::{anyhow, Result}; 6 | use skim::SkimOptions; 7 | use which::which; 8 | 9 | use crate::cmd::{select_or_list_context, SelectResult}; 10 | use crate::kubeconfig; 11 | use crate::settings::Settings; 12 | 13 | fn get_editor(settings: &Settings) -> Result { 14 | if let Some(default_editor) = &settings.default_editor { 15 | if let Ok(path) = which(default_editor) { 16 | return Ok(path); 17 | } 18 | } 19 | 20 | env::var("EDITOR") 21 | .ok() 22 | .and_then(|editor| which(editor).ok()) 23 | .or_else(|| { 24 | for editor in &["nvim", "vim", "emacs", "vi", "nano"] { 25 | if let Ok(path) = which(editor) { 26 | return Some(path); 27 | } 28 | } 29 | None 30 | }) 31 | .ok_or_else(|| anyhow!("Could not find any editor to use")) 32 | } 33 | 34 | pub fn edit_context(settings: &Settings, skim_options: &SkimOptions, context_name: Option) -> Result<()> { 35 | let mut installed = kubeconfig::get_installed_contexts(settings)?; 36 | installed.contexts.sort_by(|a, b| a.item.name.cmp(&b.item.name)); 37 | 38 | let context_name = match context_name { 39 | Some(context_name) => context_name, 40 | None => match select_or_list_context(skim_options, &mut installed)? { 41 | SelectResult::Selected(x) => x, 42 | _ => return Ok(()), 43 | }, 44 | }; 45 | 46 | let context_src = installed 47 | .find_context_by_name(&context_name) 48 | .ok_or_else(|| anyhow!("Could not find context {}", context_name))?; 49 | 50 | let editor = get_editor(settings)?; 51 | 52 | let mut job = Command::new(editor).arg(context_src.source.as_ref()).spawn()?; 53 | job.wait()?; 54 | 55 | Ok(()) 56 | } 57 | 58 | pub fn edit_config(settings: &Settings) -> Result<()> { 59 | let editor = get_editor(settings)?; 60 | let settings_path = Settings::path(); 61 | 62 | let mut job = Command::new(editor).arg(settings_path).spawn()?; 63 | job.wait()?; 64 | 65 | Ok(()) 66 | } 67 | -------------------------------------------------------------------------------- /src/cmd/exec.rs: -------------------------------------------------------------------------------- 1 | use std::process::Command; 2 | use std::thread; 3 | 4 | use anyhow::{anyhow, Result}; 5 | use signal_hook::consts::signal::*; 6 | use signal_hook::iterator::Signals; 7 | 8 | use crate::kubeconfig::{self, KubeConfig}; 9 | use crate::settings::{ContextHeaderBehavior, Settings}; 10 | use crate::vars; 11 | 12 | fn run_in_context(kubeconfig: &KubeConfig, args: &[String]) -> anyhow::Result { 13 | let temp_config_file = tempfile::Builder::new() 14 | .prefix("kubie-config") 15 | .suffix(".yaml") 16 | .tempfile()?; 17 | kubeconfig.write_to_file(temp_config_file.path())?; 18 | 19 | let depth = vars::get_depth(); 20 | let next_depth = depth + 1; 21 | 22 | let mut signals = Signals::new([SIGHUP, SIGTERM, SIGINT, SIGQUIT, SIGWINCH, SIGUSR1, SIGUSR2]) 23 | .expect("could not install signal handler"); 24 | 25 | let mut child = Command::new(&args[0]) 26 | .args(&args[1..]) 27 | .env("KUBECONFIG", temp_config_file.path()) 28 | .env("KUBIE_KUBECONFIG", temp_config_file.path()) 29 | .env("KUBIE_ACTIVE", "1") 30 | .env("KUBIE_DEPTH", next_depth.to_string()) 31 | .spawn()?; 32 | 33 | let child_pid = child.id(); 34 | 35 | thread::spawn(move || { 36 | for sig in signals.forever() { 37 | unsafe { 38 | libc::kill(child_pid as libc::pid_t, sig as libc::c_int); 39 | } 40 | } 41 | }); 42 | 43 | let status = child.wait()?; 44 | 45 | Ok(status.code().unwrap_or(0)) 46 | } 47 | 48 | pub fn exec( 49 | settings: &Settings, 50 | context_name: String, 51 | namespace_name: String, 52 | exit_early: bool, 53 | context_headers_flag: Option, 54 | args: Vec, 55 | ) -> Result<()> { 56 | if args.is_empty() { 57 | return Ok(()); 58 | } 59 | 60 | let installed = kubeconfig::get_installed_contexts(settings)?; 61 | let mut matching = installed.get_contexts_matching(&context_name); 62 | matching.sort_by(|a, b| a.item.name.cmp(&b.item.name)); 63 | 64 | if matching.is_empty() { 65 | return Err(anyhow!("No context matching {}", context_name)); 66 | } 67 | 68 | let print_context = context_headers_flag 69 | .as_ref() 70 | .unwrap_or(&settings.behavior.print_context_in_exec) 71 | .should_print_headers(); 72 | 73 | for context_src in matching { 74 | if print_context { 75 | println!("CONTEXT => {}", context_src.item.name); 76 | } 77 | let kubeconfig = installed.make_kubeconfig_for_context(&context_src.item.name, Some(&namespace_name))?; 78 | let return_code = run_in_context(&kubeconfig, &args)?; 79 | if print_context { 80 | println!("{}", "-".repeat(20)); 81 | } 82 | 83 | if return_code != 0 && exit_early { 84 | std::process::exit(return_code); 85 | } 86 | } 87 | 88 | std::process::exit(0); 89 | } 90 | -------------------------------------------------------------------------------- /src/cmd/export.rs: -------------------------------------------------------------------------------- 1 | use anyhow::{anyhow, Result}; 2 | 3 | use crate::kubeconfig; 4 | use crate::settings::Settings; 5 | 6 | pub fn export(settings: &Settings, context_name: String, namespace_name: String) -> Result<()> { 7 | let installed = kubeconfig::get_installed_contexts(settings)?; 8 | let matching = installed.get_contexts_matching(&context_name); 9 | 10 | if matching.is_empty() { 11 | return Err(anyhow!("No context matching {}", context_name)); 12 | } 13 | 14 | for context_src in matching { 15 | let kubeconfig = installed.make_kubeconfig_for_context(&context_src.item.name, Some(&namespace_name))?; 16 | let temp_config_file = tempfile::Builder::new() 17 | .prefix("kubie-config") 18 | .suffix(".yaml") 19 | .tempfile()?; 20 | kubeconfig.write_to_file(temp_config_file.path())?; 21 | let (_, path) = temp_config_file.keep()?; 22 | println!("{}", path.display()); 23 | } 24 | 25 | std::process::exit(0); 26 | } 27 | -------------------------------------------------------------------------------- /src/cmd/info.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Result; 2 | 3 | use crate::cmd::meta::{KubieInfo, KubieInfoKind}; 4 | use crate::kubeconfig; 5 | use crate::vars; 6 | 7 | pub fn info(info: KubieInfo) -> Result<()> { 8 | match info.kind { 9 | KubieInfoKind::Context => { 10 | vars::ensure_kubie_active()?; 11 | let conf = kubeconfig::get_current_config()?; 12 | println!("{}", conf.current_context.as_deref().unwrap_or("")); 13 | } 14 | KubieInfoKind::Namespace => { 15 | vars::ensure_kubie_active()?; 16 | let conf = kubeconfig::get_current_config()?; 17 | println!("{}", conf.contexts[0].context.namespace.as_deref().unwrap_or("default")); 18 | } 19 | KubieInfoKind::Depth => { 20 | vars::ensure_kubie_active()?; 21 | println!("{}", vars::get_depth()); 22 | } 23 | }; 24 | 25 | Ok(()) 26 | } 27 | -------------------------------------------------------------------------------- /src/cmd/lint.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashSet; 2 | use std::path::Path; 3 | 4 | use anyhow::Result; 5 | 6 | use crate::kubeconfig::{self, Installed}; 7 | use crate::settings::Settings; 8 | 9 | fn lint_clusters(installed: &Installed) { 10 | let mut set: HashSet<(&str, &Path)> = HashSet::new(); 11 | 12 | for cluster_src in &installed.clusters { 13 | let named = &cluster_src.item; 14 | 15 | if installed 16 | .find_contexts_by_cluster(&named.name, &cluster_src.source) 17 | .is_empty() 18 | { 19 | println!( 20 | "Cluster '{}' has no context referencing it in file {}", 21 | named.name, 22 | cluster_src.source.display(), 23 | ); 24 | } 25 | if set.contains(&(&named.name, &cluster_src.source)) { 26 | println!( 27 | "A cluster named '{}' appears more than once in file {}", 28 | named.name, 29 | cluster_src.source.display(), 30 | ); 31 | } else { 32 | set.insert((&named.name, &cluster_src.source)); 33 | } 34 | } 35 | } 36 | 37 | fn lint_users(installed: &Installed) { 38 | let mut set: HashSet<(&str, &Path)> = HashSet::new(); 39 | 40 | for user_src in &installed.users { 41 | let named = &user_src.item; 42 | 43 | if installed 44 | .find_contexts_by_user(&named.name, &user_src.source) 45 | .is_empty() 46 | { 47 | println!( 48 | "User '{}' has no context referencing it in file {}", 49 | named.name, 50 | user_src.source.display(), 51 | ); 52 | } 53 | if set.contains(&(&named.name, &user_src.source)) { 54 | println!( 55 | "A user named '{}' appears more than once in file {}", 56 | named.name, 57 | user_src.source.display(), 58 | ); 59 | } else { 60 | set.insert((&named.name, &user_src.source)); 61 | } 62 | } 63 | } 64 | 65 | fn lint_contexts(installed: &Installed) { 66 | let mut set = HashSet::new(); 67 | 68 | for context_src in &installed.contexts { 69 | let named = &context_src.item; 70 | 71 | if installed 72 | .find_cluster_by_name(&named.context.cluster, &context_src.source) 73 | .is_none() 74 | { 75 | println!( 76 | "Context '{}' references unknown cluster '{}' in file {}", 77 | named.name, 78 | named.context.cluster, 79 | context_src.source.display(), 80 | ); 81 | } 82 | if installed 83 | .find_user_by_name(&named.context.user, &context_src.source) 84 | .is_none() 85 | { 86 | println!( 87 | "Context '{}' references unknown users '{}' in file {}", 88 | named.name, 89 | named.context.user, 90 | context_src.source.display(), 91 | ); 92 | } 93 | if set.contains(&named.name) { 94 | println!( 95 | "A context name '{}' appears more than once in file {}", 96 | named.name, 97 | context_src.source.display() 98 | ); 99 | } else { 100 | set.insert(&named.name); 101 | } 102 | } 103 | } 104 | 105 | pub fn lint(settings: &Settings) -> Result<()> { 106 | let installed = kubeconfig::get_installed_contexts(settings)?; 107 | lint_clusters(&installed); 108 | lint_users(&installed); 109 | lint_contexts(&installed); 110 | Ok(()) 111 | } 112 | -------------------------------------------------------------------------------- /src/cmd/meta.rs: -------------------------------------------------------------------------------- 1 | use clap::Parser; 2 | 3 | use crate::settings::ContextHeaderBehavior; 4 | 5 | #[derive(Debug, Parser)] 6 | #[clap(version)] 7 | pub enum Kubie { 8 | /// Spawn a shell in the given context. The shell is isolated from other shells. 9 | /// Kubie shells can be spawned recursively without any issue. 10 | #[clap(name = "ctx")] 11 | Context { 12 | /// Specify in which namespace of the context the shell is spawned. 13 | #[clap(short = 'n', long = "namespace")] 14 | namespace_name: Option, 15 | 16 | /// Specify files from which to load contexts instead of using the installed ones. 17 | #[clap(short = 'f', long = "kubeconfig")] 18 | kubeconfigs: Vec, 19 | 20 | /// Enter the context by spawning a new recursive shell. 21 | #[clap(short = 'r', long = "recursive")] 22 | recursive: bool, 23 | 24 | /// Name of the context to enter. Use '-' to switch back to the previous context. 25 | context_name: Option, 26 | }, 27 | 28 | /// Change the namespace in which the current shell operates. The namespace change does 29 | /// not affect other shells. 30 | #[clap(name = "ns")] 31 | Namespace { 32 | /// Enter the namespace by spawning a new recursive shell. 33 | #[clap(short = 'r', long = "recursive")] 34 | recursive: bool, 35 | 36 | /// Unsets the namespace in the currently active context. 37 | #[clap(short = 'u', long = "unset")] 38 | unset: bool, 39 | 40 | /// Name of the namespace to enter. Use '-' to switch back to the previous namespace. 41 | namespace_name: Option, 42 | }, 43 | 44 | /// View info about the current kubie shell, such as the context name and the 45 | /// current namespace. 46 | #[clap(name = "info")] 47 | Info(KubieInfo), 48 | 49 | /// Execute a command inside of the given context and namespace. 50 | #[clap(name = "exec", trailing_var_arg = true)] 51 | Exec { 52 | /// Name of the context in which to run the command. 53 | context_name: String, 54 | /// Namespace in which to run the command. This is mandatory to avoid potential errors. 55 | namespace_name: String, 56 | /// Exit early if a command fails when using a wildcard context. 57 | #[clap(short = 'e', long = "exit-early")] 58 | exit_early: bool, 59 | /// Overrides behavior.print_context_in_exec in Kubie settings file. 60 | #[clap(value_enum, long = "context-headers")] 61 | context_headers_flag: Option, 62 | /// Command to run as well as its arguments. 63 | args: Vec, 64 | }, 65 | 66 | /// Prints the path to an isolated configuration file for a context and namespace. 67 | #[clap(name = "export")] 68 | Export { 69 | /// Name of the context to export. 70 | context_name: String, 71 | /// Name of the namespace in the context. This is mandatory to avoid potential errors. 72 | namespace_name: String, 73 | }, 74 | 75 | /// Check the Kubernetes config files for issues. 76 | #[clap(name = "lint")] 77 | Lint, 78 | 79 | /// Edit the given context. 80 | #[clap(name = "edit")] 81 | Edit { 82 | /// Name of the context to edit. 83 | context_name: Option, 84 | }, 85 | 86 | /// Edit kubie's config file. 87 | #[clap(name = "edit-config")] 88 | EditConfig, 89 | 90 | /// Check for a Kubie update and replace Kubie's binary if needed. 91 | /// This function can ask for sudo-mode. 92 | #[clap(name = "update")] 93 | #[cfg(feature = "update")] 94 | Update, 95 | 96 | /// Delete a context. Automatic garbage collection will be performed. 97 | /// Dangling users and clusters will be removed. 98 | #[clap(name = "delete")] 99 | Delete { 100 | /// Name of the context to edit. 101 | context_name: Option, 102 | }, 103 | } 104 | 105 | #[derive(Debug, Parser)] 106 | pub struct KubieInfo { 107 | #[clap(subcommand)] 108 | pub kind: KubieInfoKind, 109 | } 110 | 111 | /// Type of info the user is requesting. 112 | #[derive(Debug, Parser)] 113 | pub enum KubieInfoKind { 114 | /// Get the current shell's context name. 115 | #[clap(name = "ctx")] 116 | Context, 117 | /// Get the current shell's namespace name. 118 | #[clap(name = "ns")] 119 | Namespace, 120 | /// Get the current depth of contexts. 121 | #[clap(name = "depth")] 122 | Depth, 123 | } 124 | -------------------------------------------------------------------------------- /src/cmd/mod.rs: -------------------------------------------------------------------------------- 1 | use std::io::{self, Cursor, IsTerminal}; 2 | 3 | use anyhow::{bail, Result}; 4 | use skim::prelude::{Key, SkimItemReader}; 5 | use skim::{Skim, SkimOptions}; 6 | 7 | use crate::kubeconfig::Installed; 8 | use crate::kubectl; 9 | 10 | pub mod context; 11 | pub mod delete; 12 | pub mod edit; 13 | pub mod exec; 14 | pub mod export; 15 | pub mod info; 16 | pub mod lint; 17 | pub mod meta; 18 | pub mod namespace; 19 | #[cfg(feature = "update")] 20 | pub mod update; 21 | 22 | pub enum SelectResult { 23 | Cancelled, 24 | Listed, 25 | Selected(String), 26 | } 27 | 28 | pub fn select_or_list_context(skim_options: &SkimOptions, installed: &mut Installed) -> Result { 29 | installed.contexts.sort_by(|a, b| a.item.name.cmp(&b.item.name)); 30 | let mut context_names: Vec<_> = installed.contexts.iter().map(|c| c.item.name.clone()).collect(); 31 | 32 | if context_names.is_empty() { 33 | bail!("No contexts found"); 34 | } 35 | if context_names.len() == 1 { 36 | return Ok(SelectResult::Selected(context_names[0].clone())); 37 | } 38 | 39 | if io::stdout().is_terminal() { 40 | // NOTE: skim shows the list of context names in reverse order 41 | context_names.reverse(); 42 | let item_reader = SkimItemReader::default(); 43 | let items = item_reader.of_bufread(Cursor::new(context_names.join("\n"))); 44 | let selected_items = Skim::run_with(skim_options, Some(items)) 45 | .map(|out| match out.final_key { 46 | Key::Enter => out.selected_items, 47 | _ => Vec::new(), 48 | }) 49 | .unwrap_or_default(); 50 | if selected_items.is_empty() { 51 | return Ok(SelectResult::Cancelled); 52 | } 53 | Ok(SelectResult::Selected(selected_items[0].output().to_string())) 54 | } else { 55 | for c in context_names { 56 | println!("{c}"); 57 | } 58 | Ok(SelectResult::Listed) 59 | } 60 | } 61 | 62 | pub fn select_or_list_namespace(skim_options: &SkimOptions, namespaces: Option>) -> Result { 63 | let mut namespaces = namespaces.unwrap_or_else(|| kubectl::get_namespaces(None).expect("could not get namespaces")); 64 | 65 | namespaces.sort(); 66 | 67 | if namespaces.is_empty() { 68 | bail!("No namespaces found"); 69 | } 70 | 71 | if io::stdout().is_terminal() { 72 | // NOTE: skim shows the list of namespaces in reverse order 73 | namespaces.reverse(); 74 | let item_reader = SkimItemReader::default(); 75 | let items = item_reader.of_bufread(Cursor::new(namespaces.join("\n"))); 76 | let selected_items = Skim::run_with(skim_options, Some(items)) 77 | .map(|out| match out.final_key { 78 | Key::Enter => out.selected_items, 79 | _ => Vec::new(), 80 | }) 81 | .unwrap_or_default(); 82 | if selected_items.is_empty() { 83 | return Ok(SelectResult::Cancelled); 84 | } 85 | Ok(SelectResult::Selected(selected_items[0].output().to_string())) 86 | } else { 87 | for n in namespaces { 88 | println!("{n}"); 89 | } 90 | Ok(SelectResult::Listed) 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /src/cmd/namespace.rs: -------------------------------------------------------------------------------- 1 | use anyhow::{anyhow, Context, Result}; 2 | use skim::SkimOptions; 3 | 4 | use crate::cmd::{select_or_list_namespace, SelectResult}; 5 | use crate::kubeconfig; 6 | use crate::kubectl; 7 | use crate::session::Session; 8 | use crate::settings::{Settings, ValidateNamespacesBehavior}; 9 | use crate::shell::spawn_shell; 10 | use crate::state::State; 11 | use crate::vars; 12 | 13 | pub fn namespace( 14 | settings: &Settings, 15 | skim_options: &SkimOptions, 16 | namespace_name: Option, 17 | recursive: bool, 18 | unset: bool, 19 | ) -> Result<()> { 20 | vars::ensure_kubie_active()?; 21 | 22 | let mut session = Session::load().context("Could not load session file")?; 23 | 24 | if namespace_name.is_none() && unset { 25 | return enter_namespace(settings, &mut session, recursive, None); 26 | } 27 | 28 | let namespace_name = match namespace_name { 29 | Some(s) if s == "-" => Some( 30 | session 31 | .get_last_namespace() 32 | .context("There is not previous namespace to switch to")? 33 | .to_string(), 34 | ), 35 | Some(s) => match settings.behavior.validate_namespaces { 36 | ValidateNamespacesBehavior::False => Some(s), 37 | ValidateNamespacesBehavior::True => { 38 | let namespaces = kubectl::get_namespaces(None)?; 39 | if !namespaces.contains(&s) { 40 | return Err(anyhow!("'{}' is not a valid namespace for the context", s)); 41 | } 42 | Some(s) 43 | } 44 | ValidateNamespacesBehavior::Partial => { 45 | let namespaces = kubectl::get_namespaces(None)?; 46 | if namespaces.contains(&s) { 47 | Some(s) 48 | } else { 49 | let ns_partial_matches: Vec = 50 | namespaces.iter().filter(|&ns| ns.contains(&s)).cloned().collect(); 51 | match ns_partial_matches.len() { 52 | 0 => return Err(anyhow!("'{}' is not a valid namespace for the context", s)), 53 | 1 => Some(ns_partial_matches[0].clone()), 54 | _ => match select_or_list_namespace(skim_options, Some(ns_partial_matches))? { 55 | SelectResult::Selected(s) => Some(s), 56 | _ => return Ok(()), 57 | }, 58 | } 59 | } 60 | } 61 | }, 62 | None => match select_or_list_namespace(skim_options, None)? { 63 | SelectResult::Selected(s) => Some(s), 64 | _ => return Ok(()), 65 | }, 66 | }; 67 | 68 | enter_namespace(settings, &mut session, recursive, namespace_name) 69 | } 70 | 71 | fn enter_namespace( 72 | settings: &Settings, 73 | session: &mut Session, 74 | recursive: bool, 75 | namespace_name: Option, 76 | ) -> Result<()> { 77 | let mut config = kubeconfig::get_current_config()?; 78 | config.contexts[0].context.namespace = namespace_name.clone(); 79 | 80 | let context_name = &config.contexts[0].name; 81 | 82 | // Update the state, set the last namespace used for the context. 83 | // We take out a file lock here to avoid concurrent kubie processes 84 | // corrupting the state file 85 | State::modify(|state| { 86 | state 87 | .namespace_history 88 | .insert(context_name.into(), namespace_name.clone()); 89 | Ok(()) 90 | })?; 91 | 92 | // Update the history, add the context and namespace to it. 93 | session.add_history_entry(context_name, namespace_name); 94 | 95 | if recursive { 96 | spawn_shell(settings, config, session)?; 97 | } else { 98 | let config_file = kubeconfig::get_kubeconfig_path()?; 99 | config.write_to_file(config_file.as_path())?; 100 | session.save(None)?; 101 | } 102 | 103 | Ok(()) 104 | } 105 | -------------------------------------------------------------------------------- /src/cmd/update.rs: -------------------------------------------------------------------------------- 1 | use std::env; 2 | use std::fs; 3 | use std::fs::Permissions; 4 | use std::os::unix::fs::PermissionsExt; 5 | use std::path::Path; 6 | 7 | use anyhow::{Context, Result}; 8 | use cfg_if::cfg_if; 9 | use serde::Deserialize; 10 | 11 | const VERSION: &str = env!("CARGO_PKG_VERSION"); 12 | const LATEST_RELEASE_URL: &str = "https://api.github.com/repos/sbstp/kubie/releases/latest"; 13 | 14 | #[derive(Debug, Deserialize)] 15 | pub struct Release { 16 | tag_name: String, 17 | assets: Vec, 18 | } 19 | 20 | impl Release { 21 | pub fn get_latest() -> Result { 22 | let latest_release = attohttpc::get(LATEST_RELEASE_URL).send()?.json()?; 23 | Ok(latest_release) 24 | } 25 | 26 | // Get the right binary name based on which OS and architecture kubie was built-on. 27 | // Names match the GitHub releases: https://github.com/sbstp/kubie/releases 28 | fn get_binary_name() -> Option<&'static str> { 29 | cfg_if! { 30 | if #[cfg(all(target_os = "linux", target_arch = "x86_64"))] { 31 | Some("kubie-linux-amd64") 32 | } else if #[cfg(all(target_os = "linux", target_arch = "arm"))] { 33 | Some("kubie-linux-arm32") 34 | } else if #[cfg(all(target_os = "linux", target_arch = "aarch64"))] { 35 | Some("kubie-linux-arm64") 36 | } else if #[cfg(all(target_os = "macos", target_arch = "x86_64"))] { 37 | Some("kubie-darwin-amd64") 38 | } else if #[cfg(all(target_os = "macos", target_arch = "aarch64"))] { 39 | Some("kubie-darwin-arm64") 40 | } else { 41 | None 42 | } 43 | } 44 | } 45 | 46 | pub fn get_binary_url(&self) -> Option<&str> { 47 | let binary_name = Self::get_binary_name()?; 48 | 49 | for asset in self.assets.iter() { 50 | if asset.browser_download_url.contains(binary_name) { 51 | return Some(&asset.browser_download_url); 52 | } 53 | } 54 | None 55 | } 56 | } 57 | 58 | #[derive(Debug, Deserialize)] 59 | struct Asset { 60 | browser_download_url: String, 61 | } 62 | 63 | pub fn update() -> Result<()> { 64 | let latest_release = Release::get_latest()?; 65 | if latest_release.tag_name == format!("v{VERSION}") { 66 | println!("Kubie is up-to-date : v{VERSION}"); 67 | } else { 68 | println!( 69 | "A new version of Kubie is available ({}), the new version will be installed by replacing this binary.", 70 | latest_release.tag_name 71 | ); 72 | 73 | let download_url = latest_release.get_binary_url().context("Sorry, this release has no build for your OS, please create an issue : https://github.com/sbstp/kubie/issues")?; 74 | println!("Download url is: {download_url}"); 75 | 76 | let resp = attohttpc::get(download_url).send()?; 77 | if resp.is_success() { 78 | let temp_file = tempfile::Builder::new().prefix("kubie").tempfile()?; 79 | resp.write_to(&temp_file)?; 80 | 81 | let old_file = env::current_exe().expect("Could not get own binary path"); 82 | replace_file(&old_file, temp_file.path()).context("Update failed. Consider using sudo?")?; 83 | 84 | println!("Kubie has been updated successfully: {}", Path::display(&old_file)); 85 | } 86 | } 87 | Ok(()) 88 | } 89 | 90 | pub fn replace_file(old_file: &Path, new_file: &Path) -> std::io::Result<()> { 91 | fs::set_permissions(new_file, Permissions::from_mode(0o755))?; 92 | fs::remove_file(old_file)?; 93 | fs::copy(new_file, old_file)?; 94 | Ok(()) 95 | } 96 | 97 | #[test] 98 | #[cfg(all(target_os = "linux", target_arch = "x86_64"))] 99 | fn test_binary_name() { 100 | assert_eq!(Release::get_binary_name(), Some("kubie-linux-amd64")) 101 | } 102 | 103 | #[test] 104 | #[cfg(all(target_os = "macos", target_arch = "x86_64"))] 105 | fn test_binary_name() { 106 | assert_eq!(Release::get_binary_name(), Some("kubie-darwin-amd64")) 107 | } 108 | 109 | #[test] 110 | #[cfg(all(target_os = "macos", target_arch = "aarch64"))] 111 | fn test_binary_name() { 112 | assert_eq!(Release::get_binary_name(), Some("kubie-darwin-arm64")) 113 | } 114 | -------------------------------------------------------------------------------- /src/ioutil.rs: -------------------------------------------------------------------------------- 1 | use std::io::{BufReader, BufWriter}; 2 | use std::path::Path; 3 | use std::{ 4 | fs::{DirBuilder, File, OpenOptions}, 5 | panic::{self, UnwindSafe}, 6 | }; 7 | 8 | use anyhow::{Context, Result}; 9 | use fs2::FileExt; 10 | use serde::{de::DeserializeOwned, Serialize}; 11 | 12 | pub fn read_json(path: P) -> Result 13 | where 14 | P: AsRef, 15 | T: DeserializeOwned, 16 | { 17 | let file = File::open(path.as_ref())?; 18 | let reader = BufReader::new(file); 19 | let obj = serde_json::from_reader(reader)?; 20 | Ok(obj) 21 | } 22 | 23 | pub fn write_json(path: P, obj: &T) -> Result<()> 24 | where 25 | P: AsRef, 26 | T: Serialize, 27 | { 28 | let path = path.as_ref(); 29 | DirBuilder::new() 30 | .recursive(true) 31 | .create(path.parent().expect("path has no parent"))?; 32 | let file = File::create(path)?; 33 | let writer = BufWriter::new(file); 34 | serde_json::to_writer(writer, obj)?; 35 | Ok(()) 36 | } 37 | 38 | pub fn read_yaml(path: P) -> Result 39 | where 40 | P: AsRef, 41 | T: DeserializeOwned, 42 | { 43 | let file = File::open(path.as_ref())?; 44 | let reader = BufReader::new(file); 45 | let obj = serde_yaml::from_reader(reader)?; 46 | Ok(obj) 47 | } 48 | 49 | pub fn write_yaml(path: P, obj: &T) -> Result<()> 50 | where 51 | P: AsRef, 52 | T: Serialize, 53 | { 54 | let path = path.as_ref(); 55 | DirBuilder::new() 56 | .recursive(true) 57 | .create(path.parent().expect("path has no parent"))?; 58 | let file = File::create(path)?; 59 | let writer = BufWriter::new(file); 60 | serde_yaml::to_writer(writer, obj)?; 61 | Ok(()) 62 | } 63 | 64 | pub fn file_lock(path: P, scope: F) -> Result 65 | where 66 | P: AsRef, 67 | F: FnOnce() -> Result + UnwindSafe, 68 | { 69 | let path = path.as_ref(); 70 | let file = OpenOptions::new() 71 | .append(true) 72 | .truncate(false) 73 | .read(true) 74 | .create(true) 75 | .open(path) 76 | .with_context(|| format!("Could not open lock file at {}", path.display()))?; 77 | 78 | file.lock_exclusive() 79 | .with_context(|| format!("Could not lock file at {}", path.display()))?; 80 | 81 | // Run the given closure, but catch any panics so we can unlock before resuming the panic. 82 | let exception = panic::catch_unwind(scope); 83 | 84 | // Ignore errors during unlock. If we had a panic, we don't want to return the potential error. 85 | // If we did not panic, we want to return the closure's result. 86 | let _ = FileExt::unlock(&file); 87 | 88 | match exception { 89 | Ok(result) => result, 90 | Err(x) => panic::resume_unwind(x), 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /src/kubeconfig.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | use std::env; 3 | use std::fs::{self, File, Permissions}; 4 | use std::io::BufWriter; 5 | use std::os::unix::fs::PermissionsExt; 6 | use std::path::{Path, PathBuf}; 7 | use std::rc::Rc; 8 | 9 | use anyhow::{anyhow, bail, Context as _, Result}; 10 | use serde::{Deserialize, Serialize}; 11 | use serde_yaml::{Mapping, Value}; 12 | use wildmatch::WildMatch; 13 | 14 | use crate::ioutil; 15 | use crate::settings::Settings; 16 | 17 | #[derive(Clone, Debug, Deserialize, Serialize)] 18 | pub struct KubeConfig { 19 | pub clusters: Vec, 20 | pub users: Vec, 21 | pub contexts: Vec, 22 | #[serde(rename = "current-context")] 23 | pub current_context: Option, 24 | #[serde(flatten)] 25 | pub others: HashMap, 26 | } 27 | 28 | #[derive(Clone, Debug, Deserialize, Serialize)] 29 | pub struct NamedCluster { 30 | pub name: String, 31 | pub cluster: Mapping, 32 | } 33 | 34 | #[derive(Clone, Debug, Deserialize, Serialize)] 35 | pub struct NamedUser { 36 | pub name: String, 37 | pub user: Mapping, 38 | } 39 | 40 | #[derive(Clone, Debug, Deserialize, Serialize)] 41 | pub struct NamedContext { 42 | pub name: String, 43 | pub context: Context, 44 | } 45 | 46 | #[derive(Clone, Debug, Deserialize, Serialize)] 47 | pub struct Context { 48 | pub cluster: String, 49 | pub namespace: Option, 50 | pub user: String, 51 | } 52 | 53 | #[derive(Clone, Debug)] 54 | pub struct Sourced { 55 | pub source: Rc, 56 | pub item: T, 57 | } 58 | 59 | impl Sourced { 60 | pub fn new(source: &Rc, item: T) -> Self { 61 | Sourced { 62 | source: source.clone(), 63 | item, 64 | } 65 | } 66 | } 67 | 68 | #[derive(Debug)] 69 | pub struct Installed { 70 | pub clusters: Vec>, 71 | pub users: Vec>, 72 | pub contexts: Vec>, 73 | } 74 | 75 | impl KubeConfig { 76 | pub fn write_to_file(&self, path: &Path) -> anyhow::Result<()> { 77 | let file = File::create(path).context("could not write file")?; 78 | fs::set_permissions(path, Permissions::from_mode(0o600))?; 79 | 80 | let buffer = BufWriter::new(file); 81 | serde_yaml::to_writer(buffer, self)?; 82 | Ok(()) 83 | } 84 | } 85 | 86 | impl Installed { 87 | pub fn find_context_by_name(&self, name: &str) -> Option<&Sourced> { 88 | self.contexts.iter().find(|s| s.item.name == name) 89 | } 90 | 91 | pub fn find_cluster_by_name(&self, name: &str, source: &Path) -> Option<&Sourced> { 92 | self.clusters 93 | .iter() 94 | .find(|s| s.item.name == name && *s.source == source) 95 | .or_else(|| self.clusters.iter().find(|s| s.item.name == name)) 96 | } 97 | 98 | pub fn find_user_by_name(&self, name: &str, source: &Path) -> Option<&Sourced> { 99 | self.users 100 | .iter() 101 | .find(|s| s.item.name == name && *s.source == source) 102 | .or_else(|| self.users.iter().find(|s| s.item.name == name)) 103 | } 104 | 105 | pub fn find_contexts_by_cluster(&self, name: &str, source: &Path) -> Vec<&Sourced> { 106 | self.contexts 107 | .iter() 108 | .filter(|s| s.item.context.cluster == name && *s.source == source) 109 | .collect() 110 | } 111 | 112 | pub fn find_contexts_by_user(&self, name: &str, source: &Path) -> Vec<&Sourced> { 113 | self.contexts 114 | .iter() 115 | .filter(|s| s.item.context.user == name && *s.source == source) 116 | .collect() 117 | } 118 | 119 | pub fn get_contexts_matching(&self, pattern: &str) -> Vec<&Sourced> { 120 | let matcher = WildMatch::new(pattern); 121 | self.contexts.iter().filter(|s| matcher.matches(&s.item.name)).collect() 122 | } 123 | 124 | pub fn delete_context(&mut self, name: &str) -> Result<()> { 125 | let context = self 126 | .find_context_by_name(name) 127 | .ok_or_else(|| anyhow!("Context not found"))?; 128 | 129 | let mut kubeconfig: KubeConfig = ioutil::read_yaml(context.source.as_ref())?; 130 | 131 | // Retain all contexts whose name is not our context. 132 | kubeconfig.contexts.retain(|x| x.name != context.item.name); 133 | 134 | // Retain all clusters whose name is not our context's cluster reference. 135 | kubeconfig.clusters.retain(|x| x.name != context.item.context.cluster); 136 | 137 | // Retain all users whose name is not our context's user reference. 138 | kubeconfig.users.retain(|x| x.name != context.item.context.user); 139 | 140 | if kubeconfig.contexts.is_empty() && kubeconfig.clusters.is_empty() && kubeconfig.users.is_empty() { 141 | // If the kubeconfig is empty after removing the context and dangling references, 142 | // we simply remove the file. 143 | println!( 144 | "Deleting kubeconfig {} because is it now empty.", 145 | context.source.display() 146 | ); 147 | 148 | fs::remove_file(context.source.as_ref()).context("Could not delete empty kubeconfig file")?; 149 | } else { 150 | // If the kubeconfig is not empty, we rewrite it with the context and dangling references removed. 151 | println!("Updating kubeconfig {}.", context.source.display()); 152 | 153 | ioutil::write_yaml(context.source.as_ref(), &kubeconfig) 154 | .context("Could not open kubeconfig file to rewrite it.")?; 155 | } 156 | 157 | Ok(()) 158 | } 159 | 160 | fn make_path_absolute(mapping: &mut Mapping, key: &str, parent: &Path) { 161 | if !mapping.contains_key(key) { 162 | return; 163 | } 164 | let str = mapping.get(key).unwrap().as_str().expect("value should be a string"); 165 | let path = Path::new(str); 166 | if !path.is_absolute() { 167 | mapping.insert(key.into(), parent.join(path).to_str().expect("path should be a valid unicode string").into()); 168 | } 169 | } 170 | 171 | pub fn make_kubeconfig_for_context( 172 | &self, 173 | context_name: &str, 174 | namespace_name: Option>, 175 | ) -> Result { 176 | let mut context_src = self 177 | .contexts 178 | .iter() 179 | .find(|c| c.item.name == context_name) 180 | .cloned() 181 | .ok_or_else(|| anyhow!("Could not find context {}", context_name))?; 182 | 183 | context_src.item.context.namespace = namespace_name.map(Into::into); 184 | let kubeconfig_dir = context_src.source.parent().expect("kubeconfig path should have a parent dir"); 185 | 186 | let cluster_src = self 187 | .find_cluster_by_name(&context_src.item.context.cluster, &context_src.source) 188 | .cloned() 189 | .ok_or_else(|| { 190 | anyhow!( 191 | "Could not find cluster {} referenced by context {}", 192 | context_src.item.context.cluster, 193 | context_name, 194 | ) 195 | })?; 196 | 197 | let mut named_cluster = cluster_src.item; 198 | let cluster = &mut named_cluster.cluster; 199 | Self::make_path_absolute(cluster, "certificate-authority", kubeconfig_dir); 200 | 201 | let user_src = self 202 | .find_user_by_name(&context_src.item.context.user, &context_src.source) 203 | .cloned() 204 | .ok_or_else(|| { 205 | anyhow!( 206 | "Could not find user {} referenced by context {}", 207 | context_src.item.context.user, 208 | context_name, 209 | ) 210 | })?; 211 | 212 | let mut named_user = user_src.item; 213 | let user = &mut named_user.user; 214 | 215 | Self::make_path_absolute(user, "client-certificate", kubeconfig_dir); 216 | Self::make_path_absolute(user, "client-key", kubeconfig_dir); 217 | 218 | Ok(KubeConfig { 219 | clusters: vec![named_cluster], 220 | contexts: vec![context_src.item], 221 | users: vec![named_user], 222 | current_context: Some(context_name.into()), 223 | others: { 224 | let mut m: HashMap = HashMap::new(); 225 | m.insert("apiVersion".into(), Value::String("v1".into())); 226 | m.insert("kind".into(), Value::String("Config".into())); 227 | m 228 | }, 229 | }) 230 | } 231 | } 232 | 233 | fn load_kubeconfigs(kubeconfigs: I) -> Result 234 | where 235 | I: IntoIterator, 236 | P: AsRef, 237 | { 238 | let mut installed = Installed { 239 | clusters: vec![], 240 | contexts: vec![], 241 | users: vec![], 242 | }; 243 | 244 | for path in kubeconfigs.into_iter() { 245 | let path = path.as_ref(); 246 | 247 | // Avoid parsing things that aren't files or don't link to a file. 248 | if !path.is_file() { 249 | continue; 250 | } 251 | 252 | let kubeconfig: Result = ioutil::read_yaml(path); 253 | 254 | match kubeconfig { 255 | Ok(mut kubeconfig) => { 256 | let path = Rc::new(path.to_owned()); 257 | installed 258 | .clusters 259 | .extend(kubeconfig.clusters.drain(..).map(|x| Sourced::new(&path, x))); 260 | installed 261 | .contexts 262 | .extend(kubeconfig.contexts.drain(..).map(|x| Sourced::new(&path, x))); 263 | installed 264 | .users 265 | .extend(kubeconfig.users.drain(..).map(|x| Sourced::new(&path, x))); 266 | } 267 | Err(err) => { 268 | eprintln!("Error loading kubeconfig {}: {}", path.display(), err); 269 | } 270 | } 271 | } 272 | 273 | Ok(installed) 274 | } 275 | 276 | pub fn get_installed_contexts(settings: &Settings) -> Result { 277 | let installed = load_kubeconfigs(settings.get_kube_configs_paths()?)?; 278 | if installed.contexts.is_empty() { 279 | bail!("Could not find any contexts in the Kubie kubeconfig directories!"); 280 | } 281 | Ok(installed) 282 | } 283 | 284 | pub fn get_kubeconfigs_contexts(kubeconfigs: &Vec) -> Result { 285 | let installed = load_kubeconfigs(kubeconfigs)?; 286 | if installed.contexts.is_empty() { 287 | bail!("Could not find any contexts in the given set of files!"); 288 | } 289 | Ok(installed) 290 | } 291 | 292 | pub fn get_kubeconfig_path() -> Result { 293 | let path = env::var_os("KUBIE_KUBECONFIG").context("KUBIE_CONFIG not found")?; 294 | Ok(PathBuf::from(path)) 295 | } 296 | 297 | pub fn get_current_config() -> Result { 298 | ioutil::read_yaml(get_kubeconfig_path()?) 299 | } 300 | -------------------------------------------------------------------------------- /src/kubectl.rs: -------------------------------------------------------------------------------- 1 | use std::env; 2 | use std::process::Command; 3 | use std::str; 4 | 5 | use anyhow::{anyhow, Context}; 6 | 7 | use crate::kubeconfig::KubeConfig; 8 | 9 | pub fn get_namespaces<'a>(kubeconfig: impl Into>) -> anyhow::Result> { 10 | let mut cmd = Command::new("kubectl"); 11 | cmd.arg("get"); 12 | cmd.arg("namespaces"); 13 | 14 | let temp_config_file; 15 | 16 | if let Some(kubeconfig) = kubeconfig.into() { 17 | temp_config_file = tempfile::Builder::new() 18 | .prefix("kubie-config") 19 | .suffix(".yaml") 20 | .tempfile()?; 21 | kubeconfig.write_to_file(temp_config_file.path())?; 22 | cmd.env("KUBECONFIG", temp_config_file.path()); 23 | } else { 24 | cmd.env( 25 | "KUBECONFIG", 26 | env::var("KUBIE_KUBECONFIG").context("KUBIE_KUBECONFIG variable is not set")?, 27 | ); 28 | } 29 | 30 | let result = cmd.output()?; 31 | if !result.status.success() { 32 | let stderr = str::from_utf8(&result.stderr).unwrap_or("could not decode stderr of kubectl as utf-8"); 33 | return Err(anyhow!("Error calling kubectl: {}", stderr)); 34 | } 35 | 36 | let text = str::from_utf8(&result.stdout)?; 37 | let mut namespaces = vec![]; 38 | for line in text.lines().skip(1) { 39 | let idx = line.find(' ').unwrap_or(line.len()); 40 | namespaces.push(line[..idx].to_string()); 41 | } 42 | 43 | Ok(namespaces) 44 | } 45 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Result; 2 | use clap::Parser; 3 | 4 | use cmd::meta::Kubie; 5 | use settings::Settings; 6 | use skim::prelude::SkimOptionsBuilder; 7 | 8 | mod cmd; 9 | mod ioutil; 10 | mod kubeconfig; 11 | mod kubectl; 12 | mod session; 13 | mod settings; 14 | mod shell; 15 | mod state; 16 | mod vars; 17 | 18 | fn main() -> Result<()> { 19 | let settings = Settings::load()?; 20 | let skim_options = SkimOptionsBuilder::default().multi(false).build().unwrap(); 21 | let kubie = Kubie::parse(); 22 | 23 | match kubie { 24 | Kubie::Context { 25 | namespace_name, 26 | context_name, 27 | kubeconfigs, 28 | recursive, 29 | } => { 30 | cmd::context::context( 31 | &settings, 32 | &skim_options, 33 | context_name, 34 | namespace_name, 35 | kubeconfigs, 36 | recursive, 37 | )?; 38 | } 39 | Kubie::Namespace { 40 | namespace_name, 41 | recursive, 42 | unset, 43 | } => { 44 | cmd::namespace::namespace(&settings, &skim_options, namespace_name, recursive, unset)?; 45 | } 46 | Kubie::Info(info) => { 47 | cmd::info::info(info)?; 48 | } 49 | Kubie::Exec { 50 | context_name, 51 | namespace_name, 52 | exit_early, 53 | context_headers_flag, 54 | args, 55 | } => { 56 | cmd::exec::exec( 57 | &settings, 58 | context_name, 59 | namespace_name, 60 | exit_early, 61 | context_headers_flag, 62 | args, 63 | )?; 64 | } 65 | Kubie::Lint => { 66 | cmd::lint::lint(&settings)?; 67 | } 68 | Kubie::Edit { context_name } => { 69 | cmd::edit::edit_context(&settings, &skim_options, context_name)?; 70 | } 71 | Kubie::EditConfig => { 72 | cmd::edit::edit_config(&settings)?; 73 | } 74 | #[cfg(feature = "update")] 75 | Kubie::Update => { 76 | cmd::update::update()?; 77 | } 78 | Kubie::Delete { context_name } => { 79 | cmd::delete::delete_context(&settings, &skim_options, context_name)?; 80 | } 81 | Kubie::Export { 82 | context_name, 83 | namespace_name, 84 | } => { 85 | cmd::export::export(&settings, context_name, namespace_name)?; 86 | } 87 | } 88 | 89 | Ok(()) 90 | } 91 | -------------------------------------------------------------------------------- /src/session.rs: -------------------------------------------------------------------------------- 1 | use std::path::Path; 2 | 3 | use anyhow::{Context, Result}; 4 | use serde::{Deserialize, Serialize}; 5 | 6 | use crate::ioutil; 7 | use crate::vars; 8 | 9 | /// Session contains information which is scoped to a kubie shell. 10 | /// 11 | /// Currently it stores the history of contexts and namespaces entered to allow 12 | /// users to switch back to the previous context with `-`. 13 | #[derive(Debug, Default, Deserialize, Serialize)] 14 | pub struct Session { 15 | history: Vec, 16 | } 17 | 18 | impl Session { 19 | pub fn load() -> Result { 20 | let session_path = match vars::get_session_path() { 21 | None => return Ok(Default::default()), 22 | Some(x) => x, 23 | }; 24 | 25 | if !session_path.exists() { 26 | return Ok(Default::default()); 27 | } 28 | 29 | ioutil::read_json(session_path) 30 | } 31 | 32 | pub fn save(&self, path: Option<&Path>) -> Result<()> { 33 | let session_path = match path { 34 | Some(p) => p.to_path_buf(), 35 | None => vars::get_session_path().context("KUBIE_SESSION env variable missing")?, 36 | }; 37 | 38 | ioutil::write_json(session_path, self) 39 | } 40 | 41 | pub fn add_history_entry(&mut self, context: impl Into, namespace: Option>) { 42 | self.history.push(HistoryEntry { 43 | context: context.into(), 44 | namespace: namespace.map(Into::into), 45 | }) 46 | } 47 | 48 | pub fn get_last_context(&self) -> Option<&HistoryEntry> { 49 | let current_context = self.history.last()?; 50 | self.history 51 | .iter() 52 | .rev() 53 | .skip(1) 54 | .find(|&entry| current_context.context != entry.context) 55 | } 56 | 57 | pub fn get_last_namespace(&self) -> Option<&str> { 58 | let current_context = self.history.last()?; 59 | for entry in self.history.iter().rev().skip(1) { 60 | if current_context.context != entry.context { 61 | return None; 62 | } 63 | if current_context.namespace != entry.namespace { 64 | return entry.namespace.as_deref(); 65 | } 66 | } 67 | None 68 | } 69 | } 70 | 71 | #[derive(Debug, Deserialize, Serialize)] 72 | pub struct HistoryEntry { 73 | pub context: String, 74 | pub namespace: Option, 75 | } 76 | -------------------------------------------------------------------------------- /src/settings.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashSet; 2 | use std::fs::File; 3 | use std::io::{self, BufReader, IsTerminal}; 4 | use std::path::{Path, PathBuf}; 5 | 6 | use anyhow::{Context, Result}; 7 | use glob::glob; 8 | use lazy_static::lazy_static; 9 | use serde::Deserialize; 10 | 11 | lazy_static! { 12 | static ref HOME_DIR: String = dirs::home_dir() 13 | .expect("could not get home directory path") 14 | .to_str() 15 | .expect("home directory contains non unicode characters") 16 | .to_string(); 17 | } 18 | 19 | #[inline] 20 | fn home_dir() -> &'static str { 21 | &HOME_DIR 22 | } 23 | 24 | pub fn expanduser(path: &str) -> String { 25 | if let Some(stripped) = path.strip_prefix("~/") { 26 | format!("{}/{}", home_dir(), stripped) 27 | } else { 28 | path.to_string() 29 | } 30 | } 31 | 32 | #[derive(Debug, Default, Deserialize)] 33 | pub struct Settings { 34 | #[serde(default)] 35 | pub shell: Option, 36 | #[serde(default)] 37 | pub default_editor: Option, 38 | #[serde(default)] 39 | pub configs: Configs, 40 | #[serde(default)] 41 | pub prompt: Prompt, 42 | #[serde(default)] 43 | pub behavior: Behavior, 44 | #[serde(default)] 45 | pub hooks: Hooks, 46 | } 47 | 48 | impl Settings { 49 | pub fn path() -> String { 50 | format!("{}/.kube/kubie.yaml", home_dir()) 51 | } 52 | 53 | pub fn load() -> Result { 54 | let settings_path_str = Self::path(); 55 | let settings_path = Path::new(&settings_path_str); 56 | 57 | let mut settings = if settings_path.exists() { 58 | let file = File::open(settings_path)?; 59 | let reader = BufReader::new(file); 60 | serde_yaml::from_reader(reader).context("could not parse kubie config")? 61 | } else { 62 | Settings::default() 63 | }; 64 | 65 | // Very important to exclude kubie's own config file ~/.kube/kubie.yaml from the results. 66 | settings.configs.exclude.push(settings_path_str); 67 | Ok(settings) 68 | } 69 | 70 | pub fn get_kube_configs_paths(&self) -> Result> { 71 | let mut paths = HashSet::new(); 72 | for inc in &self.configs.include { 73 | let expanded = expanduser(inc); 74 | for entry in glob(&expanded)? { 75 | paths.insert(entry?); 76 | } 77 | } 78 | 79 | for exc in &self.configs.exclude { 80 | let expanded = expanduser(exc); 81 | for entry in glob(&expanded)? { 82 | paths.remove(&entry?); 83 | } 84 | } 85 | 86 | Ok(paths) 87 | } 88 | } 89 | 90 | #[derive(Debug, Deserialize)] 91 | pub struct Configs { 92 | #[serde(default = "default_include_path")] 93 | pub include: Vec, 94 | #[serde(default = "default_exclude_path")] 95 | pub exclude: Vec, 96 | } 97 | 98 | impl Default for Configs { 99 | fn default() -> Self { 100 | Configs { 101 | include: default_include_path(), 102 | exclude: default_exclude_path(), 103 | } 104 | } 105 | } 106 | 107 | fn default_include_path() -> Vec { 108 | let home_dir = home_dir(); 109 | vec![ 110 | format!("{home_dir}/.kube/config"), 111 | format!("{home_dir}/.kube/*.yml"), 112 | format!("{home_dir}/.kube/*.yaml"), 113 | format!("{home_dir}/.kube/configs/*.yml"), 114 | format!("{home_dir}/.kube/configs/*.yaml"), 115 | format!("{home_dir}/.kube/kubie/*.yml"), 116 | format!("{home_dir}/.kube/kubie/*.yaml"), 117 | ] 118 | } 119 | 120 | fn default_exclude_path() -> Vec { 121 | vec![] 122 | } 123 | 124 | #[derive(Debug, Deserialize)] 125 | pub struct Prompt { 126 | #[serde(default = "def_bool_false")] 127 | pub disable: bool, 128 | #[serde(default = "def_bool_true")] 129 | pub show_depth: bool, 130 | #[serde(default = "def_bool_false")] 131 | pub zsh_use_rps1: bool, 132 | #[serde(default = "def_bool_false")] 133 | pub fish_use_rprompt: bool, 134 | #[serde(default = "def_bool_false")] 135 | pub xonsh_use_right_prompt: bool, 136 | } 137 | 138 | impl Default for Prompt { 139 | fn default() -> Self { 140 | Prompt { 141 | disable: false, 142 | show_depth: true, 143 | zsh_use_rps1: false, 144 | fish_use_rprompt: false, 145 | xonsh_use_right_prompt: false, 146 | } 147 | } 148 | } 149 | 150 | #[derive(Debug, Clone, clap::ValueEnum, Deserialize)] 151 | #[clap(rename_all = "lower")] 152 | #[serde(rename_all = "lowercase")] 153 | #[derive(Default)] 154 | pub enum ContextHeaderBehavior { 155 | #[default] 156 | Auto, 157 | Always, 158 | Never, 159 | } 160 | 161 | impl ContextHeaderBehavior { 162 | pub fn should_print_headers(&self) -> bool { 163 | match self { 164 | ContextHeaderBehavior::Auto => io::stdout().is_terminal(), 165 | ContextHeaderBehavior::Always => true, 166 | ContextHeaderBehavior::Never => false, 167 | } 168 | } 169 | } 170 | 171 | #[derive(Debug, Deserialize, Default)] 172 | pub struct Behavior { 173 | #[serde(default)] 174 | pub validate_namespaces: ValidateNamespacesBehavior, 175 | #[serde(default)] 176 | pub print_context_in_exec: ContextHeaderBehavior, 177 | } 178 | 179 | #[derive(Debug, Deserialize, Default)] 180 | #[serde(rename_all = "lowercase")] 181 | pub enum ValidateNamespacesBehavior { 182 | #[default] 183 | True, 184 | False, 185 | Partial, 186 | } 187 | 188 | impl ValidateNamespacesBehavior { 189 | pub fn can_list_namespaces(&self) -> bool { 190 | match self { 191 | ValidateNamespacesBehavior::True | ValidateNamespacesBehavior::Partial => true, 192 | ValidateNamespacesBehavior::False => false, 193 | } 194 | } 195 | } 196 | 197 | #[derive(Debug, Deserialize, Default)] 198 | pub struct Hooks { 199 | #[serde(default)] 200 | pub start_ctx: String, 201 | #[serde(default)] 202 | pub stop_ctx: String, 203 | } 204 | 205 | fn def_bool_true() -> bool { 206 | true 207 | } 208 | 209 | fn def_bool_false() -> bool { 210 | false 211 | } 212 | 213 | #[test] 214 | fn test_expanduser() { 215 | assert_eq!( 216 | expanduser("~/hello/world/*.foo"), 217 | format!("{}/hello/world/*.foo", home_dir()) 218 | ); 219 | } 220 | -------------------------------------------------------------------------------- /src/shell/bash.rs: -------------------------------------------------------------------------------- 1 | use std::io::{BufWriter, Write}; 2 | use std::process::Command; 3 | 4 | use anyhow::Result; 5 | 6 | use super::ShellSpawnInfo; 7 | 8 | pub fn spawn_shell(info: &ShellSpawnInfo) -> Result<()> { 9 | let temp_rc_file = tempfile::Builder::new() 10 | .prefix("kubie-bashrc") 11 | .suffix(".bash") 12 | .tempfile()?; 13 | let mut temp_rc_file_buf = BufWriter::new(temp_rc_file.as_file()); 14 | 15 | write!( 16 | temp_rc_file_buf, 17 | r#" 18 | KUBIE_LOGIN_SHELL=0 19 | if [[ "$OSTYPE" == "darwin"* ]] ; then 20 | KUBIE_LOGIN_SHELL=1 21 | fi 22 | 23 | # Reference for loading behavior 24 | # https://shreevatsa.wordpress.com/2008/03/30/zshbash-startup-files-loading-order-bashrc-zshrc-etc/ 25 | 26 | 27 | if [[ "$KUBIE_LOGIN_SHELL" == "1" ]] ; then 28 | if [[ -f "/etc/profile" ]] ; then 29 | source "/etc/profile" 30 | fi 31 | 32 | if [[ -f "$HOME/.bash_profile" ]] ; then 33 | source "$HOME/.bash_profile" 34 | elif [[ -f "$HOME/.bash_login" ]] ; then 35 | source "$HOME/.bash_login" 36 | elif [[ -f "$HOME/.profile" ]] ; then 37 | source "$HOME/.profile" 38 | fi 39 | else 40 | if [[ -f "/etc/bash.bashrc" ]] ; then 41 | source "/etc/bash.bashrc" 42 | fi 43 | 44 | if [[ -f "$HOME/.bashrc" ]] ; then 45 | source "$HOME/.bashrc" 46 | fi 47 | fi 48 | 49 | function __kubie_cmd_pre_exec__() {{ 50 | export KUBECONFIG="$KUBIE_KUBECONFIG" 51 | }} 52 | 53 | trap '__kubie_cmd_pre_exec__' DEBUG 54 | "# 55 | )?; 56 | 57 | if !info.settings.prompt.disable { 58 | write!( 59 | temp_rc_file_buf, 60 | r#" 61 | KUBIE_PROMPT='{}' 62 | PS1="$KUBIE_PROMPT $PS1" 63 | unset KUBIE_PROMPT 64 | "#, 65 | info.prompt, 66 | )?; 67 | } 68 | 69 | if !info.settings.hooks.start_ctx.is_empty() { 70 | write!(temp_rc_file_buf, "{}", info.settings.hooks.start_ctx)?; 71 | } 72 | 73 | temp_rc_file_buf.flush()?; 74 | 75 | let mut cmd = Command::new("bash"); 76 | cmd.arg("--rcfile"); 77 | cmd.arg(temp_rc_file.path()); 78 | info.env_vars.apply(&mut cmd); 79 | 80 | let mut child = cmd.spawn()?; 81 | child.wait()?; 82 | 83 | if !info.settings.hooks.start_ctx.is_empty() { 84 | let temp_exit_hook_file = tempfile::Builder::new() 85 | .prefix("kubie-bash-exit-hook") 86 | .suffix(".bash") 87 | .tempfile()?; 88 | let mut temp_exit_hook_file_buf = BufWriter::new(temp_exit_hook_file.as_file()); 89 | 90 | write!(temp_exit_hook_file_buf, "{}", info.settings.hooks.stop_ctx)?; 91 | 92 | temp_exit_hook_file_buf.flush()?; 93 | let mut exit_cmd = Command::new("bash"); 94 | exit_cmd.arg(temp_exit_hook_file.path()); 95 | info.env_vars.apply(&mut exit_cmd); 96 | 97 | let mut child = exit_cmd.spawn()?; 98 | child.wait()?; 99 | } 100 | 101 | Ok(()) 102 | } 103 | -------------------------------------------------------------------------------- /src/shell/detect.rs: -------------------------------------------------------------------------------- 1 | use std::process::Command; 2 | use std::str; 3 | 4 | use anyhow::{anyhow, Context, Result}; 5 | 6 | #[derive(Copy, Clone, Debug, PartialEq, Eq)] 7 | pub enum ShellKind { 8 | Bash, 9 | Fish, 10 | Xonsh, 11 | Zsh, 12 | Nu, 13 | } 14 | 15 | impl ShellKind { 16 | pub fn from_str(name: &str) -> Option { 17 | Some(match name { 18 | "bash" | "dash" => ShellKind::Bash, 19 | "fish" => ShellKind::Fish, 20 | "xonsh" | "python" => ShellKind::Xonsh, 21 | "zsh" => ShellKind::Zsh, 22 | "nu" => ShellKind::Nu, 23 | _ => return None, 24 | }) 25 | } 26 | } 27 | 28 | fn run_ps(args: &[&str]) -> Result> { 29 | let result = Command::new("ps").args(args).output().context("Could not spawn ps")?; 30 | 31 | if !result.status.success() { 32 | let stderr = str::from_utf8(&result.stderr).unwrap_or("Could not decode stderr of ps as utf-8"); 33 | return Err(anyhow!("Error calling ps: {}", stderr)); 34 | } 35 | 36 | let text = str::from_utf8(&result.stdout)?; 37 | Ok(text.split('\n').filter(|x| !x.is_empty()).map(String::from).collect()) 38 | } 39 | 40 | fn parent_of(pid: &str) -> Result { 41 | let lines = run_ps(&["-o", "ppid=", pid])?; 42 | lines 43 | .into_iter() 44 | .next() 45 | .map(|x| x.trim().to_string()) 46 | .ok_or_else(|| anyhow!("Could not get parent pid of pid={}", pid)) 47 | } 48 | 49 | fn command_of(pid: &str) -> Result { 50 | let lines = run_ps(&["-o", "args=", pid])?; 51 | lines 52 | .into_iter() 53 | .next() 54 | .ok_or_else(|| anyhow!("Could not get command of pid={}", pid)) 55 | } 56 | 57 | fn parse_command(cmd: &str) -> &str { 58 | let first_space = cmd.find(' ').unwrap_or(cmd.len()); 59 | let binary_path = &cmd[..first_space]; 60 | let last_path_sep = binary_path.rfind('/').map(|x| x + 1).unwrap_or(0); 61 | let binary = &binary_path[last_path_sep..]; 62 | binary 63 | .trim_start_matches('-') 64 | .trim_end_matches(|c: char| c.is_ascii_digit() || c == '.') 65 | } 66 | 67 | /// Detect from which kind of shell kubie was spawned. 68 | /// 69 | /// This function walks up the process tree and finds all the ancestors to kubie. 70 | /// If any of kubie's ancestor is a known shell, we have found which shell is in 71 | /// use. 72 | /// 73 | /// This functions depends on the `ps` command being installed and available in 74 | /// the PATH variable. 75 | /// 76 | /// The SHELL environment variable corresponds to the user's configured SHELL, not 77 | /// the shell currently in use. 78 | pub fn detect() -> Result { 79 | let kubie_pid = format!("{}", std::process::id()); 80 | let mut parent_pid = parent_of(&kubie_pid)?; 81 | loop { 82 | if parent_pid == "1" { 83 | return Err(anyhow!("Could not detect shell in use")); 84 | } 85 | 86 | let cmd = command_of(&parent_pid)?; 87 | let name = parse_command(&cmd); 88 | if let Some(kind) = ShellKind::from_str(name) { 89 | return Ok(kind); 90 | } 91 | 92 | parent_pid = parent_of(&parent_pid)?; 93 | } 94 | } 95 | 96 | #[test] 97 | fn test_parse_command_simple() { 98 | assert_eq!(parse_command("bash"), "bash"); 99 | } 100 | 101 | #[test] 102 | fn test_parse_command_with_args() { 103 | assert_eq!(parse_command("bash --rcfile hello.sh"), "bash"); 104 | } 105 | 106 | #[test] 107 | fn test_parse_command_with_path() { 108 | assert_eq!(parse_command("/bin/bash"), "bash"); 109 | } 110 | 111 | #[test] 112 | fn test_parse_command_with_path_and_args() { 113 | assert_eq!(parse_command("/bin/bash --rcfile hello.sh"), "bash"); 114 | } 115 | 116 | #[test] 117 | fn test_parse_command_login_shell() { 118 | assert_eq!(parse_command("-zsh"), "zsh"); 119 | } 120 | 121 | #[test] 122 | fn test_parse_command_versioned_intepreter() { 123 | assert_eq!(parse_command("python3.8"), "python"); 124 | } 125 | 126 | #[test] 127 | fn test_parse_command_nu() { 128 | assert_eq!(parse_command("/bin/nu"), "nu"); 129 | } 130 | -------------------------------------------------------------------------------- /src/shell/fish.rs: -------------------------------------------------------------------------------- 1 | use std::process::Command; 2 | 3 | use anyhow::Result; 4 | 5 | use super::ShellSpawnInfo; 6 | 7 | pub fn spawn_shell(info: &ShellSpawnInfo) -> Result<()> { 8 | let mut cmd = Command::new("fish"); 9 | // run fish as an interactive login shell 10 | cmd.arg("-ilC"); 11 | cmd.arg(format!( 12 | r#" 13 | # Set the proper KUBECONFIG variable before each command runs, 14 | # to prevent the user from overwriting it. 15 | function kubie_preexec --on-event fish_preexec 16 | set -xg KUBECONFIG "$KUBIE_KUBECONFIG" 17 | end 18 | 19 | if test "$KUBIE_PROMPT_DISABLE" = "0" 20 | # The general idea behind the prompt substitions is to save the existing 21 | # prompt's output _before_ anything else is run. This is important since the 22 | # existing prompt might be dependent on say the status of the executed command. 23 | 24 | if test "$KUBIE_FISH_USE_RPROMPT" = "1" 25 | functions -q fish_right_prompt 26 | and functions --copy fish_right_prompt fish_right_prompt_original 27 | or function fish_right_prompt_original; end 28 | 29 | function fish_right_prompt 30 | set -l original (fish_right_prompt_original) 31 | 32 | # Fish's right prompt does not support newlines, so there's no point in 33 | # iterating through the (potentially) existing prompt's lines. 34 | printf '%s %s' (string unescape {prompt}) $original 35 | end 36 | else 37 | functions --copy fish_prompt fish_prompt_original 38 | function fish_prompt 39 | set -l original (fish_prompt_original) 40 | 41 | printf '%s ' (string unescape {prompt}) 42 | 43 | # Due to idiosyncrasies with the way fish is managing newlines in 44 | # process substitions, each line needs to be printed separately 45 | # to mirror the existing output. For more details, 46 | # see https://github.com/fish-shell/fish-shell/issues/159. 47 | for line in $original 48 | echo -e $line 49 | end 50 | end 51 | end 52 | end 53 | "#, 54 | prompt = info.prompt, 55 | )); 56 | info.env_vars.apply(&mut cmd); 57 | 58 | let mut child = cmd.spawn()?; 59 | child.wait()?; 60 | Ok(()) 61 | } 62 | -------------------------------------------------------------------------------- /src/shell/mod.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | use std::ffi::OsString; 3 | use std::process::Command; 4 | 5 | use anyhow::{anyhow, Result}; 6 | 7 | use self::detect::{detect, ShellKind}; 8 | use crate::kubeconfig::KubeConfig; 9 | use crate::session::Session; 10 | use crate::settings::Settings; 11 | use crate::state; 12 | use crate::vars; 13 | 14 | mod bash; 15 | mod detect; 16 | mod fish; 17 | mod nu; 18 | mod prompt; 19 | mod xonsh; 20 | mod zsh; 21 | 22 | pub struct EnvVars<'n> { 23 | vars: HashMap<&'n str, OsString>, 24 | } 25 | 26 | impl<'n> EnvVars<'n> { 27 | pub fn new() -> EnvVars<'n> { 28 | EnvVars { vars: HashMap::new() } 29 | } 30 | 31 | pub fn insert(&mut self, name: &'n str, value: impl Into) { 32 | self.vars.insert(name, value.into()); 33 | } 34 | 35 | pub fn apply(&self, cmd: &mut Command) { 36 | for (name, value) in &self.vars { 37 | cmd.env(name, value); 38 | } 39 | } 40 | } 41 | 42 | pub struct ShellSpawnInfo<'s, 'n> { 43 | settings: &'s Settings, 44 | env_vars: EnvVars<'n>, 45 | prompt: String, 46 | } 47 | 48 | pub fn spawn_shell(settings: &Settings, config: KubeConfig, session: &Session) -> Result<()> { 49 | let kind = match &settings.shell { 50 | Some(shell) => ShellKind::from_str(shell).ok_or_else(|| anyhow!("Invalid shell setting: {}", shell))?, 51 | None => detect()?, 52 | }; 53 | 54 | let temp_config_file = tempfile::Builder::new() 55 | .prefix("kubie-config") 56 | .suffix(".yaml") 57 | .tempfile()?; 58 | config.write_to_file(temp_config_file.path())?; 59 | 60 | let temp_session_file = tempfile::Builder::new() 61 | .prefix("kubie-session") 62 | .suffix(".json") 63 | .tempfile()?; 64 | session.save(Some(temp_session_file.path()))?; 65 | 66 | let depth = vars::get_depth(); 67 | let next_depth = depth + 1; 68 | 69 | let mut env_vars = EnvVars::new(); 70 | 71 | // Pre-insert the KUBECONFIG variable into the shell. 72 | // This will make sure any shell plugins/add-ons which require this env variable 73 | // will have it available at the beginninng of the .rc file 74 | env_vars.insert("KUBECONFIG", temp_config_file.path()); 75 | env_vars.insert("KUBIE_ACTIVE", "1"); 76 | env_vars.insert("KUBIE_DEPTH", next_depth.to_string()); 77 | env_vars.insert("KUBIE_KUBECONFIG", temp_config_file.path()); 78 | env_vars.insert("KUBIE_SESSION", temp_session_file.path()); 79 | env_vars.insert("KUBIE_STATE", state::paths::state()); 80 | 81 | env_vars.insert("KUBIE_PROMPT_DISABLE", if settings.prompt.disable { "1" } else { "0" }); 82 | env_vars.insert( 83 | "KUBIE_ZSH_USE_RPS1", 84 | if settings.prompt.zsh_use_rps1 { "1" } else { "0" }, 85 | ); 86 | env_vars.insert( 87 | "KUBIE_FISH_USE_RPROMPT", 88 | if settings.prompt.fish_use_rprompt { "1" } else { "0" }, 89 | ); 90 | env_vars.insert( 91 | "KUBIE_XONSH_USE_RIGHT_PROMPT", 92 | if settings.prompt.xonsh_use_right_prompt { 93 | "1" 94 | } else { 95 | "0" 96 | }, 97 | ); 98 | 99 | match kind { 100 | ShellKind::Bash => { 101 | env_vars.insert("KUBIE_SHELL", "bash"); 102 | } 103 | ShellKind::Fish => { 104 | env_vars.insert("KUBIE_SHELL", "fish"); 105 | } 106 | ShellKind::Xonsh => { 107 | env_vars.insert("KUBIE_SHELL", "xonsh"); 108 | } 109 | ShellKind::Zsh => { 110 | env_vars.insert("KUBIE_SHELL", "zsh"); 111 | } 112 | ShellKind::Nu => { 113 | env_vars.insert("KUBIE_SHELL", "nu"); 114 | } 115 | } 116 | 117 | let info = ShellSpawnInfo { 118 | settings, 119 | env_vars, 120 | prompt: prompt::generate_ps1(settings, next_depth, kind), 121 | }; 122 | 123 | match kind { 124 | ShellKind::Bash => bash::spawn_shell(&info), 125 | ShellKind::Fish => fish::spawn_shell(&info), 126 | ShellKind::Xonsh => xonsh::spawn_shell(&info), 127 | ShellKind::Zsh => zsh::spawn_shell(&info), 128 | ShellKind::Nu => nu::spawn_shell(&info), 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /src/shell/nu.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Result; 2 | use std::process::Command; 3 | 4 | use crate::shell::ShellSpawnInfo; 5 | 6 | pub fn spawn_shell(info: &ShellSpawnInfo) -> Result<()> { 7 | // let dir = tempdir()?; 8 | 9 | let mut cmd = Command::new("nu"); 10 | let mut args = "".to_string(); 11 | 12 | for (name, value) in &info.env_vars.vars { 13 | args.push_str(&format!( 14 | r#"$env.{} = '{}';"#, 15 | name, 16 | value.as_os_str().to_str().unwrap() 17 | )); 18 | 19 | if String::from("KUBIE_PROMPT_DISABLE").eq(name) && value == "0" { 20 | let mut _prompt = info.prompt.clone(); 21 | // TODO: This is improvable, but it works for now 22 | _prompt = _prompt 23 | .replace("\\[\\e[31m\\]", "") 24 | .replace("\\[\\e[32m\\]", "") 25 | .replace("\\[\\e[0m\\]", "") 26 | .replace('$', ""); 27 | let prompt = format!( 28 | r#"$env.PROMPT_COMMAND = {{ || $"{prompt}\n(create_left_prompt)" }};"#, 29 | prompt = _prompt 30 | ); 31 | args.push_str(prompt.as_str()); 32 | } 33 | } 34 | cmd.arg("-e"); 35 | cmd.arg(args); 36 | 37 | let mut child = cmd.spawn()?; 38 | child.wait()?; 39 | Ok(()) 40 | } 41 | -------------------------------------------------------------------------------- /src/shell/prompt.rs: -------------------------------------------------------------------------------- 1 | use std::env; 2 | use std::fmt::{self, Display}; 3 | 4 | use crate::settings::Settings; 5 | use crate::shell::ShellKind; 6 | 7 | struct Command { 8 | content: String, 9 | shell_kind: ShellKind, 10 | } 11 | 12 | impl Command { 13 | fn new(content: impl Into, shell_kind: ShellKind) -> Command { 14 | Command { 15 | content: content.into(), 16 | shell_kind, 17 | } 18 | } 19 | } 20 | 21 | impl fmt::Display for Command { 22 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 23 | match self.shell_kind { 24 | ShellKind::Fish => write!(f, "({})", self.content), 25 | _ => write!(f, "$({})", self.content), 26 | } 27 | } 28 | } 29 | 30 | struct Color { 31 | color: u32, 32 | content: D, 33 | shell_kind: ShellKind, 34 | } 35 | 36 | impl Color { 37 | fn new(color: u32, content: D, shell_kind: ShellKind) -> Color { 38 | Color { 39 | color, 40 | content, 41 | shell_kind, 42 | } 43 | } 44 | } 45 | 46 | // FIXME: @Miuler Validate this implementation for nu shell 47 | impl Color 48 | where 49 | D: Display, 50 | { 51 | fn isolate(&self, f: &mut fmt::Formatter, content: E) -> fmt::Result 52 | where 53 | E: Display, 54 | { 55 | match self.shell_kind { 56 | ShellKind::Fish | ShellKind::Xonsh => write!(f, "{content}"), 57 | ShellKind::Zsh => write!(f, "%{{{content}%}}"), 58 | ShellKind::Bash => write!(f, "\\[{content}\\]"), 59 | _ => write!(f, "{content}"), 60 | } 61 | } 62 | 63 | fn start_color(&self, f: &mut fmt::Formatter, color: u32) -> fmt::Result { 64 | match self.shell_kind { 65 | ShellKind::Xonsh => self.isolate(f, format!("\\033[{color}m")), 66 | _ => self.isolate(f, format!("\\e[{color}m")), 67 | } 68 | } 69 | 70 | fn end_color(&self, f: &mut fmt::Formatter) -> fmt::Result { 71 | match self.shell_kind { 72 | ShellKind::Xonsh => self.isolate(f, "\\033[0m"), 73 | _ => self.isolate(f, "\\e[0m"), 74 | } 75 | } 76 | } 77 | 78 | impl fmt::Display for Color 79 | where 80 | D: Display, 81 | { 82 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 83 | self.start_color(f, self.color)?; 84 | write!(f, "{}", self.content)?; 85 | self.end_color(f)?; 86 | Ok(()) 87 | } 88 | } 89 | 90 | const RED: u32 = 31; 91 | const GREEN: u32 = 32; 92 | const BLUE: u32 = 34; 93 | 94 | /// Generates a PS1 string that shows the current context, namespace and depth. 95 | /// 96 | /// Makes sure to protect the escape sequences so that the shell will not count the escape 97 | /// sequences in the length calculation of the prompt. 98 | pub fn generate_ps1(settings: &Settings, depth: u32, shell_kind: ShellKind) -> String { 99 | let current_exe_path = env::current_exe().expect("Could not get own binary path"); 100 | let current_exe_path_str = current_exe_path.to_str().expect("Binary path is not unicode"); 101 | 102 | let mut parts = vec![]; 103 | parts.push( 104 | Color::new( 105 | RED, 106 | Command::new(format!("{current_exe_path_str} info ctx"), shell_kind), 107 | shell_kind, 108 | ) 109 | .to_string(), 110 | ); 111 | parts.push( 112 | Color::new( 113 | GREEN, 114 | Command::new(format!("{current_exe_path_str} info ns"), shell_kind), 115 | shell_kind, 116 | ) 117 | .to_string(), 118 | ); 119 | if settings.prompt.show_depth && depth > 1 { 120 | parts.push(Color::new(BLUE, depth, shell_kind).to_string()); 121 | } 122 | 123 | format!("[{}]", parts.join("|")) 124 | } 125 | -------------------------------------------------------------------------------- /src/shell/xonsh.rs: -------------------------------------------------------------------------------- 1 | use std::io::{BufWriter, Write}; 2 | use std::process::Command; 3 | 4 | use anyhow::Result; 5 | 6 | use super::ShellSpawnInfo; 7 | 8 | pub fn spawn_shell(info: &ShellSpawnInfo) -> Result<()> { 9 | let temp_rc_file = tempfile::Builder::new() 10 | .prefix("kubie-xonshrc") 11 | .suffix(".xsh") 12 | .tempfile()?; 13 | let mut temp_rc_file_buf = BufWriter::new(temp_rc_file.as_file()); 14 | 15 | write!( 16 | temp_rc_file_buf, 17 | r#" 18 | # https://xon.sh/xonshrc.html 19 | from pathlib import Path 20 | 21 | files = [ 22 | "/etc/xonshrc", 23 | "~/.xonshrc", 24 | "~/.config/xonsh/rc.xsh", 25 | ] 26 | for file in files: 27 | if Path(file).is_file(): 28 | source @(file) 29 | if Path("~/.config/xonsh/rc.d").is_dir(): 30 | for file in path.glob('*.xsh'): 31 | source @(file) 32 | 33 | @events.on_precommand 34 | def __kubie_cmd_pre_exec__(cmd): 35 | $KUBECONFIG = $KUBIE_KUBECONFIG 36 | "# 37 | )?; 38 | 39 | if !info.settings.prompt.disable { 40 | write!( 41 | temp_rc_file_buf, 42 | r#" 43 | $KUBIE_PROMPT='{}' 44 | import re 45 | 46 | # Fanciful prompt-command replacement as xonsh forces the use of PROMPT_FIELDS 47 | for match in re.finditer(r'\$\(([^)]*)\)', $KUBIE_PROMPT): 48 | command = match.group(1) 49 | name = command.split().pop() 50 | $PROMPT_FIELDS[name] = evalx(f'lambda: $({{command}}).strip()') 51 | $KUBIE_PROMPT = $KUBIE_PROMPT.replace(f'$({{command}})', '{{' + name + '}}') 52 | 53 | if $KUBIE_XONSH_USE_RIGHT_PROMPT == "1": 54 | $RIGHT_PROMPT = $KUBIE_PROMPT + $RIGHT_PROMPT 55 | else: 56 | $PROMPT = $KUBIE_PROMPT + $PROMPT 57 | 58 | del $KUBIE_PROMPT 59 | "#, 60 | info.prompt, 61 | )?; 62 | } 63 | 64 | temp_rc_file_buf.flush()?; 65 | 66 | let mut cmd = Command::new("xonsh"); 67 | cmd.arg("--rc"); 68 | cmd.arg(temp_rc_file.path()); 69 | info.env_vars.apply(&mut cmd); 70 | 71 | let mut child = cmd.spawn()?; 72 | child.wait()?; 73 | 74 | Ok(()) 75 | } 76 | -------------------------------------------------------------------------------- /src/shell/zsh.rs: -------------------------------------------------------------------------------- 1 | use std::fs::File; 2 | use std::io::{BufWriter, Write}; 3 | use std::process::Command; 4 | 5 | use anyhow::{Context, Result}; 6 | use tempfile::tempdir; 7 | 8 | use super::ShellSpawnInfo; 9 | 10 | pub fn spawn_shell(info: &ShellSpawnInfo) -> Result<()> { 11 | let dir = tempdir()?; 12 | { 13 | let zshrc_path = dir.path().join(".zshrc"); 14 | let zshrc = File::create(zshrc_path).context("Could not open zshrc file")?; 15 | let mut zshrc_buf = BufWriter::new(zshrc); 16 | write!( 17 | zshrc_buf, 18 | r#" 19 | # Reference for loading behavior 20 | # https://shreevatsa.wordpress.com/2008/03/30/zshbash-startup-files-loading-order-bashrc-zshrc-etc/ 21 | 22 | if [[ -f "/etc/zshenv" ]] ; then 23 | source "/etc/zshenv" 24 | elif [[ -f "/etc/zsh/zshenv" ]] ; then 25 | source "/etc/zsh/zshenv" 26 | fi 27 | 28 | if [[ -f "$HOME/.zshenv" ]] ; then 29 | tmp_ZDOTDIR=$ZDOTDIR 30 | source "$HOME/.zshenv" 31 | # If the user has overridden $ZDOTDIR, we save that in $_KUBIE_USER_ZDOTDIR for later reference 32 | # and reset $ZDOTDIR 33 | if [[ "$tmp_ZDOTDIR" != "$ZDOTDIR" ]]; then 34 | _KUBIE_USER_ZDOTDIR=$ZDOTDIR 35 | ZDOTDIR=$tmp_ZDOTDIR 36 | unset tmp_ZDOTDIR 37 | fi 38 | fi 39 | 40 | # If a zsh_history file exists, copy it over before zsh initialization so history is maintained 41 | if [[ -f "$HOME/.zsh_history" ]] ; then 42 | cp $HOME/.zsh_history $ZDOTDIR 43 | fi 44 | 45 | KUBIE_LOGIN_SHELL=0 46 | if [[ "$OSTYPE" == "darwin"* ]] ; then 47 | KUBIE_LOGIN_SHELL=1 48 | fi 49 | 50 | if [[ -f "/etc/zprofile" && "$KUBIE_LOGIN_SHELL" == "1" ]] ; then 51 | source "/etc/zprofile" 52 | elif [[ -f "/etc/zsh/zprofile" && "$KUBIE_LOGIN_SHELL" == "1" ]] ; then 53 | source "/etc/zsh/zprofile" 54 | fi 55 | 56 | if [[ -f "${{_KUBIE_USER_ZDOTDIR:-$HOME}}/.zprofile" && "$KUBIE_LOGIN_SHELL" == "1" ]] ; then 57 | source "${{_KUBIE_USER_ZDOTDIR:-$HOME}}/.zprofile" 58 | fi 59 | 60 | if [[ -f "/etc/zshrc" ]] ; then 61 | source "/etc/zshrc" 62 | elif [[ -f "/etc/zsh/zshrc" ]] ; then 63 | source "/etc/zsh/zshrc" 64 | fi 65 | 66 | if [[ -f "${{_KUBIE_USER_ZDOTDIR:-$HOME}}/.zshrc" ]] ; then 67 | ZDOTDIR=$_KUBIE_USER_ZDOTDIR \ 68 | source "${{_KUBIE_USER_ZDOTDIR:-$HOME}}/.zshrc" 69 | fi 70 | 71 | if [[ -f "/etc/zlogin" && "$KUBIE_LOGIN_SHELL" == "1" ]] ; then 72 | source "/etc/zlogin" 73 | elif [[ -f "/etc/zsh/zlogin" && "$KUBIE_LOGIN_SHELL" == "1" ]] ; then 74 | source "/etc/zsh/zlogin" 75 | fi 76 | 77 | if [[ -f "${{_KUBIE_USER_ZDOTDIR:-$HOME}}/.zlogin" && "$KUBIE_LOGIN_SHELL" == "1" ]] ; then 78 | source "${{_KUBIE_USER_ZDOTDIR:-$HOME}}/.zlogin" 79 | fi 80 | 81 | unset _KUBIE_USER_ZDOTDIR 82 | 83 | autoload -Uz add-zsh-hook 84 | 85 | # This function sets the proper KUBECONFIG variable before a command runs, 86 | # in case something overwrote it. 87 | function __kubie_cmd_pre_exec__() {{ 88 | export KUBECONFIG="$KUBIE_KUBECONFIG" 89 | }} 90 | 91 | add-zsh-hook preexec __kubie_cmd_pre_exec__ 92 | "#, 93 | )?; 94 | 95 | if !info.settings.prompt.disable { 96 | write!( 97 | zshrc_buf, 98 | r#" 99 | # Activate prompt substitution. 100 | setopt PROMPT_SUBST 101 | 102 | # This function fixes the prompt via a precmd hook. 103 | function __kubie_cmd_pre_cmd__() {{ 104 | local KUBIE_PROMPT=$'{}' 105 | 106 | # If KUBIE_ZSH_USE_RPS1 is set, we use RPS1 instead of PS1. 107 | if [[ "$KUBIE_ZSH_USE_RPS1" == "1" ]] ; then 108 | 109 | # Avoid modifying RPS1 again if the RPS1 has not been reset. 110 | if [[ "$RPS1" != *"$KUBIE_PROMPT"* ]] ; then 111 | 112 | # If RPS1 is empty, we do not seperate with a space. 113 | if [[ -z "$RPS1" ]] ; then 114 | RPS1="$KUBIE_PROMPT" 115 | else 116 | RPS1="$KUBIE_PROMPT $RPS1" 117 | fi 118 | fi 119 | else 120 | # Avoid modifying PS1 again if the PS1 has not been reset. 121 | if [[ "$PS1" != *"$KUBIE_PROMPT"* ]] ; then 122 | PS1="$KUBIE_PROMPT $PS1" 123 | fi 124 | fi 125 | }} 126 | 127 | # When promptinit is activated, a precmd hook which updates PS1 is installed. 128 | # In order to inject the kubie PS1 when promptinit is activated, we must 129 | # also add our own precmd hook which modifies PS1 after promptinit themes. 130 | add-zsh-hook precmd __kubie_cmd_pre_cmd__ 131 | "#, 132 | info.prompt 133 | )?; 134 | } 135 | 136 | if !info.settings.hooks.start_ctx.is_empty() { 137 | write!(zshrc_buf, "{}", info.settings.hooks.start_ctx)?; 138 | } 139 | } 140 | 141 | let mut cmd = Command::new("zsh"); 142 | cmd.env("ZDOTDIR", dir.path()); 143 | info.env_vars.apply(&mut cmd); 144 | 145 | let mut child = cmd.spawn()?; 146 | child.wait()?; 147 | 148 | if !info.settings.hooks.stop_ctx.is_empty() { 149 | let temp_exit_hook_file = tempfile::Builder::new() 150 | .prefix("kubie-zsh-exit-hook") 151 | .suffix(".zsh") 152 | .tempfile()?; 153 | let mut temp_exit_hook_file_buf = BufWriter::new(temp_exit_hook_file.as_file()); 154 | 155 | write!(temp_exit_hook_file_buf, "{}", info.settings.hooks.stop_ctx)?; 156 | 157 | temp_exit_hook_file_buf.flush()?; 158 | let mut exit_cmd = Command::new("zsh"); 159 | exit_cmd.arg(temp_exit_hook_file.path()); 160 | info.env_vars.apply(&mut exit_cmd); 161 | 162 | let mut child = exit_cmd.spawn()?; 163 | child.wait()?; 164 | } 165 | 166 | Ok(()) 167 | } 168 | -------------------------------------------------------------------------------- /src/state.rs: -------------------------------------------------------------------------------- 1 | use std::fs::DirBuilder; 2 | use std::{collections::HashMap, panic::UnwindSafe}; 3 | 4 | use anyhow::{Context, Result}; 5 | use serde::{Deserialize, Serialize}; 6 | 7 | use crate::ioutil; 8 | 9 | pub mod paths { 10 | use std::path::{Path, PathBuf}; 11 | 12 | use lazy_static::lazy_static; 13 | 14 | lazy_static! { 15 | static ref KUBIE_DATA_DIR: PathBuf = { 16 | let base_data_dir = dirs::data_local_dir().expect("Could not get local data dir"); 17 | base_data_dir.join("kubie") 18 | }; 19 | static ref KUBIE_STATE_PATH: PathBuf = KUBIE_DATA_DIR.join("state.json"); 20 | static ref KUBIE_STATE_LOCK_PATH: PathBuf = KUBIE_DATA_DIR.join(".state.json.lock"); 21 | } 22 | 23 | #[inline] 24 | pub fn data_dir() -> &'static Path { 25 | &KUBIE_DATA_DIR 26 | } 27 | 28 | #[inline] 29 | pub fn state() -> &'static Path { 30 | &KUBIE_STATE_PATH 31 | } 32 | 33 | #[inline] 34 | pub fn state_lock() -> &'static Path { 35 | &KUBIE_STATE_LOCK_PATH 36 | } 37 | } 38 | 39 | #[derive(Debug, Default, Deserialize, Serialize)] 40 | pub struct State { 41 | /// This map stores the last namespace in which a context was used, in order to restore the namespace 42 | /// when the context is entered again. 43 | /// 44 | /// The key represents the name of the context and the value is the namespace's name. 45 | pub namespace_history: HashMap>, 46 | } 47 | 48 | impl State { 49 | /// Loads the state.json from the filesystem, waiting for a file lock to ensure no other 50 | /// concurrent Kubie processes are accessing/writing the file at the same time. 51 | pub fn load() -> Result { 52 | Self::access(Ok) 53 | } 54 | 55 | /// Takes a closure that allows for modifications of the state. Automatically handles 56 | /// locking/unlocking and saving after execution of the closure. 57 | pub fn modify Result<()> + UnwindSafe>(func: F) -> Result<()> { 58 | Self::access(|mut state| { 59 | func(&mut state)?; 60 | state.save()?; 61 | Ok(()) 62 | }) 63 | } 64 | 65 | fn access Result + UnwindSafe>(func: F) -> Result { 66 | // Create directory where state and lock will live. 67 | DirBuilder::new() 68 | .recursive(true) 69 | .create(paths::data_dir()) 70 | .with_context(|| format!("Could not create data dir: {}", paths::data_dir().display()))?; 71 | 72 | ioutil::file_lock(paths::state_lock(), || { 73 | State::read_and_parse() 74 | .with_context(|| format!("Could not load state file: {}", paths::state().display())) 75 | .and_then(func) 76 | }) 77 | } 78 | 79 | fn read_and_parse() -> Result { 80 | if !paths::state().exists() { 81 | return Ok(State::default()); 82 | } 83 | ioutil::read_json(paths::state()) 84 | .with_context(|| format!("Failed to read state from '{}'", paths::state().display())) 85 | } 86 | 87 | fn save(&self) -> Result<()> { 88 | ioutil::write_json(paths::state(), self) 89 | .with_context(|| format!("Failed to write state to '{}'", paths::state().display())) 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /src/vars.rs: -------------------------------------------------------------------------------- 1 | use std::env; 2 | use std::path::PathBuf; 3 | 4 | use anyhow::{anyhow, Result}; 5 | 6 | /// Get the current depth of shells. 7 | pub fn get_depth() -> u32 { 8 | env::var("KUBIE_DEPTH") 9 | .ok() 10 | .and_then(|s| s.parse::().ok()) 11 | .unwrap_or(0) 12 | } 13 | 14 | /// Check if we're in a kubie shell. 15 | pub fn is_kubie_active() -> bool { 16 | let active = env::var("KUBIE_ACTIVE").unwrap_or_else(|_| "0".into()); 17 | active == "1" 18 | } 19 | 20 | /// Ensure that we're inside a kubie shell, returning an error if we aren't. 21 | pub fn ensure_kubie_active() -> Result<()> { 22 | if !is_kubie_active() { 23 | return Err(anyhow!("Not in a kubie shell!")); 24 | } 25 | Ok(()) 26 | } 27 | 28 | pub fn get_session_path() -> Option { 29 | env::var_os("KUBIE_SESSION").map(PathBuf::from) 30 | } 31 | --------------------------------------------------------------------------------