dockworker-0.5.1/.cargo_vcs_info.json0000644000000001360000000000100132110ustar { "git": { "sha1": "480ff2a84e61c30338bc9598f2384c781fca7933" }, "path_in_vcs": "" }dockworker-0.5.1/.circleci/config.yml000064400000000000000000000061471046102023000156340ustar 00000000000000version: 2 jobs: build_image: working_directory: /tmp/dockworker machine: image: ubuntu-2004:202008-01 steps: - checkout - run: name: Build docker containers for testing command: | docker-compose build docker save -o test-iostream test-iostream:latest docker save -o test-signal test-signal:latest - persist_to_workspace: root: /tmp/dockworker paths: - test-iostream - test-signal build: working_directory: /tmp/dockworker docker: - image: rust:1.49.0 environment: DOCKER_VERSION: 18.03.1-ce steps: - checkout - setup_remote_docker - attach_workspace: at: /tmp/dockworker - run: name: Setup Packages command: | apt-get update apt-get install -y ca-certificates curl libssl-dev - run: name: Install Docker Client command: | # client only curl -fsSL https://download.docker.com/linux/static/stable/x86_64/docker-${DOCKER_VERSION}.tgz | tar -zxf - --strip 1 -C /usr/local/bin docker/docker - run: name: Install rustfmt command: | rustup component add rustfmt - run: name: Generate cache key command: | cat Cargo.toml Cargo.lock > /tmp/build-dep rustc --version >> /tmp/build-dep rustfmt --version >> /tmp/build-dep echo $OS_VERSION >> /tmp/build-dep apt list --installed >> /tmp/build-dep - restore_cache: key: cache-cargo-target-{{ .Environment.CIRCLE_JOB }}-{{ .Environment.CIRCLECI_CACHE_VERSION }}-{{ checksum "/tmp/build-dep" }} - run: name: Checking source code style command: cargo fmt -- --check - run: name: Build project command: | RUST_BACKTRACE=1 cargo build --verbose --features ssl RUST_BACKTRACE=1 cargo build --examples -j 8 - run: name: Unit testing with ssl feature command: | RUST_BACKTRACE=1 cargo test -j 8 --verbose --features ssl - run: name: Unit testing with ssl-rustls feature command: | RUST_BACKTRACE=1 cargo test -j 8 --verbose --features ssl-rustls - run: name: More unit testing command: | docker load -i test-iostream docker load -i test-signal RUST_BACKTRACE=1 cargo test --verbose --features ssl -- --ignored - run: name: More unit testing with ssl-rustls command: | RUST_BACKTRACE=1 cargo test --verbose --features ssl-rustls -- --ignored - save_cache: key: cache-cargo-target-{{ .Environment.CIRCLE_JOB }}-{{ .Environment.CIRCLECI_CACHE_VERSION }}-{{ checksum "/tmp/build-dep" }} paths: - ~/.cargo/registry/ - target/debug workflows: version: 2 build_and_test: jobs: - build: requires: - build_image - build_image dockworker-0.5.1/.gitignore000064400000000000000000000000211046102023000137620ustar 00000000000000.DS_Store target dockworker-0.5.1/Cargo.lock0000644000001216450000000000100111750ustar # This file is automatically @generated by Cargo. # It is not intended for manual editing. version = 3 [[package]] name = "android-tzdata" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" [[package]] name = "android_system_properties" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" dependencies = [ "libc", ] [[package]] name = "async-stream" version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cd56dd203fef61ac097dd65721a419ddccb106b2d2b70ba60a6b529f03961a51" dependencies = [ "async-stream-impl", "futures-core", "pin-project-lite", ] [[package]] name = "async-stream-impl" version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "16e62a023e7c117e27523144c5d2459f4397fcc3cab0085af8e2224f643a0193" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "async-trait" version = "0.1.68" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9ccdd8f2a161be9bd5c023df56f1b2a0bd1d83872ae53b71a84a12c9bf6e842" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "autocfg" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" [[package]] name = "base64" version = "0.21.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "604178f6c5c21f02dc555784810edfb88d34ac2c73b2eae109655649ee73ce3d" [[package]] name = "bitflags" version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bumpalo" version = "3.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a3e2c3daef883ecc1b5d58c15adae93470a91d425f3532ba1695849656af3fc1" [[package]] name = "byteorder" version = "1.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" [[package]] name = "bytes" version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be" [[package]] name = "cc" version = "1.0.79" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" [[package]] name = "cfg-if" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "chrono" version = "0.4.26" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec837a71355b28f6556dbd569b37b3f363091c0bd4b2e735674521b4c5fd9bc5" dependencies = [ "android-tzdata", "iana-time-zone", "num-traits", "winapi", ] [[package]] name = "core-foundation" version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "194a7a9e6de53fa55116934067c844d9d749312f75c6f6d0980e8c252f8c2146" dependencies = [ "core-foundation-sys", "libc", ] [[package]] name = "core-foundation-sys" version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa" [[package]] name = "dirs" version = "5.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "44c45a9d03d6676652bcb5e724c7e988de1acad23a711b5217ab9cbecbec2225" dependencies = [ "dirs-sys", ] [[package]] name = "dirs-sys" version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c" dependencies = [ "libc", "option-ext", "redox_users", "windows-sys 0.48.0", ] [[package]] name = "dockworker" version = "0.5.1" dependencies = [ "async-stream", "async-trait", "base64", "byteorder", "bytes", "chrono", "dirs", "futures", "http", "hyper", "hyper-rustls", "hyper-tls", "hyperlocal", "log", "named_pipe", "native-tls", "nix", "openssl", "rand", "reqwest", "rustls", "rustls-pemfile", "serde", "serde_json", "tar", "thiserror", "tokio", "tokio-stream", "tokio-util", "url", ] [[package]] name = "encoding_rs" version = "0.8.33" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7268b386296a025e474d5140678f75d6de9493ae55a5d709eeb9dd08149945e1" dependencies = [ "cfg-if", ] [[package]] name = "errno" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4bcfec3a70f97c962c307b2d2c56e358cf1d00b558d74262b5f929ee8cc7e73a" dependencies = [ "errno-dragonfly", "libc", "windows-sys 0.48.0", ] [[package]] name = "errno-dragonfly" version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" dependencies = [ "cc", "libc", ] [[package]] name = "fastrand" version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be" dependencies = [ "instant", ] [[package]] name = "filetime" version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5cbc844cecaee9d4443931972e1289c8ff485cb4cc2767cb03ca139ed6885153" dependencies = [ "cfg-if", "libc", "redox_syscall 0.2.16", "windows-sys 0.48.0", ] [[package]] name = "fnv" version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" [[package]] name = "foreign-types" version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" dependencies = [ "foreign-types-shared", ] [[package]] name = "foreign-types-shared" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" [[package]] name = "form_urlencoded" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a62bc1cf6f830c2ec14a513a9fb124d0a213a629668a4186f329db21fe045652" dependencies = [ "percent-encoding", ] [[package]] name = "futures" version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "23342abe12aba583913b2e62f22225ff9c950774065e4bfb61a19cd9770fec40" dependencies = [ "futures-channel", "futures-core", "futures-io", "futures-sink", "futures-task", "futures-util", ] [[package]] name = "futures-channel" version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "955518d47e09b25bbebc7a18df10b81f0c766eaf4c4f1cccef2fca5f2a4fb5f2" dependencies = [ "futures-core", "futures-sink", ] [[package]] name = "futures-core" version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4bca583b7e26f571124fe5b7561d49cb2868d79116cfa0eefce955557c6fee8c" [[package]] name = "futures-io" version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4fff74096e71ed47f8e023204cfd0aa1289cd54ae5430a9523be060cdb849964" [[package]] name = "futures-macro" version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "futures-sink" version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f43be4fe21a13b9781a69afa4985b0f6ee0e1afab2c6f454a8cf30e2b2237b6e" [[package]] name = "futures-task" version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "76d3d132be6c0e6aa1534069c705a74a5997a356c0dc2f86a47765e5617c5b65" [[package]] name = "futures-util" version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26b01e40b772d54cf6c6d721c1d1abd0647a0106a12ecaa1c186273392a69533" dependencies = [ "futures-channel", "futures-core", "futures-io", "futures-macro", "futures-sink", "futures-task", "memchr", "pin-project-lite", "pin-utils", "slab", ] [[package]] name = "getrandom" version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c85e1d9ab2eadba7e5040d4e09cbd6d072b76a557ad64e797c2cb9d4da21d7e4" dependencies = [ "cfg-if", "libc", "wasi", ] [[package]] name = "h2" version = "0.3.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d357c7ae988e7d2182f7d7871d0b963962420b0678b0997ce7de72001aeab782" dependencies = [ "bytes", "fnv", "futures-core", "futures-sink", "futures-util", "http", "indexmap", "slab", "tokio", "tokio-util", "tracing", ] [[package]] name = "hashbrown" version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" [[package]] name = "hermit-abi" version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ee512640fe35acbfb4bb779db6f0d80704c2cacfa2e39b601ef3e3f47d1ae4c7" dependencies = [ "libc", ] [[package]] name = "hermit-abi" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fed44880c466736ef9a5c5b5facefb5ed0785676d0c02d612db14e54f0d84286" [[package]] name = "hex" version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" [[package]] name = "http" version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bd6effc99afb63425aff9b05836f029929e345a6148a14b7ecd5ab67af944482" dependencies = [ "bytes", "fnv", "itoa", ] [[package]] name = "http-body" version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1" dependencies = [ "bytes", "http", "pin-project-lite", ] [[package]] name = "httparse" version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" [[package]] name = "httpdate" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421" [[package]] name = "hyper" version = "0.14.26" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ab302d72a6f11a3b910431ff93aae7e773078c769f0a3ef15fb9ec692ed147d4" dependencies = [ "bytes", "futures-channel", "futures-core", "futures-util", "h2", "http", "http-body", "httparse", "httpdate", "itoa", "pin-project-lite", "socket2", "tokio", "tower-service", "tracing", "want", ] [[package]] name = "hyper-rustls" version = "0.24.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0646026eb1b3eea4cd9ba47912ea5ce9cc07713d105b1a14698f4e6433d348b7" dependencies = [ "http", "hyper", "log", "rustls", "rustls-native-certs", "tokio", "tokio-rustls", ] [[package]] name = "hyper-tls" version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" dependencies = [ "bytes", "hyper", "native-tls", "tokio", "tokio-native-tls", ] [[package]] name = "hyperlocal" version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fafdf7b2b2de7c9784f76e02c0935e65a8117ec3b768644379983ab333ac98c" dependencies = [ "futures-util", "hex", "hyper", "pin-project", "tokio", ] [[package]] name = "iana-time-zone" version = "0.1.56" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0722cd7114b7de04316e7ea5456a0bbb20e4adb46fd27a3697adb812cff0f37c" dependencies = [ "android_system_properties", "core-foundation-sys", "iana-time-zone-haiku", "js-sys", "wasm-bindgen", "windows", ] [[package]] name = "iana-time-zone-haiku" version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" dependencies = [ "cc", ] [[package]] name = "idna" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7d20d6b07bfbc108882d88ed8e37d39636dcc260e15e30c45e6ba089610b917c" dependencies = [ "unicode-bidi", "unicode-normalization", ] [[package]] name = "indexmap" version = "1.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" dependencies = [ "autocfg", "hashbrown", ] [[package]] name = "instant" version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" dependencies = [ "cfg-if", ] [[package]] name = "io-lifetimes" version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2" dependencies = [ "hermit-abi 0.3.1", "libc", "windows-sys 0.48.0", ] [[package]] name = "ipnet" version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3" [[package]] name = "itoa" version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "453ad9f582a441959e5f0d088b02ce04cfe8d51a8eaf077f12ac6d3e94164ca6" [[package]] name = "js-sys" version = "0.3.63" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2f37a4a5928311ac501dee68b3c7613a1037d0edb30c8e5427bd832d55d1b790" dependencies = [ "wasm-bindgen", ] [[package]] name = "lazy_static" version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" version = "0.2.144" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b00cc1c228a6782d0f076e7b232802e0c5689d41bb5df366f2a6b6621cfdfe1" [[package]] name = "linux-raw-sys" version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519" [[package]] name = "log" version = "0.4.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "518ef76f2f87365916b142844c16d8fefd85039bc5699050210a7778ee1cd1de" [[package]] name = "memchr" version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" [[package]] name = "memoffset" version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5de893c32cde5f383baa4c04c5d6dbdd735cfd4a794b0debdb2bb1b421da5ff4" dependencies = [ "autocfg", ] [[package]] name = "mime" version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" [[package]] name = "mio" version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "927a765cd3fc26206e66b296465fa9d3e5ab003e651c1b3c060e7956d96b19d2" dependencies = [ "libc", "wasi", "windows-sys 0.48.0", ] [[package]] name = "named_pipe" version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ad9c443cce91fc3e12f017290db75dde490d685cdaaf508d7159d7cf41f0eb2b" dependencies = [ "winapi", ] [[package]] name = "native-tls" version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "07226173c32f2926027b63cce4bcd8076c3552846cbe7925f3aaffeac0a3b92e" dependencies = [ "lazy_static", "libc", "log", "openssl", "openssl-probe", "openssl-sys", "schannel", "security-framework", "security-framework-sys", "tempfile", ] [[package]] name = "nix" version = "0.26.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bfdda3d196821d6af13126e40375cdf7da646a96114af134d5f417a9a1dc8e1a" dependencies = [ "bitflags", "cfg-if", "libc", "memoffset", "pin-utils", "static_assertions", ] [[package]] name = "num-traits" version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" dependencies = [ "autocfg", ] [[package]] name = "num_cpus" version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fac9e2da13b5eb447a6ce3d392f23a29d8694bff781bf03a16cd9ac8697593b" dependencies = [ "hermit-abi 0.2.6", "libc", ] [[package]] name = "once_cell" version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" [[package]] name = "openssl" version = "0.10.54" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69b3f656a17a6cbc115b5c7a40c616947d213ba182135b014d6051b73ab6f019" dependencies = [ "bitflags", "cfg-if", "foreign-types", "libc", "once_cell", "openssl-macros", "openssl-sys", ] [[package]] name = "openssl-macros" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "openssl-probe" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" [[package]] name = "openssl-sys" version = "0.9.88" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2ce0f250f34a308dcfdbb351f511359857d4ed2134ba715a4eadd46e1ffd617" dependencies = [ "cc", "libc", "pkg-config", "vcpkg", ] [[package]] name = "option-ext" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" [[package]] name = "percent-encoding" version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94" [[package]] name = "pin-project" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c95a7476719eab1e366eaf73d0260af3021184f18177925b07f54b30089ceead" dependencies = [ "pin-project-internal", ] [[package]] name = "pin-project-internal" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39407670928234ebc5e6e580247dd567ad73a3578460c5990f9503df207e8f07" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "pin-project-lite" version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" [[package]] name = "pin-utils" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] name = "pkg-config" version = "0.3.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964" [[package]] name = "ppv-lite86" version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" [[package]] name = "proc-macro2" version = "1.0.59" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6aeca18b86b413c660b781aa319e4e2648a3e6f9eadc9b47e9038e6fe9f3451b" dependencies = [ "unicode-ident", ] [[package]] name = "quote" version = "1.0.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b9ab9c7eadfd8df19006f1cf1a4aed13540ed5cbc047010ece5826e10825488" dependencies = [ "proc-macro2", ] [[package]] name = "rand" version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ "libc", "rand_chacha", "rand_core", ] [[package]] name = "rand_chacha" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" dependencies = [ "ppv-lite86", "rand_core", ] [[package]] name = "rand_core" version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ "getrandom", ] [[package]] name = "redox_syscall" version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" dependencies = [ "bitflags", ] [[package]] name = "redox_syscall" version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" dependencies = [ "bitflags", ] [[package]] name = "redox_users" version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b" dependencies = [ "getrandom", "redox_syscall 0.2.16", "thiserror", ] [[package]] name = "reqwest" version = "0.11.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c6920094eb85afde5e4a138be3f2de8bbdf28000f0029e72c45025a56b042251" dependencies = [ "base64", "bytes", "encoding_rs", "futures-core", "futures-util", "h2", "http", "http-body", "hyper", "hyper-tls", "ipnet", "js-sys", "log", "mime", "native-tls", "once_cell", "percent-encoding", "pin-project-lite", "rustls-pemfile", "serde", "serde_json", "serde_urlencoded", "sync_wrapper", "system-configuration", "tokio", "tokio-native-tls", "tower-service", "url", "wasm-bindgen", "wasm-bindgen-futures", "web-sys", "winreg", ] [[package]] name = "ring" version = "0.16.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc" dependencies = [ "cc", "libc", "once_cell", "spin", "untrusted", "web-sys", "winapi", ] [[package]] name = "rustix" version = "0.37.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "acf8729d8542766f1b2cf77eb034d52f40d375bb8b615d0b147089946e16613d" dependencies = [ "bitflags", "errno", "io-lifetimes", "libc", "linux-raw-sys", "windows-sys 0.48.0", ] [[package]] name = "rustls" version = "0.21.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c911ba11bc8433e811ce56fde130ccf32f5127cab0e0194e9c68c5a5b671791e" dependencies = [ "log", "ring", "rustls-webpki", "sct", ] [[package]] name = "rustls-native-certs" version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0167bac7a9f490495f3c33013e7722b53cb087ecbe082fb0c6387c96f634ea50" dependencies = [ "openssl-probe", "rustls-pemfile", "schannel", "security-framework", ] [[package]] name = "rustls-pemfile" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d194b56d58803a43635bdc398cd17e383d6f71f9182b9a192c127ca42494a59b" dependencies = [ "base64", ] [[package]] name = "rustls-webpki" version = "0.100.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d6207cd5ed3d8dca7816f8f3725513a34609c0c765bf652b8c3cb4cfd87db46b" dependencies = [ "ring", "untrusted", ] [[package]] name = "ryu" version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f91339c0467de62360649f8d3e185ca8de4224ff281f66000de5eb2a77a79041" [[package]] name = "schannel" version = "0.1.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "713cfb06c7059f3588fb8044c0fad1d09e3c01d225e25b9220dbfdcf16dbb1b3" dependencies = [ "windows-sys 0.42.0", ] [[package]] name = "sct" version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d53dcdb7c9f8158937a7981b48accfd39a43af418591a5d008c7b22b5e1b7ca4" dependencies = [ "ring", "untrusted", ] [[package]] name = "security-framework" version = "2.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fc758eb7bffce5b308734e9b0c1468893cae9ff70ebf13e7090be8dcbcc83a8" dependencies = [ "bitflags", "core-foundation", "core-foundation-sys", "libc", "security-framework-sys", ] [[package]] name = "security-framework-sys" version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f51d0c0d83bec45f16480d0ce0058397a69e48fcdc52d1dc8855fb68acbd31a7" dependencies = [ "core-foundation-sys", "libc", ] [[package]] name = "serde" version = "1.0.163" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2113ab51b87a539ae008b5c6c02dc020ffa39afd2d83cffcb3f4eb2722cebec2" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" version = "1.0.163" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8c805777e3930c8883389c602315a24224bcc738b63905ef87cd1420353ea93e" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "serde_json" version = "1.0.96" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "057d394a50403bcac12672b2b18fb387ab6d289d957dab67dd201875391e52f1" dependencies = [ "itoa", "ryu", "serde", ] [[package]] name = "serde_urlencoded" version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" dependencies = [ "form_urlencoded", "itoa", "ryu", "serde", ] [[package]] name = "slab" version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6528351c9bc8ab22353f9d776db39a20288e8d6c37ef8cfe3317cf875eecfc2d" dependencies = [ "autocfg", ] [[package]] name = "socket2" version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "64a4a911eed85daf18834cfaa86a79b7d266ff93ff5ba14005426219480ed662" dependencies = [ "libc", "winapi", ] [[package]] name = "spin" version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" [[package]] name = "static_assertions" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" [[package]] name = "syn" version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32d41677bcbe24c20c52e7c70b0d8db04134c5d1066bf98662e2871ad200ea3e" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] [[package]] name = "sync_wrapper" version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" [[package]] name = "system-configuration" version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7" dependencies = [ "bitflags", "core-foundation", "system-configuration-sys", ] [[package]] name = "system-configuration-sys" version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a75fb188eb626b924683e3b95e3a48e63551fcfb51949de2f06a9d91dbee93c9" dependencies = [ "core-foundation-sys", "libc", ] [[package]] name = "tar" version = "0.4.38" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4b55807c0344e1e6c04d7c965f5289c39a8d94ae23ed5c0b57aabac549f871c6" dependencies = [ "filetime", "libc", "xattr", ] [[package]] name = "tempfile" version = "3.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9fbec84f381d5795b08656e4912bec604d162bff9291d6189a78f4c8ab87998" dependencies = [ "cfg-if", "fastrand", "redox_syscall 0.3.5", "rustix", "windows-sys 0.45.0", ] [[package]] name = "thiserror" version = "1.0.40" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "978c9a314bd8dc99be594bc3c175faaa9794be04a5a5e153caba6915336cebac" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" version = "1.0.40" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "tinyvec" version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" dependencies = [ "tinyvec_macros", ] [[package]] name = "tinyvec_macros" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" version = "1.28.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94d7b1cfd2aa4011f2de74c2c4c63665e27a71006b0a192dcd2710272e73dfa2" dependencies = [ "autocfg", "bytes", "libc", "mio", "num_cpus", "pin-project-lite", "socket2", "tokio-macros", "windows-sys 0.48.0", ] [[package]] name = "tokio-macros" version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "tokio-native-tls" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" dependencies = [ "native-tls", "tokio", ] [[package]] name = "tokio-rustls" version = "0.24.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e0d409377ff5b1e3ca6437aa86c1eb7d40c134bfec254e44c830defa92669db5" dependencies = [ "rustls", "tokio", ] [[package]] name = "tokio-stream" version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "397c988d37662c7dda6d2208364a706264bf3d6138b11d436cbac0ad38832842" dependencies = [ "futures-core", "pin-project-lite", "tokio", ] [[package]] name = "tokio-util" version = "0.7.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "806fe8c2c87eccc8b3267cbae29ed3ab2d0bd37fca70ab622e46aaa9375ddb7d" dependencies = [ "bytes", "futures-core", "futures-sink", "pin-project-lite", "tokio", "tracing", ] [[package]] name = "tower-service" version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" [[package]] name = "tracing" version = "0.1.37" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8" dependencies = [ "cfg-if", "pin-project-lite", "tracing-core", ] [[package]] name = "tracing-core" version = "0.1.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0955b8137a1df6f1a2e9a37d8a6656291ff0297c1a97c24e0d8425fe2312f79a" dependencies = [ "once_cell", ] [[package]] name = "try-lock" version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3528ecfd12c466c6f163363caf2d02a71161dd5e1cc6ae7b34207ea2d42d81ed" [[package]] name = "unicode-bidi" version = "0.3.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460" [[package]] name = "unicode-ident" version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b15811caf2415fb889178633e7724bad2509101cde276048e013b9def5e51fa0" [[package]] name = "unicode-normalization" version = "0.1.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" dependencies = [ "tinyvec", ] [[package]] name = "untrusted" version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" [[package]] name = "url" version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "50bff7831e19200a85b17131d085c25d7811bc4e186efdaf54bbd132994a88cb" dependencies = [ "form_urlencoded", "idna", "percent-encoding", ] [[package]] name = "vcpkg" version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" [[package]] name = "want" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1ce8a968cb1cd110d136ff8b819a556d6fb6d919363c61534f6860c7eb172ba0" dependencies = [ "log", "try-lock", ] [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" version = "0.2.86" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5bba0e8cb82ba49ff4e229459ff22a191bbe9a1cb3a341610c9c33efc27ddf73" dependencies = [ "cfg-if", "wasm-bindgen-macro", ] [[package]] name = "wasm-bindgen-backend" version = "0.2.86" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "19b04bc93f9d6bdee709f6bd2118f57dd6679cf1176a1af464fca3ab0d66d8fb" dependencies = [ "bumpalo", "log", "once_cell", "proc-macro2", "quote", "syn", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-futures" version = "0.4.36" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2d1985d03709c53167ce907ff394f5316aa22cb4e12761295c5dc57dacb6297e" dependencies = [ "cfg-if", "js-sys", "wasm-bindgen", "web-sys", ] [[package]] name = "wasm-bindgen-macro" version = "0.2.86" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "14d6b024f1a526bb0234f52840389927257beb670610081360e5a03c5df9c258" dependencies = [ "quote", "wasm-bindgen-macro-support", ] [[package]] name = "wasm-bindgen-macro-support" version = "0.2.86" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e128beba882dd1eb6200e1dc92ae6c5dbaa4311aa7bb211ca035779e5efc39f8" dependencies = [ "proc-macro2", "quote", "syn", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" version = "0.2.86" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed9d5b4305409d1fc9482fee2d7f9bcbf24b3972bf59817ef757e23982242a93" [[package]] name = "web-sys" version = "0.3.63" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3bdd9ef4e984da1187bf8110c5cf5b845fbc87a23602cdf912386a76fcd3a7c2" dependencies = [ "js-sys", "wasm-bindgen", ] [[package]] name = "winapi" version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" dependencies = [ "winapi-i686-pc-windows-gnu", "winapi-x86_64-pc-windows-gnu", ] [[package]] name = "winapi-i686-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "windows" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e686886bc078bc1b0b600cac0147aadb815089b6e4da64016cbd754b6342700f" dependencies = [ "windows-targets 0.48.0", ] [[package]] name = "windows-sys" version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" dependencies = [ "windows_aarch64_gnullvm 0.42.2", "windows_aarch64_msvc 0.42.2", "windows_i686_gnu 0.42.2", "windows_i686_msvc 0.42.2", "windows_x86_64_gnu 0.42.2", "windows_x86_64_gnullvm 0.42.2", "windows_x86_64_msvc 0.42.2", ] [[package]] name = "windows-sys" version = "0.45.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" dependencies = [ "windows-targets 0.42.2", ] [[package]] name = "windows-sys" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" dependencies = [ "windows-targets 0.48.0", ] [[package]] name = "windows-targets" version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" dependencies = [ "windows_aarch64_gnullvm 0.42.2", "windows_aarch64_msvc 0.42.2", "windows_i686_gnu 0.42.2", "windows_i686_msvc 0.42.2", "windows_x86_64_gnu 0.42.2", "windows_x86_64_gnullvm 0.42.2", "windows_x86_64_msvc 0.42.2", ] [[package]] name = "windows-targets" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7b1eb6f0cd7c80c79759c929114ef071b87354ce476d9d94271031c0497adfd5" dependencies = [ "windows_aarch64_gnullvm 0.48.0", "windows_aarch64_msvc 0.48.0", "windows_i686_gnu 0.48.0", "windows_i686_msvc 0.48.0", "windows_x86_64_gnu 0.48.0", "windows_x86_64_gnullvm 0.48.0", "windows_x86_64_msvc 0.48.0", ] [[package]] name = "windows_aarch64_gnullvm" version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" [[package]] name = "windows_aarch64_gnullvm" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc" [[package]] name = "windows_aarch64_msvc" version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" [[package]] name = "windows_aarch64_msvc" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3" [[package]] name = "windows_i686_gnu" version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" [[package]] name = "windows_i686_gnu" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241" [[package]] name = "windows_i686_msvc" version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" [[package]] name = "windows_i686_msvc" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00" [[package]] name = "windows_x86_64_gnu" version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" [[package]] name = "windows_x86_64_gnu" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1" [[package]] name = "windows_x86_64_gnullvm" version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" [[package]] name = "windows_x86_64_gnullvm" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953" [[package]] name = "windows_x86_64_msvc" version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" [[package]] name = "windows_x86_64_msvc" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" [[package]] name = "winreg" version = "0.50.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1" dependencies = [ "cfg-if", "windows-sys 0.48.0", ] [[package]] name = "xattr" version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6d1526bbe5aaeb5eb06885f4d987bcdfa5e23187055de9b83fe00156a821fabc" dependencies = [ "libc", ] dockworker-0.5.1/Cargo.toml0000644000000056310000000000100112140ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies. # # If you are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] edition = "2021" name = "dockworker" version = "0.5.1" authors = ["takayuki goto "] description = "Docker daemon API client. (a fork of Faraday's boondock)" homepage = "https://github.com/Idein/dockworker" documentation = "https://docs.rs/dockworker" readme = "README.md" keywords = ["docker"] license = "Apache-2.0" repository = "https://github.com/Idein/dockworker" [dependencies.async-stream] version = "0.3" [dependencies.async-trait] version = "0.1" [dependencies.base64] version = "0.21" [dependencies.byteorder] version = "1" [dependencies.bytes] version = "1" [dependencies.chrono] version = "0.4" features = ["clock"] default-features = false [dependencies.dirs] version = "5.0" [dependencies.futures] version = "0.3" features = ["std"] default-features = false [dependencies.http] version = "0.2" [dependencies.hyper] version = "0.14" features = [ "client", "http1", "stream", "tcp", ] [dependencies.hyper-rustls] version = "0.24" features = ["http2"] optional = true [dependencies.hyper-tls] version = "0.5" optional = true [dependencies.log] version = "0.4" [dependencies.native-tls] version = "0.2" optional = true [dependencies.nix] version = "0.26" [dependencies.openssl] version = "0.10" optional = true [dependencies.rustls] version = "0.21" optional = true [dependencies.rustls-pemfile] version = "1.0.0" optional = true [dependencies.serde] version = "1" features = ["derive"] [dependencies.serde_json] version = "1" [dependencies.tar] version = "0.4" [dependencies.thiserror] version = "1" [dependencies.tokio] version = "1" features = [ "time", "fs", "net", "io-util", "io-std", "test-util", "macros", "rt", "rt-multi-thread", ] [dependencies.tokio-stream] version = "0.1" features = ["io-util"] [dependencies.tokio-util] version = "0.7" features = ["io"] [dependencies.url] version = "2" [dev-dependencies.rand] version = "0.8" [dev-dependencies.reqwest] version = "0.11.24" [features] default = [] experimental = [] ssl = [ "openssl", "native-tls", "hyper-tls", ] ssl-rustls = [ "rustls", "hyper-rustls", "rustls-pemfile", ] [target."cfg(unix)".dependencies.hyperlocal] version = "0.8" [target."cfg(windows)".dependencies.named_pipe] version = "0.4" [badges.appveyor] branch = "master" repository = "eldesh/dockworker" service = "github" [badges.circle-ci] repository = "Idein/dockworker" [badges.maintenance] status = "actively-developed" dockworker-0.5.1/Cargo.toml.orig0000644000000042010000000000100121430ustar [package] name = "dockworker" description = "Docker daemon API client. (a fork of Faraday's boondock)" version = "0.5.1" authors = ["takayuki goto "] license = "Apache-2.0" homepage = "https://github.com/Idein/dockworker" repository = "https://github.com/Idein/dockworker" documentation = "https://docs.rs/dockworker" readme = "README.md" keywords = ["docker"] edition = "2021" [badges] appveyor = { repository = "eldesh/dockworker", branch = "master", service = "github" } circle-ci = { repository = "Idein/dockworker" } maintenance = { status = "actively-developed" } [features] # OpenSSL is fairly hard to build on certain platforms, especially if you # want to produce release binaries. So we disable it by default. default = [] experimental = [] # Enable OpenSSL both directly and for Hyper. ssl = ["openssl", "native-tls", "hyper-tls"] ssl-rustls = ["rustls", "hyper-rustls", "rustls-pemfile"] [dependencies] async-trait = "0.1" async-stream = "0.3" bytes = "1" chrono = { version = "0.4", default-features = false, features = ["clock"] } futures = { version = "0.3", default-features = false, features = ["std"] } http = "0.2" hyper = { version = "0.14", features = ["client", "http1", "stream", "tcp"] } openssl = { version = "0.10", optional = true } rustls = { version = "0.21", optional = true } rustls-pemfile = { version = "1.0.0", optional = true } hyper-tls = { version = "0.5", optional = true } hyper-rustls = { version = "0.24", optional = true, features = ["http2"] } serde = { version = "1", features = ["derive"] } serde_json = "1" url = "2" byteorder = "1" tar = "0.4" tokio = { version = "1", features = [ "time", "fs", "net", "io-util", "io-std", "test-util", "macros", "rt", "rt-multi-thread", ] } tokio-stream = { version = "0.1", features = ["io-util"] } tokio-util = { version = "0.7", features = ["io"] } log = "0.4" native-tls = { version = "0.2", optional = true } nix = "0.26" base64 = "0.21" dirs = "5.0" thiserror = "1" [dev-dependencies] rand = "0.8" reqwest = "0.11.24" [target.'cfg(unix)'.dependencies] hyperlocal = "0.8" [target.'cfg(windows)'.dependencies] named_pipe = "0.4" dockworker-0.5.1/Cargo.toml.orig000064400000000000000000000042011046102023000146650ustar 00000000000000[package] name = "dockworker" description = "Docker daemon API client. (a fork of Faraday's boondock)" version = "0.5.1" authors = ["takayuki goto "] license = "Apache-2.0" homepage = "https://github.com/Idein/dockworker" repository = "https://github.com/Idein/dockworker" documentation = "https://docs.rs/dockworker" readme = "README.md" keywords = ["docker"] edition = "2021" [badges] appveyor = { repository = "eldesh/dockworker", branch = "master", service = "github" } circle-ci = { repository = "Idein/dockworker" } maintenance = { status = "actively-developed" } [features] # OpenSSL is fairly hard to build on certain platforms, especially if you # want to produce release binaries. So we disable it by default. default = [] experimental = [] # Enable OpenSSL both directly and for Hyper. ssl = ["openssl", "native-tls", "hyper-tls"] ssl-rustls = ["rustls", "hyper-rustls", "rustls-pemfile"] [dependencies] async-trait = "0.1" async-stream = "0.3" bytes = "1" chrono = { version = "0.4", default-features = false, features = ["clock"] } futures = { version = "0.3", default-features = false, features = ["std"] } http = "0.2" hyper = { version = "0.14", features = ["client", "http1", "stream", "tcp"] } openssl = { version = "0.10", optional = true } rustls = { version = "0.21", optional = true } rustls-pemfile = { version = "1.0.0", optional = true } hyper-tls = { version = "0.5", optional = true } hyper-rustls = { version = "0.24", optional = true, features = ["http2"] } serde = { version = "1", features = ["derive"] } serde_json = "1" url = "2" byteorder = "1" tar = "0.4" tokio = { version = "1", features = [ "time", "fs", "net", "io-util", "io-std", "test-util", "macros", "rt", "rt-multi-thread", ] } tokio-stream = { version = "0.1", features = ["io-util"] } tokio-util = { version = "0.7", features = ["io"] } log = "0.4" native-tls = { version = "0.2", optional = true } nix = "0.26" base64 = "0.21" dirs = "5.0" thiserror = "1" [dev-dependencies] rand = "0.8" reqwest = "0.11.24" [target.'cfg(unix)'.dependencies] hyperlocal = "0.8" [target.'cfg(windows)'.dependencies] named_pipe = "0.4" dockworker-0.5.1/README.md000064400000000000000000000052351046102023000132650ustar 00000000000000# Dockworker: Rust library for talking to the Docker daemon [![CircleCI](https://circleci.com/gh/Idein/dockworker/tree/master.svg?style=svg)](https://circleci.com/gh/Idein/dockworker/tree/master) [![Build status](https://ci.appveyor.com/api/projects/status/88ut6hplkw7vtjy4/branch/master?svg=true)](https://ci.appveyor.com/project/eldesh/dockworker) ## Support ### Environment - Docker - API version 1.26 - OS - Linux (developped in Ubuntu(amd64)) - Windows ### Api Supported Api List. `Support` means that any wrapper method exists in this crate. - container - [x] `/containers/json` - [x] `/containers/create` - [x] `/containers/{id}/json` - [x] `/containers/{id}/top` - [x] `/containers/{id}/logs` - [x] `/containers/{id}/changes` - [x] `/containers/{id}/export` - [x] `/containers/{id}/exec` - [x] `/containers/{id}/stats` - [ ] `/containers/{id}/resize` - [x] `/containers/{id}/start` - [x] `/containers/{id}/stop` - [x] `/containers/{id}/restart` - [x] `/containers/{id}/kill` - [ ] `/containers/{id}/update` - [ ] `/containers/{id}/rename` - [ ] `/containers/{id}/pause` - [ ] `/containers/{id}/unpause` - [x] `/containers/{id}/attach` - [ ] `/containers/{id}/attach/ws` - [x] `/containers/{id}/wait` - [x] `/containers/{id}` # remove - [x] `/containers/{id}/archive` - [ ] `/containers/{id}/prune` - checkpoints - [x] `/containers/{id}/checkpoints` - exec - [x] `/exec/{id}/start` - [x] `/exec/{id}/json` - image - [x] `/images/json` - [x] `/build` - [ ] `/build/prune` - [x] `/images/create` - [x] `/images/{name}/json` - [x] `/images/{name}/history` - [x] `/images/{name}/push` - [ ] `/images/{name}/tag` - [x] `/images/{name}` # remove - [ ] `/images/search` - [x] `/images/prune` - [ ] `/commit` - [x] `/images/{name}/get` - [ ] `/images/get` - [x] `/images/load` - system - [x] `/auth` - [x] `/info` - [x] `/version` - [x] `/_ping` - [x] `/events` - [ ] `/system/df` - network - [x] `/networks` - [x] `/networks/{id}` - [x] `/networks/{id}` # remove - [x] `/networks/create` - [x] `/networks/{id}/connect` - [x] `/networks/{id}/disconnect` - [x] `/networks/prune` ## Test Executing unit tests: ```shell $ cargo test ``` ### Depends on docker Some test cases depend on docker are disabled by default. These containers required from test cases are built by `docker-compose` like below: ```shell $ docker-compose build $ cargo test -- --ignored ``` ## Original Project Contributors `Dockworker` crate is forked from [boondock](https://github.com/faradayio/boondock). Heres are contributors to it. - Graham Lee - Toby Lawrence - Eric Kidd dockworker-0.5.1/examples/attach-container.rs000064400000000000000000000023441046102023000174140ustar 00000000000000use dockworker::{ container::ContainerStdioType, ContainerCreateOptions, ContainerHostConfig, Docker, }; #[tokio::main] async fn main() { let docker = Docker::connect_with_defaults().unwrap(); let mut host_config = ContainerHostConfig::new(); host_config.auto_remove(true); let mut create = ContainerCreateOptions::new("hello-world:linux"); create.host_config(host_config); let container = docker .create_container(Some("testing"), &create) .await .unwrap(); docker.start_container(&container.id).await.unwrap(); let mut res = docker .attach_container(&container.id, None, true, true, false, true, false) .await .unwrap(); use futures::stream::StreamExt; while let Some(stdio) = res.next().await.transpose().unwrap() { match stdio.type_ { ContainerStdioType::Stdin => { println!("{}", String::from_utf8(stdio.frame).unwrap()); } ContainerStdioType::Stdout => { println!("{}", String::from_utf8(stdio.frame).unwrap()); } ContainerStdioType::Stderr => { println!("{}", String::from_utf8(stdio.frame).unwrap()); } } } } dockworker-0.5.1/examples/build-image.rs000064400000000000000000000024251046102023000163470ustar 00000000000000use dockworker::{ContainerBuildOptions, Docker}; use futures::stream::StreamExt; use std::path::Path; use tar::Builder; #[tokio::main] async fn main() { { use tokio::io::AsyncWriteExt; let mut dockerfile = tokio::fs::File::create("Dockerfile").await.unwrap(); dockerfile .write_all( r#"FROM alpine:edge RUN echo Hi mum "# .as_bytes(), ) .await .unwrap(); } // Create tar file { let tar_file = tokio::fs::File::create("image.tar") .await .unwrap() .into_std() .await; let mut a = Builder::new(tar_file); a.append_path("Dockerfile").unwrap(); } let docker = Docker::connect_with_defaults().unwrap(); let name = "test-image"; let tag = "latest"; println!("build an image {name}:{tag} ..."); let options = ContainerBuildOptions { dockerfile: "Dockerfile".into(), t: vec!["silly:lat".to_owned()], ..ContainerBuildOptions::default() }; let mut stream = docker .build_image(options, Path::new("image.tar")) .await .unwrap(); while let Some(msg) = stream.next().await { println!("msg: {:?}", msg); } } dockworker-0.5.1/examples/container-logs.rs000064400000000000000000000034531046102023000171160ustar 00000000000000use std::collections::HashMap; use std::iter::FromIterator; use dockworker::{ ContainerCreateOptions, ContainerHostConfig, ContainerLogOptions, Docker, LogConfig, }; #[tokio::main] async fn main() { let docker = Docker::connect_with_defaults().unwrap(); let mut create = ContainerCreateOptions::new("alpine:latest"); create.tty(true); create.entrypoint(vec!["/bin/ping".into(), "-c".into(), "5".into()]); create.cmd("localhost".to_string()); create.host_config({ let mut host = ContainerHostConfig::new(); let log_config = LogConfig { config: HashMap::from_iter( vec![("tag".to_string(), "dockworker-test".to_string())].into_iter(), ), ..Default::default() }; println!("logging with: {log_config:?}"); host.log_config(log_config); host }); let container = docker.create_container(None, &create).await.unwrap(); docker.start_container(&container.id).await.unwrap(); println!("Container to log: {}", &container.id); let log_options = ContainerLogOptions { stdout: true, stderr: true, follow: true, ..ContainerLogOptions::default() }; let mut res = docker .log_container(&container.id, &log_options) .await .unwrap(); use futures::stream::StreamExt; while let Some(line) = res.next().await { match line { Ok(line) => println!("read: {line}"), Err(e) => eprintln!("err: {e:?}"), } } println!(); // line break // already stopped // docker // .stop_container(&container.id, Duration::from_secs(2)) // .await // .unwrap(); docker .remove_container(&container.id, None, Some(true), None) .await .unwrap(); } dockworker-0.5.1/examples/containers.rs000064400000000000000000000005721046102023000163360ustar 00000000000000use dockworker::{container::ContainerFilters, Docker}; #[tokio::main] async fn main() { let docker = Docker::connect_with_defaults().unwrap(); let filter = ContainerFilters::new(); let containers = docker .list_containers(None, None, None, filter) .await .unwrap(); containers.iter().for_each(|c| { println!("{c:?}"); }); } dockworker-0.5.1/examples/create-container.rs000064400000000000000000000005431046102023000174120ustar 00000000000000use dockworker::{ContainerCreateOptions, Docker}; #[tokio::main] async fn main() { let docker = Docker::connect_with_defaults().unwrap(); let create = ContainerCreateOptions::new("hello-world:linux"); let container = docker .create_container(Some("testing"), &create) .await .unwrap(); println!("{container:?}") } dockworker-0.5.1/examples/create-image.rs000064400000000000000000000006261046102023000165140ustar 00000000000000use dockworker::Docker; #[tokio::main] async fn main() { let docker = Docker::connect_with_defaults().unwrap(); let name = "debian"; let tag = "latest"; println!("create an image {name}:{tag} ..."); let mut stats = docker.create_image(name, tag).await.unwrap(); use futures::stream::StreamExt; while let Some(stat) = stats.next().await { println!("{stat:?}"); } } dockworker-0.5.1/examples/events.rs000064400000000000000000000014511046102023000154720ustar 00000000000000use dockworker::{ContainerCreateOptions, Docker}; #[tokio::main] async fn main() { let docker = Docker::connect_with_defaults().unwrap(); let mut events = docker.events(None, None, None).await.unwrap(); let create = ContainerCreateOptions::new("hello-world:linux"); docker.create_image("hello-world", "linux").await.unwrap(); let container = docker.create_container(None, &create).await.unwrap(); docker.start_container(&container.id).await.unwrap(); use futures::stream::StreamExt; while let Some(e) = events.next().await { let e = e.unwrap(); if e.Type == "network" && e.Action == "disconnect" { println!("{e:?}"); } } docker .remove_container(&container.id, None, Some(true), None) .await .unwrap(); } dockworker-0.5.1/examples/export.rs000064400000000000000000000013761046102023000155150ustar 00000000000000use dockworker::{container::ContainerFilters, Docker}; #[tokio::main] async fn main() { let docker = Docker::connect_with_defaults().unwrap(); let mut file = tokio::fs::File::create("temp.tar").await.unwrap(); if let Some(container) = docker .list_containers(None, None, None, ContainerFilters::default()) .await .unwrap() .get(0) { let res = docker .export_container(container.Id.as_str()) .await .unwrap(); use futures::stream::TryStreamExt; let mut res = tokio_util::io::StreamReader::new( res.map_err(|err| std::io::Error::new(std::io::ErrorKind::Other, err)), ); tokio::io::copy(&mut res, &mut file).await.unwrap(); } } dockworker-0.5.1/examples/filesystem.rs000064400000000000000000000010141046102023000163450ustar 00000000000000use dockworker::{container::ContainerFilters, Docker}; #[tokio::main] async fn main() { let docker = Docker::connect_with_defaults().unwrap(); if let Some(container) = docker .list_containers(None, None, None, ContainerFilters::default()) .await .unwrap() .get(0) { let changes = docker .filesystem_changes(container.Id.as_str()) .await .unwrap(); for change in changes { println!("{change:#?}"); } } } dockworker-0.5.1/examples/findports.rs000064400000000000000000000012241046102023000161740ustar 00000000000000use dockworker::{container::ContainerFilters, Docker}; #[tokio::main] async fn main() { let docker = Docker::connect_with_defaults().unwrap(); let containers = docker .list_containers(Some(true), None, None, ContainerFilters::default()) .await .unwrap(); for container in &containers { let info = docker.container_info(container.Id.as_str()).await.unwrap(); // Uncomment this to dump everything we know about a container. //println!("{:#?}", &info); println!("{}", info.Name); for (k, v) in info.NetworkSettings.Ports.iter() { println!("{k}: {v:?}"); } } } dockworker-0.5.1/examples/history.rs000064400000000000000000000004071046102023000156670ustar 00000000000000use dockworker::Docker; #[tokio::main] async fn main() { let docker = Docker::connect_with_defaults().unwrap(); let histories = docker.history_image("my_image_name").await.unwrap(); for change in histories { println!("{change:#?}"); } } dockworker-0.5.1/examples/images.rs000064400000000000000000000005351046102023000154350ustar 00000000000000use dockworker::Docker; #[tokio::main] async fn main() { let docker = Docker::connect_with_defaults().unwrap(); let images = docker.images(false).await.unwrap(); for image in &images { println!( "{} -> Size: {}, Size: {}, Created: {}", image.Id, image.Size, image.Size, image.Created ); } } dockworker-0.5.1/examples/info.rs000064400000000000000000000002631046102023000151210ustar 00000000000000use dockworker::Docker; #[tokio::main] async fn main() { let docker = Docker::connect_with_defaults().unwrap(); println!("{:#?}", docker.system_info().await.unwrap()); } dockworker-0.5.1/examples/load.rs000064400000000000000000000005211046102023000151020ustar 00000000000000use dockworker::Docker; use std::path::Path; #[tokio::main] async fn main() { let docker = Docker::connect_with_defaults().unwrap(); let id = docker .load_image(false, Path::new("image.tar")) .await .expect("prepare a tar-archive like: $docker save busybox > image.tar"); println!("loaded: {id}"); } dockworker-0.5.1/examples/login.rs000064400000000000000000000005511046102023000152760ustar 00000000000000use dockworker::Docker; #[tokio::main] async fn main() { let docker = Docker::connect_with_defaults().unwrap(); let token = docker .auth( "someusername", "somepassword", "someusername@example.com", "localhost:5000", ) .await .unwrap(); println!("token: {token:?}"); } dockworker-0.5.1/examples/networks.rs000064400000000000000000000022041046102023000160370ustar 00000000000000extern crate dockworker; use dockworker::{network::*, Docker}; use std::net::Ipv4Addr; #[tokio::main] async fn main() { let docker = Docker::connect_with_defaults().unwrap(); for network in docker .list_networks(ListNetworkFilters::default()) .await .unwrap() { println!( "{:20.12}{:25}{:10}{:8}", network.Id, network.Name, network.Driver, network.Scope ); } let create = { let mut opt = NetworkCreateOptions::new("example_network"); opt.enable_icc() .enable_ip_masquerade() .host_binding_ipv4(Ipv4Addr::new(0, 0, 0, 0)) .bridge_name("dockworker_ex_0") .driver_mtu(1500); opt.internal = true; opt }; println!( "create network: {}", serde_json::to_string_pretty(&create).unwrap() ); let res = docker.create_network(&create).await.unwrap(); println!("res: {res:?}"); let mut filter = ListNetworkFilters::default(); filter.id(res.Id.as_str().into()); println!("remove network: {}", res.Id); docker.remove_network(&res.Id).await.unwrap(); } dockworker-0.5.1/examples/ping.rs000064400000000000000000000002311046102023000151160ustar 00000000000000use dockworker::Docker; #[tokio::main] async fn main() { let docker = Docker::connect_with_defaults().unwrap(); docker.ping().await.unwrap(); } dockworker-0.5.1/examples/port_mapping.rs000064400000000000000000000012221046102023000166610ustar 00000000000000use dockworker::{ContainerCreateOptions, ContainerHostConfig, Docker, ExposedPorts, PortBindings}; #[tokio::main] async fn main() { let docker = Docker::connect_with_defaults().unwrap(); let mut create = ContainerCreateOptions::new("nginx:latest"); create.tty(true); create.exposed_ports(ExposedPorts(vec![(80, "tcp".to_string())])); let mut host_config = ContainerHostConfig::new(); host_config.port_bindings(PortBindings(vec![(80, "tcp".to_string(), 8080)])); let container = docker .create_container(Some("test"), &create) .await .unwrap(); docker.start_container(&container.id).await.unwrap(); } dockworker-0.5.1/examples/processes.rs000064400000000000000000000007421046102023000161760ustar 00000000000000use dockworker::{container::ContainerFilters, Docker}; #[tokio::main] async fn main() { let docker = Docker::connect_with_defaults().unwrap(); if let Some(container) = docker .list_containers(None, None, None, ContainerFilters::default()) .await .unwrap() .get(0) { let processes = docker.processes(container.Id.as_str()).await.unwrap(); for process in processes { println!("{process:#?}"); } } } dockworker-0.5.1/examples/prune-image.rs000064400000000000000000000011051046102023000163730ustar 00000000000000use dockworker::{container::ContainerFilters, Docker}; #[tokio::main] async fn main() { let docker = Docker::connect_with_defaults().unwrap(); let prunedt = docker.prune_image(true).await.unwrap(); println!("pruned(true): {prunedt:?}"); let prunedf = docker.prune_image(false).await.unwrap(); println!("pruned(false): {prunedf:?}"); let containers = docker .list_containers(Some(true), None, None, ContainerFilters::new()) .await .unwrap(); containers.iter().for_each(|c| { println!("image: {}", c.Image); }); } dockworker-0.5.1/examples/push.rs000064400000000000000000000013261046102023000151460ustar 00000000000000use dockworker::{ credentials::{Credential, UserPassword}, Docker, }; #[tokio::main] async fn main() { let docker = Docker::connect_with_defaults().unwrap(); let (name, tag) = ("alpine", "latest"); docker.create_image(name, tag).await.unwrap(); let serveraddress = "localhost:5000"; docker.set_credential(Credential::with_password(UserPassword::new( "someusername".to_owned(), "somepassword".to_owned(), "someusername@example.com".to_owned(), serveraddress.to_owned(), ))); println!("pulled: {name}:{tag}"); docker .push_image(&format!("{serveraddress}/{name}"), tag) .await .unwrap(); println!("pushed: {name}:{tag}"); } dockworker-0.5.1/examples/start-container.rs000064400000000000000000000006311046102023000173020ustar 00000000000000use dockworker::{ContainerCreateOptions, Docker}; #[tokio::main] async fn main() { let docker = Docker::connect_with_defaults().unwrap(); let mut create = ContainerCreateOptions::new("hello-world:linux"); create.tty(true); let container = docker .create_container(Some("testing"), &create) .await .unwrap(); docker.start_container(&container.id).await.unwrap(); } dockworker-0.5.1/examples/stats.rs000064400000000000000000000011361046102023000153240ustar 00000000000000use dockworker::{container::ContainerFilters, Docker}; #[tokio::main] async fn main() { let docker = Docker::connect_with_defaults().unwrap(); let containers = docker .list_containers(None, None, None, ContainerFilters::default()) .await .unwrap(); for container in containers { let mut stats = docker .stats(&container.Id, Some(false), Some(true)) .await .unwrap(); use futures::stream::StreamExt; while let Some(stats) = stats.next().await { println!("{:#?}", stats.unwrap()); } } } dockworker-0.5.1/examples/version.rs000064400000000000000000000002571046102023000156560ustar 00000000000000use dockworker::Docker; #[tokio::main] async fn main() { let docker = Docker::connect_with_defaults().unwrap(); println!("{:#?}", docker.version().await.unwrap()); } dockworker-0.5.1/src/checkpoint.rs000064400000000000000000000016501046102023000152670ustar 00000000000000#[cfg(feature = "experimental")] #[derive(Debug, Deserialize)] #[allow(non_snake_case)] pub struct Checkpoint { pub Name: String, } #[cfg(feature = "experimental")] #[derive(Debug, Clone, Deserialize, Serialize)] #[serde(rename_all = "PascalCase")] pub struct CheckpointCreateOptions { pub checkpoint_id: String, #[serde(skip_serializing_if = "Option::is_none")] // None -> set by docker to /var/lib/docker/containers/{containerid}/checkpoints pub checkpoint_dir: Option, #[serde(skip_serializing_if = "Option::is_none")] // None -> set by docker to false, container keeps running by default pub exit: Option, } #[cfg(feature = "experimental")] #[derive(Debug, Clone, Deserialize, Serialize)] pub struct CheckpointDeleteOptions { pub checkpoint_id: String, // None -> set by docker to /var/lib/docker/containers/{containerid}/checkpoints pub checkpoint_dir: Option, } dockworker-0.5.1/src/container.rs000064400000000000000000000300421046102023000151170ustar 00000000000000use crate::network::EndpointConfig; use serde::de::{self, DeserializeOwned, Deserializer}; use serde::{Deserialize, Serialize}; use std::collections::HashMap; use std::fmt; use std::str::FromStr; #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] #[allow(non_snake_case)] pub struct Container { pub Id: String, pub Image: String, pub ImageID: String, pub State: String, pub Status: String, pub Command: String, pub Created: u64, pub Names: Vec, pub Ports: Vec, pub SizeRw: Option, // I guess it is optional on Mac. pub SizeRootFs: Option, pub Labels: Option>, pub HostConfig: HostConfig, pub NetworkSettings: Option, pub Mounts: Option>, } #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)] #[allow(non_snake_case)] pub struct Port { pub IP: Option, pub PrivatePort: u64, pub PublicPort: Option, pub Type: PortType, } #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)] #[serde(rename_all = "snake_case")] pub enum PortType { Tcp, Udp, } #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)] #[allow(non_snake_case)] pub struct HostConfig { pub NetworkMode: String, } #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] #[allow(non_snake_case)] pub struct SummaryNetworkSettings { pub Networks: Option>>, } #[derive(Debug, Clone, Serialize, Deserialize)] #[allow(non_snake_case)] pub struct ContainerInfo { pub AppArmorProfile: String, pub Args: Vec, pub Config: Config, pub Created: String, pub Driver: String, // ExecIDs // GraphDriver // HostConfig pub HostnamePath: String, pub HostsPath: String, pub Id: String, pub Image: String, pub LogPath: String, pub MountLabel: String, pub Mounts: Vec, pub Name: String, pub NetworkSettings: NetworkSettings, pub Path: String, pub ProcessLabel: String, pub ResolvConfPath: String, pub RestartCount: u64, pub State: State, } #[derive(Debug, Clone, Deserialize)] #[allow(non_snake_case)] pub struct ExecProcessConfig { pub arguments: Vec, pub entrypoint: String, pub privileged: bool, pub tty: bool, pub user: Option, } #[derive(Debug, Clone, Deserialize)] #[allow(non_snake_case)] pub struct ExecInfo { pub CanRemove: bool, pub ContainerID: String, pub DetachKeys: String, pub ExitCode: Option, pub ID: String, pub OpenStderr: bool, pub OpenStdin: bool, pub OpenStdout: bool, pub ProcessConfig: ExecProcessConfig, pub Running: bool, pub Pid: u64, } /// This type represents a `struct{}` in the Go code. pub type UnspecifiedObject = HashMap; #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[allow(non_snake_case)] pub struct Config { pub AttachStderr: bool, pub AttachStdin: bool, pub AttachStdout: bool, #[serde(deserialize_with = "null_to_default")] pub Cmd: Vec, pub Domainname: String, #[serde(deserialize_with = "null_to_default")] pub Entrypoint: Vec, #[serde(deserialize_with = "null_to_default")] pub Env: Vec, #[serde(default = "Default::default")] pub ExposedPorts: HashMap, pub Hostname: String, pub Image: String, #[serde(deserialize_with = "null_to_default")] pub Labels: HashMap, #[serde(deserialize_with = "null_to_default")] pub OnBuild: Vec, pub OpenStdin: bool, pub StdinOnce: bool, pub Tty: bool, pub User: String, #[serde(deserialize_with = "null_to_default")] pub Volumes: HashMap, pub WorkingDir: String, } #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)] #[allow(non_snake_case)] pub struct Mount { // Name (optional) // Driver (optional) pub Source: String, pub Destination: String, pub Mode: String, pub RW: bool, pub Propagation: String, } #[derive(Debug, Clone, Serialize, Deserialize)] #[allow(non_snake_case)] pub struct NetworkSettings { pub Bridge: String, pub EndpointID: String, pub Gateway: String, pub GlobalIPv6Address: String, pub GlobalIPv6PrefixLen: u32, pub HairpinMode: bool, pub IPAddress: String, pub IPPrefixLen: u32, pub IPv6Gateway: String, pub LinkLocalIPv6Address: String, pub LinkLocalIPv6PrefixLen: u32, pub MacAddress: String, /// network name to Network mapping pub Networks: HashMap, pub Ports: HashMap>>, pub SandboxID: String, pub SandboxKey: String, // These two are null in the current output. //pub SecondaryIPAddresses: , //pub SecondaryIPv6Addresses: , } pub type Network = EndpointConfig; #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)] #[allow(non_snake_case)] pub struct PortMapping { pub HostIp: String, pub HostPort: String, } #[derive(Debug, Clone, Serialize, Deserialize)] #[allow(non_snake_case)] pub struct LogMessage { pub Start: String, pub End: String, pub ExitCode: u64, pub Output: String, } #[derive(Debug, Clone, Serialize, PartialEq, PartialOrd, Eq, Ord)] #[serde(rename_all = "lowercase")] pub enum HealthState { /// Indicates there is no healthcheck NoHealthcheck, /// Indicates that the container is not yet ready Starting, /// Indicates that the container is running correctly Healthy, /// Indicates that the container has a problem Unhealthy, } impl fmt::Display for HealthState { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { HealthState::NoHealthcheck => write!(f, "none"), HealthState::Starting => write!(f, "starting"), HealthState::Healthy => write!(f, "healthy"), HealthState::Unhealthy => write!(f, "unhealthy"), } } } impl<'de> Deserialize<'de> for HealthState { fn deserialize(deserializer: D) -> std::result::Result where D: Deserializer<'de>, { let s = String::deserialize(deserializer)?; s.parse().map_err(de::Error::custom) } } impl FromStr for HealthState { type Err = String; fn from_str(s: &str) -> std::result::Result { match s { "none" => Ok(HealthState::NoHealthcheck), "starting" => Ok(HealthState::Starting), "healthy" => Ok(HealthState::Healthy), "unhealthy" => Ok(HealthState::Unhealthy), _ => Err(format!("Cannot parse {s} into known HealthState variant!")), } } } #[derive(Debug, Clone, Serialize, Deserialize)] #[allow(non_snake_case)] pub struct Health { pub Status: HealthState, pub FailingStreak: u64, pub Log: Vec, } #[derive(Debug, Clone, Serialize, Deserialize)] #[allow(non_snake_case)] pub struct State { pub Status: String, pub Running: bool, pub Paused: bool, pub Restarting: bool, pub OOMKilled: bool, pub Dead: bool, // I don't know whether PIDs can be negative here. They're normally // positive, but sometimes negative PIDs are used in certain APIs. pub Pid: i64, pub ExitCode: i64, pub Error: String, pub StartedAt: String, pub FinishedAt: String, #[serde(skip_serializing_if = "Option::is_none")] pub Health: Option, } impl std::fmt::Display for Container { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::result::Result<(), std::fmt::Error> { write!(f, "{}", self.Id) } } impl std::fmt::Display for ContainerInfo { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::result::Result<(), std::fmt::Error> { write!(f, "{}", self.Id) } } #[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Serialize)] #[serde(rename_all = "lowercase")] pub enum ContainerStatus { Created, Restarting, Running, Removing, Paused, Exited, Dead, } #[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Serialize, Default)] pub struct ContainerFilters { #[serde(skip_serializing_if = "Vec::is_empty")] id: Vec, #[serde(skip_serializing_if = "Vec::is_empty")] name: Vec, #[serde(skip_serializing_if = "Vec::is_empty")] status: Vec, } impl ContainerFilters { pub fn new() -> Self { Self::default() } pub fn id(&mut self, id: &str) -> &mut Self { self.id.push(id.to_owned()); self } pub fn name(&mut self, name: &str) -> &mut Self { self.name.push(name.to_owned()); self } pub fn status(&mut self, status: ContainerStatus) -> &mut Self { self.status.push(status); self } } #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub enum ContainerStdioType { Stdin, Stdout, Stderr, } /// response fragment of the attach container api #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone)] pub struct AttachResponseFrame { pub type_: ContainerStdioType, pub frame: Vec, } #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] #[allow(non_snake_case)] pub struct ExitStatus { StatusCode: i32, } impl ExitStatus { pub fn new(status_code: i32) -> Self { Self { StatusCode: status_code, } } pub fn into_inner(self) -> i32 { self.StatusCode } } impl From for ExitStatus { fn from(status_code: i32) -> Self { Self::new(status_code) } } fn null_to_default<'de, D, T>(de: D) -> std::result::Result where D: Deserializer<'de>, T: DeserializeOwned + Default, { let actual: Option = Option::deserialize(de)?; Ok(actual.unwrap_or_default()) } #[cfg(test)] mod test { use super::*; // https://github.com/idein/dockworker/issues/84 #[test] fn serde_network() { let network_settings_str = r#"{ "Bridge": "", "SandboxID": "7c5ebca03e210aa5cdfa81206950a72584930291812fc82502ae0406efca60cf", "HairpinMode": false, "LinkLocalIPv6Address": "", "LinkLocalIPv6PrefixLen": 0, "Ports": { "3306/tcp": null }, "SandboxKey": "/var/run/docker/netns/7c5ebcaace21", "SecondaryIPAddresses": null, "SecondaryIPv6Addresses": null, "EndpointID": "0a9c1de4bebcbf778248009fe2b4a747478e2136645563de7ba8d48f287d9388", "Gateway": "172.11.0.1", "GlobalIPv6Address": "", "GlobalIPv6PrefixLen": 0, "IPAddress": "171.11.0.70", "IPPrefixLen": 16, "IPv6Gateway": "", "MacAddress": "01:42:0c:11:c0:f9", "Networks": { "bridge": { "IPAMConfig": {}, "Links": null, "Aliases": null, "NetworkID": "c6bcc45303b33fb881911c25e755da483291123b0a8099e42b2226bcd4f2d549", "EndpointID": "0a9c1de4bebcbf778248009fe2b4a74432012136645563de7ba8719e987d9388", "Gateway": "172.11.0.1", "IPAddress": "172.11.0.70", "IPPrefixLen": 16, "IPv6Gateway": "", "GlobalIPv6Address": "", "GlobalIPv6PrefixLen": 0, "MacAddress": "01:42:0c:11:c0:f9", "DriverOpts": null } } }"#; let network_settings: NetworkSettings = serde_json::from_str(network_settings_str).unwrap(); let network_settings_json: serde_json::Value = serde_json::to_value(network_settings).unwrap(); let network_settings_serde: serde_json::Value = { let network_settings_str = serde_json::to_string(&network_settings_json).unwrap(); serde_json::from_str(&network_settings_str).unwrap() }; assert_eq!(network_settings_json, network_settings_serde); } } dockworker-0.5.1/src/credentials.rs000064400000000000000000000034201046102023000154320ustar 00000000000000///! Access credentials for accessing any docker daemon endpoints ///! ///! Currently, any values of these types are only used for `/images/{name}/push` api. use crate::system::AuthToken; use serde::{Deserialize, Serialize}; /// Access credential #[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)] #[serde(untagged)] pub enum Credential { /// identity token issued by docker registry Token(IdentityToken), /// user password Password(UserPassword), } impl Credential { pub fn with_token(token: IdentityToken) -> Self { Credential::Token(token) } pub fn with_password(password: UserPassword) -> Self { Credential::Password(password) } } /// User informations for accessing apis /// /// At least, this value is required for accessing `/images/{name}/push` api. #[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)] pub struct UserPassword { username: String, password: String, email: String, serveraddress: String, } impl UserPassword { pub fn new(username: String, password: String, email: String, serveraddress: String) -> Self { Self { username, password, email, serveraddress, } } } /// Access token for accessing apis #[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)] pub struct IdentityToken { identitytoken: String, } impl IdentityToken { #[allow(dead_code)] pub fn token(&self) -> String { self.identitytoken.clone() } #[allow(dead_code)] pub fn from_auth_token(auth_token: &AuthToken) -> Self { Self { identitytoken: auth_token.token(), } } pub fn from_bare_token(token: String) -> Self { Self { identitytoken: token, } } } dockworker-0.5.1/src/docker.rs000064400000000000000000002326501046102023000144150ustar 00000000000000#![allow(clippy::bool_assert_comparison)] use crate::container::{ AttachResponseFrame, Container, ContainerFilters, ContainerInfo, ContainerStdioType, ExecInfo, ExitStatus, }; pub use crate::credentials::{Credential, UserPassword}; use crate::errors::{DockerError, Error as DwError}; use crate::event::EventResponse; use crate::filesystem::{FilesystemChange, XDockerContainerPathStat}; use crate::http_client::{HaveHttpClient, HttpClient}; use crate::hyper_client::HyperClient; use crate::image::{Image, ImageId, SummaryImage}; use crate::network::*; use crate::options::*; use crate::process::{Process, Top}; use crate::response::Response as DockerResponse; use crate::signal::Signal; use crate::stats::Stats; use crate::system::{AuthToken, SystemInfo}; use crate::version::Version; use base64::{engine::general_purpose, Engine as _}; use bytes::Bytes; #[cfg(feature = "experimental")] use checkpoint::{Checkpoint, CheckpointCreateOptions, CheckpointDeleteOptions}; use futures::stream::BoxStream; use http::{HeaderMap, StatusCode}; use log::debug; use serde::de::DeserializeOwned; use std::env; use std::path::{Path, PathBuf}; use std::time::Duration; async fn into_aframe_stream( body: hyper::Body, ) -> Result>, DwError> { use futures::stream::StreamExt; use futures::stream::TryStreamExt; let mut aread = tokio_util::io::StreamReader::new( body.map_err(|err| std::io::Error::new(std::io::ErrorKind::Other, err)), ); let mut buf = [0u8; 8]; let src = async_stream::stream! { loop { use tokio::io::AsyncReadExt; if let Err(err) = aread.read_exact(&mut buf).await { if err.kind() == std::io::ErrorKind::UnexpectedEof { break; // end of stream } log::error!("unexpected io error{:?}", err); yield Err(DwError::from(err)); break; } // read body let mut frame_size_raw = &buf[4..]; let frame_size = byteorder::ReadBytesExt::read_u32::(&mut frame_size_raw) .map_err(|e| DwError::Unknown{ message: format!("unexpeced buffer: {e:?}") })?; let mut frame = vec![0; frame_size as usize]; if let Err(err) = aread.read_exact(&mut frame).await { if err.kind() == std::io::ErrorKind::UnexpectedEof { break; // end of stream } log::error!("unexpected io error{:?}", err); yield Err(DwError::from(err)); break; } match buf[0] { 0 => { yield Ok(AttachResponseFrame{ type_: ContainerStdioType::Stdin, frame }); }, 1 => { yield Ok(AttachResponseFrame{ type_: ContainerStdioType::Stdout, frame }); }, 2 => { yield Ok(AttachResponseFrame{ type_: ContainerStdioType::Stderr, frame }); }, n => { log::error!("unexpected kind of chunk: {}", n); yield Err(DwError::Unknown{ message: format!("unexpected kind of chunk: {}",n) }); break; } } } }; Ok(src.boxed()) } async fn into_docker_error(body: hyper::Body) -> Result { let body = hyper::body::to_bytes(body).await?; let err = serde_json::from_slice::(body.as_ref())?; Ok(err) } fn into_lines(body: hyper::Body) -> Result>, DwError> { use futures::stream::StreamExt; use futures::stream::TryStreamExt; use tokio::io::AsyncBufReadExt; let aread = tokio_util::io::StreamReader::new( body.map_err(|err| std::io::Error::new(std::io::ErrorKind::Other, err)), ); let stream = tokio_stream::wrappers::LinesStream::new(aread.lines()); let stream = stream.map_err(Into::into).boxed(); Ok(stream) } pub fn into_jsonlines( body: hyper::Body, ) -> Result>, DwError> where T: DeserializeOwned, { use futures::StreamExt; let o = into_lines(body)?; let stream = o .map(|o| match o { Ok(o) => serde_json::from_str(&o).map_err(Into::into), Err(e) => Err(e), }) .boxed(); Ok(stream) } /// The default `DOCKER_HOST` address that we will try to connect to. #[cfg(unix)] pub static DEFAULT_DOCKER_HOST: &str = "unix:///var/run/docker.sock"; /// The default `DOCKER_HOST` address that we will try to connect to. /// /// This should technically be `"npipe:////./pipe/docker_engine"` on /// Windows, but we don't support Windows pipes yet. However, the TCP port /// is still available. #[cfg(windows)] pub static DEFAULT_DOCKER_HOST: &'static str = "tcp://localhost:2375"; /// The default directory in which to look for our Docker certificate /// files. pub fn default_cert_path() -> Result { let from_env = env::var("DOCKER_CERT_PATH").or_else(|_| env::var("DOCKER_CONFIG")); if let Ok(ref path) = from_env { Ok(PathBuf::from(path)) } else { let home = dirs::home_dir().ok_or(DwError::NoCertPath)?; Ok(home.join(".docker")) } } /// protocol connect to docker daemon #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone)] enum Protocol { /// unix domain socket Unix, /// tcp/ip (BSD like socket) Tcp, } /// Handle to connection to the docker daemon #[derive(Debug, Clone)] pub struct Docker { /// http client client: HyperClient, /// connection protocol #[allow(dead_code)] protocol: Protocol, /// http headers used for any requests headers: HeaderMap, /// access credential for accessing apis credential: std::sync::Arc>>, } /// Deserialize from json string fn api_result(res: http::Response>) -> Result { if res.status().is_success() { Ok(serde_json::from_slice::(res.body())?) } else { Err(serde_json::from_slice::(res.body())?.into()) } } /// Expect 204 NoContent fn no_content(res: http::Response>) -> Result<(), DwError> { if res.status() == StatusCode::NO_CONTENT { Ok(()) } else { Err(serde_json::from_slice::(res.body())?.into()) } } /// Expect 204 NoContent or 304 NotModified fn no_content_or_not_modified(res: http::Response>) -> Result<(), DwError> { if res.status() == StatusCode::NO_CONTENT || res.status() == StatusCode::NOT_MODIFIED { Ok(()) } else { Err(serde_json::from_slice::(res.body())?.into()) } } /// Ignore succeed response /// /// Read whole response body, then ignore it. fn ignore_result(res: http::Response>) -> Result<(), DwError> { if res.status().is_success() { Ok(()) } else { Err(serde_json::from_slice::(res.body())?.into()) } } impl Docker { fn new(client: HyperClient, protocol: Protocol) -> Self { Self { client, protocol, headers: HeaderMap::new(), credential: std::sync::Arc::new(std::sync::Mutex::new(None)), } } pub fn set_credential(&self, credential: Credential) { let mut o = self.credential.lock().unwrap(); *o = Some(credential) } fn headers(&self) -> &HeaderMap { &self.headers } /// Connect to the Docker daemon /// /// # Summary /// Connect to the Docker daemon using the standard Docker configuration options. /// This includes: /// - `DOCKER_HOST` /// - `DOCKER_TLS_VERIFY` /// - `DOCKER_CERT_PATH` /// - `DOCKER_CONFIG` /// /// and we try to interpret these as much like the standard `docker` client as possible. pub fn connect_with_defaults() -> Result { // Read in our configuration from the Docker environment. let host = env::var("DOCKER_HOST").unwrap_or_else(|_| DEFAULT_DOCKER_HOST.to_string()); let tls_verify = env::var("DOCKER_TLS_VERIFY").is_ok(); let cert_path = default_cert_path()?; // Dispatch to the correct connection function. if host.starts_with("unix://") { Docker::connect_with_unix(&host) } else if host.starts_with("tcp://") { if tls_verify { Docker::connect_with_ssl( &host, &cert_path.join("key.pem"), &cert_path.join("cert.pem"), &cert_path.join("ca.pem"), ) } else { Docker::connect_with_http(&host) } } else { Err(DwError::UnsupportedScheme { host }) } } /// This ensures that using a fully-qualified path /// /// e.g. unix://.... -- works. /// The unix socket provider expects a Path, so we don't need scheme. #[cfg(unix)] pub fn connect_with_unix(addr: &str) -> Result { if let Some(addr) = addr.strip_prefix("unix://") { let client = HyperClient::connect_with_unix(addr); Ok(Docker::new(client, Protocol::Unix)) } else { let client = HyperClient::connect_with_unix(addr); Ok(Docker::new(client, Protocol::Unix)) } } #[cfg(not(unix))] pub fn connect_with_unix(addr: &str) -> Result { Err(DwError::UnsupportedScheme { host: addr.to_owned(), } .into()) } #[cfg(any(feature = "openssl", feature = "rustls"))] pub fn connect_with_ssl( addr: &str, key: &Path, cert: &Path, ca: &Path, ) -> Result { let client = HyperClient::connect_with_ssl(addr, key, cert, ca).map_err(|err| { DwError::CouldNotConnect { addr: addr.to_owned(), source: err.into(), } })?; Ok(Docker::new(client, Protocol::Tcp)) } #[cfg(not(any(feature = "openssl", feature = "rustls")))] pub fn connect_with_ssl( _addr: &str, _key: &Path, _cert: &Path, _ca: &Path, ) -> Result { Err(DwError::SslDisabled) } /// Connect using unsecured HTTP. This is strongly discouraged /// everywhere but on Windows when npipe support is not available. pub fn connect_with_http(addr: &str) -> Result { let client = HyperClient::connect_with_http(addr).map_err(|err| DwError::CouldNotConnect { addr: addr.to_owned(), source: err.into(), })?; Ok(Docker::new(client, Protocol::Tcp)) } /// List containers /// /// # API /// /containers/json pub async fn list_containers( &self, all: Option, limit: Option, size: Option, filters: ContainerFilters, ) -> Result, DwError> { let mut param = url::form_urlencoded::Serializer::new(String::new()); param.append_pair("all", &(all.unwrap_or(false) as u64).to_string()); if let Some(limit) = limit { param.append_pair("limit", &limit.to_string()); } param.append_pair("size", &(size.unwrap_or(false) as u64).to_string()); param.append_pair("filters", &serde_json::to_string(&filters).unwrap()); debug!("filter: {}", serde_json::to_string(&filters).unwrap()); let res = self .http_client() .get( self.headers(), &format!("/containers/json?{}", param.finish()), ) .await?; api_result(res).map_err(Into::into) } /// Create a container /// /// # Summary /// /// * `name` - None: auto naming /// * `option` - create options /// /// # API /// POST /containers/create?{name} pub async fn create_container( &self, name: Option<&str>, option: &ContainerCreateOptions, ) -> Result { let path = match name { Some(name) => { let mut param = url::form_urlencoded::Serializer::new(String::new()); param.append_pair("name", name); format!("/containers/create?{}", param.finish()) } None => "/containers/create".to_string(), }; let json_body = serde_json::to_string(&option)?; let mut headers = self.headers().clone(); headers.insert( http::header::CONTENT_TYPE, "application/json".parse().unwrap(), ); let res = self.http_client().post(&headers, &path, &json_body).await?; api_result(res).map_err(Into::into) } /// Start a container /// /// # API /// /containers/{id}/start pub async fn start_container(&self, id: &str) -> Result<(), DwError> { let res = self .http_client() .post(self.headers(), &format!("/containers/{id}/start"), "") .await?; no_content(res).map_err(Into::into) } /// Start a container from a checkpoint /// /// Using normal container start endpoint with preconfigured arguments /// /// # API /// /containers/{id}/start #[cfg(feature = "experimental")] pub async fn resume_container_from_checkpoint( &self, id: &str, checkpoint_id: &str, checkpoint_dir: Option<&str>, ) -> Result<(), DwError> { let mut param = url::form_urlencoded::Serializer::new(String::new()); param.append_pair("checkpoint", &checkpoint_id); if let Some(dir) = checkpoint_dir { param.append_pair("checkpoint-dir", &dir); } self.http_client() .post( self.headers(), &format!("/containers/{}/start?{}", id, param.finish()), "", ) .await?; no_content(res).map_err(Into::into) } /// Stop a container /// /// # API /// /containers/{id}/stop pub async fn stop_container(&self, id: &str, timeout: Duration) -> Result<(), DwError> { let mut param = url::form_urlencoded::Serializer::new(String::new()); param.append_pair("t", &timeout.as_secs().to_string()); let res = self .http_client() .post( self.headers(), &format!("/containers/{}/stop?{}", id, param.finish()), "", ) .await?; no_content_or_not_modified(res).map_err(Into::into) } /// Kill a container /// /// # API /// /containers/{id}/kill pub async fn kill_container(&self, id: &str, signal: Signal) -> Result<(), DwError> { let mut param = url::form_urlencoded::Serializer::new(String::new()); param.append_pair("signal", &signal.as_i32().to_string()); let res = self .http_client() .post( self.headers(), &format!("/containers/{}/kill?{}", id, param.finish()), "", ) .await?; no_content(res).map_err(Into::into) } /// Restart a container /// /// # API /// /containers/{id}/restart pub async fn restart_container(&self, id: &str, timeout: Duration) -> Result<(), DwError> { let mut param = url::form_urlencoded::Serializer::new(String::new()); param.append_pair("t", &timeout.as_secs().to_string()); let res = self .http_client() .post( self.headers(), &format!("/containers/{}/restart?{}", id, param.finish()), "", ) .await?; no_content(res).map_err(Into::into) } /// Attach to a container /// /// Attach to a container to read its output or send it input. /// /// # API /// /containers/{id}/attach #[allow(non_snake_case)] #[allow(clippy::too_many_arguments)] pub async fn attach_container( &self, id: &str, detachKeys: Option<&str>, logs: bool, stream: bool, stdin: bool, stdout: bool, stderr: bool, ) -> Result>, DwError> { let mut param = url::form_urlencoded::Serializer::new(String::new()); if let Some(keys) = detachKeys { param.append_pair("detachKeys", keys); } param.append_pair("logs", &logs.to_string()); param.append_pair("stream", &stream.to_string()); param.append_pair("stdin", &stdin.to_string()); param.append_pair("stdout", &stdout.to_string()); param.append_pair("stderr", &stderr.to_string()); let res = self .http_client() .post_stream( self.headers(), &format!("/containers/{}/attach?{}", id, param.finish()), "", ) .await?; if res.status().is_success() { into_aframe_stream(res.into_body()).await } else { Err(into_docker_error(res.into_body()).await?.into()) } } /// List existing checkpoints from container /// /// Lists all snapshots made from the container in the specified directory. /// /// # API /// GET /containers/{id}/checkpoints #[cfg(feature = "experimental")] #[allow(non_snake_case)] pub async fn list_container_checkpoints( &self, id: &str, dir: Option, ) -> Result, DwError> { let mut headers = self.headers().clone(); headers.set::(ContentType::json()); let mut param = url::form_urlencoded::Serializer::new(String::new()); if let Some(_dir) = dir { param.append_pair("dir", &_dir); } let res = self .http_client() .get( &headers, &format!("/containers/{}/checkpoints?{}", id, param.finish()), ) .await?; api_result(res).map_err(Into::into) } /// Create Checkpoint from current running container /// /// Create a snapshot of the container's current state. /// /// # API /// POST /containers/{id}/checkpoints #[cfg(feature = "experimental")] #[allow(non_snake_case)] pub async fn checkpoint_container( &self, id: &str, option: &CheckpointCreateOptions, ) -> Result<(), DwError> { let json_body = serde_json::to_string(&option)?; let mut headers = self.headers().clone(); headers.set::(ContentType::json()); let res = self .http_client() .post( &headers, &format!("/containers/{}/checkpoints", id), &json_body, ) .await?; if res.status.is_success() && res.status == StatusCode::CREATED { Ok(()) } else { Err(serde_json::from_reader::<_, DockerError>(res)?.into()) } } /// Delete a checkpoint /// /// Delete a snapshot of a container specified by its name. /// /// # API /// DELETE /containers/{id}/checkpoints #[cfg(feature = "experimental")] #[allow(non_snake_case)] pub async fn delete_checkpoint( &self, id: &str, option: &CheckpointDeleteOptions, ) -> Result<(), DwError> { let mut headers = self.headers().clone(); headers.set::(ContentType::json()); let mut param = url::form_urlencoded::Serializer::new(String::new()); let options = option.clone(); if let Some(checkpoint_dir) = options.checkpoint_dir { param.append_pair("dir", &checkpoint_dir); } let res = self .http_client() .delete( &headers, &format!( "/containers/{}/checkpoints/{}?{}", id, option.checkpoint_id, param.finish() ), ) .await?; no_content(res).map_err(Into::into) } /// Create Exec instance for a container /// /// Run a command inside a running container. /// /// # API /// /containers/{id}/exec #[allow(non_snake_case)] pub async fn exec_container( &self, id: &str, option: &CreateExecOptions, ) -> Result { let json_body = serde_json::to_string(&option)?; let mut headers = self.headers().clone(); headers.insert( http::header::CONTENT_TYPE, "application/json".parse().unwrap(), ); let res = self .http_client() .post(&headers, &format!("/containers/{id}/exec"), &json_body) .await?; api_result(res).map_err(Into::into) } /// Start an exec instance /// /// Starts a previously set up exec instance. If detach is true, this endpoint returns immediately after starting the command. Otherwise, it sets up an interactive session with the command. /// /// # API /// /exec/{id}/start #[allow(non_snake_case)] pub async fn start_exec( &self, id: &str, option: &StartExecOptions, ) -> Result>, DwError> { let json_body = serde_json::to_string(&option)?; let mut headers = self.headers().clone(); headers.insert( http::header::CONTENT_TYPE, "application/json".parse().unwrap(), ); let res = self .http_client() .post_stream(&headers, &format!("/exec/{id}/start"), &json_body) .await?; if res.status().is_success() { into_aframe_stream(res.into_body()).await } else { Err(into_docker_error(res.into_body()).await?.into()) } } /// Inspect an exec instance /// /// Return low-level information about an exec instance. /// /// # API /// /exec/{id}/json #[allow(non_snake_case)] pub async fn exec_inspect(&self, id: &str) -> Result { let res = self .http_client() .get(self.headers(), &format!("/exec/{id}/json")) .await?; api_result(res).map_err(Into::into) } /// Gets current logs and tails logs from a container /// /// # API /// /containers/{id}/logs pub async fn log_container( &self, id: &str, option: &ContainerLogOptions, ) -> Result>, DwError> { let res = self .http_client() .get_stream( self.headers(), &format!("/containers/{}/logs?{}", id, option.to_url_params()), ) .await?; if res.status().is_success() { into_lines(res.into_body()) } else { Err(into_docker_error(res.into_body()).await?.into()) } } /// List processes running inside a container /// /// # API /// /containers/{id}/top pub async fn container_top(&self, container_id: &str) -> Result { let res = self .http_client() .get(self.headers(), &format!("/containers/{container_id}/top")) .await?; api_result(res).map_err(Into::into) } pub async fn processes(&self, container_id: &str) -> Result, DwError> { let top = self.container_top(container_id).await?; Ok(top .Processes .iter() .map(|process| { let mut p = Process::default(); for (i, value) in process.iter().enumerate() { let v = value.clone(); match top.Titles[i].as_ref() { "UID" => p.user = v, "USER" => p.user = v, "PID" => p.pid = v, "%CPU" => p.cpu = Some(v), "%MEM" => p.memory = Some(v), "VSZ" => p.vsz = Some(v), "RSS" => p.rss = Some(v), "TTY" => p.tty = Some(v), "STAT" => p.stat = Some(v), "START" => p.start = Some(v), "STIME" => p.start = Some(v), "TIME" => p.time = Some(v), "CMD" => p.command = v, "COMMAND" => p.command = v, _ => {} } } p }) .collect()) } /// Get containers stats based resource usage /// /// # API /// GET /containers/{id}/stats pub async fn stats( &self, container_id: &str, stream: Option, oneshot: Option, ) -> Result>, DwError> { let mut query = url::form_urlencoded::Serializer::new(String::new()); query.append_pair("stream", &stream.unwrap_or(true).to_string()); query.append_pair("one-shot", &oneshot.unwrap_or(false).to_string()); let res = self .http_client() .get_stream( self.headers(), &format!("/containers/{}/stats?{}", container_id, query.finish()), ) .await?; if res.status().is_success() { into_jsonlines(res.into_body()) } else { Err(into_docker_error(res.into_body()).await?.into()) } } /// Wait for a container /// /// # API /// /containers/{id}/wait pub async fn wait_container(&self, id: &str) -> Result { let res = self .http_client() .post(self.headers(), &format!("/containers/{id}/wait"), "") .await?; api_result(res).map_err(Into::into) } /// Remove a container /// /// # API /// /containers/{id} pub async fn remove_container( &self, id: &str, volume: Option, force: Option, link: Option, ) -> Result<(), DwError> { let mut param = url::form_urlencoded::Serializer::new(String::new()); param.append_pair("v", &volume.unwrap_or(false).to_string()); param.append_pair("force", &force.unwrap_or(false).to_string()); param.append_pair("link", &link.unwrap_or(false).to_string()); let res = self .http_client() .delete( self.headers(), &format!("/containers/{}?{}", id, param.finish()), ) .await?; no_content(res).map_err(Into::into) } /// Get an archive of a filesystem resource in a container /// /// # API /// /containers/{id}/archive pub async fn get_file( &self, id: &str, path: &Path, ) -> Result>, DwError> { let mut param = url::form_urlencoded::Serializer::new(String::new()); debug!("get_file({}, {})", id, path.display()); param.append_pair("path", path.to_str().unwrap_or("")); // FIXME: cause an invalid path error let res = self .http_client() .get_stream( self.headers(), &format!("/containers/{}/archive?{}", id, param.finish()), ) .await?; if res.status().is_success() { use futures::stream::StreamExt; use futures::stream::TryStreamExt; Ok(res.into_body().map_err(DwError::from).boxed()) } else { Err(into_docker_error(res.into_body()).await?.into()) } } /// Get information about files in a container /// /// # API /// /containers/{id}/archive pub async fn head_file( &self, id: &str, path: &Path, ) -> Result { let mut param = url::form_urlencoded::Serializer::new(String::new()); debug!("head_file({}, {})", id, path.display()); param.append_pair("path", path.to_str().unwrap_or("")); let res = self .http_client() .head( self.headers(), &format!("/containers/{}/archive?{}", id, param.finish()), ) .await?; let stat_base64: &str = res .get("X-Docker-Container-Path-Stat") .map(|h| h.to_str().unwrap_or("")) .unwrap_or(""); let bytes = general_purpose::STANDARD .decode(stat_base64) .map_err(|err| DwError::ParseError { input: String::from(stat_base64), source: err, })?; let path_stat: XDockerContainerPathStat = serde_json::from_slice(&bytes)?; Ok(path_stat) } /// Extract an archive of files or folders to a directory in a container /// /// # Summary /// Extract given src file into the container specified with id. /// The input file must be a tar archive with id(no compress), gzip, bzip2 or xz. /// /// * id : container name or ID /// * src : path to a source *file* /// * dst : path to a *directory* in the container to extract the archive's contents into /// /// # API /// /containers/{id}/archive #[allow(non_snake_case)] pub async fn put_file( &self, id: &str, src: &Path, dst: &Path, noOverwriteDirNonDir: bool, ) -> Result<(), DwError> { debug!( "put_file({}, {}, {}, {})", id, src.display(), dst.display(), noOverwriteDirNonDir ); let mut param = url::form_urlencoded::Serializer::new(String::new()); param.append_pair("path", &dst.to_string_lossy()); param.append_pair("noOverwriteDirNonDir", &noOverwriteDirNonDir.to_string()); let res = self .http_client() .put_file( self.headers(), &format!("/containers/{}/archive?{}", id, param.finish()), src, ) .await?; ignore_result(res).map_err(Into::into) } /// Build an image from a tar archive with a Dockerfile in it. /// /// # API /// /build? pub async fn build_image( &self, options: ContainerBuildOptions, tar_path: &Path, ) -> Result>, DwError> { let mut headers = self.headers().clone(); headers.insert( http::header::CONTENT_TYPE, "application/x-tar".parse().unwrap(), ); let res = self .http_client() .post_file_stream( &headers, &format!("/build?{}", options.to_url_params()), tar_path, ) .await?; if res.status().is_success() { into_jsonlines(res.into_body()) } else { Err(into_docker_error(res.into_body()).await?.into()) } } /// Create an image by pulling it from registry /// /// # API /// /images/create?fromImage={image}&tag={tag} /// /// # NOTE /// When control returns from this function, creating job may not have been completed. /// For waiting the completion of the job, consuming response like /// `create_image("hello-world", "linux").map(|r| r.for_each(|_| ()));`. /// /// # TODO /// - Typing result iterator like image::ImageStatus. /// - Generalize input parameters pub async fn create_image( &self, image: &str, tag: &str, ) -> Result>, DwError> { let mut param = url::form_urlencoded::Serializer::new(String::new()); param.append_pair("fromImage", image); param.append_pair("tag", tag); let mut headers = self.headers().clone(); if let Some(ref credential) = self.credential.lock().unwrap().as_ref() { headers.insert( "X-Registry-Auth", general_purpose::STANDARD .encode(serde_json::to_string(credential).unwrap().as_bytes()) .parse() .unwrap(), ); } let res = self .http_client() .post_stream(&headers, &format!("/images/create?{}", param.finish()), "") .await?; if res.status().is_success() { into_jsonlines(res.into_body()) } else { Err(into_docker_error(res.into_body()).await?.into()) } } /// Inspect an image /// /// # API /// /images/{name}/json /// pub async fn inspect_image(&self, name: &str) -> Result { let res = self .http_client() .get(self.headers(), &format!("/images/{name}/json")) .await?; api_result(res).map_err(Into::into) } /// Push an image /// /// # NOTE /// For pushing an image to non default registry, add registry id to prefix of the image name like `/` . /// But the name of the local cache image is `:` . /// /// # API /// /images/{name}/push /// pub async fn push_image(&self, name: &str, tag: &str) -> Result<(), DwError> { let mut param = url::form_urlencoded::Serializer::new(String::new()); param.append_pair("tag", tag); let mut headers = self.headers().clone(); if let Some(ref credential) = self.credential.lock().unwrap().as_ref() { headers.insert( "X-Registry-Auth", general_purpose::STANDARD .encode(serde_json::to_string(credential).unwrap().as_bytes()) .parse() .unwrap(), ); } let res = self .http_client() .post( &headers, &format!("/images/{}/push?{}", name, param.finish()), "", ) .await?; ignore_result(res).map_err(Into::into) } /// Remove an image /// /// # API /// /images/{name} /// pub async fn remove_image( &self, name: &str, force: Option, noprune: Option, ) -> Result, DwError> { let mut param = url::form_urlencoded::Serializer::new(String::new()); param.append_pair("force", &force.unwrap_or(false).to_string()); param.append_pair("noprune", &noprune.unwrap_or(false).to_string()); let res = self .http_client() .delete( self.headers(), &format!("/images/{}?{}", name, param.finish()), ) .await?; api_result(res).map_err(Into::into) } /// Delete unused images /// /// # API /// /images/prune pub async fn prune_image(&self, dangling: bool) -> Result { debug!("start pruning...dangling? {}", &dangling); let mut param = url::form_urlencoded::Serializer::new(String::new()); param.append_pair( "filters", &format!(r#"{{ "dangling": {{ "{dangling}": true }} }}"#), ); let res = self .http_client() .post( self.headers(), &format!("/images/prune?{}", param.finish()), "", ) .await?; api_result(res).map_err(Into::into) } /// History of an image /// /// # API /// /images/{name}/history /// pub async fn history_image(&self, name: &str) -> Result, DwError> { let res = self .http_client() .get(self.headers(), &format!("/images/{name}/history")) .await?; api_result(res) .map_err(Into::into) .map(|mut hs: Vec| { hs.iter_mut().for_each(|change| { if change.id.as_deref() == Some("") { change.id = None; } }); hs }) } /// List images /// /// # API /// /images/json pub async fn images(&self, all: bool) -> Result, DwError> { let res = self .http_client() .get(self.headers(), &format!("/images/json?a={}", all as u32)) .await?; api_result(res).map_err(Into::into) } /// Get a tarball containing all images and metadata for a repository /// /// # API /// /images/{name}/get pub async fn export_image( &self, name: &str, ) -> Result>, DwError> { let res = self .http_client() .get_stream(self.headers(), &format!("/images/{name}/get")) .await?; if res.status().is_success() { use futures::stream::StreamExt; use futures::stream::TryStreamExt; Ok(res.into_body().map_err(Into::into).boxed()) } else { Err(into_docker_error(res.into_body()).await?.into()) } } /// Import images /// /// # Summary /// Load a set of images and tags into a repository /// /// # API /// /images/load pub async fn load_image(&self, quiet: bool, path: &Path) -> Result { let mut headers = self.headers().clone(); headers.insert( http::header::CONTENT_TYPE, "application/x-tar".parse().unwrap(), ); let res = self .http_client() .post_file(&headers, &format!("/images/load?quiet={quiet}"), path) .await?; if !res.status().is_success() { return Err(serde_json::from_slice::(res.body())?.into()); } let path = path.to_owned(); tokio::task::spawn_blocking(move || { let file = std::fs::File::open(path)?; let mut ar = tar::Archive::new(file); for entry in ar.entries()?.filter_map(|e| e.ok()) { let path = entry.path()?; // looking for file name like XXXXXXXXXXXXXX.json if path.extension() == Some(std::ffi::OsStr::new("json")) && path != Path::new("manifest.json") { let stem = path.file_stem().unwrap(); // contains .json let id = stem.to_str().ok_or(DwError::Unknown { message: format!("convert to String: {stem:?}"), })?; return Ok(ImageId::new(id.to_string())); } } Err(DwError::Unknown { message: "no expected file: XXXXXX.json".to_owned(), }) }) .await .expect("join error") } /// Check auth configuration /// /// # API /// /auth /// /// # NOTE /// In some cases, docker daemon returns an empty token with `200 Ok`. /// The empty token could not be used for authenticating users. pub async fn auth( &self, username: &str, password: &str, email: &str, serveraddress: &str, ) -> Result { let req = UserPassword::new( username.to_string(), password.to_string(), email.to_string(), serveraddress.to_string(), ); let json_body = serde_json::to_string(&req)?; let mut headers = self.headers().clone(); headers.insert( http::header::CONTENT_TYPE, "application/json".parse().unwrap(), ); let res = self .http_client() .post(&headers, "/auth", &json_body) .await?; api_result(res).map_err(Into::into) } /// Get system information /// /// # API /// /info pub async fn system_info(&self) -> Result { let res = self.http_client().get(self.headers(), "/info").await?; api_result(res).map_err(Into::into) } /// Inspect about a container /// /// # API /// /containers/{id}/json pub async fn container_info(&self, container_id: &str) -> Result { let res = self .http_client() .get(self.headers(), &format!("/containers/{container_id}/json")) .await?; api_result(res).map_err(Into::into) } /// Get changes on a container's filesystem. /// /// (This is the same as `docker container diff` command.) /// /// # API /// /containers/{id}/changes pub async fn filesystem_changes( &self, container_id: &str, ) -> Result, DwError> { let res = self .http_client() .get( self.headers(), &format!("/containers/{container_id}/changes"), ) .await?; api_result(res).map_err(Into::into) } /// Export a container /// /// # Summary /// Returns a pointer to tar archive stream. /// /// # API /// /containers/{id}/export pub async fn export_container( &self, container_id: &str, ) -> Result>, DwError> { let res = self .http_client() .get_stream( self.headers(), &format!("/containers/{container_id}/export"), ) .await?; if res.status().is_success() { use futures::stream::StreamExt; use futures::stream::TryStreamExt; Ok(res.into_body().map_err(Into::into).boxed()) } else { Err(into_docker_error(res.into_body()).await?.into()) } } /// Test if the server is accessible /// /// # API /// /_ping pub async fn ping(&self) -> Result<(), DwError> { let res = self.http_client().get(self.headers(), "/_ping").await?; if res.status().is_success() { let buf = String::from_utf8(res.into_body().to_vec()).unwrap(); assert_eq!(&buf, "OK"); Ok(()) } else { Err(serde_json::from_slice::(res.body())?.into()) } } /// Get version and various information /// /// # API /// /version pub async fn version(&self) -> Result { let res = self.http_client().get(self.headers(), "/version").await?; api_result(res).map_err(Into::into) } /// Get monitor events /// /// # API /// /events pub async fn events( &self, since: Option, until: Option, filters: Option, ) -> Result>, DwError> { let param = { let mut param = url::form_urlencoded::Serializer::new(String::new()); if let Some(since) = since { param.append_pair("since", &since.to_string()); } if let Some(until) = until { param.append_pair("until", &until.to_string()); } if let Some(filters) = filters { param.append_pair("filters", &serde_json::to_string(&filters).unwrap()); } param.finish() }; let res = self .http_client() .get_stream(self.headers(), &format!("/events?{}", param)) .await?; into_jsonlines(res.into_body()) } /// List networks /// /// # API /// /networks pub async fn list_networks( &self, filters: ListNetworkFilters, ) -> Result, DwError> { let path = if filters.is_empty() { "/networks".to_string() } else { let mut param = url::form_urlencoded::Serializer::new(String::new()); param.append_pair("filters", &serde_json::to_string(&filters).unwrap()); debug!("filter: {}", serde_json::to_string(&filters).unwrap()); format!("/networks?{}", param.finish()) }; let res = self.http_client().get(self.headers(), &path).await?; api_result(res).map_err(Into::into) } /// Inspect a network /// /// # API /// /networks/{id} pub async fn inspect_network( &self, id: &str, verbose: Option, scope: Option<&str>, ) -> Result { let mut param = url::form_urlencoded::Serializer::new(String::new()); param.append_pair("verbose", &verbose.unwrap_or(false).to_string()); if let Some(scope) = scope { param.append_pair("scope", scope); } let res = self .http_client() .get( self.headers(), &format!("/networks/{}?{}", id, param.finish()), ) .await?; api_result(res).map_err(Into::into) } /// Remove a network /// /// # API /// /networks/{id} pub async fn remove_network(&self, id: &str) -> Result<(), DwError> { let res = self .http_client() .delete(self.headers(), &format!("/networks/{id}")) .await?; no_content(res).map_err(Into::into) } /// Create a network /// /// # API /// /networks/create pub async fn create_network( &self, option: &NetworkCreateOptions, ) -> Result { let json_body = serde_json::to_string(&option)?; let mut headers = self.headers().clone(); headers.insert( http::header::CONTENT_TYPE, "application/json".parse().unwrap(), ); let res = self .http_client() .post(&headers, "/networks/create", &json_body) .await?; api_result(res).map_err(Into::into) } /// Connect a container to a network /// /// # API /// /networks/{id}/connect pub async fn connect_network( &self, id: &str, option: &NetworkConnectOptions, ) -> Result<(), DwError> { let json_body = serde_json::to_string(&option)?; let mut headers = self.headers().clone(); headers.insert( http::header::CONTENT_TYPE, "application/json".parse().unwrap(), ); let res = self .http_client() .post(&headers, &format!("/networks/{id}/connect"), &json_body) .await?; ignore_result(res).map_err(Into::into) } /// Disconnect a container from a network /// /// # API /// /networks/{id}/disconnect pub async fn disconnect_network( &self, id: &str, option: &NetworkDisconnectOptions, ) -> Result<(), DwError> { let json_body = serde_json::to_string(&option)?; let mut headers = self.headers().clone(); headers.insert( http::header::CONTENT_TYPE, "application/json".parse().unwrap(), ); let res = self .http_client() .post(&headers, &format!("/networks/{id}/disconnect"), &json_body) .await?; ignore_result(res).map_err(Into::into) } /// Delete unused networks /// /// # API /// /networks/prune pub async fn prune_networks( &self, filters: PruneNetworkFilters, ) -> Result { let path = if filters.is_empty() { "/networks/prune".to_string() } else { let mut param = url::form_urlencoded::Serializer::new(String::new()); debug!("filters: {}", serde_json::to_string(&filters).unwrap()); param.append_pair("filters", &serde_json::to_string(&filters).unwrap()); format!("/networks/prune?{}", param.finish()) }; let res = self.http_client().post(self.headers(), &path, "").await?; api_result(res).map_err(Into::into) } } impl HaveHttpClient for Docker { type Client = HyperClient; fn http_client(&self) -> &Self::Client { &self.client } } #[cfg(all(test, unix))] mod tests { use super::*; use std::convert::From; use std::env; use std::iter::{self, Iterator}; use std::path::PathBuf; use chrono::Local; use futures::StreamExt; use http::request; use log::trace; use rand::Rng; async fn read_bytes_stream_to_end(src: BoxStream<'static, Result>) -> Vec { use futures::stream::TryStreamExt; let src = src.map_err(|err| std::io::Error::new(std::io::ErrorKind::Other, err)); let mut aread = tokio_util::io::StreamReader::new(src); let mut buf = vec![]; use tokio::io::AsyncReadExt; aread.read_to_end(&mut buf).await.unwrap(); buf } async fn read_frame_all( mut src: BoxStream<'static, Result>, ) -> Result<(Vec, Vec, Vec), DwError> { let mut stdout_buf = vec![]; let mut stdin_buf = vec![]; let mut stderr_buf = vec![]; use futures::stream::StreamExt; while let Some(mut stdio) = src.next().await.transpose()? { match stdio.type_ { ContainerStdioType::Stdin => { stdin_buf.append(&mut stdio.frame); } ContainerStdioType::Stdout => { stdout_buf.append(&mut stdio.frame); } ContainerStdioType::Stderr => { stderr_buf.append(&mut stdio.frame); } } } Ok((stdin_buf, stdout_buf, stderr_buf)) } async fn read_file(path: PathBuf) -> Vec { let mut file = tokio::fs::File::open(path).await.unwrap(); let mut buf = vec![]; use tokio::io::AsyncReadExt; file.read_to_end(&mut buf).await.unwrap(); buf } #[tokio::test] async fn test_ping() { let docker = Docker::connect_with_defaults().unwrap(); docker.ping().await.unwrap(); } #[tokio::test] async fn test_system_info() { let docker = Docker::connect_with_defaults().unwrap(); docker.system_info().await.unwrap(); } #[tokio::test] async fn test_version() { let docker = Docker::connect_with_defaults().unwrap(); docker.version().await.unwrap(); } #[tokio::test] async fn test_events() { let docker = Docker::connect_with_defaults().unwrap(); let _ = docker.events(None, None, None).await.unwrap(); } async fn double_stop_container(docker: &Docker, container: &str) { let info = docker.container_info(container).await.unwrap(); println!("container info: {info:?}"); docker.start_container(container).await.unwrap(); docker .stop_container(container, Duration::from_secs(10)) .await .unwrap(); docker .stop_container(container, Duration::from_secs(10)) .await .unwrap(); } async fn restart_container(docker: &Docker, container: &str) { docker.start_container(container).await.unwrap(); docker .stop_container(container, Duration::from_secs(10)) .await .unwrap(); docker .restart_container(container, Duration::from_secs(10)) .await .unwrap(); docker .stop_container(container, Duration::from_secs(10)) .await .unwrap(); } async fn stop_wait_container(docker: &Docker, container: &str) { docker.start_container(container).await.unwrap(); docker.wait_container(container).await.unwrap(); } async fn head_file_container(docker: &Docker, container: &str) { let res = docker .head_file(container, Path::new("/bin/ls")) .await .unwrap(); assert_eq!(res.name, "ls"); chrono::DateTime::parse_from_rfc3339(&res.mtime).unwrap(); } async fn stats_container(docker: &Docker, container: &str) { docker.start_container(container).await.unwrap(); // one shot let one_stats = docker .stats(container, Some(false), Some(true)) .await .unwrap(); use futures::StreamExt; let one_stats = one_stats.collect::>().await; assert_eq!(one_stats.len(), 1); // stream let thr_stats = docker .stats(container, Some(true), Some(false)) .await .unwrap() .take(3) .collect::>() .await; assert!(thr_stats.iter().all(Result::is_ok)); docker .stop_container(container, Duration::from_secs(10)) .await .unwrap(); } async fn wait_container(docker: &Docker, container: &str) { let status = docker.wait_container(container).await.unwrap(); assert_eq!(status, ExitStatus::new(0)); } async fn put_file_container(docker: &Docker, container: &str) { let temp_dir = env::temp_dir(); let test_file = temp_dir.join("test_file"); gen_rand_file(&test_file, 1024).await.unwrap(); // prepare test file tokio::task::spawn_blocking({ let test_file = test_file.clone(); move || { let file = std::fs::File::create(test_file.with_extension("tar")).unwrap(); let mut builder = tar::Builder::new(file); let mut file2 = std::fs::File::open(&test_file).unwrap(); builder .append_file(test_file.strip_prefix("/").unwrap(), &mut file2) .unwrap(); } }) .await .unwrap(); let res = docker.get_file(container, &test_file).await; assert!(matches!( res.map(|_| ()).unwrap_err(), DwError::Docker(_) // not found )); docker .put_file( container, &test_file.with_extension("tar"), Path::new("/"), true, ) .await .unwrap(); let src = docker.get_file(container, &test_file).await.unwrap(); let buf = read_bytes_stream_to_end(src).await; let temp_dir_put = temp_dir.join("put"); tokio::task::spawn_blocking(move || { let cur = std::io::Cursor::new(buf); tar::Archive::new(cur).unpack(&temp_dir_put).unwrap(); }) .await .unwrap(); docker.wait_container(container).await.unwrap(); let is_eq = equal_file( &test_file, &temp_dir.join("put").join(test_file.file_name().unwrap()), ) .await; assert!(is_eq); } async fn log_container(docker: &Docker, container: &str) { docker.start_container(container).await.unwrap(); let log_options = ContainerLogOptions { stdout: true, stderr: true, follow: true, ..ContainerLogOptions::default() }; let log = docker.log_container(container, &log_options).await.unwrap(); use futures::stream::StreamExt; let log_all = log.collect::>>().await; let log_all = log_all.into_iter().collect::, _>>().unwrap(); let log_all = log_all.join("\n"); println!("log_all\n{log_all}"); } async fn connect_container( docker: &Docker, container_name: &str, container_id: &str, network: &str, ) { // docker run --net=network container docker.start_container(container_id).await.unwrap(); let network_start = docker.inspect_network(network, None, None).await.unwrap(); assert_eq!(&network_start.Containers[container_id].Name, container_name); // docker network disconnect network container docker .disconnect_network( network, &NetworkDisconnectOptions { Container: container_id.to_owned(), Force: false, }, ) .await .unwrap(); let network_disconn = docker.inspect_network(network, None, None).await.unwrap(); assert!(network_disconn.Containers.is_empty()); // docker network connect network container // connecting with `docker network connect` command docker .connect_network( network, &NetworkConnectOptions { Container: container_id.to_owned(), EndpointConfig: EndpointConfig::default(), }, ) .await .unwrap(); let network_conn = docker.inspect_network(network, None, None).await.unwrap(); assert_eq!(&network_start.Id, &network_conn.Id); // .keys == ID of containers let is_eq = network_start .Containers .keys() .eq(network_conn.Containers.keys()); assert!(is_eq); docker .stop_container(container_id, Duration::new(5, 0)) .await .unwrap(); } async fn test_container(docker: &Docker, image: &str) { let mut next_id = { let mut id = 0; move || { let next = format!("test_container_{id}"); id += 1; next } }; println!("stop container"); { let create = ContainerCreateOptions::new(image); let container = docker .create_container(Some(&next_id()), &create) .await .unwrap(); double_stop_container(docker, &container.id).await; docker .remove_container(&container.id, None, None, None) .await .unwrap(); } println!("restart container"); { let create = ContainerCreateOptions::new(image); let container = docker .create_container(Some(&next_id()), &create) .await .unwrap(); restart_container(docker, &container.id).await; docker .remove_container(&container.id, None, None, None) .await .unwrap(); } println!("auto remove container"); { let mut create = ContainerCreateOptions::new(image); let mut host_config = ContainerHostConfig::new(); host_config.auto_remove(true); create.host_config(host_config); let container = docker .create_container(Some(&next_id()), &create) .await .unwrap(); stop_wait_container(docker, &container.id).await; // auto removed // 'no such container' or 'removel container in progress' let res = docker .remove_container(&container.id, None, None, None) .await; assert!(res.is_err()); } println!("head file container"); { let create = ContainerCreateOptions::new(image); let container = docker .create_container(Some(&next_id()), &create) .await .unwrap(); head_file_container(docker, &container.id).await; docker .remove_container(&container.id, None, None, None) .await .unwrap(); } println!("stats container"); { let create = ContainerCreateOptions::new(image); let container = docker .create_container(Some(&next_id()), &create) .await .unwrap(); stats_container(docker, &container.id).await; docker .remove_container(&container.id, None, None, None) .await .unwrap(); } println!("exit 0"); { let mut create = ContainerCreateOptions::new(image); create.cmd("ls".to_string()); let container = docker .create_container(Some(&next_id()), &create) .await .unwrap(); wait_container(docker, &container.id).await; docker .remove_container(&container.id, None, None, None) .await .unwrap(); } println!("put file"); { let create = ContainerCreateOptions::new(image); let container = docker .create_container(Some(&next_id()), &create) .await .unwrap(); put_file_container(docker, &container.id).await; docker .remove_container(&container.id, None, None, None) .await .unwrap(); } println!("logging container"); { let mut create = ContainerCreateOptions::new(image); create.entrypoint(vec!["cat".into()]); create.cmd("/etc/motd".to_string()); let container = docker .create_container(Some(&next_id()), &create) .await .unwrap(); log_container(docker, &container.id).await; docker .remove_container(&container.id, None, None, None) .await .unwrap(); } println!("connect networks"); { use std::collections::HashMap; let network_name = "dockworker_test_network_1"; let network = docker .create_network(&NetworkCreateOptions::new(network_name)) .await .unwrap(); let mut create = ContainerCreateOptions::new(image); create .attach_stdout(false) .attach_stderr(false) .tty(true) .open_stdin(true); let mut config = HashMap::new(); config.insert(network_name.to_owned(), EndpointConfig::default()); create.networking_config(NetworkingConfig { endpoints_config: config.into(), }); let container_name = next_id(); let container = docker .create_container(Some(&container_name), &create) .await .unwrap(); connect_container(docker, &container_name, &container.id, &network.Id).await; docker .remove_container(&container.id, None, None, None) .await .unwrap(); docker.remove_network(&network.Id).await.unwrap(); } } async fn test_image_api(docker: &Docker, name: &str, tag: &str) { let mut filter = ContainerFilters::new(); filter.name("test_container_"); let containers = docker .list_containers(Some(true), None, Some(true), filter.clone()) .await .unwrap(); assert!( containers.is_empty(), "remove containers 'test_container_*'" ); test_container(docker, &format!("{name}:{tag}")).await; let containers = docker .list_containers(Some(true), None, Some(true), filter) .await .unwrap(); assert!(containers.is_empty()); } async fn test_image(docker: &Docker, name: &str, tag: &str) { let mut src = docker.create_image(name, tag).await.unwrap(); use futures::stream::StreamExt; while let Some(st) = src.next().await.transpose().unwrap() { println!("{:?}", st); } let image = format!("{name}:{tag}"); let image_file = format!("dockworker_test_{name}_{tag}.tar"); { let res = docker.export_image(&image).await.unwrap(); let buf = read_bytes_stream_to_end(res).await; tokio::fs::write(&image_file, &buf).await.unwrap(); } docker.remove_image(&image, None, None).await.unwrap(); docker .load_image(false, Path::new(&image_file)) .await .unwrap(); tokio::fs::remove_file(&image_file).await.unwrap(); test_image_api(docker, name, tag).await; docker .remove_image(&format!("{name}:{tag}"), None, None) .await .unwrap(); } #[tokio::test] async fn test_api() { let docker = Docker::connect_with_defaults().unwrap(); let (name, tag) = ("alpine", "3.9"); test_image(&docker, name, tag).await; } #[cfg(feature = "experimental")] #[tokio::test] async fn test_container_checkpointing() { let docker = Docker::connect_with_defaults().unwrap(); let (name, tag) = ("alpine", "3.10"); with_image(&docker, name, tag, |name, tag| { let mut create = ContainerCreateOptions::new(&format!("{}:{}", name, tag)); create.host_config(ContainerHostConfig::new()); create.cmd("sleep".to_string()); create.cmd("10000".to_string()); let container = docker .create_container(Some("dockworker_checkpoint_test"), &create) .await .unwrap(); docker.start_container(&container.id).await.unwrap(); docker .checkpoint_container( &container.id, &CheckpointCreateOptions { checkpoint_id: "v1".to_string(), checkpoint_dir: None, exit: Some(true), }, ) .await .unwrap(); let checkpoints = docker .list_container_checkpoints(&container.id, None) .await .unwrap(); assert_eq!("v1", &checkpoints[0].Name); thread::sleep(Duration::from_secs(1)); docker .resume_container_from_checkpoint(&container.id, "v1", None) .await .unwrap(); docker .stop_container(&container.id, Duration::new(0, 0)) .await .unwrap(); docker .delete_checkpoint( &container.id, &CheckpointDeleteOptions { checkpoint_id: "v1".to_string(), checkpoint_dir: None, }, ) .await .unwrap(); docker .remove_container("dockworker_checkpoint_test", None, None, None) .await .unwrap(); }) } // generate a file on path which is constructed from size chars alphanum seq async fn gen_rand_file(path: &Path, size: usize) -> std::io::Result<()> { let mut rng = rand::thread_rng(); let mut file = tokio::fs::File::create(path).await?; let vec: String = iter::repeat(()) .map(|_| rng.sample(rand::distributions::Alphanumeric) as char) .take(size) .collect(); use tokio::io::AsyncWriteExt; file.write_all(vec.as_bytes()).await } async fn equal_file(patha: &Path, pathb: &Path) -> bool { let mut filea = tokio::fs::File::open(patha).await.unwrap(); let mut fileb = tokio::fs::File::open(pathb).await.unwrap(); let mut a = vec![]; let mut b = vec![]; use tokio::io::AsyncReadExt; filea.read_to_end(&mut a).await.unwrap(); fileb.read_to_end(&mut b).await.unwrap(); a == b } #[tokio::test] async fn test_networks() { let docker = Docker::connect_with_defaults().unwrap(); inspect_networks(&docker).await; prune_networks(&docker).await; } async fn inspect_networks(docker: &Docker) { for network in &docker .list_networks(ListNetworkFilters::default()) .await .unwrap() { let network = docker .inspect_network(&network.Id, Some(true), None) .await .unwrap(); println!("network: {network:?}"); } let create = NetworkCreateOptions::new("dockworker_test_network"); let res = docker.create_network(&create).await.unwrap(); let mut filter = ListNetworkFilters::default(); filter.id(res.Id.as_str().into()); let networks = docker.list_networks(filter.clone()).await.unwrap(); assert_eq!(networks.iter().filter(|n| n.Id == res.Id).count(), 1); docker.remove_network(&res.Id).await.unwrap(); let networks = docker.list_networks(filter).await.unwrap(); assert!(!networks.iter().any(|n| n.Id == res.Id)); } async fn prune_networks(docker: &Docker) { use crate::network::LabelFilter as F; use crate::network::NetworkCreateOptions as Net; use crate::network::PruneNetworkFilters as Prune; let mut create_nw_3 = Local::now(); for i in 1..=6 { docker .create_network( Net::new(&format!("nw_test_{i}")) .label("alias", &format!("my-test-network-{i}")) .label(&format!("test-network-{i}"), &i.to_string()) .label("not2", if i == 2 { "true" } else { "false" }), ) .await .unwrap(); tokio::time::sleep(Duration::from_secs(1)).await; // drift timestamp in sec if i == 3 { create_nw_3 = Local::now(); } } println!("filter network by label"); { let mut filter = Prune::default(); filter.label(F::with(&[("test-network-1", None)])); let res = docker.prune_networks(filter).await.unwrap(); assert_eq!(&res.networks_deleted, &["nw_test_1".to_owned()]); } println!("filter network by negated label"); { let mut filter = Prune::default(); filter.label_not(F::with(&[("not2", Some("false"))])); let res = docker.prune_networks(filter).await.unwrap(); assert_eq!(&res.networks_deleted, &["nw_test_2".to_owned()]); } println!("filter network by timestamp"); { let mut filter = Prune::default(); filter.until(vec![create_nw_3.timestamp()]); let res = docker.prune_networks(filter).await.unwrap(); assert_eq!(res.networks_deleted, &["nw_test_3".to_owned()]); } println!("filter network by label"); { let mut filter = Prune::default(); filter.label(F::with(&[("test-network-4", Some("4"))])); let res = docker.prune_networks(filter).await.unwrap(); assert_eq!(&res.networks_deleted, &["nw_test_4".to_owned()]); } println!("filter network by negated label"); { let mut filter = Prune::default(); filter.label_not(F::with(&[("alias", Some("my-test-network-6"))])); let res = docker.prune_networks(filter).await.unwrap(); assert_eq!(&res.networks_deleted, &["nw_test_5".to_owned()]); } println!("prune network"); { let res = docker.prune_networks(Prune::default()).await.unwrap(); assert_eq!(&res.networks_deleted, &["nw_test_6".to_owned()]); } } /// This is executed after `docker-compose build iostream` #[tokio::test] #[ignore] async fn attach_container() { use crate::signal::*; let docker = Docker::connect_with_defaults().unwrap(); // expected files let root = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("docker/attach"); let exps: &[&str; 2] = &["./sample/apache-2.0.txt", "./sample/bsd4.txt"]; let image_name = "test-iostream:latest"; let host_config = ContainerHostConfig::new(); //host_config.auto_remove(true); let mut create = ContainerCreateOptions::new(image_name); create .cmd(exps[0].to_owned()) .cmd(exps[1].to_owned()) .host_config(host_config) .env("WAIT_BEFORE_CONTINUING=YES".to_string()); let container = docker .create_container(Some("attach_container_test"), &create) .await .unwrap(); docker.start_container(&container.id).await.unwrap(); let res = docker .attach_container(&container.id, None, true, true, false, true, true) .await .unwrap(); let kill = async { // wait a moment tokio::time::sleep(std::time::Duration::from_secs(3)).await; // We've successfully attached, tell the container // to continue printing to stdout and stderr docker .kill_container(&container.id, Signal::from(SIGUSR1)) .await .unwrap(); }; let (ret, _) = futures::future::join(read_frame_all(res), kill).await; let (_stdin_buf, stdout_buf, stderr_buf) = ret.unwrap(); // expected files let exp_stdout_buf = read_file(root.join(exps[0])).await; let exp_stderr_buf = read_file(root.join(exps[1])).await; assert_eq!(exp_stdout_buf, stdout_buf); assert_eq!(exp_stderr_buf, stderr_buf); docker.wait_container(&container.id).await.unwrap(); docker .remove_container(&container.id, None, None, None) .await .unwrap(); } /// This is executed after `docker-compose build iostream` #[tokio::test] #[ignore] async fn exec_container() { let docker = Docker::connect_with_defaults().unwrap(); // expected files let root = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("docker/attach"); let exps: &[&str; 2] = &["./sample/apache-2.0.txt", "./sample/bsd4.txt"]; let image_name = "test-iostream:latest"; let host_config = ContainerHostConfig::new(); let mut create = ContainerCreateOptions::new(image_name); create .entrypoint(vec!["sleep".to_owned()]) .cmd("10".to_owned()) .host_config(host_config); let container = docker .create_container(Some("exec_container_test"), &create) .await .unwrap(); docker.start_container(&container.id).await.unwrap(); let mut exec_config = CreateExecOptions::new(); exec_config .cmd("./entrypoint.sh".to_owned()) .cmd(exps[0].to_owned()) .cmd(exps[1].to_owned()); let exec_instance = docker .exec_container(&container.id, &exec_config) .await .unwrap(); let exec_start_config = StartExecOptions::new(); let res = docker .start_exec(&exec_instance.id, &exec_start_config) .await .unwrap(); let (_stdin_buf, stdout_buf, stderr_buf) = read_frame_all(res).await.unwrap(); // expected files let exp_stdout_buf = read_file(root.join(exps[0])).await; let exp_stderr_buf = read_file(root.join(exps[1])).await; assert_eq!(exp_stdout_buf, stdout_buf); assert_eq!(exp_stderr_buf, stderr_buf); let exec_inspect = docker.exec_inspect(&exec_instance.id).await.unwrap(); assert_eq!(exec_inspect.ExitCode, Some(0)); assert_eq!(exec_inspect.Running, false); docker.wait_container(&container.id).await.unwrap(); docker .remove_container(&container.id, None, None, None) .await .unwrap(); } /// This is executed after `docker-compose build signal` #[tokio::test] #[ignore] async fn signal_container() { use crate::signal::*; let docker = Docker::connect_with_defaults().unwrap(); let image_name = "test-signal:latest"; let host_config = ContainerHostConfig::new(); let mut create = ContainerCreateOptions::new(image_name); create.host_config(host_config); let container = docker .create_container(Some("signal_container_test"), &create) .await .unwrap(); docker.start_container(&container.id).await.unwrap(); let res = docker .attach_container(&container.id, None, true, true, false, true, true) .await .unwrap(); let signals = [SIGHUP, SIGINT, SIGUSR1, SIGUSR2, SIGTERM]; let signalstrs = vec![ "HUP".to_string(), "INT".to_string(), "USR1".to_string(), "USR2".to_string(), "TERM".to_string(), ]; let kill = async { // wait a moment tokio::time::sleep(std::time::Duration::from_secs(3)).await; for sig in signals { trace!("cause signal: {:?}", sig); docker .kill_container(&container.id, Signal::from(sig)) .await .unwrap(); } }; let (ret, _) = futures::future::join(read_frame_all(res), kill).await; let (_stdin_buf, stdout_buf, _stderr_buf) = ret.unwrap(); let stdout = std::io::Cursor::new(stdout_buf); let stdout_buffer = std::io::BufReader::new(stdout); use std::io::BufRead; let lines = stdout_buffer.lines().map(|line| line.unwrap()); assert!(lines.eq(signalstrs)); trace!("wait"); assert_eq!( docker.wait_container(&container.id).await.unwrap(), ExitStatus::new(15) ); trace!("remove container"); docker .remove_container(&container.id, None, None, None) .await .unwrap(); } } dockworker-0.5.1/src/errors.rs000064400000000000000000000032661046102023000144610ustar 00000000000000use crate::response; use std::env; use std::io; use thiserror::Error; /// Type of general docker error response #[derive(Debug, serde::Deserialize, Error)] #[error("{message}")] pub struct DockerError { pub message: String, } #[derive(Error, Debug)] pub enum Error { #[error("io error")] Io(#[from] io::Error), #[error("envvar error")] Envvar(#[from] env::VarError), #[error("hyper error")] Hyper(#[from] hyper::Error), #[error("json error")] Json(#[from] serde_json::Error), #[error("docker error")] Docker(#[from] DockerError), #[error("response error")] Response(#[from] response::Error), #[error("http error")] Http(#[from] http::Error), #[error("invalid uri")] InvalidUri { var: String, source: http::uri::InvalidUri, }, #[cfg(feature = "native-tls")] #[error("ssl error")] NativeTls(#[from] native_tls::Error), #[cfg(feature = "openssl")] #[error("ssl error")] OpenSsl(#[from] openssl::error::ErrorStack), #[cfg(feature = "rustls")] #[error("ssl error")] Rustls(#[from] rustls::Error), #[error("could not connect: {}", addr)] CouldNotConnect { addr: String, source: Box }, #[error("could not find DOCKER_CERT_PATH")] NoCertPath, #[error("parse error: {}", input)] ParseError { input: String, source: base64::DecodeError, }, #[error("ssl support was disabled at compile time")] SslDisabled, #[error("unsupported scheme: {}", host)] UnsupportedScheme { host: String }, #[error("poison error: {}", message)] Poison { message: String }, #[error("unknown error: {}", message)] Unknown { message: String }, } dockworker-0.5.1/src/event.rs000064400000000000000000000006411046102023000142600ustar 00000000000000use std::collections::HashMap; use serde::Deserialize; #[derive(Debug, Clone, Deserialize)] #[allow(non_snake_case)] pub struct EventActor { pub ID: String, pub Attributes: HashMap, } #[derive(Debug, Clone, Deserialize)] #[allow(non_snake_case)] pub struct EventResponse { pub Type: String, pub Action: String, pub Actor: EventActor, pub time: u64, pub timeNano: u64, } dockworker-0.5.1/src/filesystem.rs000064400000000000000000000010261046102023000153210ustar 00000000000000use serde::{Deserialize, Serialize}; /// response of /containers/{id}/changes #[derive(Debug, Serialize, Deserialize)] #[allow(non_snake_case)] pub struct FilesystemChange { pub Path: String, pub Kind: u8, } /// content of X-Docker-Container-Path-Stat header /// acquired from HEAD /containers/{id}/archive #[derive(Debug, Serialize, Deserialize)] #[allow(non_snake_case)] pub struct XDockerContainerPathStat { pub name: String, pub size: u64, pub mode: u64, pub mtime: String, pub linkTarget: String, } dockworker-0.5.1/src/fixtures/container_inspect.json000064400000000000000000000161661046102023000210550ustar 00000000000000{ "Id": "774758ca1db8d05bd848d2b3456c8253a417a0511329692869df1cbe82978d37", "Created": "2016-10-25T11:59:37.858589354Z", "Path": "rails", "Args": [ "server", "-b", "0.0.0.0" ], "State": { "Status": "running", "Running": true, "Paused": false, "Restarting": false, "OOMKilled": false, "Dead": false, "Pid": 13038, "ExitCode": 0, "Error": "", "StartedAt": "2016-10-25T11:59:38.261828009Z", "FinishedAt": "0001-01-01T00:00:00Z" }, "Image": "sha256:f5e9d349e7e5c0f6de798d732d83fa5e087695cd100149121f01c891e6167c13", "ResolvConfPath": "/var/lib/docker/containers/774758ca1db8d05bd848d2b3456c8253a417a0511329692869df1cbe82978d37/resolv.conf", "HostnamePath": "/var/lib/docker/containers/774758ca1db8d05bd848d2b3456c8253a417a0511329692869df1cbe82978d37/hostname", "HostsPath": "/var/lib/docker/containers/774758ca1db8d05bd848d2b3456c8253a417a0511329692869df1cbe82978d37/hosts", "LogPath": "/var/lib/docker/containers/774758ca1db8d05bd848d2b3456c8253a417a0511329692869df1cbe82978d37/774758ca1db8d05bd848d2b3456c8253a417a0511329692869df1cbe82978d37-json.log", "Name": "/railshello_web_1", "RestartCount": 0, "Driver": "aufs", "MountLabel": "", "ProcessLabel": "", "AppArmorProfile": "", "ExecIDs": null, "HostConfig": { "Binds": [], "ContainerIDFile": "", "LogConfig": { "Type": "json-file", "Config": {} }, "NetworkMode": "railshello_default", "PortBindings": { "3000/tcp": [ { "HostIp": "", "HostPort": "3000" } ] }, "RestartPolicy": { "Name": "", "MaximumRetryCount": 0 }, "AutoRemove": false, "VolumeDriver": "", "VolumesFrom": [], "CapAdd": null, "CapDrop": null, "Dns": null, "DnsOptions": null, "DnsSearch": null, "ExtraHosts": null, "GroupAdd": null, "IpcMode": "", "Cgroup": "", "Links": null, "OomScoreAdj": 0, "PidMode": "", "Privileged": false, "PublishAllPorts": false, "ReadonlyRootfs": false, "SecurityOpt": null, "UTSMode": "", "UsernsMode": "", "ShmSize": 67108864, "Runtime": "runc", "ConsoleSize": [ 0, 0 ], "Isolation": "", "CpuShares": 0, "Memory": 0, "CgroupParent": "", "BlkioWeight": 0, "BlkioWeightDevice": null, "BlkioDeviceReadBps": null, "BlkioDeviceWriteBps": null, "BlkioDeviceReadIOps": null, "BlkioDeviceWriteIOps": null, "CpuPeriod": 0, "CpuQuota": 0, "CpusetCpus": "", "CpusetMems": "", "Devices": null, "DiskQuota": 0, "KernelMemory": 0, "MemoryReservation": 0, "MemorySwap": 0, "MemorySwappiness": -1, "OomKillDisable": false, "PidsLimit": 0, "Ulimits": null, "CpuCount": 0, "CpuPercent": 0, "IOMaximumIOps": 0, "IOMaximumBandwidth": 0 }, "GraphDriver": { "Name": "aufs", "Data": null }, "Mounts": [], "Config": { "Hostname": "774758ca1db8", "Domainname": "", "User": "", "AttachStdin": false, "AttachStdout": false, "AttachStderr": false, "ExposedPorts": { "3000/tcp": {} }, "Tty": false, "OpenStdin": false, "StdinOnce": false, "Env": [ "RACK_ENV=development", "PROJECT_NAME=rails_hello", "GLOBAL_PASSWORD=magic", "SOME_PASSWORD=secret", "RAILS_ENV=development", "DATABASE_URL=postgres://postgres@db:5432/rails_hello_development", "PATH=/usr/local/bundle/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", "RUBY_MAJOR=2.3", "RUBY_VERSION=2.3.1", "RUBY_DOWNLOAD_SHA256=b87c738cb2032bf4920fef8e3864dc5cf8eae9d89d8d523ce0236945c5797dcd", "RUBYGEMS_VERSION=2.6.7", "BUNDLER_VERSION=1.13.4", "GEM_HOME=/usr/local/bundle", "BUNDLE_PATH=/usr/local/bundle", "BUNDLE_BIN=/usr/local/bundle/bin", "BUNDLE_SILENCE_ROOT_WARNING=1", "BUNDLE_APP_CONFIG=/usr/local/bundle" ], "Cmd": [ "rails", "server", "-b", "0.0.0.0" ], "Image": "faraday/rails_hello", "Volumes": { "/etc/grafana/provisioning": {}, "/var/lib/grafana/config.monitoring": {}, "/var/lib/grafana/dashboards": {} }, "WorkingDir": "/usr/src/app", "Entrypoint": null, "OnBuild": null, "Labels": { "com.docker.compose.config-hash": "ff040c76ba24b1bac8d89e95cfb5ba7e29bd19423ed548a1436ae3c94bc6381a", "com.docker.compose.container-number": "1", "com.docker.compose.oneoff": "False", "com.docker.compose.project": "railshello", "com.docker.compose.service": "web", "com.docker.compose.version": "1.8.1", "io.fdy.cage.lib.coffee_rails": "/usr/src/app/vendor/coffee-rails", "io.fdy.cage.pod": "frontend", "io.fdy.cage.shell": "bash", "io.fdy.cage.srcdir": "/usr/src/app", "io.fdy.cage.target": "development", "io.fdy.cage.test": "bundle exec rake" } }, "NetworkSettings": { "Bridge": "", "SandboxID": "ca243185e052f364f6f9e4141ee985397cda9c66a87258f8a8048a05452738cf", "HairpinMode": false, "LinkLocalIPv6Address": "", "LinkLocalIPv6PrefixLen": 0, "Ports": { "3000/tcp": [ { "HostIp": "0.0.0.0", "HostPort": "3000" } ] }, "SandboxKey": "/var/run/docker/netns/ca243185e052", "SecondaryIPAddresses": null, "SecondaryIPv6Addresses": null, "EndpointID": "", "Gateway": "", "GlobalIPv6Address": "", "GlobalIPv6PrefixLen": 0, "IPAddress": "", "IPPrefixLen": 0, "IPv6Gateway": "", "MacAddress": "", "Networks": { "railshello_default": { "IPAMConfig": null, "Links": null, "Aliases": [ "web", "774758ca1db8" ], "NetworkID": "4b237b1de0928a11bb399adaa249705b666bdc5dece3e9bdc260a630643bf945", "EndpointID": "7d5e1e9df4bdf400654b96afdd1d42040c150a4f5b414f084c8fd5c95a9a906e", "Gateway": "172.24.0.1", "IPAddress": "172.24.0.3", "IPPrefixLen": 16, "IPv6Gateway": "", "GlobalIPv6Address": "", "GlobalIPv6PrefixLen": 0, "MacAddress": "02:42:ac:18:00:03" } } } }dockworker-0.5.1/src/fixtures/container_inspect_health.json000064400000000000000000000213711046102023000223740ustar 00000000000000{ "Id": "774758ca1db8d05bd848d2b3456c8253a417a0511329692869df1cbe82978d37", "Created": "2016-10-25T11:59:37.858589354Z", "Path": "rails", "Args": [ "server", "-b", "0.0.0.0" ], "State": { "Status": "running", "Running": true, "Paused": false, "Restarting": false, "OOMKilled": false, "Dead": false, "Pid": 13038, "ExitCode": 0, "Error": "", "StartedAt": "2016-10-25T11:59:38.261828009Z", "FinishedAt": "0001-01-01T00:00:00Z", "Health": { "Status": "healthy", "FailingStreak": 0, "Log": [ { "Start": "2018-12-31T16:02:55.845185754+03:00", "End": "2018-12-31T16:02:56.295432308+03:00", "ExitCode": 0, "Output": " % Total % Received % Xferd Average Speed Time Time Time Current\n Dload Upload Total Spent Left Speed\n\r 0 0 0 0 0 0 0 0 --:--:-- --:--:-- --:--:-- 0OK\r100 2 100 2 0 0 45 0 --:--:-- --:--:-- --:--:-- 46\n" }, { "Start": "2018-12-31T16:03:26.295564382+03:00", "End": "2018-12-31T16:03:26.349103623+03:00", "ExitCode": 0, "Output": " % Total % Received % Xferd Average Speed Time Time Time Current\n Dload Upload Total Spent Left Speed\n\r 0 0 0 0 0 0 0 0 --:--:-- --:--:-- --:--:-- 0\r100 2 100 2 0 0 365 0 --:--:-- --:--:-- --:--:-- 400\nOK" }, { "Start": "2018-12-31T16:03:56.349227982+03:00", "End": "2018-12-31T16:03:56.402644196+03:00", "ExitCode": 0, "Output": " % Total % Received % Xferd Average Speed Time Time Time Current\n Dload Upload Total Spent Left Speed\n\r 0 0 0 0 0 0 0 0 --:--:-- --:--:-- --:--:-- 0\r100 2 100 2 0 0 350 0 --:--:-- --:--:-- --:--:-- 400\nOK" } ] } }, "Image": "sha256:f5e9d349e7e5c0f6de798d732d83fa5e087695cd100149121f01c891e6167c13", "ResolvConfPath": "/var/lib/docker/containers/774758ca1db8d05bd848d2b3456c8253a417a0511329692869df1cbe82978d37/resolv.conf", "HostnamePath": "/var/lib/docker/containers/774758ca1db8d05bd848d2b3456c8253a417a0511329692869df1cbe82978d37/hostname", "HostsPath": "/var/lib/docker/containers/774758ca1db8d05bd848d2b3456c8253a417a0511329692869df1cbe82978d37/hosts", "LogPath": "/var/lib/docker/containers/774758ca1db8d05bd848d2b3456c8253a417a0511329692869df1cbe82978d37/774758ca1db8d05bd848d2b3456c8253a417a0511329692869df1cbe82978d37-json.log", "Name": "/railshello_web_1", "RestartCount": 0, "Driver": "aufs", "MountLabel": "", "ProcessLabel": "", "AppArmorProfile": "", "ExecIDs": null, "HostConfig": { "Binds": [], "ContainerIDFile": "", "LogConfig": { "Type": "json-file", "Config": {} }, "NetworkMode": "railshello_default", "PortBindings": { "3000/tcp": [ { "HostIp": "", "HostPort": "3000" } ] }, "RestartPolicy": { "Name": "", "MaximumRetryCount": 0 }, "AutoRemove": false, "VolumeDriver": "", "VolumesFrom": [], "CapAdd": null, "CapDrop": null, "Dns": null, "DnsOptions": null, "DnsSearch": null, "ExtraHosts": null, "GroupAdd": null, "IpcMode": "", "Cgroup": "", "Links": null, "OomScoreAdj": 0, "PidMode": "", "Privileged": false, "PublishAllPorts": false, "ReadonlyRootfs": false, "SecurityOpt": null, "UTSMode": "", "UsernsMode": "", "ShmSize": 67108864, "Runtime": "runc", "ConsoleSize": [ 0, 0 ], "Isolation": "", "CpuShares": 0, "Memory": 0, "CgroupParent": "", "BlkioWeight": 0, "BlkioWeightDevice": null, "BlkioDeviceReadBps": null, "BlkioDeviceWriteBps": null, "BlkioDeviceReadIOps": null, "BlkioDeviceWriteIOps": null, "CpuPeriod": 0, "CpuQuota": 0, "CpusetCpus": "", "CpusetMems": "", "Devices": null, "DiskQuota": 0, "KernelMemory": 0, "MemoryReservation": 0, "MemorySwap": 0, "MemorySwappiness": -1, "OomKillDisable": false, "PidsLimit": 0, "Ulimits": null, "CpuCount": 0, "CpuPercent": 0, "IOMaximumIOps": 0, "IOMaximumBandwidth": 0 }, "GraphDriver": { "Name": "aufs", "Data": null }, "Mounts": [], "Config": { "Hostname": "774758ca1db8", "Domainname": "", "User": "", "AttachStdin": false, "AttachStdout": false, "AttachStderr": false, "ExposedPorts": { "3000/tcp": {} }, "Tty": false, "OpenStdin": false, "StdinOnce": false, "Env": [ "RACK_ENV=development", "PROJECT_NAME=rails_hello", "GLOBAL_PASSWORD=magic", "SOME_PASSWORD=secret", "RAILS_ENV=development", "DATABASE_URL=postgres://postgres@db:5432/rails_hello_development", "PATH=/usr/local/bundle/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", "RUBY_MAJOR=2.3", "RUBY_VERSION=2.3.1", "RUBY_DOWNLOAD_SHA256=b87c738cb2032bf4920fef8e3864dc5cf8eae9d89d8d523ce0236945c5797dcd", "RUBYGEMS_VERSION=2.6.7", "BUNDLER_VERSION=1.13.4", "GEM_HOME=/usr/local/bundle", "BUNDLE_PATH=/usr/local/bundle", "BUNDLE_BIN=/usr/local/bundle/bin", "BUNDLE_SILENCE_ROOT_WARNING=1", "BUNDLE_APP_CONFIG=/usr/local/bundle" ], "Cmd": [ "rails", "server", "-b", "0.0.0.0" ], "Image": "faraday/rails_hello", "Volumes": null, "WorkingDir": "/usr/src/app", "Entrypoint": null, "OnBuild": null, "Labels": { "com.docker.compose.config-hash": "ff040c76ba24b1bac8d89e95cfb5ba7e29bd19423ed548a1436ae3c94bc6381a", "com.docker.compose.container-number": "1", "com.docker.compose.oneoff": "False", "com.docker.compose.project": "railshello", "com.docker.compose.service": "web", "com.docker.compose.version": "1.8.1", "io.fdy.cage.lib.coffee_rails": "/usr/src/app/vendor/coffee-rails", "io.fdy.cage.pod": "frontend", "io.fdy.cage.shell": "bash", "io.fdy.cage.srcdir": "/usr/src/app", "io.fdy.cage.target": "development", "io.fdy.cage.test": "bundle exec rake" } }, "NetworkSettings": { "Bridge": "", "SandboxID": "ca243185e052f364f6f9e4141ee985397cda9c66a87258f8a8048a05452738cf", "HairpinMode": false, "LinkLocalIPv6Address": "", "LinkLocalIPv6PrefixLen": 0, "Ports": { "3000/tcp": [ { "HostIp": "0.0.0.0", "HostPort": "3000" } ] }, "SandboxKey": "/var/run/docker/netns/ca243185e052", "SecondaryIPAddresses": null, "SecondaryIPv6Addresses": null, "EndpointID": "", "Gateway": "", "GlobalIPv6Address": "", "GlobalIPv6PrefixLen": 0, "IPAddress": "", "IPPrefixLen": 0, "IPv6Gateway": "", "MacAddress": "", "Networks": { "railshello_default": { "IPAMConfig": null, "Links": null, "Aliases": [ "web", "774758ca1db8" ], "NetworkID": "4b237b1de0928a11bb399adaa249705b666bdc5dece3e9bdc260a630643bf945", "EndpointID": "7d5e1e9df4bdf400654b96afdd1d42040c150a4f5b414f084c8fd5c95a9a906e", "Gateway": "172.24.0.1", "IPAddress": "172.24.0.3", "IPPrefixLen": 16, "IPv6Gateway": "", "GlobalIPv6Address": "", "GlobalIPv6PrefixLen": 0, "MacAddress": "02:42:ac:18:00:03" } } } }dockworker-0.5.1/src/fixtures/containers_response.json000064400000000000000000000034211046102023000214170ustar 00000000000000[ { "Id": "ed3221f4adc05b9ecfbf56b1aa76d4e6e70d5b73b3876c322fc10d017c64ca86", "Names": [ "/rust" ], "Image": "ghmlee/rust:latest", "ImageID": "533da4fa223bfbca0f56f65724bb7a4aae7a1acd6afa2309f370463eaf9c34a4", "Command": "bash", "Created": 1439434052, "Ports": [ { "IP": "0.0.0.0", "PrivatePort": 8888, "PublicPort": 8888, "Type": "tcp" } ], "SizeRootFs": 253602755, "Labels": null, "State": "exited", "Status": "Exited (137) 12 hours ago", "HostConfig": { "NetworkMode": "default" }, "NetworkSettings": { "Networks": { "bridge": { "IPAMConfig": null, "Links": null, "Aliases": null, "NetworkID": "c033e08c176af51c8eca4aca77a0a6b3def00f181918ecd0836589d74e94973a", "EndpointID": "7b4f20e7a13f2ccbfc31f3252dc1ca3afb65b5eb2b7250fe93074c6e83671baf", "Gateway": "10.10.0.1", "IPAddress": "10.10.0.4", "IPPrefixLen": 24, "IPv6Gateway": "", "GlobalIPv6Address": "", "GlobalIPv6PrefixLen": 0, "MacAddress": "02:42:0a:0a:00:04", "DriverOpts": null }, "none": { "IPAMConfig": null, "Links": null, "Aliases": null, "NetworkID": "3d8e6b21bced2737e634f897a54b83973da92498fe0774aa5fb6d8217b2c9322", "EndpointID": "4cd498f3bc50a9c3e9ae606d94d447b121bb2719701410d5cc98f6a033349ec1", "Gateway": "", "IPAddress": "", "IPPrefixLen": 0, "IPv6Gateway": "", "GlobalIPv6Address": "", "GlobalIPv6PrefixLen": 0, "MacAddress": "", "DriverOpts": null } } }, "Mounts": [], "SizeRw": 10832473 } ] dockworker-0.5.1/src/fixtures/filesystem_changes.json000064400000000000000000000000561046102023000212110ustar 00000000000000[ { "Path": "/tmp", "Kind": 0 } ] dockworker-0.5.1/src/fixtures/image.json000064400000000000000000000037641046102023000164300ustar 00000000000000{ "Id": "sha256:301e280df919c411b7c2b049f938f3e26e4269a9be4a8ac3babce1ede930be0f", "RepoTags": [ "debian:wheezy-20190204-slim" ], "RepoDigests": [ "debian@sha256:8af4c5d36bf9e97bd9e9d32f4b23c30197269a8690d1aee6771beb7bdc744d5d" ], "Parent": "", "Comment": "", "Created": "2019-02-06T03:31:46.89466512Z", "Container": "bde93de20096d0e854d13ce9e3e8a506b3a2b7798c051bd1e2bebb644ad60b9a", "ContainerConfig": { "Hostname": "bde93de20096", "Domainname": "", "User": "", "AttachStdin": false, "AttachStdout": false, "AttachStderr": false, "Tty": false, "OpenStdin": false, "StdinOnce": false, "Env": [ "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" ], "Cmd": [ "/bin/sh", "-c", "#(nop) ", "CMD [\"bash\"]" ], "ArgsEscaped": true, "Image": "sha256:cf2bd8704a6c5c4fc4b7a9801c5dacac8ddd3fc699b6e02227119b168d8cf2a9", "Volumes": null, "WorkingDir": "", "Entrypoint": null, "OnBuild": null, "Labels": null }, "DockerVersion": "18.06.1-ce", "Author": "", "Config": { "Hostname": "", "Domainname": "", "User": "", "AttachStdin": false, "AttachStdout": false, "AttachStderr": false, "Tty": false, "OpenStdin": false, "StdinOnce": false, "Env": [ "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" ], "Cmd": [ "bash" ], "ArgsEscaped": true, "Image": "sha256:cf2bd8704a6c5c4fc4b7a9801c5dacac8ddd3fc699b6e02227119b168d8cf2a9", "Volumes": null, "WorkingDir": "", "Entrypoint": null, "OnBuild": null, "Labels": null }, "Architecture": "amd64", "Os": "linux", "Size": 46924746, "VirtualSize": 46924746, "GraphDriver": { "Data": null, "Name": "aufs" }, "RootFS": { "Type": "layers", "Layers": [ "sha256:745d171eb8c3d69f788da3a1b053056231ad140b80be71d6869229846a1f3a77" ] }, "Metadata": { "LastTagTime": "0001-01-01T00:00:00Z" } } dockworker-0.5.1/src/fixtures/image_history.json000064400000000000000000000006241046102023000202010ustar 00000000000000[ { "Comment": "", "Created": 1539614714, "CreatedBy": "/bin/sh -c apk add --update openssl", "Id": "1234", "Size": 4736047, "Tags": null }, { "Comment": "", "Created": 1536704390, "CreatedBy": "/bin/sh -c #(nop) ADD file:25c10b1d1b41d46a1827ad0b0d2389c24df6d31430005ff4e9a2d84ea23ebd42 in / ", "Id": "", "Size": 4413370, "Tags": null } ] dockworker-0.5.1/src/fixtures/image_list.json000064400000000000000000000012501046102023000174470ustar 00000000000000[ { "Created": 1428533761, "Id": "533da4fa223bfbca0f56f65724bb7a4aae7a1acd6afa2309f370463eaf9c34a4", "ParentId": "84ac0b87e42afe881d36f03dea817f46893f9443f9fc10b64ec279737384df12", "RepoTags": [ "ghmlee/rust:nightly" ], "Size": 0, "VirtualSize": 806688288 }, { "Created": 1371157430, "Id": "511136ea3c5a64f264b78b5433614aec563103b4d4702f3ba7d4d2698e22c158", "ParentId": "", "RepoTags": [], "Size": 0, "VirtualSize": 0 }, { "Created": 1371157430, "Id": "511136ea3c5a64f264b78b5433614aec563103b4d4702f3ba7d4d2698e22c158", "ParentId": "", "RepoTags": null, "Size": 0, "VirtualSize": 0 } ] dockworker-0.5.1/src/fixtures/list_networks.json000064400000000000000000000075701046102023000202540ustar 00000000000000[ { "Name": "none", "Id": "05f2d3a562ec74049f2764a840e17b891893675da6853755ed710bb08c8c70fb", "Created": "2018-02-06T00:08:35.592125758+09:00", "Scope": "local", "Driver": "null", "EnableIPv6": false, "IPAM": { "Driver": "default", "Options": null, "Config": [] }, "Internal": false, "Attachable": false, "Ingress": false, "ConfigFrom": { "Network": "" }, "ConfigOnly": false, "Containers": {}, "Options": {}, "Labels": {} }, { "Name": "bridge", "Id": "3a385229739add5014d3d74e37d5c87c79900215e44f698ed1a2399a33974b47", "Created": "2020-02-09T21:09:24.738911076+09:00", "Scope": "local", "Driver": "bridge", "EnableIPv6": false, "IPAM": { "Driver": "default", "Options": null, "Config": [ { "Subnet": "172.17.0.0/16", "Gateway": "172.17.0.1" } ] }, "Internal": false, "Attachable": false, "Ingress": false, "ConfigFrom": { "Network": "" }, "ConfigOnly": false, "Containers": {}, "Options": { "com.docker.network.bridge.default_bridge": "true", "com.docker.network.bridge.enable_icc": "true", "com.docker.network.bridge.enable_ip_masquerade": "true", "com.docker.network.bridge.host_binding_ipv4": "0.0.0.0", "com.docker.network.bridge.name": "docker0", "com.docker.network.driver.mtu": "1500" }, "Labels": {} }, { "Name": "host", "Id": "8aa84b124c69c7b92d8c49bcb93a964bac1b654c7ed92cd1c4a3e24444340179", "Created": "2018-02-06T00:08:35.613000321+09:00", "Scope": "local", "Driver": "host", "EnableIPv6": false, "IPAM": { "Driver": "default", "Options": null, "Config": [] }, "Internal": false, "Attachable": false, "Ingress": false, "ConfigFrom": { "Network": "" }, "ConfigOnly": false, "Containers": {}, "Options": {}, "Labels": {} }, { "Name": "test_default", "Id": "b5ce4e42975c1e19e6ac09002a865843690dc280773862fc642c7babe053f345", "Created": "2020-01-01T22:16:18.860734803+09:00", "Scope": "local", "Driver": "bridge", "EnableIPv6": false, "IPAM": { "Driver": "default", "Options": null, "Config": [ { "Subnet": "172.18.0.0/16", "Gateway": "172.18.0.1" } ] }, "Internal": false, "Attachable": true, "Ingress": false, "ConfigFrom": { "Network": "" }, "ConfigOnly": false, "Containers": {}, "Options": {}, "Labels": { "com.docker.compose.network": "default", "com.docker.compose.project": "test" } }, { "Name": "test_macvlan", "Id": "032c6988efef37feb7f7c807fc66e13708bec7325bff4eed90a38815c0d1501f", "Created": "2020-12-10T12:00:07.472567245-04:00", "Scope": "local", "Driver": "macvlan", "EnableIPv6": false, "IPAM": { "Driver": "default", "Options": {}, "Config": [ { "Subnet": "172.16.0.0/16", "IPRange": "172.16.64.0/24", "Gateway": "172.16.0.1", "AuxiliaryAddresses": { "host": "172.16.64.0", "test_device": "172.16.64.1" } } ] }, "Internal": false, "Attachable": false, "Ingress": false, "ConfigFrom": { "Network": "" }, "ConfigOnly": false, "Containers": { "042116c93520a9e1cd37999db08691a3ab343ac95947b3518d38293f026fc7cb": { "Name": "macvlan_container", "EndpointID": "8a54b40ee98d6918b26c5fdeecb3cc748f89782f0e7e3dc2f4b0ec990b5b0319", "MacAddress": "02:42:ac:1b:27:6f", "IPv4Address": "172.16.64.100/16", "IPv6Address": "" } }, "Options": { "parent": "eno1" }, "Labels": {} } ] dockworker-0.5.1/src/fixtures/processes.json000064400000000000000000000002051046102023000173370ustar 00000000000000{ "Processes": [ [ "4586", "999", "rust" ] ], "Titles": [ "PID", "USER", "COMMAND" ] } dockworker-0.5.1/src/fixtures/stats_stream.json000064400000000000000000000155221046102023000200520ustar 00000000000000{"read":"2020-12-21T04:27:30.929463029Z","preread":"0001-01-01T00:00:00Z","pids_stats":{"current":82},"blkio_stats":{"io_service_bytes_recursive":[],"io_serviced_recursive":[],"io_queue_recursive":[],"io_service_time_recursive":[],"io_wait_time_recursive":[],"io_merged_recursive":[],"io_time_recursive":[],"sectors_recursive":[]},"num_procs":0,"storage_stats":{},"cpu_stats":{"cpu_usage":{"total_usage":302863447,"percpu_usage":[35291545,72551001,146926706,48094195,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"usage_in_kernelmode":150000000,"usage_in_usermode":20000000},"system_cpu_usage":5742433100000000,"online_cpus":4,"throttling_data":{"periods":0,"throttled_periods":0,"throttled_time":0}},"precpu_stats":{"cpu_usage":{"total_usage":0,"usage_in_kernelmode":0,"usage_in_usermode":0},"throttling_data":{"periods":0,"throttled_periods":0,"throttled_time":0}},"memory_stats":{"usage":8179712,"max_usage":17543168,"stats":{"active_anon":2711552,"active_file":0,"cache":380928,"dirty":4096,"hierarchical_memory_limit":9223372036854771712,"hierarchical_memsw_limit":0,"inactive_anon":360448,"inactive_file":4096,"mapped_file":348160,"pgfault":2960,"pgmajfault":0,"pgpgin":4909,"pgpgout":4149,"rss":2732032,"rss_huge":0,"total_active_anon":2711552,"total_active_file":0,"total_cache":380928,"total_dirty":4096,"total_inactive_anon":360448,"total_inactive_file":4096,"total_mapped_file":348160,"total_pgfault":2960,"total_pgmajfault":0,"total_pgpgin":4909,"total_pgpgout":4149,"total_rss":2732032,"total_rss_huge":0,"total_unevictable":0,"total_writeback":0,"unevictable":0,"writeback":0},"limit":8340705280},"name":"/dockworker_test_read_stats_container","id":"621d5971f1e21dddbcff975a36a13c44e9f387897708e6831ff95b5cd06a2aae","networks":{"eth0":{"rx_bytes":266,"rx_packets":3,"rx_errors":0,"rx_dropped":0,"tx_bytes":0,"tx_packets":0,"tx_errors":0,"tx_dropped":0}}} {"read":"2020-12-21T04:27:31.954188852Z","preread":"2020-12-21T04:27:30.929463029Z","pids_stats":{"current":82},"blkio_stats":{"io_service_bytes_recursive":[],"io_serviced_recursive":[],"io_queue_recursive":[],"io_service_time_recursive":[],"io_wait_time_recursive":[],"io_merged_recursive":[],"io_time_recursive":[],"sectors_recursive":[]},"num_procs":0,"storage_stats":{},"cpu_stats":{"cpu_usage":{"total_usage":302899962,"percpu_usage":[35291545,72551001,146926706,48130710,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"usage_in_kernelmode":150000000,"usage_in_usermode":20000000},"system_cpu_usage":5742437130000000,"online_cpus":4,"throttling_data":{"periods":0,"throttled_periods":0,"throttled_time":0}},"precpu_stats":{"cpu_usage":{"total_usage":302863447,"percpu_usage":[35291545,72551001,146926706,48094195,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"usage_in_kernelmode":150000000,"usage_in_usermode":20000000},"system_cpu_usage":5742433100000000,"online_cpus":4,"throttling_data":{"periods":0,"throttled_periods":0,"throttled_time":0}},"memory_stats":{"usage":7950336,"max_usage":17543168,"stats":{"active_anon":2711552,"active_file":0,"cache":380928,"dirty":4096,"hierarchical_memory_limit":9223372036854771712,"hierarchical_memsw_limit":0,"inactive_anon":360448,"inactive_file":4096,"mapped_file":348160,"pgfault":2960,"pgmajfault":0,"pgpgin":4909,"pgpgout":4149,"rss":2732032,"rss_huge":0,"total_active_anon":2711552,"total_active_file":0,"total_cache":380928,"total_dirty":4096,"total_inactive_anon":360448,"total_inactive_file":4096,"total_mapped_file":348160,"total_pgfault":2960,"total_pgmajfault":0,"total_pgpgin":4909,"total_pgpgout":4149,"total_rss":2732032,"total_rss_huge":0,"total_unevictable":0,"total_writeback":0,"unevictable":0,"writeback":0},"limit":8340705280},"name":"/dockworker_test_read_stats_container","id":"621d5971f1e21dddbcff975a36a13c44e9f387897708e6831ff95b5cd06a2aae","networks":{"eth0":{"rx_bytes":1010,"rx_packets":11,"rx_errors":0,"rx_dropped":0,"tx_bytes":0,"tx_packets":0,"tx_errors":0,"tx_dropped":0}}} {"read":"2020-12-21T04:27:32.957532891Z","preread":"2020-12-21T04:27:31.954188852Z","pids_stats":{"current":82},"blkio_stats":{"io_service_bytes_recursive":[],"io_serviced_recursive":[],"io_queue_recursive":[],"io_service_time_recursive":[],"io_wait_time_recursive":[],"io_merged_recursive":[],"io_time_recursive":[],"sectors_recursive":[]},"num_procs":0,"storage_stats":{},"cpu_stats":{"cpu_usage":{"total_usage":302932506,"percpu_usage":[35291545,72551001,146926706,48163254,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"usage_in_kernelmode":150000000,"usage_in_usermode":20000000},"system_cpu_usage":5742441170000000,"online_cpus":4,"throttling_data":{"periods":0,"throttled_periods":0,"throttled_time":0}},"precpu_stats":{"cpu_usage":{"total_usage":302899962,"percpu_usage":[35291545,72551001,146926706,48130710,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"usage_in_kernelmode":150000000,"usage_in_usermode":20000000},"system_cpu_usage":5742437130000000,"online_cpus":4,"throttling_data":{"periods":0,"throttled_periods":0,"throttled_time":0}},"memory_stats":{"usage":7901184,"max_usage":17543168,"stats":{"active_anon":2711552,"active_file":0,"cache":380928,"dirty":4096,"hierarchical_memory_limit":9223372036854771712,"hierarchical_memsw_limit":0,"inactive_anon":360448,"inactive_file":4096,"mapped_file":348160,"pgfault":2960,"pgmajfault":0,"pgpgin":4909,"pgpgout":4149,"rss":2732032,"rss_huge":0,"total_active_anon":2711552,"total_active_file":0,"total_cache":380928,"total_dirty":4096,"total_inactive_anon":360448,"total_inactive_file":4096,"total_mapped_file":348160,"total_pgfault":2960,"total_pgmajfault":0,"total_pgpgin":4909,"total_pgpgout":4149,"total_rss":2732032,"total_rss_huge":0,"total_unevictable":0,"total_writeback":0,"unevictable":0,"writeback":0},"limit":8340705280},"name":"/dockworker_test_read_stats_container","id":"621d5971f1e21dddbcff975a36a13c44e9f387897708e6831ff95b5cd06a2aae","networks":{"eth0":{"rx_bytes":1010,"rx_packets":11,"rx_errors":0,"rx_dropped":0,"tx_bytes":0,"tx_packets":0,"tx_errors":0,"tx_dropped":0}}} dockworker-0.5.1/src/fixtures/stats_suspend.json000064400000000000000000000020531046102023000202330ustar 00000000000000{ "read": "0001-01-01T00:00:00Z", "preread": "0001-01-01T00:00:00Z", "pids_stats": {}, "blkio_stats": { "io_service_bytes_recursive": null, "io_serviced_recursive": null, "io_queue_recursive": null, "io_service_time_recursive": null, "io_wait_time_recursive": null, "io_merged_recursive": null, "io_time_recursive": null, "sectors_recursive": null }, "num_procs": 0, "storage_stats": {}, "cpu_stats": { "cpu_usage": { "total_usage": 0, "usage_in_kernelmode": 0, "usage_in_usermode": 0 }, "throttling_data": { "periods": 0, "throttled_periods": 0, "throttled_time": 0 } }, "precpu_stats": { "cpu_usage": { "total_usage": 0, "usage_in_kernelmode": 0, "usage_in_usermode": 0 }, "throttling_data": { "periods": 0, "throttled_periods": 0, "throttled_time": 0 } }, "memory_stats": {}, "name": "/dockworker_test_read_stats_container", "id": "7075568d51e91e359ddb2e176c5272111190c98fe4ba508557e5f7c9cf139b2e" } dockworker-0.5.1/src/fixtures/system_info.json000064400000000000000000000017731046102023000177030ustar 00000000000000{ "Containers": 6, "Debug": 0, "DockerRootDir": "/var/lib/docker", "Driver": "btrfs", "DriverStatus": [ [ "Build Version", "Btrfs v3.17.1" ], [ "Library Version", "101" ] ], "ExecutionDriver": "native-0.2", "ID": "WG63:3NIU:TSI2:FV7J:IL2O:YPXA:JR3F:XEKT:JZVR:JA6T:QMYE:B4SB", "IPv4Forwarding": 1, "Images": 190, "IndexServerAddress": "https://index.docker.io/v1/", "InitPath": "/usr/libexec/docker/dockerinit", "InitSha1": "30c93967bdc3634b6036e1a76fd547bbe171b264", "KernelVersion": "3.18.6", "Labels": null, "MemTotal": 16854257664, "MemoryLimit": 1, "NCPU": 4, "NEventsListener": 0, "NFd": 68, "NGoroutines": 95, "Name": "core", "OperatingSystem": "CoreOS 607.0.0", "RegistryConfig": { "IndexConfigs": { "docker.io": { "Mirrors": null, "Name": "docker.io", "Official": true, "Secure": true } }, "InsecureRegistryCIDRs": [ "127.0.0.0/8" ] }, "SwapLimit": 1 } dockworker-0.5.1/src/fixtures/version.json000064400000000000000000000003401046102023000170160ustar 00000000000000{ "Version": "1.8.1", "ApiVersion": "1.20", "GitCommit": "d12ea79", "GoVersion": "go1.4.2", "Os": "linux", "Arch": "amd64", "KernelVersion": "4.0.9-boot2docker", "BuildTime": "Thu Aug 13 02:49:29 UTC 2015" } dockworker-0.5.1/src/http_client.rs000064400000000000000000000027661046102023000154660ustar 00000000000000use http::{HeaderMap, Response}; use std::path::Path; /// A http client #[async_trait::async_trait] pub trait HttpClient { type Err: Send + 'static; async fn get(&self, headers: &HeaderMap, path: &str) -> Result>, Self::Err>; async fn get_stream( &self, headers: &HeaderMap, path: &str, ) -> Result, Self::Err>; async fn head(&self, headers: &HeaderMap, path: &str) -> Result; async fn post( &self, headers: &HeaderMap, path: &str, body: &str, ) -> Result>, Self::Err>; async fn post_stream( &self, headers: &HeaderMap, path: &str, body: &str, ) -> Result, Self::Err>; async fn post_file( &self, headers: &HeaderMap, path: &str, file: &Path, ) -> Result>, Self::Err>; async fn post_file_stream( &self, headers: &HeaderMap, path: &str, file: &Path, ) -> Result, Self::Err>; async fn delete(&self, headers: &HeaderMap, path: &str) -> Result>, Self::Err>; async fn put_file( &self, headers: &HeaderMap, path: &str, file: &Path, ) -> Result>, Self::Err>; } /// Access to inner HttpClient pub trait HaveHttpClient { type Client: HttpClient; fn http_client(&self) -> &Self::Client; } dockworker-0.5.1/src/hyper_client.rs000064400000000000000000000330621046102023000156270ustar 00000000000000use crate::errors::Error as DwError; use crate::http_client::HttpClient; use http::{HeaderMap, Request, Response}; use hyper::Uri; use std::path::Path; use std::str::FromStr; #[allow(clippy::enum_variant_names)] #[derive(Clone, Debug)] enum Client { HttpClient(hyper::Client), #[cfg(feature = "openssl")] HttpsClient(hyper::Client>), #[cfg(feature = "rustls")] HttpsClient(hyper::Client>), #[cfg(unix)] UnixClient(hyper::Client), } impl Client { fn request(&self, req: Request) -> hyper::client::ResponseFuture { match self { Client::HttpClient(http_client) => http_client.request(req), #[cfg(feature = "openssl")] Client::HttpsClient(https_client) => https_client.request(req), #[cfg(feature = "rustls")] Client::HttpsClient(https_client) => https_client.request(req), #[cfg(unix)] Client::UnixClient(unix_client) => unix_client.request(req), } } } /// Http client using hyper #[derive(Debug, Clone)] pub struct HyperClient { /// http client client: Client, /// base connection address base: Uri, } fn join_uri(uri: &Uri, path: &str) -> Result { let joined = format!("{uri}{path}"); Uri::from_str(&joined).map_err(|err| DwError::InvalidUri { var: joined, source: err, }) } fn request_builder( method: &http::Method, uri: &Uri, headers: &HeaderMap, ) -> http::request::Builder { let mut request = Request::builder().method(method).uri(uri); for (name, value) in headers.iter() { request = request.header(name, value); } request } async fn request_with_redirect + Sync + Send + 'static + Clone>( client: Client, method: http::Method, uri: Uri, headers: HeaderMap, body: Option, ) -> Result, DwError> { let request = request_builder(&method, &uri, &headers).body(if let Some(body) = body.clone() { body.into() } else { hyper::Body::empty() })?; let mut future = client.request(request); let mut max_redirects = 10; loop { let resp = future.await?; if max_redirects == 0 { return Ok(resp); } else { let mut request = request_builder(&method, &uri, &headers); let uri_parts = http::uri::Parts::from(uri.clone()); if !resp.status().is_redirection() || resp.headers().get("Location").is_none() { return Ok(resp); } else { let mut see_other = false; if resp.status() == hyper::StatusCode::SEE_OTHER { request = request.method(hyper::Method::GET); see_other = true; } let location = resp.headers().get("Location").unwrap(); let location = location.to_str().unwrap(); let location = Uri::from_str(location).unwrap(); let mut location_parts = http::uri::Parts::from(location); if location_parts.scheme.is_none() { location_parts.scheme = uri_parts.scheme; } if location_parts.authority.is_none() { location_parts.authority = uri_parts.authority; } let location = http::uri::Uri::from_parts(location_parts).unwrap(); request = request.uri(location.clone()); future = client.request(if see_other { request.body(hyper::Body::empty()).unwrap() } else if let Some(body) = body.clone() { request.body(body.into()).unwrap() } else { request.body(hyper::Body::empty()).unwrap() }); max_redirects -= 1; } } } } async fn fetch_body(resp: http::Response) -> Result>, DwError> { let (p, b) = resp.into_parts(); let b = hyper::body::to_bytes(b).await?.to_vec(); Ok(Response::from_parts(p, b)) } impl HyperClient { fn new(client: Client, base: Uri) -> Self { Self { client, base } } /// path to unix socket #[cfg(unix)] pub fn connect_with_unix(path: &str) -> Self { let url = hyperlocal::Uri::new(path, "").into(); // Prevent from using connection pooling. // See https://github.com/hyperium/hyper/issues/2312. let client: hyper::Client<_> = hyper::Client::builder() .pool_idle_timeout(std::time::Duration::from_millis(0)) .pool_max_idle_per_host(0) .build(hyperlocal::UnixConnector); Self::new(Client::UnixClient(client), url) } #[cfg(feature = "openssl")] pub fn connect_with_ssl( addr: &str, key: &Path, cert: &Path, ca: &Path, ) -> Result { let key_buf = std::fs::read(key)?; let cert_buf = std::fs::read(cert)?; let ca_buf = std::fs::read(ca)?; let pkey = openssl::pkey::PKey::from_rsa(openssl::rsa::Rsa::private_key_from_pem(&key_buf)?)?; let cert = openssl::x509::X509::from_pem(&cert_buf)?; let pkcs12 = openssl::pkcs12::Pkcs12::builder().build("", "", &pkey, &cert)?; let der = pkcs12.to_der()?; let id = native_tls::Identity::from_pkcs12(&der, "")?; let ca = native_tls::Certificate::from_pem(&ca_buf)?; let mut builder = native_tls::TlsConnector::builder(); builder.identity(id); builder.add_root_certificate(ca); // This ensures that using docker-machine-esque addresses work with Hyper. let addr_https = addr.to_string().replacen("tcp://", "https://", 1); let url = Uri::from_str(&addr_https).map_err(|err| DwError::InvalidUri { var: addr_https, source: err, })?; let mut http = hyper::client::HttpConnector::new(); http.enforce_http(false); let https = hyper_tls::HttpsConnector::from((http, builder.build()?.into())); let client = hyper::Client::builder().build::<_, hyper::Body>(https); Ok(Self::new(Client::HttpsClient(client), url)) } #[cfg(feature = "rustls")] pub fn connect_with_ssl( addr: &str, key: &Path, cert: &Path, ca: &Path, ) -> Result { use log::warn; use rustls::{Certificate, PrivateKey}; use rustls_pemfile::Item; use std::fs::File; use std::io::BufReader; let addr_https = addr.clone().replacen("tcp://", "https://", 1); let url = Uri::from_str(&addr_https).map_err(|err| DwError::InvalidUri { var: addr_https, source: err, })?; let mut key_buf = BufReader::new(File::open(key)?); let mut cert_buf = BufReader::new(File::open(cert)?); let mut ca_buf = BufReader::new(File::open(ca)?); let private_key = match rustls_pemfile::rsa_private_keys(&mut key_buf)? { keys if keys.is_empty() => return Err(rustls::Error::NoCertificatesPresented.into()), mut keys if keys.len() == 1 => PrivateKey(keys.remove(0)), mut keys => { // if keys.len() > 1 warn!("Private key file contains multiple keys. Using only first one."); PrivateKey(keys.remove(0)) } }; let certs = rustls_pemfile::read_all(&mut cert_buf)? .into_iter() .filter_map(|item| match item { Item::X509Certificate(c) => Some(Certificate(c)), _ => None, }) .collect(); let mut root_certs = rustls::RootCertStore::empty(); for c in rustls_pemfile::certs(&mut ca_buf)? { root_certs.add(&Certificate(c))?; } let config = rustls::ClientConfig::builder() .with_safe_default_cipher_suites() .with_safe_default_kx_groups() .with_safe_default_protocol_versions() .unwrap() .with_root_certificates(root_certs) .with_single_cert(certs, private_key) .expect("bad certificate/key"); let https = hyper_rustls::HttpsConnectorBuilder::new() .with_tls_config(config) .https_or_http() .enable_all_versions() .build(); let client = hyper::Client::builder().build::<_, hyper::Body>(https); Ok(Self::new(Client::HttpsClient(client), url)) } pub fn connect_with_http(addr: &str) -> Result { // This ensures that using docker-machine-esque addresses work with Hyper. let addr_https = addr.to_string().replace("tcp://", "http://"); let url = Uri::from_str(&addr_https).map_err(|err| DwError::InvalidUri { var: addr_https, source: err, })?; Ok(Self::new(Client::HttpClient(hyper::Client::new()), url)) } } #[async_trait::async_trait] impl HttpClient for HyperClient { type Err = DwError; async fn get(&self, headers: &HeaderMap, path: &str) -> Result>, Self::Err> { let url = join_uri(&self.base, path)?; let res = request_with_redirect::>( self.client.clone(), http::Method::GET, url, headers.clone(), None, ) .await?; let res = fetch_body(res).await?; Ok(res) } async fn get_stream( &self, headers: &HeaderMap, path: &str, ) -> Result, Self::Err> { let url = join_uri(&self.base, path)?; let res = request_with_redirect::>( self.client.clone(), http::Method::GET, url, headers.clone(), None, ) .await?; Ok(res) } async fn head(&self, headers: &HeaderMap, path: &str) -> Result { let url = join_uri(&self.base, path)?; let res = request_with_redirect::>( self.client.clone(), http::Method::HEAD, url, headers.clone(), None, ) .await?; Ok(res.headers().clone()) } async fn post( &self, headers: &HeaderMap, path: &str, body: &str, ) -> Result>, Self::Err> { let url = join_uri(&self.base, path)?; let res = request_with_redirect( self.client.clone(), http::Method::POST, url, headers.clone(), Some(body.to_string()), ) .await?; let res = fetch_body(res).await?; Ok(res) } async fn post_stream( &self, headers: &HeaderMap, path: &str, body: &str, ) -> Result, Self::Err> { let url = join_uri(&self.base, path)?; let res = request_with_redirect( self.client.clone(), http::Method::POST, url, headers.clone(), Some(body.to_string()), ) .await?; Ok(res) } async fn post_file( &self, headers: &HeaderMap, path: &str, file: &Path, ) -> Result>, Self::Err> { let mut content = tokio::fs::File::open(file).await?; let url = join_uri(&self.base, path)?; use tokio::io::AsyncReadExt; let mut buf = Vec::new(); content.read_to_end(&mut buf).await?; let res = request_with_redirect( self.client.clone(), http::Method::POST, url, headers.clone(), Some(buf), ) .await?; let res = fetch_body(res).await?; Ok(res) } async fn post_file_stream( &self, headers: &HeaderMap, path: &str, file: &Path, ) -> Result, Self::Err> { let mut content = tokio::fs::File::open(file).await?; let url = join_uri(&self.base, path)?; use tokio::io::AsyncReadExt; let mut buf = Vec::new(); content.read_to_end(&mut buf).await?; let res = request_with_redirect( self.client.clone(), http::Method::POST, url, headers.clone(), Some(buf), ) .await?; Ok(res) } async fn delete( &self, headers: &HeaderMap, path: &str, ) -> Result>, Self::Err> { let url = join_uri(&self.base, path)?; let res = request_with_redirect::>( self.client.clone(), http::Method::DELETE, url, headers.clone(), None, ) .await?; let res = fetch_body(res).await?; Ok(res) } async fn put_file( &self, headers: &HeaderMap, path: &str, file: &Path, ) -> Result>, Self::Err> { let mut content = tokio::fs::File::open(file).await?; let url = join_uri(&self.base, path)?; use tokio::io::AsyncReadExt; let mut buf = Vec::new(); content.read_to_end(&mut buf).await?; let res = request_with_redirect( self.client.clone(), http::Method::PUT, url, headers.clone(), Some(buf), ) .await?; let res = fetch_body(res).await?; Ok(res) } } dockworker-0.5.1/src/image.rs000064400000000000000000000075011046102023000142230ustar 00000000000000use crate::container::Config; use chrono::offset::FixedOffset; use chrono::DateTime; use serde::de::{DeserializeOwned, Deserializer}; use serde::{Deserialize, Serialize}; use std::collections::HashMap; use std::{fmt, result}; fn null_to_default<'de, D, T>(de: D) -> Result where D: Deserializer<'de>, T: DeserializeOwned + Default, { let actual: Option = Option::deserialize(de)?; Ok(actual.unwrap_or_default()) } #[derive(Debug, Clone, Serialize, Deserialize)] #[allow(non_snake_case)] pub struct SummaryImage { pub Id: String, pub ParentId: String, #[serde(deserialize_with = "null_to_default")] pub RepoTags: Vec, #[serde(deserialize_with = "null_to_default", default = "Vec::default")] pub RepoDigests: Vec, pub Created: u64, pub Size: i64, #[serde(default = "i64::default")] pub SharedSize: i64, #[deprecated] #[serde(skip_serializing_if = "Option::is_none")] pub VirtualSize: Option, #[serde(default = "i64::default")] pub Containers: i64, } /// Type of /images/{}/json api #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[allow(non_snake_case)] pub struct Image { pub Id: String, pub RepoTags: Vec, pub RepoDigests: Vec, pub Parent: String, pub Comment: String, #[serde(with = "format::datetime_rfc3339")] // https://github.com/moby/moby/blob/611b23c1a0e9a9f440165a331964923fd1116256/daemon/images/image_inspect.go#L72 pub Created: DateTime, #[deprecated] #[serde(skip_serializing_if = "Option::is_none")] pub Container: Option, #[deprecated] #[serde(skip_serializing_if = "Option::is_none")] pub ContainerConfig: Option, pub DockerVersion: String, pub Author: String, pub Config: Config, pub Architecture: String, pub Os: String, pub Size: i64, #[deprecated] #[serde(skip_serializing_if = "Option::is_none")] pub VirtualSize: Option, pub GraphDriver: GraphDriver, pub RootFS: RootFS, } #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[allow(non_snake_case)] pub struct GraphDriver { pub Name: String, #[serde(deserialize_with = "null_to_default")] pub Data: HashMap, } #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[allow(non_snake_case)] pub struct RootFS { pub Type: String, pub Layers: Vec, #[serde(default = "Default::default")] pub BaseLayer: String, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct ImageStatus { pub status: Option, pub error: Option, } #[derive(Debug, Clone, PartialEq, PartialOrd, Serialize, Deserialize)] pub struct ImageId { id: String, } impl From for ImageId { fn from(id: String) -> Self { Self { id } } } impl fmt::Display for ImageId { fn fmt(&self, f: &mut fmt::Formatter) -> result::Result<(), fmt::Error> { self.id.fmt(f) } } impl ImageId { pub fn new>(id: S) -> Self { Self { id: id.into() } } pub fn id(&self) -> &str { &self.id } } pub mod format { pub mod datetime_rfc3339 { use chrono::offset::FixedOffset; use chrono::DateTime; use serde::de::{self, Deserialize, Deserializer}; use serde::Serializer; pub fn serialize(dt: &DateTime, serializer: S) -> Result where S: Serializer, { let str = dt.to_rfc3339(); serializer.serialize_str(&str) } pub fn deserialize<'de, D>(de: D) -> Result, D::Error> where D: Deserializer<'de>, { let str = String::deserialize(de)?; DateTime::parse_from_rfc3339(&str).map_err(de::Error::custom) } } } dockworker-0.5.1/src/lib.rs000064400000000000000000000006031046102023000137030ustar 00000000000000//! Docker Engine API client pub mod checkpoint; pub mod container; pub mod credentials; mod docker; pub mod errors; pub mod event; pub mod filesystem; mod http_client; mod hyper_client; pub mod image; pub mod network; mod options; pub mod process; pub mod response; pub mod signal; pub mod stats; pub mod system; mod test; pub mod version; pub use docker::Docker; pub use options::*; dockworker-0.5.1/src/network.rs000064400000000000000000000354441046102023000146410ustar 00000000000000#![allow(clippy::new_without_default)] use log::warn; use serde::{Deserialize, Serialize}; use std::borrow::Cow; use std::collections::HashMap; use std::net::Ipv4Addr; #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[allow(non_snake_case)] pub struct Network { pub Name: String, pub Id: String, pub Created: String, pub Scope: String, pub Driver: String, pub EnableIPv6: bool, pub IPAM: IPAM, pub Internal: bool, pub Attachable: bool, pub Ingress: bool, /// Container name to NetworkContainer pub Containers: HashMap, pub Options: HashMap, pub Labels: HashMap, } #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[allow(non_snake_case)] pub struct IPAM { pub Driver: String, pub Config: Option>, #[serde(deserialize_with = "format::null_to_default")] pub Options: HashMap, } impl Default for IPAM { fn default() -> Self { IPAM { Driver: "default".to_string(), Config: None, Options: HashMap::new(), } } } #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)] #[allow(non_snake_case)] pub struct IPAMConfig { #[serde(skip_serializing_if = "Option::is_none")] pub Subnet: Option, #[serde(skip_serializing_if = "Option::is_none")] pub IPRange: Option, #[serde(skip_serializing_if = "Option::is_none")] pub Gateway: Option, #[serde( skip_serializing_if = "HashMap::is_empty", deserialize_with = "format::null_to_default", default )] /// This field is given by "macvlan" network. /// /// # Example /// When the docker command is executed like below: /// ```text /// docker network create -d macvlan .. --aux-address="my-router=172.16.86.1" .. /// ``` /// The value will be equals to `HashMap::from([("my-router", "172.16.86.5")])`. pub AuxiliaryAddresses: HashMap, } #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] #[allow(non_snake_case)] pub struct NetworkContainer { /// Container name /// e.g. gifted_turing pub Name: String, pub EndpointID: String, pub MacAddress: String, pub IPv4Address: String, pub IPv6Address: String, } #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Deserialize, Serialize, Default)] pub struct ListNetworkFilters { #[serde(skip_serializing_if = "Vec::is_empty")] pub driver: Vec, #[serde(skip_serializing_if = "Vec::is_empty")] pub id: Vec, #[serde(skip_serializing_if = "Vec::is_empty")] pub label: Vec, #[serde(skip_serializing_if = "Vec::is_empty")] pub name: Vec, #[serde(skip_serializing_if = "Vec::is_empty")] pub scope: Vec, #[serde(skip_serializing_if = "Vec::is_empty")] pub r#type: Vec, } impl ListNetworkFilters { pub fn is_empty(&self) -> bool { self.driver.is_empty() && self.id.is_empty() && self.label.is_empty() && self.name.is_empty() && self.scope.is_empty() && self.r#type.is_empty() } pub fn driver(&mut self, driver: Cow) -> &mut Self { self.driver.push(driver.into_owned()); self } pub fn id(&mut self, id: Cow) -> &mut Self { self.id.push(id.into_owned()); self } pub fn label(&mut self, label: Cow) -> &mut Self { self.label.push(label.into_owned()); self } pub fn name(&mut self, name: Cow) -> &mut Self { self.name.push(name.into_owned()); self } pub fn scope(&mut self, scope: NetworkScope) -> &mut Self { self.scope.push(scope); self } pub fn r#type(&mut self, r#type: NetworkType) -> &mut Self { self.r#type.push(r#type); self } } #[derive(Debug, Clone, PartialEq, Eq)] pub struct PruneNetworkFilters { pub until: Vec, pub label: LabelFilter, pub label_not: LabelFilter, } impl Default for PruneNetworkFilters { fn default() -> Self { Self { until: vec![], label: LabelFilter::new(), label_not: LabelFilter::new(), } } } impl PruneNetworkFilters { pub fn is_empty(&self) -> bool { self.until.is_empty() && self.label.is_empty() && self.label_not.is_empty() } pub fn until(&mut self, until: Vec) -> &mut Self { self.until = until; self } pub fn label(&mut self, label: LabelFilter) -> &mut Self { self.label = label; self } pub fn label_not(&mut self, label_not: LabelFilter) -> &mut Self { self.label_not = label_not; self } } #[derive(Debug, Clone, PartialEq, Eq)] pub struct LabelFilter(HashMap>); impl LabelFilter { pub fn new() -> Self { Self(HashMap::new()) } pub fn is_empty(&self) -> bool { self.0.is_empty() } pub fn with(filters: &[(&str, Option<&str>)]) -> Self { let mut map = HashMap::new(); for (k, v) in filters { map.insert((*k).to_owned(), (*v).map(ToOwned::to_owned)); } Self(map) } pub fn key(&mut self, key: &str) -> &mut Self { self.0.insert(key.to_owned(), None); self } pub fn key_value(&mut self, key: &str, value: &str) -> &mut Self { self.0.insert(key.to_owned(), Some(value.to_owned())); self } } #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Deserialize, Serialize)] #[serde(rename_all = "lowercase")] pub enum NetworkScope { Swarm, Global, Local, } #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Deserialize, Serialize)] #[serde(rename_all = "lowercase")] pub enum NetworkType { Custom, Builtin, } /// request body of /networks/create api #[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)] #[serde(rename_all = "PascalCase")] pub struct NetworkCreateOptions { pub name: String, pub check_duplicate: bool, pub driver: String, /// Restrict connections between containers to only those under the same network. /// Default `false` pub internal: bool, pub attachable: bool, pub ingress: bool, #[serde(rename = "IPAM")] pub ipam: IPAM, #[serde(rename = "EnableIPv6")] pub enable_ipv6: bool, pub options: HashMap, pub labels: HashMap, } /// Create network options /// /// To create a network equivalent to the default bridge network, /// set the options as follows: /// /// ``` /// # use crate::dockworker::network::*; /// # use std::net::Ipv4Addr; /// let network_name = "sample-network"; /// let mut opt = NetworkCreateOptions::new(network_name); /// opt.enable_icc() /// .enable_ip_masquerade() /// .host_binding_ipv4(Ipv4Addr::new(0, 0, 0, 0)) /// .bridge_name("docker0") /// .driver_mtu(1500); /// // let network = docker.create_network(&opt)?; /// ``` impl NetworkCreateOptions { /// equivalent to `docker network create ` pub fn new(name: &str) -> Self { Self { attachable: false, check_duplicate: true, driver: "bridge".to_owned(), enable_ipv6: false, ipam: IPAM::default(), ingress: false, internal: false, labels: HashMap::new(), name: name.to_owned(), options: HashMap::new(), } } fn force_bridge_driver(&mut self) { if &self.driver != "bridge" { warn!("network driver is {} (!= bridge)", self.driver); warn!("driver is enforced to bridge"); self.driver = "bridge".to_owned(); } } /// bridge name to be used when creating the Linux bridge pub fn bridge_name(&mut self, name: &str) -> &mut Self { self.force_bridge_driver(); self.options .insert("com.docker.network.bridge.name".to_owned(), name.to_owned()); self } /// equivalent to `--ip-masq` of dockerd flag pub fn enable_ip_masquerade(&mut self) -> &mut Self { self.force_bridge_driver(); self.options.insert( "com.docker.network.bridge.enable_ip_masquerade".to_owned(), "true".to_owned(), ); self } /// equivalent to `--icc` of dockerd flag pub fn enable_icc(&mut self) -> &mut Self { self.force_bridge_driver(); self.options.insert( "com.docker.network.bridge.enable_icc".to_owned(), "true".to_owned(), ); self } /// equivalent to `--ip` of dockerd flag pub fn host_binding_ipv4(&mut self, ipv4: Ipv4Addr) -> &mut Self { self.force_bridge_driver(); self.options.insert( "com.docker.network.bridge.host_binding_ipv4".to_owned(), ipv4.to_string(), ); self } /// equivalent to `--mtu` option pub fn driver_mtu(&mut self, mtu: u16) -> &mut Self { self.force_bridge_driver(); self.options .insert("com.docker.network.driver.mtu".to_owned(), mtu.to_string()); self } pub fn label(&mut self, key: &str, value: &str) -> &mut Self { self.labels.insert(key.to_owned(), value.to_owned()); self } } #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] #[allow(non_snake_case)] pub struct CreateNetworkResponse { pub Id: String, pub Warning: String, } #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] #[serde(rename_all = "PascalCase")] pub struct PruneNetworkResponse { #[serde( serialize_with = "format::vec_to_null", deserialize_with = "format::null_to_default" )] pub networks_deleted: Vec, } #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[allow(non_snake_case)] #[derive(Default)] pub struct EndpointConfig { pub IPAMConfig: Option, pub Links: Option>, pub Aliases: Option>, pub NetworkID: String, pub EndpointID: String, pub Gateway: String, pub IPAddress: String, pub IPPrefixLen: i64, pub IPv6Gateway: String, pub GlobalIPv6Address: String, pub GlobalIPv6PrefixLen: i64, pub MacAddress: String, #[serde( serialize_with = "format::hashmap_to_null", deserialize_with = "format::null_to_default", default )] pub DriverOpts: HashMap, } #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Default, Deserialize)] #[allow(non_snake_case)] #[serde(default)] pub struct EndpointIPAMConfig { pub IPv4Address: String, pub IPv6Address: String, pub LinkLocalIPs: Vec, } #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[allow(non_snake_case)] pub struct NetworkConnectOptions { /// The ID or name of the container to connect to the network pub Container: String, /// Configuration for a network endpoint pub EndpointConfig: EndpointConfig, } #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] #[allow(non_snake_case)] pub struct NetworkDisconnectOptions { /// The ID or name of the container to disconnect to the network pub Container: String, /// Force the container to disconnect from the network pub Force: bool, } mod format { use super::*; use serde::de::{DeserializeOwned, Deserializer}; use serde::{ser::*, Deserialize, Serialize, Serializer}; pub fn null_to_default<'de, D, T>(de: D) -> Result where D: Deserializer<'de>, T: DeserializeOwned + Default, { let actual: Option = Option::deserialize(de)?; Ok(actual.unwrap_or_default()) } pub fn vec_to_null(t: &Vec, se: S) -> Result where S: Serializer, T: Serialize, { if t.is_empty() { se.serialize_none() } else { t.serialize(se) } } pub fn hashmap_to_null(t: &HashMap, se: S) -> Result where S: Serializer, T: Serialize, { if t.is_empty() { se.serialize_none() } else { t.serialize(se) } } impl Serialize for PruneNetworkFilters { fn serialize(&self, serializer: S) -> Result where S: Serializer, { let count = [ self.until.is_empty(), self.label.is_empty(), self.label_not.is_empty(), ] .iter() .filter(|x| **x) .count(); let mut state = serializer.serialize_map(Some(count))?; if !self.until.is_empty() { state.serialize_entry("until", &UntilTimestamp(&self.until))?; } if !self.label.is_empty() { state.serialize_entry("label", &self.label)?; } if !self.label_not.is_empty() { state.serialize_entry("label!", &self.label_not)?; } state.end() } } impl Serialize for EndpointIPAMConfig { fn serialize(&self, serializer: S) -> Result where S: Serializer, { if self.IPv4Address.is_empty() && self.IPv6Address.is_empty() && self.LinkLocalIPs.is_empty() { let map = serializer.serialize_map(Some(0))?; map.end() } else { let mut map = serializer.serialize_map(Some(3))?; map.serialize_entry("IPv4Address", &self.IPv4Address)?; map.serialize_entry("IPv6Address", &self.IPv6Address)?; map.serialize_entry("LinkLocalIPs", &self.LinkLocalIPs)?; map.end() } } } #[derive(Debug, Clone)] struct UntilTimestamp<'a>(&'a Vec); impl<'a> Serialize for UntilTimestamp<'a> { fn serialize(&self, serializer: S) -> Result where S: Serializer, { let mut map = serializer.serialize_map(Some(self.0.len()))?; for tm in self.0 { map.serialize_entry(&tm.to_string(), &true)?; } map.end() } } impl Serialize for LabelFilter { fn serialize(&self, serializer: S) -> Result where S: Serializer, { let mut map = serializer.serialize_map(None)?; for (k, v) in &self.0 { let key = match v { Some(v) => format!("{k}={v}"), None => k.to_string(), }; map.serialize_entry(&key, &true)?; } map.end() } } } dockworker-0.5.1/src/options.rs000064400000000000000000001146021046102023000146350ustar 00000000000000//! Options which can be passed to various `Docker` commands. #![allow(clippy::new_without_default)] use crate::network; use serde::de::{DeserializeOwned, Deserializer}; use serde::{Deserialize, Serialize}; use std::collections::{HashMap, HashSet}; use std::path::PathBuf; use std::str::FromStr; use std::time::Duration; use url::{self, form_urlencoded}; fn null_to_default<'de, D, T>(de: D) -> Result where D: Deserializer<'de>, T: DeserializeOwned + Default, { let actual: Option = Option::deserialize(de)?; Ok(actual.unwrap_or_default()) } /// Options for `Docker::containers`. This uses a "builder" pattern, so /// most methods will consume the object and return a new one. #[derive(Debug, Clone, Default)] pub struct ContainerListOptions { all: bool, //before: Option, //filter: Filter, latest: bool, limit: Option, //since: Option, size: bool, } impl ContainerListOptions { /// Return all containers, including stopped ones. pub fn all(mut self) -> Self { self.all = true; self } /// Return just the most-recently-started container (even if it has /// stopped). pub fn latest(mut self) -> Self { self.latest = true; self } /// Limit the number of containers we return. pub fn limit(mut self, n: u64) -> Self { self.limit = Some(n); self } /// Calculate the total file sizes for our containers. **WARNING:** /// This is very expensive. pub fn size(mut self) -> Self { self.size = true; self } /// Convert to URL parameters. pub fn to_url_params(&self) -> String { let mut params = form_urlencoded::Serializer::new(String::new()); if self.all { params.append_pair("all", "1"); } if self.latest { params.append_pair("latest", "1"); } if let Some(limit) = self.limit { params.append_pair("limit", &limit.to_string()); } if self.size { params.append_pair("size", "1"); } params.finish() } } /// Restart policy of a container. #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] #[allow(non_snake_case)] pub struct RestartPolicy { /// Restart type /// This option can be "no", "always", "on-failure" or "unless-stopped" pub Name: String, /// Maximum retry count. This value is used only when "on-failure" mode pub MaximumRetryCount: u16, } impl Default for RestartPolicy { fn default() -> Self { Self::new("no".to_owned(), 0) } } impl RestartPolicy { pub fn new(name: String, maximum_retry_count: u16) -> Self { RestartPolicy { Name: name, MaximumRetryCount: maximum_retry_count, } } pub fn no() -> Self { Self::new("no".to_owned(), 0) } pub fn always() -> Self { Self::new("always".to_owned(), 0) } pub fn on_failure() -> Self { Self::new("on-failure".to_owned(), 10) } pub fn unless_stopped() -> Self { Self::new("unless-stopped".to_owned(), 0) } } #[cfg(test)] mod tests { use super::*; #[test] fn serde_logconfig() { let cfg = LogConfig::new(LogConfigType::JsonFile); let json = serde_json::to_string(&cfg).unwrap(); let json_cfg = serde_json::from_str(&json).unwrap(); assert_eq!(&cfg, &json_cfg); } #[test] fn serde_logconfig_with_opt() { let config = { let mut cfg = HashMap::new(); cfg.insert("tagA".to_string(), "valueA".to_string()); cfg.insert("tagB".to_string(), "valueB".to_string()); cfg }; let cfg = LogConfig { r#type: LogConfigType::JsonFile, config, }; let json = serde_json::to_string(&cfg).unwrap(); let json_cfg = serde_json::from_str(&json).unwrap(); assert_eq!(&cfg, &json_cfg); } #[test] fn deser_restart_policy() { let no = r#"{"MaximumRetryCount":0, "Name":"no"}"#; assert_eq!(RestartPolicy::default(), serde_json::from_str(no).unwrap()); assert_eq!(RestartPolicy::no(), serde_json::from_str(no).unwrap()); let always = r#"{"MaximumRetryCount":0, "Name":"always"}"#; assert_eq!( RestartPolicy::always(), serde_json::from_str(always).unwrap() ); let onfailure = r#"{"MaximumRetryCount":10, "Name":"on-failure"}"#; assert_eq!( RestartPolicy::on_failure(), serde_json::from_str(onfailure).unwrap() ); let unlessstopped = r#"{"MaximumRetryCount":0, "Name":"unless-stopped"}"#; assert_eq!( RestartPolicy::unless_stopped(), serde_json::from_str(unlessstopped).unwrap() ); } #[test] fn iso_restart_policy() { let no = RestartPolicy::default(); assert_eq!( serde_json::from_str::(&serde_json::to_string(&no).unwrap()).unwrap(), no ); let always = RestartPolicy::new("always".to_owned(), 0); assert_eq!( serde_json::from_str::(&serde_json::to_string(&always).unwrap()) .unwrap(), always ); let onfailure = RestartPolicy::new("on-failure".to_owned(), 10); assert_eq!( serde_json::from_str::(&serde_json::to_string(&onfailure).unwrap()) .unwrap(), onfailure ); let unlessstopped = RestartPolicy::new("unless-stopped".to_owned(), 0); assert_eq!( serde_json::from_str::(&serde_json::to_string(&unlessstopped).unwrap()) .unwrap(), unlessstopped ); } } #[derive(Debug, Clone, Default, Serialize, Deserialize)] #[allow(non_snake_case)] pub struct DeviceMapping { PathOnHost: PathBuf, PathInContainer: PathBuf, /// combination of r,w,m CgroupPermissions: String, } impl DeviceMapping { pub fn new( path_on_host: PathBuf, path_in_container: PathBuf, cgroup_permissions: String, ) -> Self { Self { PathOnHost: path_on_host, PathInContainer: path_in_container, CgroupPermissions: cgroup_permissions, } } } #[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "PascalCase")] pub struct ContainerHostConfig { binds: Option>, tmpfs: Option>, links: Option>, memory: Option, memory_swap: Option, memory_reservation: Option, kernel_memory: Option, cpu_percent: Option, cpu_shares: Option, cpu_period: Option, cpu_quota: Option, cpuset_cpus: Option, io_maximum_bandwidth: Option, io_maximum_ops: Option, blkio_weight: Option, memory_swappiness: Option, oom_kill_disable: Option, oom_score_adj: Option, pid_mode: Option, pids_limit: Option, port_bindings: Option, publish_all_ports: Option, privileged: Option, readonly_rootfs: Option, dns: Option>, dns_options: Option>, dns_search: Option>, auto_remove: Option, volumes_from: Option>, cap_add: Option>, cap_drop: Option>, group_add: Option>, restart_policy: Option, network_mode: Option, devices: Option>, sysctls: Option>, runtime: Option, log_config: Option, cgroup_parent: Option, volume_driver: Option, shm_size: Option, } impl ContainerHostConfig { pub fn new() -> Self { Self { ..Default::default() } } pub fn binds(&mut self, binds: Vec) -> &mut Self { self.binds = Some(binds); self } pub fn tmpfs(&mut self, tmpfs: HashMap) -> &mut Self { self.tmpfs = Some(tmpfs); self } pub fn links(&mut self, links: Vec) -> &mut Self { self.links = Some(links); self } pub fn memory(&mut self, memory: u64) -> &mut Self { self.memory = Some(memory); self } pub fn memory_swap(&mut self, memory_swap: u64) -> &mut Self { self.memory_swap = Some(memory_swap); self } pub fn memory_reservation(&mut self, memory_reservation: u64) -> &mut Self { self.memory_reservation = Some(memory_reservation); self } pub fn kernel_memory(&mut self, kernel_memory: u64) -> &mut Self { self.kernel_memory = Some(kernel_memory); self } pub fn cpu_percent(&mut self, cpu_percent: u64) -> &mut Self { self.cpu_percent = Some(cpu_percent); self } pub fn cpu_shares(&mut self, cpu_shares: u64) -> &mut Self { self.cpu_shares = Some(cpu_shares); self } pub fn cpu_period(&mut self, cpu_period: u64) -> &mut Self { self.cpu_period = Some(cpu_period); self } pub fn cpu_quota(&mut self, cpu_quota: u64) -> &mut Self { self.cpu_quota = Some(cpu_quota); self } pub fn cpuset_cpus(&mut self, cpuset_cpus: String) -> &mut Self { self.cpuset_cpus = Some(cpuset_cpus); self } pub fn io_maximum_bandwidth(&mut self, io_maximum_bandwidth: u64) -> &mut Self { self.io_maximum_bandwidth = Some(io_maximum_bandwidth); self } pub fn io_maximum_ops(&mut self, io_maximum_ops: u64) -> &mut Self { self.io_maximum_ops = Some(io_maximum_ops); self } pub fn blkio_weight(&mut self, blkio_weight: u64) -> &mut Self { self.blkio_weight = Some(blkio_weight); self } pub fn memory_swappiness(&mut self, memory_swappiness: i32) -> &mut Self { self.memory_swappiness = Some(memory_swappiness); self } pub fn oom_kill_disable(&mut self, oom_kill_disable: bool) -> &mut Self { self.oom_kill_disable = Some(oom_kill_disable); self } pub fn oom_score_adj(&mut self, oom_score_adj: u16) -> &mut Self { self.oom_score_adj = Some(oom_score_adj); self } pub fn pid_mode(&mut self, pid_mode: String) -> &mut Self { self.pid_mode = Some(pid_mode); self } pub fn pids_limit(&mut self, pids_limit: i16) -> &mut Self { self.pids_limit = Some(pids_limit); self } pub fn publish_all_ports(&mut self, publish_all_ports: bool) -> &mut Self { self.publish_all_ports = Some(publish_all_ports); self } pub fn privileged(&mut self, privileged: bool) -> &mut Self { self.privileged = Some(privileged); self } pub fn readonly_rootfs(&mut self, readonly_rootfs: bool) -> &mut Self { self.readonly_rootfs = Some(readonly_rootfs); self } pub fn dns(&mut self, dns: Vec) -> &mut Self { self.dns = Some(dns); self } pub fn dns_options(&mut self, dns_options: Vec) -> &mut Self { self.dns_options = Some(dns_options); self } pub fn dns_search(&mut self, dns_search: Vec) -> &mut Self { self.dns_search = Some(dns_search); self } pub fn auto_remove(&mut self, auto_remove: bool) -> &mut Self { self.auto_remove = Some(auto_remove); self } pub fn volumes_from(&mut self, volumes_from: Vec) -> &mut Self { self.volumes_from = Some(volumes_from); self } pub fn cap_add(&mut self, cap_add: Vec) -> &mut Self { self.cap_add = Some(cap_add); self } pub fn cap_drop(&mut self, cap_drop: Vec) -> &mut Self { self.cap_drop = Some(cap_drop); self } pub fn group_add(&mut self, group_add: Vec) -> &mut Self { self.group_add = Some(group_add); self } pub fn restart_policy(&mut self, restart_policy: RestartPolicy) -> &mut Self { self.restart_policy = Some(restart_policy); self } pub fn network_mode(&mut self, network_mode: String) -> &mut Self { self.network_mode = Some(network_mode); self } pub fn devices(&mut self, devices: Vec) -> &mut Self { self.devices = Some(devices); self } pub fn sysctls(&mut self, sysctls: HashMap) -> &mut Self { self.sysctls = Some(sysctls); self } pub fn runtime(&mut self, runtime: String) -> &mut Self { self.runtime = Some(runtime); self } pub fn log_config(&mut self, log_config: LogConfig) -> &mut Self { self.log_config = Some(log_config); self } pub fn cgroup_parent(&mut self, cgroup_parent: String) -> &mut Self { self.cgroup_parent = Some(cgroup_parent); self } pub fn volume_driver(&mut self, volume_driver: String) -> &mut Self { self.volume_driver = Some(volume_driver); self } pub fn shm_size(&mut self, shm_size: u64) -> &mut Self { self.shm_size = Some(shm_size); self } pub fn port_bindings(&mut self, port_bindings: PortBindings) -> &mut Self { self.port_bindings = Some(port_bindings); self } } #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[serde(rename_all = "PascalCase")] #[derive(Default)] pub struct LogConfig { pub r#type: LogConfigType, pub config: HashMap, } impl LogConfig { pub fn new(r#type: LogConfigType) -> Self { Self { r#type, config: HashMap::new(), } } } #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[serde(rename_all = "kebab-case")] #[derive(Default)] pub enum LogConfigType { JsonFile, Syslog, #[default] Journald, Gelf, Fluentd, Awslogs, Splunk, Etwlogs, None, } #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[serde(rename_all = "PascalCase")] pub struct NetworkingConfig { pub endpoints_config: EndpointsConfig, } /// network name to EndpointConfig #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct EndpointsConfig(HashMap); impl From> for EndpointsConfig { fn from(endpoints: HashMap) -> Self { EndpointsConfig(endpoints) } } #[derive(Debug, Clone)] pub struct ContainerLogOptions { pub stdout: bool, pub stderr: bool, pub since: Option, pub timestamps: Option, pub tail: Option, pub follow: bool, } impl ContainerLogOptions { pub(crate) fn to_url_params(&self) -> String { let mut param = url::form_urlencoded::Serializer::new(String::new()); param.append_pair("stdout", &self.stdout.to_string()); param.append_pair("stderr", &self.stderr.to_string()); param.append_pair("follow", &self.follow.to_string()); if let Some(since) = self.since { param.append_pair("since", &since.to_string()); } if let Some(timestamps) = self.timestamps { param.append_pair("timestamps", ×tamps.to_string()); } if let Some(tail) = self.tail { param.append_pair("tail", &tail.to_string()); } param.finish() } } impl Default for ContainerLogOptions { fn default() -> Self { ContainerLogOptions { stdout: true, stderr: true, follow: false, since: None, timestamps: None, tail: None, } } } #[derive(Debug, Clone, Deserialize, Serialize)] pub struct ContainerBuildOptions { /// Path within the build context to the Dockerfile. /// This is ignored if remote is specified and points to an external Dockerfile. pub dockerfile: String, /// A name and optional tag to apply to the image in the name:tag format. /// If you omit the tag the default latest value is assumed. You can provide several t parameters. pub t: Vec, /// Extra hosts to add to /etc/hosts pub extrahosts: Option, /// A Git repository URI or HTTP/HTTPS context URI pub remote: Option, /// Suppress verbose build output. pub q: bool, /// Do not use the cache when building the image. pub nocache: bool, /// JSON array of images used for build cache resolution. pub cachefrom: Option>, /// Attempt to pull the image even if an older image exists locally. pub pull: Option, /// Remove intermediate containers after a successful build. pub rm: bool, /// Always remove intermediate containers, even upon failure. pub forcerm: bool, /// Set memory limit for build. pub memory: Option, /// Total memory (memory + swap). Set as -1 to disable swap. pub memswap: Option, /// CPU shares (relative weight). pub cpushares: Option, /// CPUs in which to allow execution (e.g., 0-3, 0,1). pub cpusetcpus: Option, /// The length of a CPU period in microseconds. pub cpuperiod: Option, /// Microseconds of CPU time that the container can get in a CPU period. pub cpuquota: Option, /// JSON map of string pairs for build-time variables. /// This is not meant for passing secret values. pub buildargs: Option>, /// Size of /dev/shm in bytes. The size must be greater than 0. If omitted the system uses 64MB. pub shmsize: Option, /// Squash the resulting images layers into a single layer. (Experimental release only.) pub squash: Option, /// Arbitrary key/value labels to set on the image, as a JSON map of string pairs. pub labels: Option>, /// Sets the networking mode for the run commands during build. /// Supported standard values are: bridge, host, none, and container:. /// Any other value is taken as a custom network's name to which this container should connect to. pub networkmode: Option, /// Platform in the format os[/arch[/variant]] pub platform: String, } impl ContainerBuildOptions { /// Convert to URL parameters. pub fn to_url_params(&self) -> String { let mut params = form_urlencoded::Serializer::new(String::new()); params.append_pair("dockerfile", &self.dockerfile); for tag in &self.t { params.append_pair("t", tag); } if let Some(ref extrahosts) = self.extrahosts { params.append_pair("extrahosts", extrahosts); } if let Some(ref remote) = self.remote { params.append_pair("remote", remote); } if self.q { params.append_pair("q", "true"); } if self.nocache { params.append_pair("nocache", "true"); } if let Some(ref cachefrom) = self.cachefrom { params.append_pair("cachefrom", &serde_json::to_string(&cachefrom).unwrap()); } if let Some(ref pull) = self.pull { params.append_pair("pull", pull); } if self.rm { params.append_pair("rm", "true"); } if self.forcerm { params.append_pair("forcerm", "true"); } if let Some(ref memory) = self.memory { params.append_pair("memory", &memory.to_string()); } if let Some(ref memswap) = self.memswap { params.append_pair("memswap", &memswap.to_string()); } if let Some(ref cpushares) = self.cpushares { params.append_pair("cpushares", &cpushares.to_string()); } if let Some(ref cpusetcpus) = self.cpusetcpus { params.append_pair("cpusetcpus", cpusetcpus); } if let Some(ref cpuperiod) = self.cpuperiod { params.append_pair("cpuperiod", &cpuperiod.to_string()); } if let Some(ref cpuquota) = self.cpuquota { params.append_pair("cpuquota", &cpuquota.to_string()); } if let Some(ref buildargs) = self.buildargs { params.append_pair( "buildargs", &serde_json::to_string(&buildargs).expect("Json parsing of buildargs param"), ); } if let Some(ref shmsize) = self.shmsize { params.append_pair("shmsize", &shmsize.to_string()); } if let Some(ref squash) = self.squash { params.append_pair("squash", &squash.to_string()); } if let Some(ref labels) = self.labels { params.append_pair( "labels", &serde_json::to_string(&labels).expect("Json parsing of labels param"), ); } if let Some(ref networkmode) = self.networkmode { params.append_pair("networkmode", networkmode); } params.append_pair("platform", &self.platform); params.finish() } } impl Default for ContainerBuildOptions { fn default() -> Self { ContainerBuildOptions { dockerfile: String::from("Dockerfile"), t: Vec::new(), extrahosts: None, remote: None, q: false, nocache: false, cachefrom: None, pull: None, rm: true, forcerm: false, memory: None, memswap: None, cpushares: None, cpusetcpus: None, cpuperiod: None, cpuquota: None, buildargs: None, shmsize: None, squash: Some(false), labels: None, networkmode: None, platform: String::new(), } } } #[derive(Debug, Clone, Default)] pub struct ExposedPorts(pub Vec<(u16, String)>); impl serde::Serialize for ExposedPorts { fn serialize(&self, serializer: S) -> Result { let mut map = HashMap::new(); for (port, protocol) in &self.0 { map.insert( format!("{}/{}", port, protocol).clone(), serde_json::Value::Object(serde_json::Map::new()), ); } map.serialize(serializer) } } impl<'de> serde::Deserialize<'de> for ExposedPorts { fn deserialize>(deserializer: D) -> Result { let map = HashMap::::deserialize(deserializer)?; let keys = map .keys() .map(|k| { let mut parts = k.split('/'); let port = parts.next().unwrap().parse().unwrap(); let protocol = parts.next().unwrap().to_owned(); (port, protocol) }) .collect(); Ok(ExposedPorts(keys)) } } #[test] fn test_exposed_ports() { let ports = ExposedPorts(vec![ (80, "tcp".to_owned()), (443, "tcp".to_owned()), (8080, "tcp".to_owned()), (8443, "tcp".to_owned()), ]); let json = serde_json::to_string(&ports).unwrap(); // hashmapのkey順序は不定であるため,json_valueに変換してから比較が必要 let result_json = serde_json::Value::from_str(&json).unwrap(); let expected_json = serde_json::Value::from_str(r#"{"80/tcp":{},"443/tcp":{},"8080/tcp":{},"8443/tcp":{}}"#) .unwrap(); assert_eq!(result_json, expected_json); let ports: ExposedPorts = serde_json::from_str(&json).unwrap(); let result: HashSet<&(u16, String)> = HashSet::from_iter(ports.0.iter()); // hashmapのkey順序は不定であるため,hash_setに変換してから比較する assert_eq!( result, HashSet::from_iter( vec![ (80, "tcp".to_owned()), (443, "tcp".to_owned()), (8080, "tcp".to_owned()), (8443, "tcp".to_owned()) ] .iter() ) ); } #[derive(Debug, Clone, Default)] pub struct PortBindings(pub Vec<(u16, String, u16)>); impl serde::Serialize for PortBindings { fn serialize(&self, serializer: S) -> Result { let mut map = HashMap::new(); for (container_port, protocol, host_port) in &self.0 { map.insert( format!("{}/{}", container_port, protocol).clone(), vec![serde_json::json!({"HostPort": host_port.to_string()})], ); } map.serialize(serializer) } } impl<'de> serde::Deserialize<'de> for PortBindings { fn deserialize>(deserializer: D) -> Result { let map = HashMap::::deserialize(deserializer)?; let tuples = map .keys() .map(|k| { let mut parts = k.split('/'); let port = parts.next().unwrap().parse().unwrap(); let protocol = parts.next().unwrap().to_owned(); let host_port = map .get(k) .unwrap() .as_array() .unwrap() .first() .unwrap() .get("HostPort") .unwrap() .as_str() .unwrap() .parse() .unwrap(); (port, protocol, host_port) }) .collect(); Ok(PortBindings(tuples)) } } #[test] fn test_port_bindings() { let ports = PortBindings(vec![ (80, "tcp".to_owned(), 8080), (443, "tcp".to_owned(), 8000), ]); let json = serde_json::to_string(&ports).unwrap(); // hashmapのkey順序は不定であるため,json_valueに変換してから比較が必要 let result_json = serde_json::Value::from_str(&json).unwrap(); let expected_json = serde_json::Value::from_str( r#"{"80/tcp":[{"HostPort":"8080"}],"443/tcp":[{"HostPort":"8000"}]}"#, ) .unwrap(); assert_eq!(result_json, expected_json); let ports: PortBindings = serde_json::from_str(&json).unwrap(); let result: HashSet<&(u16, String, u16)> = HashSet::from_iter(ports.0.iter()); // hashmapのkey順序は不定であるため,hash_setに変換してから比較する assert_eq!( result, HashSet::from_iter( vec![(80, "tcp".to_owned(), 8080), (443, "tcp".to_owned(), 8000),].iter() ) ); } /// request body of /containers/create api #[derive(Debug, Clone, Deserialize, Serialize)] #[serde(rename_all = "PascalCase")] pub struct ContainerCreateOptions { hostname: String, domainname: String, user: String, attach_stdin: bool, attach_stdout: bool, attach_stderr: bool, // exposed_ports: HashMap, not sure the type that this would need to be tty: bool, open_stdin: bool, stdin_once: bool, env: Vec, #[serde(skip_serializing_if = "Vec::is_empty")] cmd: Vec, #[serde(skip_serializing_if = "Vec::is_empty")] entrypoint: Vec, image: String, labels: HashMap, // volumes: HashMap, not sure the type that this would need to be. // healthcheck: Not sure the type that this would be working_dir: PathBuf, network_disabled: bool, mac_address: String, on_build: Vec, stop_signal: String, #[serde(with = "format::duration::DurationDelegate")] stop_timeout: Duration, host_config: Option, networking_config: Option, exposed_ports: Option, } impl ContainerCreateOptions { pub fn new(image: &str) -> Self { Self { hostname: "".to_owned(), domainname: "".to_owned(), user: "".to_owned(), attach_stdin: false, attach_stdout: true, attach_stderr: true, tty: false, open_stdin: false, stdin_once: false, env: vec![], cmd: vec![], image: image.to_owned(), working_dir: PathBuf::new(), entrypoint: vec![], network_disabled: false, mac_address: "".to_owned(), on_build: vec![], labels: HashMap::new(), stop_signal: "SIGTERM".to_owned(), stop_timeout: Duration::from_secs(10), host_config: None, networking_config: None, exposed_ports: None, } } pub fn hostname(&mut self, hostname: String) -> &mut Self { self.hostname = hostname; self } pub fn domainname(&mut self, domainname: String) -> &mut Self { self.domainname = domainname; self } pub fn user(&mut self, user: String) -> &mut Self { self.user = user; self } pub fn attach_stdin(&mut self, attach_stdin: bool) -> &mut Self { self.attach_stdin = attach_stdin; self } pub fn attach_stdout(&mut self, attach_stdout: bool) -> &mut Self { self.attach_stdout = attach_stdout; self } pub fn attach_stderr(&mut self, attach_stderr: bool) -> &mut Self { self.attach_stderr = attach_stderr; self } pub fn tty(&mut self, tty: bool) -> &mut Self { self.tty = tty; self } pub fn open_stdin(&mut self, open_stdin: bool) -> &mut Self { self.open_stdin = open_stdin; self } pub fn stdin_once(&mut self, stdin_once: bool) -> &mut Self { self.stdin_once = stdin_once; self } /// push back an envvar entry pub fn env(&mut self, env: String) -> &mut Self { self.env.push(env); self } /// push back a cmd argment pub fn cmd(&mut self, cmd: String) -> &mut Self { self.cmd.push(cmd); self } /// update entrypoint pub fn entrypoint(&mut self, entrypoint: Vec) -> &mut Self { self.entrypoint = entrypoint; self } pub fn image(&mut self, image: String) -> &mut Self { self.image = image; self } /// add a label/value pair pub fn label(&mut self, key: String, value: String) -> &mut Self { self.labels.insert(key, value); self } pub fn working_dir(&mut self, working_dir: PathBuf) -> &mut Self { self.working_dir = working_dir; self } pub fn network_disabled(&mut self, network_disabled: bool) -> &mut Self { self.network_disabled = network_disabled; self } pub fn mac_address(&mut self, mac_address: String) -> &mut Self { self.mac_address = mac_address; self } pub fn on_build(&mut self, on_build: Vec) -> &mut Self { self.on_build = on_build; self } pub fn stop_signal(&mut self, stop_signal: String) -> &mut Self { self.stop_signal = stop_signal; self } pub fn stop_timeout(&mut self, stop_timeout: Duration) -> &mut Self { self.stop_timeout = stop_timeout; self } pub fn host_config(&mut self, host_config: ContainerHostConfig) -> &mut Self { self.host_config = Some(host_config); self } pub fn networking_config(&mut self, networking_config: NetworkingConfig) -> &mut Self { self.networking_config = Some(networking_config); self } pub fn exposed_ports(&mut self, exposed_ports: ExposedPorts) -> &mut Self { self.exposed_ports = Some(exposed_ports); self } } mod format { pub mod duration { use serde::{Deserialize, Serialize}; use std::time::Duration; #[derive(Serialize, Deserialize)] #[serde(remote = "Duration")] pub struct DurationDelegate(#[serde(getter = "Duration::as_secs")] u64); // Provide a conversion to construct the remote type. impl From for Duration { fn from(def: DurationDelegate) -> Duration { Duration::new(def.0, 0) } } } } #[derive(Debug, Clone, Deserialize)] #[serde(rename_all = "PascalCase")] pub struct CreateContainerResponse { pub id: String, pub warnings: Option>, } #[derive(Debug, Clone, Deserialize)] #[serde(rename_all = "PascalCase")] pub struct CreateExecResponse { pub id: String, } /// request body of /containers/Create an exec instance #[derive(Debug, Clone, Deserialize, Serialize)] #[serde(rename_all = "PascalCase")] pub struct CreateExecOptions { attach_stdin: bool, attach_stdout: bool, attach_stderr: bool, detach_keys: String, tty: bool, #[serde(skip_serializing_if = "Vec::is_empty")] env: Vec, #[serde(skip_serializing_if = "Vec::is_empty")] cmd: Vec, privileged: bool, user: String, working_dir: PathBuf, } impl CreateExecOptions { pub fn new() -> Self { Self { attach_stdin: false, attach_stdout: true, attach_stderr: true, detach_keys: "".to_owned(), tty: false, env: vec![], cmd: vec![], privileged: false, user: "".to_owned(), working_dir: PathBuf::new(), } } pub fn attach_stdin(&mut self, attach_stdin: bool) -> &mut Self { self.attach_stdin = attach_stdin; self } pub fn attach_stdout(&mut self, attach_stdout: bool) -> &mut Self { self.attach_stdout = attach_stdout; self } pub fn attach_stderr(&mut self, attach_stderr: bool) -> &mut Self { self.attach_stderr = attach_stderr; self } pub fn tty(&mut self, tty: bool) -> &mut Self { self.tty = tty; self } pub fn env(&mut self, env: String) -> &mut Self { self.env.push(env); self } /// push back a cmd argment pub fn cmd(&mut self, cmd: String) -> &mut Self { self.cmd.push(cmd); self } pub fn privileged(&mut self, privileged: bool) -> &mut Self { self.privileged = privileged; self } pub fn user(&mut self, user: String) -> &mut Self { self.user = user; self } pub fn working_dir(&mut self, working_dir: PathBuf) -> &mut Self { self.working_dir = working_dir; self } } /// request body of /exec/start an exec instance #[derive(Debug, Clone, Deserialize, Serialize)] #[serde(rename_all = "PascalCase")] pub struct StartExecOptions { detach: bool, tty: bool, } impl StartExecOptions { pub fn new() -> Self { Self { detach: false, tty: false, } } pub fn detach(&mut self, detach: bool) -> &mut Self { self.detach = detach; self } pub fn tty(&mut self, tty: bool) -> &mut Self { self.tty = tty; self } } /// Response of the removing image api #[derive(Debug, Clone, PartialEq, PartialOrd, Serialize, Deserialize)] pub enum RemovedImage { Untagged(String), Deleted(String), } /// Response of the prune image api #[derive(Debug, Clone, PartialEq, PartialOrd, Serialize, Deserialize)] #[allow(non_snake_case)] pub struct PrunedImages { #[serde(deserialize_with = "null_to_default")] ImagesDeleted: Vec, SpaceReclaimed: i64, } /// Response of the history image api #[derive(Debug, Clone, PartialEq, PartialOrd, Serialize, Deserialize)] #[serde(rename_all = "PascalCase")] pub struct ImageLayer { pub id: Option, pub created: i64, pub created_by: String, pub tags: Option>, pub size: u64, pub comment: String, } #[derive(Debug, PartialEq, PartialOrd, Serialize, Default)] pub struct EventFilters { #[serde(skip_serializing_if = "Vec::is_empty")] config: Vec, #[serde(skip_serializing_if = "Vec::is_empty")] container: Vec, #[serde(skip_serializing_if = "Vec::is_empty")] daemon: Vec, #[serde(skip_serializing_if = "Vec::is_empty")] event: Vec, #[serde(skip_serializing_if = "Vec::is_empty")] image: Vec, #[serde(skip_serializing_if = "Vec::is_empty")] label: Vec, #[serde(skip_serializing_if = "Vec::is_empty")] network: Vec, #[serde(skip_serializing_if = "Vec::is_empty")] node: Vec, #[serde(skip_serializing_if = "Vec::is_empty")] plugin: Vec, #[serde(skip_serializing_if = "Vec::is_empty")] scope: Vec, #[serde(skip_serializing_if = "Vec::is_empty")] secret: Vec, #[serde(skip_serializing_if = "Vec::is_empty")] service: Vec, #[serde(skip_serializing_if = "Vec::is_empty")] #[serde(rename = "type")] type_: Vec, #[serde(skip_serializing_if = "Vec::is_empty")] volume: Vec, } impl EventFilters { pub fn new() -> Self { Self::default() } pub fn config(&mut self, config: &str) -> &mut Self { self.config.push(config.to_owned()); self } pub fn container(&mut self, container: &str) -> &mut Self { self.container.push(container.to_owned()); self } pub fn daemon(&mut self, daemon: &str) -> &mut Self { self.daemon.push(daemon.to_owned()); self } pub fn event(&mut self, event: &str) -> &mut Self { self.event.push(event.to_owned()); self } pub fn image(&mut self, image: &str) -> &mut Self { self.image.push(image.to_owned()); self } pub fn label(&mut self, label: &str) -> &mut Self { self.label.push(label.to_owned()); self } pub fn network(&mut self, network: &str) -> &mut Self { self.network.push(network.to_owned()); self } pub fn node(&mut self, node: &str) -> &mut Self { self.node.push(node.to_owned()); self } pub fn plugin(&mut self, plugin: &str) -> &mut Self { self.plugin.push(plugin.to_owned()); self } pub fn scope(&mut self, scope: &str) -> &mut Self { self.scope.push(scope.to_owned()); self } pub fn secret(&mut self, secret: &str) -> &mut Self { self.secret.push(secret.to_owned()); self } pub fn service(&mut self, service: &str) -> &mut Self { self.service.push(service.to_owned()); self } pub fn type_(&mut self, type_: &str) -> &mut Self { self.type_.push(type_.to_owned()); self } pub fn volume(&mut self, volume: &str) -> &mut Self { self.volume.push(volume.to_owned()); self } } dockworker-0.5.1/src/process.rs000064400000000000000000000034061046102023000146170ustar 00000000000000use serde::{Deserialize, Serialize}; use std::fmt::Error; use std::fmt::{Display, Formatter}; #[derive(Debug, Default)] pub struct Process { pub user: String, pub pid: String, pub cpu: Option, pub memory: Option, pub vsz: Option, pub rss: Option, pub tty: Option, pub stat: Option, pub start: Option, pub time: Option, pub command: String, } #[derive(Debug, Serialize, Deserialize)] #[allow(non_snake_case)] pub struct Top { pub Titles: Vec, pub Processes: Vec>, } impl Display for Process { fn fmt(&self, f: &mut Formatter) -> Result<(), Error> { let mut s = String::new(); s.push_str(&self.user.clone()); s.push(','); s.push_str(&self.pid.clone()); if let Some(v) = self.cpu.clone() { s.push(','); s.push_str(&v); } if let Some(v) = self.memory.clone() { s.push(','); s.push_str(&v); } if let Some(v) = self.vsz.clone() { s.push(','); s.push_str(&v); } if let Some(v) = self.rss.clone() { s.push(','); s.push_str(&v); } if let Some(v) = self.tty.clone() { s.push(','); s.push_str(&v); } if let Some(v) = self.stat.clone() { s.push(','); s.push_str(&v); } if let Some(v) = self.start.clone() { s.push(','); s.push_str(&v); } if let Some(v) = self.time.clone() { s.push(','); s.push_str(&v); } s.push(','); s.push_str(&self.command.clone()); write!(f, "{s}") } } dockworker-0.5.1/src/response.rs000064400000000000000000000167121046102023000150030ustar 00000000000000///! Response from Dockerd ///! use serde::{Deserialize, Serialize}; use serde_json::value as json; use std::error::Error as StdError; use std::fmt; #[derive(Debug, PartialEq, Eq, PartialOrd, Clone, Serialize, Deserialize)] pub struct ProgressDetail { pub current: u64, pub total: u64, } #[derive(Debug, PartialEq, Eq, PartialOrd, Clone, Serialize, Deserialize)] #[allow(non_snake_case)] pub struct Progress { /// image tag or hash of image layer or ... pub id: String, /// progress bar pub progress: Option, /// progress detail #[serde(deserialize_with = "progress_detail_opt::deserialize")] pub progressDetail: Option, /// message or auxiliary info pub status: String, } #[derive(Debug, PartialEq, Eq, PartialOrd, Clone, Serialize, Deserialize)] pub struct Stream { pub stream: String, } #[derive(Debug, PartialEq, Eq, PartialOrd, Clone, Serialize, Deserialize)] pub struct Status { #[serde(skip_serializing_if = "Option::is_none")] pub id: Option, pub status: String, } #[derive(Debug, PartialEq, Eq, PartialOrd, Clone, Serialize, Deserialize)] #[allow(non_snake_case)] pub struct LogID { pub ID: String, } #[derive(Debug, PartialEq, Eq, PartialOrd, Clone, Serialize, Deserialize)] pub struct Aux { pub aux: LogID, } #[derive(Debug, PartialEq, Eq, PartialOrd, Clone, Serialize, Deserialize)] pub struct LogResponse { pub response: String, } #[derive(Debug, PartialEq, Eq, PartialOrd, Clone, Serialize, Deserialize)] pub struct ErrorDetail { pub message: String, } #[derive(Debug, PartialEq, Eq, PartialOrd, Clone, Serialize, Deserialize)] #[allow(non_snake_case)] pub struct Error { pub error: String, pub errorDetail: ErrorDetail, } /// Response of /images/create or other API /// /// ## NOTE /// Structure of this type is not documented officialy. #[derive(Debug, PartialEq, Clone, Serialize, Deserialize)] #[serde(untagged)] pub enum Response { Progress(Progress), Status(Status), Stream(Stream), Aux(Aux), Response(LogResponse), Error(Error), /// unknown response Unknown(json::Value), } impl fmt::Display for Error { fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { write!(fmt, "{}: {}", self.error, self.errorDetail.message) } } impl StdError for Error { fn description(&self) -> &str { &self.error } fn cause(&self) -> Option<&(dyn std::error::Error + 'static)> { None } } impl Response { pub fn as_error(&self) -> Option<&Error> { use self::Response::*; if let Error(err) = self { Some(err) } else { None } } } mod progress_detail_opt { use super::*; use serde::de::{self, Deserializer, MapAccess, Visitor}; pub fn deserialize<'de, D>(de: D) -> Result, D::Error> where D: Deserializer<'de>, { struct OptVisitor; impl<'de> Visitor<'de> for OptVisitor { type Value = Option; fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { formatter.write_str("Option") } fn visit_map(self, mut map: V) -> Result where V: MapAccess<'de>, { let mut current = None; let mut total = None; match map.next_key()? { Some(mut key) => loop { match key { "current" => { if current.is_some() { return Err(de::Error::duplicate_field("current")); } current = Some(map.next_value()?); } "total" => { if total.is_some() { return Err(de::Error::duplicate_field("total")); } total = Some(map.next_value()?); } _ => return Err(de::Error::unknown_field(key, FIELDS)), }; if let Some(key_) = map.next_key()? { key = key_; } else { break; } }, None => return Ok(None), // {} } let current = current.ok_or_else(|| de::Error::missing_field("current"))?; let total = total.ok_or_else(|| de::Error::missing_field("total"))?; Ok(Some(ProgressDetail { current, total })) } } const FIELDS: &[&str] = &["current", "total"]; de.deserialize_map(OptVisitor) } } #[cfg(test)] mod tests { use self::Response as R; use super::*; use serde_json; #[test] #[rustfmt::skip] fn progress() { let s = r#"{ "status": "Downloading", "progressDetail":{ "current":1596117, "total":86451485 }, "progress":"[\u003e ] 1.596MB/86.45MB","id":"66aa7ce9b58b" }"#; assert_eq!( R::Progress(Progress { id: "66aa7ce9b58b".to_owned(), progress: "[\u{003e} ] 1.596MB/86.45MB" .to_owned() .into(), status: "Downloading".to_owned(), progressDetail: Some(ProgressDetail { current: 1596117, total: 86451485, }), }), serde_json::from_str(s).unwrap() ) } #[test] fn progress_empty() { let s = r#"{"status":"Already exists","progressDetail":{},"id":"18b8eb7e7f01"}"#; assert_eq!( Progress { id: "18b8eb7e7f01".to_owned(), progress: None, progressDetail: None, status: "Already exists".to_owned(), }, serde_json::from_str(s).unwrap() ); } #[test] fn status() { let s = r#"{"status":"Pulling from eldesh/smlnj","id":"110.78"}"#; assert_eq!( R::Status(Status { id: Some("110.78".to_owned()), status: "Pulling from eldesh/smlnj".to_owned(), }), serde_json::from_str(s).unwrap() ) } #[test] #[rustfmt::skip] fn error() { let s = r#"{ "errorDetail":{ "message":"failed to register layer: Error processing tar file(exit status 1): write /foo/bar: no space left on device" }, "error":"failed to register layer: Error processing tar file(exit status 1): write /foo/bar: no space left on device" }"#; assert_eq!( R::Error(Error { error: "failed to register layer: Error processing tar file(exit status 1): write /foo/bar: no space left on device".to_owned(), errorDetail: ErrorDetail { message: "failed to register layer: Error processing tar file(exit status 1): write /foo/bar: no space left on device".to_owned(), }, }), serde_json::from_str(s).unwrap() ) } } dockworker-0.5.1/src/signal.rs000064400000000000000000000052511046102023000144160ustar 00000000000000#[cfg(unix)] mod unix { use std::convert::TryFrom; use std::io; use std::os::raw::c_int; pub use self::NixSignal::*; use nix::sys::signal::{Signal as NixSignal, SignalIterator as NixSignalIterator}; use crate::errors::Error; #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct Signal(NixSignal); #[derive(Debug, Clone)] pub struct SignalIterator(NixSignalIterator); impl Signal { pub fn as_i32(&self) -> i32 { self.0 as i32 } pub fn iterator() -> SignalIterator { SignalIterator(NixSignal::iterator()) } pub fn from_c_int(signum: c_int) -> Result { Ok(NixSignal::try_from(signum) .map_err(|err| io::Error::from_raw_os_error(err as i32))? .into()) } } impl From for Signal { fn from(sig: NixSignal) -> Self { Self(sig) } } impl Iterator for SignalIterator { type Item = Signal; fn next(&mut self) -> Option { self.0.next().map(Into::into) } } } #[cfg(windows)] mod windows { use std::io; use std::os::raw::c_int; use crate::errors::Error; #[repr(i32)] #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Hash)] pub enum Signal { SIGKILL = 9, SIGTERM = 15, } #[derive(Debug, Clone)] pub struct SignalIterator(std::vec::IntoIter); impl Signal { pub fn as_i32(&self) -> i32 { self.clone() as i32 } pub fn iterator() -> SignalIterator { use self::Signal::*; SignalIterator(vec![SIGKILL, SIGTERM].into_iter()) } pub fn from_c_int(signum: c_int) -> Result { match signum { 9 => Ok(Signal::SIGKILL), 15 => Ok(Signal::SIGTERM), other => Err(io::Error::new( io::ErrorKind::InvalidInput, format!("unknown signal: {}", other), ) .into()), } } } impl Iterator for SignalIterator { type Item = Signal; fn next(&mut self) -> Option { self.0.next() } } #[cfg(test)] mod test { use super::*; #[test] fn iterator() { let mut it = Signal::iterator(); assert_eq!(it.next(), Some(Signal::SIGKILL)); assert_eq!(it.next(), Some(Signal::SIGTERM)); assert_eq!(it.next(), None); } } } #[cfg(unix)] pub use self::unix::*; #[cfg(windows)] pub use self::windows::*; dockworker-0.5.1/src/stats.rs000064400000000000000000000160011046102023000142720ustar 00000000000000use serde::{Deserialize, Serialize}; use std::collections::HashMap; /// response type of containers/stats api #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct Stats { pub id: String, pub name: String, pub read: String, pub networks: Option>, #[serde(with = "format::memory_stats")] pub memory_stats: Option, pub cpu_stats: CpuStats, /// The precpu_stats is the CPU statistic of the previous read, and is used to calculate the CPU usage percentage. /// It is not an exact copy of the cpu_stats field. pub precpu_stats: CpuStats, pub blkio_stats: BlkioStats, /// The number of pids in the cgroup pub pids_stats: PidsStats, } impl Stats { pub fn used_memory(&self) -> Option { self.memory_stats .as_ref() .map(|mem| mem.usage - mem.stats.cache) } pub fn available_memory(&self) -> Option { self.memory_stats.as_ref().map(|mem| mem.limit) } /// memory usage % pub fn memory_usage(&self) -> Option { if let (Some(used_memory), Some(available_memory)) = (self.used_memory(), self.available_memory()) { Some((used_memory as f64 / available_memory as f64) * 100.0) } else { None } } pub fn cpu_delta(&self) -> u64 { self.cpu_stats.cpu_usage.total_usage - self.precpu_stats.cpu_usage.total_usage } pub fn system_cpu_delta(&self) -> Option { if let (Some(cpu), Some(pre)) = ( self.cpu_stats.system_cpu_usage, self.precpu_stats.system_cpu_usage, ) { Some(cpu - pre) } else { None } } /// If either `precpu_stats.online_cpus` or `cpu_stats.online_cpus` is nil then for /// compatibility with older daemons the length of the corresponding `cpu_usage.percpu_usage` array should be used. pub fn number_cpus(&self) -> u64 { if let Some(cpus) = self.cpu_stats.online_cpus { cpus } else { let empty = &[]; self.cpu_stats .cpu_usage .percpu_usage .as_deref() .unwrap_or(empty) .len() as u64 } } /// cpu usage % pub fn cpu_usage(&self) -> Option { self.system_cpu_delta().map(|system_cpu_delta| { (self.cpu_delta() as f64 / system_cpu_delta as f64) * self.number_cpus() as f64 * 100.0 }) } } #[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Serialize, Deserialize)] pub struct Network { pub rx_dropped: u64, pub rx_bytes: u64, pub rx_errors: u64, pub tx_packets: u64, pub tx_dropped: u64, pub rx_packets: u64, pub tx_errors: u64, pub tx_bytes: u64, } #[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Serialize, Deserialize)] pub struct MemoryStats { pub max_usage: u64, pub usage: u64, #[serde(skip_serializing_if = "Option::is_none")] pub failcnt: Option, pub limit: u64, pub stats: MemoryStat, } #[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Serialize, Deserialize)] pub struct MemoryStat { pub total_pgmajfault: u64, pub cache: u64, pub mapped_file: u64, pub total_inactive_file: u64, pub pgpgout: u64, pub rss: u64, pub total_mapped_file: u64, pub writeback: u64, pub unevictable: u64, pub pgpgin: u64, pub total_unevictable: u64, pub pgmajfault: u64, pub total_rss: u64, pub total_rss_huge: u64, pub total_writeback: u64, pub total_inactive_anon: u64, pub rss_huge: u64, pub hierarchical_memory_limit: u64, pub total_pgfault: u64, pub total_active_file: u64, pub active_anon: u64, pub total_active_anon: u64, pub total_pgpgout: u64, pub total_cache: u64, pub inactive_anon: u64, pub active_file: u64, pub pgfault: u64, pub inactive_file: u64, pub total_pgpgin: u64, } #[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Serialize, Deserialize)] pub struct CpuStats { pub cpu_usage: CpuUsage, #[serde(skip_serializing_if = "Option::is_none")] pub system_cpu_usage: Option, #[serde(skip_serializing_if = "Option::is_none")] pub online_cpus: Option, pub throttling_data: ThrottlingData, } #[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Serialize, Deserialize)] pub struct CpuUsage { #[serde(skip_serializing_if = "Option::is_none")] pub percpu_usage: Option>, pub usage_in_usermode: u64, pub total_usage: u64, pub usage_in_kernelmode: u64, } #[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Serialize, Deserialize)] pub struct ThrottlingData { pub periods: u64, pub throttled_periods: u64, pub throttled_time: u64, } #[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Serialize, Deserialize)] pub struct BlkioStats { pub io_service_bytes_recursive: Option>, pub io_serviced_recursive: Option>, pub io_queue_recursive: Option>, pub io_service_time_recursive: Option>, pub io_wait_time_recursive: Option>, pub io_merged_recursive: Option>, pub io_time_recursive: Option>, pub sectors_recursive: Option>, } #[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Serialize, Deserialize)] pub struct BlkioStat { pub major: u64, pub minor: u64, pub op: String, pub value: u64, } #[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Serialize, Deserialize)] pub struct PidsStats { #[serde(skip_serializing_if = "Option::is_none")] pub current: Option, } mod format { use super::*; use serde::de::{DeserializeOwned, Deserializer}; use serde::ser::{Serialize, Serializer}; pub mod memory_stats { use super::*; #[derive(Debug, Serialize, Deserialize)] #[serde(untagged)] enum Plus1 { Item(T), Empty {}, } impl From> for Option { fn from(value: Plus1) -> Option { match value { Plus1::Item(t) => Some(t), Plus1::Empty {} => None, } } } impl From> for Plus1 { fn from(value: Option) -> Plus1 { match value { Option::Some(t) => Plus1::Item(t), Option::None => Plus1::Empty {}, } } } pub fn deserialize<'de, D, T>(de: D) -> std::result::Result, D::Error> where D: Deserializer<'de>, T: DeserializeOwned, { Plus1::::deserialize(de).map(Into::into) } pub fn serialize(t: &Option, se: S) -> std::result::Result where S: Serializer, T: Serialize, { Into::>::into(t.as_ref()).serialize(se) } } } dockworker-0.5.1/src/system.rs000064400000000000000000000050541046102023000144660ustar 00000000000000use serde::de::{self, Deserializer, Visitor}; use serde::{Deserialize, Serialize}; use std::fmt; use std::path::PathBuf; struct NumToBoolVisitor; impl<'de> Visitor<'de> for NumToBoolVisitor { type Value = bool; fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { formatter.write_str("0 or 1 or true or false") } fn visit_bool(self, value: bool) -> Result where E: de::Error, { Ok(value) } fn visit_i64(self, value: i64) -> Result where E: de::Error, { Ok(value != 0) } fn visit_u64(self, value: u64) -> Result where E: de::Error, { Ok(value != 0) } } fn num_to_bool<'de, D>(deserializer: D) -> Result where D: Deserializer<'de>, { deserializer.deserialize_any(NumToBoolVisitor) } /// response of /info #[derive(Debug, Clone, Serialize, Deserialize)] #[allow(non_snake_case)] pub struct SystemInfo { pub ID: String, pub Containers: u64, // pub ContainersRunning: u64, // pub ContainersPaused: u64, // pub ContainersStopped: u64, pub Images: u64, pub Driver: String, pub DriverStatus: Vec<(String, String)>, pub DockerRootDir: PathBuf, #[serde(deserialize_with = "num_to_bool")] pub MemoryLimit: bool, #[serde(deserialize_with = "num_to_bool")] pub SwapLimit: bool, // pub KernelMemory: bool, // pub OomKillDisable: bool, #[serde(deserialize_with = "num_to_bool")] pub IPv4Forwarding: bool, // pub BridgeNfIptables: bool, // pub BridgeNfIp6tables: bool, #[serde(deserialize_with = "num_to_bool")] pub Debug: bool, pub NFd: u64, pub NGoroutines: u64, // pub SystemTime: String, // pub LoggingDriver: String, // pub CgroupDriver: String, pub NEventsListener: u64, // pub KernelVersion: String, pub OperatingSystem: String, // pub OSType: String, // pub Architecture: String, pub NCPU: u64, pub MemTotal: u64, pub IndexServerAddress: String, // pub HttpProxy: String, // pub HttpsProxy: String, // pub NoProxy: String, // pub Name: String, pub Labels: Option>, // pub ServerVersion: String, } /// Type of the response of `/auth` api #[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)] #[allow(non_snake_case)] pub struct AuthToken { Status: String, IdentityToken: String, } impl AuthToken { #[allow(dead_code)] pub fn token(&self) -> String { self.IdentityToken.clone() } } dockworker-0.5.1/src/test.rs000064400000000000000000000107501046102023000141200ustar 00000000000000#![cfg(test)] use crate::container::{Container, ContainerInfo, HealthState}; use crate::filesystem::FilesystemChange; use crate::image::{Image, SummaryImage}; use crate::network::Network; use crate::options::ImageLayer; use crate::process::Top; use crate::stats::Stats; use crate::system::SystemInfo; use crate::version::Version; #[test] fn get_containers() { let response = get_containers_response(); assert!(serde_json::from_str::>(response).is_ok()) } #[test] fn get_networks() { let response = include_str!("fixtures/list_networks.json"); assert!(serde_json::from_str::>(response).is_ok()) } #[test] fn get_stats_suspended() { let stats_oneshot = include_str!("fixtures/stats_suspend.json"); let v = serde_json::from_str::(stats_oneshot).unwrap(); assert!(v.memory_stats.is_none()); } #[tokio::test] async fn get_stats_streaming() { let res = get_stats_response(); let src = crate::docker::into_jsonlines::(res.into_body()).unwrap(); use futures::stream::StreamExt; let stats = src .collect::>() .await .into_iter() .collect::, _>>() .unwrap(); assert_eq!(stats.len(), 3); assert!(stats[0].memory_stats.is_some()); assert!(stats[1].memory_stats.is_some()); assert!(stats[2].memory_stats.is_some()); } #[test] fn get_system_info() { let response = get_system_info_response(); assert!(serde_json::from_str::(response).is_ok()) } #[test] fn get_image_list() { let response = get_image_list_response(); let images: Vec = serde_json::from_str(response).unwrap(); assert_eq!(3, images.len()); } #[test] fn get_image() { let response = get_image_response(); println!("response: {:?}", serde_json::from_str::(response)); } #[test] fn get_image_history() { let response = get_image_history_reponse(); let images: Vec = serde_json::from_str(response).unwrap(); assert_ne!(images[0].id, None); assert_eq!(2, images.len()); } #[test] fn get_container_info() { let response = get_container_info_response(); serde_json::from_str::(response).unwrap(); let response = get_container_info_response_with_healthcheck(); serde_json::from_str::(response).unwrap(); } #[test] fn get_healthcheck_info() { let response = get_container_info_response_with_healthcheck(); let container_info = serde_json::from_str::(response).unwrap(); assert!(container_info.State.Health.is_some()); assert!(container_info.State.Health.unwrap().Status == HealthState::Healthy); } #[test] fn get_processes() { let response = get_processes_response(); assert!(serde_json::from_str::(response).is_ok()) } #[test] fn get_filesystem_changes() { let response = get_filesystem_changes_response(); assert!(serde_json::from_str::>(response).is_ok()) } #[test] fn get_version() { let response = get_version_response(); assert!(serde_json::from_str::(response).is_ok()) } fn get_containers_response() -> &'static str { include_str!("fixtures/containers_response.json") } fn get_system_info_response() -> &'static str { include_str!("fixtures/system_info.json") } // `docker inspect debian:wheely-2019- | jq '.[]' fn get_image_response() -> &'static str { include_str!("fixtures/image.json") } fn get_image_list_response() -> &'static str { include_str!("fixtures/image_list.json") } fn get_image_history_reponse() -> &'static str { // First has Id, second has Id missing. include_str!("fixtures/image_history.json") } fn get_container_info_response() -> &'static str { include_str!("fixtures/container_inspect.json") } fn get_container_info_response_with_healthcheck() -> &'static str { include_str!("fixtures/container_inspect_health.json") } fn get_processes_response() -> &'static str { include_str!("fixtures/processes.json") } fn get_filesystem_changes_response() -> &'static str { include_str!("fixtures/filesystem_changes.json") } fn get_version_response() -> &'static str { include_str!("fixtures/version.json") } fn get_stats_response() -> http::Response { let response = http::Response::builder() .status(http::StatusCode::OK) .header("Transfer-Encoding", "chunked") .header("Connection", "Close"); let body = include_str!("fixtures/stats_stream.json").to_string(); response.body(hyper::Body::from(body)).unwrap() } dockworker-0.5.1/src/version.rs000064400000000000000000000006531046102023000146270ustar 00000000000000use serde::Deserialize; #[derive(Debug, Deserialize)] #[allow(non_snake_case)] pub struct Version { pub Version: String, pub ApiVersion: String, #[serde(default = "String::default")] pub MinAPIVersion: String, pub GitCommit: String, pub GoVersion: String, pub Os: String, pub Arch: String, pub KernelVersion: String, pub Experimental: Option, pub BuildTime: Option, }