├── .gitignore ├── .vimspector.json ├── Cargo.lock ├── Cargo.toml ├── README.md ├── src ├── devcontainer.rs ├── devcontainer_tests.rs ├── errors.rs ├── main.rs ├── mount_from_str.rs ├── mount_from_str_tests.rs ├── project.rs ├── project_tests.rs ├── settings.rs └── settings_compose_model.rs └── test_files ├── application └── .devcontainer │ └── devcontainer.json ├── build └── .devcontainer │ ├── Dockerfile │ └── devcontainer.json ├── docker-compose └── .devcontainer │ ├── devcontainer.json │ └── docker-compose.yml ├── image └── .devcontainer │ └── devcontainer.json ├── invalid └── .devcontainer │ └── devcontainer.json ├── nvim-qt └── .devcontainer │ ├── devcontainer.json │ ├── install-nvim.sh │ └── run-nvim.sh └── post_create_command └── .devcontainer ├── devcontainer.json └── install-nvim.sh /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | -------------------------------------------------------------------------------- /.vimspector.json: -------------------------------------------------------------------------------- 1 | { 2 | "configurations": { 3 | "launch": { 4 | "adapter": "CodeLLDB", 5 | "configuration": { 6 | "request": "launch", 7 | "program": "${Executable}", 8 | "args": ["*${Args}"], 9 | "MIMode": "lldb" 10 | } 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | [[package]] 4 | name = "aho-corasick" 5 | version = "0.7.14" 6 | source = "registry+https://github.com/rust-lang/crates.io-index" 7 | checksum = "b476ce7103678b0c6d3d395dbbae31d48ff910bd28be979ba5d48c6351131d0d" 8 | dependencies = [ 9 | "memchr", 10 | ] 11 | 12 | [[package]] 13 | name = "ansi_term" 14 | version = "0.11.0" 15 | source = "registry+https://github.com/rust-lang/crates.io-index" 16 | checksum = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b" 17 | dependencies = [ 18 | "winapi 0.3.9", 19 | ] 20 | 21 | [[package]] 22 | name = "anyhow" 23 | version = "1.0.33" 24 | source = "registry+https://github.com/rust-lang/crates.io-index" 25 | checksum = "a1fd36ffbb1fb7c834eac128ea8d0e310c5aeb635548f9d58861e1308d46e71c" 26 | 27 | [[package]] 28 | name = "arc-swap" 29 | version = "0.4.7" 30 | source = "registry+https://github.com/rust-lang/crates.io-index" 31 | checksum = "4d25d88fd6b8041580a654f9d0c581a047baee2b3efee13275f2fc392fc75034" 32 | 33 | [[package]] 34 | name = "arrayref" 35 | version = "0.3.6" 36 | source = "registry+https://github.com/rust-lang/crates.io-index" 37 | checksum = "a4c527152e37cf757a3f78aae5a06fbeefdb07ccc535c980a3208ee3060dd544" 38 | 39 | [[package]] 40 | name = "arrayvec" 41 | version = "0.5.1" 42 | source = "registry+https://github.com/rust-lang/crates.io-index" 43 | checksum = "cff77d8686867eceff3105329d4698d96c2391c176d5d03adc90c7389162b5b8" 44 | 45 | [[package]] 46 | name = "atty" 47 | version = "0.2.14" 48 | source = "registry+https://github.com/rust-lang/crates.io-index" 49 | checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" 50 | dependencies = [ 51 | "hermit-abi", 52 | "libc", 53 | "winapi 0.3.9", 54 | ] 55 | 56 | [[package]] 57 | name = "autocfg" 58 | version = "1.0.1" 59 | source = "registry+https://github.com/rust-lang/crates.io-index" 60 | checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" 61 | 62 | [[package]] 63 | name = "base64" 64 | version = "0.12.3" 65 | source = "registry+https://github.com/rust-lang/crates.io-index" 66 | checksum = "3441f0f7b02788e948e47f457ca01f1d7e6d92c693bc132c22b087d3141c03ff" 67 | 68 | [[package]] 69 | name = "bitflags" 70 | version = "1.2.1" 71 | source = "registry+https://github.com/rust-lang/crates.io-index" 72 | checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" 73 | 74 | [[package]] 75 | name = "blake2b_simd" 76 | version = "0.5.10" 77 | source = "registry+https://github.com/rust-lang/crates.io-index" 78 | checksum = "d8fb2d74254a3a0b5cac33ac9f8ed0e44aa50378d9dbb2e5d83bd21ed1dc2c8a" 79 | dependencies = [ 80 | "arrayref", 81 | "arrayvec", 82 | "constant_time_eq", 83 | ] 84 | 85 | [[package]] 86 | name = "block-buffer" 87 | version = "0.7.3" 88 | source = "registry+https://github.com/rust-lang/crates.io-index" 89 | checksum = "c0940dc441f31689269e10ac70eb1002a3a1d3ad1390e030043662eb7fe4688b" 90 | dependencies = [ 91 | "block-padding", 92 | "byte-tools", 93 | "byteorder", 94 | "generic-array", 95 | ] 96 | 97 | [[package]] 98 | name = "block-padding" 99 | version = "0.1.5" 100 | source = "registry+https://github.com/rust-lang/crates.io-index" 101 | checksum = "fa79dedbb091f449f1f39e53edf88d5dbe95f895dae6135a8d7b881fb5af73f5" 102 | dependencies = [ 103 | "byte-tools", 104 | ] 105 | 106 | [[package]] 107 | name = "bollard" 108 | version = "0.8.0" 109 | source = "registry+https://github.com/rust-lang/crates.io-index" 110 | checksum = "98e70e4f2f2dec6396a87cd2a9acc0ac14d5aa0941a5f4a287bb25ae3a9ca183" 111 | dependencies = [ 112 | "base64", 113 | "bollard-stubs", 114 | "bytes 0.5.6", 115 | "chrono", 116 | "ct-logs", 117 | "dirs", 118 | "futures-core", 119 | "futures-util", 120 | "hex", 121 | "http", 122 | "hyper", 123 | "hyper-rustls", 124 | "hyper-unix-connector", 125 | "log", 126 | "mio-named-pipes", 127 | "pin-project", 128 | "rustls", 129 | "rustls-native-certs", 130 | "serde", 131 | "serde_derive", 132 | "serde_json", 133 | "serde_urlencoded", 134 | "thiserror", 135 | "tokio", 136 | "tokio-util", 137 | "url", 138 | "webpki-roots", 139 | "winapi 0.3.9", 140 | ] 141 | 142 | [[package]] 143 | name = "bollard-stubs" 144 | version = "1.40.6" 145 | source = "registry+https://github.com/rust-lang/crates.io-index" 146 | checksum = "abf72b3eeb9a5cce41979def2c7522cb830356c0621ca29c0b766128c4e7fded" 147 | dependencies = [ 148 | "chrono", 149 | "serde", 150 | "serde_with", 151 | ] 152 | 153 | [[package]] 154 | name = "bumpalo" 155 | version = "3.4.0" 156 | source = "registry+https://github.com/rust-lang/crates.io-index" 157 | checksum = "2e8c087f005730276d1096a652e92a8bacee2e2472bcc9715a74d2bec38b5820" 158 | 159 | [[package]] 160 | name = "byte-tools" 161 | version = "0.3.1" 162 | source = "registry+https://github.com/rust-lang/crates.io-index" 163 | checksum = "e3b5ca7a04898ad4bcd41c90c5285445ff5b791899bb1b0abdd2a2aa791211d7" 164 | 165 | [[package]] 166 | name = "byteorder" 167 | version = "1.3.4" 168 | source = "registry+https://github.com/rust-lang/crates.io-index" 169 | checksum = "08c48aae112d48ed9f069b33538ea9e3e90aa263cfa3d1c24309612b1f7472de" 170 | 171 | [[package]] 172 | name = "bytes" 173 | version = "0.4.12" 174 | source = "registry+https://github.com/rust-lang/crates.io-index" 175 | checksum = "206fdffcfa2df7cbe15601ef46c813fce0965eb3286db6b56c583b814b51c81c" 176 | dependencies = [ 177 | "byteorder", 178 | "iovec", 179 | ] 180 | 181 | [[package]] 182 | name = "bytes" 183 | version = "0.5.6" 184 | source = "registry+https://github.com/rust-lang/crates.io-index" 185 | checksum = "0e4cec68f03f32e44924783795810fa50a7035d8c8ebe78580ad7e6c703fba38" 186 | 187 | [[package]] 188 | name = "cc" 189 | version = "1.0.61" 190 | source = "registry+https://github.com/rust-lang/crates.io-index" 191 | checksum = "ed67cbde08356238e75fc4656be4749481eeffb09e19f320a25237d5221c985d" 192 | 193 | [[package]] 194 | name = "cfg-if" 195 | version = "0.1.10" 196 | source = "registry+https://github.com/rust-lang/crates.io-index" 197 | checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" 198 | 199 | [[package]] 200 | name = "chrono" 201 | version = "0.4.19" 202 | source = "registry+https://github.com/rust-lang/crates.io-index" 203 | checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73" 204 | dependencies = [ 205 | "libc", 206 | "num-integer", 207 | "num-traits", 208 | "serde", 209 | "time", 210 | "winapi 0.3.9", 211 | ] 212 | 213 | [[package]] 214 | name = "clap" 215 | version = "2.33.3" 216 | source = "registry+https://github.com/rust-lang/crates.io-index" 217 | checksum = "37e58ac78573c40708d45522f0d80fa2f01cc4f9b4e2bf749807255454312002" 218 | dependencies = [ 219 | "ansi_term", 220 | "atty", 221 | "bitflags", 222 | "strsim 0.8.0", 223 | "textwrap", 224 | "unicode-width", 225 | "vec_map", 226 | ] 227 | 228 | [[package]] 229 | name = "constant_time_eq" 230 | version = "0.1.5" 231 | source = "registry+https://github.com/rust-lang/crates.io-index" 232 | checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc" 233 | 234 | [[package]] 235 | name = "core-foundation" 236 | version = "0.7.0" 237 | source = "registry+https://github.com/rust-lang/crates.io-index" 238 | checksum = "57d24c7a13c43e870e37c1556b74555437870a04514f7685f5b354e090567171" 239 | dependencies = [ 240 | "core-foundation-sys", 241 | "libc", 242 | ] 243 | 244 | [[package]] 245 | name = "core-foundation-sys" 246 | version = "0.7.0" 247 | source = "registry+https://github.com/rust-lang/crates.io-index" 248 | checksum = "b3a71ab494c0b5b860bdc8407ae08978052417070c2ced38573a9157ad75b8ac" 249 | 250 | [[package]] 251 | name = "crossbeam-utils" 252 | version = "0.7.2" 253 | source = "registry+https://github.com/rust-lang/crates.io-index" 254 | checksum = "c3c7c73a2d1e9fc0886a08b93e98eb643461230d5f1925e4036204d5f2e261a8" 255 | dependencies = [ 256 | "autocfg", 257 | "cfg-if", 258 | "lazy_static", 259 | ] 260 | 261 | [[package]] 262 | name = "ct-logs" 263 | version = "0.7.0" 264 | source = "registry+https://github.com/rust-lang/crates.io-index" 265 | checksum = "8c8e13110a84b6315df212c045be706af261fd364791cad863285439ebba672e" 266 | dependencies = [ 267 | "sct", 268 | ] 269 | 270 | [[package]] 271 | name = "darling" 272 | version = "0.10.2" 273 | source = "registry+https://github.com/rust-lang/crates.io-index" 274 | checksum = "0d706e75d87e35569db781a9b5e2416cff1236a47ed380831f959382ccd5f858" 275 | dependencies = [ 276 | "darling_core", 277 | "darling_macro", 278 | ] 279 | 280 | [[package]] 281 | name = "darling_core" 282 | version = "0.10.2" 283 | source = "registry+https://github.com/rust-lang/crates.io-index" 284 | checksum = "f0c960ae2da4de88a91b2d920c2a7233b400bc33cb28453a2987822d8392519b" 285 | dependencies = [ 286 | "fnv", 287 | "ident_case", 288 | "proc-macro2", 289 | "quote", 290 | "strsim 0.9.3", 291 | "syn", 292 | ] 293 | 294 | [[package]] 295 | name = "darling_macro" 296 | version = "0.10.2" 297 | source = "registry+https://github.com/rust-lang/crates.io-index" 298 | checksum = "d9b5a2f4ac4969822c62224815d069952656cadc7084fdca9751e6d959189b72" 299 | dependencies = [ 300 | "darling_core", 301 | "quote", 302 | "syn", 303 | ] 304 | 305 | [[package]] 306 | name = "devcontainers-rs" 307 | version = "0.1.0" 308 | dependencies = [ 309 | "bollard", 310 | "clap", 311 | "dirs", 312 | "env_logger", 313 | "flate2", 314 | "futures 0.3.6", 315 | "http", 316 | "json5", 317 | "log", 318 | "rust-crypto", 319 | "serde", 320 | "serde_yaml", 321 | "tar", 322 | "tokio", 323 | ] 324 | 325 | [[package]] 326 | name = "digest" 327 | version = "0.8.1" 328 | source = "registry+https://github.com/rust-lang/crates.io-index" 329 | checksum = "f3d0c8c8752312f9713efd397ff63acb9f85585afbf179282e720e7704954dd5" 330 | dependencies = [ 331 | "generic-array", 332 | ] 333 | 334 | [[package]] 335 | name = "dirs" 336 | version = "3.0.1" 337 | source = "registry+https://github.com/rust-lang/crates.io-index" 338 | checksum = "142995ed02755914747cc6ca76fc7e4583cd18578746716d0508ea6ed558b9ff" 339 | dependencies = [ 340 | "dirs-sys", 341 | ] 342 | 343 | [[package]] 344 | name = "dirs-sys" 345 | version = "0.3.5" 346 | source = "registry+https://github.com/rust-lang/crates.io-index" 347 | checksum = "8e93d7f5705de3e49895a2b5e0b8855a1c27f080192ae9c32a6432d50741a57a" 348 | dependencies = [ 349 | "libc", 350 | "redox_users", 351 | "winapi 0.3.9", 352 | ] 353 | 354 | [[package]] 355 | name = "dtoa" 356 | version = "0.4.6" 357 | source = "registry+https://github.com/rust-lang/crates.io-index" 358 | checksum = "134951f4028bdadb9b84baf4232681efbf277da25144b9b0ad65df75946c422b" 359 | 360 | [[package]] 361 | name = "env_logger" 362 | version = "0.8.1" 363 | source = "registry+https://github.com/rust-lang/crates.io-index" 364 | checksum = "54532e3223c5af90a6a757c90b5c5521564b07e5e7a958681bcd2afad421cdcd" 365 | dependencies = [ 366 | "atty", 367 | "humantime", 368 | "log", 369 | "regex", 370 | "termcolor", 371 | ] 372 | 373 | [[package]] 374 | name = "fake-simd" 375 | version = "0.1.2" 376 | source = "registry+https://github.com/rust-lang/crates.io-index" 377 | checksum = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed" 378 | 379 | [[package]] 380 | name = "filetime" 381 | version = "0.2.12" 382 | source = "registry+https://github.com/rust-lang/crates.io-index" 383 | checksum = "3ed85775dcc68644b5c950ac06a2b23768d3bc9390464151aaf27136998dcf9e" 384 | dependencies = [ 385 | "cfg-if", 386 | "libc", 387 | "redox_syscall", 388 | "winapi 0.3.9", 389 | ] 390 | 391 | [[package]] 392 | name = "flate2" 393 | version = "0.2.20" 394 | source = "registry+https://github.com/rust-lang/crates.io-index" 395 | checksum = "e6234dd4468ae5d1e2dbb06fe2b058696fdc50a339c68a393aefbf00bc81e423" 396 | dependencies = [ 397 | "futures 0.1.30", 398 | "libc", 399 | "miniz-sys", 400 | "tokio-io", 401 | ] 402 | 403 | [[package]] 404 | name = "fnv" 405 | version = "1.0.7" 406 | source = "registry+https://github.com/rust-lang/crates.io-index" 407 | checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" 408 | 409 | [[package]] 410 | name = "fuchsia-cprng" 411 | version = "0.1.1" 412 | source = "registry+https://github.com/rust-lang/crates.io-index" 413 | checksum = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba" 414 | 415 | [[package]] 416 | name = "fuchsia-zircon" 417 | version = "0.3.3" 418 | source = "registry+https://github.com/rust-lang/crates.io-index" 419 | checksum = "2e9763c69ebaae630ba35f74888db465e49e259ba1bc0eda7d06f4a067615d82" 420 | dependencies = [ 421 | "bitflags", 422 | "fuchsia-zircon-sys", 423 | ] 424 | 425 | [[package]] 426 | name = "fuchsia-zircon-sys" 427 | version = "0.3.3" 428 | source = "registry+https://github.com/rust-lang/crates.io-index" 429 | checksum = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7" 430 | 431 | [[package]] 432 | name = "futures" 433 | version = "0.1.30" 434 | source = "registry+https://github.com/rust-lang/crates.io-index" 435 | checksum = "4c7e4c2612746b0df8fed4ce0c69156021b704c9aefa360311c04e6e9e002eed" 436 | 437 | [[package]] 438 | name = "futures" 439 | version = "0.3.6" 440 | source = "registry+https://github.com/rust-lang/crates.io-index" 441 | checksum = "5d8e3078b7b2a8a671cb7a3d17b4760e4181ea243227776ba83fd043b4ca034e" 442 | dependencies = [ 443 | "futures-channel", 444 | "futures-core", 445 | "futures-executor", 446 | "futures-io", 447 | "futures-sink", 448 | "futures-task", 449 | "futures-util", 450 | ] 451 | 452 | [[package]] 453 | name = "futures-channel" 454 | version = "0.3.6" 455 | source = "registry+https://github.com/rust-lang/crates.io-index" 456 | checksum = "a7a4d35f7401e948629c9c3d6638fb9bf94e0b2121e96c3b428cc4e631f3eb74" 457 | dependencies = [ 458 | "futures-core", 459 | "futures-sink", 460 | ] 461 | 462 | [[package]] 463 | name = "futures-core" 464 | version = "0.3.6" 465 | source = "registry+https://github.com/rust-lang/crates.io-index" 466 | checksum = "d674eaa0056896d5ada519900dbf97ead2e46a7b6621e8160d79e2f2e1e2784b" 467 | 468 | [[package]] 469 | name = "futures-executor" 470 | version = "0.3.6" 471 | source = "registry+https://github.com/rust-lang/crates.io-index" 472 | checksum = "cc709ca1da6f66143b8c9bec8e6260181869893714e9b5a490b169b0414144ab" 473 | dependencies = [ 474 | "futures-core", 475 | "futures-task", 476 | "futures-util", 477 | ] 478 | 479 | [[package]] 480 | name = "futures-io" 481 | version = "0.3.6" 482 | source = "registry+https://github.com/rust-lang/crates.io-index" 483 | checksum = "5fc94b64bb39543b4e432f1790b6bf18e3ee3b74653c5449f63310e9a74b123c" 484 | 485 | [[package]] 486 | name = "futures-macro" 487 | version = "0.3.6" 488 | source = "registry+https://github.com/rust-lang/crates.io-index" 489 | checksum = "f57ed14da4603b2554682e9f2ff3c65d7567b53188db96cb71538217fc64581b" 490 | dependencies = [ 491 | "proc-macro-hack", 492 | "proc-macro2", 493 | "quote", 494 | "syn", 495 | ] 496 | 497 | [[package]] 498 | name = "futures-sink" 499 | version = "0.3.6" 500 | source = "registry+https://github.com/rust-lang/crates.io-index" 501 | checksum = "0d8764258ed64ebc5d9ed185cf86a95db5cac810269c5d20ececb32e0088abbd" 502 | 503 | [[package]] 504 | name = "futures-task" 505 | version = "0.3.6" 506 | source = "registry+https://github.com/rust-lang/crates.io-index" 507 | checksum = "4dd26820a9f3637f1302da8bceba3ff33adbe53464b54ca24d4e2d4f1db30f94" 508 | dependencies = [ 509 | "once_cell", 510 | ] 511 | 512 | [[package]] 513 | name = "futures-util" 514 | version = "0.3.6" 515 | source = "registry+https://github.com/rust-lang/crates.io-index" 516 | checksum = "8a894a0acddba51a2d49a6f4263b1e64b8c579ece8af50fa86503d52cd1eea34" 517 | dependencies = [ 518 | "futures-channel", 519 | "futures-core", 520 | "futures-io", 521 | "futures-macro", 522 | "futures-sink", 523 | "futures-task", 524 | "memchr", 525 | "pin-project", 526 | "pin-utils", 527 | "proc-macro-hack", 528 | "proc-macro-nested", 529 | "slab", 530 | ] 531 | 532 | [[package]] 533 | name = "gcc" 534 | version = "0.3.55" 535 | source = "registry+https://github.com/rust-lang/crates.io-index" 536 | checksum = "8f5f3913fa0bfe7ee1fd8248b6b9f42a5af4b9d65ec2dd2c3c26132b950ecfc2" 537 | 538 | [[package]] 539 | name = "generic-array" 540 | version = "0.12.3" 541 | source = "registry+https://github.com/rust-lang/crates.io-index" 542 | checksum = "c68f0274ae0e023facc3c97b2e00f076be70e254bc851d972503b328db79b2ec" 543 | dependencies = [ 544 | "typenum", 545 | ] 546 | 547 | [[package]] 548 | name = "getrandom" 549 | version = "0.1.15" 550 | source = "registry+https://github.com/rust-lang/crates.io-index" 551 | checksum = "fc587bc0ec293155d5bfa6b9891ec18a1e330c234f896ea47fbada4cadbe47e6" 552 | dependencies = [ 553 | "cfg-if", 554 | "libc", 555 | "wasi 0.9.0+wasi-snapshot-preview1", 556 | ] 557 | 558 | [[package]] 559 | name = "h2" 560 | version = "0.2.6" 561 | source = "registry+https://github.com/rust-lang/crates.io-index" 562 | checksum = "993f9e0baeed60001cf565546b0d3dbe6a6ad23f2bd31644a133c641eccf6d53" 563 | dependencies = [ 564 | "bytes 0.5.6", 565 | "fnv", 566 | "futures-core", 567 | "futures-sink", 568 | "futures-util", 569 | "http", 570 | "indexmap", 571 | "slab", 572 | "tokio", 573 | "tokio-util", 574 | "tracing", 575 | ] 576 | 577 | [[package]] 578 | name = "hashbrown" 579 | version = "0.9.1" 580 | source = "registry+https://github.com/rust-lang/crates.io-index" 581 | checksum = "d7afe4a420e3fe79967a00898cc1f4db7c8a49a9333a29f8a4bd76a253d5cd04" 582 | 583 | [[package]] 584 | name = "hermit-abi" 585 | version = "0.1.17" 586 | source = "registry+https://github.com/rust-lang/crates.io-index" 587 | checksum = "5aca5565f760fb5b220e499d72710ed156fdb74e631659e99377d9ebfbd13ae8" 588 | dependencies = [ 589 | "libc", 590 | ] 591 | 592 | [[package]] 593 | name = "hex" 594 | version = "0.4.2" 595 | source = "registry+https://github.com/rust-lang/crates.io-index" 596 | checksum = "644f9158b2f133fd50f5fb3242878846d9eb792e445c893805ff0e3824006e35" 597 | 598 | [[package]] 599 | name = "http" 600 | version = "0.2.1" 601 | source = "registry+https://github.com/rust-lang/crates.io-index" 602 | checksum = "28d569972648b2c512421b5f2a405ad6ac9666547189d0c5477a3f200f3e02f9" 603 | dependencies = [ 604 | "bytes 0.5.6", 605 | "fnv", 606 | "itoa", 607 | ] 608 | 609 | [[package]] 610 | name = "http-body" 611 | version = "0.3.1" 612 | source = "registry+https://github.com/rust-lang/crates.io-index" 613 | checksum = "13d5ff830006f7646652e057693569bfe0d51760c0085a071769d142a205111b" 614 | dependencies = [ 615 | "bytes 0.5.6", 616 | "http", 617 | ] 618 | 619 | [[package]] 620 | name = "httparse" 621 | version = "1.3.4" 622 | source = "registry+https://github.com/rust-lang/crates.io-index" 623 | checksum = "cd179ae861f0c2e53da70d892f5f3029f9594be0c41dc5269cd371691b1dc2f9" 624 | 625 | [[package]] 626 | name = "httpdate" 627 | version = "0.3.2" 628 | source = "registry+https://github.com/rust-lang/crates.io-index" 629 | checksum = "494b4d60369511e7dea41cf646832512a94e542f68bb9c49e54518e0f468eb47" 630 | 631 | [[package]] 632 | name = "humantime" 633 | version = "2.0.1" 634 | source = "registry+https://github.com/rust-lang/crates.io-index" 635 | checksum = "3c1ad908cc71012b7bea4d0c53ba96a8cba9962f048fa68d143376143d863b7a" 636 | 637 | [[package]] 638 | name = "hyper" 639 | version = "0.13.8" 640 | source = "registry+https://github.com/rust-lang/crates.io-index" 641 | checksum = "2f3afcfae8af5ad0576a31e768415edb627824129e8e5a29b8bfccb2f234e835" 642 | dependencies = [ 643 | "bytes 0.5.6", 644 | "futures-channel", 645 | "futures-core", 646 | "futures-util", 647 | "h2", 648 | "http", 649 | "http-body", 650 | "httparse", 651 | "httpdate", 652 | "itoa", 653 | "pin-project", 654 | "socket2", 655 | "tokio", 656 | "tower-service", 657 | "tracing", 658 | "want", 659 | ] 660 | 661 | [[package]] 662 | name = "hyper-rustls" 663 | version = "0.21.0" 664 | source = "registry+https://github.com/rust-lang/crates.io-index" 665 | checksum = "37743cc83e8ee85eacfce90f2f4102030d9ff0a95244098d781e9bee4a90abb6" 666 | dependencies = [ 667 | "bytes 0.5.6", 668 | "ct-logs", 669 | "futures-util", 670 | "hyper", 671 | "log", 672 | "rustls", 673 | "rustls-native-certs", 674 | "tokio", 675 | "tokio-rustls", 676 | "webpki", 677 | ] 678 | 679 | [[package]] 680 | name = "hyper-unix-connector" 681 | version = "0.1.5" 682 | source = "registry+https://github.com/rust-lang/crates.io-index" 683 | checksum = "42b66be14087ec25c5150c9d1228a1e9bbbfe7fe2506ff85daed350724980319" 684 | dependencies = [ 685 | "anyhow", 686 | "futures-util", 687 | "hex", 688 | "hyper", 689 | "pin-project", 690 | "tokio", 691 | ] 692 | 693 | [[package]] 694 | name = "ident_case" 695 | version = "1.0.1" 696 | source = "registry+https://github.com/rust-lang/crates.io-index" 697 | checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" 698 | 699 | [[package]] 700 | name = "idna" 701 | version = "0.2.0" 702 | source = "registry+https://github.com/rust-lang/crates.io-index" 703 | checksum = "02e2673c30ee86b5b96a9cb52ad15718aa1f966f5ab9ad54a8b95d5ca33120a9" 704 | dependencies = [ 705 | "matches", 706 | "unicode-bidi", 707 | "unicode-normalization", 708 | ] 709 | 710 | [[package]] 711 | name = "indexmap" 712 | version = "1.6.0" 713 | source = "registry+https://github.com/rust-lang/crates.io-index" 714 | checksum = "55e2e4c765aa53a0424761bf9f41aa7a6ac1efa87238f59560640e27fca028f2" 715 | dependencies = [ 716 | "autocfg", 717 | "hashbrown", 718 | ] 719 | 720 | [[package]] 721 | name = "iovec" 722 | version = "0.1.4" 723 | source = "registry+https://github.com/rust-lang/crates.io-index" 724 | checksum = "b2b3ea6ff95e175473f8ffe6a7eb7c00d054240321b84c57051175fe3c1e075e" 725 | dependencies = [ 726 | "libc", 727 | ] 728 | 729 | [[package]] 730 | name = "itoa" 731 | version = "0.4.6" 732 | source = "registry+https://github.com/rust-lang/crates.io-index" 733 | checksum = "dc6f3ad7b9d11a0c00842ff8de1b60ee58661048eb8049ed33c73594f359d7e6" 734 | 735 | [[package]] 736 | name = "js-sys" 737 | version = "0.3.45" 738 | source = "registry+https://github.com/rust-lang/crates.io-index" 739 | checksum = "ca059e81d9486668f12d455a4ea6daa600bd408134cd17e3d3fb5a32d1f016f8" 740 | dependencies = [ 741 | "wasm-bindgen", 742 | ] 743 | 744 | [[package]] 745 | name = "json5" 746 | version = "0.2.8" 747 | source = "registry+https://github.com/rust-lang/crates.io-index" 748 | checksum = "8eb2522ff59fbfefb955e9bd44d04d5e5c2d0e8865bfc2c3d1ab3916183ef5ee" 749 | dependencies = [ 750 | "pest", 751 | "pest_derive", 752 | "serde", 753 | ] 754 | 755 | [[package]] 756 | name = "kernel32-sys" 757 | version = "0.2.2" 758 | source = "registry+https://github.com/rust-lang/crates.io-index" 759 | checksum = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" 760 | dependencies = [ 761 | "winapi 0.2.8", 762 | "winapi-build", 763 | ] 764 | 765 | [[package]] 766 | name = "lazy_static" 767 | version = "1.4.0" 768 | source = "registry+https://github.com/rust-lang/crates.io-index" 769 | checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" 770 | 771 | [[package]] 772 | name = "libc" 773 | version = "0.2.79" 774 | source = "registry+https://github.com/rust-lang/crates.io-index" 775 | checksum = "2448f6066e80e3bfc792e9c98bf705b4b0fc6e8ef5b43e5889aff0eaa9c58743" 776 | 777 | [[package]] 778 | name = "linked-hash-map" 779 | version = "0.5.3" 780 | source = "registry+https://github.com/rust-lang/crates.io-index" 781 | checksum = "8dd5a6d5999d9907cda8ed67bbd137d3af8085216c2ac62de5be860bd41f304a" 782 | 783 | [[package]] 784 | name = "log" 785 | version = "0.4.11" 786 | source = "registry+https://github.com/rust-lang/crates.io-index" 787 | checksum = "4fabed175da42fed1fa0746b0ea71f412aa9d35e76e95e59b192c64b9dc2bf8b" 788 | dependencies = [ 789 | "cfg-if", 790 | ] 791 | 792 | [[package]] 793 | name = "maplit" 794 | version = "1.0.2" 795 | source = "registry+https://github.com/rust-lang/crates.io-index" 796 | checksum = "3e2e65a1a2e43cfcb47a895c4c8b10d1f4a61097f9f254f183aee60cad9c651d" 797 | 798 | [[package]] 799 | name = "matches" 800 | version = "0.1.8" 801 | source = "registry+https://github.com/rust-lang/crates.io-index" 802 | checksum = "7ffc5c5338469d4d3ea17d269fa8ea3512ad247247c30bd2df69e68309ed0a08" 803 | 804 | [[package]] 805 | name = "memchr" 806 | version = "2.3.3" 807 | source = "registry+https://github.com/rust-lang/crates.io-index" 808 | checksum = "3728d817d99e5ac407411fa471ff9800a778d88a24685968b36824eaf4bee400" 809 | 810 | [[package]] 811 | name = "miniz-sys" 812 | version = "0.1.12" 813 | source = "registry+https://github.com/rust-lang/crates.io-index" 814 | checksum = "1e9e3ae51cea1576ceba0dde3d484d30e6e5b86dee0b2d412fe3a16a15c98202" 815 | dependencies = [ 816 | "cc", 817 | "libc", 818 | ] 819 | 820 | [[package]] 821 | name = "mio" 822 | version = "0.6.22" 823 | source = "registry+https://github.com/rust-lang/crates.io-index" 824 | checksum = "fce347092656428bc8eaf6201042cb551b8d67855af7374542a92a0fbfcac430" 825 | dependencies = [ 826 | "cfg-if", 827 | "fuchsia-zircon", 828 | "fuchsia-zircon-sys", 829 | "iovec", 830 | "kernel32-sys", 831 | "libc", 832 | "log", 833 | "miow 0.2.1", 834 | "net2", 835 | "slab", 836 | "winapi 0.2.8", 837 | ] 838 | 839 | [[package]] 840 | name = "mio-named-pipes" 841 | version = "0.1.7" 842 | source = "registry+https://github.com/rust-lang/crates.io-index" 843 | checksum = "0840c1c50fd55e521b247f949c241c9997709f23bd7f023b9762cd561e935656" 844 | dependencies = [ 845 | "log", 846 | "mio", 847 | "miow 0.3.5", 848 | "winapi 0.3.9", 849 | ] 850 | 851 | [[package]] 852 | name = "mio-uds" 853 | version = "0.6.8" 854 | source = "registry+https://github.com/rust-lang/crates.io-index" 855 | checksum = "afcb699eb26d4332647cc848492bbc15eafb26f08d0304550d5aa1f612e066f0" 856 | dependencies = [ 857 | "iovec", 858 | "libc", 859 | "mio", 860 | ] 861 | 862 | [[package]] 863 | name = "miow" 864 | version = "0.2.1" 865 | source = "registry+https://github.com/rust-lang/crates.io-index" 866 | checksum = "8c1f2f3b1cf331de6896aabf6e9d55dca90356cc9960cca7eaaf408a355ae919" 867 | dependencies = [ 868 | "kernel32-sys", 869 | "net2", 870 | "winapi 0.2.8", 871 | "ws2_32-sys", 872 | ] 873 | 874 | [[package]] 875 | name = "miow" 876 | version = "0.3.5" 877 | source = "registry+https://github.com/rust-lang/crates.io-index" 878 | checksum = "07b88fb9795d4d36d62a012dfbf49a8f5cf12751f36d31a9dbe66d528e58979e" 879 | dependencies = [ 880 | "socket2", 881 | "winapi 0.3.9", 882 | ] 883 | 884 | [[package]] 885 | name = "net2" 886 | version = "0.2.35" 887 | source = "registry+https://github.com/rust-lang/crates.io-index" 888 | checksum = "3ebc3ec692ed7c9a255596c67808dee269f64655d8baf7b4f0638e51ba1d6853" 889 | dependencies = [ 890 | "cfg-if", 891 | "libc", 892 | "winapi 0.3.9", 893 | ] 894 | 895 | [[package]] 896 | name = "num-integer" 897 | version = "0.1.43" 898 | source = "registry+https://github.com/rust-lang/crates.io-index" 899 | checksum = "8d59457e662d541ba17869cf51cf177c0b5f0cbf476c66bdc90bf1edac4f875b" 900 | dependencies = [ 901 | "autocfg", 902 | "num-traits", 903 | ] 904 | 905 | [[package]] 906 | name = "num-traits" 907 | version = "0.2.12" 908 | source = "registry+https://github.com/rust-lang/crates.io-index" 909 | checksum = "ac267bcc07f48ee5f8935ab0d24f316fb722d7a1292e2913f0cc196b29ffd611" 910 | dependencies = [ 911 | "autocfg", 912 | ] 913 | 914 | [[package]] 915 | name = "num_cpus" 916 | version = "1.13.0" 917 | source = "registry+https://github.com/rust-lang/crates.io-index" 918 | checksum = "05499f3756671c15885fee9034446956fff3f243d6077b91e5767df161f766b3" 919 | dependencies = [ 920 | "hermit-abi", 921 | "libc", 922 | ] 923 | 924 | [[package]] 925 | name = "once_cell" 926 | version = "1.4.1" 927 | source = "registry+https://github.com/rust-lang/crates.io-index" 928 | checksum = "260e51e7efe62b592207e9e13a68e43692a7a279171d6ba57abd208bf23645ad" 929 | 930 | [[package]] 931 | name = "opaque-debug" 932 | version = "0.2.3" 933 | source = "registry+https://github.com/rust-lang/crates.io-index" 934 | checksum = "2839e79665f131bdb5782e51f2c6c9599c133c6098982a54c794358bf432529c" 935 | 936 | [[package]] 937 | name = "openssl-probe" 938 | version = "0.1.2" 939 | source = "registry+https://github.com/rust-lang/crates.io-index" 940 | checksum = "77af24da69f9d9341038eba93a073b1fdaaa1b788221b00a69bce9e762cb32de" 941 | 942 | [[package]] 943 | name = "percent-encoding" 944 | version = "2.1.0" 945 | source = "registry+https://github.com/rust-lang/crates.io-index" 946 | checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" 947 | 948 | [[package]] 949 | name = "pest" 950 | version = "2.1.3" 951 | source = "registry+https://github.com/rust-lang/crates.io-index" 952 | checksum = "10f4872ae94d7b90ae48754df22fd42ad52ce740b8f370b03da4835417403e53" 953 | dependencies = [ 954 | "ucd-trie", 955 | ] 956 | 957 | [[package]] 958 | name = "pest_derive" 959 | version = "2.1.0" 960 | source = "registry+https://github.com/rust-lang/crates.io-index" 961 | checksum = "833d1ae558dc601e9a60366421196a8d94bc0ac980476d0b67e1d0988d72b2d0" 962 | dependencies = [ 963 | "pest", 964 | "pest_generator", 965 | ] 966 | 967 | [[package]] 968 | name = "pest_generator" 969 | version = "2.1.3" 970 | source = "registry+https://github.com/rust-lang/crates.io-index" 971 | checksum = "99b8db626e31e5b81787b9783425769681b347011cc59471e33ea46d2ea0cf55" 972 | dependencies = [ 973 | "pest", 974 | "pest_meta", 975 | "proc-macro2", 976 | "quote", 977 | "syn", 978 | ] 979 | 980 | [[package]] 981 | name = "pest_meta" 982 | version = "2.1.3" 983 | source = "registry+https://github.com/rust-lang/crates.io-index" 984 | checksum = "54be6e404f5317079812fc8f9f5279de376d8856929e21c184ecf6bbd692a11d" 985 | dependencies = [ 986 | "maplit", 987 | "pest", 988 | "sha-1", 989 | ] 990 | 991 | [[package]] 992 | name = "pin-project" 993 | version = "0.4.27" 994 | source = "registry+https://github.com/rust-lang/crates.io-index" 995 | checksum = "2ffbc8e94b38ea3d2d8ba92aea2983b503cd75d0888d75b86bb37970b5698e15" 996 | dependencies = [ 997 | "pin-project-internal", 998 | ] 999 | 1000 | [[package]] 1001 | name = "pin-project-internal" 1002 | version = "0.4.27" 1003 | source = "registry+https://github.com/rust-lang/crates.io-index" 1004 | checksum = "65ad2ae56b6abe3a1ee25f15ee605bacadb9a764edaba9c2bf4103800d4a1895" 1005 | dependencies = [ 1006 | "proc-macro2", 1007 | "quote", 1008 | "syn", 1009 | ] 1010 | 1011 | [[package]] 1012 | name = "pin-project-lite" 1013 | version = "0.1.10" 1014 | source = "registry+https://github.com/rust-lang/crates.io-index" 1015 | checksum = "e555d9e657502182ac97b539fb3dae8b79cda19e3e4f8ffb5e8de4f18df93c95" 1016 | 1017 | [[package]] 1018 | name = "pin-utils" 1019 | version = "0.1.0" 1020 | source = "registry+https://github.com/rust-lang/crates.io-index" 1021 | checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" 1022 | 1023 | [[package]] 1024 | name = "proc-macro-hack" 1025 | version = "0.5.18" 1026 | source = "registry+https://github.com/rust-lang/crates.io-index" 1027 | checksum = "99c605b9a0adc77b7211c6b1f722dcb613d68d66859a44f3d485a6da332b0598" 1028 | 1029 | [[package]] 1030 | name = "proc-macro-nested" 1031 | version = "0.1.6" 1032 | source = "registry+https://github.com/rust-lang/crates.io-index" 1033 | checksum = "eba180dafb9038b050a4c280019bbedf9f2467b61e5d892dcad585bb57aadc5a" 1034 | 1035 | [[package]] 1036 | name = "proc-macro2" 1037 | version = "1.0.24" 1038 | source = "registry+https://github.com/rust-lang/crates.io-index" 1039 | checksum = "1e0704ee1a7e00d7bb417d0770ea303c1bccbabf0ef1667dae92b5967f5f8a71" 1040 | dependencies = [ 1041 | "unicode-xid", 1042 | ] 1043 | 1044 | [[package]] 1045 | name = "quote" 1046 | version = "1.0.7" 1047 | source = "registry+https://github.com/rust-lang/crates.io-index" 1048 | checksum = "aa563d17ecb180e500da1cfd2b028310ac758de548efdd203e18f283af693f37" 1049 | dependencies = [ 1050 | "proc-macro2", 1051 | ] 1052 | 1053 | [[package]] 1054 | name = "rand" 1055 | version = "0.3.23" 1056 | source = "registry+https://github.com/rust-lang/crates.io-index" 1057 | checksum = "64ac302d8f83c0c1974bf758f6b041c6c8ada916fbb44a609158ca8b064cc76c" 1058 | dependencies = [ 1059 | "libc", 1060 | "rand 0.4.6", 1061 | ] 1062 | 1063 | [[package]] 1064 | name = "rand" 1065 | version = "0.4.6" 1066 | source = "registry+https://github.com/rust-lang/crates.io-index" 1067 | checksum = "552840b97013b1a26992c11eac34bdd778e464601a4c2054b5f0bff7c6761293" 1068 | dependencies = [ 1069 | "fuchsia-cprng", 1070 | "libc", 1071 | "rand_core 0.3.1", 1072 | "rdrand", 1073 | "winapi 0.3.9", 1074 | ] 1075 | 1076 | [[package]] 1077 | name = "rand_core" 1078 | version = "0.3.1" 1079 | source = "registry+https://github.com/rust-lang/crates.io-index" 1080 | checksum = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b" 1081 | dependencies = [ 1082 | "rand_core 0.4.2", 1083 | ] 1084 | 1085 | [[package]] 1086 | name = "rand_core" 1087 | version = "0.4.2" 1088 | source = "registry+https://github.com/rust-lang/crates.io-index" 1089 | checksum = "9c33a3c44ca05fa6f1807d8e6743f3824e8509beca625669633be0acbdf509dc" 1090 | 1091 | [[package]] 1092 | name = "rdrand" 1093 | version = "0.4.0" 1094 | source = "registry+https://github.com/rust-lang/crates.io-index" 1095 | checksum = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2" 1096 | dependencies = [ 1097 | "rand_core 0.3.1", 1098 | ] 1099 | 1100 | [[package]] 1101 | name = "redox_syscall" 1102 | version = "0.1.57" 1103 | source = "registry+https://github.com/rust-lang/crates.io-index" 1104 | checksum = "41cc0f7e4d5d4544e8861606a285bb08d3e70712ccc7d2b84d7c0ccfaf4b05ce" 1105 | 1106 | [[package]] 1107 | name = "redox_users" 1108 | version = "0.3.5" 1109 | source = "registry+https://github.com/rust-lang/crates.io-index" 1110 | checksum = "de0737333e7a9502c789a36d7c7fa6092a49895d4faa31ca5df163857ded2e9d" 1111 | dependencies = [ 1112 | "getrandom", 1113 | "redox_syscall", 1114 | "rust-argon2", 1115 | ] 1116 | 1117 | [[package]] 1118 | name = "regex" 1119 | version = "1.4.1" 1120 | source = "registry+https://github.com/rust-lang/crates.io-index" 1121 | checksum = "8963b85b8ce3074fecffde43b4b0dded83ce2f367dc8d363afc56679f3ee820b" 1122 | dependencies = [ 1123 | "aho-corasick", 1124 | "memchr", 1125 | "regex-syntax", 1126 | "thread_local", 1127 | ] 1128 | 1129 | [[package]] 1130 | name = "regex-syntax" 1131 | version = "0.6.20" 1132 | source = "registry+https://github.com/rust-lang/crates.io-index" 1133 | checksum = "8cab7a364d15cde1e505267766a2d3c4e22a843e1a601f0fa7564c0f82ced11c" 1134 | 1135 | [[package]] 1136 | name = "ring" 1137 | version = "0.16.15" 1138 | source = "registry+https://github.com/rust-lang/crates.io-index" 1139 | checksum = "952cd6b98c85bbc30efa1ba5783b8abf12fec8b3287ffa52605b9432313e34e4" 1140 | dependencies = [ 1141 | "cc", 1142 | "libc", 1143 | "once_cell", 1144 | "spin", 1145 | "untrusted", 1146 | "web-sys", 1147 | "winapi 0.3.9", 1148 | ] 1149 | 1150 | [[package]] 1151 | name = "rust-argon2" 1152 | version = "0.8.2" 1153 | source = "registry+https://github.com/rust-lang/crates.io-index" 1154 | checksum = "9dab61250775933275e84053ac235621dfb739556d5c54a2f2e9313b7cf43a19" 1155 | dependencies = [ 1156 | "base64", 1157 | "blake2b_simd", 1158 | "constant_time_eq", 1159 | "crossbeam-utils", 1160 | ] 1161 | 1162 | [[package]] 1163 | name = "rust-crypto" 1164 | version = "0.2.36" 1165 | source = "registry+https://github.com/rust-lang/crates.io-index" 1166 | checksum = "f76d05d3993fd5f4af9434e8e436db163a12a9d40e1a58a726f27a01dfd12a2a" 1167 | dependencies = [ 1168 | "gcc", 1169 | "libc", 1170 | "rand 0.3.23", 1171 | "rustc-serialize", 1172 | "time", 1173 | ] 1174 | 1175 | [[package]] 1176 | name = "rustc-serialize" 1177 | version = "0.3.24" 1178 | source = "registry+https://github.com/rust-lang/crates.io-index" 1179 | checksum = "dcf128d1287d2ea9d80910b5f1120d0b8eede3fbf1abe91c40d39ea7d51e6fda" 1180 | 1181 | [[package]] 1182 | name = "rustls" 1183 | version = "0.18.1" 1184 | source = "registry+https://github.com/rust-lang/crates.io-index" 1185 | checksum = "5d1126dcf58e93cee7d098dbda643b5f92ed724f1f6a63007c1116eed6700c81" 1186 | dependencies = [ 1187 | "base64", 1188 | "log", 1189 | "ring", 1190 | "sct", 1191 | "webpki", 1192 | ] 1193 | 1194 | [[package]] 1195 | name = "rustls-native-certs" 1196 | version = "0.4.0" 1197 | source = "registry+https://github.com/rust-lang/crates.io-index" 1198 | checksum = "629d439a7672da82dd955498445e496ee2096fe2117b9f796558a43fdb9e59b8" 1199 | dependencies = [ 1200 | "openssl-probe", 1201 | "rustls", 1202 | "schannel", 1203 | "security-framework", 1204 | ] 1205 | 1206 | [[package]] 1207 | name = "ryu" 1208 | version = "1.0.5" 1209 | source = "registry+https://github.com/rust-lang/crates.io-index" 1210 | checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e" 1211 | 1212 | [[package]] 1213 | name = "schannel" 1214 | version = "0.1.19" 1215 | source = "registry+https://github.com/rust-lang/crates.io-index" 1216 | checksum = "8f05ba609c234e60bee0d547fe94a4c7e9da733d1c962cf6e59efa4cd9c8bc75" 1217 | dependencies = [ 1218 | "lazy_static", 1219 | "winapi 0.3.9", 1220 | ] 1221 | 1222 | [[package]] 1223 | name = "sct" 1224 | version = "0.6.0" 1225 | source = "registry+https://github.com/rust-lang/crates.io-index" 1226 | checksum = "e3042af939fca8c3453b7af0f1c66e533a15a86169e39de2657310ade8f98d3c" 1227 | dependencies = [ 1228 | "ring", 1229 | "untrusted", 1230 | ] 1231 | 1232 | [[package]] 1233 | name = "security-framework" 1234 | version = "1.0.0" 1235 | source = "registry+https://github.com/rust-lang/crates.io-index" 1236 | checksum = "ad502866817f0575705bd7be36e2b2535cc33262d493aa733a2ec862baa2bc2b" 1237 | dependencies = [ 1238 | "bitflags", 1239 | "core-foundation", 1240 | "core-foundation-sys", 1241 | "libc", 1242 | "security-framework-sys", 1243 | ] 1244 | 1245 | [[package]] 1246 | name = "security-framework-sys" 1247 | version = "1.0.0" 1248 | source = "registry+https://github.com/rust-lang/crates.io-index" 1249 | checksum = "51ceb04988b17b6d1dcd555390fa822ca5637b4a14e1f5099f13d351bed4d6c7" 1250 | dependencies = [ 1251 | "core-foundation-sys", 1252 | "libc", 1253 | ] 1254 | 1255 | [[package]] 1256 | name = "serde" 1257 | version = "1.0.117" 1258 | source = "registry+https://github.com/rust-lang/crates.io-index" 1259 | checksum = "b88fa983de7720629c9387e9f517353ed404164b1e482c970a90c1a4aaf7dc1a" 1260 | dependencies = [ 1261 | "serde_derive", 1262 | ] 1263 | 1264 | [[package]] 1265 | name = "serde_derive" 1266 | version = "1.0.117" 1267 | source = "registry+https://github.com/rust-lang/crates.io-index" 1268 | checksum = "cbd1ae72adb44aab48f325a02444a5fc079349a8d804c1fc922aed3f7454c74e" 1269 | dependencies = [ 1270 | "proc-macro2", 1271 | "quote", 1272 | "syn", 1273 | ] 1274 | 1275 | [[package]] 1276 | name = "serde_json" 1277 | version = "1.0.59" 1278 | source = "registry+https://github.com/rust-lang/crates.io-index" 1279 | checksum = "dcac07dbffa1c65e7f816ab9eba78eb142c6d44410f4eeba1e26e4f5dfa56b95" 1280 | dependencies = [ 1281 | "itoa", 1282 | "ryu", 1283 | "serde", 1284 | ] 1285 | 1286 | [[package]] 1287 | name = "serde_urlencoded" 1288 | version = "0.6.1" 1289 | source = "registry+https://github.com/rust-lang/crates.io-index" 1290 | checksum = "9ec5d77e2d4c73717816afac02670d5c4f534ea95ed430442cad02e7a6e32c97" 1291 | dependencies = [ 1292 | "dtoa", 1293 | "itoa", 1294 | "serde", 1295 | "url", 1296 | ] 1297 | 1298 | [[package]] 1299 | name = "serde_with" 1300 | version = "1.5.1" 1301 | source = "registry+https://github.com/rust-lang/crates.io-index" 1302 | checksum = "8bac272128fb3b1e98872dca27a05c18d8b78b9bd089d3edb7b5871501b50bce" 1303 | dependencies = [ 1304 | "serde", 1305 | "serde_with_macros", 1306 | ] 1307 | 1308 | [[package]] 1309 | name = "serde_with_macros" 1310 | version = "1.2.2" 1311 | source = "registry+https://github.com/rust-lang/crates.io-index" 1312 | checksum = "3c747a9ab2e833b807f74f6b6141530655010bfa9c9c06d5508bce75c8f8072f" 1313 | dependencies = [ 1314 | "darling", 1315 | "proc-macro2", 1316 | "quote", 1317 | "syn", 1318 | ] 1319 | 1320 | [[package]] 1321 | name = "serde_yaml" 1322 | version = "0.8.13" 1323 | source = "registry+https://github.com/rust-lang/crates.io-index" 1324 | checksum = "ae3e2dd40a7cdc18ca80db804b7f461a39bb721160a85c9a1fa30134bf3c02a5" 1325 | dependencies = [ 1326 | "dtoa", 1327 | "linked-hash-map", 1328 | "serde", 1329 | "yaml-rust", 1330 | ] 1331 | 1332 | [[package]] 1333 | name = "sha-1" 1334 | version = "0.8.2" 1335 | source = "registry+https://github.com/rust-lang/crates.io-index" 1336 | checksum = "f7d94d0bede923b3cea61f3f1ff57ff8cdfd77b400fb8f9998949e0cf04163df" 1337 | dependencies = [ 1338 | "block-buffer", 1339 | "digest", 1340 | "fake-simd", 1341 | "opaque-debug", 1342 | ] 1343 | 1344 | [[package]] 1345 | name = "signal-hook-registry" 1346 | version = "1.2.1" 1347 | source = "registry+https://github.com/rust-lang/crates.io-index" 1348 | checksum = "a3e12110bc539e657a646068aaf5eb5b63af9d0c1f7b29c97113fad80e15f035" 1349 | dependencies = [ 1350 | "arc-swap", 1351 | "libc", 1352 | ] 1353 | 1354 | [[package]] 1355 | name = "slab" 1356 | version = "0.4.2" 1357 | source = "registry+https://github.com/rust-lang/crates.io-index" 1358 | checksum = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8" 1359 | 1360 | [[package]] 1361 | name = "socket2" 1362 | version = "0.3.15" 1363 | source = "registry+https://github.com/rust-lang/crates.io-index" 1364 | checksum = "b1fa70dc5c8104ec096f4fe7ede7a221d35ae13dcd19ba1ad9a81d2cab9a1c44" 1365 | dependencies = [ 1366 | "cfg-if", 1367 | "libc", 1368 | "redox_syscall", 1369 | "winapi 0.3.9", 1370 | ] 1371 | 1372 | [[package]] 1373 | name = "spin" 1374 | version = "0.5.2" 1375 | source = "registry+https://github.com/rust-lang/crates.io-index" 1376 | checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" 1377 | 1378 | [[package]] 1379 | name = "strsim" 1380 | version = "0.8.0" 1381 | source = "registry+https://github.com/rust-lang/crates.io-index" 1382 | checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" 1383 | 1384 | [[package]] 1385 | name = "strsim" 1386 | version = "0.9.3" 1387 | source = "registry+https://github.com/rust-lang/crates.io-index" 1388 | checksum = "6446ced80d6c486436db5c078dde11a9f73d42b57fb273121e160b84f63d894c" 1389 | 1390 | [[package]] 1391 | name = "syn" 1392 | version = "1.0.44" 1393 | source = "registry+https://github.com/rust-lang/crates.io-index" 1394 | checksum = "e03e57e4fcbfe7749842d53e24ccb9aa12b7252dbe5e91d2acad31834c8b8fdd" 1395 | dependencies = [ 1396 | "proc-macro2", 1397 | "quote", 1398 | "unicode-xid", 1399 | ] 1400 | 1401 | [[package]] 1402 | name = "tar" 1403 | version = "0.4.30" 1404 | source = "registry+https://github.com/rust-lang/crates.io-index" 1405 | checksum = "489997b7557e9a43e192c527face4feacc78bfbe6eed67fd55c4c9e381cba290" 1406 | dependencies = [ 1407 | "filetime", 1408 | "libc", 1409 | "redox_syscall", 1410 | "xattr", 1411 | ] 1412 | 1413 | [[package]] 1414 | name = "termcolor" 1415 | version = "1.1.0" 1416 | source = "registry+https://github.com/rust-lang/crates.io-index" 1417 | checksum = "bb6bfa289a4d7c5766392812c0a1f4c1ba45afa1ad47803c11e1f407d846d75f" 1418 | dependencies = [ 1419 | "winapi-util", 1420 | ] 1421 | 1422 | [[package]] 1423 | name = "textwrap" 1424 | version = "0.11.0" 1425 | source = "registry+https://github.com/rust-lang/crates.io-index" 1426 | checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" 1427 | dependencies = [ 1428 | "unicode-width", 1429 | ] 1430 | 1431 | [[package]] 1432 | name = "thiserror" 1433 | version = "1.0.21" 1434 | source = "registry+https://github.com/rust-lang/crates.io-index" 1435 | checksum = "318234ffa22e0920fe9a40d7b8369b5f649d490980cf7aadcf1eb91594869b42" 1436 | dependencies = [ 1437 | "thiserror-impl", 1438 | ] 1439 | 1440 | [[package]] 1441 | name = "thiserror-impl" 1442 | version = "1.0.21" 1443 | source = "registry+https://github.com/rust-lang/crates.io-index" 1444 | checksum = "cae2447b6282786c3493999f40a9be2a6ad20cb8bd268b0a0dbf5a065535c0ab" 1445 | dependencies = [ 1446 | "proc-macro2", 1447 | "quote", 1448 | "syn", 1449 | ] 1450 | 1451 | [[package]] 1452 | name = "thread_local" 1453 | version = "1.0.1" 1454 | source = "registry+https://github.com/rust-lang/crates.io-index" 1455 | checksum = "d40c6d1b69745a6ec6fb1ca717914848da4b44ae29d9b3080cbee91d72a69b14" 1456 | dependencies = [ 1457 | "lazy_static", 1458 | ] 1459 | 1460 | [[package]] 1461 | name = "time" 1462 | version = "0.1.44" 1463 | source = "registry+https://github.com/rust-lang/crates.io-index" 1464 | checksum = "6db9e6914ab8b1ae1c260a4ae7a49b6c5611b40328a735b21862567685e73255" 1465 | dependencies = [ 1466 | "libc", 1467 | "wasi 0.10.0+wasi-snapshot-preview1", 1468 | "winapi 0.3.9", 1469 | ] 1470 | 1471 | [[package]] 1472 | name = "tinyvec" 1473 | version = "0.3.4" 1474 | source = "registry+https://github.com/rust-lang/crates.io-index" 1475 | checksum = "238ce071d267c5710f9d31451efec16c5ee22de34df17cc05e56cbc92e967117" 1476 | 1477 | [[package]] 1478 | name = "tokio" 1479 | version = "0.2.22" 1480 | source = "registry+https://github.com/rust-lang/crates.io-index" 1481 | checksum = "5d34ca54d84bf2b5b4d7d31e901a8464f7b60ac145a284fba25ceb801f2ddccd" 1482 | dependencies = [ 1483 | "bytes 0.5.6", 1484 | "fnv", 1485 | "futures-core", 1486 | "iovec", 1487 | "lazy_static", 1488 | "libc", 1489 | "memchr", 1490 | "mio", 1491 | "mio-named-pipes", 1492 | "mio-uds", 1493 | "num_cpus", 1494 | "pin-project-lite", 1495 | "signal-hook-registry", 1496 | "slab", 1497 | "tokio-macros", 1498 | "winapi 0.3.9", 1499 | ] 1500 | 1501 | [[package]] 1502 | name = "tokio-io" 1503 | version = "0.1.13" 1504 | source = "registry+https://github.com/rust-lang/crates.io-index" 1505 | checksum = "57fc868aae093479e3131e3d165c93b1c7474109d13c90ec0dda2a1bbfff0674" 1506 | dependencies = [ 1507 | "bytes 0.4.12", 1508 | "futures 0.1.30", 1509 | "log", 1510 | ] 1511 | 1512 | [[package]] 1513 | name = "tokio-macros" 1514 | version = "0.2.5" 1515 | source = "registry+https://github.com/rust-lang/crates.io-index" 1516 | checksum = "f0c3acc6aa564495a0f2e1d59fab677cd7f81a19994cfc7f3ad0e64301560389" 1517 | dependencies = [ 1518 | "proc-macro2", 1519 | "quote", 1520 | "syn", 1521 | ] 1522 | 1523 | [[package]] 1524 | name = "tokio-rustls" 1525 | version = "0.14.1" 1526 | source = "registry+https://github.com/rust-lang/crates.io-index" 1527 | checksum = "e12831b255bcfa39dc0436b01e19fea231a37db570686c06ee72c423479f889a" 1528 | dependencies = [ 1529 | "futures-core", 1530 | "rustls", 1531 | "tokio", 1532 | "webpki", 1533 | ] 1534 | 1535 | [[package]] 1536 | name = "tokio-util" 1537 | version = "0.3.1" 1538 | source = "registry+https://github.com/rust-lang/crates.io-index" 1539 | checksum = "be8242891f2b6cbef26a2d7e8605133c2c554cd35b3e4948ea892d6d68436499" 1540 | dependencies = [ 1541 | "bytes 0.5.6", 1542 | "futures-core", 1543 | "futures-sink", 1544 | "log", 1545 | "pin-project-lite", 1546 | "tokio", 1547 | ] 1548 | 1549 | [[package]] 1550 | name = "tower-service" 1551 | version = "0.3.0" 1552 | source = "registry+https://github.com/rust-lang/crates.io-index" 1553 | checksum = "e987b6bf443f4b5b3b6f38704195592cca41c5bb7aedd3c3693c7081f8289860" 1554 | 1555 | [[package]] 1556 | name = "tracing" 1557 | version = "0.1.21" 1558 | source = "registry+https://github.com/rust-lang/crates.io-index" 1559 | checksum = "b0987850db3733619253fe60e17cb59b82d37c7e6c0236bb81e4d6b87c879f27" 1560 | dependencies = [ 1561 | "cfg-if", 1562 | "log", 1563 | "pin-project-lite", 1564 | "tracing-core", 1565 | ] 1566 | 1567 | [[package]] 1568 | name = "tracing-core" 1569 | version = "0.1.17" 1570 | source = "registry+https://github.com/rust-lang/crates.io-index" 1571 | checksum = "f50de3927f93d202783f4513cda820ab47ef17f624b03c096e86ef00c67e6b5f" 1572 | dependencies = [ 1573 | "lazy_static", 1574 | ] 1575 | 1576 | [[package]] 1577 | name = "try-lock" 1578 | version = "0.2.3" 1579 | source = "registry+https://github.com/rust-lang/crates.io-index" 1580 | checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642" 1581 | 1582 | [[package]] 1583 | name = "typenum" 1584 | version = "1.12.0" 1585 | source = "registry+https://github.com/rust-lang/crates.io-index" 1586 | checksum = "373c8a200f9e67a0c95e62a4f52fbf80c23b4381c05a17845531982fa99e6b33" 1587 | 1588 | [[package]] 1589 | name = "ucd-trie" 1590 | version = "0.1.3" 1591 | source = "registry+https://github.com/rust-lang/crates.io-index" 1592 | checksum = "56dee185309b50d1f11bfedef0fe6d036842e3fb77413abef29f8f8d1c5d4c1c" 1593 | 1594 | [[package]] 1595 | name = "unicode-bidi" 1596 | version = "0.3.4" 1597 | source = "registry+https://github.com/rust-lang/crates.io-index" 1598 | checksum = "49f2bd0c6468a8230e1db229cff8029217cf623c767ea5d60bfbd42729ea54d5" 1599 | dependencies = [ 1600 | "matches", 1601 | ] 1602 | 1603 | [[package]] 1604 | name = "unicode-normalization" 1605 | version = "0.1.13" 1606 | source = "registry+https://github.com/rust-lang/crates.io-index" 1607 | checksum = "6fb19cf769fa8c6a80a162df694621ebeb4dafb606470b2b2fce0be40a98a977" 1608 | dependencies = [ 1609 | "tinyvec", 1610 | ] 1611 | 1612 | [[package]] 1613 | name = "unicode-width" 1614 | version = "0.1.8" 1615 | source = "registry+https://github.com/rust-lang/crates.io-index" 1616 | checksum = "9337591893a19b88d8d87f2cec1e73fad5cdfd10e5a6f349f498ad6ea2ffb1e3" 1617 | 1618 | [[package]] 1619 | name = "unicode-xid" 1620 | version = "0.2.1" 1621 | source = "registry+https://github.com/rust-lang/crates.io-index" 1622 | checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564" 1623 | 1624 | [[package]] 1625 | name = "untrusted" 1626 | version = "0.7.1" 1627 | source = "registry+https://github.com/rust-lang/crates.io-index" 1628 | checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" 1629 | 1630 | [[package]] 1631 | name = "url" 1632 | version = "2.1.1" 1633 | source = "registry+https://github.com/rust-lang/crates.io-index" 1634 | checksum = "829d4a8476c35c9bf0bbce5a3b23f4106f79728039b726d292bb93bc106787cb" 1635 | dependencies = [ 1636 | "idna", 1637 | "matches", 1638 | "percent-encoding", 1639 | ] 1640 | 1641 | [[package]] 1642 | name = "vec_map" 1643 | version = "0.8.2" 1644 | source = "registry+https://github.com/rust-lang/crates.io-index" 1645 | checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" 1646 | 1647 | [[package]] 1648 | name = "want" 1649 | version = "0.3.0" 1650 | source = "registry+https://github.com/rust-lang/crates.io-index" 1651 | checksum = "1ce8a968cb1cd110d136ff8b819a556d6fb6d919363c61534f6860c7eb172ba0" 1652 | dependencies = [ 1653 | "log", 1654 | "try-lock", 1655 | ] 1656 | 1657 | [[package]] 1658 | name = "wasi" 1659 | version = "0.9.0+wasi-snapshot-preview1" 1660 | source = "registry+https://github.com/rust-lang/crates.io-index" 1661 | checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" 1662 | 1663 | [[package]] 1664 | name = "wasi" 1665 | version = "0.10.0+wasi-snapshot-preview1" 1666 | source = "registry+https://github.com/rust-lang/crates.io-index" 1667 | checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f" 1668 | 1669 | [[package]] 1670 | name = "wasm-bindgen" 1671 | version = "0.2.68" 1672 | source = "registry+https://github.com/rust-lang/crates.io-index" 1673 | checksum = "1ac64ead5ea5f05873d7c12b545865ca2b8d28adfc50a49b84770a3a97265d42" 1674 | dependencies = [ 1675 | "cfg-if", 1676 | "wasm-bindgen-macro", 1677 | ] 1678 | 1679 | [[package]] 1680 | name = "wasm-bindgen-backend" 1681 | version = "0.2.68" 1682 | source = "registry+https://github.com/rust-lang/crates.io-index" 1683 | checksum = "f22b422e2a757c35a73774860af8e112bff612ce6cb604224e8e47641a9e4f68" 1684 | dependencies = [ 1685 | "bumpalo", 1686 | "lazy_static", 1687 | "log", 1688 | "proc-macro2", 1689 | "quote", 1690 | "syn", 1691 | "wasm-bindgen-shared", 1692 | ] 1693 | 1694 | [[package]] 1695 | name = "wasm-bindgen-macro" 1696 | version = "0.2.68" 1697 | source = "registry+https://github.com/rust-lang/crates.io-index" 1698 | checksum = "6b13312a745c08c469f0b292dd2fcd6411dba5f7160f593da6ef69b64e407038" 1699 | dependencies = [ 1700 | "quote", 1701 | "wasm-bindgen-macro-support", 1702 | ] 1703 | 1704 | [[package]] 1705 | name = "wasm-bindgen-macro-support" 1706 | version = "0.2.68" 1707 | source = "registry+https://github.com/rust-lang/crates.io-index" 1708 | checksum = "f249f06ef7ee334cc3b8ff031bfc11ec99d00f34d86da7498396dc1e3b1498fe" 1709 | dependencies = [ 1710 | "proc-macro2", 1711 | "quote", 1712 | "syn", 1713 | "wasm-bindgen-backend", 1714 | "wasm-bindgen-shared", 1715 | ] 1716 | 1717 | [[package]] 1718 | name = "wasm-bindgen-shared" 1719 | version = "0.2.68" 1720 | source = "registry+https://github.com/rust-lang/crates.io-index" 1721 | checksum = "1d649a3145108d7d3fbcde896a468d1bd636791823c9921135218ad89be08307" 1722 | 1723 | [[package]] 1724 | name = "web-sys" 1725 | version = "0.3.45" 1726 | source = "registry+https://github.com/rust-lang/crates.io-index" 1727 | checksum = "4bf6ef87ad7ae8008e15a355ce696bed26012b7caa21605188cfd8214ab51e2d" 1728 | dependencies = [ 1729 | "js-sys", 1730 | "wasm-bindgen", 1731 | ] 1732 | 1733 | [[package]] 1734 | name = "webpki" 1735 | version = "0.21.3" 1736 | source = "registry+https://github.com/rust-lang/crates.io-index" 1737 | checksum = "ab146130f5f790d45f82aeeb09e55a256573373ec64409fc19a6fb82fb1032ae" 1738 | dependencies = [ 1739 | "ring", 1740 | "untrusted", 1741 | ] 1742 | 1743 | [[package]] 1744 | name = "webpki-roots" 1745 | version = "0.20.0" 1746 | source = "registry+https://github.com/rust-lang/crates.io-index" 1747 | checksum = "0f20dea7535251981a9670857150d571846545088359b28e4951d350bdaf179f" 1748 | dependencies = [ 1749 | "webpki", 1750 | ] 1751 | 1752 | [[package]] 1753 | name = "winapi" 1754 | version = "0.2.8" 1755 | source = "registry+https://github.com/rust-lang/crates.io-index" 1756 | checksum = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a" 1757 | 1758 | [[package]] 1759 | name = "winapi" 1760 | version = "0.3.9" 1761 | source = "registry+https://github.com/rust-lang/crates.io-index" 1762 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 1763 | dependencies = [ 1764 | "winapi-i686-pc-windows-gnu", 1765 | "winapi-x86_64-pc-windows-gnu", 1766 | ] 1767 | 1768 | [[package]] 1769 | name = "winapi-build" 1770 | version = "0.1.1" 1771 | source = "registry+https://github.com/rust-lang/crates.io-index" 1772 | checksum = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc" 1773 | 1774 | [[package]] 1775 | name = "winapi-i686-pc-windows-gnu" 1776 | version = "0.4.0" 1777 | source = "registry+https://github.com/rust-lang/crates.io-index" 1778 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 1779 | 1780 | [[package]] 1781 | name = "winapi-util" 1782 | version = "0.1.5" 1783 | source = "registry+https://github.com/rust-lang/crates.io-index" 1784 | checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" 1785 | dependencies = [ 1786 | "winapi 0.3.9", 1787 | ] 1788 | 1789 | [[package]] 1790 | name = "winapi-x86_64-pc-windows-gnu" 1791 | version = "0.4.0" 1792 | source = "registry+https://github.com/rust-lang/crates.io-index" 1793 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 1794 | 1795 | [[package]] 1796 | name = "ws2_32-sys" 1797 | version = "0.2.1" 1798 | source = "registry+https://github.com/rust-lang/crates.io-index" 1799 | checksum = "d59cefebd0c892fa2dd6de581e937301d8552cb44489cdff035c6187cb63fa5e" 1800 | dependencies = [ 1801 | "winapi 0.2.8", 1802 | "winapi-build", 1803 | ] 1804 | 1805 | [[package]] 1806 | name = "xattr" 1807 | version = "0.2.2" 1808 | source = "registry+https://github.com/rust-lang/crates.io-index" 1809 | checksum = "244c3741f4240ef46274860397c7c74e50eb23624996930e484c16679633a54c" 1810 | dependencies = [ 1811 | "libc", 1812 | ] 1813 | 1814 | [[package]] 1815 | name = "yaml-rust" 1816 | version = "0.4.4" 1817 | source = "registry+https://github.com/rust-lang/crates.io-index" 1818 | checksum = "39f0c922f1a334134dc2f7a8b67dc5d25f0735263feec974345ff706bcf20b0d" 1819 | dependencies = [ 1820 | "linked-hash-map", 1821 | ] 1822 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "devcontainers-rs" 3 | version = "0.1.0" 4 | authors = ["Gustavo "] 5 | edition = "2018" 6 | 7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 8 | 9 | [dependencies] 10 | clap = "2.33.3" 11 | tar = "0.4.30" 12 | flate2 = { version = "0.2", features = ["tokio"] } 13 | tokio = { version = "0.2", features = ["full"] } 14 | serde = { version = "1.0.117", features = ["derive"] } 15 | rust-crypto = "0.2.36" 16 | json5 = "0.2.8" 17 | bollard = "0.8" 18 | http = "0.2.1" 19 | futures = "0.3.6" 20 | log = "0.4.0" 21 | env_logger = "0.8.1" 22 | dirs = "3.0.1" 23 | serde_yaml = "0.8.13" 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # devcontainers-rs 2 | 3 | For those who want to try out VSCode's devcontainers without VSCode. 4 | 5 | This project is mostly aiming to bring the VSCode's remote development experience to neovim, but at 6 | the end others editors/clients can be attached too. 7 | 8 | Please refer to the devcontainer.json [reference](https://code.visualstudio.com/docs/remote/devcontainerjson-reference) 9 | 10 | **⚠️ This is in active development and rapidly growing! Use at your own risk. And feel free to 11 | play around and let me know in the issues what features you'd like to see here. ⚠️** 12 | 13 | ## Requirements 14 | 15 | - docker 16 | 17 | ## HOW-TO 18 | 19 | 0- Run `devcontainers_rs -h` to see the available options. 20 | 21 | 1- Inside a directory containing the `.devcontainer` folder, run: 22 | 23 | ```bash 24 | $ devcontainers_rs up 25 | ``` 26 | 27 | 2- You can add custom settings to be applied to all projects in `$HOME/.config/devcontainer.json` 28 | 29 | Available settings: `application` (object), `mounts` (object), `postCreateCommand` (string/array), `postStartCommand` (string/array), `postAttachCommand` (string/array), `forwardPorts` (array), `env` (object) 30 | 31 | 2.1 - Starting editor/ide after setting up containers: 32 | 33 | ```json 34 | { 35 | ... 36 | "application": { "cmd": ["nvim-qt", "--server", "127.0.0.1:9797", "--nofork"] }, 37 | "forwardPorts": [9797], 38 | "postAttachCommand": ["bash", "start-headless-nvim.sh"] 39 | ... 40 | } 41 | ``` 42 | 43 | ## FEATURES: 44 | 45 | ⚙️ - DOING 46 | ✅ - DONE 47 | 48 | [✅] create containers based on image 49 | 50 | [✅] spawn custom application 51 | 52 | [✅] `postCreateCommand`, `postStartCommand`, `postAttachCommand` 53 | 54 | [✅] `appPort` 55 | 56 | [⚙️] `devPort` 57 | 58 | [✅] `forwardPorts` 59 | 60 | [ ] `initializeCommand` 61 | 62 | [✅] create containers based on `build` 63 | 64 | [✅] create containers from docker-compose 65 | 66 | [✅] stop containers 67 | 68 | [ ] destroy containers 69 | 70 | [ ] user management (`remoteUser`, `containerUser`, `updateRemoteUserUID`) 71 | 72 | -------------------------------------------------------------------------------- /src/devcontainer.rs: -------------------------------------------------------------------------------- 1 | use serde::{de, Deserialize, Deserializer}; 2 | use std::collections::BTreeMap; 3 | use std::path::PathBuf; 4 | 5 | use crate::errors::*; 6 | 7 | fn default_true() -> bool { 8 | true 9 | } 10 | 11 | #[derive(Deserialize, Default)] 12 | pub struct DevContainer { 13 | pub name: Option, 14 | 15 | pub image: Option, 16 | 17 | pub build: Option, 18 | 19 | #[serde(rename = "appPort")] 20 | pub app_port: Option, 21 | 22 | #[serde(rename = "containerEnv")] 23 | pub container_env: Option>, 24 | 25 | #[serde(rename = "remoteEnv")] 26 | pub remote_env: Option>, 27 | 28 | #[serde(rename = "containerUser")] 29 | pub container_user: Option, 30 | 31 | #[serde(rename = "remoteUser")] 32 | pub remote_user: Option, 33 | 34 | #[serde(default, rename = "updateRemoteUserUID")] 35 | pub update_remote_user_uid: bool, 36 | 37 | pub mounts: Option>, 38 | 39 | #[serde(rename = "workspaceMount")] 40 | pub workspace_mount: Option, 41 | 42 | #[serde(rename = "runArgs")] 43 | pub run_args: Option>, 44 | 45 | #[serde(rename = "overrideCommand", default = "default_true")] 46 | pub override_command: bool, 47 | 48 | #[serde(rename = "shutdownAction")] 49 | pub shutdown_action: Option, 50 | 51 | // Docker compose stuff 52 | #[serde(rename = "dockerComposeFile")] 53 | pub docker_compose_file: Option, 54 | 55 | pub service: Option, 56 | 57 | #[serde(rename = "runServices")] 58 | pub run_services: Option>, 59 | 60 | #[serde(rename = "forwardPorts")] 61 | pub forward_ports: Option>, 62 | 63 | #[serde(rename = "postCreateCommand")] 64 | pub post_create_command: Option, 65 | 66 | #[serde(rename = "postStartCommand")] 67 | pub post_start_command: Option, 68 | 69 | #[serde(rename = "postAttachCommand")] 70 | pub post_attach_command: Option, 71 | 72 | #[serde(rename = "initializeCommand")] 73 | pub initialize_command: Option, 74 | 75 | #[serde(rename = "devPort", default)] 76 | pub dev_port: i32, 77 | } 78 | 79 | #[derive(Deserialize)] 80 | pub struct BuildOpts { 81 | #[serde(alias = "dockerFile")] 82 | pub dockerfile: String, 83 | 84 | pub context: Option, 85 | 86 | pub args: Option>, 87 | 88 | pub target: Option, 89 | } 90 | 91 | #[derive(Deserialize)] 92 | #[serde(untagged)] 93 | pub enum AppPort { 94 | Port(u32), 95 | Ports(Vec), 96 | PortStr(String), 97 | } 98 | 99 | #[derive(Deserialize)] 100 | #[serde(untagged)] 101 | pub enum DockerComposeFile { 102 | File(String), 103 | Files(Vec), 104 | } 105 | 106 | #[derive(Deserialize)] 107 | #[serde(untagged)] 108 | pub enum CommandLineVec { 109 | Line(String), 110 | Args(Vec), 111 | } 112 | 113 | #[derive(Debug, PartialEq)] 114 | pub enum ShutdownAction { 115 | None, 116 | StopContainer, 117 | StopCompose, 118 | } 119 | 120 | // Specify which mode should this devcontainer operate on 121 | pub enum Mode { 122 | Image, 123 | Build, 124 | Compose, 125 | } 126 | 127 | impl<'de> Deserialize<'de> for ShutdownAction { 128 | fn deserialize(deserializer: D) -> Result 129 | where 130 | D: Deserializer<'de>, 131 | { 132 | let s = String::deserialize(deserializer)?.to_lowercase(); 133 | let state = match s.as_str() { 134 | "none" => ShutdownAction::None, 135 | "stopcontainer" => ShutdownAction::StopContainer, 136 | "stopcompose" => ShutdownAction::StopCompose, 137 | other => { 138 | return Err(de::Error::custom(format!( 139 | "Invalid shutdown action '{}'", 140 | other 141 | ))); 142 | } 143 | }; 144 | Ok(state) 145 | } 146 | } 147 | 148 | impl DevContainer { 149 | pub fn get_mode(&self) -> Mode { 150 | if self.image.is_some() { 151 | Mode::Image 152 | } else if self.build.is_some() { 153 | Mode::Build 154 | } else { 155 | Mode::Compose 156 | } 157 | } 158 | 159 | pub fn validate(&self) -> Result<(), Error> { 160 | // image conflicts with docker_compose_file 161 | let sources = vec![ 162 | self.image.is_some(), 163 | self.docker_compose_file.is_some(), 164 | self.build.is_some(), 165 | ]; 166 | if sources.iter().filter(|v| **v).count() > 1 { 167 | return Err(Error::InvalidConfig( 168 | "Please specify only one of: image, dockerComposeFile or build".to_string(), 169 | )); 170 | } 171 | if self.image.is_none() && self.docker_compose_file.is_none() && self.build.is_none() { 172 | return Err(Error::InvalidConfig( 173 | "Please specify at least one of: image, dockerComposeFile or build".to_string(), 174 | )); 175 | } 176 | 177 | if let Some(img) = self.image.as_ref() { 178 | if img.trim().is_empty() { 179 | return Err(Error::InvalidConfig(format!("Invalid image: '{}'", img))); 180 | } 181 | } 182 | 183 | if let Some(opts) = self.build.as_ref() { 184 | if opts.dockerfile.trim().is_empty() { 185 | return Err(Error::InvalidConfig(format!( 186 | "Invalid docker file: '{}'", 187 | opts.dockerfile 188 | ))); 189 | } 190 | } 191 | 192 | if let Some(compose) = self.docker_compose_file.as_ref() { 193 | if match &compose { 194 | DockerComposeFile::File(dcf) => dcf.trim().is_empty(), 195 | DockerComposeFile::Files(v) => v.is_empty(), 196 | } { 197 | return Err(Error::InvalidConfig( 198 | "Invalid docker-compose file".to_string(), 199 | )); 200 | } 201 | 202 | if match self.service.as_ref() { 203 | None => true, 204 | Some(s) if s.is_empty() => true, 205 | _ => false, 206 | } { 207 | return Err(Error::InvalidConfig("Invalid service!".to_string())); 208 | } 209 | } 210 | 211 | Ok(()) 212 | } 213 | 214 | pub fn get_name(&self, path: &PathBuf) -> String { 215 | self.name 216 | .as_ref() 217 | .map(|s| s.to_string()) 218 | .unwrap_or(path.file_name().unwrap().to_string_lossy().to_string()) 219 | } 220 | } 221 | 222 | impl CommandLineVec { 223 | pub fn to_args_vec(&self) -> Vec { 224 | match self { 225 | CommandLineVec::Line(line) => line.clone().split(" ").map(|s| s.to_string()).collect(), 226 | CommandLineVec::Args(args) => args.clone(), 227 | } 228 | } 229 | } 230 | -------------------------------------------------------------------------------- /src/devcontainer_tests.rs: -------------------------------------------------------------------------------- 1 | use super::devcontainer::*; 2 | 3 | #[test] 4 | #[should_panic] 5 | fn test_no_valid_source() { 6 | let dc = DevContainer::default(); 7 | dc.validate().unwrap() 8 | } 9 | 10 | #[test] 11 | #[should_panic] 12 | fn test_multiple_sources() { 13 | let dc = DevContainer { 14 | image: Some("myimage".to_string()), 15 | docker_compose_file: Some(DockerComposeFile::File("docker-compose.yaml".to_string())), 16 | ..Default::default() 17 | }; 18 | dc.validate().unwrap() 19 | } 20 | 21 | #[test] 22 | #[should_panic] 23 | fn test_invalid_image() { 24 | let dc = DevContainer { 25 | image: Some("".to_string()), 26 | ..Default::default() 27 | }; 28 | dc.validate().unwrap() 29 | } 30 | 31 | #[test] 32 | #[should_panic] 33 | fn test_invalid_docker_compose() { 34 | let dc = DevContainer { 35 | docker_compose_file: Some(DockerComposeFile::File("".to_string())), 36 | ..Default::default() 37 | }; 38 | dc.validate().unwrap() 39 | } 40 | 41 | #[test] 42 | #[should_panic] 43 | fn test_invalid_docker_compose_arr() { 44 | let dc = DevContainer { 45 | docker_compose_file: Some(DockerComposeFile::Files(vec![])), 46 | ..Default::default() 47 | }; 48 | dc.validate().unwrap() 49 | } 50 | 51 | #[test] 52 | #[should_panic] 53 | fn test_invalid_docker_compose_service() { 54 | let dc = DevContainer { 55 | docker_compose_file: Some(DockerComposeFile::File("docker-compose.yaml".to_string())), 56 | service: None, 57 | ..Default::default() 58 | }; 59 | dc.validate().unwrap() 60 | } 61 | 62 | #[test] 63 | #[should_panic] 64 | fn test_invalid_docker_compose_service_empty_string() { 65 | let dc = DevContainer { 66 | docker_compose_file: Some(DockerComposeFile::File("docker-compose.yaml".to_string())), 67 | service: Some("".to_string()), 68 | ..Default::default() 69 | }; 70 | dc.validate().unwrap() 71 | } 72 | -------------------------------------------------------------------------------- /src/errors.rs: -------------------------------------------------------------------------------- 1 | use bollard::errors::Error as DockerError; 2 | 3 | #[derive(Debug)] 4 | pub enum Error { 5 | ConfigDoesNotExist(String), 6 | InvalidConfig(String), 7 | UpError(UpError), 8 | DockerError(DockerError), 9 | DownError(DownError), 10 | NoDevContainer, 11 | InvalidSettings(String), 12 | ExecCommandError(String), 13 | Other(String), 14 | } 15 | 16 | #[derive(Debug)] 17 | pub enum UpError { 18 | ContainerCreate(String), 19 | ApplicationSpawn(String), 20 | ExecCommand(String), 21 | ImagePull(String), 22 | ComposeError(String), 23 | } 24 | 25 | #[derive(Debug)] 26 | pub enum DownError {} 27 | 28 | impl std::fmt::Display for Error { 29 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 30 | match self { 31 | Error::ConfigDoesNotExist(file) => write!(f, "Config file does not exist: {}", file), 32 | Error::InvalidConfig(err) => write!(f, "Config is not valid: {}", err), 33 | Error::UpError(err) => write!(f, "Error trying to start project: {}", err), 34 | Error::DockerError(err) => { 35 | write!(f, "Error trying to communicate with docker: {}", err) 36 | } 37 | Error::DownError(err) => write!(f, "Error trying to shut down project: {}", err), 38 | Error::NoDevContainer => write!(f, "Unexpected error! No devcontainer project found!"), 39 | Error::InvalidSettings(err) => write!(f, "Error trying to parse settings: {}", err), 40 | Error::ExecCommandError(err) => write!(f, "Error trying to execute command: {}", err), 41 | Error::Other(err) => write!(f, "Unexpected error: {}", err), 42 | } 43 | } 44 | } 45 | 46 | impl std::fmt::Display for UpError { 47 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 48 | match self { 49 | UpError::ContainerCreate(err) => write!(f, "Failed to create container: {}", err), 50 | UpError::ApplicationSpawn(err) => write!(f, "Failed to spawn application: {}", err), 51 | UpError::ExecCommand(err) => write!(f, "Failed to execute command: {}", err), 52 | UpError::ImagePull(err) => { 53 | write!(f, "Failed while trying to pull docker image: {}", err) 54 | } 55 | UpError::ComposeError(err) => write!(f, "Failed to execute docker-compose: {}", err), 56 | } 57 | } 58 | } 59 | 60 | impl std::convert::From for Error { 61 | fn from(e: UpError) -> Self { 62 | Error::UpError(e) 63 | } 64 | } 65 | 66 | impl std::convert::From for Error { 67 | fn from(e: DockerError) -> Self { 68 | Error::DockerError(e) 69 | } 70 | } 71 | 72 | impl std::convert::From for Error { 73 | fn from(e: DownError) -> Self { 74 | Error::DownError(e) 75 | } 76 | } 77 | 78 | impl std::fmt::Display for DownError { 79 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 80 | write!(f, "Nothing for now") 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] 2 | extern crate log; 3 | 4 | use clap::{App, Arg, SubCommand}; 5 | use std::path::PathBuf; 6 | use tokio; 7 | 8 | mod mount_from_str; 9 | #[cfg(test)] 10 | mod mount_from_str_tests; 11 | 12 | mod devcontainer; 13 | #[cfg(test)] 14 | mod devcontainer_tests; 15 | 16 | mod settings; 17 | mod settings_compose_model; 18 | 19 | mod project; 20 | #[cfg(test)] 21 | mod project_tests; 22 | 23 | mod errors; 24 | 25 | #[tokio::main] 26 | async fn main() { 27 | let env = env_logger::Env::default() 28 | .filter_or("LOG_LEVEL", "info") 29 | .write_style_or("LOG_STYLE", "always"); 30 | env_logger::init_from_env(env); 31 | 32 | let matches = App::new("devcontainer-rs") 33 | .version("0.1") 34 | .author("Gustavo Sampaio ") 35 | .about("An open-source runner for the devcontainer format") 36 | .arg( 37 | Arg::with_name("docker-host") 38 | .short("a") 39 | .long("host") 40 | .value_name("STRING") 41 | .help("Use the specified address to connect to docker") 42 | .takes_value(true), 43 | ) 44 | .arg( 45 | Arg::with_name("no-user-settings") 46 | .short("s") 47 | .long("no-user-settings") 48 | .help("Ignore global user settings") 49 | .takes_value(false), 50 | ) 51 | .arg( 52 | Arg::with_name("path") 53 | .short("c") 54 | .long("path") 55 | .value_name("FILE") 56 | .help("Sets a custom cwd. The path that contains the .devcontainer folder") 57 | .takes_value(true), 58 | ) 59 | .subcommand( 60 | SubCommand::with_name("up") 61 | .about("starts the devcontainer") 62 | .arg( 63 | Arg::with_name("no-wait") 64 | .short("d") 65 | .long("no-wait") 66 | .help("Do not wait for the client") 67 | .takes_value(false), 68 | ), 69 | ) 70 | .subcommand(SubCommand::with_name("down").about("stops the devcontainer")) 71 | .get_matches(); 72 | 73 | let path = matches.value_of("path").map(PathBuf::from); 74 | 75 | let should_load_user_settings = match matches.is_present("no-user-settings") { 76 | true => Some(false), 77 | false => None, 78 | }; 79 | 80 | let mut project = project::Project::new(project::ProjectOpts { 81 | path, 82 | should_load_user_settings, 83 | ..project::ProjectOpts::default() 84 | }) 85 | .unwrap(); 86 | project.docket_host = matches.value_of("docker-host").map(|s| s.to_string()); 87 | 88 | if let Err(err) = project.load().await { 89 | panic!("Error found validating the config file: {}", err); 90 | } 91 | 92 | let res = match matches.subcommand() { 93 | ("up", Some(sub_matches)) => { 94 | let should_wait = !sub_matches.is_present("no-wait"); 95 | 96 | project.up(should_wait).await 97 | } 98 | ("down", Some(_)) => project.down(None, false).await, 99 | _ => Ok(()), 100 | }; 101 | 102 | res.unwrap() 103 | } 104 | -------------------------------------------------------------------------------- /src/mount_from_str.rs: -------------------------------------------------------------------------------- 1 | use bollard::service::{Mount, MountTypeEnum}; 2 | use std::str::FromStr; 3 | 4 | use super::errors::Error; 5 | 6 | pub trait MountExt: Sized { 7 | fn from_comma_string(s: &str) -> Result; 8 | fn from_colon_string(s: &str) -> Result; 9 | 10 | fn parse_from_str(s: &str) -> Result; 11 | } 12 | 13 | impl MountExt for Mount { 14 | fn from_colon_string(s: &str) -> Result { 15 | let parts: Vec<&str> = s.split(":").collect(); 16 | 17 | if parts.len() < 2 { 18 | return Err(Error::InvalidConfig(format!("Invalid mount point: {}", s))); 19 | } 20 | 21 | Ok(Mount { 22 | source: Some(parts[0].to_string()), 23 | target: Some(parts[1].to_string()), 24 | typ: Some(MountTypeEnum::BIND), 25 | ..Mount::default() 26 | }) 27 | } 28 | 29 | fn from_comma_string(s: &str) -> Result { 30 | let parts: Vec<&str> = s.split(",").collect(); 31 | 32 | if parts.len() == 0 { 33 | return Err(Error::InvalidConfig("Invalid mount point".to_string())); 34 | } 35 | 36 | let mut mount = Mount::default(); 37 | 38 | for part in parts { 39 | let attr_parts: Vec<&str> = part.split("=").collect(); 40 | if attr_parts.len() < 2 { 41 | return Err(Error::InvalidConfig(format!("Invalid mount point: {}", s))); 42 | } 43 | 44 | let attr_name = attr_parts[0]; 45 | let attr_value = attr_parts[1]; 46 | 47 | match attr_name { 48 | "source" => { 49 | mount.source = Some(attr_value.to_string()); 50 | } 51 | "target" => { 52 | mount.target = Some(attr_value.to_string()); 53 | } 54 | "type" => { 55 | mount.typ = Some(MountTypeEnum::from_str(attr_value).map_err(|err| { 56 | Error::InvalidConfig(format!( 57 | "Invalid mount point type: {} {}", 58 | attr_value, err 59 | )) 60 | })?); 61 | } 62 | "consistency" => { 63 | mount.consistency = Some(attr_value.to_string()); 64 | } 65 | attr => { 66 | return Err(Error::InvalidConfig(format!( 67 | "Invalid attr '{}' for mount point: {}", 68 | attr, s 69 | ))) 70 | } 71 | }; 72 | } 73 | 74 | Ok(mount) 75 | } 76 | 77 | fn parse_from_str(s: &str) -> Result { 78 | if s.contains(",") { 79 | Self::from_comma_string(s) 80 | } else { 81 | Self::from_colon_string(s) 82 | } 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/mount_from_str_tests.rs: -------------------------------------------------------------------------------- 1 | use bollard::service::Mount; 2 | 3 | use super::mount_from_str::*; 4 | 5 | #[test] 6 | fn test_from_comma() { 7 | let m = Mount::parse_from_str("/home/user/.config/nvim:/root/.config/nvim").unwrap(); 8 | 9 | assert_eq!(m.source, Some("/home/user/.config/nvim".to_string())); 10 | assert_eq!(m.target, Some("/root/.config/nvim".to_string())); 11 | } 12 | -------------------------------------------------------------------------------- /src/project.rs: -------------------------------------------------------------------------------- 1 | use bollard::{ 2 | container::{ 3 | self, CreateContainerOptions, ListContainersOptions, StartContainerOptions, 4 | StopContainerOptions, 5 | }, 6 | exec::{CreateExecOptions, StartExecOptions, StartExecResults}, 7 | image::{BuildImageOptions, CreateImageOptions}, 8 | service::{ContainerSummaryInner, HostConfig, Mount, PortBinding}, 9 | Docker, API_DEFAULT_VERSION, 10 | }; 11 | use crypto::digest::Digest; 12 | use crypto::sha1::Sha1; 13 | use flate2::write::GzEncoder; 14 | use flate2::Compression; 15 | use futures::StreamExt; 16 | use json5; 17 | use serde_yaml; 18 | use std::collections::HashMap; 19 | use std::fs::File; 20 | use std::io::prelude::*; 21 | use std::path::PathBuf; 22 | use tokio::fs; 23 | use tokio::process::{Child, Command}; 24 | use tokio::signal; 25 | 26 | use crate::devcontainer::*; 27 | use crate::errors::*; 28 | use crate::mount_from_str::*; 29 | use crate::settings::*; 30 | use crate::settings_compose_model::*; 31 | 32 | #[derive(Debug)] 33 | pub enum CommandHook { 34 | PostCreate, 35 | PostStart, 36 | PostAttach, 37 | } 38 | 39 | pub struct Project { 40 | pub path: PathBuf, 41 | pub filename: String, 42 | 43 | pub docket_host: Option, 44 | 45 | pub devcontainer: Option, 46 | 47 | pub settings: Option, 48 | 49 | pub opts: ProjectOpts, 50 | } 51 | 52 | impl std::default::Default for Project { 53 | fn default() -> Self { 54 | let path = std::env::current_dir().unwrap(); 55 | Project { 56 | filename: "devcontainer.json".to_string(), 57 | path, 58 | 59 | docket_host: None, 60 | 61 | devcontainer: None, 62 | 63 | settings: None, 64 | 65 | opts: ProjectOpts::default(), 66 | } 67 | } 68 | } 69 | 70 | #[derive(Default)] 71 | pub struct ProjectOpts { 72 | pub path: Option, 73 | pub filename: Option, 74 | pub should_load_user_settings: Option, 75 | } 76 | 77 | impl Project { 78 | pub fn new(opts: ProjectOpts) -> Result { 79 | let mut dc = Self::default(); 80 | if let Some(pb) = opts.path.as_ref() { 81 | pb.canonicalize() 82 | .map_err(|err| Error::InvalidConfig(err.to_string()))?; 83 | dc.path = pb.clone(); 84 | } 85 | 86 | for ancestor in dc.path.clone().ancestors() { 87 | if ancestor.join(".devcontainer").exists() { 88 | dc.path = ancestor 89 | .to_path_buf() 90 | .canonicalize() 91 | .map_err(|err| Error::InvalidConfig(err.to_string()))?; 92 | } 93 | } 94 | 95 | if let Some(f) = opts.filename.clone() { 96 | dc.filename = f; 97 | } 98 | 99 | dc.opts = opts; 100 | 101 | Ok(dc) 102 | } 103 | 104 | fn get_devcontainer_folder(&self) -> PathBuf { 105 | let mut path = self.path.clone(); 106 | path.push(".devcontainer"); 107 | 108 | path 109 | } 110 | 111 | pub async fn load(&mut self) -> Result<(), Error> { 112 | self.settings = match self.opts.should_load_user_settings.as_ref() { 113 | Some(false) => { 114 | warn!("Ignoring user settings because of -s"); 115 | Some(Settings::default()) 116 | } 117 | _ => Some(Settings::load().await?), 118 | }; 119 | 120 | let mut filename = self.get_devcontainer_folder(); 121 | filename.push(self.filename.clone()); 122 | 123 | info!("Loading project: {}", self.path.to_str().unwrap()); 124 | info!("devcontainer.json: {}", filename.to_str().unwrap()); 125 | 126 | if !filename.exists() { 127 | return Err(Error::ConfigDoesNotExist( 128 | filename.to_str().unwrap().to_string(), 129 | )); 130 | } 131 | 132 | let contents = fs::read_to_string(filename.as_path()) 133 | .await 134 | .map_err(|err| Error::InvalidConfig(err.to_string()))?; 135 | 136 | let devcontainer: DevContainer = 137 | json5::from_str(&contents).map_err(|err| Error::InvalidConfig(err.to_string()))?; 138 | 139 | if let Err(err) = devcontainer.validate() { 140 | return Err(err); 141 | } 142 | 143 | self.devcontainer = Some(devcontainer); 144 | 145 | Ok(()) 146 | } 147 | 148 | fn get_devcontainer_envs(&self, devcontainer: &DevContainer) -> HashMap { 149 | let mut envs = HashMap::new(); 150 | 151 | envs.insert( 152 | "DEVCONTAINER_PROJECT".to_string(), 153 | devcontainer.get_name(&self.path), 154 | ); 155 | 156 | envs 157 | } 158 | 159 | async fn spawn_application(&self, devcontainer: &DevContainer) -> Result { 160 | info!("Found application settings. Spawning"); 161 | let application = self 162 | .settings 163 | .as_ref() 164 | .unwrap() 165 | .application 166 | .as_ref() 167 | .unwrap(); 168 | 169 | let args = application.cmd.to_args_vec(); 170 | 171 | let mut builder = &mut Command::new(args[0].clone()); 172 | builder = builder.args(args.iter().skip(1)); 173 | 174 | if let Some(remote_envs) = devcontainer.remote_env.as_ref() { 175 | builder.envs(remote_envs); 176 | } 177 | 178 | let devcontainer_envs = self.get_devcontainer_envs(devcontainer); 179 | debug!("{:?}", devcontainer_envs); 180 | 181 | builder.envs(devcontainer_envs); 182 | 183 | let child = builder 184 | .spawn() 185 | .map_err(|err| UpError::ApplicationSpawn(err.to_string()))?; 186 | Ok(child) 187 | } 188 | 189 | async fn docker_build_image( 190 | &self, 191 | docker: &Docker, 192 | devcontainer: &DevContainer, 193 | ) -> Result { 194 | let devcontainer_dir = self.get_devcontainer_folder(); 195 | 196 | let dockerfile = devcontainer.build.as_ref().unwrap().dockerfile.clone(); 197 | let mut file = File::open(devcontainer_dir.join(dockerfile.clone())).unwrap(); 198 | let mut contents = String::new(); 199 | let mut hasher = Sha1::new(); 200 | file.read_to_string(&mut contents).unwrap(); 201 | hasher.input_str(&contents); 202 | let image_name = format!("devcontainer_{}", &hasher.result_str()[0..10]); 203 | info!("Building image: {}", image_name); 204 | 205 | // API reads the Dockerfile from a tarball 206 | let enc = GzEncoder::new(Vec::new(), Compression::default()); 207 | let mut tar = tar::Builder::new(enc); 208 | tar.append_dir_all("devcontainer/", devcontainer_dir) 209 | .unwrap(); 210 | let dockerfile_path: PathBuf = ["devcontainer", &dockerfile].iter().collect(); 211 | 212 | let options = BuildImageOptions { 213 | dockerfile: dockerfile_path.to_str().unwrap(), 214 | t: &image_name.clone(), 215 | rm: true, 216 | ..std::default::Default::default() 217 | }; 218 | 219 | let mut stream = docker.build_image( 220 | options, 221 | None, 222 | Some(tar.into_inner().unwrap().finish().unwrap().into()), 223 | ); 224 | 225 | while let Some(pull_result) = stream.next().await { 226 | match pull_result { 227 | Ok(output) => { 228 | debug!("Pull output: {:?}", output); 229 | } 230 | Err(e) => { 231 | error!("Pull error: {}", e); 232 | return Err(UpError::ImagePull(e.to_string())); 233 | } 234 | } 235 | } 236 | 237 | info!("Building image: done"); 238 | 239 | Ok(image_name) 240 | } 241 | 242 | async fn docker_pull_image(&self, docker: &Docker, image: String) -> Result<(), UpError> { 243 | info!("Pulling image: {}", image); 244 | let options = Some(CreateImageOptions { 245 | from_image: image, 246 | ..Default::default() 247 | }); 248 | 249 | let mut stream = docker.create_image(options, None, None); 250 | 251 | while let Some(pull_result) = stream.next().await { 252 | match pull_result { 253 | Ok(output) => { 254 | debug!("Pull output: {:?}", output); 255 | } 256 | Err(e) => { 257 | error!("Pull error: {}", e); 258 | return Err(UpError::ImagePull(e.to_string())); 259 | } 260 | } 261 | } 262 | 263 | info!("Pulling image: done"); 264 | 265 | Ok(()) 266 | } 267 | 268 | async fn docker_exec( 269 | &self, 270 | docker: &Docker, 271 | id: String, 272 | cmd: &CommandLineVec, 273 | ) -> Result<(), Error> { 274 | info!("Executing command in container: {}", id); 275 | 276 | let options = CreateExecOptions { 277 | cmd: Some(cmd.to_args_vec()), 278 | attach_stdout: Some(true), 279 | attach_stderr: Some(true), 280 | ..Default::default() 281 | }; 282 | 283 | let exec = docker.create_exec(id.as_str(), options).await?; 284 | 285 | let mut stream = docker.start_exec(exec.id.as_str(), None::); 286 | 287 | debug!("Args: {:?}", cmd.to_args_vec()); 288 | while let Some(exec_result) = stream.next().await { 289 | match exec_result? { 290 | StartExecResults::Attached { log } => match log { 291 | container::LogOutput::StdOut { message: bytes } => { 292 | debug!("STDOUT: {}", std::str::from_utf8(&bytes).unwrap()) 293 | } 294 | container::LogOutput::StdErr { message: bytes } => { 295 | debug!("STDERR: {}", std::str::from_utf8(&bytes).unwrap()) 296 | } 297 | container::LogOutput::Console { message: bytes } => { 298 | debug!("CONSOLE: {}", std::str::from_utf8(&bytes).unwrap()) 299 | } 300 | container::LogOutput::StdIn { message: _ } => unreachable!(), 301 | }, 302 | StartExecResults::Detached => { /*nothing to do here*/ } 303 | } 304 | } 305 | 306 | let inspect = docker.inspect_exec(&exec.id).await?; 307 | if let Some(exit_code) = inspect.exit_code.as_ref() { 308 | if *exit_code != 0 { 309 | return Err(Error::ExecCommandError(format!("Exit code: {}", exit_code))); 310 | } 311 | } 312 | 313 | Ok(()) 314 | } 315 | 316 | async fn run_hook( 317 | &self, 318 | docker: &Docker, 319 | devcontainer: &DevContainer, 320 | container_id: String, 321 | hook: CommandHook, 322 | ) -> Result<(), Error> { 323 | let cmd_st = match hook { 324 | CommandHook::PostCreate => devcontainer.post_create_command.as_ref(), 325 | CommandHook::PostStart => devcontainer.post_start_command.as_ref(), 326 | CommandHook::PostAttach => devcontainer.post_attach_command.as_ref(), 327 | }; 328 | 329 | if let Some(cmd) = cmd_st { 330 | info!("Executing hook: {:?}", hook); 331 | self.docker_exec(docker, container_id.clone(), cmd).await?; 332 | } 333 | 334 | // user hooks 335 | let cmd_st = match hook { 336 | CommandHook::PostCreate => self.settings.as_ref().unwrap().post_create_command.as_ref(), 337 | CommandHook::PostStart => self.settings.as_ref().unwrap().post_start_command.as_ref(), 338 | CommandHook::PostAttach => self.settings.as_ref().unwrap().post_attach_command.as_ref(), 339 | }; 340 | 341 | if let Some(cmd) = cmd_st { 342 | info!("Executing user hook: {:?}", hook); 343 | return self.docker_exec(docker, container_id, cmd).await; 344 | } 345 | 346 | Ok(()) 347 | } 348 | 349 | async fn container_opts_build_ports( 350 | &self, 351 | devcontainer: &DevContainer, 352 | config: &mut container::Config, 353 | ) -> Result<(), Error> { 354 | let mut ports_exposed: HashMap> = HashMap::new(); 355 | 356 | let mut host_config = match config.host_config.clone() { 357 | Some(hc) => hc, 358 | None => HostConfig::default(), 359 | }; 360 | 361 | let mut port_bindings = match host_config.port_bindings.clone() { 362 | Some(m) => m, 363 | None => HashMap::new(), 364 | }; 365 | 366 | if let Some(app_port) = devcontainer.app_port.as_ref() { 367 | match app_port { 368 | AppPort::Port(p) => { 369 | port_bindings.insert( 370 | format!("{}/tcp", p), 371 | Some(vec![PortBinding { 372 | host_ip: Some("0.0.0.0".to_string()), 373 | host_port: Some(format!("{}", p)), 374 | }]), 375 | ); 376 | ports_exposed.insert(format!("{}/tcp", p), HashMap::new()); 377 | } 378 | AppPort::Ports(ports) => { 379 | for p in ports { 380 | port_bindings.insert( 381 | format!("{}/tcp", p), 382 | Some(vec![PortBinding { 383 | host_ip: Some(String::from("0.0.0.0")), 384 | host_port: Some(format!("{}", p)), 385 | }]), 386 | ); 387 | ports_exposed.insert(format!("{}/tcp", p), HashMap::new()); 388 | } 389 | } 390 | AppPort::PortStr(p_str) => { 391 | port_bindings.insert( 392 | format!("{}/tcp", p_str), 393 | Some(vec![PortBinding { 394 | host_ip: Some(String::from("0.0.0.0")), 395 | host_port: Some(p_str.clone()), 396 | }]), 397 | ); 398 | ports_exposed.insert(format!("{}/tcp", p_str), HashMap::new()); 399 | } 400 | }; 401 | } 402 | 403 | if let Some(forward_ports) = devcontainer.forward_ports.as_ref() { 404 | for port in forward_ports { 405 | port_bindings.insert( 406 | format!("{}/tcp", port), 407 | Some(vec![PortBinding { 408 | host_ip: Some(String::from("0.0.0.0")), 409 | host_port: Some(format!("{}", port)), 410 | }]), 411 | ); 412 | ports_exposed.insert(format!("{}/tcp", port), HashMap::new()); 413 | } 414 | } 415 | 416 | // user ports 417 | if let Some(forward_ports) = self.settings.as_ref().unwrap().forward_ports.as_ref() { 418 | for port in forward_ports { 419 | port_bindings.insert( 420 | format!("{}/tcp", port), 421 | Some(vec![PortBinding { 422 | host_ip: Some(String::from("0.0.0.0")), 423 | host_port: Some(format!("{}", port)), 424 | }]), 425 | ); 426 | ports_exposed.insert(format!("{}/tcp", port), HashMap::new()); 427 | } 428 | } 429 | 430 | host_config.port_bindings = Some(port_bindings); 431 | config.host_config = Some(host_config); 432 | 433 | config.exposed_ports = Some(ports_exposed); 434 | 435 | Ok(()) 436 | } 437 | 438 | async fn container_opts_build_envs( 439 | &self, 440 | devcontainer: &DevContainer, 441 | config: &mut container::Config, 442 | ) -> Result<(), Error> { 443 | let mut envs: Vec = self 444 | .get_devcontainer_envs(devcontainer) 445 | .iter() 446 | .map(|(key, value)| format!("{}={}", key, value)) 447 | .collect(); 448 | 449 | if let Some(env_map) = devcontainer.container_env.as_ref() { 450 | envs.extend( 451 | env_map 452 | .iter() 453 | .map(|(key, value)| format!("{}={}", key, value)) 454 | .collect::>(), 455 | ); 456 | }; 457 | 458 | if let Some(env_map) = self.settings.as_ref().unwrap().envs.as_ref() { 459 | envs.extend( 460 | env_map 461 | .iter() 462 | .map(|(key, value)| format!("{}={}", key, value)), 463 | ) 464 | } 465 | 466 | config.env = Some(envs); 467 | 468 | Ok(()) 469 | } 470 | 471 | async fn container_opts_build_mounts( 472 | &self, 473 | devcontainer: &DevContainer, 474 | config: &mut container::Config, 475 | ) -> Result<(), Error> { 476 | let mut host_config = match config.host_config.clone() { 477 | Some(hc) => hc, 478 | None => HostConfig::default(), 479 | }; 480 | 481 | let mut mounts = match host_config.mounts.clone() { 482 | Some(m) => m, 483 | None => vec![], 484 | }; 485 | 486 | let wk_mount = match devcontainer.workspace_mount.as_ref() { 487 | None => { 488 | let current_dir = self.path.to_str().unwrap(); 489 | debug!( 490 | "Mounting default workspace folder: {} to /workspace", 491 | current_dir 492 | ); 493 | Mount::parse_from_str( 494 | format!( 495 | "source={},target=/workspace,type=bind,consistency=cached", 496 | current_dir, 497 | ) 498 | .as_str(), 499 | )? 500 | } 501 | Some(p) => Mount::parse_from_str(p.as_str())?, 502 | }; 503 | 504 | mounts.push(wk_mount); 505 | 506 | if let Some(dev_mounts) = devcontainer.mounts.as_ref() { 507 | for m in dev_mounts.iter() { 508 | mounts.push(Mount::parse_from_str(m.as_str())?); 509 | } 510 | } 511 | 512 | if let Some(user_mounts) = self.settings.as_ref().unwrap().mounts.as_ref() { 513 | for m in user_mounts.iter() { 514 | debug!("Adding user mount: {}", m); 515 | mounts.push(Mount::parse_from_str(m.as_str())?); 516 | } 517 | } 518 | 519 | host_config.mounts = Some(mounts); 520 | config.host_config = Some(host_config); 521 | 522 | Ok(()) 523 | } 524 | 525 | async fn container_opts_build_cmd( 526 | &self, 527 | devcontainer: &DevContainer, 528 | config: &mut container::Config, 529 | ) -> Result<(), Error> { 530 | // TODO find a way to add run args (capabilities and seccomp) 531 | //if let Some(args) = devcontainer.run_args.as_ref() { 532 | //opts_ref = opts_ref.cmd(args.iter().map(|s| s.as_str()).collect()); 533 | //} 534 | 535 | if devcontainer.override_command { 536 | config.cmd = Some( 537 | vec!["/bin/sh", "-c", "while sleep 1000; do :; done"] 538 | .iter() 539 | .map(|s| s.to_string()) 540 | .collect(), 541 | ); 542 | } 543 | 544 | Ok(()) 545 | } 546 | 547 | async fn get_container_from_filters( 548 | &self, 549 | docker: &Docker, 550 | filters: &HashMap<&str, Vec<&str>>, 551 | ) -> Result, Error> { 552 | let options = Some(ListContainersOptions { 553 | all: true, 554 | filters: filters.clone(), 555 | ..Default::default() 556 | }); 557 | 558 | let result = docker.list_containers(options).await?; 559 | 560 | if result.len() > 0 { 561 | return Ok(Some(result[0].clone())); 562 | } 563 | 564 | Ok(None) 565 | } 566 | 567 | async fn check_is_container_running_from_name( 568 | &self, 569 | docker: &Docker, 570 | name: String, 571 | ) -> Result, Error> { 572 | let label_name: String = format!("devcontainer_name={}", name); 573 | 574 | let mut filters = HashMap::new(); 575 | filters.insert("label", vec!["devcontainer=true", label_name.as_str()]); 576 | 577 | self.get_container_from_filters(docker, &filters).await 578 | } 579 | 580 | async fn up_docker( 581 | &self, 582 | docker: &Docker, 583 | devcontainer: &DevContainer, 584 | image: String, 585 | ) -> Result { 586 | let container_label = devcontainer.get_name(&self.path); 587 | 588 | if let Some(stat) = self 589 | .check_is_container_running_from_name(docker, container_label.clone()) 590 | .await? 591 | { 592 | let id = stat.id.as_ref().unwrap(); 593 | info!("Found container with id = '{}'", id); 594 | 595 | // if container is not running, try to start it 596 | if stat.state.as_ref().unwrap() != "running" { 597 | docker 598 | .start_container(id, None::>) 599 | .await?; 600 | 601 | // postStartCommand 602 | self.run_hook(docker, devcontainer, id.clone(), CommandHook::PostStart) 603 | .await?; 604 | } 605 | 606 | self.run_hook(docker, devcontainer, id.clone(), CommandHook::PostAttach) 607 | .await?; 608 | return Ok(id.clone()); 609 | } 610 | 611 | let mut config: container::Config = container::Config { 612 | image: Some(image.clone()), 613 | ..Default::default() 614 | }; 615 | 616 | self.container_opts_build_envs(devcontainer, &mut config) 617 | .await?; 618 | 619 | self.container_opts_build_mounts(devcontainer, &mut config) 620 | .await?; 621 | 622 | self.container_opts_build_ports(devcontainer, &mut config) 623 | .await?; 624 | 625 | self.container_opts_build_cmd(devcontainer, &mut config) 626 | .await?; 627 | 628 | let mut labels = HashMap::new(); 629 | labels.insert("devcontainer".to_string(), "true".to_string()); 630 | labels.insert("devcontainer_name".to_string(), container_label); 631 | 632 | config.labels = Some(labels); 633 | let mut container_options: Option> = None; 634 | 635 | if let Some(filename) = self.path.file_name() { 636 | if let Some(filename) = filename.to_str() { 637 | let image_name: &str = image.split(':').next().unwrap(); 638 | 639 | // Use unique id to avoid collision with existing containers 640 | for id in 1..20 { 641 | let name = format!("{}_devcontainer_{}_{}", filename, image_name, id); 642 | 643 | let mut filters = HashMap::new(); 644 | filters.insert("name", vec![name.as_str()]); 645 | 646 | let options = Some(ListContainersOptions { 647 | all: true, 648 | filters, 649 | ..std::default::Default::default() 650 | }); 651 | 652 | // Check if an existing container has this name 653 | if let Ok(containers) = docker.list_containers(options).await { 654 | if containers.len() > 0 { 655 | continue; 656 | } 657 | } 658 | 659 | container_options = Some(CreateContainerOptions { name }); 660 | 661 | break; 662 | } 663 | } 664 | } 665 | 666 | let info = docker 667 | .create_container::(container_options, config) 668 | .await?; 669 | 670 | let id = info.id; 671 | 672 | info!("Starting container"); 673 | docker 674 | .start_container(id.as_str(), None::>) 675 | .await?; 676 | 677 | // postCreateCommand 678 | self.run_hook(docker, devcontainer, id.clone(), CommandHook::PostCreate) 679 | .await?; 680 | 681 | // postStartCommand 682 | self.run_hook(docker, devcontainer, id.clone(), CommandHook::PostStart) 683 | .await?; 684 | 685 | // postAttachCommand 686 | self.run_hook(docker, devcontainer, id.clone(), CommandHook::PostAttach) 687 | .await?; 688 | 689 | Ok(id) 690 | } 691 | 692 | fn docker_format_image(&self, image: String) -> String { 693 | if image.contains(":") { 694 | return image; 695 | } 696 | 697 | format!("{}:latest", image) 698 | } 699 | 700 | async fn up_from_image( 701 | &self, 702 | docker: &Docker, 703 | devcontainer: &DevContainer, 704 | ) -> Result { 705 | let image = self.docker_format_image(devcontainer.image.as_ref().unwrap().to_string()); 706 | 707 | self.docker_pull_image(docker, image.clone()).await?; 708 | 709 | info!("Creating container from: {}", image); 710 | let id = self.up_docker(&docker, devcontainer, image).await?; 711 | 712 | Ok(id) 713 | } 714 | 715 | async fn up_from_build( 716 | &self, 717 | docker: &Docker, 718 | devcontainer: &DevContainer, 719 | ) -> Result { 720 | let image = self.docker_build_image(&docker, devcontainer).await?; 721 | 722 | info!("Creating container from: {}", image); 723 | let id = self.up_docker(&docker, devcontainer, image).await?; 724 | 725 | Ok(id) 726 | } 727 | 728 | async fn build_docker_compose_settings_ext( 729 | &self, 730 | devcontainer: &DevContainer, 731 | project_name: &str, 732 | compose_sample_rel: PathBuf, 733 | ) -> Result, Error> { 734 | if let None = self.settings { 735 | return Ok(None); 736 | } 737 | 738 | let mut compose_sample = compose_sample_rel.clone(); 739 | if compose_sample.is_relative() { 740 | compose_sample = self.get_devcontainer_folder(); 741 | compose_sample.push(compose_sample_rel); 742 | } 743 | 744 | debug!("Building global settings compose ext"); 745 | debug!("Compose sample: {:?}", compose_sample); 746 | let compose_data = fs::read_to_string(compose_sample) 747 | .await 748 | .map_err(|err| Error::Other(err.to_string()))?; 749 | 750 | let compose_model: SettingsComposeModel = serde_yaml::from_str(compose_data.as_str()) 751 | .map_err(|err| Error::Other(err.to_string()))?; 752 | 753 | Ok(Some( 754 | self.settings 755 | .as_ref() 756 | .unwrap() 757 | .generate_compose_override( 758 | devcontainer 759 | .service 760 | .as_ref() 761 | .unwrap_or(&project_name.to_string()) 762 | .clone(), 763 | compose_model.version, 764 | Some(self.get_devcontainer_envs(devcontainer)), 765 | ) 766 | .await?, 767 | )) 768 | } 769 | 770 | async fn build_docker_compose_cmd( 771 | &self, 772 | devcontainer: &DevContainer, 773 | project_name: &str, 774 | extended_args: Option>, 775 | ) -> Result, Error> { 776 | let mut compose_args: Vec = vec!["docker-compose", "-p", project_name] 777 | .iter() 778 | .map(|s| s.to_string()) 779 | .collect(); 780 | 781 | let mut compose_file_sample = PathBuf::new(); 782 | 783 | match devcontainer.docker_compose_file.as_ref().unwrap() { 784 | DockerComposeFile::File(file) => { 785 | compose_args.push("-f".to_string()); 786 | compose_args.push(file.clone()); 787 | 788 | compose_file_sample = PathBuf::from(&file); 789 | } 790 | DockerComposeFile::Files(files) => { 791 | if let Some(first) = files.first() { 792 | compose_file_sample = PathBuf::from(first); 793 | } 794 | 795 | for file in files { 796 | compose_args.push("-f".to_string()); 797 | compose_args.push(file.clone()); 798 | } 799 | } 800 | }; 801 | 802 | if let Some(settings_ext) = self 803 | .build_docker_compose_settings_ext(devcontainer, project_name, compose_file_sample) 804 | .await? 805 | { 806 | compose_args.push("-f".to_string()); 807 | compose_args.push(settings_ext.into_os_string().into_string().unwrap()); 808 | } 809 | 810 | if let Some(ext_args) = extended_args { 811 | compose_args.extend(ext_args); 812 | } 813 | 814 | Ok(compose_args) 815 | } 816 | 817 | async fn up_from_compose( 818 | &self, 819 | docker: &Docker, 820 | devcontainer: &DevContainer, 821 | ) -> Result { 822 | let project_name = devcontainer.get_name(&self.path); 823 | 824 | let project_label = format!("com.docker.compose.project={}", project_name); 825 | let service_label = format!( 826 | "com.docker.compose.service={}", 827 | devcontainer.service.as_ref().unwrap() 828 | ); 829 | 830 | let mut filters = HashMap::new(); 831 | filters.insert( 832 | "label", 833 | vec![project_label.as_str(), service_label.as_str()], 834 | ); 835 | 836 | let (existed_before, was_running_before) = 837 | match self.get_container_from_filters(docker, &filters).await? { 838 | Some(stat) => { 839 | debug!("State: {}", stat.state.as_ref().unwrap()); 840 | ( 841 | true, 842 | stat.state.is_some() && stat.state.as_ref().unwrap() == "running", 843 | ) 844 | } 845 | None => (false, false), 846 | }; 847 | 848 | let mut compose_args = self 849 | .build_docker_compose_cmd(devcontainer, project_name.as_str(), None) 850 | .await?; 851 | 852 | compose_args.push("up".to_string()); 853 | compose_args.push("-d".to_string()); 854 | 855 | compose_args.push(devcontainer.service.as_ref().unwrap().clone()); 856 | 857 | if let Some(services) = devcontainer.run_services.as_ref() { 858 | for service in services { 859 | compose_args.push(service.clone()); 860 | } 861 | } 862 | 863 | let compose_path = self.get_devcontainer_folder(); 864 | 865 | let mut builder = &mut Command::new(compose_args[0].clone()); 866 | builder = builder 867 | .args(compose_args.iter().skip(1)) 868 | .current_dir(compose_path); 869 | 870 | info!("Running docker-compose"); 871 | let compose_proc = builder 872 | .spawn() 873 | .map_err(|err| UpError::ComposeError(err.to_string()))?; 874 | 875 | if let Err(err) = compose_proc.await { 876 | return Err(Error::UpError(UpError::ComposeError(err.to_string()))); 877 | } 878 | 879 | let container_stat = match self.get_container_from_filters(docker, &filters).await? { 880 | Some(stat) => stat, 881 | None => { 882 | return Err(Error::UpError(UpError::ContainerCreate( 883 | "Could not locate container after compose up".to_string(), 884 | ))); 885 | } 886 | }; 887 | 888 | let container_id = container_stat.id.as_ref().unwrap(); 889 | 890 | if !existed_before { 891 | // postCreateCommand 892 | self.run_hook( 893 | docker, 894 | devcontainer, 895 | container_id.clone(), 896 | CommandHook::PostCreate, 897 | ) 898 | .await?; 899 | } 900 | 901 | if !was_running_before { 902 | // postStartCommand 903 | self.run_hook( 904 | docker, 905 | devcontainer, 906 | container_id.clone(), 907 | CommandHook::PostStart, 908 | ) 909 | .await?; 910 | } 911 | 912 | // postAttachCommand 913 | self.run_hook( 914 | docker, 915 | devcontainer, 916 | container_id.clone(), 917 | CommandHook::PostAttach, 918 | ) 919 | .await?; 920 | 921 | Ok(container_id.clone()) 922 | } 923 | 924 | async fn create_docker_client(&self) -> Result { 925 | let docker = match self.docket_host.as_ref() { 926 | None => Docker::connect_with_local_defaults()?, 927 | Some(h) => { 928 | let host = h.as_str(); 929 | Docker::connect_with_http(host, 60, API_DEFAULT_VERSION)? 930 | } 931 | }; 932 | 933 | Ok(docker) 934 | } 935 | 936 | pub async fn up(&self, should_wait: bool) -> Result<(), Error> { 937 | let devcontainer = self.devcontainer.as_ref().ok_or(Error::NoDevContainer)?; 938 | 939 | let docker = self.create_docker_client().await?; 940 | 941 | info!("Starting containers"); 942 | 943 | let container_id = match devcontainer.get_mode() { 944 | Mode::Image => self.up_from_image(&docker, &devcontainer).await?, 945 | Mode::Build => self.up_from_build(&docker, &devcontainer).await?, 946 | Mode::Compose => self.up_from_compose(&docker, &devcontainer).await?, 947 | }; 948 | 949 | info!("Containers are ready: {}", container_id); 950 | 951 | let child = if self.settings.as_ref().unwrap().application.is_some() { 952 | Some(self.spawn_application(devcontainer).await?) 953 | } else { 954 | None 955 | }; 956 | 957 | info!("Should wait: {}", should_wait); 958 | if !should_wait { 959 | return Ok(()); 960 | } 961 | 962 | let signal_stream = signal::ctrl_c(); 963 | 964 | let mut container_wait_stream = docker.wait_container( 965 | container_id.as_str(), 966 | None::>, 967 | ); 968 | 969 | if let Some(child) = child { 970 | info!("Waiting for application"); 971 | tokio::select! { 972 | child_res = child => { 973 | if let Err(err) = child_res { 974 | return Err(Error::UpError(UpError::ApplicationSpawn(err.to_string()))); 975 | } 976 | info!("Application has finished. Closing down"); 977 | }, 978 | _ = &mut container_wait_stream.next() => { 979 | warn!("Container has finished! Restart required"); 980 | return Ok(()); 981 | }, 982 | _ = signal_stream => { 983 | info!("CTRL+C: Finishing now"); 984 | } 985 | }; 986 | return self.down(Some(docker), true).await; 987 | } 988 | 989 | let should_go_down = tokio::select! { 990 | _ = &mut container_wait_stream.next() => { 991 | warn!("Container has finished! Nothing to do now. Closing down."); 992 | false 993 | } 994 | _ = signal_stream => { 995 | info!("CTRL+C: Finishing now"); 996 | true 997 | } 998 | }; 999 | 1000 | if !should_go_down { 1001 | return Ok(()); 1002 | } 1003 | 1004 | self.down(Some(docker), true).await 1005 | } 1006 | 1007 | async fn down_from_image( 1008 | &self, 1009 | docker: &Docker, 1010 | devcontainer: &DevContainer, 1011 | ) -> Result<(), Error> { 1012 | let container_label = devcontainer.get_name(&self.path); 1013 | 1014 | if let Some(stat) = self 1015 | .check_is_container_running_from_name(docker, container_label.clone()) 1016 | .await? 1017 | { 1018 | let container_id = stat.id.as_ref().unwrap(); 1019 | 1020 | docker 1021 | .stop_container(container_id, None::) 1022 | .await?; 1023 | } 1024 | 1025 | Ok(()) 1026 | } 1027 | 1028 | async fn down_from_compose(&self, devcontainer: &DevContainer) -> Result<(), Error> { 1029 | let project_name = devcontainer.get_name(&self.path); 1030 | 1031 | let compose_path = self.get_devcontainer_folder(); 1032 | 1033 | let compose_args = self 1034 | .build_docker_compose_cmd( 1035 | devcontainer, 1036 | project_name.as_str(), 1037 | Some(vec!["stop".to_string()]), 1038 | ) 1039 | .await?; 1040 | 1041 | let mut builder = &mut Command::new(compose_args[0].clone()); 1042 | builder = builder 1043 | .args(compose_args.iter().skip(1)) 1044 | .current_dir(compose_path); 1045 | 1046 | info!("Running docker-compose"); 1047 | let compose_proc = builder 1048 | .spawn() 1049 | .map_err(|err| UpError::ComposeError(err.to_string()))?; 1050 | 1051 | if let Err(err) = compose_proc.await { 1052 | return Err(Error::UpError(UpError::ComposeError(err.to_string()))); 1053 | } 1054 | 1055 | Ok(()) 1056 | } 1057 | 1058 | pub async fn down(&self, docker: Option, from_up: bool) -> Result<(), Error> { 1059 | info!("Shutting down containers"); 1060 | 1061 | let devcontainer = self.devcontainer.as_ref().ok_or(Error::NoDevContainer)?; 1062 | 1063 | let docker = match docker { 1064 | Some(d) => d, 1065 | None => self.create_docker_client().await?, 1066 | }; 1067 | 1068 | let shutdown_action = devcontainer 1069 | .shutdown_action 1070 | .as_ref() 1071 | .unwrap_or(&ShutdownAction::None); 1072 | 1073 | match devcontainer.get_mode() { 1074 | Mode::Compose => { 1075 | if from_up && shutdown_action != &ShutdownAction::StopCompose { 1076 | info!("Not shutting down composer. Shutdown action is not 'stopCompose'"); 1077 | Ok(()) 1078 | } else { 1079 | self.down_from_compose(devcontainer).await 1080 | } 1081 | } 1082 | _ => { 1083 | if from_up && shutdown_action != &ShutdownAction::StopContainer { 1084 | info!("Not shutting down container. Shutdown action is not 'stopContainer'"); 1085 | Ok(()) 1086 | } else { 1087 | self.down_from_image(&docker, devcontainer).await 1088 | } 1089 | } 1090 | } 1091 | } 1092 | } 1093 | -------------------------------------------------------------------------------- /src/project_tests.rs: -------------------------------------------------------------------------------- 1 | use std::path::PathBuf; 2 | use tokio; 3 | 4 | use crate::project::*; 5 | 6 | #[tokio::test] 7 | async fn test_new() { 8 | //let dc = Project::new(Some(PathBuf::from("/tmp")), None).unwrap(); 9 | //assert_eq!(dc.path.to_str().unwrap(), "/tmp"); 10 | 11 | let dc = Project::new(ProjectOpts::default()).unwrap(); 12 | let dir = std::env::current_dir().unwrap(); 13 | assert_eq!(dc.path.to_str().unwrap(), dir.to_str().unwrap()) 14 | } 15 | 16 | #[tokio::test] 17 | async fn test_validate_valid() { 18 | let mut dir = std::env::current_dir().unwrap(); 19 | dir.push("test_files"); 20 | dir.push("docker-compose"); 21 | let mut dc = Project::new(ProjectOpts { 22 | path: Some(dir), 23 | ..ProjectOpts::default() 24 | }) 25 | .unwrap(); 26 | dc.load().await.unwrap(); 27 | } 28 | 29 | #[tokio::test] 30 | #[should_panic] 31 | async fn test_validate_does_not_exist() { 32 | let dir = PathBuf::from("abc"); 33 | let _ = Project::new(ProjectOpts { 34 | path: Some(dir), 35 | ..ProjectOpts::default() 36 | }) 37 | .unwrap(); 38 | } 39 | 40 | #[tokio::test] 41 | async fn test_validate_invalid() { 42 | let mut dir = std::env::current_dir().unwrap(); 43 | dir.push("test_files"); 44 | dir.push("invalid"); 45 | let mut dc = Project::new(ProjectOpts { 46 | path: Some(dir), 47 | ..ProjectOpts::default() 48 | }) 49 | .unwrap(); 50 | 51 | match dc.load().await { 52 | Err(super::errors::Error::InvalidConfig(_)) => {} 53 | _ => panic!("Expected error"), 54 | }; 55 | } 56 | -------------------------------------------------------------------------------- /src/settings.rs: -------------------------------------------------------------------------------- 1 | use dirs; 2 | use json5; 3 | use serde::{de, Deserialize, Deserializer}; 4 | use serde_yaml; 5 | use std::collections::{BTreeMap, HashMap}; 6 | use std::path::PathBuf; 7 | use tokio::fs; 8 | use tokio::prelude::*; 9 | 10 | use super::devcontainer::CommandLineVec; 11 | use super::errors::*; 12 | use super::settings_compose_model::*; 13 | 14 | #[derive(Deserialize)] 15 | pub struct Application { 16 | pub cmd: CommandLineVec, 17 | } 18 | 19 | #[derive(Deserialize, Default)] 20 | pub struct Settings { 21 | pub application: Option, 22 | 23 | pub mounts: Option>, 24 | 25 | pub envs: Option>, 26 | 27 | #[serde(rename = "postCreateCommand")] 28 | pub post_create_command: Option, 29 | 30 | #[serde(rename = "postStartCommand")] 31 | pub post_start_command: Option, 32 | 33 | #[serde(rename = "postAttachCommand")] 34 | pub post_attach_command: Option, 35 | 36 | #[serde(rename = "forwardPorts")] 37 | pub forward_ports: Option>, 38 | } 39 | 40 | impl Settings { 41 | pub async fn load() -> Result { 42 | let mut settings_path = dirs::config_dir().unwrap(); 43 | 44 | settings_path.push("devcontainer.json"); 45 | 46 | if !settings_path.exists() { 47 | return Ok(Settings::default()); 48 | } 49 | 50 | let contents = fs::read_to_string(settings_path) 51 | .await 52 | .map_err(|err| Error::InvalidSettings(err.to_string()))?; 53 | 54 | let settings: Settings = 55 | json5::from_str(&contents).map_err(|err| Error::InvalidSettings(err.to_string()))?; 56 | 57 | Ok(settings) 58 | } 59 | 60 | pub async fn generate_compose_override( 61 | &self, 62 | service_name: String, 63 | version: String, 64 | envs: Option>, 65 | ) -> Result { 66 | let mut envs = envs.unwrap_or(HashMap::new()); 67 | 68 | if let Some(settings_envs) = self.envs.as_ref() { 69 | for (key, value) in settings_envs.iter() { 70 | envs.insert(key.clone(), value.clone()); 71 | } 72 | } 73 | 74 | let service = Service { 75 | ports: self 76 | .forward_ports 77 | .clone() 78 | .map(|ports| ports.iter().map(|p| format!("{}:{}", p, p)).collect()), 79 | volumes: self.mounts.clone(), 80 | environment: Some(envs), 81 | ..Service::default() 82 | }; 83 | 84 | let mut services = HashMap::new(); 85 | services.insert(service_name.clone(), service); 86 | 87 | let compose_model = SettingsComposeModel { 88 | version, 89 | services, 90 | ..SettingsComposeModel::default() 91 | }; 92 | 93 | let mut path = std::env::temp_dir(); 94 | path.push(format!("{}-compose.yml", service_name)); 95 | 96 | let mut file = tokio::fs::File::create(&path) 97 | .await 98 | .map_err(|err| Error::Other(err.to_string()))?; 99 | 100 | let data = 101 | serde_yaml::to_vec(&compose_model).map_err(|err| Error::Other(err.to_string()))?; 102 | 103 | file.write_all(data.as_slice()) 104 | .await 105 | .map_err(|err| Error::Other(err.to_string()))?; 106 | 107 | Ok(path) 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /src/settings_compose_model.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | use std::collections::HashMap; 3 | 4 | #[derive(Debug, Default, Serialize, Deserialize)] 5 | pub struct SettingsComposeModel { 6 | pub version: String, 7 | pub services: HashMap, 8 | } 9 | 10 | #[derive(Debug, Default, Serialize, Deserialize)] 11 | pub struct Service { 12 | #[serde(skip_serializing_if = "Option::is_none")] 13 | pub volumes: Option>, 14 | #[serde(skip_serializing_if = "Option::is_none")] 15 | pub ports: Option>, 16 | #[serde(skip_serializing_if = "Option::is_none")] 17 | pub environment: Option>, 18 | } 19 | -------------------------------------------------------------------------------- /test_files/application/.devcontainer/devcontainer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "up_image", 3 | "image": "ubuntu:20.04", 4 | "application": { "cmd": ["nvim-qt", "--server", "9797", "--nofork"] } 5 | } 6 | -------------------------------------------------------------------------------- /test_files/build/.devcontainer/Dockerfile: -------------------------------------------------------------------------------- 1 | ARG VERSION=latest 2 | FROM ubuntu:${VERSION} 3 | -------------------------------------------------------------------------------- /test_files/build/.devcontainer/devcontainer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Ubuntu", 3 | "build": { 4 | "dockerfile": "Dockerfile", 5 | "args": { "VARIANT": "18.04" } 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /test_files/docker-compose/.devcontainer/devcontainer.json: -------------------------------------------------------------------------------- 1 | // For format details, see https://aka.ms/vscode-remote/devcontainer.json or this file's README at: 2 | // https://github.com/microsoft/vscode-dev-containers/tree/v0.117.1/containers/docker-in-docker-compose 3 | // If you want to run as a non-root user in the container, see .devcontainer/docker-compose.yml. 4 | { 5 | "name": "compose", 6 | "dockerComposeFile": "docker-compose.yml", 7 | "service": "dev", 8 | "workspaceFolder": "/workspace", 9 | "postCreateCommand": ["echo", "create"], 10 | "postStartCommand": ["echo", "start"], 11 | "postAttachCommand": ["echo", "attach"], 12 | // Set *default* container specific settings.json values on container create. 13 | "settings": { 14 | "lldb.executable": "/usr/bin/lldb", 15 | "terminal.integrated.shell.linux": "/bin/bash", 16 | "rust-analyzer.updates.askBeforeDownload": false 17 | }, 18 | // Add the IDs of extensions you want installed when the container is created. 19 | "extensions": [ 20 | "ms-azuretools.vscode-docker", 21 | // "rust-lang.rust", 22 | "matklad.rust-analyzer", 23 | "bungcip.better-toml", 24 | "vadimcn.vscode-lldb", 25 | "gruntfuggly.todo-tree" 26 | ], 27 | // Uncomment the next line if you want start specific services in your Docker Compose config. 28 | "runServices": [ 29 | "broker", 30 | "db" 31 | ], 32 | // Use 'postCreateCommand' to run commands after the container is created. 33 | // "postCreateCommand": "docker --version", 34 | // Uncomment to connect as a non-root user. See https://aka.ms/vscode-remote/containers/non-root. 35 | // "remoteUser": "vscode" 36 | "shutdownAction": "stopCompose" 37 | } 38 | -------------------------------------------------------------------------------- /test_files/docker-compose/.devcontainer/docker-compose.yml: -------------------------------------------------------------------------------- 1 | #------------------------------------------------------------------------------------------------------------- 2 | # Copyright (c) Microsoft Corporation. All rights reserved. 3 | # Licensed under the MIT License. See https://go.microsoft.com/fwlink/?linkid=2090316 for license information. 4 | #------------------------------------------------------------------------------------------------------------- 5 | 6 | version: '3' 7 | services: 8 | dev: 9 | # Uncomment the next line to use a non-root user for all processes. You can also 10 | # simply use the "remoteUser" property in devcontainer.json if you just want VS Code 11 | # and its sub-processes (terminals, tasks, debugging) to execute as the user. On Linux, 12 | # you may need to update USER_UID and USER_GID in .devcontainer/Dockerfile to match your 13 | # user if not 1000. See https://aka.ms/vscode-remote/containers/non-root for details. 14 | # user: vscode 15 | 16 | image: ubuntu:20.04 17 | environment: 18 | DATABASE_URI: 'postgres://postgres:alkje2lkaj2e@db/common' 19 | MAX_OPEN: 64 20 | 21 | volumes: 22 | # Update this to wherever you want VS Code to mount the folder of your project 23 | - ..:/workspace:cached 24 | 25 | # Uncomment the next four lines if you will use a ptrace-based debuggers like C++, Go, and Rust. 26 | cap_add: 27 | - SYS_PTRACE 28 | security_opt: 29 | - seccomp:unconfined 30 | 31 | command: sleep infinity 32 | 33 | broker: 34 | image: rabbitmq:3.8-management-alpine 35 | environment: 36 | RABBITMQ_ERLANG_COOKIE: 'erlang_cookie' 37 | 38 | db: 39 | image: postgres:latest 40 | restart: always 41 | environment: 42 | POSTGRES_PASSWORD: alkje2lkaj2e 43 | POSTGRES_DB: common 44 | -------------------------------------------------------------------------------- /test_files/image/.devcontainer/devcontainer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "up_image", 3 | "image": "rust" 4 | } 5 | -------------------------------------------------------------------------------- /test_files/invalid/.devcontainer/devcontainer.json: -------------------------------------------------------------------------------- 1 | { 2 | invalid! 3 | } 4 | -------------------------------------------------------------------------------- /test_files/nvim-qt/.devcontainer/devcontainer.json: -------------------------------------------------------------------------------- 1 | { 2 | "image": "ubuntu:20.04", 3 | "postCreateCommand": ["bash", "/workspace/.devcontainer/install-nvim.sh"], 4 | "postAttachCommand": ["bash", "/workspace/.devcontainer/run-nvim.sh"], 5 | "appPort": 7777, 6 | "remoteEnv": {"NVIM_DEVCONTAINER": "true"} 7 | } 8 | -------------------------------------------------------------------------------- /test_files/nvim-qt/.devcontainer/install-nvim.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | set -x 5 | 6 | apt-get update 7 | 8 | apt-get upgrade -y 9 | 10 | apt-get install -y neovim python3-neovim 11 | -------------------------------------------------------------------------------- /test_files/nvim-qt/.devcontainer/run-nvim.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | NVIM_LISTEN_ADDRESS=0.0.0.0:7777 4 | 5 | nohup nvim --listen 0.0.0.0:7777 --headless