├── .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 |
33 |
34 |
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 | 
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 |
--------------------------------------------------------------------------------