├── .cargo └── config.toml ├── .github └── workflows │ └── ci.yaml ├── .gitignore ├── .rustfmt.toml ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── README.md ├── aws-build-lib ├── Cargo.toml ├── README.md └── src │ ├── container │ ├── Dockerfile │ └── build.sh │ └── lib.rs ├── aws-build ├── Cargo.toml ├── README.md └── src │ └── main.rs └── xtask ├── Cargo.toml └── src └── main.rs /.cargo/config.toml: -------------------------------------------------------------------------------- 1 | [alias] 2 | xtask = "run --manifest-path ./xtask/Cargo.toml --" 3 | -------------------------------------------------------------------------------- /.github/workflows/ci.yaml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | branches: 4 | - main 5 | pull_request: 6 | schedule: 7 | - cron: '0 0 * * 0' 8 | 9 | name: CI 10 | 11 | jobs: 12 | # Build just the library to catch compilation issues involving 13 | # disabled features in deps that would otherwise be hidden by feature 14 | # resolution with the binary package. 15 | build-lib: 16 | name: Build Lib 17 | runs-on: ubuntu-latest 18 | steps: 19 | - uses: actions/checkout@v2 20 | - uses: actions-rs/toolchain@v1 21 | with: 22 | profile: minimal 23 | toolchain: stable 24 | override: true 25 | - uses: actions-rs/cargo@v1 26 | with: 27 | command: build 28 | args: -p aws-build-lib 29 | 30 | test: 31 | name: Test Suite 32 | runs-on: ubuntu-latest 33 | steps: 34 | - uses: actions/checkout@v2 35 | - uses: actions-rs/toolchain@v1 36 | with: 37 | profile: minimal 38 | toolchain: stable 39 | override: true 40 | - uses: actions-rs/cargo@v1 41 | with: 42 | command: test 43 | 44 | fmt: 45 | name: Rustfmt 46 | runs-on: ubuntu-latest 47 | steps: 48 | - uses: actions/checkout@v2 49 | - uses: actions-rs/toolchain@v1 50 | with: 51 | profile: minimal 52 | toolchain: stable 53 | override: true 54 | - run: rustup component add rustfmt 55 | - uses: actions-rs/cargo@v1 56 | with: 57 | command: fmt 58 | args: --all -- --check 59 | 60 | clippy: 61 | name: Clippy 62 | runs-on: ubuntu-latest 63 | steps: 64 | - uses: actions/checkout@v2 65 | - uses: actions-rs/toolchain@v1 66 | with: 67 | profile: minimal 68 | toolchain: stable 69 | override: true 70 | - run: rustup component add clippy 71 | - uses: actions-rs/cargo@v1 72 | with: 73 | command: clippy 74 | args: -- -D warnings 75 | 76 | doc: 77 | name: Docs 78 | runs-on: ubuntu-latest 79 | steps: 80 | - uses: actions/checkout@v2 81 | - uses: actions-rs/toolchain@v1 82 | with: 83 | profile: minimal 84 | toolchain: stable 85 | override: true 86 | - uses: actions-rs/cargo@v1 87 | env: 88 | RUSTDOCFLAGS: -Dwarnings 89 | with: 90 | command: doc 91 | 92 | # TODO: currently the CI only runs the docker test because 93 | # podman is not yet supported on github runners. See 94 | # https://github.com/actions/runner/issues/505. 95 | xtask-docker-test: 96 | runs-on: ubuntu-latest 97 | steps: 98 | - uses: actions/checkout@v2 99 | - uses: actions-rs/toolchain@v1 100 | with: 101 | profile: minimal 102 | toolchain: stable 103 | override: true 104 | - uses: actions-rs/cargo@v1 105 | with: 106 | command: xtask 107 | args: run-container-tests --container-cmd docker 108 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /container_tests 2 | /target 3 | -------------------------------------------------------------------------------- /.rustfmt.toml: -------------------------------------------------------------------------------- 1 | max_width = 80 2 | -------------------------------------------------------------------------------- /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 = "anyhow" 13 | version = "1.0.45" 14 | source = "registry+https://github.com/rust-lang/crates.io-index" 15 | checksum = "ee10e43ae4a853c0a3591d4e2ada1719e553be18199d9da9d4a83f5927c2f5c7" 16 | 17 | [[package]] 18 | name = "argh" 19 | version = "0.1.6" 20 | source = "registry+https://github.com/rust-lang/crates.io-index" 21 | checksum = "f023c76cd7975f9969f8e29f0e461decbdc7f51048ce43427107a3d192f1c9bf" 22 | dependencies = [ 23 | "argh_derive", 24 | "argh_shared", 25 | ] 26 | 27 | [[package]] 28 | name = "argh_derive" 29 | version = "0.1.6" 30 | source = "registry+https://github.com/rust-lang/crates.io-index" 31 | checksum = "48ad219abc0c06ca788aface2e3a1970587e3413ab70acd20e54b6ec524c1f8f" 32 | dependencies = [ 33 | "argh_shared", 34 | "heck", 35 | "proc-macro2", 36 | "quote", 37 | "syn", 38 | ] 39 | 40 | [[package]] 41 | name = "argh_shared" 42 | version = "0.1.6" 43 | source = "registry+https://github.com/rust-lang/crates.io-index" 44 | checksum = "38de00daab4eac7d753e97697066238d67ce9d7e2d823ab4f72fe14af29f3f33" 45 | 46 | [[package]] 47 | name = "autocfg" 48 | version = "1.0.1" 49 | source = "registry+https://github.com/rust-lang/crates.io-index" 50 | checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" 51 | 52 | [[package]] 53 | name = "aws-build" 54 | version = "0.10.0" 55 | dependencies = [ 56 | "anyhow", 57 | "argh", 58 | "aws-build-lib", 59 | "fehler", 60 | "log", 61 | ] 62 | 63 | [[package]] 64 | name = "aws-build-lib" 65 | version = "0.10.0" 66 | dependencies = [ 67 | "anyhow", 68 | "cargo_metadata", 69 | "docker-command", 70 | "fehler", 71 | "fs-err", 72 | "log", 73 | "sha2", 74 | "tempfile", 75 | "time", 76 | "zip", 77 | ] 78 | 79 | [[package]] 80 | name = "bitflags" 81 | version = "1.3.2" 82 | source = "registry+https://github.com/rust-lang/crates.io-index" 83 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 84 | 85 | [[package]] 86 | name = "block-buffer" 87 | version = "0.9.0" 88 | source = "registry+https://github.com/rust-lang/crates.io-index" 89 | checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" 90 | dependencies = [ 91 | "generic-array", 92 | ] 93 | 94 | [[package]] 95 | name = "byteorder" 96 | version = "1.4.3" 97 | source = "registry+https://github.com/rust-lang/crates.io-index" 98 | checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" 99 | 100 | [[package]] 101 | name = "camino" 102 | version = "1.0.5" 103 | source = "registry+https://github.com/rust-lang/crates.io-index" 104 | checksum = "52d74260d9bf6944e2208aa46841b4b8f0d7ffc0849a06837b2f510337f86b2b" 105 | dependencies = [ 106 | "serde", 107 | ] 108 | 109 | [[package]] 110 | name = "cargo-platform" 111 | version = "0.1.2" 112 | source = "registry+https://github.com/rust-lang/crates.io-index" 113 | checksum = "cbdb825da8a5df079a43676dbe042702f1707b1109f713a01420fbb4cc71fa27" 114 | dependencies = [ 115 | "serde", 116 | ] 117 | 118 | [[package]] 119 | name = "cargo_metadata" 120 | version = "0.14.1" 121 | source = "registry+https://github.com/rust-lang/crates.io-index" 122 | checksum = "ba2ae6de944143141f6155a473a6b02f66c7c3f9f47316f802f80204ebfe6e12" 123 | dependencies = [ 124 | "camino", 125 | "cargo-platform", 126 | "semver", 127 | "serde", 128 | "serde_json", 129 | ] 130 | 131 | [[package]] 132 | name = "cfg-if" 133 | version = "1.0.0" 134 | source = "registry+https://github.com/rust-lang/crates.io-index" 135 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 136 | 137 | [[package]] 138 | name = "command-run" 139 | version = "1.1.1" 140 | source = "registry+https://github.com/rust-lang/crates.io-index" 141 | checksum = "ffd9f926d17735f66514f1a8cb0904f4b0f66cdc3549a9f185c5fc472642ab6e" 142 | dependencies = [ 143 | "log", 144 | "os_pipe", 145 | ] 146 | 147 | [[package]] 148 | name = "cpufeatures" 149 | version = "0.2.1" 150 | source = "registry+https://github.com/rust-lang/crates.io-index" 151 | checksum = "95059428f66df56b63431fdb4e1947ed2190586af5c5a8a8b71122bdf5a7f469" 152 | dependencies = [ 153 | "libc", 154 | ] 155 | 156 | [[package]] 157 | name = "crc32fast" 158 | version = "1.2.1" 159 | source = "registry+https://github.com/rust-lang/crates.io-index" 160 | checksum = "81156fece84ab6a9f2afdb109ce3ae577e42b1228441eded99bd77f627953b1a" 161 | dependencies = [ 162 | "cfg-if", 163 | ] 164 | 165 | [[package]] 166 | name = "crossbeam-channel" 167 | version = "0.5.1" 168 | source = "registry+https://github.com/rust-lang/crates.io-index" 169 | checksum = "06ed27e177f16d65f0f0c22a213e17c696ace5dd64b14258b52f9417ccb52db4" 170 | dependencies = [ 171 | "cfg-if", 172 | "crossbeam-utils", 173 | ] 174 | 175 | [[package]] 176 | name = "crossbeam-deque" 177 | version = "0.8.1" 178 | source = "registry+https://github.com/rust-lang/crates.io-index" 179 | checksum = "6455c0ca19f0d2fbf751b908d5c55c1f5cbc65e03c4225427254b46890bdde1e" 180 | dependencies = [ 181 | "cfg-if", 182 | "crossbeam-epoch", 183 | "crossbeam-utils", 184 | ] 185 | 186 | [[package]] 187 | name = "crossbeam-epoch" 188 | version = "0.9.5" 189 | source = "registry+https://github.com/rust-lang/crates.io-index" 190 | checksum = "4ec02e091aa634e2c3ada4a392989e7c3116673ef0ac5b72232439094d73b7fd" 191 | dependencies = [ 192 | "cfg-if", 193 | "crossbeam-utils", 194 | "lazy_static", 195 | "memoffset", 196 | "scopeguard", 197 | ] 198 | 199 | [[package]] 200 | name = "crossbeam-utils" 201 | version = "0.8.5" 202 | source = "registry+https://github.com/rust-lang/crates.io-index" 203 | checksum = "d82cfc11ce7f2c3faef78d8a684447b40d503d9681acebed6cb728d45940c4db" 204 | dependencies = [ 205 | "cfg-if", 206 | "lazy_static", 207 | ] 208 | 209 | [[package]] 210 | name = "digest" 211 | version = "0.9.0" 212 | source = "registry+https://github.com/rust-lang/crates.io-index" 213 | checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" 214 | dependencies = [ 215 | "generic-array", 216 | ] 217 | 218 | [[package]] 219 | name = "docker-command" 220 | version = "3.0.0" 221 | source = "registry+https://github.com/rust-lang/crates.io-index" 222 | checksum = "d97e4b9a82fb225e8a4fcc3039dbfd211a0e628a257c969645d39092a9136629" 223 | dependencies = [ 224 | "command-run", 225 | "users", 226 | ] 227 | 228 | [[package]] 229 | name = "either" 230 | version = "1.6.1" 231 | source = "registry+https://github.com/rust-lang/crates.io-index" 232 | checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457" 233 | 234 | [[package]] 235 | name = "fehler" 236 | version = "1.0.0" 237 | source = "registry+https://github.com/rust-lang/crates.io-index" 238 | checksum = "d5729fe49ba028cd550747b6e62cd3d841beccab5390aa398538c31a2d983635" 239 | dependencies = [ 240 | "fehler-macros", 241 | ] 242 | 243 | [[package]] 244 | name = "fehler-macros" 245 | version = "1.0.0" 246 | source = "registry+https://github.com/rust-lang/crates.io-index" 247 | checksum = "ccb5acb1045ebbfa222e2c50679e392a71dd77030b78fb0189f2d9c5974400f9" 248 | dependencies = [ 249 | "proc-macro2", 250 | "quote", 251 | "syn", 252 | ] 253 | 254 | [[package]] 255 | name = "flate2" 256 | version = "1.0.22" 257 | source = "registry+https://github.com/rust-lang/crates.io-index" 258 | checksum = "1e6988e897c1c9c485f43b47a529cef42fde0547f9d8d41a7062518f1d8fc53f" 259 | dependencies = [ 260 | "cfg-if", 261 | "crc32fast", 262 | "libc", 263 | "miniz_oxide", 264 | ] 265 | 266 | [[package]] 267 | name = "fs-err" 268 | version = "2.6.0" 269 | source = "registry+https://github.com/rust-lang/crates.io-index" 270 | checksum = "5ebd3504ad6116843b8375ad70df74e7bfe83cac77a1f3fe73200c844d43bfe0" 271 | 272 | [[package]] 273 | name = "generic-array" 274 | version = "0.14.4" 275 | source = "registry+https://github.com/rust-lang/crates.io-index" 276 | checksum = "501466ecc8a30d1d3b7fc9229b122b2ce8ed6e9d9223f1138d4babb253e51817" 277 | dependencies = [ 278 | "typenum", 279 | "version_check", 280 | ] 281 | 282 | [[package]] 283 | name = "getrandom" 284 | version = "0.2.3" 285 | source = "registry+https://github.com/rust-lang/crates.io-index" 286 | checksum = "7fcd999463524c52659517fe2cea98493cfe485d10565e7b0fb07dbba7ad2753" 287 | dependencies = [ 288 | "cfg-if", 289 | "libc", 290 | "wasi", 291 | ] 292 | 293 | [[package]] 294 | name = "heck" 295 | version = "0.3.3" 296 | source = "registry+https://github.com/rust-lang/crates.io-index" 297 | checksum = "6d621efb26863f0e9924c6ac577e8275e5e6b77455db64ffa6c65c904e9e132c" 298 | dependencies = [ 299 | "unicode-segmentation", 300 | ] 301 | 302 | [[package]] 303 | name = "hermit-abi" 304 | version = "0.1.19" 305 | source = "registry+https://github.com/rust-lang/crates.io-index" 306 | checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" 307 | dependencies = [ 308 | "libc", 309 | ] 310 | 311 | [[package]] 312 | name = "itoa" 313 | version = "0.4.8" 314 | source = "registry+https://github.com/rust-lang/crates.io-index" 315 | checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4" 316 | 317 | [[package]] 318 | name = "lazy_static" 319 | version = "1.4.0" 320 | source = "registry+https://github.com/rust-lang/crates.io-index" 321 | checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" 322 | 323 | [[package]] 324 | name = "libc" 325 | version = "0.2.107" 326 | source = "registry+https://github.com/rust-lang/crates.io-index" 327 | checksum = "fbe5e23404da5b4f555ef85ebed98fb4083e55a00c317800bc2a50ede9f3d219" 328 | 329 | [[package]] 330 | name = "log" 331 | version = "0.4.14" 332 | source = "registry+https://github.com/rust-lang/crates.io-index" 333 | checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710" 334 | dependencies = [ 335 | "cfg-if", 336 | ] 337 | 338 | [[package]] 339 | name = "memoffset" 340 | version = "0.6.4" 341 | source = "registry+https://github.com/rust-lang/crates.io-index" 342 | checksum = "59accc507f1338036a0477ef61afdae33cde60840f4dfe481319ce3ad116ddf9" 343 | dependencies = [ 344 | "autocfg", 345 | ] 346 | 347 | [[package]] 348 | name = "miniz_oxide" 349 | version = "0.4.4" 350 | source = "registry+https://github.com/rust-lang/crates.io-index" 351 | checksum = "a92518e98c078586bc6c934028adcca4c92a53d6a958196de835170a01d84e4b" 352 | dependencies = [ 353 | "adler", 354 | "autocfg", 355 | ] 356 | 357 | [[package]] 358 | name = "num_cpus" 359 | version = "1.13.0" 360 | source = "registry+https://github.com/rust-lang/crates.io-index" 361 | checksum = "05499f3756671c15885fee9034446956fff3f243d6077b91e5767df161f766b3" 362 | dependencies = [ 363 | "hermit-abi", 364 | "libc", 365 | ] 366 | 367 | [[package]] 368 | name = "opaque-debug" 369 | version = "0.3.0" 370 | source = "registry+https://github.com/rust-lang/crates.io-index" 371 | checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" 372 | 373 | [[package]] 374 | name = "os_pipe" 375 | version = "0.9.2" 376 | source = "registry+https://github.com/rust-lang/crates.io-index" 377 | checksum = "fb233f06c2307e1f5ce2ecad9f8121cffbbee2c95428f44ea85222e460d0d213" 378 | dependencies = [ 379 | "libc", 380 | "winapi", 381 | ] 382 | 383 | [[package]] 384 | name = "ppv-lite86" 385 | version = "0.2.15" 386 | source = "registry+https://github.com/rust-lang/crates.io-index" 387 | checksum = "ed0cfbc8191465bed66e1718596ee0b0b35d5ee1f41c5df2189d0fe8bde535ba" 388 | 389 | [[package]] 390 | name = "proc-macro2" 391 | version = "1.0.32" 392 | source = "registry+https://github.com/rust-lang/crates.io-index" 393 | checksum = "ba508cc11742c0dc5c1659771673afbab7a0efab23aa17e854cbab0837ed0b43" 394 | dependencies = [ 395 | "unicode-xid", 396 | ] 397 | 398 | [[package]] 399 | name = "quote" 400 | version = "1.0.10" 401 | source = "registry+https://github.com/rust-lang/crates.io-index" 402 | checksum = "38bc8cc6a5f2e3655e0899c1b848643b2562f853f114bfec7be120678e3ace05" 403 | dependencies = [ 404 | "proc-macro2", 405 | ] 406 | 407 | [[package]] 408 | name = "rand" 409 | version = "0.8.4" 410 | source = "registry+https://github.com/rust-lang/crates.io-index" 411 | checksum = "2e7573632e6454cf6b99d7aac4ccca54be06da05aca2ef7423d22d27d4d4bcd8" 412 | dependencies = [ 413 | "libc", 414 | "rand_chacha", 415 | "rand_core", 416 | "rand_hc", 417 | ] 418 | 419 | [[package]] 420 | name = "rand_chacha" 421 | version = "0.3.1" 422 | source = "registry+https://github.com/rust-lang/crates.io-index" 423 | checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" 424 | dependencies = [ 425 | "ppv-lite86", 426 | "rand_core", 427 | ] 428 | 429 | [[package]] 430 | name = "rand_core" 431 | version = "0.6.3" 432 | source = "registry+https://github.com/rust-lang/crates.io-index" 433 | checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7" 434 | dependencies = [ 435 | "getrandom", 436 | ] 437 | 438 | [[package]] 439 | name = "rand_hc" 440 | version = "0.3.1" 441 | source = "registry+https://github.com/rust-lang/crates.io-index" 442 | checksum = "d51e9f596de227fda2ea6c84607f5558e196eeaf43c986b724ba4fb8fdf497e7" 443 | dependencies = [ 444 | "rand_core", 445 | ] 446 | 447 | [[package]] 448 | name = "rayon" 449 | version = "1.5.1" 450 | source = "registry+https://github.com/rust-lang/crates.io-index" 451 | checksum = "c06aca804d41dbc8ba42dfd964f0d01334eceb64314b9ecf7c5fad5188a06d90" 452 | dependencies = [ 453 | "autocfg", 454 | "crossbeam-deque", 455 | "either", 456 | "rayon-core", 457 | ] 458 | 459 | [[package]] 460 | name = "rayon-core" 461 | version = "1.9.1" 462 | source = "registry+https://github.com/rust-lang/crates.io-index" 463 | checksum = "d78120e2c850279833f1dd3582f730c4ab53ed95aeaaaa862a2a5c71b1656d8e" 464 | dependencies = [ 465 | "crossbeam-channel", 466 | "crossbeam-deque", 467 | "crossbeam-utils", 468 | "lazy_static", 469 | "num_cpus", 470 | ] 471 | 472 | [[package]] 473 | name = "redox_syscall" 474 | version = "0.2.10" 475 | source = "registry+https://github.com/rust-lang/crates.io-index" 476 | checksum = "8383f39639269cde97d255a32bdb68c047337295414940c68bdd30c2e13203ff" 477 | dependencies = [ 478 | "bitflags", 479 | ] 480 | 481 | [[package]] 482 | name = "remove_dir_all" 483 | version = "0.5.3" 484 | source = "registry+https://github.com/rust-lang/crates.io-index" 485 | checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7" 486 | dependencies = [ 487 | "winapi", 488 | ] 489 | 490 | [[package]] 491 | name = "ryu" 492 | version = "1.0.5" 493 | source = "registry+https://github.com/rust-lang/crates.io-index" 494 | checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e" 495 | 496 | [[package]] 497 | name = "scopeguard" 498 | version = "1.1.0" 499 | source = "registry+https://github.com/rust-lang/crates.io-index" 500 | checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" 501 | 502 | [[package]] 503 | name = "semver" 504 | version = "1.0.4" 505 | source = "registry+https://github.com/rust-lang/crates.io-index" 506 | checksum = "568a8e6258aa33c13358f81fd834adb854c6f7c9468520910a9b1e8fac068012" 507 | dependencies = [ 508 | "serde", 509 | ] 510 | 511 | [[package]] 512 | name = "serde" 513 | version = "1.0.130" 514 | source = "registry+https://github.com/rust-lang/crates.io-index" 515 | checksum = "f12d06de37cf59146fbdecab66aa99f9fe4f78722e3607577a5375d66bd0c913" 516 | dependencies = [ 517 | "serde_derive", 518 | ] 519 | 520 | [[package]] 521 | name = "serde_derive" 522 | version = "1.0.130" 523 | source = "registry+https://github.com/rust-lang/crates.io-index" 524 | checksum = "d7bc1a1ab1961464eae040d96713baa5a724a8152c1222492465b54322ec508b" 525 | dependencies = [ 526 | "proc-macro2", 527 | "quote", 528 | "syn", 529 | ] 530 | 531 | [[package]] 532 | name = "serde_json" 533 | version = "1.0.71" 534 | source = "registry+https://github.com/rust-lang/crates.io-index" 535 | checksum = "063bf466a64011ac24040a49009724ee60a57da1b437617ceb32e53ad61bfb19" 536 | dependencies = [ 537 | "itoa", 538 | "ryu", 539 | "serde", 540 | ] 541 | 542 | [[package]] 543 | name = "sha2" 544 | version = "0.9.8" 545 | source = "registry+https://github.com/rust-lang/crates.io-index" 546 | checksum = "b69f9a4c9740d74c5baa3fd2e547f9525fa8088a8a958e0ca2409a514e33f5fa" 547 | dependencies = [ 548 | "block-buffer", 549 | "cfg-if", 550 | "cpufeatures", 551 | "digest", 552 | "opaque-debug", 553 | ] 554 | 555 | [[package]] 556 | name = "syn" 557 | version = "1.0.81" 558 | source = "registry+https://github.com/rust-lang/crates.io-index" 559 | checksum = "f2afee18b8beb5a596ecb4a2dce128c719b4ba399d34126b9e4396e3f9860966" 560 | dependencies = [ 561 | "proc-macro2", 562 | "quote", 563 | "unicode-xid", 564 | ] 565 | 566 | [[package]] 567 | name = "tempfile" 568 | version = "3.2.0" 569 | source = "registry+https://github.com/rust-lang/crates.io-index" 570 | checksum = "dac1c663cfc93810f88aed9b8941d48cabf856a1b111c29a40439018d870eb22" 571 | dependencies = [ 572 | "cfg-if", 573 | "libc", 574 | "rand", 575 | "redox_syscall", 576 | "remove_dir_all", 577 | "winapi", 578 | ] 579 | 580 | [[package]] 581 | name = "thiserror" 582 | version = "1.0.30" 583 | source = "registry+https://github.com/rust-lang/crates.io-index" 584 | checksum = "854babe52e4df1653706b98fcfc05843010039b406875930a70e4d9644e5c417" 585 | dependencies = [ 586 | "thiserror-impl", 587 | ] 588 | 589 | [[package]] 590 | name = "thiserror-impl" 591 | version = "1.0.30" 592 | source = "registry+https://github.com/rust-lang/crates.io-index" 593 | checksum = "aa32fd3f627f367fe16f893e2597ae3c05020f8bba2666a4e6ea73d377e5714b" 594 | dependencies = [ 595 | "proc-macro2", 596 | "quote", 597 | "syn", 598 | ] 599 | 600 | [[package]] 601 | name = "time" 602 | version = "0.3.5" 603 | source = "registry+https://github.com/rust-lang/crates.io-index" 604 | checksum = "41effe7cfa8af36f439fac33861b66b049edc6f9a32331e2312660529c1c24ad" 605 | dependencies = [ 606 | "libc", 607 | ] 608 | 609 | [[package]] 610 | name = "typenum" 611 | version = "1.14.0" 612 | source = "registry+https://github.com/rust-lang/crates.io-index" 613 | checksum = "b63708a265f51345575b27fe43f9500ad611579e764c79edbc2037b1121959ec" 614 | 615 | [[package]] 616 | name = "unicode-segmentation" 617 | version = "1.8.0" 618 | source = "registry+https://github.com/rust-lang/crates.io-index" 619 | checksum = "8895849a949e7845e06bd6dc1aa51731a103c42707010a5b591c0038fb73385b" 620 | 621 | [[package]] 622 | name = "unicode-xid" 623 | version = "0.2.2" 624 | source = "registry+https://github.com/rust-lang/crates.io-index" 625 | checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" 626 | 627 | [[package]] 628 | name = "users" 629 | version = "0.11.0" 630 | source = "registry+https://github.com/rust-lang/crates.io-index" 631 | checksum = "24cc0f6d6f267b73e5a2cadf007ba8f9bc39c6a6f9666f8cf25ea809a153b032" 632 | dependencies = [ 633 | "libc", 634 | "log", 635 | ] 636 | 637 | [[package]] 638 | name = "version_check" 639 | version = "0.9.3" 640 | source = "registry+https://github.com/rust-lang/crates.io-index" 641 | checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe" 642 | 643 | [[package]] 644 | name = "wasi" 645 | version = "0.10.2+wasi-snapshot-preview1" 646 | source = "registry+https://github.com/rust-lang/crates.io-index" 647 | checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6" 648 | 649 | [[package]] 650 | name = "winapi" 651 | version = "0.3.9" 652 | source = "registry+https://github.com/rust-lang/crates.io-index" 653 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 654 | dependencies = [ 655 | "winapi-i686-pc-windows-gnu", 656 | "winapi-x86_64-pc-windows-gnu", 657 | ] 658 | 659 | [[package]] 660 | name = "winapi-i686-pc-windows-gnu" 661 | version = "0.4.0" 662 | source = "registry+https://github.com/rust-lang/crates.io-index" 663 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 664 | 665 | [[package]] 666 | name = "winapi-x86_64-pc-windows-gnu" 667 | version = "0.4.0" 668 | source = "registry+https://github.com/rust-lang/crates.io-index" 669 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 670 | 671 | [[package]] 672 | name = "xtask" 673 | version = "0.0.0" 674 | dependencies = [ 675 | "anyhow", 676 | "argh", 677 | "command-run", 678 | "fehler", 679 | "fs-err", 680 | "rayon", 681 | ] 682 | 683 | [[package]] 684 | name = "zip" 685 | version = "0.5.13" 686 | source = "registry+https://github.com/rust-lang/crates.io-index" 687 | checksum = "93ab48844d61251bb3835145c521d88aa4031d7139e8485990f60ca911fa0815" 688 | dependencies = [ 689 | "byteorder", 690 | "crc32fast", 691 | "flate2", 692 | "thiserror", 693 | ] 694 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = [ 3 | "aws-build-lib", 4 | "aws-build", 5 | "xtask", 6 | ] 7 | exclude = ["container_tests"] 8 | 9 | [patch.crates-io] 10 | aws-build-lib = { path = "aws-build-lib" } 11 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # aws-build 2 | 3 | **This tool is no longer under active development. If you are interested in taking over or repurposing the name on crates.io, feel free to contact me: nbishop@nbishop.net** 4 | 5 | [![crates.io](https://img.shields.io/crates/v/aws-build.svg)](https://crates.io/crates/aws-build) 6 | [![Documentation](https://docs.rs/aws-build-lib/badge.svg)](https://docs.rs/aws-build-lib) 7 | 8 | Build a Rust project in a container for deployment to either an 9 | instance running AWS Linux 2 or AWS Lambda. 10 | 11 | Both a [library](https://crates.io/crates/aws-build-lib) and an 12 | [executable](https://crates.io/crates/aws-build) are provided. The 13 | executable is a very thin wrapper around the library. 14 | 15 | This crate only handles building the project locally. It does not 16 | interact with any AWS services. 17 | 18 | ## Executable 19 | 20 | Install with: 21 | 22 | ``` 23 | cargo install aws-build 24 | ``` 25 | 26 | In the common case you should be able to just run `aws-build al2` or 27 | `aws-build lambda` in the directory of the project you want to 28 | build. 29 | 30 | On successful completion, the output file (either a standalone executable 31 | for Amazon Linux 2 or a zip file containing a "bootstrap" executable 32 | for AWS Lambda) is written to a subdirectory of the `target` 33 | directory. There is also a `target/latest-al2` or 34 | `target/latest-lambda` symlink that points to the output file. 35 | 36 | ``` 37 | aws-build [] [--container-cmd ] [--rust-version ] [--strip] [--bin ] [--package ] [--code-root ] 38 | 39 | Build the project in a container for deployment to AWS. 40 | 41 | mode: al2 or lambda (for Amazon Linux 2 or AWS Lambda, respectively) 42 | project: path of the project to build (default: current directory) 43 | 44 | Options: 45 | --container-cmd base container command, e.g. docker or podman, auto-detected 46 | by default 47 | --rust-version rust version (default: latest stable) 48 | --strip strip debug symbols 49 | --bin name of the binary target to build (required if there is 50 | more than one binary target) 51 | --package yum devel package to install in build container 52 | --code-root root directory to mount into the container, must contain the 53 | project path (default: project path) 54 | --help display usage information 55 | ``` 56 | 57 | ## Related projects 58 | 59 | - [amazon-linux](https://hub.docker.com/_/amazonlinux): Docker image 60 | replicating the Aamazon Linux environment. 61 | - [cargo-aws-lambda](https://github.com/vvilhonen/cargo-aws-lambda): 62 | "dependency free cargo subcommand for cross-compiling, packaging and 63 | deploying code quickly to AWS Lambda" 64 | - [docker-lambda](https://github.com/lambci/docker-lambda): "A 65 | sandboxed local environment that replicates the live AWS Lambda 66 | environment almost identically" 67 | - [lambda-rust](https://github.com/softprops/lambda-rust): "a faithful 68 | reproduction of the actual AWS [...] Lambda runtime environment" with 69 | the stable Rust toolchain installed 70 | -------------------------------------------------------------------------------- /aws-build-lib/Cargo.toml: -------------------------------------------------------------------------------- 1 | [badges.maintenance] 2 | status = "deprecated" 3 | 4 | [package] 5 | name = "aws-build-lib" 6 | version = "0.10.1" 7 | authors = ["Nicholas Bishop "] 8 | edition = "2021" 9 | 10 | description = "Build Rust crates for deployment to AWS EC2 or AWS Lambda" 11 | keywords = ["aws", "lambda", "docker", "build", "ec2"] 12 | license = "Apache-2.0" 13 | repository = "https://github.com/nicholasbishop/aws-build" 14 | 15 | [dependencies] 16 | anyhow = { version = "1.0.45", default-features = false, features = ["std"] } 17 | cargo_metadata = { version = "0.14.1", default-features = false } 18 | docker-command = { version = "3.0.0", default-features = false, features = ["logging"] } 19 | fehler = { version = "1.0.0", default-features = false } 20 | fs-err = { version = "2.6.0", default-features = false } 21 | log = { version = "0.4.14", default-features = false, features = ["std"] } 22 | sha2 = { version = "0.9.8", default-features = false } 23 | tempfile = { version = "3.2.0", default-features = false } 24 | time = { version = "0.3.5", default-features = false, features = ["std"] } 25 | zip = { version = "0.5.13", default-features = false, features = ["deflate"] } 26 | -------------------------------------------------------------------------------- /aws-build-lib/README.md: -------------------------------------------------------------------------------- 1 | **This tool is no longer under active development. If you are interested in taking over or repurposing the name on crates.io, feel free to contact me: nbishop@nbishop.net** 2 | -------------------------------------------------------------------------------- /aws-build-lib/src/container/Dockerfile: -------------------------------------------------------------------------------- 1 | # The FROM_IMAGE arg can be set to either an Amazon Linux 2 image or a 2 | # Lambda image 3 | ARG FROM_IMAGE 4 | FROM $FROM_IMAGE 5 | 6 | # This is already in the Lambda image but not in the AL2 image 7 | RUN yum install -y gcc 8 | 9 | # Install Rust 10 | ARG RUST_VERSION 11 | RUN curl -o /rustup.sh --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs 12 | RUN CARGO_HOME=/cargo RUSTUP_HOME=/rustup sh /rustup.sh -y --profile minimal --default-toolchain $RUST_VERSION 13 | 14 | ARG DEV_PKGS 15 | RUN if [[ ! -z "$DEV_PKGS" ]] ; then yum install -y $DEV_PKGS ; fi 16 | 17 | # Add the build script 18 | ADD build.sh /build.sh 19 | RUN chmod +x /build.sh 20 | 21 | VOLUME ["/code"] 22 | 23 | # Change to the project directory. 24 | ARG PROJECT_PATH 25 | WORKDIR /code/"$PROJECT_PATH" 26 | 27 | ENTRYPOINT ["/build.sh"] 28 | -------------------------------------------------------------------------------- /aws-build-lib/src/container/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -eu 4 | 5 | export CARGO_HOME="/cargo" 6 | export RUSTUP_HOME="/rustup" 7 | 8 | # Source cargo environment 9 | . "${CARGO_HOME}/env" 10 | 11 | cargo build --locked --release --target-dir "${TARGET_DIR}" --bin "${BIN_TARGET}" 12 | -------------------------------------------------------------------------------- /aws-build-lib/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![deny(missing_docs)] 2 | 3 | //! Build a Rust project in a container for deployment to either 4 | //! Amazon Linux 2 or AWS Lambda. 5 | 6 | pub use docker_command; 7 | 8 | use anyhow::{anyhow, Context, Error}; 9 | use cargo_metadata::MetadataCommand; 10 | use docker_command::command_run::{Command, LogTo}; 11 | use docker_command::{BuildOpt, Launcher, RunOpt, UserAndGroup, Volume}; 12 | use fehler::{throw, throws}; 13 | use fs_err as fs; 14 | use log::{error, info}; 15 | use sha2::Digest; 16 | use std::io::Write; 17 | use std::path::{Path, PathBuf}; 18 | use tempfile::TempDir; 19 | use time::{Date, OffsetDateTime}; 20 | use zip::ZipWriter; 21 | 22 | /// Default rust version to install. 23 | pub static DEFAULT_RUST_VERSION: &str = "stable"; 24 | 25 | /// Create directory if it doesn't already exist. 26 | #[throws] 27 | fn ensure_dir_exists(path: &Path) { 28 | // Ignore the return value since the directory might already exist 29 | let _ = fs::create_dir(path); 30 | if !path.is_dir() { 31 | throw!(anyhow!("failed to create directory {}", path.display())); 32 | } 33 | } 34 | 35 | /// Get the names of all the binaries targets in a project. 36 | #[throws] 37 | fn get_package_binaries(path: &Path) -> Vec { 38 | let metadata = MetadataCommand::new().current_dir(path).no_deps().exec()?; 39 | let mut names = Vec::new(); 40 | for package in metadata.packages { 41 | for target in package.targets { 42 | if target.kind.contains(&"bin".to_string()) { 43 | names.push(target.name); 44 | } 45 | } 46 | } 47 | names 48 | } 49 | 50 | #[throws] 51 | fn write_container_files() -> TempDir { 52 | let tmp_dir = TempDir::new()?; 53 | 54 | let dockerfile = include_str!("container/Dockerfile"); 55 | fs::write(tmp_dir.path().join("Dockerfile"), dockerfile)?; 56 | 57 | let build_script = include_str!("container/build.sh"); 58 | fs::write(tmp_dir.path().join("build.sh"), build_script)?; 59 | 60 | tmp_dir 61 | } 62 | 63 | fn set_up_command(cmd: &mut Command) { 64 | cmd.log_to = LogTo::Log; 65 | cmd.combine_output = true; 66 | cmd.log_output_on_error = true; 67 | } 68 | 69 | /// Create a unique output file name. 70 | /// 71 | /// The file name is intended to be identifiable, sortable by time, 72 | /// unique, and reasonably short. To make this it includes: 73 | /// - build-mode prefix (al2 or lambda) 74 | /// - executable name 75 | /// - year, month, and day 76 | /// - first 16 digits of the sha256 hex hash 77 | fn make_unique_name( 78 | mode: BuildMode, 79 | name: &str, 80 | contents: &[u8], 81 | when: Date, 82 | ) -> String { 83 | let hash = sha2::Sha256::digest(contents); 84 | format!( 85 | "{}-{}-{}{:02}{:02}-{:.16x}", 86 | mode.name(), 87 | name, 88 | when.year(), 89 | u8::from(when.month()), 90 | when.day(), 91 | // The hash is truncated to 16 characters so that the file 92 | // name isn't unnecessarily long 93 | hash 94 | ) 95 | } 96 | 97 | /// Run the strip command to remove symbols and decrease the size. 98 | #[throws] 99 | fn strip(path: &Path) { 100 | let mut cmd = Command::new("strip"); 101 | cmd.add_arg(path); 102 | set_up_command(&mut cmd); 103 | cmd.run()?; 104 | } 105 | 106 | /// Recursively set the owner of `dir` using the `podman unshare` 107 | /// command. The input `user` is treated as a user (and group) 108 | /// inside the container. This means that an input of "root" is 109 | /// really the current user (from outside the chroot). 110 | #[throws] 111 | fn set_podman_permissions(user: &UserAndGroup, dir: &Path) { 112 | Command::with_args( 113 | "podman", 114 | &["unshare", "chown", "--recursive", &user.arg()], 115 | ) 116 | .add_arg(dir) 117 | .run()?; 118 | } 119 | 120 | struct ResetPodmanPermissions<'a> { 121 | user: UserAndGroup, 122 | dir: &'a Path, 123 | done: bool, 124 | } 125 | 126 | impl<'a> ResetPodmanPermissions<'a> { 127 | fn new(user: UserAndGroup, dir: &'a Path) -> Self { 128 | Self { 129 | dir, 130 | user, 131 | done: false, 132 | } 133 | } 134 | 135 | /// Reset the permissions if not already done. Calling this is 136 | /// preferred to waiting for the drop, because the error can be 137 | /// propagated. 138 | #[throws] 139 | fn reset_permissions(&mut self) { 140 | if !self.done { 141 | set_podman_permissions(&self.user, self.dir)?; 142 | self.done = true; 143 | } 144 | } 145 | } 146 | 147 | impl<'a> Drop for ResetPodmanPermissions<'a> { 148 | fn drop(&mut self) { 149 | if let Err(err) = self.reset_permissions() { 150 | error!("failed to reset permissions: {}", err); 151 | } 152 | } 153 | } 154 | 155 | struct Container<'a> { 156 | mode: BuildMode, 157 | bin: &'a String, 158 | launcher: &'a Launcher, 159 | output_dir: &'a Path, 160 | image_tag: &'a str, 161 | relabel: Option, 162 | 163 | /// The root of the code that gets mounted in the container. All the 164 | /// source must live beneath this directory. 165 | code_root: &'a Path, 166 | } 167 | 168 | impl<'a> Container<'a> { 169 | #[throws] 170 | fn run(&self) -> PathBuf { 171 | let mode_name = self.mode.name(); 172 | 173 | // Create two cache directories to speed up rebuilds. These are 174 | // host mounts rather than volumes so that the permissions aren't 175 | // set to root only. 176 | let registry_dir = self 177 | .output_dir 178 | .join(format!("{}-cargo-registry", mode_name)); 179 | ensure_dir_exists(®istry_dir)?; 180 | let git_dir = self.output_dir.join(format!("{}-cargo-git", mode_name)); 181 | ensure_dir_exists(&git_dir)?; 182 | 183 | let mut reset_podman_permissions = None; 184 | if self.launcher.is_podman() { 185 | // Recursively set the output directory's permissions such 186 | // that the non-root user in the container owns it. 187 | set_podman_permissions(&UserAndGroup::current(), self.output_dir)?; 188 | 189 | // Prepare an object to reset the permissions back to the 190 | // current user. The current user is "root" inside the 191 | // container, hence the odd-looking input. 192 | reset_podman_permissions = Some(ResetPodmanPermissions::new( 193 | UserAndGroup::root(), 194 | self.output_dir, 195 | )); 196 | } 197 | 198 | let mount_options = match self.relabel { 199 | Some(Relabel::Shared) => vec!["z".to_string()], 200 | Some(Relabel::Unshared) => vec!["Z".to_string()], 201 | None => vec![], 202 | }; 203 | 204 | let mut cmd = self.launcher.run(RunOpt { 205 | remove: true, 206 | env: vec![ 207 | ( 208 | "TARGET_DIR".into(), 209 | Path::new("/target").join(mode_name).into(), 210 | ), 211 | ("BIN_TARGET".into(), self.bin.into()), 212 | ], 213 | init: true, 214 | user: Some(UserAndGroup::current()), 215 | volumes: vec![ 216 | // Mount the code root 217 | Volume { 218 | src: self.code_root.into(), 219 | dst: Path::new("/code").into(), 220 | read_write: false, 221 | options: mount_options.clone(), 222 | }, 223 | // Mount two cargo directories to make rebuilds faster 224 | Volume { 225 | src: registry_dir, 226 | dst: Path::new("/cargo/registry").into(), 227 | read_write: true, 228 | options: mount_options.clone(), 229 | }, 230 | Volume { 231 | src: git_dir, 232 | dst: Path::new("/cargo/git").into(), 233 | read_write: true, 234 | options: mount_options.clone(), 235 | }, 236 | // Mount the output target directory 237 | Volume { 238 | src: self.output_dir.into(), 239 | dst: Path::new("/target").into(), 240 | read_write: true, 241 | options: mount_options, 242 | }, 243 | ], 244 | image: self.image_tag.into(), 245 | ..Default::default() 246 | }); 247 | set_up_command(&mut cmd); 248 | cmd.run()?; 249 | 250 | if let Some(mut resetter) = reset_podman_permissions { 251 | // Recursively set the output directory's permissions back 252 | // to the current user. 253 | resetter.reset_permissions()?; 254 | } 255 | 256 | // Return the path of the binary that was built 257 | self.output_dir 258 | .join(mode_name) 259 | .join("release") 260 | .join(self.bin) 261 | } 262 | } 263 | 264 | /// Whether to build for Amazon Linux 2 or AWS Lambda. 265 | #[derive(Debug, Clone, Copy, Eq, PartialEq)] 266 | pub enum BuildMode { 267 | /// Build for Amazon Linux 2. The result is a standalone binary 268 | /// that can be copied to (e.g) an EC2 instance running Amazon 269 | /// Linux 2. 270 | AmazonLinux2, 271 | 272 | /// Build for AWS Lambda running Amazon Linux 2. The result is a 273 | /// zip file containing a single "bootstrap" executable. 274 | Lambda, 275 | } 276 | 277 | impl BuildMode { 278 | fn name(&self) -> &'static str { 279 | match self { 280 | BuildMode::AmazonLinux2 => "al2", 281 | BuildMode::Lambda => "lambda", 282 | } 283 | } 284 | } 285 | 286 | impl std::str::FromStr for BuildMode { 287 | type Err = Error; 288 | 289 | #[throws] 290 | fn from_str(s: &str) -> Self { 291 | if s == "al2" { 292 | Self::AmazonLinux2 293 | } else if s == "lambda" { 294 | Self::Lambda 295 | } else { 296 | throw!(anyhow!("invalid mode {}", s)); 297 | } 298 | } 299 | } 300 | 301 | /// Relabel files before bind-mounting. 302 | #[derive(Clone, Copy, Debug, Eq, PartialEq)] 303 | pub enum Relabel { 304 | /// Mount volumes with the `z` option. 305 | Shared, 306 | 307 | /// Mount volumes with the `Z` option. 308 | Unshared, 309 | } 310 | 311 | /// Output returned from [`Builder::run`] on success. 312 | pub struct BuilderOutput { 313 | /// Path of the generated file. 314 | pub real: PathBuf, 315 | 316 | /// Path of the `latest-*` symlink. 317 | pub symlink: PathBuf, 318 | } 319 | 320 | /// Options for running the build. 321 | #[must_use] 322 | #[derive(Debug, Clone, Eq, PartialEq)] 323 | pub struct Builder { 324 | /// Rust version to install. Can be anything rustup understands as 325 | /// a valid version, e.g. "stable" or "1.45.2". 326 | pub rust_version: String, 327 | 328 | /// Whether to build for Amazon Linux 2 or AWS Lambda. 329 | pub mode: BuildMode, 330 | 331 | /// Name of the binary target to build. Can be None if the project 332 | /// only has one binary target. 333 | pub bin: Option, 334 | 335 | /// Strip the binary. 336 | pub strip: bool, 337 | 338 | /// Container launcher. 339 | pub launcher: Launcher, 340 | 341 | /// The root of the code that gets mounted in the container. All the 342 | /// source must live beneath this directory. 343 | pub code_root: PathBuf, 344 | 345 | /// The project path is the path of the crate to build. It must be 346 | /// somewhere within the `code_root` directory (or the same path). 347 | pub project_path: PathBuf, 348 | 349 | /// dev packages to install in container for build 350 | pub packages: Vec, 351 | 352 | /// Relabel files before bind-mounting (`z` or `Z` volume 353 | /// option). Warning: this overwrites the current label on files on 354 | /// the host. Doing this to a system directory like `/usr` could 355 | /// [break your system]. 356 | /// 357 | /// [break your system]: https://docs.docker.com/storage/bind-mounts/#configure-the-selinux-label 358 | pub relabel: Option, 359 | } 360 | 361 | impl Builder { 362 | /// Run the build in a container. 363 | /// 364 | /// This will produce either a standalone executable (for Amazon 365 | /// Linux 2) or a zip file (for AWS Lambda). The file is given a 366 | /// unique name for convenient uploading to S3, and a short 367 | /// symlink to the file is also created (target/latest-al2 or 368 | /// target/latest-lambda). 369 | /// 370 | /// The paths of the files are returned. 371 | #[throws] 372 | pub fn run(&self) -> BuilderOutput { 373 | // Canonicalize the input paths. This is necessary for when it's 374 | // passed as a Docker volume arg. 375 | let code_root = fs::canonicalize(&self.code_root)?; 376 | let project_path = fs::canonicalize(&self.project_path)?; 377 | let relative_project_path = project_path 378 | .strip_prefix(&code_root) 379 | .context("project path must be within the code root")?; 380 | 381 | // Ensure that the target directory exists 382 | let target_dir = project_path.join("target"); 383 | ensure_dir_exists(&target_dir)?; 384 | 385 | let output_dir = target_dir.join("aws-build"); 386 | ensure_dir_exists(&output_dir)?; 387 | 388 | let image_tag = self 389 | .build_container(relative_project_path) 390 | .context("container build failed")?; 391 | 392 | // Get the binary target names 393 | let binaries = get_package_binaries(&project_path)?; 394 | 395 | // Get the name of the binary target to build 396 | let bin: String = if let Some(bin) = &self.bin { 397 | bin.clone() 398 | } else if binaries.len() == 1 { 399 | binaries[0].clone() 400 | } else { 401 | throw!(anyhow!( 402 | "must specify bin target when package has more than one" 403 | )); 404 | }; 405 | 406 | // Build the project in a container 407 | let container = Container { 408 | mode: self.mode, 409 | launcher: &self.launcher, 410 | output_dir: &output_dir, 411 | image_tag: &image_tag, 412 | bin: &bin, 413 | relabel: self.relabel, 414 | code_root: &code_root, 415 | }; 416 | let bin_path = container.run().context("container run failed")?; 417 | 418 | // Optionally strip symbols 419 | if self.strip { 420 | strip(&bin_path)?; 421 | } 422 | 423 | let bin_contents = fs::read(&bin_path)?; 424 | let base_unique_name = make_unique_name( 425 | self.mode, 426 | &bin, 427 | &bin_contents, 428 | OffsetDateTime::now_utc().date(), 429 | ); 430 | 431 | let out_path = match self.mode { 432 | BuildMode::AmazonLinux2 => { 433 | // Give the binary a unique name so that multiple 434 | // versions can be uploaded to S3 without overwriting 435 | // each other. 436 | let out_path = 437 | output_dir.join(self.mode.name()).join(base_unique_name); 438 | fs::copy(bin_path, &out_path)?; 439 | info!("writing {}", out_path.display()); 440 | out_path 441 | } 442 | BuildMode::Lambda => { 443 | // Zip the binary and give the zip a unique name so 444 | // that multiple versions can be uploaded to S3 445 | // without overwriting each other. 446 | let zip_name = base_unique_name + ".zip"; 447 | let zip_path = 448 | output_dir.join(self.mode.name()).join(&zip_name); 449 | 450 | // Create the zip file containing just a bootstrap 451 | // file (the executable) 452 | info!("writing {}", zip_path.display()); 453 | let file = fs::File::create(&zip_path)?; 454 | let mut zip = ZipWriter::new(file); 455 | let options = zip::write::FileOptions::default() 456 | .unix_permissions(0o755) 457 | .compression_method(zip::CompressionMethod::Deflated); 458 | zip.start_file("bootstrap", options)?; 459 | zip.write_all(&bin_contents)?; 460 | 461 | zip.finish()?; 462 | 463 | zip_path 464 | } 465 | }; 466 | 467 | // Create a symlink pointing to the output file. Either 468 | // "target/latest-al2" or "target/latest-lambda" 469 | let symlink_path = 470 | target_dir.join(format!("latest-{}", self.mode.name())); 471 | // Remove the symlink if it already exists, but ignore an 472 | // error in case it doesn't exist. 473 | let _ = fs::remove_file(&symlink_path); 474 | std::os::unix::fs::symlink(&out_path, &symlink_path)?; 475 | info!("symlink: {}", symlink_path.display()); 476 | 477 | BuilderOutput { 478 | real: out_path, 479 | symlink: symlink_path, 480 | } 481 | } 482 | 483 | /// Build the container image and return its hash. 484 | #[throws] 485 | fn build_container(&self, relative_project_path: &Path) -> String { 486 | let from = match self.mode { 487 | BuildMode::AmazonLinux2 => { 488 | // https://hub.docker.com/_/amazonlinux 489 | "docker.io/amazonlinux:2" 490 | } 491 | BuildMode::Lambda => { 492 | // https://github.com/lambci/docker-lambda#documentation 493 | "docker.io/lambci/lambda:build-provided.al2" 494 | } 495 | }; 496 | let tmp_dir = write_container_files()?; 497 | let iid_path = tmp_dir.path().join("iidfile"); 498 | let mut cmd = self.launcher.build(BuildOpt { 499 | build_args: vec![ 500 | ("FROM_IMAGE".into(), from.into()), 501 | ("RUST_VERSION".into(), self.rust_version.clone()), 502 | ("DEV_PKGS".into(), self.packages.join(" ")), 503 | ( 504 | "PROJECT_PATH".into(), 505 | relative_project_path 506 | .to_str() 507 | .ok_or_else(|| anyhow!("project path is not utf-8"))? 508 | .into(), 509 | ), 510 | ], 511 | context: tmp_dir.path().into(), 512 | iidfile: Some(iid_path.clone()), 513 | ..Default::default() 514 | }); 515 | set_up_command(&mut cmd); 516 | cmd.run()?; 517 | fs::read_to_string(&iid_path)? 518 | } 519 | } 520 | 521 | #[cfg(test)] 522 | mod tests { 523 | use super::*; 524 | use time::Month; 525 | 526 | #[test] 527 | fn test_unique_name() { 528 | let when = Date::from_calendar_date(2020, Month::August, 31).unwrap(); 529 | assert_eq!( 530 | make_unique_name( 531 | BuildMode::Lambda, 532 | "testexecutable", 533 | "testcontents".as_bytes(), 534 | when 535 | ), 536 | "lambda-testexecutable-20200831-7097a82a108e78da" 537 | ); 538 | } 539 | } 540 | -------------------------------------------------------------------------------- /aws-build/Cargo.toml: -------------------------------------------------------------------------------- 1 | [badges.maintenance] 2 | status = "deprecated" 3 | 4 | [package] 5 | name = "aws-build" 6 | version = "0.10.1" 7 | authors = ["Nicholas Bishop "] 8 | edition = "2021" 9 | 10 | description = "Build Rust crates for deployment to AWS EC2 or AWS Lambda" 11 | keywords = ["aws", "lambda", "docker", "build", "ec2"] 12 | license = "Apache-2.0" 13 | repository = "https://github.com/nicholasbishop/aws-build" 14 | 15 | [dependencies] 16 | aws-build-lib = { version = "^0.10.0", default-features = false } 17 | 18 | anyhow = { version = "1.0.45", default-features = false, features = ["std"] } 19 | argh = { version = "0.1.6", default-features = false } 20 | fehler = { version = "1.0.0", default-features = false } 21 | log = { version = "0.4.14", default-features = false } 22 | -------------------------------------------------------------------------------- /aws-build/README.md: -------------------------------------------------------------------------------- 1 | **This tool is no longer under active development. If you are interested in taking over or repurposing the name on crates.io, feel free to contact me: nbishop@nbishop.net** 2 | -------------------------------------------------------------------------------- /aws-build/src/main.rs: -------------------------------------------------------------------------------- 1 | use anyhow::{anyhow, Error}; 2 | use argh::FromArgs; 3 | use aws_build_lib::docker_command::command_run::Command; 4 | use aws_build_lib::docker_command::Launcher; 5 | use aws_build_lib::{BuildMode, Builder, DEFAULT_RUST_VERSION}; 6 | use fehler::throws; 7 | use std::env; 8 | use std::path::PathBuf; 9 | 10 | use log::{Level, Metadata, Record}; 11 | 12 | struct Logger; 13 | 14 | impl log::Log for Logger { 15 | fn enabled(&self, metadata: &Metadata) -> bool { 16 | metadata.level() <= Level::Info 17 | } 18 | 19 | fn log(&self, record: &Record) { 20 | if self.enabled(record.metadata()) { 21 | println!("{}", record.args()); 22 | } 23 | } 24 | 25 | fn flush(&self) {} 26 | } 27 | 28 | static LOGGER: Logger = Logger; 29 | 30 | #[throws(String)] 31 | fn parse_command(s: &str) -> Command { 32 | Command::from_whitespace_separated_str(s) 33 | .ok_or_else(|| "command is empty".to_string())? 34 | } 35 | 36 | #[derive(Debug, FromArgs)] 37 | #[argh(description = "Build the project in a container for deployment to AWS. 38 | 39 | mode: al2 or lambda (for Amazon Linux 2 or AWS Lambda, respectively) 40 | project: path of the project to build (default: current directory) 41 | ")] 42 | struct Opt { 43 | /// base container command, e.g. docker or podman, auto-detected by 44 | /// default 45 | #[argh(option, from_str_fn(parse_command))] 46 | container_cmd: Option, 47 | 48 | /// rust version (default: latest stable) 49 | #[argh(option, default = "DEFAULT_RUST_VERSION.into()")] 50 | rust_version: String, 51 | 52 | /// strip debug symbols 53 | #[argh(switch)] 54 | strip: bool, 55 | 56 | /// name of the binary target to build (required if there is more 57 | /// than one binary target) 58 | #[argh(option)] 59 | bin: Option, 60 | 61 | /// yum devel package to install in build container 62 | #[argh(option)] 63 | package: Vec, 64 | 65 | /// root directory to mount into the container, must contain the 66 | /// project path (default: project path) 67 | #[argh(option)] 68 | code_root: Option, 69 | 70 | /// whether to build for Amazon Linux 2 or AWS Lambda 71 | #[argh(positional)] 72 | mode: BuildMode, 73 | 74 | /// path of the project to build (default: current directory) 75 | #[argh(positional, default = "env::current_dir().unwrap()")] 76 | project: PathBuf, 77 | } 78 | 79 | impl Opt { 80 | #[throws] 81 | fn launcher(&self) -> Launcher { 82 | if let Some(cmd) = self.container_cmd.as_ref() { 83 | Launcher::new(cmd.clone()) 84 | } else { 85 | Launcher::auto() 86 | .ok_or_else(|| anyhow!("no container system detected"))? 87 | } 88 | } 89 | } 90 | 91 | #[throws] 92 | fn main() { 93 | log::set_logger(&LOGGER) 94 | .map(|()| log::set_max_level(log::LevelFilter::Info))?; 95 | 96 | let opt: Opt = argh::from_env(); 97 | let launcher = opt.launcher()?; 98 | 99 | let builder = Builder { 100 | rust_version: opt.rust_version, 101 | mode: opt.mode, 102 | bin: opt.bin, 103 | strip: opt.strip, 104 | launcher, 105 | code_root: opt.code_root.unwrap_or_else(|| opt.project.clone()), 106 | project_path: opt.project, 107 | packages: opt.package, 108 | relabel: None, 109 | }; 110 | builder.run()?; 111 | } 112 | 113 | #[cfg(test)] 114 | mod tests { 115 | use super::*; 116 | use argh::FromArgs; 117 | 118 | /// Test that the readme's usage section is up to date 119 | #[test] 120 | fn test_readme_usage() { 121 | let readme = include_str!("../../README.md"); 122 | let mut usage = Opt::from_args(&["aws-build"], &["--help"]) 123 | .unwrap_err() 124 | .output; 125 | // Remove the "Usage: " prefix which is not in the readme 126 | usage = usage.replace("Usage: ", ""); 127 | assert!(readme.contains(&usage)); 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /xtask/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "xtask" 3 | version = "0.0.0" 4 | edition = "2021" 5 | publish = false 6 | 7 | [dependencies] 8 | anyhow = { version = "1.0.45", default-features = false, features = ["std"] } 9 | argh = { version = "0.1.6", default-features = false } 10 | command-run = { version = "1.1.1", default-features = false } 11 | fehler = { version = "1.0.0", default-features = false } 12 | fs-err = { version = "2.6.0", default-features = false } 13 | rayon = { version = "1.5.1", default-features = false } 14 | 15 | [package.metadata.release] 16 | release = false 17 | -------------------------------------------------------------------------------- /xtask/src/main.rs: -------------------------------------------------------------------------------- 1 | //! Run tests on the aws-build binary. This is done via xtask rather 2 | //! than normal tests so that we can test the end-user binary, not just 3 | //! the library. 4 | 5 | use anyhow::{anyhow, Error}; 6 | use argh::FromArgs; 7 | use command_run::Command; 8 | use fehler::throws; 9 | use fs_err as fs; 10 | use rayon::prelude::*; 11 | use std::env; 12 | use std::ffi::OsStr; 13 | use std::path::{Path, PathBuf}; 14 | 15 | /// Custom tasks. 16 | #[derive(Debug, FromArgs)] 17 | struct Opt { 18 | #[argh(subcommand)] 19 | action: Action, 20 | } 21 | 22 | #[derive(Debug, FromArgs)] 23 | #[argh(subcommand)] 24 | enum Action { 25 | RunContainerTests(RunContainerTests), 26 | } 27 | 28 | /// Run "live" tests using docker or podman. 29 | #[derive(Debug, FromArgs)] 30 | #[argh(subcommand, name = "run-container-tests")] 31 | struct RunContainerTests { 32 | /// delete the cache directory before running the tests 33 | #[argh(switch)] 34 | clean: bool, 35 | 36 | /// base container command, e.g. docker or podman, auto-detected by 37 | /// default 38 | #[argh(option)] 39 | container_cmd: Option, 40 | 41 | /// run a single test with the given name 42 | #[argh(option)] 43 | name: Option, 44 | } 45 | 46 | /// Get the absolute path of the repo. Assumes that this executable is 47 | /// located at /target//. 48 | #[throws] 49 | fn get_repo_path() -> PathBuf { 50 | let exe = env::current_exe()?; 51 | exe.parent() 52 | .map(|path| path.parent()) 53 | .flatten() 54 | .map(|path| path.parent()) 55 | .flatten() 56 | .ok_or_else(|| anyhow!("not enough parents: {}", exe.display()))? 57 | .into() 58 | } 59 | 60 | #[throws] 61 | fn make_mock_project(root: &Path, name: &str, deps: &[&str]) { 62 | fs::create_dir_all(root)?; 63 | 64 | let toml = format!( 65 | r#" 66 | [package] 67 | name = "{}" 68 | version = "0.0.0" 69 | [dependencies] 70 | {} 71 | "#, 72 | name, 73 | deps.join("\n"), 74 | ); 75 | 76 | fs::write(root.join("Cargo.toml"), toml)?; 77 | fs::create_dir_all(root.join("src"))?; 78 | fs::write( 79 | root.join("src/main.rs"), 80 | r#"fn main() {} 81 | "#, 82 | )?; 83 | Command::with_args("cargo", &["generate-lockfile"]) 84 | .set_dir(root) 85 | .run()?; 86 | } 87 | 88 | enum BuildMode { 89 | Al2, 90 | Lambda, 91 | } 92 | 93 | impl BuildMode { 94 | fn as_str(&self) -> &'static str { 95 | match self { 96 | Self::Al2 => "al2", 97 | Self::Lambda => "lambda", 98 | } 99 | } 100 | 101 | fn extension(&self) -> Option<&'static OsStr> { 102 | match self { 103 | Self::Al2 => None, 104 | Self::Lambda => Some(OsStr::new("zip")), 105 | } 106 | } 107 | } 108 | 109 | struct Checker<'a> { 110 | mode: BuildMode, 111 | project_name: &'a str, 112 | project_path: PathBuf, 113 | code_root: Option<&'a Path>, 114 | } 115 | 116 | impl<'a> Checker<'a> { 117 | /// Build the project and return the output symlink path. 118 | #[throws] 119 | fn build(&self, test_input: &TestInput) -> PathBuf { 120 | let mut cmd = 121 | Command::with_args("cargo", &["run", "--bin", "aws-build", "--"]); 122 | if let Some(code_root) = self.code_root { 123 | cmd.add_arg("--code-root"); 124 | cmd.add_arg(code_root); 125 | } 126 | cmd.add_arg(self.mode.as_str()); 127 | cmd.add_arg(&self.project_path); 128 | cmd.set_dir(&test_input.repo_dir); 129 | cmd.enable_capture(); 130 | cmd.combine_output(); 131 | cmd.log_output_on_error = true; 132 | 133 | if let Some(container_cmd) = &test_input.container_cmd { 134 | cmd.add_args(&["--container-cmd", container_cmd]); 135 | } 136 | 137 | let output = cmd.run()?; 138 | let stdout = output.stdout_string_lossy(); 139 | let symlink_path = stdout 140 | .lines() 141 | .find_map(|line| line.strip_prefix("symlink: ")) 142 | .ok_or_else(|| anyhow!("symlink not found in output"))?; 143 | PathBuf::from(symlink_path) 144 | } 145 | 146 | #[throws] 147 | fn build_and_check(&self, test_input: &TestInput) { 148 | let symlink_path = self.build(test_input)?; 149 | let real_output_path = fs::canonicalize(&symlink_path)?; 150 | 151 | let target_dir = self.project_path.join("target"); 152 | 153 | // Symlink is at the expected path. 154 | let expected_symlink_name = format!("latest-{}", self.mode.as_str()); 155 | assert_eq!(symlink_path, target_dir.join(expected_symlink_name)); 156 | 157 | // Real output is in the right directory. 158 | assert!(real_output_path.starts_with( 159 | target_dir.join("aws-build").join(self.mode.as_str()) 160 | )); 161 | 162 | // Real output's file name has the right form. 163 | let real_file_name = real_output_path.file_stem().unwrap(); 164 | let parts = real_file_name 165 | .to_str() 166 | .unwrap() 167 | .split('-') 168 | .collect::>(); 169 | dbg!(real_file_name); 170 | assert_eq!(parts.len(), 4); 171 | assert_eq!(parts[0], self.mode.as_str()); 172 | assert_eq!(parts[1], self.project_name); 173 | assert_eq!(parts[2].len(), 8); 174 | assert_eq!(parts[3].len(), 16); 175 | 176 | // Real output's extension is correct. 177 | assert_eq!(real_output_path.extension(), self.mode.extension()); 178 | } 179 | } 180 | 181 | struct TestInput { 182 | container_cmd: Option, 183 | repo_dir: PathBuf, 184 | test_dir: PathBuf, 185 | } 186 | 187 | /// Simple Amazon Linux 2 test. 188 | #[throws] 189 | fn test_al2(test_input: &TestInput) { 190 | let project_name = "proj"; 191 | make_mock_project(&test_input.test_dir, project_name, &[])?; 192 | Checker { 193 | mode: BuildMode::Al2, 194 | project_name, 195 | project_path: test_input.test_dir.clone(), 196 | code_root: None, 197 | } 198 | .build_and_check(test_input)?; 199 | } 200 | 201 | /// Simple Lambda test. 202 | #[throws] 203 | fn test_lambda(test_input: &TestInput) { 204 | let project_name = "proj"; 205 | make_mock_project(&test_input.test_dir, project_name, &[])?; 206 | Checker { 207 | mode: BuildMode::Lambda, 208 | project_name, 209 | project_path: test_input.test_dir.clone(), 210 | code_root: None, 211 | } 212 | .build_and_check(test_input)?; 213 | } 214 | 215 | /// Test that downloading dependencies works. 216 | /// 217 | /// The dependency is arbitrary, just want to check that any dependency 218 | /// works from within the container. 219 | #[throws] 220 | fn test_deps(test_input: &TestInput) { 221 | let project_name = "proj"; 222 | let dep = r#"arrayvec = { version = "0.7.2", default-features = false }"#; 223 | make_mock_project(&test_input.test_dir, project_name, &[dep])?; 224 | Checker { 225 | mode: BuildMode::Al2, 226 | project_path: test_input.test_dir.clone(), 227 | project_name, 228 | code_root: None, 229 | } 230 | .build_and_check(test_input)?; 231 | } 232 | 233 | struct TwoProjects { 234 | proj1: &'static str, 235 | proj2: &'static str, 236 | proj1_path: PathBuf, 237 | proj2_path: PathBuf, 238 | } 239 | 240 | impl TwoProjects { 241 | #[throws] 242 | fn new(test_dir: &Path) -> TwoProjects { 243 | let proj1 = "proj1"; 244 | let proj2 = "proj2"; 245 | let projects = TwoProjects { 246 | proj1, 247 | proj2, 248 | proj1_path: test_dir.join(proj1), 249 | proj2_path: test_dir.join(proj2), 250 | }; 251 | 252 | fs::create_dir_all(&projects.proj1_path)?; 253 | fs::create_dir_all(&projects.proj2_path)?; 254 | 255 | make_mock_project(&projects.proj1_path, projects.proj1, &[])?; 256 | 257 | make_mock_project( 258 | &projects.proj2_path, 259 | projects.proj2, 260 | &[r#"proj1 = { path = "../proj1" }"#], 261 | )?; 262 | 263 | projects 264 | } 265 | } 266 | 267 | /// Test that building a project in a subdirectory of the code root 268 | /// works. 269 | #[throws] 270 | fn test_code_root(test_input: &TestInput) { 271 | let projects = TwoProjects::new(&test_input.test_dir)?; 272 | 273 | Checker { 274 | mode: BuildMode::Al2, 275 | code_root: Some(&test_input.test_dir), 276 | project_name: projects.proj2, 277 | project_path: projects.proj2_path, 278 | } 279 | .build_and_check(test_input)?; 280 | } 281 | 282 | /// Test that a project path outside the code root fails. 283 | #[throws] 284 | fn test_bad_project_path(test_input: &TestInput) { 285 | let projects = TwoProjects::new(&test_input.test_dir)?; 286 | 287 | let checker = Checker { 288 | mode: BuildMode::Al2, 289 | code_root: Some(&projects.proj1_path), 290 | project_name: projects.proj2, 291 | project_path: projects.proj2_path, 292 | }; 293 | assert!(checker.build_and_check(test_input).is_err()); 294 | } 295 | 296 | type TestFn = fn(&TestInput) -> Result<(), Error>; 297 | 298 | const TEST_FUNCS: &[(TestFn, &str)] = &[ 299 | (test_al2, "test_al2"), 300 | (test_lambda, "test_lambda"), 301 | (test_deps, "test_deps"), 302 | (test_code_root, "test_code_root"), 303 | (test_bad_project_path, "test_bad_project_path"), 304 | ]; 305 | 306 | #[throws] 307 | fn run_one_test(args: &RunContainerTests, name: &str) { 308 | let mut test_input = TestInput { 309 | container_cmd: args.container_cmd.clone(), 310 | repo_dir: get_repo_path()?, 311 | test_dir: Default::default(), 312 | }; 313 | let base_test_dir = test_input.repo_dir.join("container_tests"); 314 | 315 | let (func, test_name) = TEST_FUNCS 316 | .iter() 317 | .find(|(_, test_name)| *test_name == name) 318 | .ok_or_else(|| anyhow!("test '{}' not found", name))?; 319 | test_input.test_dir = base_test_dir.join(test_name); 320 | func(&test_input)?; 321 | 322 | println!("success"); 323 | } 324 | 325 | #[throws] 326 | fn run_all_tests(args: &RunContainerTests) { 327 | let exe = env::current_exe()?; 328 | 329 | let failures: Vec<_> = TEST_FUNCS 330 | .par_iter() 331 | .filter_map(|(_func, test_name)| { 332 | let mut cmd = Command::with_args( 333 | exe.clone(), 334 | &["run-container-tests", "--name", test_name], 335 | ); 336 | if let Some(container_cmd) = &args.container_cmd { 337 | cmd.add_args(&["--container-cmd", container_cmd]); 338 | } 339 | cmd.combine_output = true; 340 | cmd.capture = true; 341 | cmd.check = false; 342 | 343 | let output = cmd.run().expect("failed to run command"); 344 | if output.status.success() { 345 | None 346 | } else { 347 | Some((test_name, output.stdout_string_lossy().to_string())) 348 | } 349 | }) 350 | .collect(); 351 | 352 | for (test_name, output) in &failures { 353 | println!("{} failed: {}\n-----\n", test_name, output); 354 | } 355 | 356 | if !failures.is_empty() { 357 | panic!("{} test(s) failed", failures.len()); 358 | } 359 | } 360 | 361 | #[throws] 362 | fn run_container_tests(args: &RunContainerTests) { 363 | let test_input = TestInput { 364 | container_cmd: args.container_cmd.clone(), 365 | repo_dir: get_repo_path()?, 366 | test_dir: Default::default(), 367 | }; 368 | let base_test_dir = test_input.repo_dir.join("container_tests"); 369 | 370 | if args.clean { 371 | println!("cleaning {}", base_test_dir.display()); 372 | fs::remove_dir_all(&base_test_dir)?; 373 | } 374 | 375 | fs::create_dir_all(&base_test_dir)?; 376 | 377 | if let Some(name) = &args.name { 378 | run_one_test(args, name)?; 379 | } else { 380 | run_all_tests(args)?; 381 | } 382 | 383 | println!("success"); 384 | } 385 | 386 | #[throws] 387 | fn main() { 388 | let opt: Opt = argh::from_env(); 389 | 390 | match opt.action { 391 | Action::RunContainerTests(args) => run_container_tests(&args)?, 392 | } 393 | } 394 | --------------------------------------------------------------------------------