From 0123bd109545d9a20aebe08479f1e434d8307482 Mon Sep 17 00:00:00 2001 From: Denis Cornehl Date: Wed, 30 Sep 2026 17:05:58 +0200 Subject: [PATCH 1/2] refactor fastly access logs into shared library, use it for crates.io & docs.rs --- Cargo.lock | 422 +++++++++++++++++- Cargo.toml | 1 + crates/fastly-access-log/.gitignore | 26 ++ crates/fastly-access-log/Cargo.toml | 13 + crates/fastly-access-log/src/lib.rs | 199 +++++++++ .../docs-rs/fastly-compute-docs-rs/Cargo.lock | 96 ++++ .../docs-rs/fastly-compute-docs-rs/Cargo.toml | 1 + .../fastly-compute-docs-rs/src/logging.rs | 13 +- .../fastly-compute-docs-rs/src/main.rs | 62 ++- terraform/docs-rs/fastly.tf | 6 + .../crates-io/compute-static/Cargo.lock | 16 +- .../crates-io/compute-static/Cargo.toml | 5 +- .../crates-io/compute-static/src/log_line.rs | 51 --- .../crates-io/compute-static/src/main.rs | 99 +--- 14 files changed, 836 insertions(+), 174 deletions(-) create mode 100644 crates/fastly-access-log/.gitignore create mode 100644 crates/fastly-access-log/Cargo.toml create mode 100644 crates/fastly-access-log/src/lib.rs delete mode 100644 terragrunt/modules/crates-io/compute-static/src/log_line.rs diff --git a/Cargo.lock b/Cargo.lock index b734604d1..3a05d5f0b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -81,6 +81,12 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "anyhow" +version = "1.0.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "330a5ed07fa54e4702c9d6c4174f74427fc0ef6e214bbd677ae50a5099946470" + [[package]] name = "autocfg" version = "1.4.0" @@ -126,6 +132,15 @@ version = "2.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c4512299f36f043ab09a583e57bceb5a5aab7a73db1805848e8fef3c9e8c78b3" +[[package]] +name = "block-buffer" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" +dependencies = [ + "generic-array", +] + [[package]] name = "bumpalo" version = "3.16.0" @@ -199,7 +214,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn", + "syn 2.0.90", ] [[package]] @@ -230,6 +245,15 @@ version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + [[package]] name = "curl" version = "0.4.47" @@ -260,6 +284,90 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "darling" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d00b9596d185e565c2207a0b01f8bd1a135483d02d9b7b0a54b11da8d53412e" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn 2.0.90", +] + +[[package]] +name = "darling_macro" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" +dependencies = [ + "darling_core", + "quote", + "syn 2.0.90", +] + +[[package]] +name = "deranged" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" +dependencies = [ + "serde_core", +] + +[[package]] +name = "derive_builder" +version = "0.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "507dfb09ea8b7fa618fcf76e953f4f5e192547945816d5358edffe39f6f94947" +dependencies = [ + "derive_builder_macro", +] + +[[package]] +name = "derive_builder_core" +version = "0.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d5bcf7b024d6835cfb3d473887cd966994907effbe9227e8c8219824d06c4e8" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn 2.0.90", +] + +[[package]] +name = "derive_builder_macro" +version = "0.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab63b0e2bf4d5928aff72e83a7dace85d7bba5fe12dcc3c5a572d78caffd3f3c" +dependencies = [ + "derive_builder_core", + "syn 2.0.90", +] + +[[package]] +name = "digest" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" +dependencies = [ + "generic-array", +] + [[package]] name = "displaydoc" version = "0.2.5" @@ -268,7 +376,29 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.90", +] + +[[package]] +name = "downcast-rs" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2" + +[[package]] +name = "either" +version = "1.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "252afb9ae5eaa683babdc6a068b3f5726eb19e05070c731f9b2a23a7c3e8ed34" + +[[package]] +name = "elsa" +version = "1.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9abf33c656a7256451ebb7d0082c5a471820c31269e49d807c538c252352186e" +dependencies = [ + "indexmap", + "stable_deref_trait", ] [[package]] @@ -296,6 +426,78 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "fastly" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c5c07c0c67d0b5826d1cb2311e563a17860b50e2ae89e1c703d6400d904d1e2" +dependencies = [ + "anyhow", + "bytes", + "downcast-rs", + "elsa", + "fastly-macros", + "fastly-shared", + "fastly-sys", + "http 1.5.0", + "itertools", + "mime", + "serde", + "serde_json", + "serde_repr", + "serde_urlencoded", + "sha2", + "smallvec", + "thiserror", + "time", + "url", +] + +[[package]] +name = "fastly-access-log" +version = "0.1.0" +dependencies = [ + "derive_builder", + "fastly", + "serde", + "serde_json", + "time", +] + +[[package]] +name = "fastly-macros" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "698025eab5612adab20df48963a5126650893014e8505af02a0af20939ee6a05" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "fastly-shared" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "191c2cf082f5d17f4ca0685cedaa0642ad4ade949fac8d1dbdd62ff756b13500" +dependencies = [ + "bitflags 1.3.2", + "http 1.5.0", +] + +[[package]] +name = "fastly-sys" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "239c77706cf9110b423a89b6b03b8011729b144390d2a7f641d95ef66b2768b8" +dependencies = [ + "bitflags 1.3.2", + "fastly-shared", + "http 1.5.0", + "wasip2", + "wit-bindgen 0.51.0", +] + [[package]] name = "fastrand" version = "2.3.0" @@ -386,6 +588,16 @@ dependencies = [ "slab", ] +[[package]] +name = "generic-array" +version = "0.14.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bb6743198531e02858aeaea5398fcc883e71851fcbcb5a2f773e2fb6cb1edf2" +dependencies = [ + "typenum", + "version_check", +] + [[package]] name = "gimli" version = "0.31.1" @@ -403,7 +615,7 @@ dependencies = [ "futures-core", "futures-sink", "futures-util", - "http", + "http 0.2.12", "indexmap", "slab", "tokio", @@ -434,6 +646,16 @@ dependencies = [ "itoa", ] +[[package]] +name = "http" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "918d3568bebf352712bc2ef3d46a8bcf1a75b373be6539de198e9105cbbf9ce0" +dependencies = [ + "bytes", + "itoa", +] + [[package]] name = "http-body" version = "0.4.6" @@ -441,7 +663,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" dependencies = [ "bytes", - "http", + "http 0.2.12", "pin-project-lite", ] @@ -468,7 +690,7 @@ dependencies = [ "futures-core", "futures-util", "h2", - "http", + "http 0.2.12", "http-body", "httparse", "httpdate", @@ -632,9 +854,15 @@ checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.90", ] +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + [[package]] name = "idna" version = "1.0.3" @@ -689,6 +917,15 @@ version = "1.70.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" +[[package]] +name = "itertools" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "1.0.14" @@ -761,7 +998,7 @@ checksum = "f3cd9f9bbedc1b92683a9847b8db12f3203cf32af6a11db085fa007708dc9555" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.90", ] [[package]] @@ -812,6 +1049,12 @@ dependencies = [ "tempfile", ] +[[package]] +name = "num-conv" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "521739c6d2bac4aa25192232afe6841231376b2b26d4d9fae5ecf8ca5772e441" + [[package]] name = "num-traits" version = "0.2.19" @@ -836,6 +1079,12 @@ version = "1.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" +[[package]] +name = "opaque-debug" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" + [[package]] name = "openssl" version = "0.10.68" @@ -859,7 +1108,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.90", ] [[package]] @@ -904,6 +1153,12 @@ version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2" +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + [[package]] name = "proc-macro2" version = "1.0.92" @@ -934,7 +1189,7 @@ dependencies = [ "futures-core", "futures-util", "h2", - "http", + "http 0.2.12", "http-body", "hyper", "hyper-tls", @@ -1055,7 +1310,7 @@ checksum = "8db53ae22f34573731bafa1db20f04027b2d25e02d8205921b569171699cdb33" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.90", ] [[package]] @@ -1070,6 +1325,17 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_repr" +version = "0.1.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d3b1629de253c70a0508c3899572da79ca359fdab27c7920ff00406df418906" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.6", +] + [[package]] name = "serde_urlencoded" version = "0.7.1" @@ -1094,6 +1360,19 @@ dependencies = [ "serde_json", ] +[[package]] +name = "sha2" +version = "0.9.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d58a1e1bf39749807d89cf2d98ac2dfa0ff1cb3faa38fbb64dd88ac8013d800" +dependencies = [ + "block-buffer", + "cfg-if", + "cpufeatures", + "digest", + "opaque-debug", +] + [[package]] name = "shlex" version = "1.3.0" @@ -1147,6 +1426,17 @@ version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + [[package]] name = "syn" version = "2.0.90" @@ -1158,6 +1448,17 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "syn" +version = "3.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8593e8e72159ed2257d083c7a454a85cbf854f37a0966d8d483aff8c8a3ebcee" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + [[package]] name = "sync_wrapper" version = "0.1.2" @@ -1172,7 +1473,7 @@ checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.90", ] [[package]] @@ -1217,6 +1518,56 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.90", +] + +[[package]] +name = "time" +version = "0.3.55" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdb87b95ec50ddfa440816d227a17b2ccbdda963a316a727fda0fc4334f7d134" +dependencies = [ + "deranged", + "num-conv", + "powerfmt", + "serde_core", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e1c906769ad99c88eaa54e728060edef082f8e358ff32030cb7c7d315e81109" + +[[package]] +name = "time-macros" +version = "0.2.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e689342a48d2ea927c87ea50cabf8594854bf940e9310208848d680d668ed85" +dependencies = [ + "num-conv", + "time-core", +] + [[package]] name = "tinystr" version = "0.7.6" @@ -1298,6 +1649,12 @@ version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" +[[package]] +name = "typenum" +version = "1.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20" + [[package]] name = "unicode-ident" version = "1.0.14" @@ -1339,6 +1696,12 @@ version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + [[package]] name = "want" version = "0.3.1" @@ -1354,6 +1717,15 @@ version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +[[package]] +name = "wasip2" +version = "1.0.4+wasi-0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b67efb37e106e55ce722a510d6b5f9c17f083e5fc79afc2badeb12cc313d9487" +dependencies = [ + "wit-bindgen 0.57.1", +] + [[package]] name = "wasm-bindgen" version = "0.2.97" @@ -1376,7 +1748,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn", + "syn 2.0.90", "wasm-bindgen-shared", ] @@ -1411,7 +1783,7 @@ checksum = "98c9ae5a76e46f4deecd0f0255cc223cfa18dc9b261213b8aa0c7b36f61b3f1d" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.90", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -1599,6 +1971,24 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "wit-bindgen" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" +dependencies = [ + "bitflags 2.11.1", +] + +[[package]] +name = "wit-bindgen" +version = "0.57.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" +dependencies = [ + "bitflags 2.11.1", +] + [[package]] name = "write16" version = "1.0.0" @@ -1631,7 +2021,7 @@ checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.90", "synstructure", ] @@ -1652,7 +2042,7 @@ checksum = "595eed982f7d355beb85837f651fa22e90b3c044842dc7f2c2842c086f295808" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.90", "synstructure", ] @@ -1675,5 +2065,5 @@ checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.90", ] diff --git a/Cargo.toml b/Cargo.toml index 8bcabea67..cea855d20 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,6 +3,7 @@ # https://doc.rust-lang.org/cargo/reference/resolver.html#resolver-versions resolver = "2" members = [ + "crates/fastly-access-log", "setup-deploy-keys", "ansible/roles/dev-desktop/files/team_login", ] diff --git a/crates/fastly-access-log/.gitignore b/crates/fastly-access-log/.gitignore new file mode 100644 index 000000000..b99b5ae6f --- /dev/null +++ b/crates/fastly-access-log/.gitignore @@ -0,0 +1,26 @@ +### Rust +# Generated by Cargo +# will have compiled files and executables +debug +target + +# These are backup files generated by rustfmt +**/*.rs.bk + +# MSVC Windows builds of rustc generate these, which store debugging information +*.pdb + +# Generated by cargo mutants +# Contains mutation testing data +**/mutants.out*/ + +# rustc will dump stack traces when hitting an internal compiler error to PWD +rustc-ice-*.txt + +# RustRover +# JetBrains specific template is maintained in a separate JetBrains.gitignore that can +# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore +# and can be added to the global gitignore or merged into this file. For a more nuclear +# option (not recommended) you can uncomment the following to ignore the entire idea folder. +#.idea/ + diff --git a/crates/fastly-access-log/Cargo.toml b/crates/fastly-access-log/Cargo.toml new file mode 100644 index 000000000..e8edba151 --- /dev/null +++ b/crates/fastly-access-log/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "fastly-access-log" +description = "Shared access-log schema and collection helpers for Fastly Compute services" +version = "0.1.0" +edition = "2024" +publish = false + +[dependencies] +derive_builder = "0.20.2" +fastly = "0.13.1" +serde = { version = "1.0", features = ["derive"] } +serde_json = "1.0" +time = { version = "0.3.17", features = ["serde-human-readable"] } diff --git a/crates/fastly-access-log/src/lib.rs b/crates/fastly-access-log/src/lib.rs new file mode 100644 index 000000000..d0ed196ab --- /dev/null +++ b/crates/fastly-access-log/src/lib.rs @@ -0,0 +1,199 @@ +use derive_builder::Builder; +use fastly::{Error, Request, Response, http::Version}; +use serde::Serialize; +use std::{env::var, net::IpAddr}; +use time::OffsetDateTime; + +#[derive(Debug, Serialize)] +#[serde(tag = "version")] +pub enum LogLine { + #[serde(rename = "1")] + V1(LogLineV1), +} + +impl LogLine { + pub fn to_json(self) -> String { + serde_json::to_string(&self).expect("failed to serialize request log") + } +} + +// `ddsource`, `ddtags`, and `service` are reserved Datadog log attributes. +#[derive(Debug, Builder, Serialize)] +#[builder(build_fn(private, name = "build_internal"))] +pub struct LogLineV1 { + #[builder(default = "default_source()")] + ddsource: &'static str, + ddtags: String, + service: &'static str, + bytes: Option, + content_type: Option, + #[serde(with = "time::serde::rfc3339")] + date_time: OffsetDateTime, + edge_location: Option, + host: Option, + http: Option, + ip: Option, + method: Option, + status: Option, + tls: Option, + url: String, +} + +impl LogLineV1Builder { + pub fn build(self) -> Result { + Ok(LogLine::V1(self.build_internal()?)) + } +} + +/// return default source for usage with datadog. +/// +/// We have custom input parsers in datadog that +/// handle exactly this access log format. +/// +/// These are bound to this source identifier. +fn default_source() -> &'static str { + "fastly" +} + +#[derive(Clone, Debug, Builder, Serialize)] +pub struct HttpDetails { + protocol: Option, + referer: Option, + useragent: Option, +} + +#[derive(Clone, Debug, Builder, Serialize)] +pub struct TlsDetails { + cipher: Option, + protocol: Option, +} + +/// Collect access-log fields available before the request is sent to a backend. +pub fn collect_request( + request: &Request, + service: &'static str, + app: &str, + env: &str, + host: Option, +) -> LogLineV1Builder { + let http_details = HttpDetailsBuilder::default() + .protocol(http_version_to_string(request.get_version())) + .referer( + request + .get_header("Referer") + .and_then(|s| s.to_str().ok()) + .map(str::to_owned), + ) + .useragent( + request + .get_header("User-Agent") + .and_then(|s| s.to_str().ok()) + .map(str::to_owned), + ) + .build() + .ok(); + + let tls_details = TlsDetailsBuilder::default() + .cipher( + request + .get_tls_cipher_openssl_name() + .ok() + .flatten() + .map(str::to_owned), + ) + .protocol(request.get_tls_protocol().ok().flatten().map(str::to_owned)) + .build() + .ok(); + + LogLineV1Builder::default() + .ddtags(format!("app:{},env:{}", app, env)) + .service(service) + .date_time(OffsetDateTime::now_utc()) + .edge_location(var("FASTLY_POP").ok()) + .host(host) + .http(http_details) + .ip(request.get_client_ip_addr()) + .method(Some(request.get_method().to_string())) + .url(request.get_url_str().into()) + .tls(tls_details) + .to_owned() +} + +/// Add fields available after the backend response is received. +pub fn collect_response( + log_line: &mut LogLineV1Builder, + response: &Result, +) -> LogLineV1Builder { + if let Ok(response) = response { + log_line + .bytes(response.get_content_length()) + .content_type( + response + .get_content_type() + .map(|content_type| content_type.to_string()), + ) + .status(Some(response.get_status().as_u16())) + .to_owned() + } else { + collect_error_response(log_line) + } +} + +fn collect_error_response(log_line: &mut LogLineV1Builder) -> LogLineV1Builder { + // No response was received, so this cannot have been a cache hit. + log_line.status(Some(500)).to_owned() +} + +fn http_version_to_string(version: Version) -> Option { + match version { + Version::HTTP_09 => Some("HTTP/0.9".into()), + Version::HTTP_10 => Some("HTTP/1.0".into()), + Version::HTTP_11 => Some("HTTP/1.1".into()), + Version::HTTP_2 => Some("HTTP/2".into()), + Version::HTTP_3 => Some("HTTP/3".into()), + _ => None, + } +} + +#[cfg(test)] +mod tests { + use super::*; + use serde_json::Value; + + fn minimal_log_builder() -> LogLineV1Builder { + LogLineV1Builder::default() + .ddtags("app:test,env:test".into()) + .service("test") + .bytes(None) + .content_type(None) + .date_time(OffsetDateTime::UNIX_EPOCH) + .edge_location(None) + .host(None) + .http(None) + .ip(None) + .method(None) + .status(None) + .tls(None) + .url("https://example.test/".into()) + .to_owned() + } + + #[test] + fn serialized_log_has_the_expected_access_log_contract() { + let serialized = minimal_log_builder().build().unwrap().to_json(); + let value: Value = serde_json::from_str(&serialized).unwrap(); + + assert_eq!(value["version"], Value::String("1".into())); + assert!(value["status"].is_null()); + } + + #[test] + fn error_responses_are_logged_as_uncached_500s() { + let log = collect_error_response(&mut minimal_log_builder()) + .build() + .unwrap(); + let value = serde_json::to_value(log).unwrap(); + + assert_eq!(value["status"], Value::from(500)); + } +} diff --git a/terraform/docs-rs/fastly-compute-docs-rs/Cargo.lock b/terraform/docs-rs/fastly-compute-docs-rs/Cargo.lock index fb0560baf..8955ef267 100644 --- a/terraform/docs-rs/fastly-compute-docs-rs/Cargo.lock +++ b/terraform/docs-rs/fastly-compute-docs-rs/Cargo.lock @@ -76,6 +76,41 @@ dependencies = [ "libc", ] +[[package]] +name = "darling" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d00b9596d185e565c2207a0b01f8bd1a135483d02d9b7b0a54b11da8d53412e" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn 2.0.119", +] + +[[package]] +name = "darling_macro" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" +dependencies = [ + "darling_core", + "quote", + "syn 2.0.119", +] + [[package]] name = "deranged" version = "0.5.8" @@ -85,6 +120,37 @@ dependencies = [ "serde_core", ] +[[package]] +name = "derive_builder" +version = "0.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "507dfb09ea8b7fa618fcf76e953f4f5e192547945816d5358edffe39f6f94947" +dependencies = [ + "derive_builder_macro", +] + +[[package]] +name = "derive_builder_core" +version = "0.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d5bcf7b024d6835cfb3d473887cd966994907effbe9227e8c8219824d06c4e8" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "derive_builder_macro" +version = "0.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab63b0e2bf4d5928aff72e83a7dace85d7bba5fe12dcc3c5a572d78caffd3f3c" +dependencies = [ + "derive_builder_core", + "syn 2.0.119", +] + [[package]] name = "digest" version = "0.9.0" @@ -160,11 +226,23 @@ dependencies = [ "url", ] +[[package]] +name = "fastly-access-log" +version = "0.1.0" +dependencies = [ + "derive_builder", + "fastly", + "serde", + "serde_json", + "time", +] + [[package]] name = "fastly-compute-project" version = "0.1.0" dependencies = [ "fastly", + "fastly-access-log", "serde", "serde_json", "time", @@ -208,6 +286,12 @@ dependencies = [ "wit-bindgen 0.51.0", ] +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + [[package]] name = "form_urlencoded" version = "1.2.2" @@ -362,6 +446,12 @@ dependencies = [ "zerovec", ] +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + [[package]] name = "idna" version = "1.1.0" @@ -653,6 +743,12 @@ version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + [[package]] name = "syn" version = "1.0.109" diff --git a/terraform/docs-rs/fastly-compute-docs-rs/Cargo.toml b/terraform/docs-rs/fastly-compute-docs-rs/Cargo.toml index 17a74a45d..824d843a0 100644 --- a/terraform/docs-rs/fastly-compute-docs-rs/Cargo.toml +++ b/terraform/docs-rs/fastly-compute-docs-rs/Cargo.toml @@ -10,6 +10,7 @@ publish = false [dependencies] fastly = "0.13.1" +fastly-access-log = { path = "../../../crates/fastly-access-log" } serde = { version = "1", features = ["derive"] } serde_json = "1" uuid = { version = "1.25.0", features = ["v4", "fast-rng"] } diff --git a/terraform/docs-rs/fastly-compute-docs-rs/src/logging.rs b/terraform/docs-rs/fastly-compute-docs-rs/src/logging.rs index 776631639..a9082b49c 100644 --- a/terraform/docs-rs/fastly-compute-docs-rs/src/logging.rs +++ b/terraform/docs-rs/fastly-compute-docs-rs/src/logging.rs @@ -1,9 +1,10 @@ +use crate::{DATADOG_APP, DATADOG_ENV, DATADOG_SERVICE}; use fastly::log::Endpoint; use serde::Serialize; use serde_json::{Map, Value}; use std::{ io::{self, Write}, - sync::Mutex, + sync::{LazyLock, Mutex}, }; use time::OffsetDateTime; use tracing::{ @@ -19,8 +20,12 @@ use tracing_subscriber::{ util::SubscriberInitExt, }; -// Must match the logging endpoint name in Terraform. +static DDTAGS: LazyLock = + LazyLock::new(|| format!("app:{},env:{}", DATADOG_APP, DATADOG_ENV)); + +// Must match the logging endpoint names in Terraform. const APPLICATION_LOG_ENDPOINT: &str = "application_logs"; +pub(crate) const ACCESS_LOG_ENDPOINT: &str = "access_logs"; // About `ddsource` // This corresponds to the integration name, the technology @@ -129,11 +134,11 @@ where let row = TraceLog { ddsource: LOG_SOURCE_FORMAT, - ddtags: "env:production", + ddtags: &DDTAGS, hostname: fastly::compute_runtime::hostname(), timestamp: OffsetDateTime::now_utc(), message: message.as_deref().unwrap_or_default(), - service: "docs.rs fastly WASM", + service: DATADOG_SERVICE, status: level_name(*event.metadata().level()), target: event.metadata().target(), fields, diff --git a/terraform/docs-rs/fastly-compute-docs-rs/src/main.rs b/terraform/docs-rs/fastly-compute-docs-rs/src/main.rs index 6a5d3061e..7ce55dac0 100644 --- a/terraform/docs-rs/fastly-compute-docs-rs/src/main.rs +++ b/terraform/docs-rs/fastly-compute-docs-rs/src/main.rs @@ -2,14 +2,18 @@ mod logging; mod ngwaf; mod shield; +use crate::logging::ACCESS_LOG_ENDPOINT; use fastly::{ ConfigStore, Error, Request, Response, SecretStore, - error::Context as _, + error::{Context as _, bail}, http::{ HeaderName, Method, StatusCode, header::{CACHE_CONTROL, EXPIRES, STRICT_TRANSPORT_SECURITY}, }, + log::Endpoint, }; +use fastly_access_log::{LogLineV1Builder, collect_request, collect_response}; +use std::io::Write; use tracing::{error, warn}; use uuid::Uuid; @@ -35,15 +39,59 @@ const X_FORWARDED_HOST: HeaderName = HeaderName::from_static("x-forwarded-host") const X_ORIGIN_AUTH: HeaderName = HeaderName::from_static("x-origin-auth"); const X_REQUEST_ID: HeaderName = HeaderName::from_static("x-request-id"); +pub(crate) const DATADOG_APP: &str = "docs.rs"; +pub(crate) const DATADOG_SERVICE: &str = "docs.rs fastly WASM"; +pub(crate) const DATADOG_ENV: &str = "production"; + #[fastly::main] -fn main(mut req: Request) -> Result { +fn main(req: Request) -> Result { logging::setup(); let request_span = tracing::info_span!("request", request_id = tracing::field::Empty); let _request_guard = request_span.enter(); + let mut access_log = collect_request( + &req, + DATADOG_SERVICE, + DATADOG_APP, + DATADOG_ENV, + Some(fastly::compute_runtime::hostname().to_owned()), + ); + + let result = handle(req); + let access_log = collect_response(&mut access_log, &result); + + if let Err(err) = build_and_send_log(access_log) { + warn!(?err, "error emitting access log"); + } + + result +} + +/// Finalize the builder and log the line +fn build_and_send_log(log_line: LogLineV1Builder) -> Result<(), Error> { + let mut endpoint = Endpoint::from_name(ACCESS_LOG_ENDPOINT); + + let serialized_log = log_line + .build() + .context("error building log line")? + .to_json(); + + let written = endpoint + .write(serialized_log.as_bytes()) + .context("failed to write access log")?; + + if written != serialized_log.len() { + bail!("short access log write: {written}/{}", serialized_log.len()); + } + + Ok(()) +} + +fn handle(mut req: Request) -> Result { let config = ConfigStore::open(DOCS_RS_CONFIG); let secrets = SecretStore::open(DOCS_RS_SECRET_STORE).expect("failed to open secret store"); + let shield = shield::Context::load(&config, &secrets, &mut req)?; let ngwaf = ngwaf::NgWaf::load(&config); @@ -96,15 +144,17 @@ fn main(mut req: Request) -> Result { // // Also sets the request id on the surrounding tracing span for the request, // the Datadog formatter will merge the value into the log-record. - if shield.response_is_for_client() { + let request_id = if shield.response_is_for_client() { let rid = Uuid::new_v4().to_string(); req.set_header(X_REQUEST_ID, &rid); - request_span.record("request_id", rid); + Some(rid) } else if let Some(rid) = req.get_header_str(&X_REQUEST_ID) { - request_span.record("request_id", rid); + Some(rid.to_owned()) } else { error!("shield POP received request without expected X-Request-ID header from edge POP"); - } + None + }; + tracing::Span::current().record("request_id", &request_id); if shield.target_is_origin() { let origin_auth = secrets diff --git a/terraform/docs-rs/fastly.tf b/terraform/docs-rs/fastly.tf index 360b75742..d18bac626 100644 --- a/terraform/docs-rs/fastly.tf +++ b/terraform/docs-rs/fastly.tf @@ -54,6 +54,12 @@ resource "fastly_service_compute" "docs_rs" { region = "US" } + logging_datadog { + name = "access_logs" + token = data.aws_ssm_parameter.datadog_api_key.value + region = "US" + } + domain { name = local.domain_name } diff --git a/terragrunt/modules/crates-io/compute-static/Cargo.lock b/terragrunt/modules/crates-io/compute-static/Cargo.lock index 7746e314f..8f0ef26ce 100644 --- a/terragrunt/modules/crates-io/compute-static/Cargo.lock +++ b/terragrunt/modules/crates-io/compute-static/Cargo.lock @@ -54,13 +54,10 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" name = "compute-static" version = "0.1.0" dependencies = [ - "derive_builder", "fastly", + "fastly-access-log", "log", "log-fastly", - "serde", - "serde_json", - "time", ] [[package]] @@ -222,6 +219,17 @@ dependencies = [ "url", ] +[[package]] +name = "fastly-access-log" +version = "0.1.0" +dependencies = [ + "derive_builder", + "fastly", + "serde", + "serde_json", + "time", +] + [[package]] name = "fastly-macros" version = "0.13.1" diff --git a/terragrunt/modules/crates-io/compute-static/Cargo.toml b/terragrunt/modules/crates-io/compute-static/Cargo.toml index 0237150e4..ad37bc019 100644 --- a/terragrunt/modules/crates-io/compute-static/Cargo.toml +++ b/terragrunt/modules/crates-io/compute-static/Cargo.toml @@ -17,10 +17,7 @@ codegen-units = 1 lto = "fat" [dependencies] -derive_builder = "0.20.2" fastly = "0.13.1" +fastly-access-log = { path = "../../../../crates/fastly-access-log" } log = "0.4.17" log-fastly = "0.13.1" -serde = { version = "1.0.152", features = ["derive"] } -serde_json = "1.0.92" -time = { version = "0.3.17", features = ["serde-human-readable"] } diff --git a/terragrunt/modules/crates-io/compute-static/src/log_line.rs b/terragrunt/modules/crates-io/compute-static/src/log_line.rs deleted file mode 100644 index da2c178cb..000000000 --- a/terragrunt/modules/crates-io/compute-static/src/log_line.rs +++ /dev/null @@ -1,51 +0,0 @@ -use std::net::IpAddr; - -use derive_builder::Builder; -use serde::Serialize; -use time::OffsetDateTime; - -#[derive(Debug, Serialize)] -#[serde(tag = "version")] -pub enum LogLine { - #[serde(rename = "1")] - V1(LogLineV1), -} - -// `ddsource`, `ddtags`, and `service` are reserved log attributes on Datadog that have a special -// meaning. See https://docs.datadoghq.com/logs/log_configuration/attributes_naming_convention. -#[derive(Debug, Builder, Serialize)] -pub struct LogLineV1 { - #[builder(default = "default_source()")] - ddsource: &'static str, - ddtags: String, - service: &'static str, - bytes: Option, - content_type: Option, - #[serde(with = "time::serde::rfc3339")] - date_time: OffsetDateTime, - edge_location: Option, - host: Option, - http: Option, - ip: Option, - method: Option, - status: Option, - tls: Option, - url: String, -} - -fn default_source() -> &'static str { - "fastly" -} - -#[derive(Clone, Debug, Builder, Serialize)] -pub struct HttpDetails { - protocol: Option, - referer: Option, - useragent: Option, -} - -#[derive(Clone, Debug, Builder, Serialize)] -pub struct TlsDetails { - cipher: Option, - protocol: Option, -} diff --git a/terragrunt/modules/crates-io/compute-static/src/main.rs b/terragrunt/modules/crates-io/compute-static/src/main.rs index e50ea58ce..643db1a8f 100644 --- a/terragrunt/modules/crates-io/compute-static/src/main.rs +++ b/terragrunt/modules/crates-io/compute-static/src/main.rs @@ -1,17 +1,14 @@ use compute_static::compression::is_compressible_content_type; use fastly::convert::ToHeaderValue; -use fastly::http::{header, Method, StatusCode, Version}; +use fastly::http::{header, Method, StatusCode}; use fastly::{Error, Request, Response}; +use fastly_access_log::{collect_request, collect_response, LogLineV1Builder}; use log::{info, warn, LevelFilter}; use log_fastly::Logger; -use std::env::var; -use time::OffsetDateTime; use crate::config::Config; -use crate::log_line::{HttpDetailsBuilder, LogLine, LogLineV1Builder, TlsDetailsBuilder}; mod config; -mod log_line; const DATADOG_APP: &str = "crates.io"; const DATADOG_SERVICE: &str = "static.crates.io"; @@ -30,7 +27,13 @@ fn main(request: Request) -> Result { } init_logging(&config); - let mut log = collect_request(&config, &request); + let mut log = collect_request( + &request, + DATADOG_SERVICE, + DATADOG_APP, + &config.datadog_env, + Some(config.datadog_host.clone()), + ); let has_origin_header = request.get_header("Origin").is_some(); let mut response = handle_request(&config, request); @@ -61,59 +64,6 @@ fn init_logging(config: &Config) { .init(); } -/// Collect data for the logs from the request -fn collect_request(config: &Config, request: &Request) -> LogLineV1Builder { - let http_details = HttpDetailsBuilder::default() - .protocol(http_version_to_string(request.get_version())) - .referer( - request - .get_header("Referer") - .and_then(|s| s.to_str().ok()) - .map(|s| s.to_string()), - ) - .useragent( - request - .get_header("User-Agent") - .and_then(|s| s.to_str().ok()) - .map(|s| s.to_string()), - ) - .build() - .ok(); - - let cipher = request - .get_tls_cipher_openssl_name() - .ok() - .flatten() - .map(|s| s.to_string()); - - let protocol = request - .get_tls_protocol() - .ok() - .flatten() - .map(|s| s.to_string()); - - let tls_details = TlsDetailsBuilder::default() - .cipher(cipher) - .protocol(protocol) - .build() - .ok(); - - let log_line = LogLineV1Builder::default() - .ddtags(format!("app:{},env:{}", DATADOG_APP, config.datadog_env)) - .service(DATADOG_SERVICE) - .date_time(OffsetDateTime::now_utc()) - .edge_location(var("FASTLY_POP").ok()) - .host(Some(config.datadog_host.clone())) - .http(http_details) - .ip(request.get_client_ip_addr()) - .method(Some(request.get_method().to_string())) - .url(request.get_url_str().into()) - .tls(tls_details) - .to_owned(); - - log_line -} - /// Handle the request /// /// This method handles the incoming request and returns a response for the client. It first ensures @@ -389,29 +339,11 @@ fn is_eligible_for_dynamic_compression(request: &Request, response: &Response) - is_compressible_content_type(&content_type) } -/// Collect data for the logs from the response -fn collect_response( - log_line: &mut LogLineV1Builder, - response: &Result, -) -> LogLineV1Builder { - if let Ok(response) = response { - log_line - .bytes(response.get_content_length()) - .content_type(response.get_content_type().map(|s| s.to_string())) - .status(Some(response.get_status().as_u16())) - .to_owned() - } else { - log_line.status(Some(500)).to_owned() - } -} - /// Finalize the builder and log the line fn build_and_send_log(log_line: LogLineV1Builder, config: &Config) { match log_line.build() { Ok(log) => { - let versioned_log = LogLine::V1(log); - let serialized_log = - serde_json::to_string(&versioned_log).expect("failed to serialize request log"); + let serialized_log = log.to_json(); for endpoint in [ config.datadog_request_logs_endpoint.as_str(), @@ -426,17 +358,6 @@ fn build_and_send_log(log_line: LogLineV1Builder, config: &Config) { }; } -fn http_version_to_string(version: Version) -> Option { - match version { - Version::HTTP_09 => Some("HTTP/0.9".into()), - Version::HTTP_10 => Some("HTTP/1.0".into()), - Version::HTTP_11 => Some("HTTP/1.1".into()), - Version::HTTP_2 => Some("HTTP/2".into()), - Version::HTTP_3 => Some("HTTP/3".into()), - _ => None, - } -} - #[cfg(test)] mod tests { use super::*; From 4b93e8c922822bf3aa251e776039904acea1c906 Mon Sep 17 00:00:00 2001 From: Denis Cornehl Date: Wed, 7 Oct 2026 09:38:49 +0200 Subject: [PATCH 2/2] terragrunt/crates-io: use get_repo_root() to reference module --- .../accounts/legacy/crates-io-prod/crates-io/terragrunt.hcl | 2 +- .../accounts/legacy/crates-io-staging/crates-io/terragrunt.hcl | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/terragrunt/accounts/legacy/crates-io-prod/crates-io/terragrunt.hcl b/terragrunt/accounts/legacy/crates-io-prod/crates-io/terragrunt.hcl index 9c338cdda..d79e4e6ab 100644 --- a/terragrunt/accounts/legacy/crates-io-prod/crates-io/terragrunt.hcl +++ b/terragrunt/accounts/legacy/crates-io-prod/crates-io/terragrunt.hcl @@ -1,5 +1,5 @@ terraform { - source = "../../../../modules//crates-io" + source = "${get_repo_root()}//terragrunt/modules/crates-io" # Marco removed the deployed ref because too annoying. If you need it, feel free to add it back # source = "git::../../../../..//terragrunt/modules/crates-io?ref=${trimspace(file("../deployed-ref"))}" } diff --git a/terragrunt/accounts/legacy/crates-io-staging/crates-io/terragrunt.hcl b/terragrunt/accounts/legacy/crates-io-staging/crates-io/terragrunt.hcl index 9a19568ba..7850a988b 100644 --- a/terragrunt/accounts/legacy/crates-io-staging/crates-io/terragrunt.hcl +++ b/terragrunt/accounts/legacy/crates-io-staging/crates-io/terragrunt.hcl @@ -1,5 +1,5 @@ terraform { - source = "../../../../modules//crates-io" + source = "${get_repo_root()}//terragrunt/modules/crates-io" } include {