├── .dockerignore ├── .github └── workflows │ ├── ci.yaml │ └── release.yaml ├── .gitignore ├── .gitmodules ├── .vscode └── tasks.json ├── CNAME ├── Cargo.lock ├── Cargo.toml ├── Dockerfile ├── LICENSE ├── Makefile ├── README.md ├── _config.yml ├── build.rs ├── codegen ├── gen_commands.rs ├── gen_converters.rs ├── mod.rs ├── smithy_model.rs └── utils.rs ├── docs ├── architecture.md ├── developer-guide.md ├── s3d-diagram.png └── user-guide.md ├── examples └── k8s-s3d-deployment.yaml ├── s3d.png ├── src ├── bin │ ├── s3.rs │ └── s3d.rs ├── cli │ ├── api_cmd.rs │ ├── completion_cmd.rs │ ├── get_cmd.rs │ ├── list_cmd.rs │ ├── mod.rs │ ├── put_cmd.rs │ └── tag_cmd.rs ├── codegen_include.rs ├── config.rs ├── fuse.rs ├── lib.rs ├── s3 │ ├── api.rs │ ├── mod.rs │ └── server.rs ├── utils.rs └── write_queue.rs └── test └── sanity.sh /.dockerignore: -------------------------------------------------------------------------------- 1 | # s3d ignored files 2 | target/ 3 | .s3d 4 | .IGNORE 5 | 6 | # smithy-rs ignored files 7 | build/ 8 | .gradle/ 9 | !smithy-rs/s3d/build/crates 10 | -------------------------------------------------------------------------------- /.github/workflows/ci.yaml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | workflow_dispatch: 5 | push: 6 | branches: 7 | - main 8 | tags: 9 | - "*" 10 | pull_request: 11 | branches: 12 | - "*" 13 | paths-ignore: 14 | - "docs/**" 15 | - "**.md" 16 | 17 | env: 18 | CARGO_TERM_COLOR: always 19 | 20 | jobs: 21 | 22 | make-codegen: 23 | name: Make / Codegen 24 | runs-on: ubuntu-latest 25 | steps: 26 | - name: Git Checkout 27 | uses: actions/checkout@v2 28 | with: 29 | submodules: true 30 | - name: Cache Gradle 31 | uses: actions/cache@v2 32 | with: 33 | path: | 34 | ~/.gradle/caches 35 | ~/.gradle/wrapper 36 | key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*') }} 37 | restore-keys: | 38 | ${{ runner.os }}-gradle- 39 | - name: Cache Rust 40 | uses: Swatinem/rust-cache@v1 41 | - name: Make Codegen 42 | run: make codegen 43 | - name: Create Codegen Tarball 44 | run: tar cvzf codegen.tar.gz smithy-rs/s3d/build/crates/ 45 | - name: Upload Codegen Tarball 46 | uses: actions/upload-artifact@v2 47 | with: 48 | name: codegen.tar.gz 49 | path: codegen.tar.gz 50 | if-no-files-found: error 51 | 52 | make-debug: 53 | name: Make Build Debug 54 | needs: make-codegen 55 | runs-on: ubuntu-latest 56 | steps: 57 | - name: Install Fuse 58 | run: sudo apt-get install -y fuse libfuse-dev pkg-config 59 | - name: Git Checkout 60 | uses: actions/checkout@v2 61 | with: 62 | submodules: true 63 | - name: Download Codegen Tarball 64 | uses: actions/download-artifact@v2 65 | with: 66 | name: codegen.tar.gz 67 | - name: Extract Codegen Tarball 68 | run: tar xvzf codegen.tar.gz 69 | - name: Cache Rust 70 | uses: Swatinem/rust-cache@v1 71 | - name: Make Build Debug 72 | run: make build 73 | - name: Upload Binary 74 | uses: actions/upload-artifact@v2 75 | with: 76 | name: s3d-debug 77 | path: target/debug/s3d 78 | if-no-files-found: error 79 | - name: Upload Binary 80 | uses: actions/upload-artifact@v2 81 | with: 82 | name: s3-debug 83 | path: target/debug/s3 84 | if-no-files-found: error 85 | 86 | make-release: 87 | name: Make Build Release 88 | needs: make-codegen 89 | runs-on: ubuntu-latest 90 | steps: 91 | - name: Install Fuse 92 | run: sudo apt-get install -y fuse libfuse-dev pkg-config 93 | - name: Git Checkout 94 | uses: actions/checkout@v2 95 | with: 96 | submodules: true 97 | - name: Download Codegen Tarball 98 | uses: actions/download-artifact@v2 99 | with: 100 | name: codegen.tar.gz 101 | - name: Extract Codegen Tarball 102 | run: tar xvzf codegen.tar.gz 103 | - name: Cache Rust 104 | uses: Swatinem/rust-cache@v1 105 | - name: Make Build Release 106 | run: make build RELEASE=1 107 | - name: Upload Binary 108 | uses: actions/upload-artifact@v2 109 | with: 110 | name: s3d-release 111 | path: target/release/s3d 112 | if-no-files-found: error 113 | - name: Upload Binary 114 | uses: actions/upload-artifact@v2 115 | with: 116 | name: s3-release 117 | path: target/release/s3 118 | if-no-files-found: error 119 | 120 | make-test: 121 | name: Make Test 122 | needs: make-codegen 123 | runs-on: ubuntu-latest 124 | steps: 125 | # - name: Install Deps 126 | # run: sudo apt-get install -y fuse libfuse-dev pkg-config 127 | - name: Git Checkout 128 | uses: actions/checkout@v2 129 | with: 130 | submodules: true 131 | - name: Download Codegen Tarball 132 | uses: actions/download-artifact@v2 133 | with: 134 | name: codegen.tar.gz 135 | - name: Extract Codegen Tarball 136 | run: tar xvzf codegen.tar.gz 137 | - name: Cache Rust 138 | uses: Swatinem/rust-cache@v1 139 | - name: Make Test 140 | run: make test 141 | -------------------------------------------------------------------------------- /.github/workflows/release.yaml: -------------------------------------------------------------------------------- 1 | name: RELEASE 2 | 3 | on: 4 | push: 5 | tags: 6 | - "*" 7 | 8 | env: 9 | CARGO_TERM_COLOR: always 10 | 11 | jobs: 12 | release: 13 | name: Release 14 | runs-on: ubuntu-latest 15 | steps: 16 | - name: Install Fuse 17 | run: sudo apt-get install -y fuse libfuse-dev pkg-config 18 | - name: Git Checkout 19 | uses: actions/checkout@v2 20 | with: 21 | submodules: true 22 | - name: Cache Gradle 23 | uses: actions/cache@v2 24 | with: 25 | path: | 26 | ~/.gradle/caches 27 | ~/.gradle/wrapper 28 | key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*') }} 29 | restore-keys: | 30 | ${{ runner.os }}-gradle- 31 | - name: Cache Rust 32 | uses: Swatinem/rust-cache@v1 33 | - name: Make Build Release 34 | run: make build RELEASE=1 35 | - uses: "marvinpinto/action-automatic-releases@latest" 36 | with: 37 | repo_token: "${{ secrets.GITHUB_TOKEN }}" 38 | files: | 39 | target/debug/s3 40 | target/debug/s3d 41 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # rust build output 2 | target/ 3 | 4 | # local store per dir 5 | .s3d 6 | 7 | # developer drafts 8 | .IGNORE 9 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "smithy-rs"] 2 | path = smithy-rs 3 | url = https://github.com/s3d-rs/smithy-rs.git 4 | -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0.0", 3 | "tasks": [ 4 | { 5 | "label": "make", 6 | "group": { 7 | "kind": "build", 8 | "isDefault": true 9 | }, 10 | "type": "shell", 11 | "command": "make", 12 | "args": [], 13 | "problemMatcher": "$rustc" 14 | }, 15 | { 16 | "label": "make test", 17 | "group": { 18 | "kind": "test", 19 | "isDefault": true 20 | }, 21 | "type": "shell", 22 | "command": "make test", 23 | "args": [], 24 | "problemMatcher": "$rustc" 25 | } 26 | ] 27 | } 28 | -------------------------------------------------------------------------------- /CNAME: -------------------------------------------------------------------------------- 1 | s3d.rs -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "adler" 7 | version = "1.0.2" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" 10 | 11 | [[package]] 12 | name = "aho-corasick" 13 | version = "0.7.18" 14 | source = "registry+https://github.com/rust-lang/crates.io-index" 15 | checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f" 16 | dependencies = [ 17 | "memchr", 18 | ] 19 | 20 | [[package]] 21 | name = "anyhow" 22 | version = "1.0.57" 23 | source = "registry+https://github.com/rust-lang/crates.io-index" 24 | checksum = "08f9b8508dccb7687a1d6c4ce66b2b0ecef467c94667de27d8d7fe1f8d2a9cdc" 25 | 26 | [[package]] 27 | name = "async-trait" 28 | version = "0.1.53" 29 | source = "registry+https://github.com/rust-lang/crates.io-index" 30 | checksum = "ed6aa3524a2dfcf9fe180c51eae2b58738348d819517ceadf95789c51fff7600" 31 | dependencies = [ 32 | "proc-macro2", 33 | "quote", 34 | "syn", 35 | ] 36 | 37 | [[package]] 38 | name = "atty" 39 | version = "0.2.14" 40 | source = "registry+https://github.com/rust-lang/crates.io-index" 41 | checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" 42 | dependencies = [ 43 | "hermit-abi", 44 | "libc", 45 | "winapi", 46 | ] 47 | 48 | [[package]] 49 | name = "autocfg" 50 | version = "1.1.0" 51 | source = "registry+https://github.com/rust-lang/crates.io-index" 52 | checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" 53 | 54 | [[package]] 55 | name = "aws-config" 56 | version = "0.11.0" 57 | dependencies = [ 58 | "aws-http", 59 | "aws-sdk-sso", 60 | "aws-sdk-sts", 61 | "aws-smithy-async", 62 | "aws-smithy-client", 63 | "aws-smithy-http", 64 | "aws-smithy-http-tower", 65 | "aws-smithy-json", 66 | "aws-smithy-types", 67 | "aws-types", 68 | "bytes", 69 | "hex", 70 | "http", 71 | "hyper", 72 | "ring", 73 | "tokio", 74 | "tower", 75 | "tracing", 76 | "zeroize", 77 | ] 78 | 79 | [[package]] 80 | name = "aws-endpoint" 81 | version = "0.11.0" 82 | dependencies = [ 83 | "aws-smithy-http", 84 | "aws-types", 85 | "http", 86 | "regex", 87 | "tracing", 88 | ] 89 | 90 | [[package]] 91 | name = "aws-http" 92 | version = "0.11.0" 93 | dependencies = [ 94 | "aws-smithy-http", 95 | "aws-smithy-types", 96 | "aws-types", 97 | "http", 98 | "lazy_static", 99 | "percent-encoding", 100 | "tracing", 101 | ] 102 | 103 | [[package]] 104 | name = "aws-sdk-s3" 105 | version = "0.11.0" 106 | dependencies = [ 107 | "aws-endpoint", 108 | "aws-http", 109 | "aws-sig-auth", 110 | "aws-sigv4", 111 | "aws-smithy-async", 112 | "aws-smithy-client", 113 | "aws-smithy-eventstream", 114 | "aws-smithy-http", 115 | "aws-smithy-http-tower", 116 | "aws-smithy-types", 117 | "aws-smithy-xml", 118 | "aws-types", 119 | "bytes", 120 | "http", 121 | "md5", 122 | "tokio-stream", 123 | "tower", 124 | ] 125 | 126 | [[package]] 127 | name = "aws-sdk-sso" 128 | version = "0.11.0" 129 | dependencies = [ 130 | "aws-endpoint", 131 | "aws-http", 132 | "aws-sig-auth", 133 | "aws-smithy-async", 134 | "aws-smithy-client", 135 | "aws-smithy-http", 136 | "aws-smithy-http-tower", 137 | "aws-smithy-json", 138 | "aws-smithy-types", 139 | "aws-types", 140 | "bytes", 141 | "http", 142 | "tokio-stream", 143 | "tower", 144 | ] 145 | 146 | [[package]] 147 | name = "aws-sdk-sts" 148 | version = "0.11.0" 149 | dependencies = [ 150 | "aws-endpoint", 151 | "aws-http", 152 | "aws-sig-auth", 153 | "aws-smithy-async", 154 | "aws-smithy-client", 155 | "aws-smithy-http", 156 | "aws-smithy-http-tower", 157 | "aws-smithy-query", 158 | "aws-smithy-types", 159 | "aws-smithy-xml", 160 | "aws-types", 161 | "bytes", 162 | "http", 163 | "tower", 164 | ] 165 | 166 | [[package]] 167 | name = "aws-sig-auth" 168 | version = "0.11.0" 169 | dependencies = [ 170 | "aws-sigv4", 171 | "aws-smithy-eventstream", 172 | "aws-smithy-http", 173 | "aws-types", 174 | "http", 175 | "tracing", 176 | ] 177 | 178 | [[package]] 179 | name = "aws-sigv4" 180 | version = "0.11.0" 181 | dependencies = [ 182 | "aws-smithy-eventstream", 183 | "aws-smithy-http", 184 | "bytes", 185 | "form_urlencoded", 186 | "hex", 187 | "http", 188 | "once_cell", 189 | "percent-encoding", 190 | "regex", 191 | "ring", 192 | "time 0.3.7", 193 | "tracing", 194 | ] 195 | 196 | [[package]] 197 | name = "aws-smithy-async" 198 | version = "0.41.0" 199 | dependencies = [ 200 | "futures-util", 201 | "pin-project-lite", 202 | "tokio", 203 | "tokio-stream", 204 | ] 205 | 206 | [[package]] 207 | name = "aws-smithy-client" 208 | version = "0.41.0" 209 | dependencies = [ 210 | "aws-smithy-async", 211 | "aws-smithy-http", 212 | "aws-smithy-http-tower", 213 | "aws-smithy-types", 214 | "bytes", 215 | "fastrand", 216 | "http", 217 | "http-body", 218 | "hyper", 219 | "hyper-rustls", 220 | "lazy_static", 221 | "pin-project", 222 | "pin-project-lite", 223 | "tokio", 224 | "tower", 225 | "tracing", 226 | ] 227 | 228 | [[package]] 229 | name = "aws-smithy-eventstream" 230 | version = "0.41.0" 231 | dependencies = [ 232 | "aws-smithy-types", 233 | "bytes", 234 | "crc32fast", 235 | ] 236 | 237 | [[package]] 238 | name = "aws-smithy-http" 239 | version = "0.41.0" 240 | dependencies = [ 241 | "aws-smithy-eventstream", 242 | "aws-smithy-types", 243 | "bytes", 244 | "bytes-utils", 245 | "futures-core", 246 | "http", 247 | "http-body", 248 | "hyper", 249 | "once_cell", 250 | "percent-encoding", 251 | "pin-project", 252 | "tokio", 253 | "tokio-util 0.7.0", 254 | "tracing", 255 | ] 256 | 257 | [[package]] 258 | name = "aws-smithy-http-server" 259 | version = "0.41.0" 260 | dependencies = [ 261 | "async-trait", 262 | "aws-smithy-http", 263 | "aws-smithy-json", 264 | "aws-smithy-types", 265 | "aws-smithy-xml", 266 | "bytes", 267 | "futures-util", 268 | "http", 269 | "http-body", 270 | "hyper", 271 | "mime", 272 | "nom", 273 | "paste", 274 | "pin-project-lite", 275 | "regex", 276 | "serde_urlencoded", 277 | "strum_macros", 278 | "thiserror", 279 | "tokio", 280 | "tower", 281 | "tower-http", 282 | ] 283 | 284 | [[package]] 285 | name = "aws-smithy-http-tower" 286 | version = "0.41.0" 287 | dependencies = [ 288 | "aws-smithy-http", 289 | "bytes", 290 | "http", 291 | "http-body", 292 | "pin-project", 293 | "tower", 294 | "tracing", 295 | ] 296 | 297 | [[package]] 298 | name = "aws-smithy-json" 299 | version = "0.41.0" 300 | dependencies = [ 301 | "aws-smithy-types", 302 | ] 303 | 304 | [[package]] 305 | name = "aws-smithy-query" 306 | version = "0.41.0" 307 | dependencies = [ 308 | "aws-smithy-types", 309 | "urlencoding", 310 | ] 311 | 312 | [[package]] 313 | name = "aws-smithy-types" 314 | version = "0.41.0" 315 | dependencies = [ 316 | "itoa", 317 | "num-integer", 318 | "ryu", 319 | "time 0.3.7", 320 | ] 321 | 322 | [[package]] 323 | name = "aws-smithy-xml" 324 | version = "0.41.0" 325 | dependencies = [ 326 | "xmlparser", 327 | ] 328 | 329 | [[package]] 330 | name = "aws-types" 331 | version = "0.11.0" 332 | dependencies = [ 333 | "aws-smithy-async", 334 | "aws-smithy-client", 335 | "aws-smithy-http", 336 | "aws-smithy-types", 337 | "http", 338 | "rustc_version", 339 | "tracing", 340 | "zeroize", 341 | ] 342 | 343 | [[package]] 344 | name = "base64" 345 | version = "0.13.0" 346 | source = "registry+https://github.com/rust-lang/crates.io-index" 347 | checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" 348 | 349 | [[package]] 350 | name = "bitflags" 351 | version = "1.3.2" 352 | source = "registry+https://github.com/rust-lang/crates.io-index" 353 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 354 | 355 | [[package]] 356 | name = "bumpalo" 357 | version = "3.9.1" 358 | source = "registry+https://github.com/rust-lang/crates.io-index" 359 | checksum = "a4a45a46ab1f2412e53d3a0ade76ffad2025804294569aae387231a0cd6e0899" 360 | 361 | [[package]] 362 | name = "byteorder" 363 | version = "1.4.3" 364 | source = "registry+https://github.com/rust-lang/crates.io-index" 365 | checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" 366 | 367 | [[package]] 368 | name = "bytes" 369 | version = "1.1.0" 370 | source = "registry+https://github.com/rust-lang/crates.io-index" 371 | checksum = "c4872d67bab6358e59559027aa3b9157c53d9358c51423c17554809a8858e0f8" 372 | 373 | [[package]] 374 | name = "bytes-utils" 375 | version = "0.1.1" 376 | source = "registry+https://github.com/rust-lang/crates.io-index" 377 | checksum = "4e314712951c43123e5920a446464929adc667a5eade7f8fb3997776c9df6e54" 378 | dependencies = [ 379 | "bytes", 380 | "either", 381 | ] 382 | 383 | [[package]] 384 | name = "cc" 385 | version = "1.0.73" 386 | source = "registry+https://github.com/rust-lang/crates.io-index" 387 | checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11" 388 | 389 | [[package]] 390 | name = "cfg-if" 391 | version = "1.0.0" 392 | source = "registry+https://github.com/rust-lang/crates.io-index" 393 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 394 | 395 | [[package]] 396 | name = "chrono" 397 | version = "0.4.19" 398 | source = "registry+https://github.com/rust-lang/crates.io-index" 399 | checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73" 400 | dependencies = [ 401 | "libc", 402 | "num-integer", 403 | "num-traits", 404 | "time 0.1.44", 405 | "winapi", 406 | ] 407 | 408 | [[package]] 409 | name = "clap" 410 | version = "3.1.18" 411 | source = "registry+https://github.com/rust-lang/crates.io-index" 412 | checksum = "d2dbdf4bdacb33466e854ce889eee8dfd5729abf7ccd7664d0a2d60cd384440b" 413 | dependencies = [ 414 | "atty", 415 | "bitflags", 416 | "clap_derive", 417 | "clap_lex", 418 | "indexmap", 419 | "lazy_static", 420 | "strsim", 421 | "termcolor", 422 | "textwrap", 423 | ] 424 | 425 | [[package]] 426 | name = "clap_complete" 427 | version = "3.1.4" 428 | source = "registry+https://github.com/rust-lang/crates.io-index" 429 | checksum = "da92e6facd8d73c22745a5d3cbb59bdf8e46e3235c923e516527d8e81eec14a4" 430 | dependencies = [ 431 | "clap", 432 | ] 433 | 434 | [[package]] 435 | name = "clap_derive" 436 | version = "3.1.18" 437 | source = "registry+https://github.com/rust-lang/crates.io-index" 438 | checksum = "25320346e922cffe59c0bbc5410c8d8784509efb321488971081313cb1e1a33c" 439 | dependencies = [ 440 | "heck", 441 | "proc-macro-error", 442 | "proc-macro2", 443 | "quote", 444 | "syn", 445 | ] 446 | 447 | [[package]] 448 | name = "clap_lex" 449 | version = "0.2.0" 450 | source = "registry+https://github.com/rust-lang/crates.io-index" 451 | checksum = "a37c35f1112dad5e6e0b1adaff798507497a18fceeb30cceb3bae7d1427b9213" 452 | dependencies = [ 453 | "os_str_bytes", 454 | ] 455 | 456 | [[package]] 457 | name = "core-foundation" 458 | version = "0.9.3" 459 | source = "registry+https://github.com/rust-lang/crates.io-index" 460 | checksum = "194a7a9e6de53fa55116934067c844d9d749312f75c6f6d0980e8c252f8c2146" 461 | dependencies = [ 462 | "core-foundation-sys", 463 | "libc", 464 | ] 465 | 466 | [[package]] 467 | name = "core-foundation-sys" 468 | version = "0.8.3" 469 | source = "registry+https://github.com/rust-lang/crates.io-index" 470 | checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc" 471 | 472 | [[package]] 473 | name = "crc32fast" 474 | version = "1.3.2" 475 | source = "registry+https://github.com/rust-lang/crates.io-index" 476 | checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" 477 | dependencies = [ 478 | "cfg-if", 479 | ] 480 | 481 | [[package]] 482 | name = "crossbeam-channel" 483 | version = "0.5.2" 484 | source = "registry+https://github.com/rust-lang/crates.io-index" 485 | checksum = "e54ea8bc3fb1ee042f5aace6e3c6e025d3874866da222930f70ce62aceba0bfa" 486 | dependencies = [ 487 | "cfg-if", 488 | "crossbeam-utils", 489 | ] 490 | 491 | [[package]] 492 | name = "crossbeam-utils" 493 | version = "0.8.7" 494 | source = "registry+https://github.com/rust-lang/crates.io-index" 495 | checksum = "b5e5bed1f1c269533fa816a0a5492b3545209a205ca1a54842be180eb63a16a6" 496 | dependencies = [ 497 | "cfg-if", 498 | "lazy_static", 499 | ] 500 | 501 | [[package]] 502 | name = "ct-logs" 503 | version = "0.8.0" 504 | source = "registry+https://github.com/rust-lang/crates.io-index" 505 | checksum = "c1a816186fa68d9e426e3cb4ae4dff1fcd8e4a2c34b781bf7a822574a0d0aac8" 506 | dependencies = [ 507 | "sct", 508 | ] 509 | 510 | [[package]] 511 | name = "either" 512 | version = "1.6.1" 513 | source = "registry+https://github.com/rust-lang/crates.io-index" 514 | checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457" 515 | 516 | [[package]] 517 | name = "env_logger" 518 | version = "0.9.0" 519 | source = "registry+https://github.com/rust-lang/crates.io-index" 520 | checksum = "0b2cf0344971ee6c64c31be0d530793fba457d322dfec2810c453d0ef228f9c3" 521 | dependencies = [ 522 | "atty", 523 | "humantime", 524 | "log", 525 | "regex", 526 | "termcolor", 527 | ] 528 | 529 | [[package]] 530 | name = "envy" 531 | version = "0.4.2" 532 | source = "registry+https://github.com/rust-lang/crates.io-index" 533 | checksum = "3f47e0157f2cb54f5ae1bd371b30a2ae4311e1c028f575cd4e81de7353215965" 534 | dependencies = [ 535 | "serde", 536 | ] 537 | 538 | [[package]] 539 | name = "fastrand" 540 | version = "1.7.0" 541 | source = "registry+https://github.com/rust-lang/crates.io-index" 542 | checksum = "c3fcf0cee53519c866c09b5de1f6c56ff9d647101f81c1964fa632e148896cdf" 543 | dependencies = [ 544 | "instant", 545 | ] 546 | 547 | [[package]] 548 | name = "flate2" 549 | version = "1.0.22" 550 | source = "registry+https://github.com/rust-lang/crates.io-index" 551 | checksum = "1e6988e897c1c9c485f43b47a529cef42fde0547f9d8d41a7062518f1d8fc53f" 552 | dependencies = [ 553 | "cfg-if", 554 | "crc32fast", 555 | "libc", 556 | "miniz_oxide", 557 | ] 558 | 559 | [[package]] 560 | name = "fnv" 561 | version = "1.0.7" 562 | source = "registry+https://github.com/rust-lang/crates.io-index" 563 | checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" 564 | 565 | [[package]] 566 | name = "form_urlencoded" 567 | version = "1.0.1" 568 | source = "registry+https://github.com/rust-lang/crates.io-index" 569 | checksum = "5fc25a87fa4fd2094bffb06925852034d90a17f0d1e05197d4956d3555752191" 570 | dependencies = [ 571 | "matches", 572 | "percent-encoding", 573 | ] 574 | 575 | [[package]] 576 | name = "fuser" 577 | version = "0.11.0" 578 | source = "registry+https://github.com/rust-lang/crates.io-index" 579 | checksum = "aef8400a4ea1d18a8302e2952f5137a9a21ab257825ccc7d67db4a8018b89022" 580 | dependencies = [ 581 | "libc", 582 | "log", 583 | "memchr", 584 | "page_size", 585 | "pkg-config", 586 | "smallvec", 587 | "users", 588 | "zerocopy", 589 | ] 590 | 591 | [[package]] 592 | name = "futures-channel" 593 | version = "0.3.21" 594 | source = "registry+https://github.com/rust-lang/crates.io-index" 595 | checksum = "c3083ce4b914124575708913bca19bfe887522d6e2e6d0952943f5eac4a74010" 596 | dependencies = [ 597 | "futures-core", 598 | ] 599 | 600 | [[package]] 601 | name = "futures-core" 602 | version = "0.3.21" 603 | source = "registry+https://github.com/rust-lang/crates.io-index" 604 | checksum = "0c09fd04b7e4073ac7156a9539b57a484a8ea920f79c7c675d05d289ab6110d3" 605 | 606 | [[package]] 607 | name = "futures-macro" 608 | version = "0.3.21" 609 | source = "registry+https://github.com/rust-lang/crates.io-index" 610 | checksum = "33c1e13800337f4d4d7a316bf45a567dbcb6ffe087f16424852d97e97a91f512" 611 | dependencies = [ 612 | "proc-macro2", 613 | "quote", 614 | "syn", 615 | ] 616 | 617 | [[package]] 618 | name = "futures-sink" 619 | version = "0.3.21" 620 | source = "registry+https://github.com/rust-lang/crates.io-index" 621 | checksum = "21163e139fa306126e6eedaf49ecdb4588f939600f0b1e770f4205ee4b7fa868" 622 | 623 | [[package]] 624 | name = "futures-task" 625 | version = "0.3.21" 626 | source = "registry+https://github.com/rust-lang/crates.io-index" 627 | checksum = "57c66a976bf5909d801bbef33416c41372779507e7a6b3a5e25e4749c58f776a" 628 | 629 | [[package]] 630 | name = "futures-util" 631 | version = "0.3.21" 632 | source = "registry+https://github.com/rust-lang/crates.io-index" 633 | checksum = "d8b7abd5d659d9b90c8cba917f6ec750a74e2dc23902ef9cd4cc8c8b22e6036a" 634 | dependencies = [ 635 | "futures-core", 636 | "futures-macro", 637 | "futures-task", 638 | "pin-project-lite", 639 | "pin-utils", 640 | "slab", 641 | ] 642 | 643 | [[package]] 644 | name = "getrandom" 645 | version = "0.2.5" 646 | source = "registry+https://github.com/rust-lang/crates.io-index" 647 | checksum = "d39cd93900197114fa1fcb7ae84ca742095eed9442088988ae74fa744e930e77" 648 | dependencies = [ 649 | "cfg-if", 650 | "libc", 651 | "wasi 0.10.0+wasi-snapshot-preview1", 652 | ] 653 | 654 | [[package]] 655 | name = "h2" 656 | version = "0.3.11" 657 | source = "registry+https://github.com/rust-lang/crates.io-index" 658 | checksum = "d9f1f717ddc7b2ba36df7e871fd88db79326551d3d6f1fc406fbfd28b582ff8e" 659 | dependencies = [ 660 | "bytes", 661 | "fnv", 662 | "futures-core", 663 | "futures-sink", 664 | "futures-util", 665 | "http", 666 | "indexmap", 667 | "slab", 668 | "tokio", 669 | "tokio-util 0.6.9", 670 | "tracing", 671 | ] 672 | 673 | [[package]] 674 | name = "hashbrown" 675 | version = "0.11.2" 676 | source = "registry+https://github.com/rust-lang/crates.io-index" 677 | checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e" 678 | 679 | [[package]] 680 | name = "hdrhistogram" 681 | version = "7.5.0" 682 | source = "registry+https://github.com/rust-lang/crates.io-index" 683 | checksum = "31672b7011be2c4f7456c4ddbcb40e7e9a4a9fad8efe49a6ebaf5f307d0109c0" 684 | dependencies = [ 685 | "base64", 686 | "byteorder", 687 | "crossbeam-channel", 688 | "flate2", 689 | "nom", 690 | "num-traits", 691 | ] 692 | 693 | [[package]] 694 | name = "heck" 695 | version = "0.4.0" 696 | source = "registry+https://github.com/rust-lang/crates.io-index" 697 | checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9" 698 | 699 | [[package]] 700 | name = "hermit-abi" 701 | version = "0.1.19" 702 | source = "registry+https://github.com/rust-lang/crates.io-index" 703 | checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" 704 | dependencies = [ 705 | "libc", 706 | ] 707 | 708 | [[package]] 709 | name = "hex" 710 | version = "0.4.3" 711 | source = "registry+https://github.com/rust-lang/crates.io-index" 712 | checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" 713 | 714 | [[package]] 715 | name = "http" 716 | version = "0.2.6" 717 | source = "registry+https://github.com/rust-lang/crates.io-index" 718 | checksum = "31f4c6746584866f0feabcc69893c5b51beef3831656a968ed7ae254cdc4fd03" 719 | dependencies = [ 720 | "bytes", 721 | "fnv", 722 | "itoa", 723 | ] 724 | 725 | [[package]] 726 | name = "http-body" 727 | version = "0.4.4" 728 | source = "registry+https://github.com/rust-lang/crates.io-index" 729 | checksum = "1ff4f84919677303da5f147645dbea6b1881f368d03ac84e1dc09031ebd7b2c6" 730 | dependencies = [ 731 | "bytes", 732 | "http", 733 | "pin-project-lite", 734 | ] 735 | 736 | [[package]] 737 | name = "http-range-header" 738 | version = "0.3.0" 739 | source = "registry+https://github.com/rust-lang/crates.io-index" 740 | checksum = "0bfe8eed0a9285ef776bb792479ea3834e8b94e13d615c2f66d03dd50a435a29" 741 | 742 | [[package]] 743 | name = "httparse" 744 | version = "1.6.0" 745 | source = "registry+https://github.com/rust-lang/crates.io-index" 746 | checksum = "9100414882e15fb7feccb4897e5f0ff0ff1ca7d1a86a23208ada4d7a18e6c6c4" 747 | 748 | [[package]] 749 | name = "httpdate" 750 | version = "1.0.2" 751 | source = "registry+https://github.com/rust-lang/crates.io-index" 752 | checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421" 753 | 754 | [[package]] 755 | name = "humantime" 756 | version = "2.1.0" 757 | source = "registry+https://github.com/rust-lang/crates.io-index" 758 | checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" 759 | 760 | [[package]] 761 | name = "hyper" 762 | version = "0.14.18" 763 | source = "registry+https://github.com/rust-lang/crates.io-index" 764 | checksum = "b26ae0a80afebe130861d90abf98e3814a4f28a4c6ffeb5ab8ebb2be311e0ef2" 765 | dependencies = [ 766 | "bytes", 767 | "futures-channel", 768 | "futures-core", 769 | "futures-util", 770 | "h2", 771 | "http", 772 | "http-body", 773 | "httparse", 774 | "httpdate", 775 | "itoa", 776 | "pin-project-lite", 777 | "socket2", 778 | "tokio", 779 | "tower-service", 780 | "tracing", 781 | "want", 782 | ] 783 | 784 | [[package]] 785 | name = "hyper-rustls" 786 | version = "0.22.1" 787 | source = "registry+https://github.com/rust-lang/crates.io-index" 788 | checksum = "5f9f7a97316d44c0af9b0301e65010573a853a9fc97046d7331d7f6bc0fd5a64" 789 | dependencies = [ 790 | "ct-logs", 791 | "futures-util", 792 | "hyper", 793 | "log", 794 | "rustls", 795 | "rustls-native-certs", 796 | "tokio", 797 | "tokio-rustls", 798 | "webpki", 799 | ] 800 | 801 | [[package]] 802 | name = "idna" 803 | version = "0.2.3" 804 | source = "registry+https://github.com/rust-lang/crates.io-index" 805 | checksum = "418a0a6fab821475f634efe3ccc45c013f742efe03d853e8d3355d5cb850ecf8" 806 | dependencies = [ 807 | "matches", 808 | "unicode-bidi", 809 | "unicode-normalization", 810 | ] 811 | 812 | [[package]] 813 | name = "indexmap" 814 | version = "1.8.0" 815 | source = "registry+https://github.com/rust-lang/crates.io-index" 816 | checksum = "282a6247722caba404c065016bbfa522806e51714c34f5dfc3e4a3a46fcb4223" 817 | dependencies = [ 818 | "autocfg", 819 | "hashbrown", 820 | ] 821 | 822 | [[package]] 823 | name = "instant" 824 | version = "0.1.12" 825 | source = "registry+https://github.com/rust-lang/crates.io-index" 826 | checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" 827 | dependencies = [ 828 | "cfg-if", 829 | ] 830 | 831 | [[package]] 832 | name = "itoa" 833 | version = "1.0.1" 834 | source = "registry+https://github.com/rust-lang/crates.io-index" 835 | checksum = "1aab8fc367588b89dcee83ab0fd66b72b50b72fa1904d7095045ace2b0c81c35" 836 | 837 | [[package]] 838 | name = "js-sys" 839 | version = "0.3.56" 840 | source = "registry+https://github.com/rust-lang/crates.io-index" 841 | checksum = "a38fc24e30fd564ce974c02bf1d337caddff65be6cc4735a1f7eab22a7440f04" 842 | dependencies = [ 843 | "wasm-bindgen", 844 | ] 845 | 846 | [[package]] 847 | name = "lazy_static" 848 | version = "1.4.0" 849 | source = "registry+https://github.com/rust-lang/crates.io-index" 850 | checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" 851 | 852 | [[package]] 853 | name = "libc" 854 | version = "0.2.125" 855 | source = "registry+https://github.com/rust-lang/crates.io-index" 856 | checksum = "5916d2ae698f6de9bfb891ad7a8d65c09d232dc58cc4ac433c7da3b2fd84bc2b" 857 | 858 | [[package]] 859 | name = "linked-hash-map" 860 | version = "0.5.4" 861 | source = "registry+https://github.com/rust-lang/crates.io-index" 862 | checksum = "7fb9b38af92608140b86b693604b9ffcc5824240a484d1ecd4795bacb2fe88f3" 863 | 864 | [[package]] 865 | name = "lock_api" 866 | version = "0.4.6" 867 | source = "registry+https://github.com/rust-lang/crates.io-index" 868 | checksum = "88943dd7ef4a2e5a4bfa2753aaab3013e34ce2533d1996fb18ef591e315e2b3b" 869 | dependencies = [ 870 | "scopeguard", 871 | ] 872 | 873 | [[package]] 874 | name = "log" 875 | version = "0.4.17" 876 | source = "registry+https://github.com/rust-lang/crates.io-index" 877 | checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" 878 | dependencies = [ 879 | "cfg-if", 880 | ] 881 | 882 | [[package]] 883 | name = "matches" 884 | version = "0.1.9" 885 | source = "registry+https://github.com/rust-lang/crates.io-index" 886 | checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f" 887 | 888 | [[package]] 889 | name = "md5" 890 | version = "0.7.0" 891 | source = "registry+https://github.com/rust-lang/crates.io-index" 892 | checksum = "490cc448043f947bae3cbee9c203358d62dbee0db12107a74be5c30ccfd09771" 893 | 894 | [[package]] 895 | name = "memchr" 896 | version = "2.4.1" 897 | source = "registry+https://github.com/rust-lang/crates.io-index" 898 | checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a" 899 | 900 | [[package]] 901 | name = "mime" 902 | version = "0.3.16" 903 | source = "registry+https://github.com/rust-lang/crates.io-index" 904 | checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d" 905 | 906 | [[package]] 907 | name = "minimal-lexical" 908 | version = "0.2.1" 909 | source = "registry+https://github.com/rust-lang/crates.io-index" 910 | checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" 911 | 912 | [[package]] 913 | name = "miniz_oxide" 914 | version = "0.4.4" 915 | source = "registry+https://github.com/rust-lang/crates.io-index" 916 | checksum = "a92518e98c078586bc6c934028adcca4c92a53d6a958196de835170a01d84e4b" 917 | dependencies = [ 918 | "adler", 919 | "autocfg", 920 | ] 921 | 922 | [[package]] 923 | name = "mio" 924 | version = "0.8.2" 925 | source = "registry+https://github.com/rust-lang/crates.io-index" 926 | checksum = "52da4364ffb0e4fe33a9841a98a3f3014fb964045ce4f7a45a398243c8d6b0c9" 927 | dependencies = [ 928 | "libc", 929 | "log", 930 | "miow", 931 | "ntapi", 932 | "wasi 0.11.0+wasi-snapshot-preview1", 933 | "winapi", 934 | ] 935 | 936 | [[package]] 937 | name = "miow" 938 | version = "0.3.7" 939 | source = "registry+https://github.com/rust-lang/crates.io-index" 940 | checksum = "b9f1c5b025cda876f66ef43a113f91ebc9f4ccef34843000e0adf6ebbab84e21" 941 | dependencies = [ 942 | "winapi", 943 | ] 944 | 945 | [[package]] 946 | name = "nom" 947 | version = "7.1.0" 948 | source = "registry+https://github.com/rust-lang/crates.io-index" 949 | checksum = "1b1d11e1ef389c76fe5b81bcaf2ea32cf88b62bc494e19f493d0b30e7a930109" 950 | dependencies = [ 951 | "memchr", 952 | "minimal-lexical", 953 | "version_check", 954 | ] 955 | 956 | [[package]] 957 | name = "ntapi" 958 | version = "0.3.7" 959 | source = "registry+https://github.com/rust-lang/crates.io-index" 960 | checksum = "c28774a7fd2fbb4f0babd8237ce554b73af68021b5f695a3cebd6c59bac0980f" 961 | dependencies = [ 962 | "winapi", 963 | ] 964 | 965 | [[package]] 966 | name = "num-integer" 967 | version = "0.1.44" 968 | source = "registry+https://github.com/rust-lang/crates.io-index" 969 | checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db" 970 | dependencies = [ 971 | "autocfg", 972 | "num-traits", 973 | ] 974 | 975 | [[package]] 976 | name = "num-traits" 977 | version = "0.2.14" 978 | source = "registry+https://github.com/rust-lang/crates.io-index" 979 | checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290" 980 | dependencies = [ 981 | "autocfg", 982 | ] 983 | 984 | [[package]] 985 | name = "num_cpus" 986 | version = "1.13.1" 987 | source = "registry+https://github.com/rust-lang/crates.io-index" 988 | checksum = "19e64526ebdee182341572e50e9ad03965aa510cd94427a4549448f285e957a1" 989 | dependencies = [ 990 | "hermit-abi", 991 | "libc", 992 | ] 993 | 994 | [[package]] 995 | name = "num_threads" 996 | version = "0.1.3" 997 | source = "registry+https://github.com/rust-lang/crates.io-index" 998 | checksum = "97ba99ba6393e2c3734791401b66902d981cb03bf190af674ca69949b6d5fb15" 999 | dependencies = [ 1000 | "libc", 1001 | ] 1002 | 1003 | [[package]] 1004 | name = "once_cell" 1005 | version = "1.10.0" 1006 | source = "registry+https://github.com/rust-lang/crates.io-index" 1007 | checksum = "87f3e037eac156d1775da914196f0f37741a274155e34a0b7e427c35d2a2ecb9" 1008 | 1009 | [[package]] 1010 | name = "openssl-probe" 1011 | version = "0.1.5" 1012 | source = "registry+https://github.com/rust-lang/crates.io-index" 1013 | checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" 1014 | 1015 | [[package]] 1016 | name = "os_str_bytes" 1017 | version = "6.0.0" 1018 | source = "registry+https://github.com/rust-lang/crates.io-index" 1019 | checksum = "8e22443d1643a904602595ba1cd8f7d896afe56d26712531c5ff73a15b2fbf64" 1020 | 1021 | [[package]] 1022 | name = "page_size" 1023 | version = "0.4.2" 1024 | source = "registry+https://github.com/rust-lang/crates.io-index" 1025 | checksum = "eebde548fbbf1ea81a99b128872779c437752fb99f217c45245e1a61dcd9edcd" 1026 | dependencies = [ 1027 | "libc", 1028 | "winapi", 1029 | ] 1030 | 1031 | [[package]] 1032 | name = "parking_lot" 1033 | version = "0.12.0" 1034 | source = "registry+https://github.com/rust-lang/crates.io-index" 1035 | checksum = "87f5ec2493a61ac0506c0f4199f99070cbe83857b0337006a30f3e6719b8ef58" 1036 | dependencies = [ 1037 | "lock_api", 1038 | "parking_lot_core", 1039 | ] 1040 | 1041 | [[package]] 1042 | name = "parking_lot_core" 1043 | version = "0.9.1" 1044 | source = "registry+https://github.com/rust-lang/crates.io-index" 1045 | checksum = "28141e0cc4143da2443301914478dc976a61ffdb3f043058310c70df2fed8954" 1046 | dependencies = [ 1047 | "cfg-if", 1048 | "libc", 1049 | "redox_syscall", 1050 | "smallvec", 1051 | "windows-sys", 1052 | ] 1053 | 1054 | [[package]] 1055 | name = "paste" 1056 | version = "1.0.7" 1057 | source = "registry+https://github.com/rust-lang/crates.io-index" 1058 | checksum = "0c520e05135d6e763148b6426a837e239041653ba7becd2e538c076c738025fc" 1059 | 1060 | [[package]] 1061 | name = "percent-encoding" 1062 | version = "2.1.0" 1063 | source = "registry+https://github.com/rust-lang/crates.io-index" 1064 | checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" 1065 | 1066 | [[package]] 1067 | name = "pin-project" 1068 | version = "1.0.10" 1069 | source = "registry+https://github.com/rust-lang/crates.io-index" 1070 | checksum = "58ad3879ad3baf4e44784bc6a718a8698867bb991f8ce24d1bcbe2cfb4c3a75e" 1071 | dependencies = [ 1072 | "pin-project-internal", 1073 | ] 1074 | 1075 | [[package]] 1076 | name = "pin-project-internal" 1077 | version = "1.0.10" 1078 | source = "registry+https://github.com/rust-lang/crates.io-index" 1079 | checksum = "744b6f092ba29c3650faf274db506afd39944f48420f6c86b17cfe0ee1cb36bb" 1080 | dependencies = [ 1081 | "proc-macro2", 1082 | "quote", 1083 | "syn", 1084 | ] 1085 | 1086 | [[package]] 1087 | name = "pin-project-lite" 1088 | version = "0.2.8" 1089 | source = "registry+https://github.com/rust-lang/crates.io-index" 1090 | checksum = "e280fbe77cc62c91527259e9442153f4688736748d24660126286329742b4c6c" 1091 | 1092 | [[package]] 1093 | name = "pin-utils" 1094 | version = "0.1.0" 1095 | source = "registry+https://github.com/rust-lang/crates.io-index" 1096 | checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" 1097 | 1098 | [[package]] 1099 | name = "pkg-config" 1100 | version = "0.3.24" 1101 | source = "registry+https://github.com/rust-lang/crates.io-index" 1102 | checksum = "58893f751c9b0412871a09abd62ecd2a00298c6c83befa223ef98c52aef40cbe" 1103 | 1104 | [[package]] 1105 | name = "ppv-lite86" 1106 | version = "0.2.16" 1107 | source = "registry+https://github.com/rust-lang/crates.io-index" 1108 | checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872" 1109 | 1110 | [[package]] 1111 | name = "proc-macro-error" 1112 | version = "1.0.4" 1113 | source = "registry+https://github.com/rust-lang/crates.io-index" 1114 | checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" 1115 | dependencies = [ 1116 | "proc-macro-error-attr", 1117 | "proc-macro2", 1118 | "quote", 1119 | "syn", 1120 | "version_check", 1121 | ] 1122 | 1123 | [[package]] 1124 | name = "proc-macro-error-attr" 1125 | version = "1.0.4" 1126 | source = "registry+https://github.com/rust-lang/crates.io-index" 1127 | checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" 1128 | dependencies = [ 1129 | "proc-macro2", 1130 | "quote", 1131 | "version_check", 1132 | ] 1133 | 1134 | [[package]] 1135 | name = "proc-macro2" 1136 | version = "1.0.38" 1137 | source = "registry+https://github.com/rust-lang/crates.io-index" 1138 | checksum = "9027b48e9d4c9175fa2218adf3557f91c1137021739951d4932f5f8268ac48aa" 1139 | dependencies = [ 1140 | "unicode-xid", 1141 | ] 1142 | 1143 | [[package]] 1144 | name = "quote" 1145 | version = "1.0.18" 1146 | source = "registry+https://github.com/rust-lang/crates.io-index" 1147 | checksum = "a1feb54ed693b93a84e14094943b84b7c4eae204c512b7ccb95ab0c66d278ad1" 1148 | dependencies = [ 1149 | "proc-macro2", 1150 | ] 1151 | 1152 | [[package]] 1153 | name = "rand" 1154 | version = "0.8.5" 1155 | source = "registry+https://github.com/rust-lang/crates.io-index" 1156 | checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" 1157 | dependencies = [ 1158 | "libc", 1159 | "rand_chacha", 1160 | "rand_core", 1161 | ] 1162 | 1163 | [[package]] 1164 | name = "rand_chacha" 1165 | version = "0.3.1" 1166 | source = "registry+https://github.com/rust-lang/crates.io-index" 1167 | checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" 1168 | dependencies = [ 1169 | "ppv-lite86", 1170 | "rand_core", 1171 | ] 1172 | 1173 | [[package]] 1174 | name = "rand_core" 1175 | version = "0.6.3" 1176 | source = "registry+https://github.com/rust-lang/crates.io-index" 1177 | checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7" 1178 | dependencies = [ 1179 | "getrandom", 1180 | ] 1181 | 1182 | [[package]] 1183 | name = "redox_syscall" 1184 | version = "0.2.10" 1185 | source = "registry+https://github.com/rust-lang/crates.io-index" 1186 | checksum = "8383f39639269cde97d255a32bdb68c047337295414940c68bdd30c2e13203ff" 1187 | dependencies = [ 1188 | "bitflags", 1189 | ] 1190 | 1191 | [[package]] 1192 | name = "regex" 1193 | version = "1.5.5" 1194 | source = "registry+https://github.com/rust-lang/crates.io-index" 1195 | checksum = "1a11647b6b25ff05a515cb92c365cec08801e83423a235b51e231e1808747286" 1196 | dependencies = [ 1197 | "aho-corasick", 1198 | "memchr", 1199 | "regex-syntax", 1200 | ] 1201 | 1202 | [[package]] 1203 | name = "regex-syntax" 1204 | version = "0.6.25" 1205 | source = "registry+https://github.com/rust-lang/crates.io-index" 1206 | checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b" 1207 | 1208 | [[package]] 1209 | name = "ring" 1210 | version = "0.16.20" 1211 | source = "registry+https://github.com/rust-lang/crates.io-index" 1212 | checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc" 1213 | dependencies = [ 1214 | "cc", 1215 | "libc", 1216 | "once_cell", 1217 | "spin", 1218 | "untrusted", 1219 | "web-sys", 1220 | "winapi", 1221 | ] 1222 | 1223 | [[package]] 1224 | name = "rustc_version" 1225 | version = "0.4.0" 1226 | source = "registry+https://github.com/rust-lang/crates.io-index" 1227 | checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" 1228 | dependencies = [ 1229 | "semver", 1230 | ] 1231 | 1232 | [[package]] 1233 | name = "rustls" 1234 | version = "0.19.1" 1235 | source = "registry+https://github.com/rust-lang/crates.io-index" 1236 | checksum = "35edb675feee39aec9c99fa5ff985081995a06d594114ae14cbe797ad7b7a6d7" 1237 | dependencies = [ 1238 | "base64", 1239 | "log", 1240 | "ring", 1241 | "sct", 1242 | "webpki", 1243 | ] 1244 | 1245 | [[package]] 1246 | name = "rustls-native-certs" 1247 | version = "0.5.0" 1248 | source = "registry+https://github.com/rust-lang/crates.io-index" 1249 | checksum = "5a07b7c1885bd8ed3831c289b7870b13ef46fe0e856d288c30d9cc17d75a2092" 1250 | dependencies = [ 1251 | "openssl-probe", 1252 | "rustls", 1253 | "schannel", 1254 | "security-framework", 1255 | ] 1256 | 1257 | [[package]] 1258 | name = "rustversion" 1259 | version = "1.0.6" 1260 | source = "registry+https://github.com/rust-lang/crates.io-index" 1261 | checksum = "f2cc38e8fa666e2de3c4aba7edeb5ffc5246c1c2ed0e3d17e560aeeba736b23f" 1262 | 1263 | [[package]] 1264 | name = "ryu" 1265 | version = "1.0.9" 1266 | source = "registry+https://github.com/rust-lang/crates.io-index" 1267 | checksum = "73b4b750c782965c211b42f022f59af1fbceabdd026623714f104152f1ec149f" 1268 | 1269 | [[package]] 1270 | name = "s3d" 1271 | version = "0.0.1" 1272 | dependencies = [ 1273 | "anyhow", 1274 | "async-trait", 1275 | "aws-config", 1276 | "aws-endpoint", 1277 | "aws-http", 1278 | "aws-sdk-s3", 1279 | "aws-sig-auth", 1280 | "aws-sigv4", 1281 | "aws-smithy-async", 1282 | "aws-smithy-client", 1283 | "aws-smithy-http", 1284 | "aws-smithy-http-server", 1285 | "aws-smithy-http-tower", 1286 | "aws-smithy-types", 1287 | "aws-smithy-xml", 1288 | "aws-types", 1289 | "base64", 1290 | "bytes", 1291 | "chrono", 1292 | "clap", 1293 | "clap_complete", 1294 | "env_logger", 1295 | "envy", 1296 | "fuser", 1297 | "hyper", 1298 | "lazy_static", 1299 | "libc", 1300 | "log", 1301 | "paste", 1302 | "proc-macro2", 1303 | "quote", 1304 | "s3d-smithy-codegen-server-s3", 1305 | "serde", 1306 | "serde_json", 1307 | "serde_yaml", 1308 | "syn", 1309 | "tokio", 1310 | "tokio-stream", 1311 | "tower", 1312 | "url", 1313 | "urlencoding", 1314 | "uuid", 1315 | ] 1316 | 1317 | [[package]] 1318 | name = "s3d-smithy-codegen-server-s3" 1319 | version = "0.0.1" 1320 | dependencies = [ 1321 | "async-trait", 1322 | "aws-smithy-http", 1323 | "aws-smithy-http-server", 1324 | "aws-smithy-types", 1325 | "aws-smithy-xml", 1326 | "futures-util", 1327 | "http", 1328 | "hyper", 1329 | "nom", 1330 | "percent-encoding", 1331 | "pin-project-lite", 1332 | "serde_urlencoded", 1333 | "tower", 1334 | ] 1335 | 1336 | [[package]] 1337 | name = "schannel" 1338 | version = "0.1.19" 1339 | source = "registry+https://github.com/rust-lang/crates.io-index" 1340 | checksum = "8f05ba609c234e60bee0d547fe94a4c7e9da733d1c962cf6e59efa4cd9c8bc75" 1341 | dependencies = [ 1342 | "lazy_static", 1343 | "winapi", 1344 | ] 1345 | 1346 | [[package]] 1347 | name = "scopeguard" 1348 | version = "1.1.0" 1349 | source = "registry+https://github.com/rust-lang/crates.io-index" 1350 | checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" 1351 | 1352 | [[package]] 1353 | name = "sct" 1354 | version = "0.6.1" 1355 | source = "registry+https://github.com/rust-lang/crates.io-index" 1356 | checksum = "b362b83898e0e69f38515b82ee15aa80636befe47c3b6d3d89a911e78fc228ce" 1357 | dependencies = [ 1358 | "ring", 1359 | "untrusted", 1360 | ] 1361 | 1362 | [[package]] 1363 | name = "security-framework" 1364 | version = "2.6.1" 1365 | source = "registry+https://github.com/rust-lang/crates.io-index" 1366 | checksum = "2dc14f172faf8a0194a3aded622712b0de276821addc574fa54fc0a1167e10dc" 1367 | dependencies = [ 1368 | "bitflags", 1369 | "core-foundation", 1370 | "core-foundation-sys", 1371 | "libc", 1372 | "security-framework-sys", 1373 | ] 1374 | 1375 | [[package]] 1376 | name = "security-framework-sys" 1377 | version = "2.6.1" 1378 | source = "registry+https://github.com/rust-lang/crates.io-index" 1379 | checksum = "0160a13a177a45bfb43ce71c01580998474f556ad854dcbca936dd2841a5c556" 1380 | dependencies = [ 1381 | "core-foundation-sys", 1382 | "libc", 1383 | ] 1384 | 1385 | [[package]] 1386 | name = "semver" 1387 | version = "1.0.6" 1388 | source = "registry+https://github.com/rust-lang/crates.io-index" 1389 | checksum = "a4a3381e03edd24287172047536f20cabde766e2cd3e65e6b00fb3af51c4f38d" 1390 | 1391 | [[package]] 1392 | name = "serde" 1393 | version = "1.0.137" 1394 | source = "registry+https://github.com/rust-lang/crates.io-index" 1395 | checksum = "61ea8d54c77f8315140a05f4c7237403bf38b72704d031543aa1d16abbf517d1" 1396 | dependencies = [ 1397 | "serde_derive", 1398 | ] 1399 | 1400 | [[package]] 1401 | name = "serde_derive" 1402 | version = "1.0.137" 1403 | source = "registry+https://github.com/rust-lang/crates.io-index" 1404 | checksum = "1f26faba0c3959972377d3b2d306ee9f71faee9714294e41bb777f83f88578be" 1405 | dependencies = [ 1406 | "proc-macro2", 1407 | "quote", 1408 | "syn", 1409 | ] 1410 | 1411 | [[package]] 1412 | name = "serde_json" 1413 | version = "1.0.81" 1414 | source = "registry+https://github.com/rust-lang/crates.io-index" 1415 | checksum = "9b7ce2b32a1aed03c558dc61a5cd328f15aff2dbc17daad8fb8af04d2100e15c" 1416 | dependencies = [ 1417 | "itoa", 1418 | "ryu", 1419 | "serde", 1420 | ] 1421 | 1422 | [[package]] 1423 | name = "serde_urlencoded" 1424 | version = "0.7.1" 1425 | source = "registry+https://github.com/rust-lang/crates.io-index" 1426 | checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" 1427 | dependencies = [ 1428 | "form_urlencoded", 1429 | "itoa", 1430 | "ryu", 1431 | "serde", 1432 | ] 1433 | 1434 | [[package]] 1435 | name = "serde_yaml" 1436 | version = "0.8.24" 1437 | source = "registry+https://github.com/rust-lang/crates.io-index" 1438 | checksum = "707d15895415db6628332b737c838b88c598522e4dc70647e59b72312924aebc" 1439 | dependencies = [ 1440 | "indexmap", 1441 | "ryu", 1442 | "serde", 1443 | "yaml-rust", 1444 | ] 1445 | 1446 | [[package]] 1447 | name = "signal-hook-registry" 1448 | version = "1.4.0" 1449 | source = "registry+https://github.com/rust-lang/crates.io-index" 1450 | checksum = "e51e73328dc4ac0c7ccbda3a494dfa03df1de2f46018127f60c693f2648455b0" 1451 | dependencies = [ 1452 | "libc", 1453 | ] 1454 | 1455 | [[package]] 1456 | name = "slab" 1457 | version = "0.4.5" 1458 | source = "registry+https://github.com/rust-lang/crates.io-index" 1459 | checksum = "9def91fd1e018fe007022791f865d0ccc9b3a0d5001e01aabb8b40e46000afb5" 1460 | 1461 | [[package]] 1462 | name = "smallvec" 1463 | version = "1.8.0" 1464 | source = "registry+https://github.com/rust-lang/crates.io-index" 1465 | checksum = "f2dd574626839106c320a323308629dcb1acfc96e32a8cba364ddc61ac23ee83" 1466 | 1467 | [[package]] 1468 | name = "socket2" 1469 | version = "0.4.4" 1470 | source = "registry+https://github.com/rust-lang/crates.io-index" 1471 | checksum = "66d72b759436ae32898a2af0a14218dbf55efde3feeb170eb623637db85ee1e0" 1472 | dependencies = [ 1473 | "libc", 1474 | "winapi", 1475 | ] 1476 | 1477 | [[package]] 1478 | name = "spin" 1479 | version = "0.5.2" 1480 | source = "registry+https://github.com/rust-lang/crates.io-index" 1481 | checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" 1482 | 1483 | [[package]] 1484 | name = "strsim" 1485 | version = "0.10.0" 1486 | source = "registry+https://github.com/rust-lang/crates.io-index" 1487 | checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" 1488 | 1489 | [[package]] 1490 | name = "strum_macros" 1491 | version = "0.24.0" 1492 | source = "registry+https://github.com/rust-lang/crates.io-index" 1493 | checksum = "6878079b17446e4d3eba6192bb0a2950d5b14f0ed8424b852310e5a94345d0ef" 1494 | dependencies = [ 1495 | "heck", 1496 | "proc-macro2", 1497 | "quote", 1498 | "rustversion", 1499 | "syn", 1500 | ] 1501 | 1502 | [[package]] 1503 | name = "syn" 1504 | version = "1.0.93" 1505 | source = "registry+https://github.com/rust-lang/crates.io-index" 1506 | checksum = "04066589568b72ec65f42d65a1a52436e954b168773148893c020269563decf2" 1507 | dependencies = [ 1508 | "proc-macro2", 1509 | "quote", 1510 | "unicode-xid", 1511 | ] 1512 | 1513 | [[package]] 1514 | name = "synstructure" 1515 | version = "0.12.6" 1516 | source = "registry+https://github.com/rust-lang/crates.io-index" 1517 | checksum = "f36bdaa60a83aca3921b5259d5400cbf5e90fc51931376a9bd4a0eb79aa7210f" 1518 | dependencies = [ 1519 | "proc-macro2", 1520 | "quote", 1521 | "syn", 1522 | "unicode-xid", 1523 | ] 1524 | 1525 | [[package]] 1526 | name = "termcolor" 1527 | version = "1.1.2" 1528 | source = "registry+https://github.com/rust-lang/crates.io-index" 1529 | checksum = "2dfed899f0eb03f32ee8c6a0aabdb8a7949659e3466561fc0adf54e26d88c5f4" 1530 | dependencies = [ 1531 | "winapi-util", 1532 | ] 1533 | 1534 | [[package]] 1535 | name = "textwrap" 1536 | version = "0.15.0" 1537 | source = "registry+https://github.com/rust-lang/crates.io-index" 1538 | checksum = "b1141d4d61095b28419e22cb0bbf02755f5e54e0526f97f1e3d1d160e60885fb" 1539 | 1540 | [[package]] 1541 | name = "thiserror" 1542 | version = "1.0.30" 1543 | source = "registry+https://github.com/rust-lang/crates.io-index" 1544 | checksum = "854babe52e4df1653706b98fcfc05843010039b406875930a70e4d9644e5c417" 1545 | dependencies = [ 1546 | "thiserror-impl", 1547 | ] 1548 | 1549 | [[package]] 1550 | name = "thiserror-impl" 1551 | version = "1.0.30" 1552 | source = "registry+https://github.com/rust-lang/crates.io-index" 1553 | checksum = "aa32fd3f627f367fe16f893e2597ae3c05020f8bba2666a4e6ea73d377e5714b" 1554 | dependencies = [ 1555 | "proc-macro2", 1556 | "quote", 1557 | "syn", 1558 | ] 1559 | 1560 | [[package]] 1561 | name = "time" 1562 | version = "0.1.44" 1563 | source = "registry+https://github.com/rust-lang/crates.io-index" 1564 | checksum = "6db9e6914ab8b1ae1c260a4ae7a49b6c5611b40328a735b21862567685e73255" 1565 | dependencies = [ 1566 | "libc", 1567 | "wasi 0.10.0+wasi-snapshot-preview1", 1568 | "winapi", 1569 | ] 1570 | 1571 | [[package]] 1572 | name = "time" 1573 | version = "0.3.7" 1574 | source = "registry+https://github.com/rust-lang/crates.io-index" 1575 | checksum = "004cbc98f30fa233c61a38bc77e96a9106e65c88f2d3bef182ae952027e5753d" 1576 | dependencies = [ 1577 | "libc", 1578 | "num_threads", 1579 | ] 1580 | 1581 | [[package]] 1582 | name = "tinyvec" 1583 | version = "1.5.1" 1584 | source = "registry+https://github.com/rust-lang/crates.io-index" 1585 | checksum = "2c1c1d5a42b6245520c249549ec267180beaffcc0615401ac8e31853d4b6d8d2" 1586 | dependencies = [ 1587 | "tinyvec_macros", 1588 | ] 1589 | 1590 | [[package]] 1591 | name = "tinyvec_macros" 1592 | version = "0.1.0" 1593 | source = "registry+https://github.com/rust-lang/crates.io-index" 1594 | checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" 1595 | 1596 | [[package]] 1597 | name = "tokio" 1598 | version = "1.18.2" 1599 | source = "registry+https://github.com/rust-lang/crates.io-index" 1600 | checksum = "4903bf0427cf68dddd5aa6a93220756f8be0c34fcfa9f5e6191e103e15a31395" 1601 | dependencies = [ 1602 | "bytes", 1603 | "libc", 1604 | "memchr", 1605 | "mio", 1606 | "num_cpus", 1607 | "once_cell", 1608 | "parking_lot", 1609 | "pin-project-lite", 1610 | "signal-hook-registry", 1611 | "socket2", 1612 | "tokio-macros", 1613 | "winapi", 1614 | ] 1615 | 1616 | [[package]] 1617 | name = "tokio-macros" 1618 | version = "1.7.0" 1619 | source = "registry+https://github.com/rust-lang/crates.io-index" 1620 | checksum = "b557f72f448c511a979e2564e55d74e6c4432fc96ff4f6241bc6bded342643b7" 1621 | dependencies = [ 1622 | "proc-macro2", 1623 | "quote", 1624 | "syn", 1625 | ] 1626 | 1627 | [[package]] 1628 | name = "tokio-rustls" 1629 | version = "0.22.0" 1630 | source = "registry+https://github.com/rust-lang/crates.io-index" 1631 | checksum = "bc6844de72e57df1980054b38be3a9f4702aba4858be64dd700181a8a6d0e1b6" 1632 | dependencies = [ 1633 | "rustls", 1634 | "tokio", 1635 | "webpki", 1636 | ] 1637 | 1638 | [[package]] 1639 | name = "tokio-stream" 1640 | version = "0.1.8" 1641 | source = "registry+https://github.com/rust-lang/crates.io-index" 1642 | checksum = "50145484efff8818b5ccd256697f36863f587da82cf8b409c53adf1e840798e3" 1643 | dependencies = [ 1644 | "futures-core", 1645 | "pin-project-lite", 1646 | "tokio", 1647 | ] 1648 | 1649 | [[package]] 1650 | name = "tokio-util" 1651 | version = "0.6.9" 1652 | source = "registry+https://github.com/rust-lang/crates.io-index" 1653 | checksum = "9e99e1983e5d376cd8eb4b66604d2e99e79f5bd988c3055891dcd8c9e2604cc0" 1654 | dependencies = [ 1655 | "bytes", 1656 | "futures-core", 1657 | "futures-sink", 1658 | "log", 1659 | "pin-project-lite", 1660 | "tokio", 1661 | ] 1662 | 1663 | [[package]] 1664 | name = "tokio-util" 1665 | version = "0.7.0" 1666 | source = "registry+https://github.com/rust-lang/crates.io-index" 1667 | checksum = "64910e1b9c1901aaf5375561e35b9c057d95ff41a44ede043a03e09279eabaf1" 1668 | dependencies = [ 1669 | "bytes", 1670 | "futures-core", 1671 | "futures-sink", 1672 | "log", 1673 | "pin-project-lite", 1674 | "tokio", 1675 | ] 1676 | 1677 | [[package]] 1678 | name = "tower" 1679 | version = "0.4.12" 1680 | source = "registry+https://github.com/rust-lang/crates.io-index" 1681 | checksum = "9a89fd63ad6adf737582df5db40d286574513c69a11dac5214dc3b5603d6713e" 1682 | dependencies = [ 1683 | "futures-core", 1684 | "futures-util", 1685 | "hdrhistogram", 1686 | "indexmap", 1687 | "pin-project", 1688 | "pin-project-lite", 1689 | "rand", 1690 | "slab", 1691 | "tokio", 1692 | "tokio-util 0.7.0", 1693 | "tower-layer", 1694 | "tower-service", 1695 | "tracing", 1696 | ] 1697 | 1698 | [[package]] 1699 | name = "tower-http" 1700 | version = "0.2.3" 1701 | source = "registry+https://github.com/rust-lang/crates.io-index" 1702 | checksum = "2bb284cac1883d54083a0edbdc9cabf931dfed87455f8c7266c01ece6394a43a" 1703 | dependencies = [ 1704 | "bitflags", 1705 | "bytes", 1706 | "futures-core", 1707 | "futures-util", 1708 | "http", 1709 | "http-body", 1710 | "http-range-header", 1711 | "pin-project-lite", 1712 | "tower-layer", 1713 | "tower-service", 1714 | ] 1715 | 1716 | [[package]] 1717 | name = "tower-layer" 1718 | version = "0.3.1" 1719 | source = "registry+https://github.com/rust-lang/crates.io-index" 1720 | checksum = "343bc9466d3fe6b0f960ef45960509f84480bf4fd96f92901afe7ff3df9d3a62" 1721 | 1722 | [[package]] 1723 | name = "tower-service" 1724 | version = "0.3.1" 1725 | source = "registry+https://github.com/rust-lang/crates.io-index" 1726 | checksum = "360dfd1d6d30e05fda32ace2c8c70e9c0a9da713275777f5a4dbb8a1893930c6" 1727 | 1728 | [[package]] 1729 | name = "tracing" 1730 | version = "0.1.31" 1731 | source = "registry+https://github.com/rust-lang/crates.io-index" 1732 | checksum = "f6c650a8ef0cd2dd93736f033d21cbd1224c5a967aa0c258d00fcf7dafef9b9f" 1733 | dependencies = [ 1734 | "cfg-if", 1735 | "log", 1736 | "pin-project-lite", 1737 | "tracing-attributes", 1738 | "tracing-core", 1739 | ] 1740 | 1741 | [[package]] 1742 | name = "tracing-attributes" 1743 | version = "0.1.19" 1744 | source = "registry+https://github.com/rust-lang/crates.io-index" 1745 | checksum = "8276d9a4a3a558d7b7ad5303ad50b53d58264641b82914b7ada36bd762e7a716" 1746 | dependencies = [ 1747 | "proc-macro2", 1748 | "quote", 1749 | "syn", 1750 | ] 1751 | 1752 | [[package]] 1753 | name = "tracing-core" 1754 | version = "0.1.22" 1755 | source = "registry+https://github.com/rust-lang/crates.io-index" 1756 | checksum = "03cfcb51380632a72d3111cb8d3447a8d908e577d31beeac006f836383d29a23" 1757 | dependencies = [ 1758 | "lazy_static", 1759 | ] 1760 | 1761 | [[package]] 1762 | name = "try-lock" 1763 | version = "0.2.3" 1764 | source = "registry+https://github.com/rust-lang/crates.io-index" 1765 | checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642" 1766 | 1767 | [[package]] 1768 | name = "unicode-bidi" 1769 | version = "0.3.7" 1770 | source = "registry+https://github.com/rust-lang/crates.io-index" 1771 | checksum = "1a01404663e3db436ed2746d9fefef640d868edae3cceb81c3b8d5732fda678f" 1772 | 1773 | [[package]] 1774 | name = "unicode-normalization" 1775 | version = "0.1.19" 1776 | source = "registry+https://github.com/rust-lang/crates.io-index" 1777 | checksum = "d54590932941a9e9266f0832deed84ebe1bf2e4c9e4a3554d393d18f5e854bf9" 1778 | dependencies = [ 1779 | "tinyvec", 1780 | ] 1781 | 1782 | [[package]] 1783 | name = "unicode-xid" 1784 | version = "0.2.2" 1785 | source = "registry+https://github.com/rust-lang/crates.io-index" 1786 | checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" 1787 | 1788 | [[package]] 1789 | name = "untrusted" 1790 | version = "0.7.1" 1791 | source = "registry+https://github.com/rust-lang/crates.io-index" 1792 | checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" 1793 | 1794 | [[package]] 1795 | name = "url" 1796 | version = "2.2.2" 1797 | source = "registry+https://github.com/rust-lang/crates.io-index" 1798 | checksum = "a507c383b2d33b5fc35d1861e77e6b383d158b2da5e14fe51b83dfedf6fd578c" 1799 | dependencies = [ 1800 | "form_urlencoded", 1801 | "idna", 1802 | "matches", 1803 | "percent-encoding", 1804 | ] 1805 | 1806 | [[package]] 1807 | name = "urlencoding" 1808 | version = "2.1.0" 1809 | source = "registry+https://github.com/rust-lang/crates.io-index" 1810 | checksum = "68b90931029ab9b034b300b797048cf23723400aa757e8a2bfb9d748102f9821" 1811 | 1812 | [[package]] 1813 | name = "users" 1814 | version = "0.11.0" 1815 | source = "registry+https://github.com/rust-lang/crates.io-index" 1816 | checksum = "24cc0f6d6f267b73e5a2cadf007ba8f9bc39c6a6f9666f8cf25ea809a153b032" 1817 | dependencies = [ 1818 | "libc", 1819 | "log", 1820 | ] 1821 | 1822 | [[package]] 1823 | name = "uuid" 1824 | version = "1.0.0" 1825 | source = "registry+https://github.com/rust-lang/crates.io-index" 1826 | checksum = "8cfcd319456c4d6ea10087ed423473267e1a071f3bc0aa89f80d60997843c6f0" 1827 | dependencies = [ 1828 | "getrandom", 1829 | ] 1830 | 1831 | [[package]] 1832 | name = "version_check" 1833 | version = "0.9.4" 1834 | source = "registry+https://github.com/rust-lang/crates.io-index" 1835 | checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" 1836 | 1837 | [[package]] 1838 | name = "want" 1839 | version = "0.3.0" 1840 | source = "registry+https://github.com/rust-lang/crates.io-index" 1841 | checksum = "1ce8a968cb1cd110d136ff8b819a556d6fb6d919363c61534f6860c7eb172ba0" 1842 | dependencies = [ 1843 | "log", 1844 | "try-lock", 1845 | ] 1846 | 1847 | [[package]] 1848 | name = "wasi" 1849 | version = "0.10.0+wasi-snapshot-preview1" 1850 | source = "registry+https://github.com/rust-lang/crates.io-index" 1851 | checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f" 1852 | 1853 | [[package]] 1854 | name = "wasi" 1855 | version = "0.11.0+wasi-snapshot-preview1" 1856 | source = "registry+https://github.com/rust-lang/crates.io-index" 1857 | checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" 1858 | 1859 | [[package]] 1860 | name = "wasm-bindgen" 1861 | version = "0.2.79" 1862 | source = "registry+https://github.com/rust-lang/crates.io-index" 1863 | checksum = "25f1af7423d8588a3d840681122e72e6a24ddbcb3f0ec385cac0d12d24256c06" 1864 | dependencies = [ 1865 | "cfg-if", 1866 | "wasm-bindgen-macro", 1867 | ] 1868 | 1869 | [[package]] 1870 | name = "wasm-bindgen-backend" 1871 | version = "0.2.79" 1872 | source = "registry+https://github.com/rust-lang/crates.io-index" 1873 | checksum = "8b21c0df030f5a177f3cba22e9bc4322695ec43e7257d865302900290bcdedca" 1874 | dependencies = [ 1875 | "bumpalo", 1876 | "lazy_static", 1877 | "log", 1878 | "proc-macro2", 1879 | "quote", 1880 | "syn", 1881 | "wasm-bindgen-shared", 1882 | ] 1883 | 1884 | [[package]] 1885 | name = "wasm-bindgen-macro" 1886 | version = "0.2.79" 1887 | source = "registry+https://github.com/rust-lang/crates.io-index" 1888 | checksum = "2f4203d69e40a52ee523b2529a773d5ffc1dc0071801c87b3d270b471b80ed01" 1889 | dependencies = [ 1890 | "quote", 1891 | "wasm-bindgen-macro-support", 1892 | ] 1893 | 1894 | [[package]] 1895 | name = "wasm-bindgen-macro-support" 1896 | version = "0.2.79" 1897 | source = "registry+https://github.com/rust-lang/crates.io-index" 1898 | checksum = "bfa8a30d46208db204854cadbb5d4baf5fcf8071ba5bf48190c3e59937962ebc" 1899 | dependencies = [ 1900 | "proc-macro2", 1901 | "quote", 1902 | "syn", 1903 | "wasm-bindgen-backend", 1904 | "wasm-bindgen-shared", 1905 | ] 1906 | 1907 | [[package]] 1908 | name = "wasm-bindgen-shared" 1909 | version = "0.2.79" 1910 | source = "registry+https://github.com/rust-lang/crates.io-index" 1911 | checksum = "3d958d035c4438e28c70e4321a2911302f10135ce78a9c7834c0cab4123d06a2" 1912 | 1913 | [[package]] 1914 | name = "web-sys" 1915 | version = "0.3.56" 1916 | source = "registry+https://github.com/rust-lang/crates.io-index" 1917 | checksum = "c060b319f29dd25724f09a2ba1418f142f539b2be99fbf4d2d5a8f7330afb8eb" 1918 | dependencies = [ 1919 | "js-sys", 1920 | "wasm-bindgen", 1921 | ] 1922 | 1923 | [[package]] 1924 | name = "webpki" 1925 | version = "0.21.4" 1926 | source = "registry+https://github.com/rust-lang/crates.io-index" 1927 | checksum = "b8e38c0608262c46d4a56202ebabdeb094cef7e560ca7a226c6bf055188aa4ea" 1928 | dependencies = [ 1929 | "ring", 1930 | "untrusted", 1931 | ] 1932 | 1933 | [[package]] 1934 | name = "winapi" 1935 | version = "0.3.9" 1936 | source = "registry+https://github.com/rust-lang/crates.io-index" 1937 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 1938 | dependencies = [ 1939 | "winapi-i686-pc-windows-gnu", 1940 | "winapi-x86_64-pc-windows-gnu", 1941 | ] 1942 | 1943 | [[package]] 1944 | name = "winapi-i686-pc-windows-gnu" 1945 | version = "0.4.0" 1946 | source = "registry+https://github.com/rust-lang/crates.io-index" 1947 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 1948 | 1949 | [[package]] 1950 | name = "winapi-util" 1951 | version = "0.1.5" 1952 | source = "registry+https://github.com/rust-lang/crates.io-index" 1953 | checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" 1954 | dependencies = [ 1955 | "winapi", 1956 | ] 1957 | 1958 | [[package]] 1959 | name = "winapi-x86_64-pc-windows-gnu" 1960 | version = "0.4.0" 1961 | source = "registry+https://github.com/rust-lang/crates.io-index" 1962 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 1963 | 1964 | [[package]] 1965 | name = "windows-sys" 1966 | version = "0.32.0" 1967 | source = "registry+https://github.com/rust-lang/crates.io-index" 1968 | checksum = "3df6e476185f92a12c072be4a189a0210dcdcf512a1891d6dff9edb874deadc6" 1969 | dependencies = [ 1970 | "windows_aarch64_msvc", 1971 | "windows_i686_gnu", 1972 | "windows_i686_msvc", 1973 | "windows_x86_64_gnu", 1974 | "windows_x86_64_msvc", 1975 | ] 1976 | 1977 | [[package]] 1978 | name = "windows_aarch64_msvc" 1979 | version = "0.32.0" 1980 | source = "registry+https://github.com/rust-lang/crates.io-index" 1981 | checksum = "d8e92753b1c443191654ec532f14c199742964a061be25d77d7a96f09db20bf5" 1982 | 1983 | [[package]] 1984 | name = "windows_i686_gnu" 1985 | version = "0.32.0" 1986 | source = "registry+https://github.com/rust-lang/crates.io-index" 1987 | checksum = "6a711c68811799e017b6038e0922cb27a5e2f43a2ddb609fe0b6f3eeda9de615" 1988 | 1989 | [[package]] 1990 | name = "windows_i686_msvc" 1991 | version = "0.32.0" 1992 | source = "registry+https://github.com/rust-lang/crates.io-index" 1993 | checksum = "146c11bb1a02615db74680b32a68e2d61f553cc24c4eb5b4ca10311740e44172" 1994 | 1995 | [[package]] 1996 | name = "windows_x86_64_gnu" 1997 | version = "0.32.0" 1998 | source = "registry+https://github.com/rust-lang/crates.io-index" 1999 | checksum = "c912b12f7454c6620635bbff3450962753834be2a594819bd5e945af18ec64bc" 2000 | 2001 | [[package]] 2002 | name = "windows_x86_64_msvc" 2003 | version = "0.32.0" 2004 | source = "registry+https://github.com/rust-lang/crates.io-index" 2005 | checksum = "504a2476202769977a040c6364301a3f65d0cc9e3fb08600b2bda150a0488316" 2006 | 2007 | [[package]] 2008 | name = "xmlparser" 2009 | version = "0.13.3" 2010 | source = "registry+https://github.com/rust-lang/crates.io-index" 2011 | checksum = "114ba2b24d2167ef6d67d7d04c8cc86522b87f490025f39f0303b7db5bf5e3d8" 2012 | 2013 | [[package]] 2014 | name = "yaml-rust" 2015 | version = "0.4.5" 2016 | source = "registry+https://github.com/rust-lang/crates.io-index" 2017 | checksum = "56c1936c4cc7a1c9ab21a1ebb602eb942ba868cbd44a99cb7cdc5892335e1c85" 2018 | dependencies = [ 2019 | "linked-hash-map", 2020 | ] 2021 | 2022 | [[package]] 2023 | name = "zerocopy" 2024 | version = "0.6.1" 2025 | source = "registry+https://github.com/rust-lang/crates.io-index" 2026 | checksum = "332f188cc1bcf1fe1064b8c58d150f497e697f49774aa846f2dc949d9a25f236" 2027 | dependencies = [ 2028 | "byteorder", 2029 | "zerocopy-derive", 2030 | ] 2031 | 2032 | [[package]] 2033 | name = "zerocopy-derive" 2034 | version = "0.3.1" 2035 | source = "registry+https://github.com/rust-lang/crates.io-index" 2036 | checksum = "a0fbc82b82efe24da867ee52e015e58178684bd9dd64c34e66bdf21da2582a9f" 2037 | dependencies = [ 2038 | "proc-macro2", 2039 | "syn", 2040 | "synstructure", 2041 | ] 2042 | 2043 | [[package]] 2044 | name = "zeroize" 2045 | version = "1.5.2" 2046 | source = "registry+https://github.com/rust-lang/crates.io-index" 2047 | checksum = "7c88870063c39ee00ec285a2f8d6a966e5b6fb2becc4e8dac77ed0d370ed6006" 2048 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | ## https://doc.rust-lang.org/cargo/reference/manifest.html 2 | 3 | [package] 4 | 5 | name = "s3d" 6 | version = "0.0.1" 7 | authors = ["guymguym"] 8 | edition = "2021" 9 | description = "s3d is a daemon for data access using S3 API. A modern cousin of nfsd, ftpd, httpd, etc. It is designed to be simple, tiny, blazing fast, and portable in order to fit in a variety of environments from developer machines, containers, kubernetes, edge devices, etc." 10 | homepage = "https://s3d.rs" 11 | repository = "https://github.com/s3d-rs/s3d" 12 | license = "Apache-2.0" 13 | keywords = ["s3", "edge", "object", "storage", "bucket"] 14 | categories = ["database-implementations", "filesystem", "web-programming::http-server"] 15 | 16 | [features] 17 | 18 | # enable features by default 19 | default = ["fuse"] 20 | # The fuse feature requires `sudo apt-get install -y fuse libfuse-dev pkg-config` or equivalent 21 | # when enabled it requires the optional dependency on crate `fuser`. 22 | fuse = ["fuser"] 23 | 24 | [badges] 25 | 26 | maintenance = { status = "experimental" } 27 | 28 | [profile.dev] 29 | 30 | # When running in development we want to abort on panic 31 | # to make it easier to debug. Use `export RUST_BACKTRACE=1` to print stack traces. 32 | panic = 'abort' 33 | 34 | [build-dependencies] 35 | 36 | syn = "1.0.93" 37 | quote = "1.0.18" 38 | proc-macro2 = "1.0.38" 39 | serde = { version = "1.0.137", features = ["derive"] } 40 | serde_json = "1.0.81" 41 | 42 | [dependencies] 43 | 44 | ## Runtime crates 45 | 46 | hyper = { version = "0.14.18", features = ["full"] } 47 | tower = { version = "0.4.12", features = ["full"] } 48 | tokio = { version = "1.18.2", features = ["full"] } 49 | tokio-stream = "0.1.8" 50 | libc = "0.2.125" 51 | log = "0.4.17" 52 | env_logger = "0.9.0" 53 | 54 | ## Rust related crates 55 | 56 | quote = "1.0.18" 57 | paste = "1.0.7" 58 | anyhow = "1.0.57" 59 | async-trait = "0.1.53" 60 | proc-macro2 = "1.0.38" 61 | lazy_static = "1.4.0" 62 | 63 | ## General purpose crates 64 | 65 | bytes = "1.1.0" 66 | base64 = "0.13.0" 67 | chrono = "0.4.19" 68 | url = "2.2.2" 69 | urlencoding = "2.1.0" 70 | uuid = { version = "1.0.0", features = ["v4"] } 71 | clap = { version = "3.1.18", features = ["derive", "cargo"] } 72 | clap_complete = "3.1.4" 73 | 74 | ## Encoding/decoding crates 75 | 76 | serde = { version = "1.0.137", features = ["derive"] } 77 | serde_yaml = "0.8.24" 78 | envy = "0.4.2" 79 | #serde_json = "1.0.81" 80 | #quick-xml = "0.22.0" # seems to be more popular than other serde_xml* crates 81 | 82 | ## Optional features crates 83 | 84 | fuser = { version = "0.11.0", optional = true } 85 | 86 | ## aws smithy crates 87 | 88 | aws-smithy-client = { version = "0.41.0", features = ["client-hyper"] } 89 | aws-smithy-async = { version = "0.41.0", features = ["rt-tokio"] } 90 | aws-smithy-types = "0.41.0" 91 | aws-smithy-xml = "0.41.0" 92 | aws-smithy-http = "0.41.0" 93 | aws-smithy-http-tower = "0.41.0" 94 | aws-smithy-http-server = "0.41.0" 95 | 96 | aws-http = "0.11.0" 97 | aws-types = "0.11.0" 98 | aws-sigv4 = "0.11.0" 99 | aws-sig-auth = "0.11.0" 100 | aws-endpoint = "0.11.0" 101 | aws-config = "0.11.0" 102 | aws-sdk-s3 = "0.11.0" 103 | 104 | s3d-smithy-codegen-server-s3 = "0.0.1" 105 | 106 | ## patching to use local overides of the smithy-rs crates 107 | ## can also be added in another file `.cargo/config.toml` 108 | ## see https://doc.rust-lang.org/cargo/reference/overriding-dependencies.html 109 | 110 | [patch.crates-io] 111 | 112 | aws-smithy-client = { path = "smithy-rs/s3d/build/crates/aws-smithy-client" } 113 | aws-smithy-async = { path = "smithy-rs/s3d/build/crates/aws-smithy-async" } 114 | aws-smithy-types = { path = "smithy-rs/s3d/build/crates/aws-smithy-types" } 115 | aws-smithy-xml = { path = "smithy-rs/s3d/build/crates/aws-smithy-xml" } 116 | aws-smithy-http = { path = "smithy-rs/s3d/build/crates/aws-smithy-http" } 117 | aws-smithy-http-tower = { path = "smithy-rs/s3d/build/crates/aws-smithy-http-tower" } 118 | aws-smithy-http-server = { path = "smithy-rs/s3d/build/crates/aws-smithy-http-server" } 119 | aws-http = { path = "smithy-rs/s3d/build/crates/aws-http" } 120 | aws-types = { path = "smithy-rs/s3d/build/crates/aws-types" } 121 | aws-sigv4 = { path = "smithy-rs/s3d/build/crates/aws-sigv4" } 122 | aws-sig-auth = { path = "smithy-rs/s3d/build/crates/aws-sig-auth" } 123 | aws-endpoint = { path = "smithy-rs/s3d/build/crates/aws-endpoint" } 124 | aws-config = { path = "smithy-rs/s3d/build/crates/aws-config" } 125 | aws-sdk-s3 = { path = "smithy-rs/s3d/build/crates/s3" } 126 | s3d-smithy-codegen-server-s3 = { path = "smithy-rs/s3d/build/crates/s3d-smithy-codegen-server-s3" } 127 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # https://github.com/skerkour/kerkour.com/blob/main/2021/2021_04_06_rust_minimal_docker_image/myip/Dockerfile.scratch 2 | 3 | #################################################################################################### 4 | ## Builder image 5 | #################################################################################################### 6 | 7 | FROM rust:latest AS builder 8 | WORKDIR /s3d 9 | 10 | RUN rustup target add x86_64-unknown-linux-musl 11 | RUN apt update && apt install -y musl-tools musl-dev 12 | RUN update-ca-certificates 13 | 14 | # Create user 15 | ENV USER=s3d 16 | ENV UID=10001 17 | RUN adduser \ 18 | --uid "${UID}" \ 19 | --home "/s3d" \ 20 | --shell "/sbin/nologin" \ 21 | --gecos "" \ 22 | --no-create-home \ 23 | --disabled-password \ 24 | "${USER}" 25 | 26 | COPY ./ . 27 | ENV CARGO_BUILD_TARGET="x86_64-unknown-linux-musl" 28 | RUN make RELEASE=1 29 | 30 | #################################################################################################### 31 | ## Final image 32 | #################################################################################################### 33 | 34 | FROM scratch 35 | WORKDIR /s3d 36 | 37 | # Copy files from builder image 38 | COPY --from=builder /etc/passwd /etc/passwd 39 | COPY --from=builder /etc/group /etc/group 40 | COPY --from=builder /s3d/target/x86_64-unknown-linux-musl/release/s3d ./ 41 | COPY --from=builder /s3d/target/x86_64-unknown-linux-musl/release/s3 ./ 42 | 43 | # Use an unprivileged user 44 | USER s3d:s3d 45 | 46 | CMD ["/s3d/s3d"] 47 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright 2021 s3d Authors 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | #--------------# 2 | # s3d Makefile # 3 | #--------------# 4 | 5 | MODE := debug 6 | ifeq ($(RELEASE),1) 7 | MODE = release 8 | CARGO_BUILD_FLAGS += --release 9 | endif 10 | ifdef VERBOSE 11 | CARGO_BUILD_FLAGS += -v 12 | endif 13 | LOG = @echo "\nMakefile: 🥷 " 14 | LOG_START = @echo "\nMakefile: 🥷 $@ start ...\n" 15 | LOG_DONE = @echo "\nMakefile: ✅ $@ done\n" 16 | TIMER := time 17 | IMAGE := s3d:dev 18 | IMAGE_BUILDER := docker 19 | CODEGEN_CRATES_DIR := smithy-rs/s3d/build/crates 20 | CODEGEN_SERVER_S3 := $(CODEGEN_CRATES_DIR)/s3d-smithy-codegen-server-s3 21 | CODEGEN_SDK_DIR := smithy-rs/aws/sdk/build/aws-sdk/sdk 22 | CARGO_BUILD_CMD := $(TIMER) cargo build $(CARGO_BUILD_FLAGS) 23 | CARGO_TEST_CMD := $(TIMER) cargo test $(CARGO_BUILD_FLAGS) # using same flags as build for now 24 | 25 | #--------------------------------------------# 26 | # build - binaries only, the default for dev # 27 | #--------------------------------------------# 28 | 29 | build: codegen_init_once 30 | $(LOG_START) 31 | $(CARGO_BUILD_CMD) 32 | $(LOG) "Built binary: target/$(MODE)/s3" 33 | $(LOG) "Built binary: target/$(MODE)/s3d" 34 | $(LOG_DONE) 35 | .PHONY: build 36 | 37 | #------# 38 | # help # 39 | #------# 40 | 41 | help: 42 | @echo "" 43 | @echo "Makefile targets:" 44 | @echo "" 45 | @echo " build - (default) binaries only, the default for dev" 46 | @echo " all - codegen + build + test" 47 | @echo " codegen - generate code from smithy-rs" 48 | @echo " test - cargo test" 49 | @echo " image - build container image" 50 | @echo " clean - start fresh" 51 | @echo " env - prints shell commands for dev" 52 | @echo "" 53 | .PHONY: help 54 | 55 | 56 | #------------------------------# 57 | # all - codegen + build + test # 58 | #------------------------------# 59 | 60 | all: codegen build test 61 | $(LOG_DONE) 62 | .PHONY: all 63 | 64 | #-----------------------------------# 65 | # codegen - generate from smithy-rs # 66 | #-----------------------------------# 67 | 68 | codegen: submodules_init_once 69 | $(LOG_START) 70 | cd smithy-rs && $(TIMER) ./gradlew -i \ 71 | :rust-runtime:assemble \ 72 | :aws:sdk:assemble \ 73 | :s3d:assemble \ 74 | -Paws.services=+sts,+sso,+s3 \ 75 | -Ps3d.release=false 76 | @##################################### 77 | @## moving all crates to crates dir ## 78 | @##################################### 79 | rm -rf $(CODEGEN_CRATES_DIR) 80 | mkdir -p $(CODEGEN_CRATES_DIR) 81 | mv smithy-rs/rust-runtime/build/smithy-rs/rust-runtime/* $(CODEGEN_CRATES_DIR)/ 82 | mv $(CODEGEN_SDK_DIR)/aws-config $(CODEGEN_CRATES_DIR)/ 83 | mv $(CODEGEN_SDK_DIR)/aws-endpoint $(CODEGEN_CRATES_DIR)/ 84 | mv $(CODEGEN_SDK_DIR)/aws-http $(CODEGEN_CRATES_DIR)/ 85 | mv $(CODEGEN_SDK_DIR)/aws-sig-auth $(CODEGEN_CRATES_DIR)/ 86 | mv $(CODEGEN_SDK_DIR)/aws-sigv4 $(CODEGEN_CRATES_DIR)/ 87 | mv $(CODEGEN_SDK_DIR)/aws-types $(CODEGEN_CRATES_DIR)/ 88 | mv $(CODEGEN_SDK_DIR)/s3 $(CODEGEN_CRATES_DIR)/ 89 | mv $(CODEGEN_SDK_DIR)/sso $(CODEGEN_CRATES_DIR)/ 90 | mv $(CODEGEN_SDK_DIR)/sts $(CODEGEN_CRATES_DIR)/ 91 | mv smithy-rs/s3d/build/smithyprojections/s3d/s3/rust-server-codegen $(CODEGEN_SERVER_S3) 92 | $(LOG_DONE) 93 | .PHONY: codegen 94 | 95 | # CAUTION: 96 | # submodules target should NOT be used if you are making changes directly 97 | # on the smithy-rs submodule (which is useful for dual development), 98 | # because it will effectively `git reset --hard` on the submodule HEAD 99 | # and discard local commits and worktree changes. however, for most users 100 | # this is desired as they would not change the submodule directly. 101 | 102 | submodules: 103 | $(LOG_START) 104 | cd smithy-rs && git remote -v 105 | cd smithy-rs && git branch -avv 106 | git submodule status 107 | git submodule update --init 108 | git submodule status 109 | $(LOG_DONE) 110 | .PHONY: submodules 111 | 112 | # the "init_once" targets avoid rebuilding more than once when used as dep, 113 | # but we can still run the main targets unconditionally as needed. 114 | 115 | codegen_init_once: | $(CODEGEN_SERVER_S3) 116 | .PHONY: codegen_init_once 117 | 118 | submodules_init_once: | smithy-rs/README.md 119 | .PHONY: submodules_init_once 120 | 121 | $(CODEGEN_SERVER_S3): 122 | $(call LOG,call make codegen) 123 | $(TIMER) $(MAKE) codegen 124 | 125 | smithy-rs/README.md: 126 | $(call LOG,call make submodules) 127 | $(TIMER) $(MAKE) submodules 128 | 129 | #-------------------# 130 | # test - cargo test # 131 | #-------------------# 132 | 133 | test: 134 | $(LOG_START) 135 | @#### TODO - no tests yet !!! #### 136 | @# $(CARGO_TEST_CMD) 137 | @# cd $(CODEGEN_SERVER_S3) && $(CARGO_TEST_CMD) 138 | $(LOG_DONE) 139 | .PHONY: test 140 | 141 | #-------------------------------------# 142 | # image - containerization buildation # 143 | #-------------------------------------# 144 | 145 | image: 146 | $(LOG_START) 147 | $(TIMER) $(IMAGE_BUILDER) build . -t $(IMAGE) 148 | $(LOG_DONE) 149 | .PHONY: image 150 | 151 | #---------------------# 152 | # clean - start fresh # 153 | #---------------------# 154 | 155 | clean: 156 | $(LOG_START) 157 | cd smithy-rs && $(TIMER) ./gradlew clean 158 | $(TIMER) cargo clean 159 | $(LOG_DONE) 160 | .PHONY: clean 161 | 162 | 163 | #------------------------------------# 164 | # env - print shell commands for dev # 165 | #------------------------------------# 166 | 167 | env: 168 | @echo "alias s3=\"\$$PWD/target/$(MODE)/s3\";" 169 | @echo "alias s3d=\"\$$PWD/target/$(MODE)/s3d\";" 170 | @echo "alias aws3d='aws --endpoint http://localhost:33333';" 171 | @echo "# usage: eval \$$(make env)" 172 | .PHONY: env 173 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 16 | 17 | 18 | 19 | 20 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 |
33 |
34 |
35 | 36 | s3d 37 | 38 |
39 | 40 | # The S3 Daemon 41 | 42 | `s3d` is a daemon for data access using S3 API. A modern cousin of `nfsd`, `ftpd`, `httpd`, etc. It is designed to be simple, tiny, blazing fast, and portable in order to fit in a variety of environments from developer machines, containers, kubernetes, edge devices, etc. 43 | 44 | By default, `s3d` serves the S3 API as a gateway to a main remote S3 storage (AWS/compatible), with ultimate protocol compatiblity (based on the AWS SDK and Smithy API), it adds critical features needed by remote clients such as queueing, caching, and synching, in order to optimize performance, increase data availability, and service continuity for its clients. 45 | 46 | The need for a daemon running alongside applications emerges in Edge computing use cases, where data is stored and processed locally at the edge as it gets collected, while some of the data gets synced to and from a main data storage. Refer to [Wikipedia - Edge computing](https://en.wikipedia.org/wiki/Edge_computing): _"Edge computing is a distributed computing paradigm that brings computation and data storage closer to the sources of data. This is expected to improve response times and save bandwidth. ..."_. 47 | 48 | # Features 49 | 50 | Maturity legend: 🥉 - design phase, 🥈 - development phase, 🥇 - released. 51 | 52 | - 🥈   **S3 API** 53 | - Generated S3 protocol code with [awslabs/smithy-rs](https://github.com/awslabs/smithy-rs) which builds the AWS SDK for Rust. 54 | - Provides compatible parsing of API operations, inputs, outputs, errors, and the server skeleton. 55 | - 🥈   **Write Queue** 56 | - Writing new objects to local filesystem first to tolerate connection issues. 57 | - Pushing in the background to remote storage. 58 | - 🥉   **Read Cache** 59 | - Store cached and prefetched objects in local filesystem. 60 | - Reduce egress costs and latency of reads from remote storage. 61 | - 🥉   **Filters** 62 | - Fine control over which objects to include/exclude for upload/cache/sync. 63 | - Filter by bucket name, bucket tags, object keys (or prefixes), object tags, and object meta-data. 64 | - Optional integration with [OpenPolicyAgent (OPA)](https://www.openpolicyagent.org) for advanced filtering. 65 | - 🥉   **Sync Folder** 66 | - Continuous, bidirectional and background sync of local dirs to remote buckets. 67 | - This is a simple way to get data that begins as local files to the remote S3 storage. 68 | - 🥉   **Fuse Mount** 69 | - Virtual filesystem mountpoint provided by the daemon (see [kernel fuse docs](https://www.kernel.org/doc/html/latest/filesystems/fuse.html)). 70 | - The filesystem can be accessed normally for applications that do not use the S3 API. 71 | - 🥉   **Metrics** 72 | - Optional integration with [OpenTelemetry](https://opentelemetry.io). 73 | - Expose logging, metrics, and tracing of S3 operations and `s3d` features. 74 | 75 | # Docs 76 | 77 | - 🧑‍🚀   **[User guide](docs/user-guide.md)** - how to use features and configurations. 78 | - 🥷   **[Developer guide](docs/developer-guide.md)** - how to build and test. 79 | - 🧝   **[Architecture page](docs/architecture.md)** - designs of components, software and roadmap ideas. 80 | 81 | # Status 82 | 83 | 🧪🧪❕   **Experimental** \ 84 | 🧪🧪❕   \ 85 | 🧪🧪❕   This project is still in it's early days, which means it's a great time \ 86 | 🧪🧪❕   to affect its direction, and it welcomes contributions and open discussions. \ 87 | 🧪🧪❕   \ 88 | 🧪🧪❕   Keep in mind that all internal and external interfaces are considered unstable \ 89 | 🧪🧪❕   and might change without notice. 90 | 91 | # Getting Started 92 | 93 | Until the first releases are available, please refer to the [Developer guide](docs/developer-guide.md) for building `s3d` from source. 94 | 95 | Here are some commands to explore: 96 | 97 | ```bash 98 | make # build from source (see dev guide for prerequisites) 99 | eval $(make env) # defines aliases such as s3d -> build output binary 100 | aws configure list # make sure remote S3 credentials are configured 101 | s3d run # runs daemon (foreground) 102 | s3d status # check daemon status 103 | s3d status bucket/key # check bucket or object status 104 | s3d ls bucket/prefix # list buckets or objects 105 | s3d get bucket/key > file # get object data to stdout (meta-data to stderr) 106 | s3d put bucket/key < file # put object data from stdin 107 | s3d set bucket/key \ 108 | --tag s3d.upload=true \ 109 | --tag s3d.cache=pin # set tags for bucket or object to be used in filters 110 | s3d --help # show usage for command 111 | ``` 112 | 113 | # Project 114 | 115 | - [Github repo](https://github.com/s3d-rs/s3d) - The project lives here, set your options to get notified on releases, issues, etc. 116 | - [Discord chat](https://discord.com/channels/897764851580035072) - use this [invite link](https://discord.gg/kPWHDuCdhh) to join. 117 | - [Redhat-et](https://github.com/redhat-et) - this project was initiated at Red Hat Emerging Technologies. 118 | - [License](LICENSE) - Apache 2.0 119 | -------------------------------------------------------------------------------- /_config.yml: -------------------------------------------------------------------------------- 1 | # https://github.com/jekyll/minima 2 | remote_theme: jekyll/minima 3 | plugins: 4 | - jekyll-remote-theme 5 | title: s3d.rs 6 | minima: 7 | skin: classic 8 | social_links: 9 | github: s3d-rs 10 | header_pages: 11 | - docs/user-guide.md 12 | - docs/developer-guide.md 13 | - docs/architecture.md 14 | -------------------------------------------------------------------------------- /build.rs: -------------------------------------------------------------------------------- 1 | //! # build.rs for s3d 2 | //! 3 | //! This is the cargo build script which is called during build. 4 | //! We use it to generate code that for the S3 protocol. 5 | //! 6 | //! It reads the S3 smithy model as input, and writes generated code out to `$OUT_DIR/`, 7 | //! which is then included! in the src/build_gen.rs file. 8 | //! 9 | //! The S3 protocol is defined in a Smithy JSON AST model: 10 | //! - https://github.com/awslabs/smithy-rs/blob/main/aws/sdk/aws-models/s3.json 11 | //! 12 | 13 | // the build script flow uses a module not under src 14 | mod codegen; 15 | 16 | use crate::{ 17 | codegen::gen_commands::GenCommands, 18 | codegen::gen_converters::GenConverters, 19 | codegen::smithy_model::{FromJson, SmithyModel}, 20 | }; 21 | use std::{env, path::Path}; 22 | 23 | /// main function of the project's cargo build script 24 | /// See https://doc.rust-lang.org/cargo/reference/build-scripts.html 25 | fn main() { 26 | println!("cargo:warning=build codegen starting..."); 27 | 28 | let out_dir = env::var("OUT_DIR").unwrap(); 29 | let out_path = Path::new(out_dir.as_str()); 30 | let model_path = Path::new("smithy-rs/aws/sdk/aws-models/s3.json"); 31 | 32 | // printing out cargo directives 33 | println!("cargo:rerun-if-changed=build.rs"); 34 | println!("cargo:rerun-if-changed=codegen/"); 35 | println!("cargo:rerun-if-changed={}", model_path.display()); 36 | 37 | // load the smithy model and invoke code generators 38 | let model = SmithyModel::from_json_file(&model_path); 39 | GenCommands::new(&model, &out_path.join("s3_cli.rs")).generate(); 40 | GenConverters::new(&model, &out_path.join("s3_conv.rs")).generate(); 41 | 42 | println!("cargo:warning=build codegen done."); 43 | } 44 | -------------------------------------------------------------------------------- /codegen/gen_commands.rs: -------------------------------------------------------------------------------- 1 | use crate::codegen::utils::CodeWriter; 2 | use crate::codegen::smithy_model::*; 3 | use quote::{format_ident, quote}; 4 | use std::{collections::HashSet, path::Path}; 5 | 6 | /// GenCommands generates clap commands for every operation. 7 | pub struct GenCommands<'a> { 8 | pub model: &'a SmithyModel, 9 | pub writer: CodeWriter, 10 | } 11 | 12 | impl<'a> GenCommands<'a> { 13 | pub fn new(model: &'a SmithyModel, out_path: &Path) -> Self { 14 | Self { 15 | model, 16 | writer: CodeWriter::new(out_path), 17 | } 18 | } 19 | 20 | pub fn generate(mut self) { 21 | let ops_names: Vec<_> = self.model.iter_ops().map(|op| op.ident()).collect(); 22 | let ops_command = self 23 | .model 24 | .iter_ops() 25 | .map(|op| format_ident!("{}Command", op.name)); 26 | let ops_about = self.model.iter_ops().map(|op| op.get_doc_summary()); 27 | let mut structs_set = HashSet::::new(); 28 | 29 | // generate the subcommands enum with each S3 operation as a cli subcommand 30 | self.writer.write_code(quote! { 31 | 32 | #[derive(clap::Subcommand, Debug, Clone)] 33 | pub enum S3OpsCommands { 34 | #( 35 | #[clap(about = #ops_about, long_about = None)] 36 | #ops_names(#ops_command), 37 | )* 38 | } 39 | 40 | impl S3OpsCommands { 41 | pub async fn run(&self, s3: &'static aws_sdk_s3::Client) { 42 | match self { 43 | #( 44 | S3OpsCommands::#ops_names(cmd) => cmd.run(s3).await, 45 | )* 46 | } 47 | } 48 | } 49 | }); 50 | 51 | for op in self.model.iter_ops() { 52 | let cmd = format_ident!("{}Command", op.name); 53 | let op_snake = format_ident!("{}", snake(&op.name)); 54 | let input_shape = op.members.get("input").map(|m| self.model.get_shape_of(m)); 55 | let empty = SmithyMemberMap::new(); 56 | let members = input_shape.map_or(&empty, |s| &s.members); 57 | let set_inputs = members.values().map(|m| m.set_ident()); 58 | 59 | let command_args = members.values().map(|m| { 60 | let ident = m.ident(); 61 | let long_name = m.snake.replace("_", "-"); 62 | let arg_name = m.name.clone(); 63 | let help = m.get_doc_summary(); 64 | let required = m.has_trait(SM_REQUIRED); 65 | let shape = self.model.get_shape_of(m); 66 | let rust_type = match shape.typ { 67 | SmithyType::Boolean => quote! { bool }, 68 | SmithyType::Byte => quote! { i8 }, 69 | SmithyType::Short => quote! { i16 }, 70 | SmithyType::Integer => quote! { i32 }, 71 | SmithyType::Long => quote! { i64 }, 72 | SmithyType::Float => quote! { f32 }, 73 | SmithyType::Double => quote! { f64 }, 74 | SmithyType::String => quote! { String }, 75 | SmithyType::Timestamp => quote! { String }, 76 | SmithyType::Blob => quote! { Vec }, 77 | SmithyType::Map => quote! { String }, 78 | SmithyType::Structure => { 79 | let struct_name = format_ident!("{}Args", shape.name); 80 | if !structs_set.contains(&shape.name) { 81 | structs_set.insert(shape.name.clone()); 82 | let args = shape.members.values().map(|k| k.ident()); 83 | let long_names = shape.members.values().map(|k| k.snake.replace("_", "-")); 84 | self.writer.write_code(quote! { 85 | #[derive(clap::Args, Debug, Clone)] 86 | pub struct #struct_name { 87 | #( 88 | #[clap(long = #long_names)] 89 | #args: Option, 90 | )* 91 | } 92 | }); 93 | } 94 | quote! { #struct_name } 95 | } 96 | _ => todo!( 97 | "unsupported input shape type {:?} shape name {} member name {} required {}", 98 | shape.typ, 99 | shape.name, 100 | m.name, 101 | required 102 | ), 103 | }; 104 | if shape.typ == SmithyType::Structure { 105 | quote! { 106 | #[clap(flatten)] 107 | #ident: #rust_type 108 | } 109 | } else { 110 | let rust_type = if required { 111 | rust_type 112 | } else { 113 | quote! { Option<#rust_type> } 114 | }; 115 | quote! { 116 | #[clap(long = #long_name, name = #arg_name, help = #help, long_help = None)] 117 | #ident: #rust_type 118 | } 119 | } 120 | }).collect::>(); 121 | 122 | let inputs_from = members.values().map(|m| { 123 | let id = m.ident(); 124 | let required = m.has_trait(SM_REQUIRED); 125 | let shape = self.model.get_shape_of(m); 126 | match shape.typ { 127 | SmithyType::Boolean 128 | | SmithyType::Byte 129 | | SmithyType::Short 130 | | SmithyType::Integer 131 | | SmithyType::Long 132 | | SmithyType::Float 133 | | SmithyType::Double => { 134 | if required { 135 | quote! { Some(self.#id) } 136 | } else { 137 | quote! { self.#id } 138 | } 139 | } 140 | SmithyType::String => { 141 | if shape.has_trait(SM_ENUM) { 142 | quote! { None } 143 | } else if required { 144 | quote! { Some(self.#id.to_owned()) } 145 | } else { 146 | quote! { self.#id.to_owned() } 147 | } 148 | } 149 | _ => quote! { None }, 150 | } 151 | }); 152 | 153 | self.writer.write_code(quote! { 154 | 155 | #[derive(clap::Parser, Debug, Clone)] 156 | pub struct #cmd { 157 | #( 158 | #command_args, 159 | )* 160 | } 161 | 162 | impl #cmd { 163 | pub async fn run(&self, s3: &'static aws_sdk_s3::Client) { 164 | log::info!("{:#?}", self); 165 | let res = s3.#op_snake() 166 | #( 167 | .#set_inputs(#inputs_from) 168 | )* 169 | .send() 170 | .await; 171 | match res { 172 | Ok(out) => { 173 | log::info!("{:#?}", out); 174 | } 175 | Err(err) => match err { 176 | aws_smithy_http::result::SdkError::ServiceError {err,raw} => { 177 | log::error!("{:#?}", err); 178 | } 179 | _ => { 180 | log::error!("{:#?}", err); 181 | } 182 | } 183 | } 184 | } 185 | } 186 | 187 | }); 188 | } 189 | 190 | self.writer.done(); 191 | } 192 | } 193 | 194 | /* 195 | /// Generate the basic enum of operation kinds + macros to quickly generate code for each operation. 196 | /// The enum is flat - meaning it defines no attached state to any of the operations. 197 | /// It might be interesting to consider a more complex enum if needed by the daemon, 198 | /// or perhaps that would instead go to it's own enum, with auto-generated-mapping to this one. 199 | fn gen_ops_enum(&mut self) { 200 | let ops_names: Vec<_> = self.model.iter_ops().map(|op| op.ident()).collect(); 201 | 202 | self.writer.write_code(quote! { 203 | 204 | #[derive(Debug, PartialEq, Eq, Clone, Copy)] 205 | pub enum S3Ops { 206 | #(#ops_names),* 207 | } 208 | 209 | /// This macro calls a provided $macro for each S3 operation to generate code per op. 210 | macro_rules! generate_ops_code { 211 | ($macro:ident) => { 212 | #( $macro!(#ops_names); )* 213 | } 214 | } 215 | 216 | /// This macro calls a provided $macro for each S3 operation to generate item per op. 217 | macro_rules! generate_ops_items { 218 | ($macro:ident) => { 219 | #( $macro!(#ops_names), )* 220 | } 221 | } 222 | 223 | /// This macro matches a variable of S3Ops type and expands the provided $macro 224 | /// for each S3 operation to generate code handler per op. 225 | macro_rules! generate_ops_match { 226 | ($macro:ident, $op:expr) => { 227 | match ($op) { 228 | #( S3Ops::#ops_names => $macro!(#ops_names), )* 229 | } 230 | } 231 | } 232 | 233 | pub(crate) use generate_ops_code; 234 | pub(crate) use generate_ops_items; 235 | pub(crate) use generate_ops_match; 236 | }); 237 | } 238 | */ 239 | -------------------------------------------------------------------------------- /codegen/gen_converters.rs: -------------------------------------------------------------------------------- 1 | use crate::codegen::utils::CodeWriter; 2 | use crate::codegen::smithy_model::*; 3 | use proc_macro2::TokenStream; 4 | use quote::{format_ident, quote}; 5 | use std::path::Path; 6 | 7 | /// ConvertersGenerator generates functions to convert input and output structs 8 | /// between smithy client and server because they are not the same. 9 | /// See https://github.com/awslabs/smithy-rs/issues/1099 10 | pub struct GenConverters<'a> { 11 | pub model: &'a SmithyModel, 12 | pub writer: CodeWriter, 13 | pub client_crate: String, 14 | pub server_crate: String, 15 | } 16 | 17 | impl<'a> GenConverters<'a> { 18 | pub fn new(model: &'a SmithyModel, out_path: &Path) -> Self { 19 | Self { 20 | model, 21 | writer: CodeWriter::new(out_path), 22 | client_crate: String::from("aws_sdk_s3"), 23 | server_crate: String::from("s3d_smithy_codegen_server_s3"), 24 | } 25 | } 26 | 27 | pub fn generate(mut self) { 28 | let client_crate = format_ident!("{}", self.client_crate); 29 | let server_crate = format_ident!("{}", self.server_crate); 30 | 31 | for op in self.model.iter_ops() { 32 | { 33 | let input_id = format_ident!("{}Input", op.name); 34 | let conv_to_client_input = 35 | format_ident!("conv_to_client_{}", snake(&input_id.to_string())); 36 | let conv_to_client_input_gen = 37 | if let Some(mut input_member) = op.members.get("input").cloned() { 38 | input_member.name = format!("input/{}", input_id); 39 | self.gen_conv_to_client(&input_member, quote! { input }) 40 | } else { 41 | quote! { #client_crate::input::#input_id::builder().build().unwrap() } 42 | }; 43 | self.writer.write_code(quote! { 44 | pub fn #conv_to_client_input(input: #server_crate::input::#input_id) -> #client_crate::input::#input_id { 45 | #conv_to_client_input_gen 46 | } 47 | }); 48 | } 49 | { 50 | let output_id = format_ident!("{}Output", op.name); 51 | let conv_from_client_output = 52 | format_ident!("conv_from_client_{}", snake(&output_id.to_string())); 53 | let conv_from_client_output_gen = 54 | if let Some(mut output_member) = op.members.get("output").cloned() { 55 | output_member.name = format!("output/{}", output_id); 56 | self.gen_conv_from_client(&output_member, quote! { output }) 57 | } else { 58 | quote! { #server_crate::output::#output_id {} } 59 | }; 60 | self.writer.write_code(quote! { 61 | pub fn #conv_from_client_output(output: #client_crate::output::#output_id) -> #server_crate::output::#output_id { 62 | #conv_from_client_output_gen 63 | } 64 | }); 65 | } 66 | } 67 | 68 | self.writer.done(); 69 | } 70 | 71 | fn gen_conv_to_client(&mut self, member: &SmithyMember, from: TokenStream) -> TokenStream { 72 | let client_crate = format_ident!("{}", self.client_crate); 73 | let server_crate = format_ident!("{}", self.server_crate); 74 | let shape = self.model.get_shape_of(member); 75 | let member_split: Vec<_> = member.name.split('/').collect(); 76 | let pkg_name = format_ident!( 77 | "{}", 78 | match member_split[0] { 79 | "input" => "input", 80 | "output" => "output", 81 | _ => "model", 82 | } 83 | ); 84 | let type_name = format_ident!( 85 | "{}", 86 | match member_split[0] { 87 | "input" => member_split[1], 88 | "output" => member_split[1], 89 | _ => shape.name.as_str(), 90 | } 91 | ); 92 | 93 | match shape.typ { 94 | SmithyType::Structure => { 95 | let members: Vec = shape 96 | .members 97 | .values() 98 | .map(|m| { 99 | let m_ident = m.ident(); 100 | let set_ident = m.set_ident(); 101 | let s = self.model.get_shape_of(m); 102 | let convert = if m.has_trait(SM_REQUIRED) || s.typ.is_always_required() { 103 | let c = self.gen_conv_to_client(m, quote! { v. #m_ident }); 104 | quote! { Some(#c)} 105 | } else { 106 | let c = self.gen_conv_to_client(m, quote! { v }); 107 | quote! { v. #m_ident .map(|v| #c) } 108 | }; 109 | quote! { b = b.#set_ident(#convert); } 110 | }) 111 | .collect(); 112 | let build_it = if pkg_name == "input" { 113 | quote! { b.build().unwrap() } 114 | } else { 115 | quote! { b.build() } 116 | }; 117 | quote! {{ 118 | let v = #from; 119 | let mut b = #client_crate::#pkg_name::#type_name::builder(); 120 | #(#members)* 121 | #build_it 122 | }} 123 | } 124 | 125 | SmithyType::List => { 126 | let m = &shape.members["member"]; 127 | let s = self.model.get_shape_of(m); 128 | let convert = self.gen_conv_to_client(m, quote! { v }); 129 | if s.typ.is_always_required() 130 | || (s.typ == SmithyType::String && !s.has_trait(SM_ENUM)) 131 | { 132 | quote! { #from .clone() } 133 | } else { 134 | quote! { #from .into_iter().map(|v| #convert).collect() } 135 | } 136 | } 137 | 138 | // SmithyType::Map => {} 139 | SmithyType::Union => { 140 | let members: Vec = shape 141 | .members 142 | .values() 143 | .map(|m| { 144 | let enum_name = format_ident!("{}", m.name); 145 | let c = self.gen_conv_to_client(m, quote! { v }); 146 | quote! { 147 | #server_crate::#pkg_name::#type_name::#enum_name(v) => 148 | #client_crate::#pkg_name::#type_name::#enum_name(#c), 149 | } 150 | }) 151 | .collect(); 152 | quote! {{ 153 | match #from { 154 | #(#members)* 155 | _ => panic!("unknown union value"), 156 | } 157 | }} 158 | } 159 | 160 | SmithyType::Blob => { 161 | quote! { #from } 162 | } 163 | 164 | SmithyType::String => { 165 | if shape.has_trait(SM_ENUM) { 166 | quote! { #client_crate::#pkg_name::#type_name::from(#from .as_str()) } 167 | } else { 168 | quote! { #from .to_owned() } 169 | } 170 | } 171 | 172 | _ => { 173 | quote! { #from .to_owned() } 174 | } 175 | } 176 | } 177 | 178 | fn gen_conv_from_client(&mut self, member: &SmithyMember, from: TokenStream) -> TokenStream { 179 | let client_crate = format_ident!("{}", self.client_crate); 180 | let server_crate = format_ident!("{}", self.server_crate); 181 | let shape = self.model.get_shape_of(member); 182 | let member_split: Vec<_> = member.name.split('/').collect(); 183 | let pkg_name = format_ident!( 184 | "{}", 185 | match member_split[0] { 186 | "input" => "input", 187 | "output" => "output", 188 | _ => "model", 189 | } 190 | ); 191 | let type_name = format_ident!( 192 | "{}", 193 | match member_split[0] { 194 | "input" => member_split[1], 195 | "output" => member_split[1], 196 | _ => shape.name.as_str(), 197 | } 198 | ); 199 | 200 | match shape.typ { 201 | SmithyType::Structure => { 202 | let mut has_required = false; 203 | let members: Vec = shape 204 | .members 205 | .values() 206 | .map(|m| { 207 | let m_ident = m.ident(); 208 | let set_ident = m.set_ident(); 209 | let s = self.model.get_shape_of(m); 210 | if m.has_trait(SM_REQUIRED) 211 | && !s.typ.is_always_required() 212 | && (s.typ != SmithyType::String || s.has_trait(SM_ENUM)) 213 | { 214 | has_required = true; 215 | } 216 | let convert = if s.typ.is_always_required() { 217 | let c = self.gen_conv_from_client(m, quote! { v. #m_ident }); 218 | quote! { Some(#c)} 219 | } else { 220 | let c = self.gen_conv_from_client(m, quote! { v }); 221 | quote! { v. #m_ident .map(|v| #c) } 222 | }; 223 | quote! { b = b.#set_ident(#convert); } 224 | }) 225 | .collect(); 226 | let build_it = if pkg_name == "input" || has_required { 227 | quote! { b.build().unwrap() } 228 | } else { 229 | quote! { b.build() } 230 | }; 231 | quote! {{ 232 | let v = #from; 233 | let mut b = #server_crate::#pkg_name::#type_name::builder(); 234 | #(#members)* 235 | #build_it 236 | }} 237 | } 238 | 239 | SmithyType::List => { 240 | let m = &shape.members["member"]; 241 | let s = self.model.get_shape_of(m); 242 | let convert = self.gen_conv_from_client(m, quote! { v }); 243 | if s.typ.is_always_required() 244 | || (s.typ == SmithyType::String && !s.has_trait(SM_ENUM)) 245 | { 246 | quote! { #from .clone() } 247 | } else { 248 | quote! { #from .into_iter().map(|v| #convert).collect() } 249 | } 250 | } 251 | 252 | // SmithyType::Map => {} 253 | SmithyType::Union => { 254 | let members: Vec = shape 255 | .members 256 | .values() 257 | .map(|m| { 258 | let enum_name = format_ident!("{}", m.name); 259 | let c = self.gen_conv_from_client(m, quote! { v }); 260 | quote! { 261 | #client_crate::#pkg_name::#type_name::#enum_name(v) => 262 | #server_crate::#pkg_name::#type_name::#enum_name(#c), 263 | } 264 | }) 265 | .collect(); 266 | quote! {{ 267 | match #from { 268 | #(#members)* 269 | _ => panic!("unknown union value"), 270 | } 271 | }} 272 | } 273 | 274 | SmithyType::Blob => { 275 | quote! { #from } 276 | } 277 | 278 | SmithyType::String => { 279 | if shape.has_trait(SM_ENUM) { 280 | quote! { #server_crate::#pkg_name::#type_name::from(#from .as_str()) } 281 | } else { 282 | quote! { #from .to_owned() } 283 | } 284 | } 285 | 286 | _ => { 287 | quote! { #from .to_owned() } 288 | } 289 | } 290 | } 291 | } 292 | -------------------------------------------------------------------------------- /codegen/mod.rs: -------------------------------------------------------------------------------- 1 | //! This codegen module is a cargo build script utility module - see `build.rs` 2 | pub mod gen_commands; 3 | pub mod gen_converters; 4 | pub mod smithy_model; 5 | pub mod utils; 6 | -------------------------------------------------------------------------------- /codegen/smithy_model.rs: -------------------------------------------------------------------------------- 1 | //! This module provides structures to parse and make sense of 2 | //! a smithy model which is loaded in JSON AST format. 3 | //! 4 | //! Smithy specs: 5 | //! - https://awslabs.github.io/smithy/1.0/spec/index.html 6 | //! - https://awslabs.github.io/smithy/1.0/spec/core/json-ast.html 7 | 8 | use proc_macro2::Ident; 9 | use quote::format_ident; 10 | use serde_json::{Map, Value}; 11 | use std::{collections::HashMap, fs::File, path::Path}; 12 | 13 | /// SmithyModel is a wrapper around the smithy JSON AST model 14 | /// which provides a convenient interface to read the model 15 | #[derive(Debug, Clone)] 16 | pub struct SmithyModel { 17 | pub shapes: SmithyShapeMap, 18 | } 19 | 20 | #[derive(Debug, Clone)] 21 | pub struct SmithyShape { 22 | pub key: String, 23 | pub name: String, 24 | pub typ: SmithyType, 25 | pub traits: Value, 26 | pub members: SmithyMemberMap, 27 | } 28 | 29 | #[derive(Debug, Clone)] 30 | pub struct SmithyMember { 31 | pub name: String, 32 | pub snake: String, 33 | pub traits: Value, 34 | pub target: String, 35 | } 36 | 37 | pub type SmithyShapeMap = HashMap; 38 | pub type SmithyMemberMap = HashMap; 39 | pub type JsonObject = Map; 40 | 41 | impl SmithyModel { 42 | pub fn get_shape_of<'a>(&'a self, member: &'a SmithyMember) -> &'a SmithyShape { 43 | &self.shapes[&member.target] 44 | } 45 | pub fn _get_shape_if<'a>(&'a self, member: Option<&'a SmithyMember>) -> Option<&'a SmithyShape> { 46 | member.map(|m| self.get_shape_of(m)) 47 | } 48 | pub fn _get_shape_by_key<'a>(&'a self, k: &str) -> &'a SmithyShape { 49 | &self.shapes[k] 50 | } 51 | pub fn iter_shapes_by_type<'a>( 52 | &'a self, 53 | t: SmithyType, 54 | ) -> impl Iterator + 'a { 55 | self.shapes.values().filter(move |s| s.typ == t) 56 | } 57 | pub fn _iter_shapes_with_trait<'a>( 58 | &'a self, 59 | t: &'a str, 60 | ) -> impl Iterator + 'a { 61 | self.shapes.values().filter(|s| s.has_trait(t)) 62 | } 63 | pub fn iter_ops<'a>(&'a self) -> impl Iterator + 'a { 64 | self.iter_shapes_by_type(SmithyType::Operation) 65 | .filter(|op| op.name != "SelectObjectContent") 66 | } 67 | } 68 | 69 | impl SmithyShape { 70 | pub fn new(json: &Value, key: &str) -> Self { 71 | let typ = SmithyType::from(json["type"].as_str().unwrap()); 72 | let traits = json["traits"].to_owned(); 73 | let mut members = SmithyMemberMap::from_json(&json["members"]); 74 | for k in ["input", "output", "member", "key", "value"].iter() { 75 | if json[k].is_object() { 76 | members.insert(k.to_string(), SmithyMember::new(k, &json[k])); 77 | } 78 | } 79 | // TODO json["errors"].as_array() 80 | // TODO json["operations"].as_array() 81 | Self { 82 | key: key.to_string(), 83 | name: camel(&unprefix(key)), 84 | typ, 85 | traits, 86 | members, 87 | } 88 | } 89 | pub fn ident(&self) -> Ident { 90 | format_ident!("{}", self.name) 91 | } 92 | pub fn _get_type(&self) -> &str { 93 | self.typ.as_ref() 94 | } 95 | } 96 | 97 | impl Default for SmithyShape { 98 | fn default() -> Self { 99 | SmithyShape { 100 | key: "".to_string(), 101 | name: "".to_string(), 102 | typ: SmithyType::String, 103 | traits: Value::Null, 104 | members: SmithyMemberMap::new(), 105 | } 106 | } 107 | } 108 | 109 | impl SmithyMember { 110 | pub fn new(key: &str, json: &Value) -> Self { 111 | let traits = json["traits"].to_owned(); 112 | let target = json["target"].as_str().unwrap_or("").to_string(); 113 | Self { 114 | name: key.to_string(), 115 | snake: snake(key), 116 | traits, 117 | target, 118 | } 119 | } 120 | pub fn ident(&self) -> Ident { 121 | if syn::parse_str::(&self.snake).is_err() { 122 | format_ident!("r#{}", self.snake) 123 | } else { 124 | format_ident!("{}", self.snake) 125 | } 126 | } 127 | pub fn set_ident(&self) -> Ident { 128 | format_ident!("set_{}", self.snake) 129 | } 130 | pub fn _get_ident(&self) -> Ident { 131 | format_ident!("get_{}", self.snake) 132 | } 133 | } 134 | 135 | pub trait FromJson: Sized { 136 | fn from_json(json: &Value) -> Self; 137 | fn from_json_file(path: &Path) -> Self { 138 | let v = serde_json::from_reader(File::open(path).unwrap()).unwrap(); 139 | Self::from_json(&v) 140 | } 141 | } 142 | 143 | impl FromJson for SmithyModel { 144 | fn from_json(json: &Value) -> Self { 145 | let shapes = SmithyShapeMap::from_json(&json["shapes"]); 146 | SmithyModel { shapes } 147 | } 148 | } 149 | 150 | impl FromJson for SmithyShapeMap { 151 | fn from_json(v: &Value) -> Self { 152 | v.as_object().map_or_else( 153 | || SmithyShapeMap::new(), 154 | |m| { 155 | m.iter() 156 | .map(|(k, v)| (k.to_owned(), SmithyShape::new(v, k))) 157 | .collect() 158 | }, 159 | ) 160 | } 161 | } 162 | 163 | impl FromJson for SmithyMemberMap { 164 | fn from_json(v: &Value) -> Self { 165 | v.as_object().map_or_else( 166 | || SmithyMemberMap::new(), 167 | |m| { 168 | m.iter() 169 | .map(|(k, v)| (k.to_owned(), SmithyMember::new(k, v))) 170 | .collect() 171 | }, 172 | ) 173 | } 174 | } 175 | 176 | /// SmithyTraits provide an interface to read the traits of a shape or a member 177 | pub trait SmithyTraits { 178 | fn set_trait(&mut self, t: &str); 179 | fn has_trait(&self, t: &str) -> bool; 180 | fn get_trait(&self, t: &str) -> String; 181 | fn get_trait_value(&self, t: &str) -> Value; 182 | fn has_http_trait(&self) -> bool { 183 | self.has_trait(SM_HTTP_LABEL) 184 | || self.has_trait(SM_HTTP_QUERY) 185 | || self.has_trait(SM_HTTP_HEADER) 186 | || self.has_trait(SM_HTTP_PREFIX_HEADERS) 187 | } 188 | fn get_doc_summary(&self) -> String { 189 | let doc = self.get_trait(SM_DOC); 190 | if doc.is_empty() { 191 | return String::new(); 192 | } 193 | let startp = doc.find("

").map_or(0, |p| p + 3); 194 | let endp = doc.find("

").unwrap_or(doc.len()); 195 | let summary = doc[startp..endp].to_string(); 196 | summary.split_whitespace().collect::>().join(" ") 197 | } 198 | } 199 | 200 | /// SmithyTraitor provide an interface to get direct access to the complete traits values of a shape or a member 201 | /// It is used to implement SmithyTraits easily for shapes and members loaded from json. 202 | pub trait SmithyTraitor { 203 | fn traits(&self) -> &Value; 204 | fn traits_mut(&mut self) -> &mut Value; 205 | } 206 | 207 | /// Implement SmithyTraits for any SmithyTraitor 208 | impl SmithyTraits for T { 209 | fn set_trait(&mut self, t: &str) { 210 | self.traits_mut() 211 | .as_object_mut() 212 | .map(|o| o.insert(t.to_string(), Value::Object(JsonObject::new()))); 213 | } 214 | fn has_trait(&self, t: &str) -> bool { 215 | self.traits() 216 | .as_object() 217 | .map_or(false, |o| o.contains_key(t)) 218 | } 219 | fn get_trait(&self, t: &str) -> String { 220 | self.traits() 221 | .as_object() 222 | .and_then(|o| o.get(t)) 223 | .and_then(|v| v.as_str()) 224 | .map(|s| s.to_string()) 225 | .unwrap_or_default() 226 | } 227 | fn get_trait_value(&self, t: &str) -> Value { 228 | self.traits() 229 | .as_object() 230 | .and_then(|o| o.get(t)) 231 | .map_or(Value::Null, |v| v.to_owned()) 232 | } 233 | } 234 | 235 | impl SmithyTraitor for SmithyShape { 236 | fn traits(&self) -> &Value { 237 | &self.traits 238 | } 239 | fn traits_mut(&mut self) -> &mut Value { 240 | &mut self.traits 241 | } 242 | } 243 | 244 | impl SmithyTraitor for SmithyMember { 245 | fn traits(&self) -> &Value { 246 | &self.traits 247 | } 248 | fn traits_mut(&mut self) -> &mut Value { 249 | &mut self.traits 250 | } 251 | } 252 | 253 | /// unprefix returns just the suffix for `prefix#suffix` strings 254 | pub fn unprefix(s: &str) -> String { 255 | s.split_once('#') 256 | .map_or_else(|| s.to_string(), |(_prefix, suffix)| suffix.to_string()) 257 | } 258 | 259 | /// camel changes from MIXOfUPPERCaseAndCamelCase to MixOfUpperCaseAndCamelCase 260 | pub fn camel(s: &str) -> String { 261 | let mut out = String::new(); 262 | let mut upper_streak = 0; 263 | for c in s.chars() { 264 | if c.is_uppercase() || c.is_numeric() { 265 | if upper_streak == 0 { 266 | out.push(c); 267 | } else { 268 | out.push(c.to_lowercase().next().unwrap()); 269 | } 270 | upper_streak += 1; 271 | } else { 272 | if upper_streak > 1 && out.len() > 1 { 273 | let c = out.pop().unwrap(); 274 | out.push(c.to_uppercase().next().unwrap()); 275 | } 276 | out.push(c); 277 | upper_streak = 0; 278 | } 279 | } 280 | out 281 | } 282 | 283 | /// snake changes from CamelCase to snake_case 284 | pub fn snake(s: &str) -> String { 285 | let mut out = String::new(); 286 | let mut upper_streak = 0; 287 | for mut c in s.chars() { 288 | if c.is_uppercase() || c.is_numeric() { 289 | if upper_streak == 0 && out.len() > 0 && out.chars().last().unwrap() != '_' { 290 | out.push('_'); 291 | } 292 | out.push(c.to_lowercase().next().unwrap()); 293 | upper_streak += 1; 294 | } else { 295 | if !c.is_alphanumeric() { 296 | c = '_'; 297 | } 298 | if upper_streak > 1 && out.len() > 1 && c != '_' { 299 | let c = out.pop().unwrap(); 300 | out.push('_'); 301 | out.push(c); 302 | } 303 | out.push(c); 304 | upper_streak = 0; 305 | } 306 | } 307 | out 308 | } 309 | 310 | /// smithy shape types 311 | /// https://awslabs.github.io/smithy/1.0/spec/core/model.html# 312 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] 313 | pub enum SmithyType { 314 | // primitive-shapes 315 | Boolean, 316 | Byte, 317 | Short, 318 | Integer, 319 | Long, 320 | Float, 321 | Double, 322 | BigInteger, 323 | BigDecimal, 324 | // basic-shapes 325 | Blob, 326 | String, 327 | Timestamp, 328 | Document, 329 | // aggregate-shapes 330 | Member, 331 | List, 332 | Set, 333 | Map, 334 | Structure, 335 | Union, 336 | // service-shapes 337 | Service, 338 | Operation, 339 | Resource, 340 | } 341 | 342 | impl SmithyType { 343 | pub fn is_always_required(&self) -> bool { 344 | match self { 345 | SmithyType::Blob 346 | | SmithyType::Boolean 347 | | SmithyType::Byte 348 | | SmithyType::Short 349 | | SmithyType::Integer 350 | | SmithyType::Long 351 | | SmithyType::Float 352 | | SmithyType::Double 353 | | SmithyType::BigInteger 354 | | SmithyType::BigDecimal => true, 355 | _ => false, 356 | } 357 | } 358 | } 359 | 360 | impl ToString for SmithyType { 361 | fn to_string(&self) -> String { 362 | self.as_ref().to_string() 363 | } 364 | } 365 | 366 | impl AsRef for SmithyType { 367 | fn as_ref(&self) -> &str { 368 | match self { 369 | // primitive-shapes 370 | SmithyType::Boolean => SM_TYPE_BOOLEAN, 371 | SmithyType::Byte => SM_TYPE_BYTE, 372 | SmithyType::Short => SM_TYPE_SHORT, 373 | SmithyType::Integer => SM_TYPE_INTEGER, 374 | SmithyType::Long => SM_TYPE_LONG, 375 | SmithyType::Float => SM_TYPE_FLOAT, 376 | SmithyType::Double => SM_TYPE_DOUBLE, 377 | SmithyType::BigInteger => SM_TYPE_BIGINTEGER, 378 | SmithyType::BigDecimal => SM_TYPE_BIGDECIMAL, 379 | // basic-shapes 380 | SmithyType::Blob => SM_TYPE_BLOB, 381 | SmithyType::String => SM_TYPE_STRING, 382 | SmithyType::Timestamp => SM_TYPE_TIMESTAMP, 383 | SmithyType::Document => SM_TYPE_DOCUMENT, 384 | // aggregate-shapes 385 | SmithyType::Member => SM_TYPE_MEMBER, 386 | SmithyType::List => SM_TYPE_LIST, 387 | SmithyType::Set => SM_TYPE_SET, 388 | SmithyType::Map => SM_TYPE_MAP, 389 | SmithyType::Structure => SM_TYPE_STRUCTURE, 390 | SmithyType::Union => SM_TYPE_UNION, 391 | // service-shapes 392 | SmithyType::Service => SM_TYPE_SERVICE, 393 | SmithyType::Operation => SM_TYPE_OPERATION, 394 | SmithyType::Resource => SM_TYPE_RESOURCE, 395 | } 396 | } 397 | } 398 | 399 | impl From<&str> for SmithyType { 400 | fn from(s: &str) -> Self { 401 | match s { 402 | // primitive-shapes 403 | SM_TYPE_BOOLEAN => SmithyType::Boolean, 404 | SM_TYPE_BYTE => SmithyType::Byte, 405 | SM_TYPE_SHORT => SmithyType::Short, 406 | SM_TYPE_INTEGER => SmithyType::Integer, 407 | SM_TYPE_LONG => SmithyType::Long, 408 | SM_TYPE_FLOAT => SmithyType::Float, 409 | SM_TYPE_DOUBLE => SmithyType::Double, 410 | SM_TYPE_BIGINTEGER => SmithyType::BigInteger, 411 | SM_TYPE_BIGDECIMAL => SmithyType::BigDecimal, 412 | // basic-shapes 413 | SM_TYPE_BLOB => SmithyType::Blob, 414 | SM_TYPE_STRING => SmithyType::String, 415 | SM_TYPE_TIMESTAMP => SmithyType::Timestamp, 416 | SM_TYPE_DOCUMENT => SmithyType::Document, 417 | // aggregate-shapes 418 | SM_TYPE_MEMBER => SmithyType::Member, 419 | SM_TYPE_LIST => SmithyType::List, 420 | SM_TYPE_SET => SmithyType::Set, 421 | SM_TYPE_MAP => SmithyType::Map, 422 | SM_TYPE_STRUCTURE => SmithyType::Structure, 423 | SM_TYPE_UNION => SmithyType::Union, 424 | // service-shapes 425 | SM_TYPE_SERVICE => SmithyType::Service, 426 | SM_TYPE_OPERATION => SmithyType::Operation, 427 | SM_TYPE_RESOURCE => SmithyType::Resource, 428 | _ => panic!("unknown SmithyType: {}", s), 429 | } 430 | } 431 | } 432 | 433 | // primitive-shapes 434 | const SM_TYPE_BOOLEAN: &str = "boolean"; 435 | const SM_TYPE_BYTE: &str = "byte"; 436 | const SM_TYPE_SHORT: &str = "short"; 437 | const SM_TYPE_INTEGER: &str = "integer"; 438 | const SM_TYPE_LONG: &str = "long"; 439 | const SM_TYPE_FLOAT: &str = "float"; 440 | const SM_TYPE_DOUBLE: &str = "double"; 441 | const SM_TYPE_BIGINTEGER: &str = "bigInteger"; 442 | const SM_TYPE_BIGDECIMAL: &str = "bigDecimal"; 443 | // basic-shapes 444 | const SM_TYPE_BLOB: &str = "blob"; 445 | const SM_TYPE_STRING: &str = "string"; 446 | const SM_TYPE_TIMESTAMP: &str = "timestamp"; 447 | const SM_TYPE_DOCUMENT: &str = "document"; 448 | // aggregate-shapes 449 | const SM_TYPE_MEMBER: &str = "member"; 450 | const SM_TYPE_LIST: &str = "list"; 451 | const SM_TYPE_SET: &str = "set"; 452 | const SM_TYPE_MAP: &str = "map"; 453 | const SM_TYPE_STRUCTURE: &str = "structure"; 454 | const SM_TYPE_UNION: &str = "union"; 455 | // service-shapes 456 | const SM_TYPE_SERVICE: &str = "service"; 457 | const SM_TYPE_OPERATION: &str = "operation"; 458 | const SM_TYPE_RESOURCE: &str = "resource"; 459 | 460 | // smithy traits used in s3.json: 461 | const _SM_PREFIX: &str = "smithy.api#"; 462 | pub const SM_ENUM: &str = "smithy.api#enum"; 463 | pub const SM_REQUIRED: &str = "smithy.api#required"; 464 | const SM_DOC: &str = "smithy.api#documentation"; 465 | const _SM_ERROR: &str = "smithy.api#error"; 466 | const _SM_HTTP: &str = "smithy.api#http"; 467 | #[allow(unused)] 468 | const SM_HTTP_LABEL: &str = "smithy.api#httpLabel"; 469 | #[allow(unused)] 470 | const SM_HTTP_QUERY: &str = "smithy.api#httpQuery"; 471 | #[allow(unused)] 472 | const SM_HTTP_HEADER: &str = "smithy.api#httpHeader"; 473 | const _SM_HTTP_PAYLOAD: &str = "smithy.api#httpPayload"; 474 | #[allow(unused)] 475 | const SM_HTTP_PREFIX_HEADERS: &str = "smithy.api#httpPrefixHeaders"; 476 | const _SM_HTTP_CHECKSUM_REQUIRED: &str = "smithy.api#httpChecksumRequired"; 477 | const _SM_XML_NS: &str = "smithy.api#xmlNamespace"; 478 | const _SM_XML_NAME: &str = "smithy.api#xmlName"; 479 | const _SM_XML_ATTR: &str = "smithy.api#xmlAttribute"; 480 | const _SM_XML_FLATTENED: &str = "smithy.api#xmlFlattened"; 481 | const _SM_SENSITIVE: &str = "smithy.api#sensitive"; 482 | const _SM_TIMESTAMP_FORMAT: &str = "smithy.api#timestampFormat"; 483 | const _SM_EVENT_PAYLOAD: &str = "smithy.api#eventPayload"; 484 | const _SM_STREAMING: &str = "smithy.api#streaming"; 485 | const _SM_PAGINATED: &str = "smithy.api#paginated"; 486 | const _SM_DEPRECATED: &str = "smithy.api#deprecated"; 487 | const _SM_TITLE: &str = "smithy.api#title"; 488 | const _SM_PATTERN: &str = "smithy.api#pattern"; 489 | const _SM_LENGTH: &str = "smithy.api#length"; 490 | const _SM_HOST_LABEL: &str = "smithy.api#hostLabel"; 491 | const _SM_ENDPOINT: &str = "smithy.api#endpoint"; 492 | const _SM_AUTH: &str = "smithy.api#auth"; 493 | -------------------------------------------------------------------------------- /codegen/utils.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | fs::File, 3 | io::{BufWriter, Write}, 4 | path::{Path, PathBuf}, 5 | // process::{Child, ChildStdin, Command, Stdio}, 6 | }; 7 | 8 | /// CodeWriter pipes generated code through rustfmt and then into an output file. 9 | /// However rustfmt seems to get stuck so we had to disable it for now. 10 | pub struct CodeWriter { 11 | path: PathBuf, 12 | w: Option>, 13 | // w: Box, 14 | // rustfmt: Option, 15 | } 16 | 17 | impl CodeWriter { 18 | pub fn new(file_path: &Path) -> Self { 19 | println!("CodeWriter file {:?}", file_path); 20 | let file = File::create(file_path).unwrap(); 21 | // let mut rustfmt = Command::new("rustfmt") 22 | // .arg("--edition=2021") 23 | // .stdin(Stdio::piped()) 24 | // .stdout(file) 25 | // .spawn() 26 | // .unwrap(); 27 | // println!("CodeWriter rustfmt {:?}", rustfmt); 28 | // let w = BufWriter::new(rustfmt.stdin.take().unwrap()); 29 | let w = BufWriter::new(file); 30 | CodeWriter { 31 | path: file_path.to_path_buf(), 32 | w: Some(w), 33 | // w: Box::new(w), 34 | // rustfmt: Some(rustfmt), 35 | // rustfmt: None, 36 | } 37 | } 38 | 39 | pub fn write_code(&mut self, code: T) { 40 | self.write_all(code.to_string().as_bytes()).unwrap(); 41 | self.write_all(b"\n\n").unwrap(); 42 | } 43 | 44 | pub fn done(mut self) { 45 | self.flush().unwrap(); 46 | } 47 | } 48 | 49 | impl Write for CodeWriter { 50 | fn write(&mut self, buf: &[u8]) -> std::io::Result { 51 | self.w.as_mut().unwrap().write(buf) 52 | // self.w.as_mut().write(buf) 53 | } 54 | fn flush(&mut self) -> std::io::Result<()> { 55 | println!("CodeWriter flush buffers {}", self.path.display()); 56 | self.w.take().unwrap().flush()?; 57 | // self.w.flush()?; 58 | // println!("CodeWriter wait rustfmt {}", self.path.display()); 59 | // self.rustfmt.take().unwrap().wait()?; 60 | println!("CodeWriter done {}", self.path.display()); 61 | Ok(()) 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /docs/architecture.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Architecture 3 | --- 4 | 5 | > 🚧   **Warning - work in progress**   🚧 6 | > 7 | > This page is under development and not yet complete.\ 8 | > Apologies for the inconvenience. 9 | 10 | # System Design 11 | 12 | ![Application models diagram](s3d-diagram.png) 13 | 14 | ## Service 15 | 16 | `s3d` is meant to run alongside the application(s) and provide transparent access to remote S3 storage: 17 | 18 | ``` 19 | /-------------\ 20 | | apps -> s3d | ----> remote S3 21 | \-------------/ 22 | ``` 23 | 24 | In containerized environments, such as Kubernetes, `s3d` can run in several different ways: 25 | - Per app - as a Pod or Sidecar Container. 26 | - Per node - as a DaemonSet. 27 | - Per cluster - as a scalable fleet of daemons with a Deployment + Service. 28 | 29 | ## Storage 30 | 31 | Every `s3d` instance requires its own local storage volume (FS) to store its data. 32 | 33 | This volume is recommended to be persistent to avoid data loss of pending data in the write queue, 34 | but it can be ephemeral and `s3d` will try to flush the data to S3 on shutdown. 35 | 36 | The capacity of the volume is not required to have the same size of the remote bucket, 37 | as `s3d` will use it to store pending writes, and cached reads, which allow it to operate 38 | with limited capacity by recycling the available capacity on demand. 39 | 40 | ## Security 41 | 42 | The connection from `s3d` to the remote S3 storage is encrypted and authenticated. 43 | However, the connectivity from clients to `s3d` is currently not encrypted and not authenticated. 44 | For testing and development this works fine, but for real production environments 45 | it will be required to support HTTPS and verify client requests authentication. 46 | 47 | # Software Design 48 | 49 | In order to keep `s3d` as simple as possible, and yet use bleeding-edge technology and provide a fully capable service for edge computing stack, `s3d` builds on the shoulders of great open source projects. 50 | 51 | The following sections describe the software used in the making of `s3d`: 52 | 53 | ## Rust-lang 54 | 55 | - The choice of the Rust language was a natural fit for edge systems, 56 | as it is a modern language with a focus on functionality, safety and performance. 57 | - Building with the rust toolchain into a single, standalone, lightweight binary, 58 | makes it easy to set up and configure for linux and containers, 59 | in order to run alongside any application. 60 | - Libraries from crates.io provide a great set of features for building daemons, 61 | such as the `tokio` library for async I/O, `hyper` for HTTP, etc. 62 | 63 | ## Smithy-rs 64 | 65 | - [awslabs/smithy-rs](https://github.com/awslabs/smithy-rs) builds the official AWS SDK for Rust. 66 | - It aims for high API compatibility and provides the solid S3 protocol foundation. 67 | - Using it to generate server and client S3 protocol code, and hook in the added functionality. 68 | 69 | ## Filters 70 | 71 | - A simple textual syntax is defined for fine grain object filters 72 | to include/exclude by bucket-name, key/prefix, tags, headers, meta-data. 73 | 74 | ## OpenPolicyAgent (OPA) 75 | 76 | - [OPA](https://www.openpolicyagent.org/) provides tools for declaring and evaluating policies 77 | which would extend the capabilities of filters. 78 | 79 | ## OpenTelemetry (OTEL) 80 | 81 | - [OTEL](https://opentelemetry.io/) provides a set of tools for logging, tracing and metrics. 82 | - The [opentelemetry crate](https://crates.io/crates/opentelemetry) provides the library. 83 | 84 | ## FUSE ("Filesystem in Userspace") 85 | 86 | - FUSE provides POSIX-like data access for applications that do not use the S3 API (see [kernel fuse docs](https://www.kernel.org/doc/html/latest/filesystems/fuse.html)) 87 | - The daemon binds a FUSE filesystem and creates the mountpoint that maps the filesystem to the S3 API. 88 | - FUSE is a good fit for immutable files, and reading small portions of large datasets. 89 | - FUSE is a **not** a good fit for mutable files (overwrites/appends), or file locks (not supported). 90 | - The [fuser crate](https://crates.io/crates/fuser) is used to set up the FUSE binding. 91 | 92 | # Roadmap 93 | 94 | - Wasm support for filters and S3-select. 95 | - Multi-tenancy and authentication: 96 | - IAM - Identity and Access Management (long-term credentials) 97 | - STS - Secure Token Service (short-term credentials) 98 | - IMDSv2 - Instance Meta-Data Service (integrated credential provider) 99 | -------------------------------------------------------------------------------- /docs/developer-guide.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Developer Guide 3 | --- 4 | 5 | # Prerequisites: 6 | 7 | - [Rust](https://www.rust-lang.org/tools/install) - stable channel, currently rustc 1.58.1 (db9d1b20b 2022-01-20). 8 | - [Java 14](https://jdk.java.net/archive/) - currently openjdk 14.0.2 (2020-07-14). 9 | 10 | Notice that JDK <= 14 is still the required for smithy-rs, but this restriction would be removed with the move to gradle 7 - see [tracking issue](https://github.com/awslabs/smithy-rs/issues/1167). 11 | 12 | # Build from source 13 | 14 | Clone the repo (use a fork if you want to contribute back upstream): 15 | ```bash 16 | git clone https://github.com/s3d-rs/s3d.git 17 | cd s3d 18 | ``` 19 | 20 | Build debug mode: 21 | ```bash 22 | make 23 | ``` 24 | 25 | Build release mode: 26 | ```bash 27 | make RELEASE=1 28 | ``` 29 | 30 | # Run locally 31 | 32 | Run from target dir: 33 | ```bash 34 | ./target/debug/s3d 35 | ``` 36 | 37 | Load shell env to simplify running (run `make env` to show the commands): 38 | ```bash 39 | eval $(make env) 40 | s3d # aliased to ./target/debug/s3d 41 | eval $(make env RELEASE=1) 42 | s3d # aliased to ./target/release/s3d 43 | ``` 44 | -------------------------------------------------------------------------------- /docs/s3d-diagram.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/s3d-rs/s3d/6c23cc627ee4cc5f718efaeeb07fdec77cdeef46/docs/s3d-diagram.png -------------------------------------------------------------------------------- /docs/user-guide.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: User Guide 3 | --- 4 | 5 | > 🚧   **Warning - work in progress**   🚧 6 | > 7 | > This page is under development and most of the mentioned options are not yet available.\ 8 | > Apologies for the inconvenience. 9 | 10 | # User Guide 11 | 12 | To run the daemon in foreground: 13 | 14 | ```bash 15 | s3d run 16 | ``` 17 | 18 | Configuration using environment variables: 19 | 20 | - `S3D_LOCAL_DIR` - path to the local storage dir, default `$HOME/.s3d`. 21 | - `S3D_ENDPOINT` - S3 listen address, default `http://localhost:33333`. 22 | - `S3_ENDPOINT` - remote S3 address, default empty (SDK will choose default -> AWS). 23 | - `AWS_ACCESS_KEY_ID` - AWS access key ID, default empty (SDK will choose default). 24 | - `AWS_SECRET_ACCESS_KEY` - AWS secret access key, default empty (SDK will choose default). 25 | - `AWS_SESSION_TOKEN` - AWS session token, default empty (SDK will choose default). 26 | 27 | `s3d` uses the filesystem dir as a local storage, which is used for queueing, caching, and synching data from and to the remote storage. 28 | 29 | `s3d` reads the remote S3 config and credential files and environment variables 30 | just like any other S3 SDK client in order to connect to its remote storage. 31 | In addition, to support S3 compatible endpoints, it reads the `S3_ENDPOINT` environment variable. 32 | 33 | The credentials provided for `s3d` in the aws config files should be valid for the main storage, 34 | and the identity provided to `s3d` is the one it will use in all the requests to the main storage. 35 | 36 | To check and report the status of the daemon and the remote S3 storage, use: 37 | 38 | ```bash 39 | s3d status 40 | ``` 41 | 42 | # Write Queue 43 | 44 | Environment variables: 45 | 46 | - `S3D_WRITE_QUEUE` - true/false, default false. 47 | - `S3D_WRITE_QUEUE_DIR` - directory to store the queue, default `$S3D_LOCAL_DIR/write_queue`. 48 | - `S3D_WRITE_QUEUE_FILTER` - object filter to push, default all. 49 | - `S3D_WRITE_QUEUE_MAX_SIZE` - maximum size of the queue in bytes, default 1GB. 50 | - `S3D_WRITE_QUEUE_MAX_FILES` - maximum number of files in the queue, default 100. 51 | - `S3D_WRITE_QUEUE_MAX_AGE` - maximum age of writes in the queue in seconds, default 3600. 52 | 53 | When enabled, `s3d` first writes new objects to files in the local store, and will push them to the main storage in the background. This is to mitigate connection issues and improve performance. 54 | 55 | When the limits are exceeded, new write requests will not be added to the queue, instead it will wait for pending writes to push and make room for it. 56 | 57 | See filters syntax for fine grain control of which data to push. In order to dynamically change the filtering of an object that was not pushed, use put-object-tagging which can be used on an existing in the write queue. 58 | 59 | # Read Cache 60 | 61 | Environment variables: 62 | 63 | - `S3D_READ_CACHE` - true/false, default false. 64 | - `S3D_READ_CACHE_DIR` - directory to store the cache, default `$S3D_LOCAL_DIR/read_cache`. 65 | - `S3D_READ_CACHE_FILTER` - object filter to cache, default all. 66 | - `S3D_READ_CACHE_MAX_SIZE` - maximum size of the cache in bytes, default 1GB. 67 | - `S3D_READ_CACHE_MAX_FILES` - maximum number of files in the cache, default 100. 68 | - `S3D_READ_CACHE_MAX_AGE` - maximum age of files in the cache in seconds, default 3600. 69 | 70 | When enabled, `s3d` will store objects in the local store on read, in order to reduce egress costs and latency on repeated reads from the main storage. 71 | 72 | When the limits are exceeded, old items from the cache will be pruned before adding new items. 73 | 74 | See filters syntax for fine grain control of which data to cache. 75 | 76 | # Filters 77 | 78 | By default, `s3d` will include all objects eligible for write queueing, read caching, and folder syncing. However, for fine control over which objects to include, filters can be configured. 79 | 80 | Here are a few examples of a filters syntax: 81 | 82 | ```re 83 | bucket[tag:key] 84 | bucket[tag:key=value] 85 | bucket[tag:key!=value] 86 | bucket[tag:key=value][tag:key=value] 87 | bucket/prefix* 88 | bucket/prefix*[tag:key] 89 | bucket/prefix*[tag:key=value] 90 | bucket/prefix*[tag:key!=value] 91 | bucket/prefix*[tag:key1=value][tag:key2=value] 92 | bucket/prefix*[hdr:content-type=value] 93 | bucket/prefix*[hdr:content-length<100] 94 | bucket/prefix*[md:custom-meta-data=value] 95 | bucket[tag:key1=val1]/prefix*[tag:key2=val2][hdr:content-type='video/*'] 96 | ``` 97 | 98 | Tags provide a way to update the filtering of existing objects, 99 | for example using the S3 put-object-tagging API: 100 | 101 | ```bash 102 | alias s3api='aws --endpoint localhost:33333 s3api' 103 | s3api put-object-tagging --bucket bucket --key key --tagging '{"TagSet":[ 104 | { "Key": "s3d.upload", "Value": "false" } 105 | ]}' 106 | ``` 107 | 108 | Notice that put-object-tagging is overriding the entire tag set, so in order to add a tag to existing set, you will need to use get-object-tagging, append to the TagSet array and then put-object-tagging. 109 | 110 | # Sync Folder 111 | 112 | When enabled, `s3d` will perform a continuous bidirectional background sync of the remote buckets with a local dir (aka "dropbox folder"). 113 | 114 | The following environment variables can be used to configure the sync-folder: 115 | 116 | - `S3D_SYNC_FOLDER` - true/false, default false. 117 | - `S3D_SYNC_FOLDER_DIR` - directory to store the folder, default `$S3D_LOCAL_DIR/sync_folder`. 118 | - `S3D_SYNC_FOLDER_FILTER` - object filter to sync, default all. 119 | - `S3D_SYNC_FOLDER_MAX_SIZE` - maximum size of the folder in bytes, default 1GB. 120 | - `S3D_SYNC_FOLDER_MAX_FILES` - maximum number of files in the folder, default 100. 121 | - `S3D_SYNC_FOLDER_MAX_AGE` - maximum age of (unsync-ed) files in the folder in seconds, default 3600. 122 | 123 | When the limits are exceeded, sync will skip adding new data to the local folder. 124 | See filters syntax for fine grain control of which data to sync. 125 | 126 | # Fuse Mount 127 | 128 | When enabled, `s3d` will set up a FUSE mount point, which exposes the same buckets and objects through a POSIX-like file interface. 129 | 130 | The following environment variables can be used to configure the fuse-mount: 131 | 132 | - `S3D_FUSE_MOUNT` - true/false, default false. 133 | - `S3D_FUSE_MOUNT_DIR` - directory to bind the mount point, default `$S3D_LOCAL_DIR/fuse_mount`. 134 | 135 | # Kubernetes Deployment 136 | 137 | Container image for s3d can be built using the `Dockerfile` in the project root (also used in `make image`): 138 | ```bash 139 | IMG="//s3d:" 140 | docker build . -t $IMG 141 | docker push $IMG 142 | ``` 143 | 144 | For Kubernetes see `examples/s3d-kube-deploy.yaml` which you can use as a base, just REMEMBER to set the image to match the one you built and pushed above. 145 | 146 | ```bash 147 | # set image in yaml ... TODO kustomize 148 | kubectl apply -f examples/s3d-kube-deploy.yaml 149 | ``` 150 | -------------------------------------------------------------------------------- /examples/k8s-s3d-deployment.yaml: -------------------------------------------------------------------------------- 1 | # TODO: Work in progress 2 | --- 3 | apiVersion: apps/v1 4 | kind: Deployment 5 | metadata: 6 | name: s3d 7 | labels: 8 | app: s3d 9 | spec: 10 | replicas: 1 11 | selector: 12 | matchLabels: 13 | app: s3d 14 | template: 15 | metadata: 16 | labels: 17 | app: s3d 18 | spec: 19 | containers: 20 | - name: s3d 21 | image: s3d:dev 22 | ports: 23 | - containerPort: 33333 24 | env: 25 | - name: TODO 26 | value: "Work in progress" 27 | --- 28 | apiVersion: v1 29 | kind: Service 30 | metadata: 31 | name: s3d 32 | labels: 33 | app: s3d 34 | spec: 35 | type: NodePort 36 | selector: 37 | app: s3d 38 | ports: 39 | - port: 33333 40 | targetPort: 33333 41 | -------------------------------------------------------------------------------- /s3d.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/s3d-rs/s3d/6c23cc627ee4cc5f718efaeeb07fdec77cdeef46/s3d.png -------------------------------------------------------------------------------- /src/bin/s3.rs: -------------------------------------------------------------------------------- 1 | extern crate s3d; 2 | use clap::{IntoApp, Parser}; 3 | use std::fmt::Debug; 4 | 5 | #[tokio::main] 6 | pub async fn main() -> anyhow::Result<()> { 7 | // env_logger::init_from_env(env_logger::Env::default().default_filter_or("warn,s3d=info")); 8 | env_logger::init(); 9 | CLI::parse().run().await 10 | } 11 | 12 | #[derive(clap::Parser, Debug, Clone)] 13 | #[clap(name = "s3")] 14 | #[clap(about = "S3 CLI tool for applications or services that need to access S3 buckets (with/out the s3d daemon)")] 15 | #[clap(version = clap::crate_version!())] 16 | #[clap(setting = clap::AppSettings::DeriveDisplayOrder)] 17 | pub struct CLI { 18 | /// subcommand 19 | #[clap(subcommand)] 20 | cmd: Cmd, 21 | } 22 | 23 | impl CLI { 24 | pub async fn run(self) -> anyhow::Result<()> { 25 | log::debug!("{:?}", self); 26 | match self.cmd { 27 | Cmd::Api(cmd) => cmd.run().await, 28 | Cmd::List(cmd) => cmd.run().await, 29 | Cmd::Get(cmd) => cmd.run().await, 30 | Cmd::Put(cmd) => cmd.run().await, 31 | // Cmd::Tag(cmd) => cmd.run().await, 32 | // Cmd::Remote(cmd) => cmd.run().await, 33 | // Cmd::Status(cmd) => cmd.run().await, 34 | Cmd::Completion(cmd) => cmd.run(CLI::command()).await, 35 | } 36 | } 37 | } 38 | 39 | #[derive(clap::Subcommand, Debug, Clone)] 40 | enum Cmd { 41 | Api(s3d::cli::api_cmd::ApiCmd), 42 | List(s3d::cli::list_cmd::ListCmd), 43 | Get(s3d::cli::get_cmd::GetCmd), 44 | Put(s3d::cli::put_cmd::PutCmd), 45 | // Tag(s3d::cli::tag_cmd::TagCmd), 46 | // Remote(s3d::cli::remote_cmd::RemoteCmd), 47 | // Status(s3d::cli::status_cmd::StatusCmd), 48 | Completion(s3d::cli::completion_cmd::CompletionCmd), 49 | } 50 | 51 | // #[clap(about = "Init sets up config and local store for the daemon")] 52 | // #[clap(about = "Fetch metadata only from remote")] 53 | // #[clap(about = "Pull changes from remote")] 54 | // #[clap(about = "Push changes to remote")] 55 | // #[clap(about = "Prune objects from local store")] 56 | // #[clap(about = "Diff shows objects pending for pull/push")] 57 | -------------------------------------------------------------------------------- /src/bin/s3d.rs: -------------------------------------------------------------------------------- 1 | extern crate s3d; 2 | use clap::Parser; 3 | use std::fmt::Debug; 4 | 5 | #[tokio::main] 6 | pub async fn main() -> anyhow::Result<()> { 7 | // env_logger::init(); 8 | env_logger::init_from_env(env_logger::Env::default().default_filter_or("warn,s3d=info")); 9 | Daemon::parse().run().await 10 | } 11 | 12 | #[derive(clap::Parser, Debug, Clone)] 13 | #[clap(name = "s3d")] 14 | #[clap(about = clap::crate_description!())] 15 | #[clap(version = clap::crate_version!())] 16 | #[clap(setting = clap::AppSettings::DeriveDisplayOrder)] 17 | pub struct Daemon {} 18 | 19 | impl Daemon { 20 | pub async fn run(self) -> anyhow::Result<()> { 21 | log::debug!("{:?}", self); 22 | #[cfg(feature = "fuse")] 23 | { 24 | s3d::fuse::Fuse::start_fuse_mount().await?; 25 | } 26 | s3d::s3::server::serve().await?; 27 | Ok(()) 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/cli/api_cmd.rs: -------------------------------------------------------------------------------- 1 | // S3OpsCommands is generated into build_gen and contain a subcommand per operation in the model. 2 | use crate::codegen_include::S3OpsCommands; 3 | use crate::utils::{new_s3_client, staticify}; 4 | 5 | /// Call S3 API operations 6 | #[derive(clap::Parser, Debug, Clone)] 7 | pub struct ApiCmd { 8 | #[clap(subcommand)] 9 | op: S3OpsCommands, 10 | } 11 | 12 | impl ApiCmd { 13 | pub async fn run(&self) -> anyhow::Result<()> { 14 | let s3 = staticify(new_s3_client().await); 15 | self.op.run(s3).await; 16 | Ok(()) 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/cli/completion_cmd.rs: -------------------------------------------------------------------------------- 1 | /// Generates shell commpletion script 2 | #[derive(clap::Parser, Debug, Clone)] 3 | pub struct CompletionCmd { 4 | #[clap(arg_enum)] 5 | shell: clap_complete::Shell, 6 | } 7 | 8 | impl CompletionCmd { 9 | pub async fn run(&self, mut cmd: clap::Command<'_>) -> anyhow::Result<()> { 10 | let name = cmd.get_name().to_owned(); 11 | Ok(clap_complete::generate( 12 | self.shell, 13 | &mut cmd, 14 | name, 15 | &mut std::io::stdout(), 16 | )) 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/cli/get_cmd.rs: -------------------------------------------------------------------------------- 1 | use crate::utils::{new_s3_client, parse_bucket_and_key, pipe_stream_to_outfile_or_stdout}; 2 | 3 | /// Get object data to stdout, and meta-data and tags to stderr 4 | #[derive(clap::Parser, Debug, Clone)] 5 | pub struct GetCmd { 6 | /// Get object from `bucket/key` 7 | #[clap(name = "bucket/key")] 8 | bucket_and_key: String, 9 | 10 | /// Output file name, if not specified, stdout is used 11 | #[clap(name = "outfile")] 12 | outfile: Option, 13 | } 14 | 15 | impl GetCmd { 16 | pub async fn run(&self) -> anyhow::Result<()> { 17 | let s3 = new_s3_client().await; 18 | let (bucket, key) = parse_bucket_and_key(&self.bucket_and_key)?; 19 | let mut res = s3.get_object().bucket(bucket).key(key).send().await?; 20 | 21 | info!("{:#?}", res); 22 | 23 | let num_bytes = pipe_stream_to_outfile_or_stdout(&mut res.body, self.outfile.as_deref()).await?; 24 | 25 | info!("Received {} bytes", num_bytes); 26 | 27 | Ok(()) 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/cli/list_cmd.rs: -------------------------------------------------------------------------------- 1 | use crate::utils::{new_s3_client, parse_bucket_and_prefix}; 2 | use aws_smithy_types::date_time::Format; 3 | 4 | /// List buckets or objects 5 | #[derive(clap::Parser, Debug, Clone)] 6 | #[clap(aliases = &["ls"])] 7 | pub struct ListCmd { 8 | /// When empty list all buckets. 9 | /// Otherwise list objects in bucket with optional key prefix (`bucket` or `bucket/prefix`) 10 | #[clap(name = "bucket[/prefix]", default_value = "")] 11 | bucket_and_prefix: String, 12 | } 13 | 14 | impl ListCmd { 15 | pub async fn run(&self) -> anyhow::Result<()> { 16 | let s3 = new_s3_client().await; 17 | let (bucket, prefix) = parse_bucket_and_prefix(&self.bucket_and_prefix)?; 18 | 19 | if bucket.is_empty() { 20 | let res = s3.list_buckets().send().await?; 21 | for it in res.buckets.unwrap_or_default() { 22 | println!( 23 | "{} {}", 24 | it.creation_date().unwrap().fmt(Format::DateTime).unwrap(), 25 | it.name().unwrap() 26 | ); 27 | } 28 | } else { 29 | let res = s3 30 | .list_objects() 31 | .bucket(bucket) 32 | .prefix(prefix) 33 | .delimiter("/") 34 | .send() 35 | .await?; 36 | for it in res.common_prefixes.unwrap_or_default() { 37 | println!( 38 | "{:.^20} {:.>12} {}", 39 | "PREFIX", 40 | "", 41 | it.prefix().unwrap() 42 | ); 43 | } 44 | for it in res.contents.unwrap_or_default() { 45 | println!( 46 | "{:>20} {:.>12} {}", 47 | it.last_modified().unwrap().fmt(Format::DateTime).unwrap(), 48 | it.size(), 49 | it.key().unwrap(), 50 | ); 51 | } 52 | } 53 | 54 | Ok(()) 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/cli/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod api_cmd; 2 | pub mod completion_cmd; 3 | pub mod get_cmd; 4 | pub mod list_cmd; 5 | pub mod put_cmd; 6 | pub mod tag_cmd; 7 | -------------------------------------------------------------------------------- /src/cli/put_cmd.rs: -------------------------------------------------------------------------------- 1 | use crate::utils::{byte_stream_from_infile_or_stdin, new_s3_client, parse_bucket_and_key}; 2 | 3 | /// Put object from stdin 4 | #[derive(clap::Parser, Debug, Clone)] 5 | pub struct PutCmd { 6 | /// Put object in `bucket/key` 7 | #[clap(name = "bucket/key")] 8 | bucket_and_key: String, 9 | 10 | /// Input file name, if not specified, stdin is used 11 | #[clap(name = "infile")] 12 | infile: Option, 13 | } 14 | 15 | impl PutCmd { 16 | pub async fn run(&self) -> anyhow::Result<()> { 17 | let s3 = new_s3_client().await; 18 | let (bucket, key) = parse_bucket_and_key(&self.bucket_and_key)?; 19 | let body = byte_stream_from_infile_or_stdin(self.infile.as_deref()).await?; 20 | let res = s3 21 | .put_object() 22 | .bucket(bucket) 23 | .key(key) 24 | .body(body) 25 | .send() 26 | .await?; 27 | info!("{:#?}", res); 28 | 29 | Ok(()) 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/cli/tag_cmd.rs: -------------------------------------------------------------------------------- 1 | use crate::utils::{new_s3_client, parse_bucket_and_key}; 2 | 3 | /// Get or set tags for bucket or object 4 | #[derive(clap::Parser, Debug, Clone)] 5 | pub struct TagCmd { 6 | /// Set tags for `bucket` or `bucket/key` 7 | #[clap(name = "bucket[/key]")] 8 | bucket_and_key: String, 9 | 10 | /// Tag `name=value`. Can be used multiple times. 11 | #[clap(long, short, multiple_occurrences(true))] 12 | tag: Option>, 13 | 14 | /// Reset previous tags instead of appending 15 | #[clap(long, short)] 16 | reset: bool, 17 | } 18 | 19 | impl TagCmd { 20 | pub async fn run(&self) -> anyhow::Result<()> { 21 | let s3 = new_s3_client().await; 22 | let (bucket, key) = parse_bucket_and_key(&self.bucket_and_key)?; 23 | let tagging = aws_sdk_s3::model::Tagging::builder() 24 | .set_tag_set(self.tag.clone().map(|v| { 25 | v.iter() 26 | .map(|t| { 27 | let mut parts = t.splitn(2, '='); 28 | let k = parts.next().map(String::from); 29 | let v = parts.next().map(String::from); 30 | aws_sdk_s3::model::Tag::builder() 31 | .set_key(k) 32 | .set_value(v) 33 | .build() 34 | }) 35 | .collect::>() 36 | })) 37 | .build(); 38 | 39 | if tagging.tag_set().is_none() { 40 | if key.is_empty() { 41 | let res = s3.get_bucket_tagging().bucket(bucket).send().await?; 42 | info!("{:#?}", res); 43 | } else { 44 | let res = s3 45 | .get_object_tagging() 46 | .bucket(bucket) 47 | .key(key) 48 | .send() 49 | .await?; 50 | info!("{:#?}", res); 51 | } 52 | } else { 53 | if key.is_empty() { 54 | let res = s3 55 | .put_bucket_tagging() 56 | .bucket(bucket) 57 | .tagging(tagging) 58 | .send() 59 | .await?; 60 | info!("{:#?}", res); 61 | } else { 62 | let res = s3 63 | .put_object_tagging() 64 | .bucket(bucket) 65 | .key(key) 66 | .tagging(tagging) 67 | .send() 68 | .await?; 69 | info!("{:#?}", res); 70 | } 71 | } 72 | 73 | Ok(()) 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/codegen_include.rs: -------------------------------------------------------------------------------- 1 | //! This includes files generated by build.rs. 2 | #![allow(unused)] 3 | include!(concat!(env!("OUT_DIR"), "/s3_cli.rs")); 4 | include!(concat!(env!("OUT_DIR"), "/s3_conv.rs")); 5 | -------------------------------------------------------------------------------- /src/config.rs: -------------------------------------------------------------------------------- 1 | macro_rules! env_config { 2 | ($env:ident optional) => { 3 | lazy_static::lazy_static! { 4 | pub static ref $env : Option = std::env::var(stringify!($env)).ok(); 5 | } 6 | }; 7 | ($env:ident required) => { 8 | lazy_static::lazy_static! { 9 | pub static ref $env : String = std::env::var(stringify!($env)).unwrap(); 10 | } 11 | }; 12 | ($env:ident default $default_val:expr) => { 13 | lazy_static::lazy_static! { 14 | pub static ref $env : String = std::env::var(stringify!($env)).unwrap_or(($default_val).to_string()); 15 | } 16 | }; 17 | } 18 | 19 | env_config!(HOME required); 20 | env_config!(S3D_LOCAL_DIR default ".s3d"); 21 | env_config!(S3D_ENDPOINT default "http://localhost:33333"); 22 | 23 | env_config!(S3_ENDPOINT optional); 24 | env_config!(S3_ACCESS_KEY optional); 25 | env_config!(S3_SECRET_KEY optional); 26 | 27 | env_config!(S3D_WRITE_QUEUE default "false"); 28 | env_config!(S3D_WRITE_QUEUE_DIR default format!("{}/write_queue", *S3D_LOCAL_DIR)); 29 | env_config!(S3D_WRITE_QUEUE_FILTER optional); 30 | env_config!(S3D_WRITE_QUEUE_MAX_SIZE optional); 31 | env_config!(S3D_WRITE_QUEUE_MAX_FILES optional); 32 | env_config!(S3D_WRITE_QUEUE_MAX_AGE optional); 33 | 34 | env_config!(S3D_READ_CACHE default "false"); 35 | env_config!(S3D_READ_CACHE_DIR default format!("{}/read_cache", *S3D_LOCAL_DIR)); 36 | env_config!(S3D_READ_CACHE_FILTER optional); 37 | env_config!(S3D_READ_CACHE_MAX_SIZE optional); 38 | env_config!(S3D_READ_CACHE_MAX_FILES optional); 39 | env_config!(S3D_READ_CACHE_MAX_AGE optional); 40 | 41 | env_config!(S3D_SYNC_FOLDER default "false"); 42 | env_config!(S3D_SYNC_FOLDER_DIR default format!("{}/sync_folder", *S3D_LOCAL_DIR)); 43 | env_config!(S3D_SYNC_FOLDER_FILTER optional); 44 | env_config!(S3D_SYNC_FOLDER_MAX_SIZE optional); 45 | env_config!(S3D_SYNC_FOLDER_MAX_FILES optional); 46 | env_config!(S3D_SYNC_FOLDER_MAX_AGE optional); 47 | 48 | env_config!(S3D_FUSE_MOUNT default "false"); 49 | env_config!(S3D_FUSE_MOUNT_DIR default format!("{}/fuse_mount", *S3D_LOCAL_DIR)); 50 | -------------------------------------------------------------------------------- /src/fuse.rs: -------------------------------------------------------------------------------- 1 | //! FUSE 2 | //! 3 | //! - Filesystems in the Linux kernel - FUSE 4 | //! https://www.kernel.org/doc/html/latest/filesystems/fuse.html 5 | //! 6 | //! - To FUSE or Not to FUSE: Performance of User-Space File Systems 7 | //! https://www.usenix.org/system/files/conference/fast17/fast17-vangoor.pdf 8 | 9 | use crate::config; 10 | use crate::utils::staticify; 11 | use fuser::{FileAttr, FileType, Filesystem, Request}; 12 | use std::time::Duration; 13 | 14 | pub const BLOCK_SIZE: u32 = 4096; 15 | pub const NAMELEN: u32 = 1024; 16 | pub const KB: u64 = 1u64 << 10; 17 | pub const MB: u64 = 1u64 << 20; 18 | pub const GB: u64 = 1u64 << 30; 19 | pub const TB: u64 = 1u64 << 40; 20 | pub const PB: u64 = 1u64 << 50; 21 | 22 | pub struct Fuse {} 23 | 24 | impl Fuse { 25 | pub async fn start_fuse_mount() -> anyhow::Result<()> { 26 | if *config::S3D_FUSE_MOUNT != "true" { 27 | debug!("Fuse mount disabled"); 28 | return Ok(()); 29 | } 30 | info!("Fuse mount enabled"); 31 | 32 | let fuse = staticify(Fuse {}); 33 | 34 | let mountpoint = config::S3D_FUSE_MOUNT_DIR.as_str(); 35 | if mountpoint.is_empty() { 36 | return Ok(()); 37 | } 38 | let mut session = fuser::Session::new( 39 | fuse, 40 | mountpoint.as_ref(), 41 | &[ 42 | fuser::MountOption::RW, 43 | // fuser::MountOption::RO, 44 | // fuser::MountOption::Sync, 45 | // fuser::MountOption::DirSync, 46 | // fuser::MountOption::Async, 47 | fuser::MountOption::AllowRoot, 48 | fuser::MountOption::AllowOther, 49 | fuser::MountOption::AutoUnmount, 50 | fuser::MountOption::DefaultPermissions, 51 | fuser::MountOption::NoDev, 52 | fuser::MountOption::NoSuid, 53 | fuser::MountOption::NoAtime, 54 | fuser::MountOption::CUSTOM("nobrowse".to_string()), 55 | fuser::MountOption::FSName("s3d".to_string()), 56 | fuser::MountOption::Subtype("s3d".to_string()), 57 | ], 58 | )?; 59 | // run the fuse event loop in a separate thread 60 | let res = tokio::task::spawn_blocking(move || session.run()).await; 61 | Ok(res??) 62 | } 63 | 64 | fn make_fuse_attr(&self, ino: u64, kind: FileType, size: u64) -> FileAttr { 65 | let now = std::time::SystemTime::now(); 66 | FileAttr { 67 | ino, // inode's number 68 | size, 69 | blocks: (size + (BLOCK_SIZE as u64) - 1) / BLOCK_SIZE as u64, 70 | blksize: BLOCK_SIZE, 71 | kind, 72 | rdev: 0, // device type, for special file inode 73 | uid: unsafe { libc::geteuid() }, // user-id of owner 74 | gid: unsafe { libc::getegid() }, // group-id of owner 75 | perm: if kind == FileType::Directory { 76 | 0o755 77 | } else { 78 | 0o644 79 | }, // inode protection mode 80 | nlink: if kind == FileType::Directory { 81 | 2 // parent + '.' + (subdirs * '..') 82 | } else { 83 | 1 84 | }, // number of hard links to the file 85 | flags: 0, 86 | atime: now, 87 | mtime: now, 88 | ctime: now, 89 | crtime: now, 90 | } 91 | } 92 | } 93 | 94 | impl Filesystem for &Fuse { 95 | fn statfs(&mut self, _req: &Request<'_>, ino: u64, reply: fuser::ReplyStatfs) { 96 | trace!("FUSE::statfs() ino={}", ino); 97 | reply.statfs( 98 | 42, // total data blocks in file system 99 | 1u64 << 38, // free blocks in fs 100 | 1u64 << 38, // free blocks avail to non-superuser 101 | 42, // total file nodes in file system 102 | 1_000_000_000, // free file nodes in fs 103 | BLOCK_SIZE, // fundamental file system block size 104 | 1024, // namelen 105 | 1024 * 1024, // optimal transfer block size 106 | ); 107 | } 108 | 109 | fn open(&mut self, _req: &Request<'_>, ino: u64, flags: i32, reply: fuser::ReplyOpen) { 110 | trace!("FUSE::open() ino={} flags={}", ino, flags); 111 | reply.opened(ino, flags as u32); 112 | } 113 | 114 | fn release( 115 | &mut self, 116 | _req: &Request<'_>, 117 | ino: u64, 118 | fh: u64, 119 | flags: i32, 120 | lock_owner: Option, 121 | flush: bool, 122 | reply: fuser::ReplyEmpty, 123 | ) { 124 | trace!( 125 | "FUSE::release() ino={} fh={} flags={} lock_owner={:?} flush={}", 126 | ino, 127 | fh, 128 | flags, 129 | lock_owner, 130 | flush 131 | ); 132 | reply.ok(); 133 | } 134 | 135 | fn opendir(&mut self, _req: &Request<'_>, ino: u64, flags: i32, reply: fuser::ReplyOpen) { 136 | trace!("FUSE::opendir() ino={} flags={}", ino, flags); 137 | if ino < 1000 { 138 | reply.opened(ino, flags as u32); 139 | } else { 140 | reply.error(libc::ENOTDIR); 141 | } 142 | } 143 | 144 | fn releasedir( 145 | &mut self, 146 | _req: &Request<'_>, 147 | ino: u64, 148 | fh: u64, 149 | flags: i32, 150 | reply: fuser::ReplyEmpty, 151 | ) { 152 | trace!("FUSE::releasedir() ino={} fh={} flags={}", ino, fh, flags); 153 | if ino < 1000 { 154 | reply.ok(); 155 | } else { 156 | reply.error(libc::ENOTDIR); 157 | } 158 | } 159 | 160 | fn lookup( 161 | &mut self, 162 | _req: &Request<'_>, 163 | ino: u64, 164 | name: &std::ffi::OsStr, 165 | reply: fuser::ReplyEntry, 166 | ) { 167 | let name = name.to_str().unwrap(); 168 | trace!("FUSE::lookup() ino={} name={}", ino, name); 169 | if ino >= 1000 { 170 | reply.error(libc::ENOTDIR); 171 | return; 172 | } 173 | if !name.starts_with("file") { 174 | reply.error(libc::ENOENT); 175 | return; 176 | } 177 | let i = name[4..].parse::().unwrap(); 178 | let kind = if i < 1000 { 179 | FileType::Directory 180 | } else { 181 | FileType::RegularFile 182 | }; 183 | let attr = self.make_fuse_attr(i, kind, 10); 184 | let ttl = Duration::from_secs(60); 185 | reply.entry(&ttl, &attr, 0); 186 | } 187 | 188 | fn readdir( 189 | &mut self, 190 | _req: &Request<'_>, 191 | ino: u64, 192 | fh: u64, 193 | offset: i64, 194 | mut reply: fuser::ReplyDirectory, 195 | ) { 196 | trace!("FUSE::readdir() ino={} fh={} offset={}", ino, fh, offset); 197 | if ino >= 1000 { 198 | reply.error(libc::ENOTDIR); 199 | return; 200 | } 201 | for i in 1000..1003 as u64 { 202 | if i > offset as u64 { 203 | if reply.add(i, i as i64, FileType::RegularFile, &format!("file{}", i)) { 204 | break; 205 | } 206 | } 207 | } 208 | reply.ok(); 209 | } 210 | 211 | fn readdirplus( 212 | &mut self, 213 | _req: &Request<'_>, 214 | ino: u64, 215 | fh: u64, 216 | offset: i64, 217 | mut reply: fuser::ReplyDirectoryPlus, 218 | ) { 219 | trace!( 220 | "FUSE::readdirplus() ino={} fh={} offset={}", 221 | ino, 222 | fh, 223 | offset 224 | ); 225 | if ino >= 1000 { 226 | reply.error(libc::ENOTDIR); 227 | return; 228 | } 229 | let ttl = Duration::from_secs(60); 230 | for i in 1000..1003 as u64 { 231 | if i >= offset as u64 { 232 | let attr = self.make_fuse_attr(i as u64, FileType::RegularFile, 10); 233 | reply.add(i, i as i64, &format!("file{}", i), &ttl, &attr, 0); 234 | } 235 | } 236 | reply.ok(); 237 | } 238 | 239 | fn getattr(&mut self, _req: &Request<'_>, ino: u64, reply: fuser::ReplyAttr) { 240 | trace!("FUSE::getattr() ino={}", ino); 241 | let ttl = Duration::from_secs(60); 242 | if ino < 1000 { 243 | let attr = self.make_fuse_attr(ino, FileType::Directory, 10); 244 | reply.attr(&ttl, &attr); 245 | } else { 246 | let attr = self.make_fuse_attr(ino, FileType::RegularFile, 10); 247 | reply.attr(&ttl, &attr); 248 | } 249 | } 250 | 251 | fn read( 252 | &mut self, 253 | _req: &Request<'_>, 254 | ino: u64, 255 | fh: u64, 256 | offset: i64, 257 | size: u32, 258 | flags: i32, 259 | lock_owner: Option, 260 | reply: fuser::ReplyData, 261 | ) { 262 | trace!( 263 | "FUSE::read() ino={} fh={} offset={} size={} flags={} lock_owner={:?}", 264 | ino, 265 | fh, 266 | offset, 267 | size, 268 | flags, 269 | lock_owner 270 | ); 271 | if ino < 1000 { 272 | reply.error(libc::EISDIR); 273 | } else { 274 | reply.data("0123456789\n".to_string().as_bytes()); 275 | } 276 | } 277 | } 278 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! `s3d` is an S3 daemon for the Edge written in Rust 2 | //! - https://s3d.rs 3 | //! - https://github.com/s3d-rs/s3d 4 | 5 | // #![doc = include_str!("../README.md")] 6 | // #![allow(unused)] 7 | 8 | pub mod cli; 9 | pub mod codegen_include; 10 | pub mod config; 11 | pub mod s3; 12 | pub mod utils; 13 | pub mod write_queue; 14 | 15 | #[cfg(feature = "fuse")] 16 | pub mod fuse; 17 | 18 | #[macro_use] 19 | extern crate log; 20 | 21 | // #[macro_use] 22 | // extern crate clap; 23 | 24 | // #[macro_use] 25 | // extern crate anyhow; 26 | -------------------------------------------------------------------------------- /src/s3/api.rs: -------------------------------------------------------------------------------- 1 | use crate::utils::to_internal_err; 2 | use std::future::Future; 3 | use std::pin::Pin; 4 | 5 | /// Why we need this TraitFuture: 6 | /// We can't use async_trait macro inside our macro so we use the same thing it does 7 | /// which is this pin-box-dyn-future - see long explanation here: 8 | /// https://smallcultfollowing.com/babysteps/blog/2019/10/26/async-fn-in-traits-are-hard/ 9 | pub type TraitFuture<'a, O, E> = Pin> + Send + 'a>>; 10 | 11 | pub type SMClient = aws_smithy_client::Client< 12 | aws_smithy_client::erase::DynConnector, 13 | aws_sdk_s3::middleware::DefaultMiddleware, 14 | >; 15 | 16 | macro_rules! s3_op_trait { 17 | ($op:ident) => { 18 | paste::paste! { 19 | fn [<$op:snake>](&self, i: s3d_smithy_codegen_server_s3::input::[<$op Input>]) 20 | -> TraitFuture< 21 | s3d_smithy_codegen_server_s3::output::[<$op Output>], 22 | s3d_smithy_codegen_server_s3::error::[<$op Error>], 23 | >; 24 | } 25 | }; 26 | } 27 | 28 | macro_rules! s3_op_impl { 29 | ($op:ident) => { 30 | paste::paste! { 31 | fn [<$op:snake>](&self, i: s3d_smithy_codegen_server_s3::input::[<$op Input>]) -> 32 | TraitFuture< 33 | s3d_smithy_codegen_server_s3::output::[<$op Output>], 34 | s3d_smithy_codegen_server_s3::error::[<$op Error>], 35 | > 36 | { 37 | Box::pin(async move { 38 | info!("{}: {:?}", stringify!([<$op:snake>]), i); 39 | let to_client = crate::codegen_include::[]; 40 | let from_client = crate::codegen_include::[]; 41 | let r = self.sm_client 42 | .call(to_client(i).make_operation(self.s3_client.conf()).await.unwrap()) 43 | .await 44 | .map(from_client) 45 | .map_err(to_internal_err); 46 | info!("{}: {:?}", stringify!([<$op:snake>]), r); 47 | r 48 | }) 49 | } 50 | } 51 | }; 52 | } 53 | 54 | pub trait S3Api { 55 | // LIST OPS 56 | s3_op_trait!(ListBuckets); 57 | s3_op_trait!(ListObjects); 58 | s3_op_trait!(ListObjectsV2); 59 | s3_op_trait!(ListObjectVersions); 60 | // SIMPLE OBJECT OPS 61 | s3_op_trait!(HeadObject); 62 | s3_op_trait!(GetObject); 63 | s3_op_trait!(PutObject); 64 | s3_op_trait!(CopyObject); 65 | s3_op_trait!(DeleteObject); 66 | s3_op_trait!(DeleteObjects); 67 | s3_op_trait!(GetObjectTagging); 68 | s3_op_trait!(PutObjectTagging); 69 | s3_op_trait!(DeleteObjectTagging); 70 | // SIMPLE BUCKET OPS 71 | s3_op_trait!(HeadBucket); 72 | s3_op_trait!(CreateBucket); 73 | s3_op_trait!(DeleteBucket); 74 | s3_op_trait!(GetBucketTagging); 75 | s3_op_trait!(PutBucketTagging); 76 | s3_op_trait!(DeleteBucketTagging); 77 | // MULTIPART UPLOAD OPS 78 | s3_op_trait!(CreateMultipartUpload); 79 | s3_op_trait!(CompleteMultipartUpload); 80 | s3_op_trait!(AbortMultipartUpload); 81 | s3_op_trait!(ListMultipartUploads); 82 | s3_op_trait!(ListParts); 83 | s3_op_trait!(UploadPart); 84 | s3_op_trait!(UploadPartCopy); 85 | // ADVANCED OBJECT OPS 86 | s3_op_trait!(GetObjectAcl); 87 | s3_op_trait!(PutObjectAcl); 88 | s3_op_trait!(GetObjectLegalHold); 89 | s3_op_trait!(PutObjectLegalHold); 90 | s3_op_trait!(GetObjectLockConfiguration); 91 | s3_op_trait!(PutObjectLockConfiguration); 92 | s3_op_trait!(GetObjectRetention); 93 | s3_op_trait!(PutObjectRetention); 94 | s3_op_trait!(GetObjectTorrent); 95 | s3_op_trait!(RestoreObject); 96 | // ADVANCED BUCKET OPS 97 | s3_op_trait!(GetBucketAccelerateConfiguration); 98 | s3_op_trait!(GetBucketAcl); 99 | s3_op_trait!(GetBucketAnalyticsConfiguration); 100 | s3_op_trait!(GetBucketCors); 101 | s3_op_trait!(GetBucketEncryption); 102 | s3_op_trait!(GetBucketIntelligentTieringConfiguration); 103 | s3_op_trait!(GetBucketInventoryConfiguration); 104 | s3_op_trait!(GetBucketLifecycleConfiguration); 105 | s3_op_trait!(GetBucketLocation); 106 | s3_op_trait!(GetBucketLogging); 107 | s3_op_trait!(GetBucketMetricsConfiguration); 108 | s3_op_trait!(GetBucketNotificationConfiguration); 109 | s3_op_trait!(GetBucketOwnershipControls); 110 | s3_op_trait!(GetBucketPolicy); 111 | s3_op_trait!(GetBucketPolicyStatus); 112 | s3_op_trait!(GetBucketReplication); 113 | s3_op_trait!(GetBucketRequestPayment); 114 | s3_op_trait!(GetBucketVersioning); 115 | s3_op_trait!(GetBucketWebsite); 116 | s3_op_trait!(GetPublicAccessBlock); 117 | s3_op_trait!(PutBucketAccelerateConfiguration); 118 | s3_op_trait!(PutBucketAcl); 119 | s3_op_trait!(PutBucketAnalyticsConfiguration); 120 | s3_op_trait!(PutBucketCors); 121 | s3_op_trait!(PutBucketEncryption); 122 | s3_op_trait!(PutBucketIntelligentTieringConfiguration); 123 | s3_op_trait!(PutBucketInventoryConfiguration); 124 | s3_op_trait!(PutBucketLifecycleConfiguration); 125 | s3_op_trait!(PutBucketLogging); 126 | s3_op_trait!(PutBucketMetricsConfiguration); 127 | s3_op_trait!(PutBucketNotificationConfiguration); 128 | s3_op_trait!(PutBucketOwnershipControls); 129 | s3_op_trait!(PutBucketPolicy); 130 | s3_op_trait!(PutBucketReplication); 131 | s3_op_trait!(PutBucketRequestPayment); 132 | s3_op_trait!(PutBucketVersioning); 133 | s3_op_trait!(PutBucketWebsite); 134 | s3_op_trait!(PutPublicAccessBlock); 135 | s3_op_trait!(WriteGetObjectResponse); 136 | s3_op_trait!(DeleteBucketAnalyticsConfiguration); 137 | s3_op_trait!(DeleteBucketCors); 138 | s3_op_trait!(DeleteBucketEncryption); 139 | s3_op_trait!(DeleteBucketIntelligentTieringConfiguration); 140 | s3_op_trait!(DeleteBucketInventoryConfiguration); 141 | s3_op_trait!(DeleteBucketLifecycle); 142 | s3_op_trait!(DeleteBucketMetricsConfiguration); 143 | s3_op_trait!(DeleteBucketOwnershipControls); 144 | s3_op_trait!(DeleteBucketPolicy); 145 | s3_op_trait!(DeleteBucketReplication); 146 | s3_op_trait!(DeleteBucketWebsite); 147 | s3_op_trait!(DeletePublicAccessBlock); 148 | s3_op_trait!(ListBucketAnalyticsConfigurations); 149 | s3_op_trait!(ListBucketIntelligentTieringConfigurations); 150 | s3_op_trait!(ListBucketInventoryConfigurations); 151 | s3_op_trait!(ListBucketMetricsConfigurations); 152 | } 153 | 154 | pub struct S3ApiClient { 155 | sm_client: &'static SMClient, 156 | s3_client: &'static aws_sdk_s3::Client, 157 | } 158 | 159 | impl S3Api for S3ApiClient { 160 | // LIST OPS 161 | s3_op_impl!(ListBuckets); 162 | s3_op_impl!(ListObjects); 163 | s3_op_impl!(ListObjectsV2); 164 | s3_op_impl!(ListObjectVersions); 165 | // SIMPLE OBJECT OPS 166 | s3_op_impl!(HeadObject); 167 | s3_op_impl!(GetObject); 168 | s3_op_impl!(PutObject); 169 | s3_op_impl!(CopyObject); 170 | s3_op_impl!(DeleteObject); 171 | s3_op_impl!(DeleteObjects); 172 | s3_op_impl!(GetObjectTagging); 173 | s3_op_impl!(PutObjectTagging); 174 | s3_op_impl!(DeleteObjectTagging); 175 | // SIMPLE BUCKET OPS 176 | s3_op_impl!(HeadBucket); 177 | s3_op_impl!(CreateBucket); 178 | s3_op_impl!(DeleteBucket); 179 | s3_op_impl!(GetBucketTagging); 180 | s3_op_impl!(PutBucketTagging); 181 | s3_op_impl!(DeleteBucketTagging); 182 | // MULTIPART UPLOAD OPS 183 | s3_op_impl!(CreateMultipartUpload); 184 | s3_op_impl!(CompleteMultipartUpload); 185 | s3_op_impl!(AbortMultipartUpload); 186 | s3_op_impl!(ListMultipartUploads); 187 | s3_op_impl!(ListParts); 188 | s3_op_impl!(UploadPart); 189 | s3_op_impl!(UploadPartCopy); 190 | // ADVANCED OBJECT OPS 191 | s3_op_impl!(GetObjectAcl); 192 | s3_op_impl!(PutObjectAcl); 193 | s3_op_impl!(GetObjectLegalHold); 194 | s3_op_impl!(PutObjectLegalHold); 195 | s3_op_impl!(GetObjectLockConfiguration); 196 | s3_op_impl!(PutObjectLockConfiguration); 197 | s3_op_impl!(GetObjectRetention); 198 | s3_op_impl!(PutObjectRetention); 199 | s3_op_impl!(GetObjectTorrent); 200 | s3_op_impl!(RestoreObject); 201 | // ADVANCED BUCKET OPS 202 | s3_op_impl!(GetBucketAccelerateConfiguration); 203 | s3_op_impl!(GetBucketAcl); 204 | s3_op_impl!(GetBucketAnalyticsConfiguration); 205 | s3_op_impl!(GetBucketCors); 206 | s3_op_impl!(GetBucketEncryption); 207 | s3_op_impl!(GetBucketIntelligentTieringConfiguration); 208 | s3_op_impl!(GetBucketInventoryConfiguration); 209 | s3_op_impl!(GetBucketLifecycleConfiguration); 210 | s3_op_impl!(GetBucketLocation); 211 | s3_op_impl!(GetBucketLogging); 212 | s3_op_impl!(GetBucketMetricsConfiguration); 213 | s3_op_impl!(GetBucketNotificationConfiguration); 214 | s3_op_impl!(GetBucketOwnershipControls); 215 | s3_op_impl!(GetBucketPolicy); 216 | s3_op_impl!(GetBucketPolicyStatus); 217 | s3_op_impl!(GetBucketReplication); 218 | s3_op_impl!(GetBucketRequestPayment); 219 | s3_op_impl!(GetBucketVersioning); 220 | s3_op_impl!(GetBucketWebsite); 221 | s3_op_impl!(GetPublicAccessBlock); 222 | s3_op_impl!(PutBucketAccelerateConfiguration); 223 | s3_op_impl!(PutBucketAcl); 224 | s3_op_impl!(PutBucketAnalyticsConfiguration); 225 | s3_op_impl!(PutBucketCors); 226 | s3_op_impl!(PutBucketEncryption); 227 | s3_op_impl!(PutBucketIntelligentTieringConfiguration); 228 | s3_op_impl!(PutBucketInventoryConfiguration); 229 | s3_op_impl!(PutBucketLifecycleConfiguration); 230 | s3_op_impl!(PutBucketLogging); 231 | s3_op_impl!(PutBucketMetricsConfiguration); 232 | s3_op_impl!(PutBucketNotificationConfiguration); 233 | s3_op_impl!(PutBucketOwnershipControls); 234 | s3_op_impl!(PutBucketPolicy); 235 | s3_op_impl!(PutBucketReplication); 236 | s3_op_impl!(PutBucketRequestPayment); 237 | s3_op_impl!(PutBucketVersioning); 238 | s3_op_impl!(PutBucketWebsite); 239 | s3_op_impl!(PutPublicAccessBlock); 240 | s3_op_impl!(WriteGetObjectResponse); 241 | s3_op_impl!(DeleteBucketAnalyticsConfiguration); 242 | s3_op_impl!(DeleteBucketCors); 243 | s3_op_impl!(DeleteBucketEncryption); 244 | s3_op_impl!(DeleteBucketIntelligentTieringConfiguration); 245 | s3_op_impl!(DeleteBucketInventoryConfiguration); 246 | s3_op_impl!(DeleteBucketLifecycle); 247 | s3_op_impl!(DeleteBucketMetricsConfiguration); 248 | s3_op_impl!(DeleteBucketOwnershipControls); 249 | s3_op_impl!(DeleteBucketPolicy); 250 | s3_op_impl!(DeleteBucketReplication); 251 | s3_op_impl!(DeleteBucketWebsite); 252 | s3_op_impl!(DeletePublicAccessBlock); 253 | s3_op_impl!(ListBucketAnalyticsConfigurations); 254 | s3_op_impl!(ListBucketIntelligentTieringConfigurations); 255 | s3_op_impl!(ListBucketInventoryConfigurations); 256 | s3_op_impl!(ListBucketMetricsConfigurations); 257 | } 258 | -------------------------------------------------------------------------------- /src/s3/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod api; 2 | pub mod server; 3 | -------------------------------------------------------------------------------- /src/s3/server.rs: -------------------------------------------------------------------------------- 1 | use crate::config; 2 | use crate::utils::{staticify, to_internal_err}; 3 | use crate::write_queue::WriteQueue; 4 | use s3d_smithy_codegen_server_s3::{input::*, operation_registry::*}; 5 | 6 | pub type Router = aws_smithy_http_server::Router; 7 | 8 | pub type SMClient = aws_smithy_client::Client< 9 | aws_smithy_client::erase::DynConnector, 10 | aws_sdk_s3::middleware::DefaultMiddleware, 11 | >; 12 | 13 | pub async fn serve() -> anyhow::Result<()> { 14 | let s3_config = aws_config::load_from_env().await; 15 | let s3_client = staticify(aws_sdk_s3::Client::new(&s3_config)); 16 | let sleep_impl = aws_smithy_async::rt::sleep::default_async_sleep(); 17 | let sm_builder = aws_sdk_s3::client::Builder::dyn_https() 18 | .sleep_impl(sleep_impl) 19 | .middleware(aws_sdk_s3::middleware::DefaultMiddleware::new()); 20 | let sm_client = staticify(sm_builder.build()); 21 | let write_queue = staticify(WriteQueue { 22 | s3_client, 23 | write_queue_dir: config::S3D_WRITE_QUEUE_DIR.to_string(), 24 | }); 25 | write_queue.start(); 26 | let addr = std::net::SocketAddr::from(([127, 0, 0, 1], 33333)); 27 | let router = build_router(sm_client, s3_client, write_queue); 28 | let server = hyper::Server::bind(&addr).serve(router.into_make_service()); 29 | info!("###################################"); 30 | info!("Listening on http://{}", addr); 31 | info!("###################################"); 32 | server.await?; 33 | Ok(()) 34 | } 35 | 36 | pub fn build_router( 37 | sm_client: &'static SMClient, 38 | s3_client: &'static aws_sdk_s3::Client, 39 | write_queue: &'static WriteQueue, 40 | ) -> Router { 41 | let mut b = OperationRegistryBuilder::default(); 42 | 43 | macro_rules! register_s3_gateway_op { 44 | ($op:ident) => { 45 | paste::paste! { 46 | b = b.[<$op:snake>](move |i: [<$op Input>]| async { 47 | info!("{}: {:?}", stringify!([<$op:snake>]), i); 48 | let to_client = crate::codegen_include::[]; 49 | let from_client = crate::codegen_include::[]; 50 | let r = sm_client 51 | .call(to_client(i).make_operation(s3_client.conf()).await.unwrap()) 52 | .await 53 | .map(from_client) 54 | .map_err(to_internal_err); 55 | info!("{}: {:?}", stringify!([<$op:snake>]), r); 56 | r 57 | }); 58 | } 59 | }; 60 | } 61 | 62 | b = b.put_object(move |i: PutObjectInput| async { 63 | info!("put_object: {:?}", i); 64 | write_queue.put_object(i).await 65 | }); 66 | 67 | b = b.get_object(move |i: GetObjectInput| async move { 68 | info!("get_object: {:?}", i); 69 | let i1 = i.to_owned(); 70 | let i2 = i.to_owned(); 71 | let qres = write_queue.get_object(i1).await; 72 | if qres.is_ok() { 73 | return qres; 74 | } 75 | info!("get_object: read from remote"); 76 | let to_client = crate::codegen_include::conv_to_client_get_object_input; 77 | let from_client = crate::codegen_include::conv_from_client_get_object_output; 78 | let r = sm_client 79 | .call( 80 | to_client(i2) 81 | .make_operation(s3_client.conf()) 82 | .await 83 | .unwrap(), 84 | ) 85 | .await 86 | .map(from_client) 87 | .map_err(to_internal_err); 88 | info!("get_object: read from remote {:?}", r); 89 | r 90 | }); 91 | 92 | // LIST OPS 93 | register_s3_gateway_op!(ListBuckets); 94 | register_s3_gateway_op!(ListObjects); 95 | register_s3_gateway_op!(ListObjectsV2); 96 | register_s3_gateway_op!(ListObjectVersions); 97 | // SIMPLE OBJECT OPS 98 | register_s3_gateway_op!(HeadObject); 99 | register_s3_gateway_op!(CopyObject); 100 | register_s3_gateway_op!(DeleteObject); 101 | register_s3_gateway_op!(DeleteObjects); 102 | register_s3_gateway_op!(GetObjectTagging); 103 | register_s3_gateway_op!(PutObjectTagging); 104 | register_s3_gateway_op!(DeleteObjectTagging); 105 | // SIMPLE BUCKET OPS 106 | register_s3_gateway_op!(HeadBucket); 107 | register_s3_gateway_op!(CreateBucket); 108 | register_s3_gateway_op!(DeleteBucket); 109 | register_s3_gateway_op!(GetBucketTagging); 110 | register_s3_gateway_op!(PutBucketTagging); 111 | register_s3_gateway_op!(DeleteBucketTagging); 112 | // MULTIPART UPLOAD OPS 113 | register_s3_gateway_op!(CreateMultipartUpload); 114 | register_s3_gateway_op!(CompleteMultipartUpload); 115 | register_s3_gateway_op!(AbortMultipartUpload); 116 | register_s3_gateway_op!(ListMultipartUploads); 117 | register_s3_gateway_op!(ListParts); 118 | register_s3_gateway_op!(UploadPart); 119 | register_s3_gateway_op!(UploadPartCopy); 120 | // ADVANCED OBJECT OPS 121 | register_s3_gateway_op!(GetObjectAcl); 122 | register_s3_gateway_op!(PutObjectAcl); 123 | register_s3_gateway_op!(GetObjectLegalHold); 124 | register_s3_gateway_op!(PutObjectLegalHold); 125 | register_s3_gateway_op!(GetObjectLockConfiguration); 126 | register_s3_gateway_op!(PutObjectLockConfiguration); 127 | register_s3_gateway_op!(GetObjectRetention); 128 | register_s3_gateway_op!(PutObjectRetention); 129 | register_s3_gateway_op!(GetObjectTorrent); 130 | register_s3_gateway_op!(RestoreObject); 131 | // ADVANCED BUCKET OPS 132 | register_s3_gateway_op!(GetBucketAccelerateConfiguration); 133 | register_s3_gateway_op!(GetBucketAcl); 134 | register_s3_gateway_op!(GetBucketAnalyticsConfiguration); 135 | register_s3_gateway_op!(GetBucketCors); 136 | register_s3_gateway_op!(GetBucketEncryption); 137 | register_s3_gateway_op!(GetBucketIntelligentTieringConfiguration); 138 | register_s3_gateway_op!(GetBucketInventoryConfiguration); 139 | register_s3_gateway_op!(GetBucketLifecycleConfiguration); 140 | register_s3_gateway_op!(GetBucketLocation); 141 | register_s3_gateway_op!(GetBucketLogging); 142 | register_s3_gateway_op!(GetBucketMetricsConfiguration); 143 | register_s3_gateway_op!(GetBucketNotificationConfiguration); 144 | register_s3_gateway_op!(GetBucketOwnershipControls); 145 | register_s3_gateway_op!(GetBucketPolicy); 146 | register_s3_gateway_op!(GetBucketPolicyStatus); 147 | register_s3_gateway_op!(GetBucketReplication); 148 | register_s3_gateway_op!(GetBucketRequestPayment); 149 | register_s3_gateway_op!(GetBucketVersioning); 150 | register_s3_gateway_op!(GetBucketWebsite); 151 | register_s3_gateway_op!(GetPublicAccessBlock); 152 | register_s3_gateway_op!(PutBucketAccelerateConfiguration); 153 | register_s3_gateway_op!(PutBucketAcl); 154 | register_s3_gateway_op!(PutBucketAnalyticsConfiguration); 155 | register_s3_gateway_op!(PutBucketCors); 156 | register_s3_gateway_op!(PutBucketEncryption); 157 | register_s3_gateway_op!(PutBucketIntelligentTieringConfiguration); 158 | register_s3_gateway_op!(PutBucketInventoryConfiguration); 159 | register_s3_gateway_op!(PutBucketLifecycleConfiguration); 160 | register_s3_gateway_op!(PutBucketLogging); 161 | register_s3_gateway_op!(PutBucketMetricsConfiguration); 162 | register_s3_gateway_op!(PutBucketNotificationConfiguration); 163 | register_s3_gateway_op!(PutBucketOwnershipControls); 164 | register_s3_gateway_op!(PutBucketPolicy); 165 | register_s3_gateway_op!(PutBucketReplication); 166 | register_s3_gateway_op!(PutBucketRequestPayment); 167 | register_s3_gateway_op!(PutBucketVersioning); 168 | register_s3_gateway_op!(PutBucketWebsite); 169 | register_s3_gateway_op!(PutPublicAccessBlock); 170 | register_s3_gateway_op!(WriteGetObjectResponse); 171 | register_s3_gateway_op!(DeleteBucketAnalyticsConfiguration); 172 | register_s3_gateway_op!(DeleteBucketCors); 173 | register_s3_gateway_op!(DeleteBucketEncryption); 174 | register_s3_gateway_op!(DeleteBucketIntelligentTieringConfiguration); 175 | register_s3_gateway_op!(DeleteBucketInventoryConfiguration); 176 | register_s3_gateway_op!(DeleteBucketLifecycle); 177 | register_s3_gateway_op!(DeleteBucketMetricsConfiguration); 178 | register_s3_gateway_op!(DeleteBucketOwnershipControls); 179 | register_s3_gateway_op!(DeleteBucketPolicy); 180 | register_s3_gateway_op!(DeleteBucketReplication); 181 | register_s3_gateway_op!(DeleteBucketWebsite); 182 | register_s3_gateway_op!(DeletePublicAccessBlock); 183 | register_s3_gateway_op!(ListBucketAnalyticsConfigurations); 184 | register_s3_gateway_op!(ListBucketIntelligentTieringConfigurations); 185 | register_s3_gateway_op!(ListBucketInventoryConfigurations); 186 | register_s3_gateway_op!(ListBucketMetricsConfigurations); 187 | 188 | let ops = b.build().unwrap(); 189 | 190 | { 191 | #[rustfmt::skip] 192 | let _: &OperationRegistry< 193 | hyper::Body, 194 | _, (), _, (), _, (), _, (), _, (), _, (), _, (), _, (), _, (), _, (), 195 | _, (), _, (), _, (), _, (), _, (), _, (), _, (), _, (), _, (), _, (), 196 | _, (), _, (), _, (), _, (), _, (), _, (), _, (), _, (), _, (), _, (), 197 | _, (), _, (), _, (), _, (), _, (), _, (), _, (), _, (), _, (), _, (), 198 | _, (), _, (), _, (), _, (), _, (), _, (), _, (), _, (), _, (), _, (), 199 | _, (), _, (), _, (), _, (), _, (), _, (), _, (), _, (), _, (), _, (), 200 | _, (), _, (), _, (), _, (), _, (), _, (), _, (), _, (), _, (), _, (), 201 | _, (), _, (), _, (), _, (), _, (), _, (), _, (), _, (), _, (), _, (), 202 | _, (), _, (), _, (), _, (), _, (), _, (), _, (), _, (), _, (), _, (), 203 | _, ()> = &ops; 204 | } 205 | 206 | let router = Router::from(ops); 207 | router 208 | } 209 | -------------------------------------------------------------------------------- /src/utils.rs: -------------------------------------------------------------------------------- 1 | use crate::config; 2 | use aws_smithy_http::byte_stream::ByteStream; 3 | use s3d_smithy_codegen_server_s3::error::InternalServerError; 4 | use serde::Deserialize; 5 | use std::os::unix::io::FromRawFd; 6 | use std::path::Path; 7 | use std::str::FromStr; 8 | use tokio::fs::{read_to_string, File}; 9 | use tokio::io::AsyncWriteExt; 10 | use tokio_stream::StreamExt; 11 | 12 | /// staticify uses Box::leak to make a struct with static lifetime. 13 | /// This is useful for async flows that require structs to live throughout the flow, 14 | /// where not releasing their memory is fine. 15 | pub fn staticify(x: T) -> &'static T { 16 | Box::leak(Box::new(x)) 17 | } 18 | 19 | /// new_s3_client creates a new s3 client which defaults to connect to the local daemon. 20 | pub async fn new_s3_client() -> aws_sdk_s3::Client { 21 | if config::S3_ENDPOINT.is_none() { 22 | let s3_config = aws_config::load_from_env().await; 23 | return aws_sdk_s3::Client::new(&s3_config); 24 | } 25 | 26 | aws_sdk_s3::Client::from_conf({ 27 | let ep = aws_sdk_s3::Endpoint::immutable( 28 | hyper::Uri::from_str(config::S3_ENDPOINT.as_ref().unwrap()).unwrap(), 29 | ); 30 | let creds = aws_sdk_s3::Credentials::new("s3d", "s3d", None, None, "s3d"); 31 | let region = aws_sdk_s3::Region::new("s3d"); 32 | let sleep_impl = aws_smithy_async::rt::sleep::default_async_sleep().unwrap(); 33 | aws_sdk_s3::Config::builder() 34 | .sleep_impl(sleep_impl) 35 | .endpoint_resolver(ep) 36 | .credentials_provider(creds) 37 | .region(region) 38 | .build() 39 | }) 40 | } 41 | 42 | pub fn parse_bucket_and_key(s: &str) -> anyhow::Result<(String, String)> { 43 | let mut parts = s.splitn(2, '/'); 44 | let bucket = parts 45 | .next() 46 | .ok_or_else(|| anyhow::anyhow!("Missing bucket"))?; 47 | let key = parts.next().ok_or_else(|| anyhow::anyhow!("Missing key"))?; 48 | Ok((String::from(bucket), String::from(key))) 49 | } 50 | 51 | pub fn parse_bucket_and_prefix(s: &str) -> anyhow::Result<(String, String)> { 52 | let mut parts = s.splitn(2, '/'); 53 | let bucket = parts.next().unwrap_or(""); 54 | let key = parts.next().unwrap_or(""); 55 | Ok((String::from(bucket), String::from(key))) 56 | } 57 | 58 | pub async fn read_file_as_stream(fname: &str) -> anyhow::Result { 59 | Ok(ByteStream::from_path(Path::new(&fname)).await?) 60 | } 61 | 62 | pub async fn write_stream_to_file(fname: &str, stream: &mut ByteStream) -> anyhow::Result { 63 | let mut file = File::create(fname).await?; 64 | let num_bytes = pipe_stream(stream, &mut file).await?; 65 | file.flush().await?; 66 | file.sync_all().await?; 67 | file.shutdown().await?; 68 | Ok(num_bytes) 69 | } 70 | 71 | pub async fn pipe_stream(input: &mut I, output: &mut O) -> anyhow::Result 72 | where 73 | I: tokio_stream::Stream> + std::marker::Unpin, 74 | O: tokio::io::AsyncWrite + std::marker::Unpin, 75 | E: std::error::Error + Send + Sync + 'static, 76 | { 77 | let mut num_bytes: u64 = 0; 78 | while let Some(ref mut buf) = input.try_next().await? { 79 | num_bytes += buf.len() as u64; 80 | output.write_all_buf(buf).await?; 81 | } 82 | Ok(num_bytes) 83 | } 84 | 85 | pub async fn pipe_stream_to_outfile_or_stdout( 86 | input: &mut I, 87 | outfile: Option<&str>, 88 | ) -> anyhow::Result 89 | where 90 | I: tokio_stream::Stream> + std::marker::Unpin, 91 | E: std::error::Error + Send + Sync + 'static, 92 | { 93 | match outfile { 94 | Some(ref path) => { 95 | let mut file = tokio::fs::File::create(path).await?; 96 | pipe_stream(input, &mut file).await 97 | } 98 | None => { 99 | let mut out = tokio::io::stdout(); 100 | pipe_stream(input, &mut out).await 101 | } 102 | } 103 | } 104 | 105 | pub async fn byte_stream_from_infile_or_stdin(infile: Option<&str>) -> anyhow::Result { 106 | let file = match infile { 107 | Some(ref path) => tokio::fs::File::open(path).await?, 108 | None => tokio::fs::File::from_std(unsafe { std::fs::File::from_raw_fd(0) }), 109 | }; 110 | let stream = ByteStream::read_from().file(file).build().await?; 111 | Ok(stream) 112 | } 113 | 114 | pub async fn _read_yaml_file(path: &Path) -> anyhow::Result 115 | where 116 | T: for<'de> Deserialize<'de>, 117 | { 118 | Ok(serde_yaml::from_str(&read_to_string(path).await?)?) 119 | } 120 | 121 | pub fn to_internal_err>(err: F) -> T { 122 | InternalServerError { 123 | message: err.to_string(), 124 | } 125 | .into() 126 | } 127 | -------------------------------------------------------------------------------- /src/write_queue.rs: -------------------------------------------------------------------------------- 1 | use crate::utils::{read_file_as_stream, to_internal_err, write_stream_to_file}; 2 | use aws_smithy_http::byte_stream::ByteStream; 3 | use s3d_smithy_codegen_server_s3::{ 4 | error::{GetObjectError, HeadObjectError, PutObjectError}, 5 | input::{GetObjectInput, HeadObjectInput, PutObjectInput}, 6 | output::{GetObjectOutput, HeadObjectOutput, PutObjectOutput}, 7 | }; 8 | use std::path::Path; 9 | 10 | pub struct WriteQueue { 11 | pub s3_client: &'static aws_sdk_s3::Client, 12 | pub write_queue_dir: String, 13 | } 14 | 15 | impl WriteQueue { 16 | pub fn start(&'static self) { 17 | tokio::spawn(self.worker()); 18 | } 19 | 20 | pub async fn worker(&self) { 21 | loop { 22 | tokio::time::sleep(tokio::time::Duration::from_millis(5000)).await; 23 | if let Err(err) = self.work().await { 24 | debug!("{}", err); 25 | } 26 | } 27 | } 28 | 29 | pub async fn work(&self) -> anyhow::Result<()> { 30 | debug!("Write queue worker running ..."); 31 | let mut queue = tokio::fs::read_dir(&self.write_queue_dir).await?; 32 | while let Some(entry) = queue.next_entry().await? { 33 | let entry_name_os = entry.file_name(); 34 | let entry_name = entry_name_os.to_str().unwrap(); 35 | if let Err(err) = self.push_file(entry_name).await { 36 | warn!("{}", err); 37 | } 38 | } 39 | Ok(()) 40 | } 41 | 42 | pub async fn push_file(&self, entry_name: &str) -> anyhow::Result<()> { 43 | let bucket_path_cow = urlencoding::decode(entry_name).unwrap(); 44 | let bucket_path = bucket_path_cow.as_ref(); 45 | info!("Write queue item: {:?}", bucket_path); 46 | let mut parts = bucket_path.splitn(2, '/'); 47 | let bucket = parts.next().unwrap(); 48 | let key = parts.next().unwrap(); 49 | let fname = format!("{}/{}", self.write_queue_dir, entry_name); 50 | let body = ByteStream::from_path(Path::new(&fname)).await?; 51 | self.s3_client 52 | .put_object() 53 | .bucket(bucket) 54 | .key(key) 55 | .body(body) 56 | .send() 57 | .await?; 58 | tokio::fs::remove_file(fname).await?; 59 | info!("Write queue item: {:?}", bucket_path); 60 | Ok(()) 61 | } 62 | 63 | pub async fn put_object( 64 | &self, 65 | mut i: PutObjectInput, 66 | ) -> Result { 67 | let fname = self.to_file_name(i.bucket(), i.key()); 68 | write_stream_to_file(&fname, &mut i.body) 69 | .await 70 | .map(|_| PutObjectOutput::builder().e_tag("s3d-etag").build()) 71 | .map_err(to_internal_err) 72 | } 73 | 74 | pub async fn get_object(&self, i: GetObjectInput) -> Result { 75 | let fname = self.to_file_name(i.bucket(), i.key()); 76 | read_file_as_stream(&fname) 77 | .await 78 | .map(|stream| GetObjectOutput::builder().set_body(Some(stream)).build()) 79 | .map_err(to_internal_err) 80 | } 81 | 82 | pub async fn head_object( 83 | &self, 84 | _i: HeadObjectInput, 85 | ) -> Result { 86 | // let fname = self.to_file_name(_i.bucket(), _i.key()) + ".s3d-object-md.yaml"; 87 | Ok(HeadObjectOutput::builder().build()) 88 | } 89 | 90 | pub fn to_file_name(&self, bucket: &str, key: &str) -> String { 91 | format!( 92 | "{}/{}", 93 | self.write_queue_dir, 94 | urlencoding::encode(&format!("{}/{}", bucket, key)) 95 | ) 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /test/sanity.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # s3d should be running on localhost:33333 and then run this script. 4 | 5 | EP="http://localhost:33333" 6 | BKT="${1:-s3d-test-bucket}" 7 | 8 | function LOG() { 9 | { echo -e "\n----------> sanity: $@\n"; } 2>/dev/null 10 | } 11 | 12 | function S3() { 13 | LOG "🚀 s3 $@" 14 | eval "./target/debug/s3 $@" 15 | } 16 | 17 | function CURL() { 18 | local rc 19 | LOG "🚀 curl $@" 20 | curl -s -i "${EP}$@" 21 | rc="$?" 22 | if [ $rc -ne 0 ]; then 23 | LOG "Error\n\nError: CURL failed\nReturnCode: $rc\nCommand: curl -s -i ${EP}$@" 24 | exit 1 25 | fi 26 | } 27 | 28 | function AWSCLI() { 29 | LOG "🚀 aws $@" 30 | aws --endpoint $EP "$@" 31 | } 32 | 33 | function test_s3() { 34 | LOG "▶️ test_s3 ..." 35 | S3 ls 36 | S3 ls $BKT 37 | S3 put $BKT/README.md "/dev/null" 40 | S3 ls $BKT 41 | LOG "✅ test_s3 done" 42 | } 43 | 44 | function test_curl_client() { 45 | LOG "▶️ test_curl_client ..." 46 | CURL / # ListBuckets 47 | CURL /$BKT -X PUT # CreateBucket 48 | CURL / # ListBuckets 49 | CURL /$BKT -I # HeadBucket 50 | CURL /$BKT -X GET # ListObjects 51 | CURL /$BKT/README.md -X PUT -d @README.md # PutObject 52 | CURL /$BKT/README.md -I # HeadObject 53 | CURL /$BKT/README.md -X GET # GetObject 54 | CURL /$BKT/README.md -X DELETE # DeleteObject 55 | CURL /$BKT -X DELETE # DeleteBucket 56 | CURL / # ListBuckets 57 | LOG "✅ test_curl_client done" 58 | } 59 | 60 | function test_awscli_s3() { 61 | LOG "▶️ test_awscli_s3 ..." 62 | AWSCLI s3 ls 63 | AWSCLI s3 ls s3://$BKT 64 | AWSCLI s3 cp README.md s3://$BKT/README.md 65 | AWSCLI s3 cp s3://$BKT/README.md - 66 | AWSCLI s3 rm s3://$BKT/README.md 67 | AWSCLI s3 rb s3://$BKT 68 | AWSCLI s3 ls 69 | LOG "✅ test_awscli_s3 done" 70 | } 71 | 72 | function test_awscli_s3api() { 73 | LOG "▶️ test_awscli_s3api ..." 74 | AWSCLI s3api list-buckets 75 | AWSCLI s3api list-objects --bucket $BKT 76 | AWSCLI s3api put-object --bucket $BKT --key README.md --body README.md 77 | AWSCLI s3api get-object --bucket $BKT --key README.md /dev/null 78 | AWSCLI s3api delete-object --bucket $BKT --key README.md 79 | AWSCLI s3api list-objects --bucket $BKT 80 | LOG "✅ test_awscli_s3api done" 81 | } 82 | 83 | 84 | test_s3 85 | #test_curl_client 86 | #test_awscli_s3 87 | #test_awscli_s3api 88 | --------------------------------------------------------------------------------